From 50d67467dce9f7ed2e63822b6fbba075b327d61a Mon Sep 17 00:00:00 2001 From: Jeff Date: Mon, 10 Aug 2015 00:49:16 -0400 Subject: [PATCH] Username Completer in server room Adds QCompleter in server room and a setting to enable/disable it. --- cockatrice/src/dlg_settings.cpp | 13 ++- cockatrice/src/dlg_settings.h | 1 + cockatrice/src/settingscache.cpp | 8 ++ cockatrice/src/settingscache.h | 4 + cockatrice/src/tab_room.cpp | 182 +++++++++++++++++++++++++++++-- cockatrice/src/tab_room.h | 32 +++++- 6 files changed, 221 insertions(+), 19 deletions(-) diff --git a/cockatrice/src/dlg_settings.cpp b/cockatrice/src/dlg_settings.cpp index 02e84457..6c9bd25e 100644 --- a/cockatrice/src/dlg_settings.cpp +++ b/cockatrice/src/dlg_settings.cpp @@ -573,6 +573,9 @@ MessagesSettingsPage::MessagesSettingsPage() { chatMentionCheckBox.setChecked(settingsCache->getChatMention()); connect(&chatMentionCheckBox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setChatMention(int))); + + chatMentionCompleterCheckbox.setChecked(settingsCache->getChatMentionCompleter()); + connect(&chatMentionCompleterCheckbox, SIGNAL(stateChanged(int)), settingsCache, SLOT(setChatMentionCompleter(int))); ignoreUnregUsersMainChat.setChecked(settingsCache->getIgnoreUnregisteredUsers()); ignoreUnregUserMessages.setChecked(settingsCache->getIgnoreUnregisteredUserMessages()); @@ -605,11 +608,12 @@ MessagesSettingsPage::MessagesSettingsPage() chatGrid->addWidget(&chatMentionCheckBox, 0, 0); chatGrid->addWidget(&invertMentionForeground, 0, 1); chatGrid->addWidget(mentionColor, 0, 2); - chatGrid->addWidget(&ignoreUnregUsersMainChat, 1, 0); + chatGrid->addWidget(&chatMentionCompleterCheckbox, 1, 0); + chatGrid->addWidget(&ignoreUnregUsersMainChat, 2, 0); chatGrid->addWidget(&hexLabel, 1, 2); - chatGrid->addWidget(&ignoreUnregUserMessages, 2, 0); - chatGrid->addWidget(&messagePopups, 3, 0); - chatGrid->addWidget(&mentionPopups, 4, 0); + chatGrid->addWidget(&ignoreUnregUserMessages, 3, 0); + chatGrid->addWidget(&messagePopups, 4, 0); + chatGrid->addWidget(&mentionPopups, 5, 0); chatGroupBox = new QGroupBox; chatGroupBox->setLayout(chatGrid); @@ -734,6 +738,7 @@ void MessagesSettingsPage::retranslateUi() chatGroupBox->setTitle(tr("Chat settings")); highlightGroupBox->setTitle(tr("Custom alert words")); chatMentionCheckBox.setText(tr("Enable chat mentions")); + chatMentionCompleterCheckbox.setText(tr("Enable mention completer")); messageShortcuts->setTitle(tr("In-game message macros")); ignoreUnregUsersMainChat.setText(tr("Ignore chat room messages sent by unregistered users")); ignoreUnregUserMessages.setText(tr("Ignore private messages sent by unregistered users")); diff --git a/cockatrice/src/dlg_settings.h b/cockatrice/src/dlg_settings.h index e3c8b158..56329f8c 100644 --- a/cockatrice/src/dlg_settings.h +++ b/cockatrice/src/dlg_settings.h @@ -168,6 +168,7 @@ private: QAction *aAdd; QAction *aRemove; QCheckBox chatMentionCheckBox; + QCheckBox chatMentionCompleterCheckbox; QCheckBox invertMentionForeground; QCheckBox invertHighlightForeground; QCheckBox ignoreUnregUsersMainChat; diff --git a/cockatrice/src/settingscache.cpp b/cockatrice/src/settingscache.cpp index b46746a5..dd7b34fd 100644 --- a/cockatrice/src/settingscache.cpp +++ b/cockatrice/src/settingscache.cpp @@ -77,6 +77,7 @@ SettingsCache::SettingsCache() minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 5).toInt(); tapAnimation = settings->value("cards/tapanimation", true).toBool(); chatMention = settings->value("chat/mention", true).toBool(); + chatMentionCompleter = settings->value("chat/mentioncompleter", true).toBool(); chatMentionForeground = settings->value("chat/mentionforeground", true).toBool(); chatHighlightForeground = settings->value("chat/highlightforeground", true).toBool(); chatMentionColor = settings->value("chat/mentioncolor", "A6120D").toString(); @@ -360,6 +361,13 @@ void SettingsCache::setChatMention(int _chatMention) { settings->setValue("chat/mention", chatMention); } +void SettingsCache::setChatMentionCompleter(const int _enableMentionCompleter) +{ + chatMentionCompleter = _enableMentionCompleter; + settings->setValue("chat/mentioncompleter", chatMentionCompleter); + emit chatMentionCompleterChanged(); +} + void SettingsCache::setChatMentionForeground(int _chatMentionForeground) { chatMentionForeground = _chatMentionForeground; settings->setValue("chat/mentionforeground", chatMentionForeground); diff --git a/cockatrice/src/settingscache.h b/cockatrice/src/settingscache.h index c3e5f86b..28d32e4b 100644 --- a/cockatrice/src/settingscache.h +++ b/cockatrice/src/settingscache.h @@ -43,6 +43,7 @@ signals: void ignoreUnregisteredUserMessagesChanged(); void pixmapCacheSizeChanged(int newSizeInMBs); void masterVolumeChanged(int value); + void chatMentionCompleterChanged(); private: QSettings *settings; @@ -65,6 +66,7 @@ private: int minPlayersForMultiColumnLayout; bool tapAnimation; bool chatMention; + bool chatMentionCompleter; QString chatMentionColor; QString chatHighlightColor; bool chatMentionForeground; @@ -136,6 +138,7 @@ public: int getMinPlayersForMultiColumnLayout() const { return minPlayersForMultiColumnLayout; } bool getTapAnimation() const { return tapAnimation; } bool getChatMention() const { return chatMention; } + bool getChatMentionCompleter() const { return chatMentionCompleter; } bool getChatMentionForeground() const { return chatMentionForeground; } bool getChatHighlightForeground() const { return chatHighlightForeground; } bool getZoneViewSortByName() const { return zoneViewSortByName; } @@ -218,6 +221,7 @@ public slots: void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout); void setTapAnimation(int _tapAnimation); void setChatMention(int _chatMention); + void setChatMentionCompleter(int _chatMentionCompleter); void setChatMentionForeground(int _chatMentionForeground); void setChatHighlightForeground(int _chatHighlightForeground); void setZoneViewSortByName(int _zoneViewSortByName); diff --git a/cockatrice/src/tab_room.cpp b/cockatrice/src/tab_room.cpp index 545901ce..b49b6604 100644 --- a/cockatrice/src/tab_room.cpp +++ b/cockatrice/src/tab_room.cpp @@ -11,6 +11,14 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include "tab_supervisor.h" #include "tab_room.h" #include "tab_userlists.h" @@ -51,8 +59,9 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, AbstractClient *_client, ServerI connect(chatView, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString))); connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString))); connect(chatView, SIGNAL(addMentionTag(QString)), this, SLOT(addMentionTag(QString))); + connect(settingsCache, SIGNAL(chatMentionCompleterChanged()), this, SLOT(actCompleterChanged())); sayLabel = new QLabel; - sayEdit = new QLineEdit; + sayEdit = new CustomLineEdit; sayLabel->setBuddy(sayEdit); connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage())); @@ -103,13 +112,26 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, AbstractClient *_client, ServerI setLayout(hbox); const int userListSize = info.user_list_size(); - for (int i = 0; i < userListSize; ++i) + for (int i = 0; i < userListSize; ++i){ userList->processUserInfo(info.user_list(i), true); + autocompleteUserList.append("@" + QString::fromStdString(info.user_list(i).name())); + } userList->sortItems(); const int gameListSize = info.game_list_size(); for (int i = 0; i < gameListSize; ++i) gameSelector->processGameInfo(info.game_list(i)); + + completer = new QCompleter(autocompleteUserList, sayEdit); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setMaxVisibleItems(5); + + #if QT_VERSION >= 0x050000 + completer->setFilterMode(Qt::MatchStartsWith); + #endif + + sayEdit->setCompleter(completer); + actCompleterChanged(); } TabRoom::~TabRoom() @@ -119,7 +141,7 @@ TabRoom::~TabRoom() void TabRoom::retranslateUi() { - gameSelector->retranslateUi(); + gameSelector->retranslateUi(); chatView->retranslateUi(); userList->retranslateUi(); sayLabel->setText(tr("&Say:")); @@ -166,16 +188,20 @@ QString TabRoom::sanitizeHtml(QString dirty) const void TabRoom::sendMessage() { - if (sayEdit->text().isEmpty()) - return; + if (sayEdit->text().isEmpty()){ + return; + }else if (completer->popup()->isVisible()){ + completer->popup()->hide(); + return; + }else{ + Command_RoomSay cmd; + cmd.set_message(sayEdit->text().toStdString()); - Command_RoomSay cmd; - cmd.set_message(sayEdit->text().toStdString()); - - PendingCommand *pend = prepareRoomCommand(cmd); - connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(sayFinished(const Response &))); - sendRoomCommand(pend); - sayEdit->clear(); + PendingCommand *pend = prepareRoomCommand(cmd); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(sayFinished(const Response &))); + sendRoomCommand(pend); + sayEdit->clear(); + } } void TabRoom::sayFinished(const Response &response) @@ -200,6 +226,11 @@ void TabRoom::actOpenChatSettings() { settings.exec(); } +void TabRoom::actCompleterChanged() +{ + settingsCache->getChatMentionCompleter() ? completer->setCompletionRole(2) : completer->setCompletionRole(1); +} + void TabRoom::processRoomEvent(const RoomEvent &event) { switch (static_cast(getPbExtension(event))) { @@ -222,11 +253,17 @@ void TabRoom::processJoinRoomEvent(const Event_JoinRoom &event) { userList->processUserInfo(event.user_info(), true); userList->sortItems(); + if (!autocompleteUserList.contains("@" + QString::fromStdString(event.user_info().name()))){ + autocompleteUserList << "@" + QString::fromStdString(event.user_info().name()); + sayEdit->updateCompleterModel(autocompleteUserList); + } } void TabRoom::processLeaveRoomEvent(const Event_LeaveRoom &event) { userList->deleteUser(QString::fromStdString(event.name())); + autocompleteUserList.removeOne("@" + QString::fromStdString(event.name())); + sayEdit->updateCompleterModel(autocompleteUserList); } void TabRoom::processRoomSayEvent(const Event_RoomSay &event) @@ -259,3 +296,124 @@ void TabRoom::sendRoomCommand(PendingCommand *pend) { client->sendCommand(pend); } + +CustomLineEdit::CustomLineEdit(QWidget *parent) + : QLineEdit(parent) +{ +} + +void CustomLineEdit::focusOutEvent(QFocusEvent * e){ + QLineEdit::focusOutEvent(e); + if (c->popup()->isVisible()){ + //Remove Popup + c->popup()->hide(); + //Truncate the line to last space or whole string + QString textValue = text(); + int lastIndex = textValue.length(); + int lastWordStartIndex = textValue.lastIndexOf(" ") + 1; + int leftShift = qMin(lastIndex, lastWordStartIndex); + setText(textValue.left(leftShift)); + //Insert highlighted line from popup + insert(c->completionModel()->index(c->popup()->currentIndex().row(), 0).data().toString() + " "); + //Set focus back to the textbox since tab was pressed + setFocus(); + } +} + +void CustomLineEdit::keyPressEvent(QKeyEvent * event) +{ + switch (event->key()){ + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Escape: + if (c->popup()->isVisible()){ + event->ignore(); + //Remove Popup + c->popup()->hide(); + //Truncate the line to last space or whole string + QString textValue = text(); + int lastIndexof = textValue.lastIndexOf(" "); + QString finalString = textValue.left(lastIndexof); + //Add a space if there's a word + if (finalString != "") + finalString += " "; + setText(finalString); + return; + } + break; + case Qt::Key_Space: + if (c->popup()->isVisible()){ + event->ignore(); + //Remove Popup + c->popup()->hide(); + //Truncate the line to last space or whole string + QString textValue = text(); + int lastIndex = textValue.length(); + int lastWordStartIndex = textValue.lastIndexOf(" ") + 1; + int leftShift = qMin(lastIndex, lastWordStartIndex); + setText(textValue.left(leftShift)); + //Insert highlighted line from popup + insert(c->completionModel()->index(c->popup()->currentIndex().row(), 0).data().toString() + " "); + return; + } + break; + default: + break; + } + + QLineEdit::keyPressEvent(event); + //Wait until the first character after @ + if (!c || text().right(1).contains("@")) + return; + + //Set new completion prefix + c->setCompletionPrefix(cursorWord(text())); + if (c->completionPrefix().length() < 1){ + c->popup()->hide(); + return; + } + + //Draw completion box + QRect cr = cursorRect(); + cr.setWidth(c->popup()->sizeHintForColumn(0) + c->popup()->verticalScrollBar()->sizeHint().width()); + c->complete(cr); + + //Select first item in the completion popup + QItemSelectionModel* sm = new QItemSelectionModel(c->completionModel()); + c->popup()->setSelectionModel(sm); + sm->select(c->completionModel()->index(0, 0), QItemSelectionModel::ClearAndSelect); + sm->setCurrentIndex(c->completionModel()->index(0, 0), QItemSelectionModel::NoUpdate); +} + +QString CustomLineEdit::cursorWord(const QString &line) const +{ + return line.mid(line.left(cursorPosition()).lastIndexOf(" ") + 1, + cursorPosition() - line.left(cursorPosition()).lastIndexOf(" ") - 1); +} + +void CustomLineEdit::insertCompletion(QString arg) +{ + QString s_arg = arg + " "; + setText(text().replace(text().left(cursorPosition()).lastIndexOf(" ") + 1, + cursorPosition() - text().left(cursorPosition()).lastIndexOf(" ") - 1, s_arg)); +} + +void CustomLineEdit::setCompleter(QCompleter* completer) +{ + c = completer; + c->setWidget(this); + connect(c, SIGNAL(activated(QString)),this, SLOT(insertCompletion(QString))); +} + +void CustomLineEdit::updateCompleterModel(QStringList completionList) +{ + if (!c || c->popup()->isVisible()) + return; + + QStringListModel *model; + model = (QStringListModel*)(c->model()); + if (model == NULL) + model = new QStringListModel(); + QStringList updatedList = completionList; + model->setStringList(updatedList); +} \ No newline at end of file diff --git a/cockatrice/src/tab_room.h b/cockatrice/src/tab_room.h index 377f357f..98a74d8f 100644 --- a/cockatrice/src/tab_room.h +++ b/cockatrice/src/tab_room.h @@ -4,6 +4,9 @@ #include "tab.h" #include #include +#include +#include +#include namespace google { namespace protobuf { class Message; } } class AbstractClient; @@ -13,6 +16,7 @@ class ChatView; class QLineEdit; class QPushButton; class QTextTable; +class QCompleter; class RoomEvent; class ServerInfo_Room; class ServerInfo_Game; @@ -24,6 +28,7 @@ class GameSelector; class Response; class PendingCommand; class ServerInfo_User; +class CustomLineEdit; class TabRoom : public Tab { Q_OBJECT @@ -38,14 +43,17 @@ private: UserList *userList; ChatView *chatView; QLabel *sayLabel; - QLineEdit *sayEdit; + CustomLineEdit *sayEdit; QGroupBox *chatGroupBox; QMenu *roomMenu; QAction *aLeaveRoom; QAction *aOpenChatSettings; - QAction * aClearChat; + QAction *aClearChat; QString sanitizeHtml(QString dirty) const; + + QStringList autocompleteUserList; + QCompleter *completer; signals: void roomClosing(TabRoom *tab); void openMessageDialog(const QString &userName, bool focus); @@ -59,7 +67,8 @@ private slots: void addMentionTag(QString mentionTag); void focusTab(); void actShowMentionPopup(QString &sender); - + void actCompleterChanged(); + void processListGamesEvent(const Event_ListGames &event); void processJoinRoomEvent(const Event_JoinRoom &event); void processLeaveRoomEvent(const Event_LeaveRoom &event); @@ -81,4 +90,21 @@ public: void sendRoomCommand(PendingCommand *pend); }; +class CustomLineEdit : public QLineEdit +{ + Q_OBJECT +private: + QString cursorWord(const QString& line) const; + QCompleter* c; +private slots: + void insertCompletion(QString); +protected: + void keyPressEvent(QKeyEvent * event); + void focusOutEvent(QFocusEvent * e); +public: + explicit CustomLineEdit(QWidget *parent = 0); + void setCompleter(QCompleter*); + void updateCompleterModel(QStringList); +}; + #endif