Make default completer complete custom emoji

emojigrid
Nicolas Werner 2 years ago
parent dd74bdc697
commit 51084748ee
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
  1. 2
      CMakeLists.txt
  2. 8
      man/nheko.1.adoc
  3. 52
      resources/qml/Completer.qml
  4. 2
      resources/qml/MessageInput.qml
  5. 79
      src/CombinedImagePackModel.cpp
  6. 17
      src/CombinedImagePackModel.h
  7. 4
      src/GridImagePackModel.cpp
  8. 4
      src/MainWindow.cpp
  9. 78
      src/emoji/EmojiModel.cpp
  10. 40
      src/emoji/EmojiModel.h
  11. 2
      src/emoji/Provider.h
  12. 18
      src/timeline/TimelineViewManager.cpp

@ -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

@ -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

@ -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,37 +199,32 @@ Control {
spacing: rowSpacing
Label {
visible: !!model.unicode
text: model.unicode
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
font: Settings.emojiFont
}
Label {
text: model.shortName
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
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
}
}
}
DelegateChoice {
roleValue: "command"
RowLayout {
id: del
anchors.centerIn: parent
spacing: rowSpacing
Label {
text: model.name
Layout.leftMargin: Nheko.paddingSmall
Layout.rightMargin: Nheko.paddingSmall
text: model.shortcode
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
font.bold: true
}
Label {
text: model.description
text: "(" + model.packname + ")"
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.buttonText
}
@ -238,7 +233,7 @@ Control {
}
DelegateChoice {
roleValue: "customEmoji"
roleValue: "command"
RowLayout {
id: del
@ -246,23 +241,14 @@ Control {
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
text: model.name
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.text
font.bold: true
}
Label {
text: "(" + model.packname + ")"
text: model.description
color: model.index == popup.currentIndex ? Nheko.colors.highlightedText : Nheko.colors.buttonText
}

@ -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");
}

@ -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,36 +45,60 @@ 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())) {
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);
case Roles::Url:
return QString::fromStdString(images[index.row()].image.url);
case CompletionModel::SearchRole:
case Roles::ShortCode:
return images[index.row()].shortcode;
case CompletionModel::SearchRole2:
case Roles::Body:
return QString::fromStdString(images[index.row()].image.body);
case Roles::PackName:
return images[index.row()].packname;
case Roles::OriginalRow:
return index.row();
default:
return {};
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[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[row].image.url);
case CompletionModel::SearchRole:
case Roles::ShortCode:
return images[row].shortcode;
case CompletionModel::SearchRole2:
case Roles::Body:
return QString::fromStdString(images[row].image.body);
case Roles::PackName:
return images[row].packname;
case Roles::Unicode:
return QString();
default:
return {};
}
}
}
return {};

@ -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;

@ -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:

@ -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,

@ -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 {};
}

@ -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;
};
}

@ -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)

@ -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);

Loading…
Cancel
Save