From f22107c8abb20f7bbb9730e200053bc6c63d68ab Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 10 Aug 2023 15:37:37 +0200 Subject: [PATCH] Colorful emoji in reaction Use ICU to determine if the string contains only emojis --- CMakeLists.txt | 12 +++++++--- src/CMakeLists.txt | 3 ++- src/models/reactionmodel.cpp | 43 +++++++++++++++++++++++++++++++----- src/models/reactionmodel.h | 2 +- src/qml/ReactionDelegate.qml | 28 ++++++++++++++--------- 5 files changed, 67 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d53ff6ef8..6a33367a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,9 +63,9 @@ set_package_properties(KF6 PROPERTIES PURPOSE "Basic application components" ) set_package_properties(KF6Kirigami2 PROPERTIES - TYPE REQUIRED - PURPOSE "Kirigami application UI framework" - ) + TYPE REQUIRED + PURPOSE "Kirigami application UI framework" +) find_package(KF6KirigamiAddons 0.7.2 REQUIRED) if(ANDROID) @@ -81,6 +81,12 @@ else() TYPE RUNTIME ) ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0) + + find_package(ICU 61.0 COMPONENTS uc) + set_package_properties(ICU PROPERTIES + TYPE REQUIRED + PURPOSE "Unicode library" + ) endif() if (NOT ANDROID AND NOT WIN32 AND NOT APPLE) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5d2cf98f3..673cd242c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -319,9 +319,10 @@ if(NOT ANDROID) else() target_sources(neochat PRIVATE trayicon.cpp trayicon.h) endif() - target_link_libraries(neochat PUBLIC KF6::ConfigWidgets KF6::WindowSystem) + target_link_libraries(neochat PUBLIC KF6::ConfigWidgets KF6::WindowSystem ICU::uc) target_compile_definitions(neochat PUBLIC -DHAVE_COLORSCHEME) target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM) + target_compile_definitions(neochat PUBLIC -DHAVE_ICU) endif() if (NOT ANDROID AND NOT WIN32 AND NOT APPLE) diff --git a/src/models/reactionmodel.cpp b/src/models/reactionmodel.cpp index 4db97b87b..30bc0287e 100644 --- a/src/models/reactionmodel.cpp +++ b/src/models/reactionmodel.cpp @@ -4,6 +4,12 @@ #include "reactionmodel.h" #include +#ifdef HAVE_ICU +#include +#include +#include +#include +#endif #include @@ -29,11 +35,38 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const const auto &reaction = m_reactions.at(index.row()); - if (role == TextRole) { + const auto isEmoji = [](const QString &text) { +#ifdef HAVE_ICU + QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text); + int from = 0; + while (finder.toNextBoundary() != -1) { + auto to = finder.position(); + if (text[from].isSpace()) { + from = to; + continue; + } + + auto first = text.mid(from, to - from).toUcs4()[0]; + if (!u_hasBinaryProperty(first, UCHAR_EMOJI_PRESENTATION)) { + return false; + } + from = to; + } + return true; +#else + return false; +#endif + }; + + const auto reactionText = isEmoji(reaction.reaction) + ? QStringLiteral("") + reaction.reaction + QStringLiteral("") + : reaction.reaction; + + if (role == TextContentRole) { if (reaction.authors.count() > 1) { - return QStringLiteral("%1 %2").arg(reaction.reaction, QString::number(reaction.authors.count())); + return QStringLiteral("%1 %2").arg(reactionText, QString::number(reaction.authors.count())); } else { - return reaction.reaction; + return reactionText; } } @@ -64,7 +97,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const "%2 reacted with %3", reaction.authors.count(), text, - reaction.reaction); + reactionText); return text; } @@ -101,7 +134,7 @@ void ReactionModel::setReactions(QList reactions) QHash ReactionModel::roleNames() const { return { - {TextRole, "text"}, + {TextContentRole, "textContent"}, {ReactionRole, "reaction"}, {ToolTipRole, "toolTip"}, {AuthorsRole, "authors"}, diff --git a/src/models/reactionmodel.h b/src/models/reactionmodel.h index a9f2df796..20b1d0780 100644 --- a/src/models/reactionmodel.h +++ b/src/models/reactionmodel.h @@ -34,7 +34,7 @@ public: * @brief Defines the model roles. */ enum Roles { - TextRole = Qt::DisplayRole, /**< The text to show in the reaction. */ + TextContentRole = Qt::DisplayRole, /**< The text to show in the reaction. */ ReactionRole, /**< The reaction emoji. */ ToolTipRole, /**< The tool tip to show for the reaction. */ AuthorsRole, /**< The list of authors who sent the given reaction. */ diff --git a/src/qml/ReactionDelegate.qml b/src/qml/ReactionDelegate.qml index 844dee623..f3c4346a2 100644 --- a/src/qml/ReactionDelegate.qml +++ b/src/qml/ReactionDelegate.qml @@ -28,39 +28,45 @@ Flow { id: reactionRepeater delegate: QQC2.AbstractButton { - width: Math.max(reactionTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 4, height) + id: reactionDelegate + + required property string textContent + required property string reaction + required property string toolTip + required property bool hasLocalUser + + width: Math.max(contentItem.implicitWidth + leftPadding + rightPadding, height) + height: Math.round(Kirigami.Units.gridUnit * 1.5) contentItem: QQC2.Label { id: reactionLabel horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - text: model.text - - TextMetrics { - id: reactionTextMetrics - text: reactionLabel.text - } + text: reactionDelegate.textContent + background: null + wrapMode: TextEdit.NoWrap + textFormat: Text.RichText } padding: Kirigami.Units.smallSpacing background: Kirigami.ShadowedRectangle { - color: model.hasLocalUser ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor + color: reactionDelegate.hasLocalUser ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Kirigami.Theme.View radius: height / 2 shadow { size: Kirigami.Units.smallSpacing - color: !model.hasLocalUser ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10) + color: !reactionDelegate.hasLocalUser ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10) } } - onClicked: reactionClicked(model.reaction) + onClicked: reactionClicked(reactionDelegate.reaction) hoverEnabled: true QQC2.ToolTip.visible: hovered - QQC2.ToolTip.text: model.toolTip + QQC2.ToolTip.text: reactionDelegate.toolTip } } }