File indexing completed on 2024-05-12 05:35:36
0001 /* 0002 SPDX-FileCopyrightText: 2019 Aleix Pol Gonzalez <aleixpol@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "emojierplugin.h" 0008 0009 #undef signals 0010 #include "emojidict.h" 0011 #include "emojiersettings.h" 0012 #include <QClipboard> 0013 #include <QGuiApplication> 0014 #include <QSortFilterProxyModel> 0015 #include <QStandardPaths> 0016 #include <qqml.h> 0017 0018 int AbstractEmojiModel::rowCount(const QModelIndex &parent) const 0019 { 0020 return parent.isValid() ? 0 : m_emoji.size(); 0021 } 0022 0023 QVariant AbstractEmojiModel::data(const QModelIndex &index, int role) const 0024 { 0025 if (!checkIndex(index, 0026 QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::ParentIsInvalid 0027 | QAbstractItemModel::CheckIndexOption::DoNotUseParent) 0028 || index.column() != 0) 0029 return {}; 0030 0031 const auto &emoji = m_emoji[index.row()]; 0032 switch (role) { 0033 case Qt::DisplayRole: 0034 return emoji.content; 0035 case Qt::ToolTipRole: 0036 return emoji.description; 0037 case CategoryRole: 0038 return emoji.categoryName(); 0039 case AnnotationsRole: 0040 return emoji.annotations; 0041 } 0042 return {}; 0043 } 0044 0045 EmojiModel::EmojiModel() 0046 { 0047 const QHash<QString, QString> specialCases{{QLatin1String{"zh-TW"}, QLatin1String{"zh_Hant"}}, {QLatin1String{"zh-HK"}, QLatin1String{"zh_Hant_HK"}}}; 0048 QLocale locale; 0049 QStringList dicts; 0050 auto bcp = locale.bcp47Name(); 0051 bcp = specialCases.value(bcp, bcp); 0052 bcp.replace(QLatin1Char('-'), QLatin1Char('_')); 0053 0054 const QString dictName = QLatin1String{"plasma/emoji/"} + bcp + QLatin1String{".dict"}; 0055 const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, dictName); 0056 if (!path.isEmpty()) { 0057 dicts << path; 0058 } 0059 0060 for (qsizetype underscoreIndex = -1; (underscoreIndex = bcp.lastIndexOf(QLatin1Char('_'), underscoreIndex)) != -1; --underscoreIndex) { 0061 const QString genericDictName = QLatin1String{"plasma/emoji/"} + QStringView(bcp).left(underscoreIndex) + QLatin1String{".dict"}; 0062 const QString genericPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, genericDictName); 0063 0064 if (!genericPath.isEmpty()) { 0065 dicts << genericPath; 0066 } 0067 } 0068 0069 // Always fallback to en, because some annotation data only have minimum data. 0070 const QString genericPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("plasma/emoji/en.dict")); 0071 dicts << genericPath; 0072 0073 if (dicts.isEmpty()) { 0074 qWarning() << "could not find emoji dictionaries." << dictName; 0075 return; 0076 } 0077 0078 QSet<QString> categories; 0079 EmojiDict dict; 0080 // We load in reverse order, because we want to preserve the order in en.dict. 0081 // en.dict almost always gives complete set of data. 0082 for (auto iter = dicts.crbegin(); iter != dicts.crend(); ++iter) { 0083 dict.load(*iter); 0084 } 0085 m_emoji = std::move(dict.m_emojis); 0086 for (const auto &emoji : m_emoji) { 0087 categories.insert(emoji.categoryName()); 0088 } 0089 m_categories = categories.values(); 0090 m_categories.sort(); 0091 } 0092 0093 QString EmojiModel::findFirstEmojiForCategory(const QString &category) 0094 { 0095 for (const Emoji &emoji : m_emoji) { 0096 if (emoji.categoryName() == category) 0097 return emoji.content; 0098 } 0099 return {}; 0100 } 0101 0102 RecentEmojiModel::RecentEmojiModel() 0103 { 0104 refresh(); 0105 } 0106 0107 void RecentEmojiModel::includeRecent(const QString &emoji, const QString &emojiDescription) 0108 { 0109 QStringList recent = m_settings.recent(); 0110 QStringList recentDescriptions = m_settings.recentDescriptions(); 0111 0112 const int idx = recent.indexOf(emoji); 0113 if (idx > 0) { 0114 Q_EMIT beginMoveRows(QModelIndex(), idx, idx, QModelIndex(), 0); 0115 recent.move(idx, 0); 0116 recentDescriptions.move(idx, 0); 0117 m_emoji.move(idx, 0); 0118 Q_EMIT endMoveRows(); 0119 } else if (idx < 0) { 0120 Q_EMIT beginInsertRows(QModelIndex(), 0, 0); 0121 recent.prepend(emoji); 0122 recentDescriptions.prepend(emojiDescription); 0123 m_emoji.prepend(Emoji{emoji, emojiDescription, 0, {}}); 0124 Q_EMIT endInsertRows(); 0125 0126 if (recent.size() > 50) { 0127 Q_EMIT beginRemoveRows(QModelIndex(), 50, recent.size() - 1); 0128 recent = recent.mid(0, 50); 0129 recentDescriptions = recentDescriptions.mid(0, 50); 0130 m_emoji = m_emoji.mid(0, 50); 0131 Q_EMIT endRemoveRows(); 0132 } 0133 } 0134 0135 m_settings.setRecent(recent); 0136 m_settings.setRecentDescriptions(recentDescriptions); 0137 m_settings.save(); 0138 } 0139 0140 void RecentEmojiModel::clearHistory() 0141 { 0142 m_settings.setRecent(QStringList()); 0143 m_settings.setRecentDescriptions(QStringList()); 0144 m_settings.save(); 0145 0146 refresh(); 0147 } 0148 0149 void RecentEmojiModel::refresh() 0150 { 0151 beginResetModel(); 0152 auto recent = m_settings.recent(); 0153 auto recentDescriptions = m_settings.recentDescriptions(); 0154 int i = 0; 0155 m_emoji.clear(); 0156 for (const QString &c : recent) { 0157 m_emoji += {c, recentDescriptions.at(i++), 0, {}}; 0158 } 0159 endResetModel(); 0160 } 0161 0162 QString CategoryModelFilter::category() const 0163 { 0164 return m_category; 0165 } 0166 void CategoryModelFilter::setCategory(const QString &category) 0167 { 0168 if (m_category != category) { 0169 m_category = category; 0170 invalidateFilter(); 0171 } 0172 } 0173 0174 bool CategoryModelFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 0175 { 0176 return m_category.isEmpty() || sourceModel()->index(source_row, 0, source_parent).data(EmojiModel::CategoryRole).toString() == m_category; 0177 } 0178 0179 QString SearchModelFilter::search() const 0180 { 0181 return m_search; 0182 } 0183 void SearchModelFilter::setSearch(const QString &search) 0184 { 0185 if (m_search != search) { 0186 m_search = search; 0187 invalidateFilter(); 0188 } 0189 } 0190 0191 bool SearchModelFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 0192 { 0193 const auto idx = sourceModel()->index(source_row, 0, source_parent); 0194 return idx.data(Qt::ToolTipRole).toString().contains(m_search, Qt::CaseInsensitive) 0195 || idx.data(AbstractEmojiModel::AnnotationsRole).toStringList().contains(m_search, Qt::CaseInsensitive); 0196 } 0197 0198 void CopyHelperPrivate::copyTextToClipboard(const QString &text) 0199 { 0200 QClipboard *clipboard = qGuiApp->clipboard(); 0201 clipboard->setText(text, QClipboard::Clipboard); 0202 clipboard->setText(text, QClipboard::Selection); 0203 } 0204 0205 #include "emojierplugin.moc"