Create better EmojiModel

This commit is contained in:
Tobias Fella
2022-11-18 16:19:07 +01:00
parent 8895693dc4
commit 933bf1877f
6 changed files with 3912 additions and 1587 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -3,13 +3,14 @@
#pragma once #pragma once
#include <QAbstractListModel>
#include <QObject> #include <QObject>
#include <QSettings> #include <QSettings>
struct Emoji { struct Emoji {
Emoji(QString u, QString s, bool isCustom = false) Emoji(QString u, QString s, bool isCustom = false)
: unicode(std::move(std::move(u))) : unicode(std::move(std::move(u)))
, shortname(std::move(std::move(s))) , shortName(std::move(std::move(s)))
, isCustom(isCustom) , isCustom(isCustom)
{ {
} }
@@ -18,53 +19,74 @@ struct Emoji {
friend QDataStream &operator<<(QDataStream &arch, const Emoji &object) friend QDataStream &operator<<(QDataStream &arch, const Emoji &object)
{ {
arch << object.unicode; arch << object.unicode;
arch << object.shortname; arch << object.shortName;
return arch; return arch;
} }
friend QDataStream &operator>>(QDataStream &arch, Emoji &object) friend QDataStream &operator>>(QDataStream &arch, Emoji &object)
{ {
arch >> object.unicode; arch >> object.unicode;
arch >> object.shortname; arch >> object.shortName;
object.isCustom = object.unicode.startsWith("image://"); object.isCustom = object.unicode.startsWith("image://");
return arch; return arch;
} }
QString unicode; QString unicode;
QString shortname; QString shortName;
bool isCustom = false; bool isCustom = false;
Q_GADGET Q_GADGET
Q_PROPERTY(QString unicode MEMBER unicode) Q_PROPERTY(QString unicode MEMBER unicode)
Q_PROPERTY(QString shortname MEMBER shortname) Q_PROPERTY(QString shortName MEMBER shortName)
Q_PROPERTY(bool isCustom MEMBER isCustom) Q_PROPERTY(bool isCustom MEMBER isCustom)
}; };
Q_DECLARE_METATYPE(Emoji) Q_DECLARE_METATYPE(Emoji)
class EmojiModel : public QObject class EmojiModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged) Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
Q_PROPERTY(QVariantList categories READ categories CONSTANT)
Q_PROPERTY(QVariantList people MEMBER people CONSTANT)
Q_PROPERTY(QVariantList nature MEMBER nature CONSTANT)
Q_PROPERTY(QVariantList food MEMBER food CONSTANT)
Q_PROPERTY(QVariantList activity MEMBER activity CONSTANT)
Q_PROPERTY(QVariantList travel MEMBER travel CONSTANT)
Q_PROPERTY(QVariantList objects MEMBER objects CONSTANT)
Q_PROPERTY(QVariantList symbols MEMBER symbols CONSTANT)
Q_PROPERTY(QVariantList flags MEMBER flags CONSTANT)
public: public:
explicit EmojiModel(QObject *parent = nullptr) explicit EmojiModel(QObject *parent = nullptr);
: QObject(parent)
{
}
Q_INVOKABLE QVariantList history(); enum RoleNames {
Q_INVOKABLE static QVariantList filterModel(const QString &filter); TextRole = Qt::DisplayRole,
UnicodeRole,
};
Q_ENUM(RoleNames);
enum Category {
Custom,
Search,
History,
Smileys,
People,
Nature,
Food,
Activities,
Travel,
Objects,
Symbols,
Flags,
Component,
};
Q_ENUM(Category)
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_INVOKABLE QVariantList history() const;
Q_INVOKABLE static QVariantList filterModel(const QString &filter, bool limit = true);
Q_INVOKABLE QVariantList emojis(Category category) const;
Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const;
QVariantList categories() const;
Q_SIGNALS: Q_SIGNALS:
void historyChanged(); void historyChanged();
@@ -73,14 +95,8 @@ public Q_SLOTS:
void emojiUsed(const QVariant &modelData); void emojiUsed(const QVariant &modelData);
private: private:
static const QVariantList people; static QHash<Category, QVariantList> _emojis;
static const QVariantList nature; static QMultiHash<QString, QVariant> _tones;
static const QVariantList food;
static const QVariantList activity;
static const QVariantList travel;
static const QVariantList objects;
static const QVariantList symbols;
static const QVariantList flags;
// TODO: Port away from QSettings // TODO: Port away from QSettings
QSettings m_settings; QSettings m_settings;

1857
src/emojis.h Normal file

File diff suppressed because it is too large Load Diff

1784
src/emojitones.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ import org.kde.neochat 1.0
ColumnLayout { ColumnLayout {
id: _picker id: _picker
property string emojiCategory: "history" property var emojiCategory: EmojiModel.History
property var textArea property var textArea
readonly property var emojiModel: EmojiModel readonly property var emojiModel: EmojiModel
@@ -28,37 +28,22 @@ ColumnLayout {
clip: true clip: true
orientation: ListView.Horizontal orientation: ListView.Horizontal
model: ListModel { model: EmojiModel.categories
ListElement { label: "custom"; category: "custom" }
ListElement { label: "⌛️"; category: "history" }
ListElement { label: "😏"; category: "people" }
ListElement { label: "🌲"; category: "nature" }
ListElement { label: "🍛"; category: "food"}
ListElement { label: "🚁"; category: "activity" }
ListElement { label: "🚅"; category: "travel" }
ListElement { label: "💡"; category: "objects" }
ListElement { label: "🔣"; category: "symbols" }
ListElement { label: "🏁"; category: "flags" }
}
delegate: QQC2.ItemDelegate { delegate: QQC2.ItemDelegate {
id: del id: del
required property string label
required property string category
width: contentItem.Layout.preferredWidth width: contentItem.Layout.preferredWidth
height: Kirigami.Units.gridUnit * 2 height: Kirigami.Units.gridUnit * 2
contentItem: Kirigami.Heading { contentItem: Kirigami.Heading {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
level: del.label === "custom" ? 4 : 1 level: modelData.category === EmojiModel.Custom ? 4 : 1
Layout.preferredWidth: del.label === "custom" ? implicitWidth + Kirigami.Units.largeSpacing : Kirigami.Units.gridUnit * 2 Layout.preferredWidth: modelData.category === EmojiModel.Custom ? implicitWidth + Kirigami.Units.largeSpacing : Kirigami.Units.gridUnit * 2
font.family: del.label === "custom" ? Kirigami.Theme.defaultFont.family : 'emoji' font.family: modelData.category === EmojiModel.Custom ? Kirigami.Theme.defaultFont.family : 'emoji'
text: del.label === "custom" ? i18n("Custom") : del.label text: modelData.category === EmojiModel.Custom ? i18n("Custom") : modelData.emoji
} }
Rectangle { Rectangle {
@@ -67,12 +52,12 @@ ColumnLayout {
width: parent.width width: parent.width
height: 2 height: 2
visible: emojiCategory === category visible: _picker.emojiCategory === modelData.category
color: Kirigami.Theme.focusColor color: Kirigami.Theme.focusColor
} }
onClicked: emojiCategory = category onClicked: _picker.emojiCategory = modelData.category
} }
} }
} }
@@ -93,31 +78,7 @@ ColumnLayout {
clip: true clip: true
model: { model: _picker.emojiCategory === EmojiModel.Custom ? CustomEmojiModel : EmojiModel.emojis(_picker.emojiCategory)
switch (emojiCategory) {
case "custom":
return CustomEmojiModel
case "history":
return emojiModel.history
case "people":
return emojiModel.people
case "nature":
return emojiModel.nature
case "food":
return emojiModel.food
case "activity":
return emojiModel.activity
case "travel":
return emojiModel.travel
case "objects":
return emojiModel.objects
case "symbols":
return emojiModel.symbols
case "flags":
return emojiModel.flags
}
return null
}
delegate: QQC2.ItemDelegate { delegate: QQC2.ItemDelegate {
width: Kirigami.Units.gridUnit * 2 width: Kirigami.Units.gridUnit * 2
@@ -150,7 +111,7 @@ ColumnLayout {
onClicked: { onClicked: {
if (modelData.isCustom) { if (modelData.isCustom) {
chosen(modelData.shortname) chosen(modelData.shortName)
} else { } else {
chosen(modelData.unicode) chosen(modelData.unicode)
} }

77
tools/update-emojis.py Executable file
View File

@@ -0,0 +1,77 @@
#!/bin/python
# SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
# SPDX-License-Identifier: BSD-2-Clause
import requests
import re
response = requests.get('https://unicode.org/Public/emoji/14.0/emoji-test.txt')
group = ""
file = open("../src/emojis.h", "w")
# AAAAARGH reusetool
file.write("// SPDX")
file.write("-FileCopyrightText: None\n")
file.write("// SPDX")
file.write("-License-Identifier: LGPL-2.0-or-later\n")
file.write("// This file is auto-generated. All changes will be lost. See tools/update-emojis.py\n")
file.write("// clang-format off\n")
tones_file = open("../src/emojitones.h", "w")
tones_file.write("// SPDX")
tones_file.write("-FileCopyrightText: None\n")
tones_file.write("// SPDX")
tones_file.write("-License-Identifier: LGPL-2.0-or-later\n")
tones_file.write("// This file is auto-generated. All changes will be lost. See tools/update-emojis.py\n")
tones_file.write("// clang-format off\n")
for line in response.text.split("\n"):
if line.startswith("# group"):
raw_group = line.split(": ")[1]
if raw_group == "Activities":
group = "Activities"
elif raw_group == "Animals & Nature":
group = "Nature"
elif raw_group == "Component":
group = "Component"
elif raw_group == "Flags":
group = "Flags"
elif raw_group == "Food & Drink":
group = "Food"
elif raw_group == "Objects":
group = "Objects"
elif raw_group == "People & Body":
group = "People"
elif raw_group == "Smileys & Emotion":
group = "Smileys"
elif raw_group == "Symbols":
group = "Symbols"
elif raw_group == "Travel & Places":
group = "Travel"
else:
print("Unknown group:" + group)
group = ""
elif line.startswith("#") or line == "":
pass
else:
parts = line.split(";")
first = parts[0].strip()
codepoints = first.split(" ")
x = re.search(".*E[0-9]+.[0-9] ", parts[1])
description = parts[1].removeprefix(x.group())
if "flag:" in description:
description = "Flag of " + description.split(": ")[1]
escape_sequence = ""
for codepoint in codepoints:
escape_sequence += "\\U" + codepoint.rjust(8, "0")
if "unqualified" in line or "minimally-qualified" in line:
continue
if "skin tone" in description:
tones_file.write("{\"" + description.split(":")[0] + "\", QVariant::fromValue(Emoji{QString::fromUtf8(\"" + escape_sequence + "\"), QStringLiteral(\"" + description + "\")})},\n")
continue
file.write("_emojis[" + group + "].append(QVariant::fromValue(Emoji{QString::fromUtf8(\"" + escape_sequence + "\"), QStringLiteral(\"" + description + "\")}));\n")
file.close()
tones_file.close()