From 8168b7dde8a1b6f37d1d481fe33e8f58eddec3b0 Mon Sep 17 00:00:00 2001 From: Julien Wadel Date: Tue, 7 Feb 2023 15:40:02 +0100 Subject: [PATCH] Support emojis display. --- CHANGELOG.md | 1 + .../src/components/settings/SettingsModel.cpp | 20 +++ .../src/components/settings/SettingsModel.hpp | 11 ++ linphone-app/src/utils/Constants.cpp | 1 + linphone-app/src/utils/Constants.hpp | 1 + linphone-app/src/utils/Utils.cpp | 96 +++++++--- linphone-app/src/utils/Utils.hpp | 5 +- .../Linphone/Account/AccountStatus.qml | 4 +- .../modules/Linphone/Chat/ChatTextMessage.qml | 2 +- .../ui/modules/Linphone/Contact/Avatar.qml | 4 +- .../Linphone/Contact/ContactDescription.qml | 170 ++++++++++-------- linphone-app/ui/views/App/Main/Contacts.qml | 4 +- 12 files changed, 209 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02944f146..4d17f0178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - VFS Encryption - File viewer in chats (Image/Animated Image/Video/Texts) with the option to export the file. - Accept/decline CLI commands. +- Colored Emojis with its own font family. ## 5.0.11 - udnefined diff --git a/linphone-app/src/components/settings/SettingsModel.cpp b/linphone-app/src/components/settings/SettingsModel.cpp index dc551eefc..9f906f68a 100644 --- a/linphone-app/src/components/settings/SettingsModel.cpp +++ b/linphone-app/src/components/settings/SettingsModel.cpp @@ -1468,6 +1468,26 @@ void SettingsModel::setTextMessageFontSize(const int& size){ mConfig->setInt(UiSection, "text_message_font_size", size); emit textMessageFontSizeChanged(size); } + +QFont SettingsModel::getEmojiFont() const{ + QString family = Utils::coreStringToAppString(mConfig->getString(UiSection, "emoji_font", Utils::appStringToCoreString(QFont(Constants::DefaultEmojiFont).family()))); + int pointSize = getTextMessageFontSize(); + return QFont(family,pointSize); +} + +void SettingsModel::setEmojiFont(const QFont& font){ + mConfig->setString(UiSection, "emoji_font", Utils::appStringToCoreString(font.family())); + emit emojiFontChanged(font); +} + +int SettingsModel::getEmojiFontSize() const{ + return mConfig->getInt(UiSection, "emoji_font_size", 10); +} + +void SettingsModel::setEmojiFontSize(const int& size){ + mConfig->setInt(UiSection, "emoji_font_size", size); + emit emojiFontSizeChanged(size); +} QString SettingsModel::getSavedScreenshotsFolder () const { return QDir::cleanPath( diff --git a/linphone-app/src/components/settings/SettingsModel.hpp b/linphone-app/src/components/settings/SettingsModel.hpp index 5c01789d5..40b8ba162 100644 --- a/linphone-app/src/components/settings/SettingsModel.hpp +++ b/linphone-app/src/components/settings/SettingsModel.hpp @@ -198,6 +198,8 @@ class SettingsModel : public QObject { Q_PROPERTY(QFont textMessageFont READ getTextMessageFont WRITE setTextMessageFont NOTIFY textMessageFontChanged) Q_PROPERTY(int textMessageFontSize READ getTextMessageFontSize WRITE setTextMessageFontSize NOTIFY textMessageFontSizeChanged) + Q_PROPERTY(QFont emojiFont READ getEmojiFont WRITE setEmojiFont NOTIFY emojiFontChanged) + Q_PROPERTY(int emojiFontSize READ getEmojiFontSize WRITE setEmojiFontSize NOTIFY emojiFontSizeChanged) Q_PROPERTY(QString remoteProvisioning READ getRemoteProvisioning WRITE setRemoteProvisioning NOTIFY remoteProvisioningChanged) Q_PROPERTY(QString flexiAPIUrl READ getFlexiAPIUrl WRITE setFlexiAPIUrl NOTIFY flexiAPIUrlChanged) @@ -556,6 +558,12 @@ public: int getTextMessageFontSize() const; void setTextMessageFontSize(const int& size); + QFont getEmojiFont() const; + void setEmojiFont(const QFont& font); + + int getEmojiFontSize() const; + void setEmojiFontSize(const int& size); + QString getSavedScreenshotsFolder () const; void setSavedScreenshotsFolder (const QString &folder); @@ -785,6 +793,9 @@ signals: void textMessageFontChanged(const QFont& font); void textMessageFontSizeChanged(const int& size); + void emojiFontChanged(const QFont& font); + void emojiFontSizeChanged(const int& size); + void savedScreenshotsFolderChanged (const QString &folder); void savedCallsFolderChanged (const QString &folder); void downloadFolderChanged (const QString &folder); diff --git a/linphone-app/src/utils/Constants.cpp b/linphone-app/src/utils/Constants.cpp index 1d6191af5..70c285385 100644 --- a/linphone-app/src/utils/Constants.cpp +++ b/linphone-app/src/utils/Constants.cpp @@ -23,6 +23,7 @@ constexpr char Constants::AssistantViewName[]; constexpr char Constants::ApplicationMinimalQtVersion[]; constexpr char Constants::DefaultFont[]; +constexpr char Constants::DefaultEmojiFont[]; constexpr char Constants::QtDomain[]; constexpr size_t Constants::MaxLogsCollectionSize; diff --git a/linphone-app/src/utils/Constants.hpp b/linphone-app/src/utils/Constants.hpp index 9106f1c1a..482bb90ec 100644 --- a/linphone-app/src/utils/Constants.hpp +++ b/linphone-app/src/utils/Constants.hpp @@ -38,6 +38,7 @@ public: static constexpr char DefaultLocale[] = "en"; static constexpr char DefaultFont[] = "Noto Sans"; + static constexpr char DefaultEmojiFont[] = "Noto Color Emoji"; static constexpr size_t MaxLogsCollectionSize = 10485760*5; // 50MB. diff --git a/linphone-app/src/utils/Utils.cpp b/linphone-app/src/utils/Utils.cpp index 573f8c2ae..b2b5969e1 100644 --- a/linphone-app/src/utils/Utils.cpp +++ b/linphone-app/src/utils/Utils.cpp @@ -659,6 +659,44 @@ QString Utils::getFileChecksum(const QString& filePath){ } return QString(); } +bool Utils::codepointIsEmoji(uint code){ + return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) || + (code >= 0x1f000 && code <= 0x1faff) || code == 0x200d || code == 0xfe0f; +} + +QString Utils::replaceEmoji(const QString &body){ + QString fmtBody = ""; + QVector utf32_string = body.toUcs4(); + + bool insideFontBlock = false; + for (auto &code : utf32_string) { + if (Utils::codepointIsEmoji(code)) { + if (!insideFontBlock) { + fmtBody += QString("getSettingsModel()->getEmojiFont().family() + "\">"); + insideFontBlock = true; + } + } else { + if (insideFontBlock) { + fmtBody += ""; + insideFontBlock = false; + } + } + fmtBody += QString::fromUcs4(&code, 1); + } + if (insideFontBlock) { + fmtBody += ""; + } + return fmtBody; +} + +bool Utils::isOnlyEmojis(const QString& text){ + QVector utf32_string = text.toUcs4(); + for (auto &code : utf32_string) + if( !Utils::codepointIsEmoji(code)) + return false; + return true; +} QString Utils::encodeTextToQmlRichFormat(const QString& text, const QVariantMap& options){ @@ -667,39 +705,43 @@ QString Utils::encodeTextToQmlRichFormat(const QString& text, const QVariantMap& QStringList imageFormat; for(auto format : QImageReader::supportedImageFormats()) imageFormat.append(QString::fromLatin1(format).toUpper()); - auto iriParsed = UriTools::parseIri(text); - for(int i = 0 ; i < iriParsed.size() ; ++i){ - QString iri = iriParsed[i].second.replace('&', "&") - .replace('<', "\u2063<") - .replace('>', "\u2063>") - .replace('"', """) - .replace('\'', "'"); - if(!iriParsed[i].first) - formattedText.append(iri); - else{ - QString uri = iriParsed[i].second.left(3) == "www" ? "http://"+iriParsed[i].second : iriParsed[i].second ; - int extIndex = iriParsed[i].second.lastIndexOf('.'); - QString ext; - if( extIndex >= 0) - ext = iriParsed[i].second.mid(extIndex+1).toUpper(); - if(imageFormat.contains(ext.toLatin1())){// imagesHeight is not used because of bugs on display (blank image if set without width) - images += "', "\u2063>") + .replace('"', """) + .replace('\'', "'"); + if(!iriParsed[i].first) + formattedText.append(iri); + else{ + QString uri = iriParsed[i].second.left(3) == "www" ? "http://"+iriParsed[i].second : iriParsed[i].second ; + int extIndex = iriParsed[i].second.lastIndexOf('.'); + QString ext; + if( extIndex >= 0) + ext = iriParsed[i].second.mid(extIndex+1).toUpper(); + if(imageFormat.contains(ext.toLatin1())){// imagesHeight is not used because of bugs on display (blank image if set without width) + images += ""; - }else - formattedText.append( "" + iri + ""); + ) + " src=\"" + iriParsed[i].second + "\" />"; + }else + formattedText.append( "" + iri + ""); + } } } if(images != "") images = "
" + images +"
"; - return images + "

