mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 03:00:46 +03:00
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
|
@ -243,6 +243,7 @@ set(SRC_FILES
|
||||||
|
|
||||||
# Emoji
|
# Emoji
|
||||||
src/emoji/Category.cpp
|
src/emoji/Category.cpp
|
||||||
|
src/emoji/EmojiModel.cpp
|
||||||
src/emoji/ItemDelegate.cpp
|
src/emoji/ItemDelegate.cpp
|
||||||
src/emoji/Panel.cpp
|
src/emoji/Panel.cpp
|
||||||
src/emoji/PickButton.cpp
|
src/emoji/PickButton.cpp
|
||||||
|
@ -447,9 +448,11 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||||
|
|
||||||
# Emoji
|
# Emoji
|
||||||
src/emoji/Category.h
|
src/emoji/Category.h
|
||||||
|
src/emoji/EmojiModel.h
|
||||||
src/emoji/ItemDelegate.h
|
src/emoji/ItemDelegate.h
|
||||||
src/emoji/Panel.h
|
src/emoji/Panel.h
|
||||||
src/emoji/PickButton.h
|
src/emoji/PickButton.h
|
||||||
|
src/emoji/Provider.h
|
||||||
|
|
||||||
# Timeline
|
# Timeline
|
||||||
src/timeline/ReactionsModel.h
|
src/timeline/ReactionsModel.h
|
||||||
|
|
|
@ -6,6 +6,7 @@ import QtQuick.Window 2.2
|
||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
|
||||||
import "./delegates"
|
import "./delegates"
|
||||||
|
import "./emoji"
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
@ -71,17 +72,16 @@ MouseArea {
|
||||||
Layout.preferredHeight: 16
|
Layout.preferredHeight: 16
|
||||||
width: 16
|
width: 16
|
||||||
}
|
}
|
||||||
ImageButton {
|
EmojiButton {
|
||||||
visible: timelineSettings.buttons
|
visible: timelineSettings.buttons
|
||||||
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
Layout.alignment: Qt.AlignRight | Qt.AlignTop
|
||||||
Layout.preferredHeight: 16
|
Layout.preferredHeight: 16
|
||||||
width: 16
|
width: 16
|
||||||
id: reactButton
|
id: reactButton
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
image: ":/icons/icons/ui/smile.png"
|
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: qsTr("React")
|
ToolTip.text: qsTr("React")
|
||||||
onClicked: chat.model.reactAction(model.id)
|
// onClicked: chat.model.reactAction(model.id)
|
||||||
}
|
}
|
||||||
ImageButton {
|
ImageButton {
|
||||||
visible: timelineSettings.buttons
|
visible: timelineSettings.buttons
|
||||||
|
|
27
resources/qml/emoji/EmojiButton.qml
Normal file
27
resources/qml/emoji/EmojiButton.qml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import QtQuick 2.10
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import im.nheko 1.0
|
||||||
|
import im.nheko.EmojiModel 1.0
|
||||||
|
|
||||||
|
import "../"
|
||||||
|
|
||||||
|
ImageButton {
|
||||||
|
property var colors: currentActivePalette
|
||||||
|
|
||||||
|
image: ":/icons/icons/ui/smile.png"
|
||||||
|
id: emojiButton
|
||||||
|
onClicked: emojiPopup.open()
|
||||||
|
|
||||||
|
EmojiPicker {
|
||||||
|
id: emojiPopup
|
||||||
|
x: Math.round((emojiButton.width - width) / 2)
|
||||||
|
y: emojiButton.height
|
||||||
|
width: 7 * 52
|
||||||
|
height: 6 * 52
|
||||||
|
colors: emojiButton.colors
|
||||||
|
model: EmojiProxyModel {
|
||||||
|
category: Emoji.Category.People
|
||||||
|
sourceModel: EmojiModel {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
176
resources/qml/emoji/EmojiPicker.qml
Normal file
176
resources/qml/emoji/EmojiPicker.qml
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 2.9
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
|
||||||
|
import im.nheko 1.0
|
||||||
|
import im.nheko.EmojiModel 1.0
|
||||||
|
|
||||||
|
import "../"
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
property var colors
|
||||||
|
property alias model: gridView.model
|
||||||
|
property var textArea
|
||||||
|
property string emojiCategory: "people"
|
||||||
|
|
||||||
|
id: emojiPopup
|
||||||
|
|
||||||
|
margins: 0
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
focus: true
|
||||||
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
// Search field
|
||||||
|
TextField {
|
||||||
|
id: emojiSearch
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.preferredWidth: parent.width - 4
|
||||||
|
visible: emojiPopup.model.category === Emoji.Category.Search
|
||||||
|
placeholderText: qsTr("Search")
|
||||||
|
selectByMouse: true
|
||||||
|
rightPadding: clearSearch.width
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: searchTimer
|
||||||
|
interval: 350 // tweak as needed?
|
||||||
|
onTriggered: emojiPopup.model.filter = emojiSearch.text
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
id: clearSearch
|
||||||
|
anchors {
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
// clear the default hover effects.
|
||||||
|
background: Item {}
|
||||||
|
visible: emojiSearch.text !== ''
|
||||||
|
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? colors.highlight : colors.buttonText)
|
||||||
|
focusPolicy: Qt.NoFocus
|
||||||
|
onClicked: emojiSearch.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
onTextChanged: searchTimer.restart()
|
||||||
|
onVisibleChanged: if (visible) forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// emoji grid
|
||||||
|
GridView {
|
||||||
|
id: gridView
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
cellWidth: 52
|
||||||
|
cellHeight: 52
|
||||||
|
|
||||||
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
// Individual emoji
|
||||||
|
delegate: AbstractButton {
|
||||||
|
width: 48
|
||||||
|
height: 48
|
||||||
|
|
||||||
|
contentItem: Text {
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
font.pointSize: 36
|
||||||
|
text: model.unicode
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: hovered ? colors.highlight : 'transparent'
|
||||||
|
radius: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
ToolTip.text: model.shortName
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
// TODO: emit a signal and maybe add favorites at some point?
|
||||||
|
//onClicked: textArea.insert(textArea.cursorPosition, modelData.unicode)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separator
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 2
|
||||||
|
|
||||||
|
color: emojiPopup.colors.highlight
|
||||||
|
}
|
||||||
|
|
||||||
|
// Category picker row
|
||||||
|
Row {
|
||||||
|
Repeater {
|
||||||
|
model: ListModel {
|
||||||
|
// TODO: Would like to get 'simple' icons for the categories
|
||||||
|
ListElement { label: "😏"; category: Emoji.Category.People }
|
||||||
|
ListElement { label: "🌲"; category: Emoji.Category.Nature }
|
||||||
|
ListElement { label: "🍛"; category: Emoji.Category.Food }
|
||||||
|
ListElement { label: "🚁"; category: Emoji.Category.Activity }
|
||||||
|
ListElement { label: "🚅"; category: Emoji.Category.Travel }
|
||||||
|
ListElement { label: "💡"; category: Emoji.Category.Objects }
|
||||||
|
ListElement { label: "🔣"; category: Emoji.Category.Symbols }
|
||||||
|
ListElement { label: "🏁"; category: Emoji.Category.Flags }
|
||||||
|
ListElement { label: "🔍"; category: Emoji.Category.Search }
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: AbstractButton {
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
|
||||||
|
contentItem: Text {
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
font.pointSize: 30
|
||||||
|
text: model.label
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: emojiPopup.model.category === model.category ? colors.highlight : 'transparent'
|
||||||
|
radius: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverEnabled: true
|
||||||
|
ToolTip.text: {
|
||||||
|
switch (model.category) {
|
||||||
|
case Emoji.Category.People:
|
||||||
|
return qsTr('People');
|
||||||
|
case Emoji.Category.Nature:
|
||||||
|
return qsTr('Nature');
|
||||||
|
case Emoji.Category.Food:
|
||||||
|
return qsTr('Food');
|
||||||
|
case Emoji.Category.Activity:
|
||||||
|
return qsTr('Activity');
|
||||||
|
case Emoji.Category.Travel:
|
||||||
|
return qsTr('Travel');
|
||||||
|
case Emoji.Category.Objects:
|
||||||
|
return qsTr('Objects');
|
||||||
|
case Emoji.Category.Symbols:
|
||||||
|
return qsTr('Symbols');
|
||||||
|
case Emoji.Category.Flags:
|
||||||
|
return qsTr('Flags');
|
||||||
|
case Emoji.Category.Search:
|
||||||
|
return qsTr('Search');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
|
||||||
|
onClicked: emojiPopup.model.category = model.category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -120,6 +120,8 @@
|
||||||
<file>qml/Reactions.qml</file>
|
<file>qml/Reactions.qml</file>
|
||||||
<file>qml/ScrollHelper.qml</file>
|
<file>qml/ScrollHelper.qml</file>
|
||||||
<file>qml/TimelineRow.qml</file>
|
<file>qml/TimelineRow.qml</file>
|
||||||
|
<file>qml/emoji/EmojiButton.qml</file>
|
||||||
|
<file>qml/emoji/EmojiPicker.qml</file>
|
||||||
<file>qml/delegates/MessageDelegate.qml</file>
|
<file>qml/delegates/MessageDelegate.qml</file>
|
||||||
<file>qml/delegates/TextMessage.qml</file>
|
<file>qml/delegates/TextMessage.qml</file>
|
||||||
<file>qml/delegates/NoticeMessage.qml</file>
|
<file>qml/delegates/NoticeMessage.qml</file>
|
||||||
|
|
|
@ -14,8 +14,9 @@ class Emoji(object):
|
||||||
def generate_code(emojis, category):
|
def generate_code(emojis, category):
|
||||||
tmpl = Template('''
|
tmpl = Template('''
|
||||||
const std::vector<Emoji> emoji::Provider::{{ category }} = {
|
const std::vector<Emoji> emoji::Provider::{{ category }} = {
|
||||||
|
// {{ category.capitalize() }}
|
||||||
{%- for e in emoji %}
|
{%- for e in emoji %}
|
||||||
Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}"},
|
{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}", emoji::Emoji::Category::{{ category.capitalize() }}},
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
};
|
};
|
||||||
''')
|
''')
|
||||||
|
@ -23,6 +24,19 @@ const std::vector<Emoji> emoji::Provider::{{ category }} = {
|
||||||
d = dict(category=category, emoji=emojis)
|
d = dict(category=category, emoji=emojis)
|
||||||
print(tmpl.render(d))
|
print(tmpl.render(d))
|
||||||
|
|
||||||
|
def generate_qml_list(**kwargs):
|
||||||
|
tmpl = Template('''
|
||||||
|
const QVector<Emoji> emoji::Provider::emoji = {
|
||||||
|
{%- for c in kwargs.items() %}
|
||||||
|
// {{ c[0].capitalize() }}
|
||||||
|
{%- for e in c[1] %}
|
||||||
|
{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}", emoji::Emoji::Category::{{ c[0].capitalize() }}},
|
||||||
|
{%- endfor %}
|
||||||
|
{%- endfor %}
|
||||||
|
};
|
||||||
|
''')
|
||||||
|
d = dict(kwargs=kwargs)
|
||||||
|
print(tmpl.render(d))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
|
@ -87,3 +101,4 @@ if __name__ == '__main__':
|
||||||
generate_code(objects, 'objects')
|
generate_code(objects, 'objects')
|
||||||
generate_code(symbols, 'symbols')
|
generate_code(symbols, 'symbols')
|
||||||
generate_code(flags, 'flags')
|
generate_code(flags, 'flags')
|
||||||
|
generate_qml_list(people=people, nature=nature, food=food, activity=activity, travel=travel, objects=objects, symbols=symbols, flags=flags)
|
|
@ -585,9 +585,7 @@ ChatPage::resetUI()
|
||||||
void
|
void
|
||||||
ChatPage::reactMessage(const QString &id)
|
ChatPage::reactMessage(const QString &id)
|
||||||
{
|
{
|
||||||
view_manager_->queueReactionMessage(current_room_,
|
view_manager_->queueReactionMessage(current_room_, id, "👀");
|
||||||
id,
|
|
||||||
"👀");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -59,12 +59,12 @@ Category::Category(QString category, std::vector<Emoji> emoji, QWidget *parent)
|
||||||
emojiListView_->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
emojiListView_->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||||
|
|
||||||
for (const auto &e : emoji) {
|
for (const auto &e : emoji) {
|
||||||
data_->unicode = e.unicode;
|
data_->setUnicode(e.unicode());
|
||||||
|
|
||||||
auto item = new QStandardItem;
|
auto item = new QStandardItem;
|
||||||
item->setSizeHint(QSize(emojiSize, emojiSize));
|
item->setSizeHint(QSize(emojiSize, emojiSize));
|
||||||
|
|
||||||
QVariant unicode(data_->unicode);
|
QVariant unicode(data_->unicode());
|
||||||
item->setData(unicode.toString(), Qt::UserRole);
|
item->setData(unicode.toString(), Qt::UserRole);
|
||||||
|
|
||||||
itemModel_->appendRow(item);
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSet>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QVector>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace emoji {
|
namespace emoji {
|
||||||
|
|
||||||
struct Emoji
|
class Emoji
|
||||||
{
|
{
|
||||||
// Unicode code.
|
Q_GADGET
|
||||||
QString unicode;
|
|
||||||
// Keyboard shortcut e.g :emoji:
|
Q_PROPERTY(const QString &unicode READ unicode CONSTANT)
|
||||||
QString shortname;
|
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
|
class Provider
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// all emoji for QML purposes
|
||||||
|
static const QVector<Emoji> emoji;
|
||||||
static const std::vector<Emoji> people;
|
static const std::vector<Emoji> people;
|
||||||
static const std::vector<Emoji> nature;
|
static const std::vector<Emoji> nature;
|
||||||
static const std::vector<Emoji> food;
|
static const std::vector<Emoji> food;
|
||||||
|
@ -42,4 +81,6 @@ public:
|
||||||
static const std::vector<Emoji> symbols;
|
static const std::vector<Emoji> symbols;
|
||||||
static const std::vector<Emoji> flags;
|
static const std::vector<Emoji> flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace emoji
|
} // namespace emoji
|
||||||
|
Q_DECLARE_METATYPE(emoji::Emoji)
|
||||||
|
|
|
@ -1384,28 +1384,26 @@ struct SendMessageVisitor
|
||||||
void operator()(const mtx::events::RoomEvent<mtx::events::msg::Reaction> &msg)
|
void operator()(const mtx::events::RoomEvent<mtx::events::msg::Reaction> &msg)
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
QString txn_id_qstr = txn_id_qstr_;
|
QString txn_id_qstr = txn_id_qstr_;
|
||||||
TimelineModel *model = model_;
|
TimelineModel *model = model_;
|
||||||
http::client()->send_room_message<mtx::events::msg::Reaction, mtx::events::EventType::Reaction>(
|
http::client()
|
||||||
model->room_id_.toStdString(),
|
->send_room_message<mtx::events::msg::Reaction, mtx::events::EventType::Reaction>(
|
||||||
txn_id_qstr.toStdString(),
|
model->room_id_.toStdString(),
|
||||||
msg.content,
|
txn_id_qstr.toStdString(),
|
||||||
[txn_id_qstr, model](const mtx::responses::EventId &res,
|
msg.content,
|
||||||
mtx::http::RequestErr err) {
|
[txn_id_qstr, model](const mtx::responses::EventId &res,
|
||||||
if (err) {
|
mtx::http::RequestErr err) {
|
||||||
const int status_code =
|
if (err) {
|
||||||
static_cast<int>(err->status_code);
|
const int status_code = static_cast<int>(err->status_code);
|
||||||
nhlog::net()->warn("[{}] failed to send message: {} {}",
|
nhlog::net()->warn("[{}] failed to send message: {} {}",
|
||||||
txn_id_qstr.toStdString(),
|
txn_id_qstr.toStdString(),
|
||||||
err->matrix_error.error,
|
err->matrix_error.error,
|
||||||
status_code);
|
status_code);
|
||||||
emit model->messageFailed(txn_id_qstr);
|
emit model->messageFailed(txn_id_qstr);
|
||||||
}
|
}
|
||||||
emit model->messageSent(
|
emit model->messageSent(
|
||||||
txn_id_qstr, QString::fromStdString(res.event_id.to_string()));
|
txn_id_qstr, QString::fromStdString(res.event_id.to_string()));
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString txn_id_qstr_;
|
QString txn_id_qstr_;
|
||||||
|
|
|
@ -126,7 +126,8 @@ class TimelineModel : public QAbstractListModel
|
||||||
int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
|
int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
|
||||||
Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY
|
Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY
|
||||||
typingUsersChanged)
|
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(QString reply READ reply WRITE setReply NOTIFY replyChanged RESET resetReply)
|
||||||
Q_PROPERTY(
|
Q_PROPERTY(
|
||||||
bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged)
|
bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged)
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
#include "MxcImageProvider.h"
|
#include "MxcImageProvider.h"
|
||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
#include "dialogs/ImageOverlay.h"
|
#include "dialogs/ImageOverlay.h"
|
||||||
|
#include "emoji/EmojiModel.h"
|
||||||
|
#include "emoji/Provider.h"
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
|
Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
|
||||||
|
|
||||||
|
@ -72,6 +74,12 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin
|
||||||
qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice");
|
qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice");
|
||||||
qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser");
|
qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser");
|
||||||
qRegisterMetaType<mtx::events::collections::TimelineEvents>();
|
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
|
#ifdef USE_QUICK_VIEW
|
||||||
view = new QQuickView();
|
view = new QQuickView();
|
||||||
|
@ -290,7 +298,7 @@ TimelineViewManager::queueReactionMessage(const QString &roomId,
|
||||||
mtx::events::msg::Reaction reaction;
|
mtx::events::msg::Reaction reaction;
|
||||||
reaction.relates_to.rel_type = mtx::common::RelationType::Annotation;
|
reaction.relates_to.rel_type = mtx::common::RelationType::Annotation;
|
||||||
reaction.relates_to.event_id = reactedEvent.toStdString();
|
reaction.relates_to.event_id = reactedEvent.toStdString();
|
||||||
reaction.relates_to.key = reactionKey.toStdString();
|
reaction.relates_to.key = reactionKey.toStdString();
|
||||||
|
|
||||||
auto model = models.value(roomId);
|
auto model = models.value(roomId);
|
||||||
model->sendMessage(reaction);
|
model->sendMessage(reaction);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QHash>
|
||||||
#include <QQuickView>
|
#include <QQuickView>
|
||||||
#include <QQuickWidget>
|
#include <QQuickWidget>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
|
@ -12,6 +13,8 @@
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "TimelineModel.h"
|
#include "TimelineModel.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
#include "emoji/EmojiModel.h"
|
||||||
|
#include "emoji/Provider.h"
|
||||||
|
|
||||||
class MxcImageProvider;
|
class MxcImageProvider;
|
||||||
class BlurhashProvider;
|
class BlurhashProvider;
|
||||||
|
@ -102,7 +105,8 @@ private:
|
||||||
|
|
||||||
QHash<QString, QSharedPointer<TimelineModel>> models;
|
QHash<QString, QSharedPointer<TimelineModel>> models;
|
||||||
TimelineModel *timeline_ = nullptr;
|
TimelineModel *timeline_ = nullptr;
|
||||||
bool isInitialSync_ = true;
|
|
||||||
|
bool isInitialSync_ = true;
|
||||||
|
|
||||||
QSharedPointer<UserSettings> settings;
|
QSharedPointer<UserSettings> settings;
|
||||||
QHash<QString, QColor> userColors;
|
QHash<QString, QColor> userColors;
|
||||||
|
|
Loading…
Reference in a new issue