diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e5198113..4fcd1677f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,7 +49,7 @@ set_package_properties(Qt${QT_MAJOR_VERSION} PROPERTIES TYPE REQUIRED PURPOSE "Basic application components" ) -find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons Sonnet) +find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons Sonnet ItemModels) set_package_properties(KF5 PROPERTIES TYPE REQUIRED PURPOSE "Basic application components" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 95ea78765..2b6e61cc2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -90,7 +90,7 @@ else() endif() target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR}) -target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::SonnetCore Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES}) +target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::SonnetCore KF5::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES}) if(TARGET QCoro5::Coro) target_link_libraries(neochat PUBLIC QCoro5::Coro) else() diff --git a/src/chatdocumenthandler.cpp b/src/chatdocumenthandler.cpp index 472ee6beb..9f13e3c82 100644 --- a/src/chatdocumenthandler.cpp +++ b/src/chatdocumenthandler.cpp @@ -235,7 +235,7 @@ void ChatDocumentHandler::complete(int index) m_room->mentions()->push_back({cursor, alias, 0, 0, alias}); m_highlighter->rehighlight(); } else if (m_completionModel->autoCompletionType() == ChatDocumentHandler::Emoji) { - auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::Text).toString(); + auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedText).toString(); auto text = m_room->chatBoxText(); auto at = text.lastIndexOf(QLatin1Char(':')); QTextCursor cursor(document()->textDocument()); diff --git a/src/completionmodel.cpp b/src/completionmodel.cpp index e166c293e..89606eeea 100644 --- a/src/completionmodel.cpp +++ b/src/completionmodel.cpp @@ -8,6 +8,7 @@ #include "chatdocumenthandler.h" #include "completionproxymodel.h" #include "customemojimodel.h" +#include "emojimodel.h" #include "neochatroom.h" #include "roomlistmodel.h" #include "userlistmodel.h" @@ -16,11 +17,14 @@ CompletionModel::CompletionModel(QObject *parent) : QAbstractListModel(parent) , m_filterModel(new CompletionProxyModel()) , m_userListModel(new UserListModel(this)) + , m_emojiModel(new KConcatenateRowsProxyModel(this)) { connect(this, &CompletionModel::textChanged, this, &CompletionModel::updateCompletion); connect(this, &CompletionModel::roomChanged, this, [this]() { m_userListModel->setRoom(m_room); }); + m_emojiModel->addSourceModel(&CustomEmojiModel::instance()); + m_emojiModel->addSourceModel(&EmojiModel::instance()); } QString CompletionModel::text() const @@ -90,11 +94,14 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const } if (m_autoCompletionType == ChatDocumentHandler::Emoji) { if (role == Text) { - return m_filterModel->data(filterIndex, CustomEmojiModel::Name); + return m_filterModel->data(filterIndex, CustomEmojiModel::DisplayRole); } if (role == Icon) { return m_filterModel->data(filterIndex, CustomEmojiModel::MxcUrl); } + if (role == ReplacedText) { + return m_filterModel->data(filterIndex, CustomEmojiModel::ReplacedTextRole); + } } return {}; @@ -140,7 +147,7 @@ void CompletionModel::updateCompletion() && (m_fullText.indexOf(QLatin1Char(':'), 1) == -1 || (m_fullText.indexOf(QLatin1Char(' ')) != -1 && m_fullText.indexOf(QLatin1Char(':'), 1) > m_fullText.indexOf(QLatin1Char(' '), 1)))) { m_autoCompletionType = ChatDocumentHandler::Emoji; - m_filterModel->setSourceModel(&CustomEmojiModel::instance()); + m_filterModel->setSourceModel(m_emojiModel); m_filterModel->setFilterRole(CustomEmojiModel::Name); m_filterModel->setSecondaryFilterRole(-1); m_filterModel->setFullText(m_fullText); diff --git a/src/completionmodel.h b/src/completionmodel.h index eb17faf68..097e759f7 100644 --- a/src/completionmodel.h +++ b/src/completionmodel.h @@ -5,6 +5,8 @@ #include +#include + #include "chatdocumenthandler.h" class CompletionProxyModel; @@ -64,4 +66,5 @@ private: UserListModel *m_userListModel; RoomListModel *m_roomListModel; + KConcatenateRowsProxyModel *m_emojiModel; }; diff --git a/src/customemojimodel.cpp b/src/customemojimodel.cpp index d17694a85..0dcce275f 100644 --- a/src/customemojimodel.cpp +++ b/src/customemojimodel.cpp @@ -134,6 +134,8 @@ QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const case Roles::ModelData: return QVariant::fromValue(Emoji(QStringLiteral("image://mxc/") + data.url.mid(6), data.name, true)); case Roles::Name: + case Roles::DisplayRole: + case Roles::ReplacedTextRole: return data.name; case Roles::ImageURL: return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6)); diff --git a/src/customemojimodel.h b/src/customemojimodel.h index 95c91607c..7b3be8812 100644 --- a/src/customemojimodel.h +++ b/src/customemojimodel.h @@ -19,10 +19,12 @@ class CustomEmojiModel : public QAbstractListModel public: enum Roles { - Name, + Name = Qt::DisplayRole, ImageURL, ModelData, // for emulating the regular emoji model's usage, otherwise the UI code would get too complicated - MxcUrl, + MxcUrl = 50, + DisplayRole = 51, + ReplacedTextRole = 52, }; Q_ENUM(Roles); diff --git a/src/emojimodel.cpp b/src/emojimodel.cpp index e59a6f5b1..b47df81de 100644 --- a/src/emojimodel.cpp +++ b/src/emojimodel.cpp @@ -39,10 +39,15 @@ QVariant EmojiModel::data(const QModelIndex &index, int role) const } auto emoji = category[row].value(); switch (role) { - case TextRole: - return emoji.shortName; + case ShortNameRole: + return QStringLiteral(":%1:").arg(emoji.shortName); case UnicodeRole: + case ReplacedTextRole: return emoji.unicode; + case InvalidRole: + return QStringLiteral("invalid"); + case DisplayRole: + return QStringLiteral("%2 :%1:").arg(emoji.shortName, emoji.unicode); } } return {}; @@ -50,7 +55,7 @@ QVariant EmojiModel::data(const QModelIndex &index, int role) const QHash EmojiModel::roleNames() const { - return {{TextRole, "text"}, {UnicodeRole, "unicode"}}; + return {{ShortNameRole, "shortName"}, {UnicodeRole, "unicode"}}; } QMultiHash EmojiModel::_tones = { diff --git a/src/emojimodel.h b/src/emojimodel.h index 8089525fe..8096378fb 100644 --- a/src/emojimodel.h +++ b/src/emojimodel.h @@ -51,11 +51,18 @@ class EmojiModel : public QAbstractListModel Q_PROPERTY(QVariantList categories READ categories CONSTANT) public: - explicit EmojiModel(QObject *parent = nullptr); + static EmojiModel &instance() + { + static EmojiModel _instance; + return _instance; + } enum RoleNames { - TextRole = Qt::DisplayRole, + ShortNameRole = Qt::DisplayRole, UnicodeRole, + InvalidRole = 50, + DisplayRole = 51, + ReplacedTextRole = 52, }; Q_ENUM(RoleNames); @@ -100,4 +107,5 @@ private: // TODO: Port away from QSettings QSettings m_settings; + EmojiModel(QObject *parent = nullptr); }; diff --git a/src/main.cpp b/src/main.cpp index 013cf97c3..5cccbc468 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -203,7 +203,7 @@ int main(int argc, char *argv[]) qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "FileType", &fileTypeSingleton); qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login); qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "UrlHelper", &urlHelper); - qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "EmojiModel", new EmojiModel(&app)); + qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "EmojiModel", &EmojiModel::instance()); #ifdef QUOTIENT_07 qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "AccountRegistry", &Quotient::Accounts); #else