" + formattedText.join("") + "

"; + return images + "

" + replaceEmoji(formattedText.join("")) + "

"; } QString Utils::getFileContent(const QString& filePath){ diff --git a/linphone-app/src/utils/Utils.hpp b/linphone-app/src/utils/Utils.hpp index 75ae727ca..908330c50 100644 --- a/linphone-app/src/utils/Utils.hpp +++ b/linphone-app/src/utils/Utils.hpp @@ -70,7 +70,10 @@ public: Q_INVOKABLE QSize getImageSize(const QString& url); Q_INVOKABLE static QPoint getCursorPosition(); Q_INVOKABLE static QString getFileChecksum(const QString& filePath); - Q_INVOKABLE static QString encodeTextToQmlRichFormat(const QString& text, const QVariantMap& options); + static bool codepointIsEmoji(uint code); + static QString replaceEmoji(const QString &body); + Q_INVOKABLE static bool isOnlyEmojis(const QString& text); + Q_INVOKABLE static QString encodeTextToQmlRichFormat(const QString& text, const QVariantMap& options = QVariantMap()); Q_INVOKABLE static QString getFileContent(const QString& filePath); //---------------------------------------------------------------------------------- diff --git a/linphone-app/ui/modules/Linphone/Account/AccountStatus.qml b/linphone-app/ui/modules/Linphone/Account/AccountStatus.qml index c9c16314d..a3931e9d5 100644 --- a/linphone-app/ui/modules/Linphone/Account/AccountStatus.qml +++ b/linphone-app/ui/modules/Linphone/Account/AccountStatus.qml @@ -6,6 +6,7 @@ import Common 1.0 import Linphone 1.0 import Linphone.Styles 1.0 +import UtilsCpp 1.0 // ============================================================================= Item { @@ -72,7 +73,8 @@ Item { elide: Text.ElideRight font.bold: true font.pointSize: AccountStatusStyle.username.pointSize - text: AccountSettingsModel.username + text: UtilsCpp.encodeTextToQmlRichFormat(AccountSettingsModel.username) + textFormat: Text.RichText verticalAlignment: Text.AlignBottom } Item { diff --git a/linphone-app/ui/modules/Linphone/Chat/ChatTextMessage.qml b/linphone-app/ui/modules/Linphone/Chat/ChatTextMessage.qml index 794e63cab..561434079 100644 --- a/linphone-app/ui/modules/Linphone/Chat/ChatTextMessage.qml +++ b/linphone-app/ui/modules/Linphone/Chat/ChatTextMessage.qml @@ -40,7 +40,7 @@ TextEdit { readOnly: true selectByMouse: true font.family: customFont.family - font.pointSize: Units.dp * customFont.pointSize + font.pointSize: Units.dp * customFont.pointSize * (UtilsCpp.isOnlyEmojis(contentModel.text) ? 4 : 1 ) text: visible ? UtilsCpp.encodeTextToQmlRichFormat(contentModel.text, { imagesHeight: ChatStyle.entry.message.images.height, diff --git a/linphone-app/ui/modules/Linphone/Contact/Avatar.qml b/linphone-app/ui/modules/Linphone/Contact/Avatar.qml index b7d8e7887..acc1aee00 100644 --- a/linphone-app/ui/modules/Linphone/Contact/Avatar.qml +++ b/linphone-app/ui/modules/Linphone/Contact/Avatar.qml @@ -33,7 +33,7 @@ Item { } function _computeInitials () { - return UtilsCpp.getInitials(username); + return UtilsCpp.encodeTextToQmlRichFormat(UtilsCpp.getInitials(username)); } // --------------------------------------------------------------------------- @@ -67,8 +67,8 @@ Item { return AvatarStyle.initials.pointSize * (width || 1) } - text: _computeInitials() + textFormat: Text.RichText visible: roundedImage.status !== Image.Ready && !avatar.isPhoneNumber && avatar.isOneToOne } diff --git a/linphone-app/ui/modules/Linphone/Contact/ContactDescription.qml b/linphone-app/ui/modules/Linphone/Contact/ContactDescription.qml index 6c4b37a43..8b515724e 100644 --- a/linphone-app/ui/modules/Linphone/Contact/ContactDescription.qml +++ b/linphone-app/ui/modules/Linphone/Contact/ContactDescription.qml @@ -1,9 +1,11 @@ import QtQuick 2.7 +import QtQuick.Layouts 1.3 import Linphone 1.0 import Linphone.Styles 1.0 import Common 1.0 +import UtilsCpp 1.0 // ============================================================================= Column { @@ -31,53 +33,62 @@ Column { signal titleClicked() // --------------------------------------------------------------------------- - - TextEdit { - id: title - property string fullText - anchors.horizontalCenter: (horizontalTextAlignment == Text.AlignHCenter ? parent.horizontalCenter : undefined) - color: titleColor - font.family: SettingsModel.textMessageFont.family - font.weight: contactDescriptionStyle.title.weight - font.pointSize: contactDescriptionStyle.title.pointSize - horizontalAlignment: horizontalTextAlignment - verticalAlignment: (subtitle.visible?Text.AlignBottom:Text.AlignVCenter) - width: Math.min(parent.width-statusWidth, titleImplicitWidthWorkaround.implicitWidth) + Item{ + anchors.left: parent.left + anchors.right: parent.right height: (parent.height-parent.topPadding-parent.bottomPadding)/parent.visibleChildren.length - - text: metrics.elidedText - onActiveFocusChanged: deselect(); - readOnly: true - selectByMouse: true - - Text{// Workaround to get implicitWidth from text without eliding - id: titleImplicitWidthWorkaround - text: title.fullText + RowLayout{ + anchors.fill: parent + TextEdit { + id: title + property string fullText + Layout.fillWidth: true + color: titleColor font.family: SettingsModel.textMessageFont.family - font.weight: title.font.weight - font.pointSize: title.font.pointSize - visible: false + font.weight: contactDescriptionStyle.title.weight + font.pointSize: contactDescriptionStyle.title.pointSize + textFormat: Text.RichText + horizontalAlignment: horizontalTextAlignment + verticalAlignment: (subtitle.visible?Text.AlignBottom:Text.AlignVCenter) + text: UtilsCpp.encodeTextToQmlRichFormat(metrics.elidedText, {noLink:true}) + onActiveFocusChanged: deselect(); + readOnly: true + selectByMouse: true + Layout.preferredHeight: parent.height + + Text{// Workaround to get implicitWidth from text without eliding + id: titleImplicitWidthWorkaround + text: title.fullText + font.family: SettingsModel.textMessageFont.family + font.weight: title.font.weight + font.pointSize: title.font.pointSize + textFormat: Text.RichText + visible: false + } + + TextMetrics { + id: metrics + font: title.font + text: title.fullText + elideWidth: title.width + elide: Qt.ElideRight + } + } + Text{ + id:status + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + //anchors.top:parent.top + //anchors.bottom : parent.bottom + //anchors.left:parent.right + //anchors.leftMargin:5 + verticalAlignment: Text.AlignVCenter + visible: text != '' + text : '' + color: contactDescriptionStyle.title.status.colorModel.color + font.pointSize: contactDescriptionStyle.title.status.pointSize + font.italic : true } - - TextMetrics { - id: metrics - font: title.font - text: title.fullText - elideWidth: title.width - elide: Qt.ElideRight - } - Text{ - id:status - anchors.top:parent.top - anchors.bottom : parent.bottom - anchors.left:parent.right - anchors.leftMargin:5 - verticalAlignment: Text.AlignVCenter - visible: text != '' - text : '' - color: contactDescriptionStyle.title.status.colorModel.color - font.pointSize: contactDescriptionStyle.title.status.pointSize - font.italic : true } MouseArea{ anchors.fill:parent @@ -85,40 +96,45 @@ Column { onClicked: titleClicked() } } - - TextEdit { - id:subtitle - property string fullText - anchors.horizontalCenter: (horizontalTextAlignment == Text.AlignHCenter ? parent.horizontalCenter : undefined) - color: subtitleColor - font.family: SettingsModel.textMessageFont.family - font.weight: contactDescriptionStyle.subtitle.weight - font.pointSize: contactDescriptionStyle.subtitle.pointSize - horizontalAlignment: horizontalTextAlignment - verticalAlignment: (title.visible?Text.AlignTop:Text.AlignVCenter) - width: Math.min(parent.width-statusWidth, subtitleImplicitWidthWorkaround.implicitWidth) + Item{ + anchors.left: parent.left + anchors.right: parent.right height: (parent.height-parent.topPadding-parent.bottomPadding)/parent.visibleChildren.length - visible: text != '' - - text: subtitleMetrics.elidedText - onActiveFocusChanged: deselect(); - readOnly: true - selectByMouse: true - Text{// Workaround to get implicitWidth from text without eliding - id: subtitleImplicitWidthWorkaround - text: subtitle.fullText - font.weight: subtitle.font.weight - font.pointSize: subtitle.font.pointSize - visible: false - } - - TextMetrics { - id: subtitleMetrics - font: subtitle.font - text: subtitle.fullText - elideWidth: subtitle.width - elide: Qt.ElideRight + visible: subtitle.fullText != '' + TextEdit { + id:subtitle + property string fullText + anchors.fill: parent + color: subtitleColor + font.family: SettingsModel.textMessageFont.family + font.weight: contactDescriptionStyle.subtitle.weight + font.pointSize: contactDescriptionStyle.subtitle.pointSize + textFormat: Text.RichText + horizontalAlignment: horizontalTextAlignment + verticalAlignment: (title.visible?Text.AlignTop:Text.AlignVCenter) + + text: UtilsCpp.encodeTextToQmlRichFormat(subtitleMetrics.elidedText, {noLink:true}) + + onActiveFocusChanged: deselect(); + readOnly: true + selectByMouse: true + Text{// Workaround to get implicitWidth from text without eliding + id: subtitleImplicitWidthWorkaround + text: subtitle.fullText + font.weight: subtitle.font.weight + font.pointSize: subtitle.font.pointSize + visible: false + textFormat: Text.RichText + } + + TextMetrics { + id: subtitleMetrics + font: subtitle.font + text: subtitle.fullText + elideWidth: subtitle.width + elide: Qt.ElideRight + } } } - } + diff --git a/linphone-app/ui/views/App/Main/Contacts.qml b/linphone-app/ui/views/App/Main/Contacts.qml index e4222e9d7..1d1274ba3 100644 --- a/linphone-app/ui/views/App/Main/Contacts.qml +++ b/linphone-app/ui/views/App/Main/Contacts.qml @@ -290,7 +290,9 @@ ColumnLayout { pointSize: ContactsStyle.contact.username.pointSize } font.family: SettingsModel.textMessageFont.family - text: $modelData.vcard.username + + text: UtilsCpp.encodeTextToQmlRichFormat($modelData.vcard.username) + textFormat: Text.RichText verticalAlignment: Text.AlignVCenter }