Make default completer complete custom emoji

This commit is contained in:
Nicolas Werner 2023-05-25 21:50:54 +02:00
parent dd74bdc697
commit 51084748ee
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
12 changed files with 82 additions and 228 deletions

View file

@ -356,8 +356,6 @@ set(SRC_FILES
src/dialogs/ReCaptcha.h
# Emoji
src/emoji/EmojiModel.cpp
src/emoji/EmojiModel.h
src/emoji/Provider.cpp
src/emoji/Provider.h

View file

@ -91,11 +91,9 @@ Open username completer.
Open room completer.
*:*::
Open unicode emoji picker.
*~*::
Open custom emoji picker. Requires an image pack with custom emojis. Selecting
an emoji will add HTML code for the inline image into the input line.
Open the emoji picker. Unicode emoji are inserted directly. Custom emoji will
insert the HTML code for them into the input line. You can configure custom
emoji in the room settings.
== KEYBOARD SHORTCUTS

View file

@ -21,7 +21,7 @@ Control {
property int avatarHeight: 24
property int avatarWidth: 24
property int rowMargin: 0
property int rowSpacing: 5
property int rowSpacing: Nheko.paddingSmall
property alias count: listView.count
signal completionClicked(string completion)
@ -199,16 +199,35 @@ Control {
spacing: rowSpacing
Label {
visible: !!model.unicode
text: model.unicode
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
font: Settings.emojiFont
}
Avatar {
visible: !model.unicode
height: popup.avatarHeight
width: popup.avatarWidth
displayName: model.shortcode
//userid: model.shortcode
url: (model.url ? model.url : "").replace("mxc://", "image://MxcImage/")
enabled: false
crop: false
}
Label {
text: model.shortName
Layout.leftMargin: Nheko.paddingSmall
Layout.rightMargin: Nheko.paddingSmall
text: model.shortcode
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
}
Label {
text: "(" + model.packname + ")"
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.buttonText
}
}
}
@ -237,39 +256,6 @@ Control {
}
DelegateChoice {
roleValue: "customEmoji"
RowLayout {
id: del
anchors.centerIn: parent
spacing: rowSpacing
Avatar {
height: popup.avatarHeight
width: popup.avatarWidth
displayName: model.shortcode
//userid: model.shortcode
url: model.url.replace("mxc://", "image://MxcImage/")
enabled: false
crop: false
}
Label {
text: model.shortcode
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
}
Label {
text: "(" + model.packname + ")"
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.buttonText
}
}
}
DelegateChoice {
roleValue: "room"

View file

@ -169,8 +169,6 @@ Rectangle {
messageInput.openCompleter(selectionStart-1, "emoji");
} else if (lastChar == '#') {
messageInput.openCompleter(selectionStart-1, "roomAliases");
} else if (lastChar == "~") {
messageInput.openCompleter(selectionStart-1, "customEmoji");
} else if (lastChar == "/" && cursorPosition == 1) {
messageInput.openCompleter(selectionStart-1, "command");
}

View file

@ -6,14 +6,13 @@
#include "Cache_p.h"
#include "CompletionModelRoles.h"
#include "emoji/Provider.h"
CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId,
bool stickers,
QObject *parent)
CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId, QObject *parent)
: QAbstractListModel(parent)
, room_id(roomId)
{
auto packs = cache::client()->getImagePacks(room_id, stickers);
auto packs = cache::client()->getImagePacks(room_id, false);
for (const auto &pack : packs) {
QString packname =
@ -32,7 +31,7 @@ CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId,
int
CombinedImagePackModel::rowCount(const QModelIndex &) const
{
return (int)images.size();
return (int)(emoji::Provider::emoji.size() + images.size());
}
QHash<int, QByteArray>
@ -46,37 +45,61 @@ CombinedImagePackModel::roleNames() const
{Roles::ShortCode, "shortcode"},
{Roles::Body, "body"},
{Roles::PackName, "packname"},
{Roles::OriginalRow, "originalRow"},
{Roles::Unicode, "unicode"},
};
}
QVariant
CombinedImagePackModel::data(const QModelIndex &index, int role) const
{
using emoji::Provider;
if (hasIndex(index.row(), index.column(), index.parent())) {
if (index.row() < (int)emoji::Provider::emoji.size()) {
switch (role) {
case CompletionModel::CompletionRole:
case Roles::Unicode:
return emoji::Provider::emoji[index.row()].unicode();
case Qt::ToolTipRole:
return Provider::emoji[index.row()].shortName() + ", " +
Provider::emoji[index.row()].unicodeName();
case CompletionModel::SearchRole2:
case Roles::Body:
return Provider::emoji[index.row()].unicodeName();
case CompletionModel::SearchRole:
case Roles::ShortCode:
return Provider::emoji[index.row()].shortName();
case Roles::PackName:
return emoji::categoryToName(Provider::emoji[index.row()].category);
default:
return {};
}
} else {
int row = index.row() - static_cast<int>(emoji::Provider::emoji.size());
switch (role) {
case CompletionModel::CompletionRole:
return QStringLiteral(
"<img data-mx-emoticon height=\"32\" src=\"%1\" alt=\"%2\" title=\"%2\">")
.arg(QString::fromStdString(images[index.row()].image.url).toHtmlEscaped(),
!images[index.row()].image.body.empty()
? QString::fromStdString(images[index.row()].image.body)
: images[index.row()].shortcode);
.arg(QString::fromStdString(images[row].image.url).toHtmlEscaped(),
!images[row].image.body.empty()
? QString::fromStdString(images[row].image.body)
: images[row].shortcode);
case Roles::Url:
return QString::fromStdString(images[index.row()].image.url);
return QString::fromStdString(images[row].image.url);
case CompletionModel::SearchRole:
case Roles::ShortCode:
return images[index.row()].shortcode;
return images[row].shortcode;
case CompletionModel::SearchRole2:
case Roles::Body:
return QString::fromStdString(images[index.row()].image.body);
return QString::fromStdString(images[row].image.body);
case Roles::PackName:
return images[index.row()].packname;
case Roles::OriginalRow:
return index.row();
return images[row].packname;
case Roles::Unicode:
return QString();
default:
return {};
}
}
}
return {};
}

