Add new QML-based emoji picker (work in progress)
This is necessary to support having a picker within QML. Eventually, this should replace the existing widget-based one.
This commit is contained in:
parent
8984661187
commit
ee4dcef90f
16 changed files with 15558 additions and 4710 deletions
|
|
@ -585,9 +585,7 @@ ChatPage::resetUI()
|
|||
void
|
||||
ChatPage::reactMessage(const QString &id)
|
||||
{
|
||||
view_manager_->queueReactionMessage(current_room_,
|
||||
id,
|
||||
"👀");
|
||||
view_manager_->queueReactionMessage(current_room_, id, "👀");
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -59,12 +59,12 @@ Category::Category(QString category, std::vector<Emoji> emoji, QWidget *parent)
|
|||
emojiListView_->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
|
||||
for (const auto &e : emoji) {
|
||||
data_->unicode = e.unicode;
|
||||
data_->setUnicode(e.unicode());
|
||||
|
||||
auto item = new QStandardItem;
|
||||
item->setSizeHint(QSize(emojiSize, emojiSize));
|
||||
|
||||
QVariant unicode(data_->unicode);
|
||||
QVariant unicode(data_->unicode());
|
||||
item->setData(unicode.toString(), Qt::UserRole);
|
||||
|
||||
itemModel_->appendRow(item);
|
||||
|
|
|
|||
110
src/emoji/EmojiModel.cpp
Normal file
110
src/emoji/EmojiModel.cpp
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#include "EmojiModel.h"
|
||||
|
||||
#include <Cache.h>
|
||||
#include <MatrixClient.h>
|
||||
|
||||
using namespace emoji;
|
||||
|
||||
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::Category)] =
|
||||
QByteArrayLiteral("category");
|
||||
roles[static_cast<int>(EmojiModel::Roles::Emoji)] = QByteArrayLiteral("emoji");
|
||||
}
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
int
|
||||
EmojiModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent == QModelIndex() ? Provider::emoji.count() : 0;
|
||||
}
|
||||
|
||||
QVariant
|
||||
EmojiModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case static_cast<int>(EmojiModel::Roles::Unicode):
|
||||
return Provider::emoji[index.row()].unicode();
|
||||
|
||||
case Qt::ToolTipRole:
|
||||
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 {};
|
||||
}
|
||||
|
||||
EmojiProxyModel::EmojiProxyModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{}
|
||||
|
||||
EmojiProxyModel::~EmojiProxyModel() {}
|
||||
|
||||
Emoji::Category
|
||||
EmojiProxyModel::category() const
|
||||
{
|
||||
return category_;
|
||||
}
|
||||
|
||||
void
|
||||
EmojiProxyModel::setCategory(Emoji::Category cat)
|
||||
{
|
||||
if (category_ == cat) {
|
||||
return;
|
||||
}
|
||||
|
||||
category_ = cat;
|
||||
emit categoryChanged();
|
||||
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
QString
|
||||
EmojiProxyModel::filter() const
|
||||
{
|
||||
return filterRegExp().pattern();
|
||||
}
|
||||
|
||||
void
|
||||
EmojiProxyModel::setFilter(const QString &filter)
|
||||
{
|
||||
if (filterRegExp().pattern() == filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFilterWildcard(filter);
|
||||
emit filterChanged();
|
||||
}
|
||||
|
||||
bool
|
||||
EmojiProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
const Emoji emoji = index.data(static_cast<int>(EmojiModel::Roles::Emoji)).value<Emoji>();
|
||||
|
||||
// TODO: Add favorites / recently used
|
||||
if (category_ != Emoji::Category::Search) {
|
||||
return emoji.category() == category_;
|
||||
}
|
||||
|
||||
return filterRegExp().isEmpty() ? true : filterRegExp().indexIn(emoji.shortName()) != -1;
|
||||
}
|
||||
68
src/emoji/EmojiModel.h
Normal file
68
src/emoji/EmojiModel.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#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
|
||||
Emoji, // Contains everything from the Emoji
|
||||
};
|
||||
|
||||
using QAbstractListModel::QAbstractListModel;
|
||||
|
||||
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;
|
||||
|
||||
// TODO: Need a signal for when an emoji is selected
|
||||
// public signals:
|
||||
// void emojiSelected(const QString &emoji);
|
||||
};
|
||||
|
||||
class EmojiProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(
|
||||
emoji::Emoji::Category category READ category WRITE setCategory NOTIFY categoryChanged)
|
||||
Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged)
|
||||
|
||||
public:
|
||||
explicit EmojiProxyModel(QObject *parent = nullptr);
|
||||
~EmojiProxyModel() override;
|
||||
|
||||
Emoji::Category category() const;
|
||||
void setCategory(Emoji::Category cat);
|
||||
|
||||
QString filter() const;
|
||||
void setFilter(const QString &filter);
|
||||
|
||||
signals:
|
||||
void categoryChanged();
|
||||
void filterChanged();
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
private:
|
||||
Emoji::Category category_ = Emoji::Category::Search;
|
||||
emoji::Provider emoji_provider_;
|
||||
};
|
||||
|
||||
}
|
||||
19741
src/emoji/Provider.cpp
19741
src/emoji/Provider.cpp
File diff suppressed because it is too large
Load diff
|
|
@ -17,22 +17,61 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <vector>
|
||||
|
||||
namespace emoji {
|
||||
|
||||
struct Emoji
|
||||
class Emoji
|
||||
{
|
||||
// Unicode code.
|
||||
QString unicode;
|
||||
// Keyboard shortcut e.g :emoji:
|
||||
QString shortname;
|
||||
Q_GADGET
|
||||
|
||||
Q_PROPERTY(const QString &unicode READ unicode CONSTANT)
|
||||
Q_PROPERTY(const QString &shortName READ shortName CONSTANT)
|
||||
Q_PROPERTY(emoji::Emoji::Category category READ category CONSTANT)
|
||||
|
||||
public:
|
||||
enum class Category
|
||||
{
|
||||
People,
|
||||
Nature,
|
||||
Food,
|
||||
Activity,
|
||||
Travel,
|
||||
Objects,
|
||||
Symbols,
|
||||
Flags,
|
||||
Search
|
||||
};
|
||||
Q_ENUM(Category)
|
||||
|
||||
Emoji(const QString &unicode = {},
|
||||
const QString &shortName = {},
|
||||
Emoji::Category cat = Emoji::Category::Search)
|
||||
: unicode_(unicode)
|
||||
, shortName_(shortName)
|
||||
, category_(cat)
|
||||
{}
|
||||
|
||||
inline const QString &unicode() const { return unicode_; }
|
||||
inline const QString &shortName() const { return shortName_; }
|
||||
inline Emoji::Category category() const { return category_; }
|
||||
inline void setUnicode(const QString &unicode) { unicode_ = unicode; }
|
||||
|
||||
private:
|
||||
QString unicode_;
|
||||
QString shortName_;
|
||||
Emoji::Category category_;
|
||||
};
|
||||
|
||||
class Provider
|
||||
{
|
||||
public:
|
||||
// all emoji for QML purposes
|
||||
static const QVector<Emoji> emoji;
|
||||
static const std::vector<Emoji> people;
|
||||
static const std::vector<Emoji> nature;
|
||||
static const std::vector<Emoji> food;
|
||||
|
|
@ -42,4 +81,6 @@ public:
|
|||
static const std::vector<Emoji> symbols;
|
||||
static const std::vector<Emoji> flags;
|
||||
};
|
||||
|
||||
} // namespace emoji
|
||||
Q_DECLARE_METATYPE(emoji::Emoji)
|
||||
|
|
|
|||
|
|
@ -1384,28 +1384,26 @@ struct SendMessageVisitor
|
|||
void operator()(const mtx::events::RoomEvent<mtx::events::msg::Reaction> &msg)
|
||||
|
||||
{
|
||||
|
||||
QString txn_id_qstr = txn_id_qstr_;
|
||||
TimelineModel *model = model_;
|
||||
http::client()->send_room_message<mtx::events::msg::Reaction, mtx::events::EventType::Reaction>(
|
||||
model->room_id_.toStdString(),
|
||||
txn_id_qstr.toStdString(),
|
||||
msg.content,
|
||||
[txn_id_qstr, model](const mtx::responses::EventId &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
const int status_code =
|
||||
static_cast<int>(err->status_code);
|
||||
nhlog::net()->warn("[{}] failed to send message: {} {}",
|
||||
txn_id_qstr.toStdString(),
|
||||
err->matrix_error.error,
|
||||
status_code);
|
||||
emit model->messageFailed(txn_id_qstr);
|
||||
}
|
||||
emit model->messageSent(
|
||||
txn_id_qstr, QString::fromStdString(res.event_id.to_string()));
|
||||
});
|
||||
|
||||
http::client()
|
||||
->send_room_message<mtx::events::msg::Reaction, mtx::events::EventType::Reaction>(
|
||||
model->room_id_.toStdString(),
|
||||
txn_id_qstr.toStdString(),
|
||||
msg.content,
|
||||
[txn_id_qstr, model](const mtx::responses::EventId &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err) {
|
||||
const int status_code = static_cast<int>(err->status_code);
|
||||
nhlog::net()->warn("[{}] failed to send message: {} {}",
|
||||
txn_id_qstr.toStdString(),
|
||||
err->matrix_error.error,
|
||||
status_code);
|
||||
emit model->messageFailed(txn_id_qstr);
|
||||
}
|
||||
emit model->messageSent(
|
||||
txn_id_qstr, QString::fromStdString(res.event_id.to_string()));
|
||||
});
|
||||
}
|
||||
|
||||
QString txn_id_qstr_;
|
||||
|
|
|
|||
|
|
@ -126,7 +126,8 @@ class TimelineModel : public QAbstractListModel
|
|||
int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
|
||||
Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY
|
||||
typingUsersChanged)
|
||||
Q_PROPERTY(QString reaction READ reaction WRITE setReaction NOTIFY reactionChanged RESET resetReaction)
|
||||
Q_PROPERTY(QString reaction READ reaction WRITE setReaction NOTIFY reactionChanged RESET
|
||||
resetReaction)
|
||||
Q_PROPERTY(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply)
|
||||
Q_PROPERTY(
|
||||
bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged)
|
||||
|
|
@ -230,7 +231,7 @@ public slots:
|
|||
if (!reaction_.isEmpty()) {
|
||||
reaction_ = "";
|
||||
emit reactionChanged(reaction_);
|
||||
}
|
||||
}
|
||||
}
|
||||
QString reply() const { return reply_; }
|
||||
void setReply(QString newReply)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
#include "MxcImageProvider.h"
|
||||
#include "UserSettingsPage.h"
|
||||
#include "dialogs/ImageOverlay.h"
|
||||
#include "emoji/EmojiModel.h"
|
||||
#include "emoji/Provider.h"
|
||||
|
||||
Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
|
||||
|
||||
|
|
@ -72,6 +74,12 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin
|
|||
qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice");
|
||||
qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser");
|
||||
qRegisterMetaType<mtx::events::collections::TimelineEvents>();
|
||||
qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel");
|
||||
qmlRegisterType<emoji::EmojiProxyModel>("im.nheko.EmojiModel", 1, 0, "EmojiProxyModel");
|
||||
qmlRegisterUncreatableType<QAbstractItemModel>(
|
||||
"im.nheko.EmojiModel", 1, 0, "QAbstractItemModel", "Used by proxy models");
|
||||
qmlRegisterUncreatableType<emoji::Emoji>(
|
||||
"im.nheko.EmojiModel", 1, 0, "Emoji", "Used by emoji models");
|
||||
|
||||
#ifdef USE_QUICK_VIEW
|
||||
view = new QQuickView();
|
||||
|
|
@ -290,7 +298,7 @@ TimelineViewManager::queueReactionMessage(const QString &roomId,
|
|||
mtx::events::msg::Reaction reaction;
|
||||
reaction.relates_to.rel_type = mtx::common::RelationType::Annotation;
|
||||
reaction.relates_to.event_id = reactedEvent.toStdString();
|
||||
reaction.relates_to.key = reactionKey.toStdString();
|
||||
reaction.relates_to.key = reactionKey.toStdString();
|
||||
|
||||
auto model = models.value(roomId);
|
||||
model->sendMessage(reaction);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <QHash>
|
||||
#include <QQuickView>
|
||||
#include <QQuickWidget>
|
||||
#include <QSharedPointer>
|
||||
|
|
@ -12,6 +13,8 @@
|
|||
#include "Logging.h"
|
||||
#include "TimelineModel.h"
|
||||
#include "Utils.h"
|
||||
#include "emoji/EmojiModel.h"
|
||||
#include "emoji/Provider.h"
|
||||
|
||||
class MxcImageProvider;
|
||||
class BlurhashProvider;
|
||||
|
|
@ -102,7 +105,8 @@ private:
|
|||
|
||||
QHash<QString, QSharedPointer<TimelineModel>> models;
|
||||
TimelineModel *timeline_ = nullptr;
|
||||
bool isInitialSync_ = true;
|
||||
|
||||
bool isInitialSync_ = true;
|
||||
|
||||
QSharedPointer<UserSettings> settings;
|
||||
QHash<QString, QColor> userColors;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue