diff --git a/applets/kimpanel/backend/ibus/emojier/emojier.cpp b/applets/kimpanel/backend/ibus/emojier/emojier.cpp index 91b0d33cf..71a7fae6d 100644 --- a/applets/kimpanel/backend/ibus/emojier/emojier.cpp +++ b/applets/kimpanel/backend/ibus/emojier/emojier.cpp @@ -1,412 +1,424 @@ /* * Copyright (C) 2019 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "emojiersettings.h" #include "config-workspace.h" #undef signals #include struct Emoji { QString content; QString description; QString category; QStringList annotations; }; class TextImageProvider : public QQuickImageProvider { public: TextImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap) { } QPixmap requestPixmap(const QString &id, QSize *_size, const QSize &requestedSize) override { - QPixmap dummy; - const QString renderString = id.mid(1); //drop initial / QSize size = requestedSize; QFont font; if (!size.isValid()) { + QPixmap dummy; QFontMetrics fm(font, &dummy); size = { fm.horizontalAdvance(renderString), fm.height() }; } else { font.setPointSize((requestedSize.height() * 3) / 4); } if (_size) { *_size = size; } QPixmap pixmap(size.width(), size.height()); pixmap.fill(Qt::transparent); QPainter p; p.begin(&pixmap); p.setFont(font); p.drawText(QRect(0, 0, size.width(), size.height()), Qt::AlignCenter, renderString); p.end(); return pixmap; } }; class AbstractEmojiModel : public QAbstractListModel { Q_OBJECT public: enum EmojiRole { CategoryRole = Qt::UserRole + 1, AnnotationsRole }; int rowCount(const QModelIndex & parent = {}) const override { return parent.isValid() ? 0 : m_emoji.count(); } QVariant data(const QModelIndex & index, int role) const override { if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid | QAbstractItemModel::CheckIndexOption::ParentIsInvalid | QAbstractItemModel::CheckIndexOption::DoNotUseParent) || index.column() != 0) return {}; const auto &emoji = m_emoji[index.row()]; switch(role) { case Qt::DisplayRole: return emoji.content; case Qt::ToolTipRole: return emoji.description; case CategoryRole: return emoji.category; case AnnotationsRole: return emoji.annotations; } return {}; } protected: QVector m_emoji; }; class EmojiModel : public AbstractEmojiModel { Q_OBJECT Q_PROPERTY(QStringList categories MEMBER m_categories CONSTANT) public: enum EmojiRole { CategoryRole = Qt::UserRole + 1 }; EmojiModel() { QLocale locale; + QVector dicts; const auto bcp = locale.bcp47Name(); const QString dictName = "ibus/dicts/emoji-" + QString(bcp).replace(QLatin1Char('-'), QLatin1Char('_')) + ".dict"; const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, dictName); - if (path.isEmpty()) { - qWarning() << "could not find" << dictName; - return; + if (!path.isEmpty()) { + dicts << path; } - QVector dicts = {path}; const auto idxSpecific = bcp.indexOf(QLatin1Char('-')); if (idxSpecific > 0) { - const QString genericDictName = "ibus/dicts/emoji-" + bcp.left(idxSpecific) + ".dict"; + const QString genericDictName = "ibus/dicts/emoji-" + bcp.leftRef(idxSpecific) + ".dict"; + const QString genericPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, genericDictName); + + if (!genericPath.isEmpty()) { + dicts << genericPath; + } + } + + if (dicts.isEmpty()) { + const QString genericDictName = "ibus/dicts/emoji-en.dict"; const QString genericPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, genericDictName); if (!genericPath.isEmpty()) { dicts << genericPath; } } + if (dicts.isEmpty()) { + qWarning() << "could not find ibus emoji dictionaries." << dictName; + return; + } + QSet categories; QSet processedEmoji; for (const auto &dictPath : qAsConst(dicts)) { GSList *list = ibus_emoji_data_load (dictPath.toUtf8().constData()); m_emoji.reserve(g_slist_length(list)); for (GSList *l = list; l; l = l->next) { IBusEmojiData *data = (IBusEmojiData *) l->data; if (!IBUS_IS_EMOJI_DATA (data)) { qWarning() << "Your dict format is no longer supported.\n" "Need to create the dictionaries again."; g_slist_free (list); return; } const QString emoji = QString::fromUtf8(ibus_emoji_data_get_emoji(data)); const QString description = ibus_emoji_data_get_description(data); if (description == QString::fromUtf8("↑↑↑") || description.isEmpty() || processedEmoji.contains(emoji)) { continue; } QStringList annotations; const auto annotations_glib = ibus_emoji_data_get_annotations(data); for (GSList *l = annotations_glib; l; l = l->next) { annotations << QString::fromUtf8((const gchar*) l->data); } const QString category = QString::fromUtf8(ibus_emoji_data_get_category(data)); categories.insert(category); m_emoji += { emoji, description, category, annotations }; processedEmoji << emoji; } g_slist_free (list); } categories.remove({}); m_categories = categories.values(); m_categories.sort(); m_categories.prepend({}); m_categories.prepend(QStringLiteral(":find:")); m_categories.prepend(QStringLiteral(":recent:")); } Q_SCRIPTABLE QString findFirstEmojiForCategory(const QString &category) { for (const Emoji &emoji : m_emoji) { if (emoji.category == category) return emoji.content; } return {}; } private: QStringList m_categories; }; class RecentEmojiModel : public AbstractEmojiModel { Q_OBJECT Q_PROPERTY(int count READ rowCount CONSTANT) public: RecentEmojiModel() { refresh(); } Q_SCRIPTABLE void includeRecent(const QString &emoji, const QString &emojiDescription) { QStringList recent = m_settings.recent(); QStringList recentDescriptions = m_settings.recentDescriptions(); const int idx = recent.indexOf(emoji); if (idx >= 0) { recent.removeAt(idx); recentDescriptions.removeAt(idx); } recent.prepend(emoji); recent = recent.mid(0, 50); m_settings.setRecent(recent); recentDescriptions.prepend(emojiDescription); recentDescriptions = recentDescriptions.mid(0, 50); m_settings.setRecentDescriptions(recentDescriptions); m_settings.save(); refresh(); } private: void refresh() { beginResetModel(); auto recent = m_settings.recent(); auto recentDescriptions = m_settings.recentDescriptions(); int i = 0; m_emoji.clear(); for (const QString &c : recent) { - m_emoji += { c, recentDescriptions.at(i++), QString{} }; + m_emoji += { c, recentDescriptions.at(i++), QString{}, {} }; } endResetModel(); } EmojierSettings m_settings; }; class CategoryModelFilter : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(QString category READ category WRITE setCategory) public: QString category() const { return m_category; } void setCategory(const QString &category) { if (m_category != category) { m_category = category; invalidateFilter(); } } bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override { return m_category.isEmpty() || sourceModel()->index(source_row, 0, source_parent).data(EmojiModel::CategoryRole).toString() == m_category; } private: QString m_category; }; class SearchModelFilter : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(QString search READ search WRITE setSearch) public: QString search() const { return m_search; } void setSearch(const QString &search) { if (m_search != search) { m_search = search; invalidateFilter(); } } bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override { const auto idx = sourceModel()->index(source_row, 0, source_parent); return idx.data(Qt::ToolTipRole).toString().contains(m_search, Qt::CaseInsensitive) || idx.data(AbstractEmojiModel::AnnotationsRole).toStringList().contains(m_search, Qt::CaseInsensitive); } private: QString m_search; }; class CopyHelperPrivate : public QObject { Q_OBJECT public: Q_INVOKABLE static void copyTextToClipboard(const QString& text) { QClipboard *clipboard = qGuiApp->clipboard(); clipboard->setText(text, QClipboard::Clipboard); clipboard->setText(text, QClipboard::Selection); } }; class EngineWatcher : public QObject { public: EngineWatcher(QQmlApplicationEngine* engine) : QObject(engine) { connect(engine, &QQmlApplicationEngine::objectCreated, this, &EngineWatcher::integrateObject); } void integrateObject(QObject* object) { QWindow* window = qobject_cast(object); auto conf = KSharedConfig::openConfig(); KWindowConfig::restoreWindowSize(window, conf->group("Window")); object->installEventFilter(this); } bool eventFilter(QObject * object, QEvent * event) { if (event->type() == QEvent::Close) { QWindow* window = qobject_cast(object); auto conf = KSharedConfig::openConfig(); auto group = conf->group("Window"); KWindowConfig::saveWindowSize(window, group); group.sync(); } return false; } }; int main(int argc, char** argv) { QGuiApplication::setFallbackSessionManagementEnabled(false); QApplication app(argc, argv); app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); app.setWindowIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-emoticons"))); KCrash::initialize(); KQuickAddons::QtQuickSettings::init(); KLocalizedString::setApplicationDomain("org.kde.plasma.emojier"); - KAboutData about(QStringLiteral("org.kde.plasma.emojier"), i18n("Emoji Selector"), QStringLiteral(WORKSPACE_VERSION_STRING), i18n("Emoji Selector"), - KAboutLicense::GPL, i18n("(C) 2019 Aleix Pol i Gonzalez")); + KAboutData about(QStringLiteral("plasma.emojier"), i18n("Emoji Selector"), QStringLiteral(WORKSPACE_VERSION_STRING), i18n("Emoji Selector"), + KAboutLicense::GPL, i18n("(C) 2019 Aleix Pol i Gonzalez")); about.addAuthor( QStringLiteral("Aleix Pol i Gonzalez"), QString(), QStringLiteral("aleixpol@kde.org") ); about.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails")); // about.setProductName(""); about.setProgramLogo(app.windowIcon()); KAboutData::setApplicationData(about); auto disableSessionManagement = [](QSessionManager &sm) { sm.setRestartHint(QSessionManager::RestartNever); }; QObject::connect(&app, &QGuiApplication::commitDataRequest, disableSessionManagement); QObject::connect(&app, &QGuiApplication::saveStateRequest, disableSessionManagement); - KDBusService::StartupOptions startup = nullptr; + KDBusService::StartupOptions startup = {}; { QCommandLineParser parser; QCommandLineOption replaceOption({QStringLiteral("replace")}, i18n("Replace an existing instance")); parser.addOption(replaceOption); about.setupCommandLine(&parser); parser.process(app); about.processCommandLine(&parser); if (parser.isSet(replaceOption)) { startup |= KDBusService::Replace; } } KDBusService* service = new KDBusService(KDBusService::Unique | startup, &app); qmlRegisterType("org.kde.plasma.emoji", 1, 0, "EmojiModel"); qmlRegisterType("org.kde.plasma.emoji", 1, 0, "CategoryModelFilter"); qmlRegisterType("org.kde.plasma.emoji", 1, 0, "SearchModelFilter"); qmlRegisterType("org.kde.plasma.emoji", 1, 0, "RecentEmojiModel"); qmlRegisterSingletonType("org.kde.plasma.emoji", 1, 0, "CopyHelper", [] (QQmlEngine*, QJSEngine*) -> QObject* { return new CopyHelperPrivate; }); QQmlApplicationEngine engine; new EngineWatcher(&engine); engine.addImageProvider(QLatin1String("text"), new TextImageProvider); engine.load(QUrl(QStringLiteral("qrc:/ui/emojier.qml"))); QObject::connect(service, &KDBusService::activateRequested, &engine, [&engine](const QStringList &/*arguments*/, const QString &/*workingDirectory*/) { for (QObject* object : engine.rootObjects()) { auto w = qobject_cast(object); if (!w) continue; w->setVisible(true); w->raise(); } }); return app.exec(); } #include "emojier.moc" diff --git a/kcms/activities/qml/activitiesTab/ActivitiesView.qml b/kcms/activities/qml/activitiesTab/ActivitiesView.qml index aac13a2ab..88b47bdf6 100644 --- a/kcms/activities/qml/activitiesTab/ActivitiesView.qml +++ b/kcms/activities/qml/activitiesTab/ActivitiesView.qml @@ -1,88 +1,90 @@ /* vim:set foldenable foldmethod=marker: * * Copyright (C) 2015 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.5 import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.0 import org.kde.activities 0.1 as Activities import org.kde.activities.settings 0.1 import org.kde.kirigami 2.5 as Kirigami ColumnLayout { id: root QQC2.ScrollView { Layout.fillHeight: true Layout.fillWidth: true Component.onCompleted: background.visible = true; ListView { id: activitiesList + clip: true + model: Activities.ActivityModel { id: kactivities } delegate: Kirigami.SwipeListItem { hoverEnabled: true contentItem: RowLayout { id: row Kirigami.Icon { id: icon height: Kirigami.Units.iconSizes.medium width: height source: model.icon } QQC2.Label { Layout.fillWidth: true text: model.name } } actions: [ Kirigami.Action { icon.name: "configure" tooltip: i18nc("@info:tooltip", "Configure %1 activity", model.name) onTriggered: ActivitySettings.configureActivity(model.id); }, Kirigami.Action { visible: ActivitySettings.newActivityAuthorized enabled: activitiesList.count > 1 icon.name: "edit-delete" tooltip: i18nc("@info:tooltip", "Delete %1 activity", model.name) onTriggered: ActivitySettings.deleteActivity(model.id); } ] } } } QQC2.Button { id: buttonCreateActivity visible: ActivitySettings.newActivityAuthorized text: i18nd("kcm_activities5", "Create New...") icon.name: "list-add" onClicked: ActivitySettings.newActivity(); } } diff --git a/kcms/activities/qml/privacyTab/BlacklistApplicationView.qml b/kcms/activities/qml/privacyTab/BlacklistApplicationView.qml index 981ea694f..78580a13e 100644 --- a/kcms/activities/qml/privacyTab/BlacklistApplicationView.qml +++ b/kcms/activities/qml/privacyTab/BlacklistApplicationView.qml @@ -1,90 +1,91 @@ /* vim:set foldenable foldmethod=marker: * * Copyright (C) 2012 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.5 import QtQuick.Controls 2.5 as QQC2 import org.kde.kirigami 2.5 as Kirigami QQC2.ScrollView { enabled: applicationModel.enabled Component.onCompleted: background.visible = true; GridView { id: gridView + clip: true cellHeight: Kirigami.Units.gridUnit * 5 cellWidth: Kirigami.Units.gridUnit * 9 model: applicationModel delegate: Item { height: gridView.cellHeight width: gridView.cellWidth Rectangle { anchors.fill: parent visible: mouseArea.containsMouse color: Kirigami.Theme.hoverColor } Kirigami.Icon { id: icon anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.verticalCenter height: Kirigami.Units.iconSizes.medium width: height source: model.icon opacity: model.blocked ? 0.6 : 1.0 Behavior on opacity { NumberAnimation { duration: 100 } } } Kirigami.Icon { anchors.bottom: icon.bottom anchors.right: icon.right height: Kirigami.Units.iconSizes.small width: height source: "emblem-unavailable" opacity: model.blocked ? 1.0 : 0.0 Behavior on opacity { NumberAnimation { duration: 100 } } } QQC2.Label { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.verticalCenter width: parent.width - 20 text: model.title horizontalAlignment: Text.AlignHCenter elide: Text.ElideRight maximumLineCount: 2 wrapMode: Text.Wrap opacity: model.blocked ? 0.6 : 1.0 Behavior on opacity { NumberAnimation { duration: 100 } } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true onClicked: applicationModel.toggleApplicationBlocked(model.index) } } } } diff --git a/kcms/baloo/package/contents/ui/main.qml b/kcms/baloo/package/contents/ui/main.qml index 2b56d3b8f..da782bb0f 100644 --- a/kcms/baloo/package/contents/ui/main.qml +++ b/kcms/baloo/package/contents/ui/main.qml @@ -1,122 +1,123 @@ /* * Copyright 2018 Tomaz Canabrava * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.1 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.11 as QQC2 import QtQuick.Dialogs 1.2 as QtDialogs import org.kde.kirigami 2.4 as Kirigami import org.kde.kcm 1.1 as KCM KCM.SimpleKCM { id: root implicitHeight: Kirigami.Units.gridUnit * 22 KCM.ConfigModule.quickHelp: i18n("This module lets you configure the file indexer and search functionality.") ColumnLayout { anchors.fill: parent anchors.margins: Kirigami.Units.largeSpacing QQC2.Label { text: i18n("File Search helps you quickly locate all your files based on their content.") } QQC2.CheckBox { id: fileSearchEnabled text: i18n("Enable File Search") enabled: !kcm.balooSettings.isImmutable("indexingEnabled") checked: kcm.balooSettings.indexingEnabled onCheckStateChanged: { kcm.balooSettings.indexingEnabled = checked } } QQC2.CheckBox { id: indexFileContents text: i18n("Also index file content") enabled: fileSearchEnabled.checked && !kcm.balooSettings.isImmutable("onlyBasicIndexing") checked: !kcm.balooSettings.onlyBasicIndexing onCheckStateChanged: kcm.balooSettings.onlyBasicIndexing = !checked } Item { Layout.preferredHeight: Kirigami.Units.gridUnit } QQC2.Label { text: i18n("Do not search in these locations:") } QQC2.ScrollView { id: bgObject Component.onCompleted: bgObject.background.visible = true Layout.fillWidth: true Layout.fillHeight: true ListView { id: fileExcludeList + clip: true model: kcm.filteredModel delegate: Kirigami.BasicListItem { icon: model.decoration label: model.folder onClicked: fileExcludeList.currentIndex = index } } } RowLayout { QQC2.Button { id: addFolder icon.name: "list-add" onClicked: fileDialogLoader.active = true } QQC2.Button{ id: removeFolder icon.name: "list-remove" enabled: fileExcludeList.currentIndex !== -1 onClicked: { kcm.filteredModel.removeFolder(fileExcludeList.currentIndex) } } } } Loader { id: fileDialogLoader active: false sourceComponent: QtDialogs.FileDialog { title: i18n("Select a folder to filter") folder: shortcuts.home selectFolder: true onAccepted: { kcm.filteredModel.addFolder(fileUrls[0]) fileDialogLoader.active = false } onRejected: { fileDialogLoader.active = false } Component.onCompleted: open() } } }