View file

@ -18,27 +18,14 @@ public:
ShortCode,
Body,
PackName,
OriginalRow,
Unicode,
};
CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
CombinedImagePackModel(const std::string &roomId, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
mtx::events::msc2545::PackImage imageAt(int row)
{
if (row < 0 || static_cast<size_t>(row) >= images.size())
return {};
return images.at(static_cast<size_t>(row)).image;
}
QString shortcodeAt(int row)
{
if (row < 0 || static_cast<size_t>(row) >= images.size())
return {};
return images.at(static_cast<size_t>(row)).shortcode;
}
private:
std::string room_id;

View file

@ -18,8 +18,8 @@ Q_DECLARE_METATYPE(TextEmoji)
Q_DECLARE_METATYPE(SectionDescription)
Q_DECLARE_METATYPE(QList<SectionDescription>)
static QString
categoryToName(emoji::Emoji::Category cat)
QString
emoji::categoryToName(emoji::Emoji::Category cat)
{
switch (cat) {
case emoji::Emoji::Category::People:

View file

@ -41,7 +41,6 @@
#include "UsersModel.h"
#include "Utils.h"
#include "dock/Dock.h"
#include "emoji/EmojiModel.h"
#include "emoji/Provider.h"
#include "encryption/DeviceVerificationFlow.h"
#include "encryption/SelfVerificationStatus.h"
@ -289,9 +288,6 @@ MainWindow::registerQmlTypes()
"FilteredCommunitiesModel",
QStringLiteral("Use Communities.filtered() to create a FilteredCommunitiesModel"));
qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel");
qmlRegisterUncreatableType<emoji::Emoji>(
"im.nheko.EmojiModel", 1, 0, "Emoji", QStringLiteral("Used by emoji models"));
qmlRegisterUncreatableType<MediaUpload>(
"im.nheko", 1, 0, "MediaUpload", QStringLiteral("MediaUploads can not be created in Qml"));
qmlRegisterUncreatableMetaObject(emoji::staticMetaObject,

View file

@ -1,78 +0,0 @@
// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "EmojiModel.h"
#include <Cache.h>
#include <MatrixClient.h>
#include "CompletionModelRoles.h"
using namespace emoji;
int
EmojiModel::categoryToIndex(int category)
{
auto dist = std::distance(
Provider::emoji.begin(),
std::lower_bound(Provider::emoji.begin(),
Provider::emoji.end(),
static_cast<Emoji::Category>(category),
[](const struct Emoji &e, Emoji::Category c) { return e.category < c; }));
return static_cast<int>(dist);
}
QHash<int, QByteArray>
EmojiModel::roleNames() const
{
static QHash<int, QByteArray> roles;
if (roles.isEmpty()) {
roles = QAbstractListModel::roleNames();
roles[static_cast<int>(EmojiModel::Roles::Unicode)] = QByteArrayLiteral("unicode");
roles[static_cast<int>(EmojiModel::Roles::ShortName)] = QByteArrayLiteral("shortName");
roles[static_cast<int>(EmojiModel::Roles::UnicodeName)] = QByteArrayLiteral("unicodeName");
roles[static_cast<int>(EmojiModel::Roles::Category)] = QByteArrayLiteral("category");
roles[static_cast<int>(EmojiModel::Roles::Emoji)] = QByteArrayLiteral("emoji");
}
return roles;
}
int
EmojiModel::rowCount(const QModelIndex &parent) const
{
return parent == QModelIndex() ? (int)Provider::emoji.size() : 0;
}
QVariant
EmojiModel::data(const QModelIndex &index, int role) const
{
if (hasIndex(index.row(), index.column(), index.parent())) {
switch (role) {
case Qt::DisplayRole:
case CompletionModel::CompletionRole:
case static_cast<int>(EmojiModel::Roles::Unicode):
return Provider::emoji[index.row()].unicode();
case Qt::ToolTipRole:
return Provider::emoji[index.row()].shortName() + ", " +
Provider::emoji[index.row()].unicodeName();
case CompletionModel::SearchRole2:
case static_cast<int>(EmojiModel::Roles::UnicodeName):
return Provider::emoji[index.row()].unicodeName();
case CompletionModel::SearchRole:
case static_cast<int>(EmojiModel::Roles::ShortName):
return Provider::emoji[index.row()].shortName();
case static_cast<int>(EmojiModel::Roles::Category):
return QVariant::fromValue(Provider::emoji[index.row()].category);
case static_cast<int>(EmojiModel::Roles::Emoji):
return QVariant::fromValue(Provider::emoji[index.row()]);
}
}
return {};
}

View file

@ -1,40 +0,0 @@
// SPDX-FileCopyrightText: Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QAbstractListModel>
#include <QSet>
#include <QSortFilterProxyModel>
#include <QVector>
#include "Provider.h"
namespace emoji {
/*
* Provides access to the emojis in Provider.h to QML
*/
class EmojiModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles
{
Unicode = Qt::UserRole, // unicode of emoji
Category, // category of emoji
ShortName, // shortext of the emoji
UnicodeName, // true unicode name of the emoji
Emoji, // Contains everything from the Emoji
};
using QAbstractListModel::QAbstractListModel;
Q_INVOKABLE int categoryToIndex(int category);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};
}

View file

@ -91,5 +91,7 @@ public:
static const std::array<Emoji, 3681> emoji;
};
QString
categoryToName(emoji::Emoji::Category cat);
} // namespace emoji
Q_DECLARE_METATYPE(emoji::Emoji)

