Create better EmojiModel
This commit is contained in:
1648
src/emojimodel.cpp
1648
src/emojimodel.cpp
File diff suppressed because it is too large
Load Diff
@@ -3,13 +3,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QSettings>
|
||||
|
||||
struct Emoji {
|
||||
Emoji(QString u, QString s, bool isCustom = false)
|
||||
: unicode(std::move(std::move(u)))
|
||||
, shortname(std::move(std::move(s)))
|
||||
, shortName(std::move(std::move(s)))
|
||||
, isCustom(isCustom)
|
||||
{
|
||||
}
|
||||
@@ -18,53 +19,74 @@ struct Emoji {
|
||||
friend QDataStream &operator<<(QDataStream &arch, const Emoji &object)
|
||||
{
|
||||
arch << object.unicode;
|
||||
arch << object.shortname;
|
||||
arch << object.shortName;
|
||||
return arch;
|
||||
}
|
||||
|
||||
friend QDataStream &operator>>(QDataStream &arch, Emoji &object)
|
||||
{
|
||||
arch >> object.unicode;
|
||||
arch >> object.shortname;
|
||||
arch >> object.shortName;
|
||||
object.isCustom = object.unicode.startsWith("image://");
|
||||
return arch;
|
||||
}
|
||||
|
||||
QString unicode;
|
||||
QString shortname;
|
||||
QString shortName;
|
||||
bool isCustom = false;
|
||||
|
||||
Q_GADGET
|
||||
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_DECLARE_METATYPE(Emoji)
|
||||
|
||||
class EmojiModel : public QObject
|
||||
class EmojiModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
|
||||
|
||||
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)
|
||||
Q_PROPERTY(QVariantList categories READ categories CONSTANT)
|
||||
|
||||
public:
|
||||
explicit EmojiModel(QObject *parent = nullptr)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
explicit EmojiModel(QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE QVariantList history();
|
||||
Q_INVOKABLE static QVariantList filterModel(const QString &filter);
|
||||
enum RoleNames {
|
||||
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:
|
||||
void historyChanged();
|
||||
@@ -73,14 +95,8 @@ public Q_SLOTS:
|
||||
void emojiUsed(const QVariant &modelData);
|
||||
|
||||
private:
|
||||
static const QVariantList people;
|
||||
static const QVariantList nature;
|
||||
static const QVariantList food;
|
||||
static const QVariantList activity;
|
||||
static const QVariantList travel;
|
||||
static const QVariantList objects;
|
||||
static const QVariantList symbols;
|
||||
static const QVariantList flags;
|
||||
static QHash<Category, QVariantList> _emojis;
|
||||
static QMultiHash<QString, QVariant> _tones;
|
||||
|
||||
// TODO: Port away from QSettings
|
||||
QSettings m_settings;
|
||||
|
||||
1857
src/emojis.h
Normal file
1857
src/emojis.h
Normal file
File diff suppressed because it is too large
Load Diff
1784
src/emojitones.h
Normal file
1784
src/emojitones.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,7 +11,7 @@ import org.kde.neochat 1.0
|
||||
ColumnLayout {
|
||||
id: _picker
|
||||
|
||||
property string emojiCategory: "history"
|
||||
property var emojiCategory: EmojiModel.History
|
||||
property var textArea
|
||||
readonly property var emojiModel: EmojiModel
|
||||
|
||||
@@ -28,37 +28,22 @@ ColumnLayout {
|
||||
clip: true
|
||||
orientation: ListView.Horizontal
|
||||
|
||||
model: ListModel {
|
||||
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" }
|
||||
}
|
||||
|
||||
model: EmojiModel.categories
|
||||
delegate: QQC2.ItemDelegate {
|
||||
id: del
|
||||
|
||||
required property string label
|
||||
required property string category
|
||||
|
||||
width: contentItem.Layout.preferredWidth
|
||||
height: Kirigami.Units.gridUnit * 2
|
||||
|
||||
contentItem: Kirigami.Heading {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
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'
|
||||
text: del.label === "custom" ? i18n("Custom") : del.label
|
||||
font.family: modelData.category === EmojiModel.Custom ? Kirigami.Theme.defaultFont.family : 'emoji'
|
||||
text: modelData.category === EmojiModel.Custom ? i18n("Custom") : modelData.emoji
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -67,12 +52,12 @@ ColumnLayout {
|
||||
width: parent.width
|
||||
height: 2
|
||||
|
||||
visible: emojiCategory === category
|
||||
visible: _picker.emojiCategory === modelData.category
|
||||
|
||||
color: Kirigami.Theme.focusColor
|
||||
}
|
||||
|
||||
onClicked: emojiCategory = category
|
||||
onClicked: _picker.emojiCategory = modelData.category
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,31 +78,7 @@ ColumnLayout {
|
||||
|
||||
clip: true
|
||||
|
||||
model: {
|
||||
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
|
||||
}
|
||||
model: _picker.emojiCategory === EmojiModel.Custom ? CustomEmojiModel : EmojiModel.emojis(_picker.emojiCategory)
|
||||
|
||||
delegate: QQC2.ItemDelegate {
|
||||
width: Kirigami.Units.gridUnit * 2
|
||||
@@ -150,7 +111,7 @@ ColumnLayout {
|
||||
|
||||
onClicked: {
|
||||
if (modelData.isCustom) {
|
||||
chosen(modelData.shortname)
|
||||
chosen(modelData.shortName)
|
||||
} else {
|
||||
chosen(modelData.unicode)
|
||||
}
|
||||
|
||||
77
tools/update-emojis.py
Executable file
77
tools/update-emojis.py
Executable 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()
|
||||
Reference in New Issue
Block a user