View file

@ -27,7 +27,6 @@
#include "UserSettingsPage.h"
#include "UsersModel.h"
#include "Utils.h"
#include "emoji/EmojiModel.h"
#include "encryption/VerificationManager.h"
#include "voip/CallManager.h"
#include "voip/WebRTCSession.h"
@ -454,15 +453,10 @@ TimelineViewManager::completerFor(const QString &completerName, const QString &r
userModel->setParent(proxy);
return proxy;
} else if (completerName == QLatin1String("emoji")) {
auto emojiModel = new emoji::EmojiModel();
auto emojiModel = new CombinedImagePackModel(roomId.toStdString());
auto proxy = new CompletionProxyModel(emojiModel);
emojiModel->setParent(proxy);
return proxy;
} else if (completerName == QLatin1String("allemoji")) {
auto emojiModel = new emoji::EmojiModel();
auto proxy = new CompletionProxyModel(emojiModel, 1, static_cast<size_t>(-1) / 4);
emojiModel->setParent(proxy);
return proxy;
} else if (completerName == QLatin1String("room")) {
auto roomModel = new RoomsModel(false);
auto proxy = new CompletionProxyModel(roomModel, 4);
@ -473,22 +467,12 @@ TimelineViewManager::completerFor(const QString &completerName, const QString &r
auto proxy = new CompletionProxyModel(roomModel);
roomModel->setParent(proxy);
return proxy;
} else if (completerName == QLatin1String("stickers")) {
auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), true);
auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4);
stickerModel->setParent(proxy);
return proxy;
} else if (completerName == QLatin1String("emojigrid")) {
auto stickerModel = new GridImagePackModel(roomId.toStdString(), false);
return stickerModel;
} else if (completerName == QLatin1String("stickergrid")) {
auto stickerModel = new GridImagePackModel(roomId.toStdString(), true);
return stickerModel;
} else if (completerName == QLatin1String("customEmoji")) {
auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), false);
auto proxy = new CompletionProxyModel(stickerModel);
stickerModel->setParent(proxy);
return proxy;
} else if (completerName == QLatin1String("command")) {
auto commandCompleter = new CommandCompleter();
auto proxy = new CompletionProxyModel(commandCompleter);