diff --git a/applets/kimpanel/backend/ibus/emojier/emojier.cpp b/applets/kimpanel/backend/ibus/emojier/emojier.cpp index 26c6c3edf..4444a8a12 100644 --- a/applets/kimpanel/backend/ibus/emojier/emojier.cpp +++ b/applets/kimpanel/backend/ibus/emojier/emojier.cpp @@ -1,387 +1,387 @@ /* * 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 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()) { dicts << path; } const auto idxSpecific = bcp.indexOf(QLatin1Char('-')); if (idxSpecific > 0) { 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{}, {} }; } 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) + bool eventFilter(QObject * object, QEvent * event) override { 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("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 = {}; { 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.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/applets/kimpanel/backend/ibus/ibus15/panel.cpp b/applets/kimpanel/backend/ibus/ibus15/panel.cpp index 18be6be79..61eba2ce6 100644 --- a/applets/kimpanel/backend/ibus/ibus15/panel.cpp +++ b/applets/kimpanel/backend/ibus/ibus15/panel.cpp @@ -1,1571 +1,1572 @@ /* * Copyright (C) 2011-2012 Ni Hui * Copyright (C) 2013-2014 Weng Xuetian * * 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 "panel.h" #include "propertymanager.h" #include "enginemanager.h" #include "xkblayoutmanager.h" #include "app.h" #include "gtkaccelparse_p.h" #ifndef DBUS_ERROR_FAILED #define DBUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed" #endif /* DBUS_ERROR_FAILED */ #define IBUS_SCHEMA_GENERAL "org.freedesktop.ibus.general" #define IBUS_SCHEMA_HOTKEY "org.freedesktop.ibus.general.hotkey" #define IBUS_SCHEMA_PANEL "org.freedesktop.ibus.panel" typedef struct _IBusPanelImpanelClass IBusPanelImpanelClass; struct _IBusPanelImpanel { IBusPanelService parent; IBusBus *bus; GDBusConnection *conn; PropertyManager* propManager; EngineManager* engineManager; XkbLayoutManager* xkbLayoutManager; App* app; gboolean useSystemKeyboardLayout; int selected; GSettings *settings_general; GSettings *settings_hotkey; }; struct _IBusPanelImpanelClass { IBusPanelServiceClass parent; }; /* functions prototype */ static void ibus_panel_impanel_class_init (IBusPanelImpanelClass *klass); static void ibus_panel_impanel_init (IBusPanelImpanel *impanel); static void ibus_panel_impanel_destroy (IBusPanelImpanel *impanel); static void ibus_panel_impanel_focus_in (IBusPanelService *panel, const gchar *input_context_path); static void ibus_panel_impanel_focus_out (IBusPanelService *panel, const gchar *input_context_path); static void ibus_panel_impanel_register_properties (IBusPanelService *panel, IBusPropList *prop_list); static void ibus_panel_impanel_real_register_properties (IBusPanelImpanel *impanel); static void ibus_panel_impanel_set_cursor_location (IBusPanelService *panel, gint x, gint y, gint w, gint h); static void ibus_panel_impanel_update_auxiliary_text (IBusPanelService *panel, IBusText *text, gboolean visible); static void ibus_panel_impanel_update_lookup_table (IBusPanelService *panel, IBusLookupTable *lookup_table, gboolean visible); static void ibus_panel_impanel_update_preedit_text (IBusPanelService *panel, IBusText *text, guint cursor_pos, gboolean visible); static void ibus_panel_impanel_update_property (IBusPanelService *panel, IBusProperty *prop); static void ibus_panel_impanel_cursor_down_lookup_table (IBusPanelService *panel); static void ibus_panel_impanel_cursor_up_lookup_table (IBusPanelService *panel); static void ibus_panel_impanel_hide_auxiliary_text (IBusPanelService *panel); static void ibus_panel_impanel_hide_language_bar (IBusPanelService *panel); static void ibus_panel_impanel_hide_lookup_table (IBusPanelService *panel); static void ibus_panel_impanel_hide_preedit_text (IBusPanelService *panel); static void ibus_panel_impanel_page_down_lookup_table (IBusPanelService *panel); static void ibus_panel_impanel_page_up_lookup_table (IBusPanelService *panel); static void ibus_panel_impanel_reset (IBusPanelService *panel); static void ibus_panel_impanel_show_auxiliary_text (IBusPanelService *panel); static void ibus_panel_impanel_show_language_bar (IBusPanelService *panel); static void ibus_panel_impanel_show_lookup_table (IBusPanelService *panel); static void ibus_panel_impanel_show_preedit_text (IBusPanelService *panel); static void ibus_panel_impanel_start_setup (IBusPanelService *panel); static void ibus_panel_impanel_state_changed (IBusPanelService *panel); /* impanel signal handler function */ static void ibus_panel_impanel_exec_im_menu (IBusPanelImpanel* impanel); static void ibus_panel_impanel_exec_menu (IBusPanelImpanel* impanel, IBusPropList* prop_list); static void impanel_set_engine(IBusPanelImpanel* impanel, const char* name); static QByteArray ibus_property_to_propstr (IBusProperty *property, gboolean useSymbol = FALSE); static QByteArray ibus_engine_desc_to_logo_propstr(IBusEngineDesc* engine); void impanel_update_logo_by_engine(IBusPanelImpanel* impanel, IBusEngineDesc* engine_desc) { if (!impanel->conn) { return; } QByteArray propstr = ibus_engine_desc_to_logo_propstr(engine_desc); g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "UpdateProperty", (g_variant_new ("(s)", propstr.constData())), nullptr); } void ibus_panel_impanel_set_bus (IBusPanelImpanel *impanel, IBusBus *bus) { impanel->bus = bus; } void ibus_panel_impanel_set_app(IBusPanelImpanel* impanel, App* app) { impanel->app = app; } void ibus_panel_impanel_accept(IBusPanelImpanel* impanel) { if (impanel->selected >= 0 && static_cast(impanel->selected) < impanel->engineManager->length()) { impanel_set_engine(impanel, ibus_engine_desc_get_name(impanel->engineManager->engines()[impanel->selected])); impanel->selected = -1; } } void ibus_panel_impanel_navigate(IBusPanelImpanel* impanel, gboolean start, gboolean forward) { if (start) { impanel->selected = -1; } if (impanel->engineManager->length() < 2) { return; } IBusEngineDesc* engine_desc = nullptr; if (impanel->selected < 0) { engine_desc = ibus_bus_get_global_engine(impanel->bus); } else if (static_cast(impanel->selected) < impanel->engineManager->length()) { engine_desc = impanel->engineManager->engines()[impanel->selected]; g_object_ref(engine_desc); } if (!engine_desc) { engine_desc = impanel->engineManager->engines()[0]; g_object_ref(engine_desc); } if (engine_desc) { const char* name = impanel->engineManager->navigate(engine_desc, forward); impanel->selected = impanel->engineManager->getIndexByName(name); g_object_unref(engine_desc); } else { return; } if (impanel->selected >= 0 && static_cast(impanel->selected) < impanel->engineManager->length()) { ibus_panel_impanel_real_register_properties(impanel); } } void ibus_panel_impanel_move_next(IBusPanelImpanel* impanel) { if (impanel->engineManager->length() >= 2) { impanel_set_engine(impanel, ibus_engine_desc_get_name(impanel->engineManager->engines()[1])); } } static GDBusNodeInfo *introspection_data = nullptr; static guint owner_id; static const gchar introspection_xml[] = "" " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " ""; static const char prop_sep[] = ":"; static QByteArray ibus_property_args_to_propstr (const char *key, const char *label, const char *icon, const char *tooltip, const char *hint = "") { QByteArray propstr("/IBus/"); QByteArray str(key); str.replace(':', '!'); App* app = static_cast(qApp); propstr += str; propstr += prop_sep; propstr += QByteArray(label).replace(':', '-').constData(); propstr += prop_sep; propstr += app->normalizeIconName(QByteArray(icon).replace(':', '-')); propstr += prop_sep; propstr += QByteArray(tooltip).replace(':', '-').constData(); propstr += prop_sep; propstr += QByteArray(hint).replace(':', '-').constData(); return propstr; } static QByteArray ibus_engine_desc_to_logo_propstr(IBusEngineDesc* engine) { const gchar* label = "IBus"; const gchar* tooltip = ""; const gchar* icon = "input-keyboard"; gchar xkbLabel[3]; if (engine) { const gchar* iconname = ibus_engine_desc_get_icon(engine); if (iconname && iconname[0]) { icon = iconname; } if (strncmp("xkb:", ibus_engine_desc_get_name(engine), 4) == 0) { strncpy(xkbLabel, ibus_engine_desc_get_name(engine) + 4, 2); xkbLabel[2] = 0; int i = 0; while (xkbLabel[i]) { if (xkbLabel[i] == ':') { xkbLabel[i] = 0; } i++; } label = xkbLabel; icon = ""; } const gchar* longname = ibus_engine_desc_get_longname(engine); if (longname && longname[0]) { tooltip = longname; } } return ibus_property_args_to_propstr("Logo", label, icon, tooltip); } static QByteArray ibus_property_to_propstr (IBusProperty *property, gboolean useSymbol) { const gchar* label = nullptr; const gchar* tooltip = ibus_text_get_text (ibus_property_get_tooltip (property)); const gchar* icon = ibus_property_get_icon (property); if (useSymbol) { label = ibus_text_get_text(ibus_property_get_symbol (property)); if (!label || label[0] == '\0') { label = ibus_text_get_text(ibus_property_get_label(property)); } } else { label = ibus_text_get_text(ibus_property_get_label(property)); } const char* hint = ""; if (ibus_property_get_prop_type(property) == PROP_TYPE_TOGGLE) { if (ibus_property_get_state(property) != PROP_STATE_CHECKED) { hint = "disable"; } } else if (ibus_property_get_prop_type(property) == PROP_TYPE_RADIO) { if (ibus_property_get_state(property) == PROP_STATE_CHECKED) { hint = "checked"; } } return ibus_property_args_to_propstr(ibus_property_get_key (property), label, icon, tooltip, hint); } static QByteArray ibus_engine_desc_args_to_propstr (const char *name, const char *language, const char *longname, const char *icon, const char *description) { QByteArray propstr("/IBus/Engine/"); QByteArray data(name); data.replace(':', '!'); propstr += data; propstr += prop_sep; if (language) { propstr += language; propstr += " - "; } propstr += longname; propstr += prop_sep; propstr += icon; propstr += prop_sep; propstr += description; return propstr; } static QByteArray ibus_engine_desc_to_propstr (IBusEngineDesc *engine_desc) { return ibus_engine_desc_args_to_propstr(ibus_engine_desc_get_name(engine_desc), ibus_engine_desc_get_language(engine_desc), ibus_engine_desc_get_longname(engine_desc), ibus_engine_desc_get_icon(engine_desc), ibus_engine_desc_get_description(engine_desc)); } static void impanel_get_default_engine(IBusPanelImpanel* impanel, char*** pengine_names, gsize* plen) { GList* engines = ibus_bus_list_engines(impanel->bus); if (!engines) { *pengine_names = g_new0 (gchar*, 2); *plen = 1; (*pengine_names)[0] = g_strdup ("xkb:us::eng"); return; } QList engineList; impanel->xkbLayoutManager->getLayout(); QStringList layouts = impanel->xkbLayoutManager->defaultLayout().split(','); QStringList variants = impanel->xkbLayoutManager->defaultVariant().split(','); for (int i = 0; i < layouts.size(); i ++ ) { QString variant; if (i < variants.size()) { variant = variants[i]; } for (GList* engine = g_list_first(engines); engine != nullptr ; engine = g_list_next(engine)) { IBusEngineDesc* desc = IBUS_ENGINE_DESC (engine->data); QByteArray name = ibus_engine_desc_get_name(desc); if (!name.startsWith("xkb:")) { continue; } if (QLatin1String(ibus_engine_desc_get_layout(desc)) == layouts[i] && QLatin1String(ibus_engine_desc_get_layout_variant(desc)) == variant) { engineList << name; } } } const char* locale = setlocale(LC_CTYPE, nullptr); if (!locale) { locale = "C"; } QStringList localeList = QString(locale).split('.'); QString lang = localeList.size() > 0 ? localeList[0] : ""; bool added = false; for (GList* engine = g_list_first(engines); engine != nullptr ; engine = g_list_next(engine)) { IBusEngineDesc* desc = IBUS_ENGINE_DESC (engine->data); QByteArray name = ibus_engine_desc_get_name(desc); if (name.startsWith("xkb:")) { continue; } if (QLatin1String(ibus_engine_desc_get_language(desc)) == lang && ibus_engine_desc_get_rank(desc) > 0) { engineList << name; added = true; } } if (!added) { localeList = QString(lang).split('_'); QString lang = localeList.size() > 0 ? localeList[0] : ""; for (GList* engine = g_list_first(engines); engine != nullptr ; engine = g_list_next(engine)) { IBusEngineDesc* desc = IBUS_ENGINE_DESC (engine->data); QByteArray name = ibus_engine_desc_get_name(desc); if (name.startsWith("xkb:")) { continue; } if (QLatin1String(ibus_engine_desc_get_language(desc)) == lang && ibus_engine_desc_get_rank(desc) > 0) { engineList << name; } } } for (GList* engine = g_list_first(engines); engine != nullptr ; engine = g_list_next(engine)) { IBusEngineDesc* desc = IBUS_ENGINE_DESC (engine->data); g_object_unref(desc); } g_list_free(engines); if (engineList.size() == 0) { *pengine_names = g_new0 (gchar*, 2); *plen = 1; (*pengine_names)[0] = g_strdup ("xkb:us::eng"); return; } else { *pengine_names = g_new0 (gchar*, engineList.size() + 1); *plen = engineList.size(); size_t i = 0; Q_FOREACH(const QByteArray& name, engineList) { (*pengine_names)[i] = g_strdup (name.constData()); i ++; } } } bool contains(gchar** strlist, const gchar* str) { for (; strlist; ++strlist) { if (g_strcmp0(*strlist, str) == 0) return true; } return false; } static void impanel_update_engines(IBusPanelImpanel* impanel, GVariant* var_engines) { gchar** engine_names = nullptr; size_t len = 0; if (var_engines) { engine_names = g_variant_dup_strv(var_engines, &len); } if (len == 0) { g_strfreev(engine_names); engine_names = nullptr; } if (!engine_names) { impanel_get_default_engine(impanel, &engine_names, &len); GVariant* var = g_variant_new_strv(engine_names, len); g_settings_set_value(impanel->settings_general, "preload-engines", var); } IBusEngineDesc** engines = ibus_bus_get_engines_by_names(impanel->bus, engine_names); impanel->engineManager->setEngines(engines); if (engines && engines[0] && (!ibus_bus_get_global_engine(impanel->bus) || !contains(engine_names, ibus_engine_desc_get_name(ibus_bus_get_global_engine(impanel->bus))))) { ibus_bus_set_global_engine(impanel->bus, ibus_engine_desc_get_name(engines[0])); } g_strfreev(engine_names); impanel->app->setDoGrab(len > 1); } static void impanel_update_engines_order(IBusPanelImpanel* impanel, GVariant* var_engines) { const gchar** engine_names = nullptr; size_t len = 0; engine_names = g_variant_get_strv(var_engines, &len); if (len) { impanel->engineManager->setOrder(engine_names, len); if (impanel->engineManager->engines()) { ibus_bus_set_global_engine(impanel->bus, ibus_engine_desc_get_name(impanel->engineManager->engines()[0])); } } g_free(engine_names); } static void impanel_update_triggers(IBusPanelImpanel* impanel, GVariant* variant) { gchar** triggers = nullptr; size_t len = 0; if (variant) { triggers = g_variant_dup_strv(variant, &len); } if (len == 0) { g_strfreev(triggers); triggers = nullptr; } if (!triggers) { triggers = g_new0 (gchar*, 2); len = 1; triggers[0] = g_strdup ("space"); } QList > triggersList; for (size_t i = 0; i < len; i ++) { guint key = 0; GdkModifierType mod = (GdkModifierType) 0; _gtk_accelerator_parse(triggers[i], &key, &mod); if (key) { triggersList << qMakePair(key, (uint) mod); } } impanel->app->setTriggerKeys(triggersList); } static void impanel_update_use_system_keyboard_layout(IBusPanelImpanel* impanel, GVariant* variant) { impanel->useSystemKeyboardLayout = g_variant_get_boolean(variant); } static void impanel_update_use_global_engine(IBusPanelImpanel* impanel, GVariant* variant) { impanel->engineManager->setUseGlobalEngine(g_variant_get_boolean(variant)); } static void impanel_update_latin_layouts(IBusPanelImpanel* impanel, GVariant* variant) { if (!variant) { return; } gsize length; const gchar** variants = g_variant_get_strv(variant, &length); impanel->xkbLayoutManager->setLatinLayouts(variants, length); g_free(variants); } static void impanel_settings_changed_callback (GSettings* settings, const gchar* key, gpointer user_data) { IBusPanelImpanel* impanel = ((IBusPanelImpanel *)user_data); gchar *schema = nullptr; GVariant *value = g_settings_get_value (settings, key); g_object_get(G_OBJECT(settings), "schema", &schema, NULL); if (g_strcmp0(schema, IBUS_SCHEMA_GENERAL) == 0 && g_strcmp0(key, "preload-engines") == 0) { impanel_update_engines(impanel, value); } else if (g_strcmp0(schema, IBUS_SCHEMA_HOTKEY) == 0 && g_strcmp0(key, "triggers") == 0) { impanel_update_triggers(impanel, value); } else if (g_strcmp0(schema, IBUS_SCHEMA_GENERAL) == 0 && g_strcmp0(key, "use-system-keyboard-layout") == 0) { impanel_update_use_system_keyboard_layout(impanel, value); } else if (g_strcmp0(schema, IBUS_SCHEMA_GENERAL) == 0 && g_strcmp0(key, "use-global-engine") == 0) { impanel_update_use_global_engine(impanel, value); } g_free (schema); } static void impanel_exit_callback (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { Q_UNUSED(connection); Q_UNUSED(sender_name); Q_UNUSED(object_path); Q_UNUSED(interface_name); Q_UNUSED(signal_name); Q_UNUSED(parameters); IBusPanelImpanel* impanel = ((IBusPanelImpanel *)user_data); if (impanel->bus) { ibus_bus_exit(impanel->bus, FALSE); } } static void impanel_panel_created_callback (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { Q_UNUSED(connection); Q_UNUSED(sender_name); Q_UNUSED(object_path); Q_UNUSED(interface_name); Q_UNUSED(signal_name); Q_UNUSED(parameters); IBusPanelImpanel* impanel = ((IBusPanelImpanel *)user_data); ibus_panel_impanel_real_register_properties(impanel); } static void impanel_set_engine(IBusPanelImpanel* impanel, const char* name) { if (!name || !name[0]) { return; } if (ibus_bus_set_global_engine(impanel->bus, name)) { if (!impanel->useSystemKeyboardLayout) { IBusEngineDesc* engine_desc = ibus_bus_get_global_engine(impanel->bus); if (engine_desc) { impanel->xkbLayoutManager->setLayout(engine_desc); } g_object_unref(engine_desc); } impanel->engineManager->setCurrentEngine(name); } else { qDebug() << "set engine failed."; } } static void impanel_trigger_property_callback (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { Q_UNUSED(connection); Q_UNUSED(sender_name); Q_UNUSED(object_path); Q_UNUSED(interface_name); Q_UNUSED(signal_name); IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(user_data); gchar *s0 = nullptr; g_variant_get (parameters, "(s)", &s0); if (!s0 || strlen(s0) <= 6) return; QByteArray prop_key(s0 + 6);// +6 to skip "/IBus/" prop_key.replace('!', ':'); if (g_ascii_strncasecmp (prop_key.constData(), "Logo", 4) == 0) ibus_panel_impanel_exec_im_menu(impanel); else if (g_ascii_strncasecmp (prop_key.constData(), "Engine/", 7) == 0) { impanel_set_engine(impanel, prop_key.constData() + 7); } else { IBusProperty* property = impanel->propManager->property(prop_key.constData()); if (property) { IBusPropState newstate = ibus_property_get_state(property); switch (ibus_property_get_prop_type(property)) { case PROP_TYPE_RADIO: case PROP_TYPE_TOGGLE: if (ibus_property_get_prop_type(property) == PROP_TYPE_TOGGLE) { if (newstate == PROP_STATE_CHECKED) newstate = PROP_STATE_UNCHECKED; else if (newstate == PROP_STATE_UNCHECKED) newstate = PROP_STATE_CHECKED; } else if (ibus_property_get_prop_type(property) == PROP_TYPE_RADIO) { newstate = PROP_STATE_CHECKED; } + Q_FALLTHROUGH(); case PROP_TYPE_NORMAL: ibus_property_set_state(property, newstate); ibus_panel_service_property_activate((IBusPanelService *)impanel, prop_key.constData(), newstate); break; case PROP_TYPE_MENU: ibus_panel_impanel_exec_menu(impanel, ibus_property_get_sub_props(property)); case PROP_TYPE_SEPARATOR: break; default: break; } } else { ibus_panel_service_property_activate((IBusPanelService *)impanel, prop_key.constData(), PROP_STATE_CHECKED); } } g_free(s0); } static void impanel_select_candidate_callback (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { Q_UNUSED(connection); Q_UNUSED(sender_name); Q_UNUSED(object_path); Q_UNUSED(interface_name); Q_UNUSED(signal_name); gint i; g_variant_get (parameters, "(i)", &i); IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(user_data); ibus_panel_service_candidate_clicked((IBusPanelService *)impanel, i, 0, 0); } static void impanel_prev_page_callback (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { Q_UNUSED(connection); Q_UNUSED(sender_name); Q_UNUSED(object_path); Q_UNUSED(interface_name); Q_UNUSED(signal_name); Q_UNUSED(parameters); IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(user_data); ibus_panel_service_page_up((IBusPanelService *)impanel); } static void impanel_next_page_callback (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { Q_UNUSED(connection); Q_UNUSED(sender_name); Q_UNUSED(object_path); Q_UNUSED(interface_name); Q_UNUSED(signal_name); Q_UNUSED(parameters); IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(user_data); ibus_panel_service_page_down((IBusPanelService *)impanel); } static void impanel_configure_callback (GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data) { Q_UNUSED(connection); Q_UNUSED(sender_name); Q_UNUSED(object_path); Q_UNUSED(interface_name); Q_UNUSED(signal_name); Q_UNUSED(parameters); Q_UNUSED(user_data); pid_t pid = fork(); if (pid == 0) { execlp ("ibus-setup", "ibus-setup", (char *)nullptr); exit (0); } } static void on_bus_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { Q_UNUSED(name); IBusPanelImpanel* impanel = ((IBusPanelImpanel *)user_data); impanel->conn = connection; g_dbus_connection_register_object (connection, "/kimpanel", introspection_data->interfaces[0], nullptr, /*&interface_vtable*/ nullptr, /* user_data */ nullptr, /* user_data_free_func */ nullptr); /* GError** */ g_dbus_connection_signal_subscribe (connection, "org.kde.impanel", "org.kde.impanel", "TriggerProperty", "/org/kde/impanel", nullptr, G_DBUS_SIGNAL_FLAGS_NONE, impanel_trigger_property_callback, user_data, nullptr); g_dbus_connection_signal_subscribe (connection, "org.kde.impanel", "org.kde.impanel", "SelectCandidate", "/org/kde/impanel", nullptr, G_DBUS_SIGNAL_FLAGS_NONE, impanel_select_candidate_callback, user_data, nullptr); g_dbus_connection_signal_subscribe (connection, "org.kde.impanel", "org.kde.impanel", "LookupTablePageUp", "/org/kde/impanel", nullptr, G_DBUS_SIGNAL_FLAGS_NONE, impanel_prev_page_callback, user_data, nullptr); g_dbus_connection_signal_subscribe (connection, "org.kde.impanel", "org.kde.impanel", "LookupTablePageDown", "/org/kde/impanel", nullptr, G_DBUS_SIGNAL_FLAGS_NONE, impanel_next_page_callback, user_data, nullptr); g_dbus_connection_signal_subscribe (connection, "org.kde.impanel", "org.kde.impanel", "PanelCreated", "/org/kde/impanel", nullptr, G_DBUS_SIGNAL_FLAGS_NONE, impanel_panel_created_callback, user_data, nullptr); g_dbus_connection_signal_subscribe (connection, "org.kde.impanel", "org.kde.impanel", "Exit", "/org/kde/impanel", nullptr, G_DBUS_SIGNAL_FLAGS_NONE, impanel_exit_callback, user_data, nullptr); g_dbus_connection_signal_subscribe (connection, "org.kde.impanel", "org.kde.impanel", "Configure", "/org/kde/impanel", nullptr, G_DBUS_SIGNAL_FLAGS_NONE, impanel_configure_callback, user_data, nullptr); GVariant* var_engines = g_settings_get_value(impanel->settings_general, "preload-engines"); impanel_update_engines(impanel, var_engines); if (var_engines) { g_variant_unref(var_engines); } var_engines = g_settings_get_value(impanel->settings_general, "engines-order"); if (var_engines) { impanel_update_engines_order(impanel, var_engines); g_variant_unref(var_engines); } GVariant* var_triggers = g_settings_get_value(impanel->settings_hotkey, "triggers"); impanel_update_triggers(impanel, var_triggers); if (var_triggers) { g_variant_unref(var_triggers); } GVariant* var_layouts = g_settings_get_value(impanel->settings_general, "xkb-latin-layouts"); if (var_layouts) { impanel_update_latin_layouts(impanel, var_layouts); g_variant_unref(var_layouts); } GVariant* var = g_settings_get_value(impanel->settings_general, "use-system-keyboard-layout"); if (var) { impanel_update_use_system_keyboard_layout(impanel, var); g_variant_unref(var); } var = g_settings_get_value(impanel->settings_general, "use-global-engine"); if (var) { impanel_update_use_global_engine(impanel, var); g_variant_unref(var); } ibus_panel_impanel_real_register_properties(impanel); } static void on_name_acquired (GDBusConnection *connection, const gchar *name, gpointer user_data) { Q_UNUSED(connection); Q_UNUSED(name); Q_UNUSED(user_data); } static void on_name_lost (GDBusConnection *connection, const gchar *name, gpointer user_data) { Q_UNUSED(connection); Q_UNUSED(name); Q_UNUSED(user_data); exit (1); } G_DEFINE_TYPE (IBusPanelImpanel, ibus_panel_impanel, IBUS_TYPE_PANEL_SERVICE) static void ibus_panel_impanel_class_init (IBusPanelImpanelClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); IBUS_OBJECT_CLASS (object_class)->destroy = (IBusObjectDestroyFunc) ibus_panel_impanel_destroy; IBUS_PANEL_SERVICE_CLASS (object_class)->focus_in = ibus_panel_impanel_focus_in; IBUS_PANEL_SERVICE_CLASS (object_class)->focus_out = ibus_panel_impanel_focus_out; IBUS_PANEL_SERVICE_CLASS (object_class)->register_properties = ibus_panel_impanel_register_properties; IBUS_PANEL_SERVICE_CLASS (object_class)->set_cursor_location = ibus_panel_impanel_set_cursor_location; IBUS_PANEL_SERVICE_CLASS (object_class)->update_auxiliary_text = ibus_panel_impanel_update_auxiliary_text; IBUS_PANEL_SERVICE_CLASS (object_class)->update_lookup_table = ibus_panel_impanel_update_lookup_table; IBUS_PANEL_SERVICE_CLASS (object_class)->update_preedit_text = ibus_panel_impanel_update_preedit_text; IBUS_PANEL_SERVICE_CLASS (object_class)->update_property = ibus_panel_impanel_update_property; IBUS_PANEL_SERVICE_CLASS (object_class)->cursor_down_lookup_table = ibus_panel_impanel_cursor_down_lookup_table; IBUS_PANEL_SERVICE_CLASS (object_class)->cursor_up_lookup_table = ibus_panel_impanel_cursor_up_lookup_table; IBUS_PANEL_SERVICE_CLASS (object_class)->hide_auxiliary_text = ibus_panel_impanel_hide_auxiliary_text; IBUS_PANEL_SERVICE_CLASS (object_class)->hide_language_bar = ibus_panel_impanel_hide_language_bar; IBUS_PANEL_SERVICE_CLASS (object_class)->hide_lookup_table = ibus_panel_impanel_hide_lookup_table; IBUS_PANEL_SERVICE_CLASS (object_class)->hide_preedit_text = ibus_panel_impanel_hide_preedit_text; IBUS_PANEL_SERVICE_CLASS (object_class)->page_down_lookup_table = ibus_panel_impanel_page_down_lookup_table; IBUS_PANEL_SERVICE_CLASS (object_class)->page_up_lookup_table = ibus_panel_impanel_page_up_lookup_table; IBUS_PANEL_SERVICE_CLASS (object_class)->reset = ibus_panel_impanel_reset; IBUS_PANEL_SERVICE_CLASS (object_class)->show_auxiliary_text = ibus_panel_impanel_show_auxiliary_text; IBUS_PANEL_SERVICE_CLASS (object_class)->show_language_bar = ibus_panel_impanel_show_language_bar; IBUS_PANEL_SERVICE_CLASS (object_class)->show_lookup_table = ibus_panel_impanel_show_lookup_table; IBUS_PANEL_SERVICE_CLASS (object_class)->show_preedit_text = ibus_panel_impanel_show_preedit_text; IBUS_PANEL_SERVICE_CLASS (object_class)->start_setup = ibus_panel_impanel_start_setup; IBUS_PANEL_SERVICE_CLASS (object_class)->state_changed = ibus_panel_impanel_state_changed; } static void ibus_panel_impanel_init (IBusPanelImpanel *impanel) { impanel->bus = nullptr; impanel->app = nullptr; impanel->useSystemKeyboardLayout = false; impanel->selected = -1; introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, nullptr); owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, "org.kde.kimpanel.inputmethod", G_BUS_NAME_OWNER_FLAGS_REPLACE, on_bus_acquired, on_name_acquired, on_name_lost, impanel, nullptr); impanel->propManager = new PropertyManager; impanel->engineManager = new EngineManager; impanel->xkbLayoutManager = new XkbLayoutManager; impanel->settings_general = g_settings_new (IBUS_SCHEMA_GENERAL); impanel->settings_hotkey = g_settings_new (IBUS_SCHEMA_HOTKEY); g_signal_connect(impanel->settings_general, "changed", G_CALLBACK (impanel_settings_changed_callback), impanel); g_signal_connect(impanel->settings_hotkey, "changed", G_CALLBACK (impanel_settings_changed_callback), impanel); } static void ibus_panel_impanel_destroy (IBusPanelImpanel *impanel) { delete impanel->propManager; impanel->propManager = nullptr; delete impanel->engineManager; impanel->engineManager = nullptr; delete impanel->xkbLayoutManager; impanel->xkbLayoutManager = nullptr; g_signal_handlers_disconnect_by_func (impanel->settings_general, (gpointer)impanel_settings_changed_callback, impanel); g_signal_handlers_disconnect_by_func (impanel->settings_hotkey, (gpointer)impanel_settings_changed_callback, impanel); g_clear_object (&impanel->settings_general); g_clear_object (&impanel->settings_hotkey); g_bus_unown_name (owner_id); g_dbus_node_info_unref (introspection_data); IBUS_OBJECT_CLASS (ibus_panel_impanel_parent_class)->destroy ((IBusObject *)impanel); } static void ibus_panel_impanel_focus_in (IBusPanelService *panel, const gchar *input_context_path) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL (panel); if (impanel->app->keyboardGrabbed()) { return; } auto engine_desc = ibus_bus_get_global_engine(impanel->bus); if (engine_desc) { impanel_update_logo_by_engine(impanel, engine_desc); g_object_unref(engine_desc); } impanel->engineManager->setCurrentContext(input_context_path); if (!impanel->engineManager->useGlobalEngine()) { impanel_set_engine(impanel, impanel->engineManager->currentEngine().toUtf8().constData()); } } static void ibus_panel_impanel_focus_out (IBusPanelService *panel, const gchar *input_context_path) { Q_UNUSED(panel); Q_UNUSED(input_context_path); IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL (panel); if (impanel->app->keyboardGrabbed()) { return; } if (impanel->engineManager->useGlobalEngine()) { return; } impanel->engineManager->setCurrentContext(""); } static void ibus_panel_impanel_register_properties (IBusPanelService *panel, IBusPropList *prop_list) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL (panel); impanel->propManager->setProperties(prop_list); ibus_panel_impanel_real_register_properties(impanel); } static void ibus_panel_impanel_real_register_properties(IBusPanelImpanel* impanel) { if (!impanel->conn) return; IBusProperty* property = nullptr; guint i = 0; GVariantBuilder builder; g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); if (impanel->selected >= 0 && static_cast(impanel->selected) < impanel->engineManager->length()) { auto engine_desc = impanel->engineManager->engines()[impanel->selected]; QByteArray propstr = ibus_engine_desc_to_logo_propstr(engine_desc); g_variant_builder_add (&builder, "s", propstr.constData()); } else { QByteArray propstr; auto engine_desc = ibus_bus_get_global_engine(impanel->bus); if (engine_desc) { propstr = ibus_engine_desc_to_logo_propstr(engine_desc); g_variant_builder_add (&builder, "s", propstr.constData()); g_object_unref(engine_desc); } IBusPropList* prop_list = impanel->propManager->properties(); if (prop_list) { while ( ( property = ibus_prop_list_get( prop_list, i ) ) != nullptr ) { propstr = ibus_property_to_propstr(property, TRUE); g_variant_builder_add (&builder, "s", propstr.constData()); ++i; } } } g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "RegisterProperties", (g_variant_new ("(as)", &builder)), nullptr); } static void ibus_panel_impanel_set_cursor_location (IBusPanelService *panel, gint x, gint y, gint w, gint h) { g_dbus_connection_call(IBUS_PANEL_IMPANEL (panel)->conn, "org.kde.impanel", "/org/kde/impanel", "org.kde.impanel2", "SetSpotRect", (g_variant_new("(iiii)", x, y, w, h)), nullptr, G_DBUS_CALL_FLAGS_NONE, -1, /* timeout */ nullptr, nullptr, nullptr); } static void ibus_panel_impanel_update_auxiliary_text (IBusPanelService *panel, IBusText *text, gboolean visible) { const gchar* t = ibus_text_get_text (text); const gchar *attr = ""; IBusPanelImpanel* impanel = (IBusPanelImpanel*) panel; if (!impanel->conn) return; g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "UpdateAux", (g_variant_new ("(ss)", t, attr)), nullptr); if (visible == 0) ibus_panel_impanel_hide_auxiliary_text(panel); else ibus_panel_impanel_show_auxiliary_text(panel); } static void ibus_panel_impanel_update_lookup_table (IBusPanelService *panel, IBusLookupTable *lookup_table, gboolean visible) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(panel); if (!impanel->conn) return; guint page_size = ibus_lookup_table_get_page_size(lookup_table); guint cursor_pos = ibus_lookup_table_get_cursor_pos(lookup_table); guint page = cursor_pos / page_size; guint start = page * page_size; guint end = start + page_size; guint num = ibus_lookup_table_get_number_of_candidates(lookup_table); if (end > num) { end = num; } // fprintf(stderr, "%d ~ %d pgsize %d num %d\n", start, end, page_size, num); guint i; gchar label[16][4];// WARNING large enough I think --- nihui const gchar *candidate; GVariantBuilder builder_labels; GVariantBuilder builder_candidates; GVariantBuilder builder_attrs; g_variant_builder_init (&builder_labels, G_VARIANT_TYPE ("as")); g_variant_builder_init (&builder_candidates, G_VARIANT_TYPE ("as")); g_variant_builder_init (&builder_attrs, G_VARIANT_TYPE ("as")); const gchar *attr = ""; for (i = start; i < end; i++) { g_snprintf (label[i-start], 4, "%d", (i-start+1) % 10); // NOTE ibus always return NULL for ibus_lookup_table_get_label // label = ibus_lookup_table_get_label(lookup_table, i)->text; g_variant_builder_add (&builder_labels, "s", label[i-start]); candidate = ibus_text_get_text (ibus_lookup_table_get_candidate (lookup_table, i)); g_variant_builder_add (&builder_candidates, "s", candidate); g_variant_builder_add (&builder_attrs, "s", attr); } gboolean has_prev = 1; gboolean has_next = 1; guint cursor_pos_in_page; if (ibus_lookup_table_is_cursor_visible(lookup_table)) cursor_pos_in_page = cursor_pos % page_size; else cursor_pos_in_page = -1; gint orientation = ibus_lookup_table_get_orientation(lookup_table); if (orientation == IBUS_ORIENTATION_HORIZONTAL) { orientation = 2; } else if (orientation == IBUS_ORIENTATION_VERTICAL) { orientation = 1; } else { orientation = 0; } g_dbus_connection_call(impanel->conn, "org.kde.impanel", "/org/kde/impanel", "org.kde.impanel2", "SetLookupTable", (g_variant_new ("(asasasbbii)", &builder_labels, &builder_candidates, &builder_attrs, has_prev, has_next, cursor_pos_in_page, orientation)), nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr, nullptr); if (visible == 0) ibus_panel_impanel_hide_lookup_table(panel); else ibus_panel_impanel_show_lookup_table(panel); } static void ibus_panel_impanel_update_preedit_text (IBusPanelService *panel, IBusText *text, guint cursor_pos, gboolean visible) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(panel); if (!impanel->conn) return; const gchar* t = ibus_text_get_text (text); const gchar *attr = ""; g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "UpdatePreeditText", (g_variant_new ("(ss)", t, attr)), nullptr); g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "UpdatePreeditCaret", (g_variant_new ("(i)", cursor_pos)), nullptr); if (visible == 0) ibus_panel_impanel_hide_preedit_text(panel); else ibus_panel_impanel_show_preedit_text(panel); } static void ibus_panel_impanel_update_property (IBusPanelService *panel, IBusProperty *prop) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(panel); if (!impanel->conn) return; impanel->propManager->updateProperty(prop); QByteArray propstr = ibus_property_to_propstr(prop, TRUE); g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "UpdateProperty", (g_variant_new ("(s)", propstr.constData())), nullptr); } static void ibus_panel_impanel_cursor_down_lookup_table (IBusPanelService *panel) { Q_UNUSED(panel); } static void ibus_panel_impanel_cursor_up_lookup_table (IBusPanelService *panel) { Q_UNUSED(panel); } static void ibus_panel_impanel_hide_auxiliary_text (IBusPanelService *panel) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(panel); if (!impanel->conn) return; gboolean toShow = 0; g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "ShowAux", (g_variant_new ("(b)", toShow)), nullptr); } static void ibus_panel_impanel_hide_language_bar (IBusPanelService *panel) { Q_UNUSED(panel); } static void ibus_panel_impanel_hide_lookup_table (IBusPanelService *panel) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(panel); if (!impanel->conn) return; gboolean toShow = 0; g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "ShowLookupTable", (g_variant_new ("(b)", toShow)), nullptr); } static void ibus_panel_impanel_hide_preedit_text (IBusPanelService *panel) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(panel); if (!impanel->conn) return; gboolean toShow = 0; g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "ShowPreedit", (g_variant_new ("(b)", toShow)), nullptr); } static void ibus_panel_impanel_page_down_lookup_table (IBusPanelService *panel) { Q_UNUSED(panel); } static void ibus_panel_impanel_page_up_lookup_table (IBusPanelService *panel) { Q_UNUSED(panel); } static void ibus_panel_impanel_reset (IBusPanelService *panel) { Q_UNUSED(panel); } static void ibus_panel_impanel_show_auxiliary_text (IBusPanelService *panel) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(panel); if (!impanel->conn) return; gboolean toShow = 1; g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "ShowAux", (g_variant_new ("(b)", toShow)), nullptr); } static void ibus_panel_impanel_show_language_bar (IBusPanelService *panel) { Q_UNUSED(panel); } static void ibus_panel_impanel_show_lookup_table (IBusPanelService *panel) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(panel); if (!impanel->conn) return; gboolean toShow = 1; g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "ShowLookupTable", (g_variant_new ("(b)", toShow)), nullptr); } static void ibus_panel_impanel_show_preedit_text (IBusPanelService *panel) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(panel); if (!impanel->conn) return; gboolean toShow = 1; g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "ShowPreedit", (g_variant_new ("(b)", toShow)), nullptr); } static void ibus_panel_impanel_start_setup (IBusPanelService *panel) { Q_UNUSED(panel); } static void ibus_panel_impanel_state_changed (IBusPanelService *panel) { IBusPanelImpanel* impanel = IBUS_PANEL_IMPANEL(panel); if (!impanel->conn) return; if (impanel->app->keyboardGrabbed()) { return; } IBusEngineDesc *engine_desc = ibus_bus_get_global_engine(impanel->bus); if (!engine_desc) { return; } impanel_update_logo_by_engine(impanel, engine_desc); g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "Enable", (g_variant_new ("(b)", TRUE)), nullptr); impanel->engineManager->moveToFirst(engine_desc); QStringList engineList = impanel->engineManager->engineOrder(); gchar** engine_names = g_new0 (gchar*, engineList.size() + 1); size_t i = 0; Q_FOREACH(const QString& name, engineList) { engine_names[i] = g_strdup (name.toUtf8().constData()); i ++; } GVariant* var = g_variant_new_strv(engine_names, engineList.size()); g_settings_set_value(impanel->settings_general, "engines-order", var); g_strfreev(engine_names); g_object_unref(engine_desc); } static void ibus_panel_impanel_exec_menu(IBusPanelImpanel* impanel, IBusPropList* prop_list) { if (!impanel->conn) return; GVariantBuilder builder; g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); int i = 0; while (true) { IBusProperty* prop = ibus_prop_list_get(prop_list, i); if (!prop) break; QByteArray propstr = ibus_property_to_propstr(prop); g_variant_builder_add (&builder, "s", propstr.constData()); i ++; } g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "ExecMenu", (g_variant_new ("(as)", &builder)), nullptr); } static void ibus_panel_impanel_exec_im_menu (IBusPanelImpanel* impanel) { if (!impanel->conn) return; GVariantBuilder builder; g_variant_builder_init (&builder, G_VARIANT_TYPE ("as")); IBusEngineDesc** engines = impanel->engineManager->engines(); if (engines) { int i = 0; while (engines[i]) { QByteArray propstr = ibus_engine_desc_to_propstr(engines[i]); g_variant_builder_add (&builder, "s", propstr.constData()); i ++; } } g_dbus_connection_emit_signal (impanel->conn, nullptr, "/kimpanel", "org.kde.kimpanel.inputmethod", "ExecMenu", (g_variant_new ("(as)", &builder)), nullptr); } IBusPanelImpanel * ibus_panel_impanel_new (GDBusConnection *connection) { IBusPanelImpanel *panel; panel = (IBusPanelImpanel *) g_object_new (IBUS_TYPE_PANEL_IMPANEL, "object-path", IBUS_PATH_PANEL, "connection", connection, NULL); return panel; } diff --git a/applets/pager/plugin/pagermodel.cpp b/applets/pager/plugin/pagermodel.cpp index 5860895fd..c67335a69 100644 --- a/applets/pager/plugin/pagermodel.cpp +++ b/applets/pager/plugin/pagermodel.cpp @@ -1,686 +1,686 @@ /******************************************************************** Copyright 2007 Daniel Laidig Copyright 2012 Luís Gabriel Lima Copyright 2016 Eike Hein 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) any later version. 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 . *********************************************************************/ #include "pagermodel.h" #include "windowmodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace TaskManager; class PagerModel::Private { public: Private(PagerModel *q); ~Private(); static int instanceCount; bool componentComplete = false; PagerType pagerType = VirtualDesktops; bool enabled = false; bool showDesktop = false; bool showOnlyCurrentScreen = false; QRect screenGeometry; WindowTasksModel *tasksModel = nullptr; static ActivityInfo *activityInfo; QMetaObject::Connection activityNumberConn; QMetaObject::Connection activityNamesConn; static VirtualDesktopInfo *virtualDesktopInfo; QMetaObject::Connection virtualDesktopNumberConn; QMetaObject::Connection virtualDesktopNamesConn; QList windowModels; #if HAVE_X11 QList cachedStackingOrder = KWindowSystem::stackingOrder(); #endif void refreshDataSource(); private: PagerModel *q; }; int PagerModel::Private::instanceCount = 0; ActivityInfo *PagerModel::Private::activityInfo = nullptr; VirtualDesktopInfo *PagerModel::Private::virtualDesktopInfo = nullptr; PagerModel::Private::Private(PagerModel *q) : q(q) { ++instanceCount; if (!activityInfo) { activityInfo = new ActivityInfo(); } QObject::connect(activityInfo, &ActivityInfo::numberOfRunningActivitiesChanged, q, &PagerModel::shouldShowPagerChanged); if (!virtualDesktopInfo) { virtualDesktopInfo = new VirtualDesktopInfo(); } QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::numberOfDesktopsChanged, q, &PagerModel::shouldShowPagerChanged); QObject::connect(activityInfo, &ActivityInfo::currentActivityChanged, q, [this]() { if (pagerType == VirtualDesktops && windowModels.count()) { for (auto windowModel : windowModels) { windowModel->setActivity(activityInfo->currentActivity()); } } } ); QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::desktopLayoutRowsChanged, q, &PagerModel::layoutRowsChanged); auto configureScreen = [q](QScreen* screen) { QObject::connect(screen, &QScreen::geometryChanged, q, &PagerModel::pagerItemSizeChanged); q->pagerItemSizeChanged(); }; for (QScreen* screen : qGuiApp->screens()) { configureScreen(screen); } QObject::connect(qGuiApp, &QGuiApplication::screenAdded, q, configureScreen); QObject::connect(qGuiApp, &QGuiApplication::screenRemoved, q, &PagerModel::pagerItemSizeChanged); #if HAVE_X11 QObject::connect(KWindowSystem::self(), &KWindowSystem::stackingOrderChanged, q, [this]() { cachedStackingOrder = KWindowSystem::stackingOrder(); for (auto windowModel : windowModels) { windowModel->refreshStackingOrder(); } } ); #endif } PagerModel::Private::~Private() { --instanceCount; if (!instanceCount) { delete activityInfo; activityInfo = nullptr; delete virtualDesktopInfo; virtualDesktopInfo = nullptr; } } void PagerModel::Private::refreshDataSource() { if (pagerType == VirtualDesktops) { QObject::disconnect(virtualDesktopNumberConn); virtualDesktopNumberConn = QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::numberOfDesktopsChanged, q, [this]() { q->refresh(); }); QObject::disconnect(virtualDesktopNamesConn); virtualDesktopNamesConn = QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::desktopNamesChanged, q, [this]() { if (q->rowCount()) { emit q->dataChanged(q->index(0, 0), q->index(q->rowCount() - 1, 0), QVector{Qt::DisplayRole}); } } ); QObject::disconnect(activityNumberConn); QObject::disconnect(activityNamesConn); QObject::disconnect(activityInfo, &ActivityInfo::currentActivityChanged, q, &PagerModel::currentPageChanged); QObject::connect(virtualDesktopInfo, &VirtualDesktopInfo::currentDesktopChanged, q, &PagerModel::currentPageChanged, Qt::UniqueConnection); } else { QObject::disconnect(activityNumberConn); activityNumberConn = QObject::connect(activityInfo, &ActivityInfo::numberOfRunningActivitiesChanged, q, [this]() { q->refresh(); }); QObject::disconnect(activityNamesConn); activityNamesConn = QObject::connect(activityInfo, &ActivityInfo::namesOfRunningActivitiesChanged, q, [this]() { q->refresh(); }); QObject::disconnect(virtualDesktopNumberConn); QObject::disconnect(virtualDesktopNamesConn); QObject::disconnect(virtualDesktopInfo, &VirtualDesktopInfo::currentDesktopChanged, q, &PagerModel::currentPageChanged); QObject::connect(activityInfo, &ActivityInfo::currentActivityChanged, q, &PagerModel::currentPageChanged, Qt::UniqueConnection); } emit q->currentPageChanged(); } PagerModel::PagerModel(QObject *parent) : QAbstractListModel(parent) , d(new Private(this)) { d->tasksModel = new WindowTasksModel(this); } PagerModel::~PagerModel() { } QHash PagerModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); for (int i = 0; i < e.keyCount(); ++i) { roles.insert(e.value(i), e.key(i)); } return roles; } int PagerModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return d->windowModels.count(); } QVariant PagerModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= d->windowModels.count()) { return QVariant(); } if (role == Qt::DisplayRole) { if (d->pagerType == VirtualDesktops) { return d->virtualDesktopInfo->desktopNames().at(index.row()); } else { QString activityId = d->activityInfo->runningActivities().at(index.row()); return d->activityInfo->activityName(activityId); } } else if (role == TasksModel) { return QVariant::fromValue(d->windowModels.at(index.row())); } return QVariant(); } PagerModel::PagerType PagerModel::pagerType() const { return d->pagerType; } void PagerModel::setPagerType(PagerType type) { if (d->pagerType != type) { d->pagerType = type; refresh(); emit pagerTypeChanged(); emit shouldShowPagerChanged(); } } bool PagerModel::enabled() const { return d->enabled; } void PagerModel::setEnabled(bool enabled) { if (enabled && !d->enabled) { refresh(); d->enabled = true; emit enabledChanged(); } else if (!enabled && d->enabled) { beginResetModel(); disconnect(d->activityNumberConn); disconnect(d->activityNamesConn); disconnect(d->virtualDesktopNumberConn); disconnect(d->virtualDesktopNamesConn); qDeleteAll(d->windowModels); d->windowModels.clear(); endResetModel(); d->enabled = false; emit enabledChanged(); emit countChanged(); } } bool PagerModel::shouldShowPager() const { return (d->pagerType == VirtualDesktops) ? d->virtualDesktopInfo->numberOfDesktops() > 1 : d->activityInfo->numberOfRunningActivities() > 1; } bool PagerModel::showDesktop() const { return d->showDesktop; } void PagerModel::setShowDesktop(bool show) { if (d->showDesktop != show) { d->showDesktop = show; emit showDesktopChanged(); } } bool PagerModel::showOnlyCurrentScreen() const { return d->showOnlyCurrentScreen; } void PagerModel::setShowOnlyCurrentScreen(bool show) { if (d->showOnlyCurrentScreen != show) { d->showOnlyCurrentScreen = show; if (d->screenGeometry.isValid()) { emit pagerItemSizeChanged(); refresh(); } emit showOnlyCurrentScreenChanged(); } } QRect PagerModel::screenGeometry() const { return d->screenGeometry; } void PagerModel::setScreenGeometry(const QRect &geometry) { if (d->screenGeometry != geometry) { d->screenGeometry = geometry; if (d->showOnlyCurrentScreen) { emit pagerItemSizeChanged(); refresh(); } emit showOnlyCurrentScreenChanged(); } } int PagerModel::currentPage() const { if (d->pagerType == VirtualDesktops) { return d->virtualDesktopInfo->desktopIds().indexOf(d->virtualDesktopInfo->currentDesktop()); } else { return d->activityInfo->runningActivities().indexOf(d->activityInfo->currentActivity()); } } int PagerModel::layoutRows() const { return qBound(1, d->virtualDesktopInfo->desktopLayoutRows(), d->virtualDesktopInfo->numberOfDesktops()); } QSize PagerModel::pagerItemSize() const { if (d->showOnlyCurrentScreen && d->screenGeometry.isValid()) { return d->screenGeometry.size(); } QRect totalRect; const auto screens = QGuiApplication::screens(); for (auto screen : screens) { totalRect |= screen->geometry(); } return totalRect.size(); } #if HAVE_X11 QList PagerModel::stackingOrder() const { return d->cachedStackingOrder; } #endif void PagerModel::refresh() { if (!d->componentComplete) { return; } beginResetModel(); d->refreshDataSource(); int modelCount = d->windowModels.count(); const int modelsNeeded = ((d->pagerType == VirtualDesktops) ? d->virtualDesktopInfo->numberOfDesktops() : d->activityInfo->numberOfRunningActivities()); if (modelCount > modelsNeeded) { while (modelCount != modelsNeeded) { delete d->windowModels.takeLast(); --modelCount; } } else if (modelsNeeded > modelCount) { while (modelCount != modelsNeeded) { WindowModel *windowModel = new WindowModel(this); windowModel->setFilterSkipPager(true); windowModel->setFilterByVirtualDesktop(true); windowModel->setFilterByActivity(true); windowModel->setDemandingAttentionSkipsFilters(false); windowModel->setSourceModel(d->tasksModel); d->windowModels.append(windowModel); ++modelCount; } } if (d->pagerType == VirtualDesktops) { int virtualDesktop = 0; for (auto windowModel : d->windowModels) { windowModel->setVirtualDesktop(d->virtualDesktopInfo->desktopIds().at(virtualDesktop)); ++virtualDesktop; windowModel->setActivity(d->activityInfo->currentActivity()); } } else { int activityIndex = 0; const QStringList &runningActivities = d->activityInfo->runningActivities(); for (auto windowModel : d->windowModels) { windowModel->setVirtualDesktop(); windowModel->setActivity(runningActivities.at(activityIndex)); ++activityIndex; } } for (auto windowModel : d->windowModels) { if (d->showOnlyCurrentScreen && d->screenGeometry.isValid()) { windowModel->setScreenGeometry(d->screenGeometry); windowModel->setFilterByScreen(true); } else { windowModel->setFilterByScreen(false); } } endResetModel(); emit countChanged(); } void PagerModel::moveWindow(int window, double x, double y, const QVariant &targetItemId, const QVariant &sourceItemId, qreal widthScaleFactor, qreal heightScaleFactor) { #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { const WId windowId = (WId)window; QPointF dest(x / widthScaleFactor, y / heightScaleFactor); // Don't move windows to negative positions. dest = QPointF(qMax(dest.x(), qreal(0.0)), qMax(dest.y(), qreal(0.0))); // Use _NET_MOVERESIZE_WINDOW rather than plain move, so that the WM knows this is a pager request. NETRootInfo info(QX11Info::connection(), NET::Properties()); const int flags = (0x20 << 12) | (0x03 << 8) | 1; // From tool, x/y, northwest gravity. if (!KWindowSystem::mapViewport()) { KWindowInfo windowInfo(windowId, NET::WMDesktop | NET::WMState, NET::WM2Activities); if (d->pagerType == VirtualDesktops) { if (!windowInfo.onAllDesktops()) { KWindowSystem::setOnDesktop(windowId, targetItemId.toInt()); } } else { const QStringList &runningActivities = d->activityInfo->runningActivities(); - if (targetItemId < runningActivities.length()) { + if (targetItemId.toInt() < runningActivities.length()) { const QString &newActivity = targetItemId.toString(); QStringList activities = windowInfo.activities(); if (!activities.contains(newActivity)) { activities.removeOne(sourceItemId.toString()); activities.append(newActivity); KWindowSystem::setOnActivities(windowId, activities); } } } // Only move the window if it is not full screen and if it is kept within the same desktop. // Moving when dropping between desktop is too annoying due to the small drop area. if (!(windowInfo.state() & NET::FullScreen) && (targetItemId == sourceItemId || windowInfo.onAllDesktops())) { const QPoint &d = dest.toPoint(); info.moveResizeWindowRequest(windowId, flags, d.x(), d.y(), 0, 0); } } else { // setOnDesktop() with viewports is also moving a window, and since it takes a moment // for the WM to do the move, there's a race condition with figuring out how much to move, // so do it only as one move. dest += KWindowSystem::desktopToViewport(targetItemId.toInt(), false); const QPoint &d = KWindowSystem::constrainViewportRelativePosition(dest.toPoint()); info.moveResizeWindowRequest(windowId, flags, d.x(), d.y(), 0, 0); } } #else Q_UNUSED(window) Q_UNUSED(x) Q_UNUSED(y) Q_UNUSED(sourceItemId) #endif if (KWindowSystem::isPlatformWayland()) { if (d->pagerType == VirtualDesktops) { QAbstractItemModel *model = d->windowModels.at(0)->sourceModel(); TaskManager::WindowTasksModel *tasksModel = static_cast(model); for (int i = 0; i < tasksModel->rowCount(); ++i) { const QModelIndex &idx = tasksModel->index(i, 0); if (idx.data(TaskManager::AbstractTasksModel::IsOnAllVirtualDesktops).toBool()) { break; } const QVariantList &winIds = idx.data(TaskManager::AbstractTasksModel::WinIdList).toList(); - if (!winIds.isEmpty() && winIds.at(0).toUInt() == window) { + if (!winIds.isEmpty() && winIds.at(0).toInt() == window) { tasksModel->requestVirtualDesktops(idx, QVariantList() << targetItemId.toString()); break; } } } else { //FIXME TODO: Activities support. } } } void PagerModel::changePage(int page) { if (currentPage() == page) { if (d->showDesktop) { QDBusConnection::sessionBus().asyncCall(QDBusMessage::createMethodCall(QLatin1String("org.kde.plasmashell"), QLatin1String("/PlasmaShell"), QLatin1String("org.kde.PlasmaShell"), QLatin1String("toggleDashboard"))); } } else { if (d->pagerType == VirtualDesktops) { d->virtualDesktopInfo->requestActivate(d->virtualDesktopInfo->desktopIds().at(page)); } else { const QStringList &runningActivities = d->activityInfo->runningActivities(); if (page < runningActivities.length()) { KActivities::Controller activitiesController; activitiesController.setCurrentActivity(runningActivities.at(page)); } } } } void PagerModel::drop(QMimeData *mimeData, int modifiers, const QVariant &itemId) { if (!mimeData) { return; } #if HAVE_X11 if (KWindowSystem::isPlatformX11()) { bool ok; const QList &ids = TaskManager::XWindowTasksModel::winIdsFromMimeData(mimeData, &ok); if (!ok) { return; } if (d->pagerType == VirtualDesktops) { for (const auto &id : ids) { KWindowSystem::setOnDesktop(id, itemId.toInt()); } } else { QString newActivity = itemId.toString(); const QStringList &runningActivities = d->activityInfo->runningActivities(); if (!runningActivities.contains(newActivity)) { return; } for (const auto &id : ids) { QStringList activities = KWindowInfo(id, NET::Properties(), NET::WM2Activities).activities(); if (modifiers & Qt::ControlModifier) { // 'copy' => add to activity if (!activities.contains(newActivity)) activities << newActivity; } else { // 'move' to activity // if on only one activity, set it to only the new activity // if on >1 activity, remove it from the current activity and add it to the new activity const QString currentActivity = d->activityInfo->currentActivity(); activities.removeAll(currentActivity); activities << newActivity; } KWindowSystem::setOnActivities(id, activities); } } return; } #endif if (KWindowSystem::isPlatformWayland()) { bool ok; const QList &ids = TaskManager::WaylandTasksModel::winIdsFromMimeData(mimeData, &ok); if (!ok) { return; } if (d->pagerType == VirtualDesktops) { for (const quint32 &id : ids) { QAbstractItemModel *model = d->windowModels.at(0)->sourceModel(); TaskManager::WindowTasksModel *tasksModel = static_cast(model); for (int i = 0; i < tasksModel->rowCount(); ++i) { const QModelIndex &idx = tasksModel->index(i, 0); if (idx.data(TaskManager::AbstractTasksModel::IsOnAllVirtualDesktops).toBool()) { break; } const QVariantList &winIds = idx.data(TaskManager::AbstractTasksModel::WinIdList).toList(); if (!winIds.isEmpty() && winIds.at(0).toUInt() == id) { tasksModel->requestVirtualDesktops(idx, QVariantList() << itemId.toString()); break; } } } } } } void PagerModel::addDesktop() { d->virtualDesktopInfo->requestCreateDesktop(d->virtualDesktopInfo->numberOfDesktops()); } void PagerModel::removeDesktop() { if (d->virtualDesktopInfo->numberOfDesktops() == 1) { return; } d->virtualDesktopInfo->requestRemoveDesktop(d->virtualDesktopInfo->numberOfDesktops() - 1); } void PagerModel::classBegin() { } void PagerModel::componentComplete() { d->componentComplete = true; if (d->enabled) { refresh(); } } #include "moc_pagermodel.cpp" diff --git a/applets/trash/plugin/dirmodel.cpp b/applets/trash/plugin/dirmodel.cpp index 094642cab..f7f785ba2 100644 --- a/applets/trash/plugin/dirmodel.cpp +++ b/applets/trash/plugin/dirmodel.cpp @@ -1,214 +1,215 @@ /* * Copyright 2012 by Marco Martin * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * 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 Library General Public License for more details * * You should have received a copy of the GNU Library 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. */ #include "dirmodel.h" #include #include #include #include #include #include #include #include #include #include DirModel::DirModel(QObject *parent) : KDirModel(parent), m_screenshotSize(180, 120) { #if 0 // unused here in trash QMimeDatabase db; QList mimeList = db.allMimeTypes(); m_mimeTypes << "inode/directory"; foreach (const QMimeType &mime, mimeList) { if (mime.name().startsWith(QLatin1String("image/"))) { m_mimeTypes << mime.name(); } } //TODO: configurable mime filter //dirLister()->setMimeFilter(m_mimeTypes); #endif m_previewTimer = new QTimer(this); m_previewTimer->setSingleShot(true); connect(m_previewTimer, &QTimer::timeout, this, &DirModel::delayedPreview); //using the same cache of the engine, they index both by url m_imageCache = new KImageCache(QStringLiteral("org.kde.dirmodel-qml"), 10485760); connect(this, &QAbstractItemModel::rowsInserted, this, &DirModel::countChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &DirModel::countChanged); connect(this, &QAbstractItemModel::modelReset, this, &DirModel::countChanged); } DirModel::~DirModel() { delete m_imageCache; } QHash DirModel::roleNames() const { return { { Qt::DisplayRole, "display" }, { Qt::DecorationRole, "decoration" }, { UrlRole, "url" }, { MimeTypeRole, "mimeType" }, { Thumbnail, "thumbnail" } }; } QString DirModel::url() const { return dirLister()->url().toString(); } void DirModel::setUrl(const QString& url) { if (url.isEmpty()) { return; } if (dirLister()->url().path() == url) { dirLister()->updateDirectory(QUrl(url)); return; } beginResetModel(); dirLister()->openUrl(QUrl(url)); endResetModel(); emit urlChanged(); } int DirModel::indexForUrl(const QString &url) const { QModelIndex index = KDirModel::indexForUrl(QUrl(url)); return index.row(); } QVariantMap DirModel::get(int i) const { QModelIndex modelIndex = index(i, 0); KFileItem item = itemForIndex(modelIndex); QString url = item.url().toString(); QString mimeType = item.mimetype(); QVariantMap ret; ret.insert(QStringLiteral("url"), QVariant(url)); ret.insert(QStringLiteral("mimeType"), QVariant(mimeType)); return ret; } void DirModel::emptyTrash() { KIO::emptyTrash(); } QVariant DirModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case UrlRole: { KFileItem item = itemForIndex(index); return item.url().toString(); } case MimeTypeRole: { KFileItem item = itemForIndex(index); return item.mimetype(); } case Thumbnail: { KFileItem item = itemForIndex(index); QImage preview = QImage(m_screenshotSize, QImage::Format_ARGB32_Premultiplied); if (m_imageCache->findImage(item.url().toString(), &preview)) { return preview; } m_previewTimer->start(100); const_cast(this)->m_filesToPreview[item.url()] = QPersistentModelIndex(index); + Q_FALLTHROUGH(); } default: return KDirModel::data(index, role); } } void DirModel::delayedPreview() { QHash::const_iterator i = m_filesToPreview.constBegin(); KFileItemList list; while (i != m_filesToPreview.constEnd()) { QUrl file = i.key(); QPersistentModelIndex index = i.value(); if (!m_previewJobs.contains(file) && file.isValid()) { list.append(KFileItem(file, QString(), 0)); m_previewJobs.insert(file, QPersistentModelIndex(index)); } ++i; } if (!list.isEmpty()) { KIO::PreviewJob* job = KIO::filePreview(list, m_screenshotSize); job->setIgnoreMaximumSize(true); // qDebug() << "Created job" << job; connect(job, &KIO::PreviewJob::gotPreview, this, &DirModel::showPreview); connect(job, &KIO::PreviewJob::failed, this, &DirModel::previewFailed); } m_filesToPreview.clear(); } void DirModel::showPreview(const KFileItem &item, const QPixmap &preview) { QPersistentModelIndex index = m_previewJobs.value(item.url()); m_previewJobs.remove(item.url()); if (!index.isValid()) { return; } m_imageCache->insertImage(item.url().toString(), preview.toImage()); //qDebug() << "preview size:" << preview.size(); emit dataChanged(index, index); } void DirModel::previewFailed(const KFileItem &item) { m_previewJobs.remove(item.url()); } #include "moc_dirmodel.cpp" diff --git a/containments/desktop/plugins/folder/autotests/foldermodeltest.cpp b/containments/desktop/plugins/folder/autotests/foldermodeltest.cpp index 744fce76e..c42bb353d 100644 --- a/containments/desktop/plugins/folder/autotests/foldermodeltest.cpp +++ b/containments/desktop/plugins/folder/autotests/foldermodeltest.cpp @@ -1,384 +1,383 @@ /*************************************************************************** * Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company * * * * Author: Andras Mantia * * * * 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) any later version. * * * * 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 . * ***************************************************************************/ #include "foldermodeltest.h" #include "foldermodel.h" #include "screenmapper.h" #include #include #include QTEST_MAIN(FolderModelTest) static const QLatin1String desktop(QLatin1String("Desktop")); static QUrl stringToUrl(const QString &path) { return QUrl::fromUserInput(path, {}, QUrl::AssumeLocalFile); } void FolderModelTest::createTestFolder(const QString &path) { QDir dir(m_folderDir->path()); dir.mkdir(path); dir.cd(path); dir.mkdir("firstDir"); QFile f; for (int i = 1; i < 10; i++) { f.setFileName(QStringLiteral("%1/file%2.txt").arg(dir.path(), QString::number(i))); f.open(QFile::WriteOnly); f.close(); } } void FolderModelTest::init() { m_folderDir = new QTemporaryDir(); createTestFolder(desktop); m_folderModel = new FolderModel(this); m_folderModel->classBegin(); m_folderModel->setUrl(m_folderDir->path() + QDir::separator() + desktop ); m_folderModel->componentComplete(); QSignalSpy s(m_folderModel, &FolderModel::listingCompleted); s.wait(1000); } void FolderModelTest::cleanup() { delete m_folderDir; m_folderDir = nullptr; delete m_folderModel; m_folderModel = nullptr; } void FolderModelTest::tst_listing() { QCOMPARE(m_folderModel->url(), m_folderDir->path() + QDir::separator() + desktop); const auto count = m_folderModel->rowCount(); QCOMPARE(count, 10); QCOMPARE(m_folderModel->index(0, 0).data(FolderModel::FileNameRole).toString(), QLatin1String("firstDir")); for (int i = 1; i < count; i++) { const auto index = m_folderModel->index(i, 0); QCOMPARE(index.data(FolderModel::FileNameRole).toString(), QStringLiteral("file%1.txt").arg(i)); } } void FolderModelTest::tst_listingDescending() { m_folderModel->setSortDesc(true); QCOMPARE(m_folderModel->index(0, 0).data(FolderModel::FileNameRole).toString(), QLatin1String("firstDir")); const auto count = m_folderModel->rowCount(); for (int i = 1; i < count; i++) { const auto index = m_folderModel->index(i, 0); QCOMPARE(index.data(FolderModel::FileNameRole).toString(), QStringLiteral("file%1.txt").arg(count - i)); } } void FolderModelTest::tst_listingFolderNotFirst() { const auto count = m_folderModel->rowCount(); m_folderModel->setSortDirsFirst(false); QCOMPARE(count, 10); QCOMPARE(m_folderModel->index(9, 0).data(FolderModel::FileNameRole).toString(), QLatin1String("firstDir")); for (int i = 0; i < count - 1; i++) { const auto index = m_folderModel->index(i, 0); QCOMPARE(index.data(FolderModel::FileNameRole).toString(), QStringLiteral("file%1.txt").arg(i + 1)); } } void FolderModelTest::tst_filterListing() { // a little bit weird API, as both pattern and mimetype needs to be set m_folderModel->setFilterPattern("*.txt"); m_folderModel->setFilterMimeTypes({"all/all"}); m_folderModel->setFilterMode(FolderModel::FilterShowMatches); const auto count = m_folderModel->rowCount(); QCOMPARE(count, 9); for (int i = 0; i < count; i++) { const auto index = m_folderModel->index(i, 0); QCOMPARE(index.data(FolderModel::FileNameRole).toString(), QStringLiteral("file%1.txt").arg(i + 1)); } } void FolderModelTest::tst_cd() { QSignalSpy s(m_folderModel, &FolderModel::listingCompleted); //go into firstDir subfolder const auto url = m_folderModel->resolvedUrl(); m_folderModel->cd(0); QVERIFY(s.wait(500)); const auto url2 = m_folderModel->resolvedUrl(); QVERIFY(url.isParentOf(url2)); //go back to Desktop m_folderModel->up(); QVERIFY(s.wait(500)); QCOMPARE(m_folderModel->resolvedUrl(), url); //try to cd to an invalid entry (a file) m_folderModel->cd(1); //Signal is not emitted here as it's invalided QVERIFY(!s.wait(500)); QCOMPARE(m_folderModel->resolvedUrl(), url); } void FolderModelTest::tst_rename_data() { QTest::addColumn("row"); QTest::addColumn("name"); QTest::newRow("Folder rename") << 0 << "firstDirRenamed"; QTest::newRow("File rename") << 1 << "file1.pdf"; QTest::newRow("Invalid rename") << 11 << "foo"; } void FolderModelTest::tst_rename() { QFETCH(int, row); QFETCH(QString, name); m_folderModel->rename(row, name); QSignalSpy s(m_folderModel, &FolderModel::listingCompleted); const auto index = m_folderModel->index(row, 0); s.wait(500); QEXPECT_FAIL("Invalid rename", "This is expected to fail", Continue); QCOMPARE(index.data(FolderModel::FileNameRole).toString(), name); } void FolderModelTest::tst_selection() { m_folderModel->setSelected(1); QVERIFY(m_folderModel->hasSelection()); QVERIFY(m_folderModel->isSelected(1)); m_folderModel->clearSelection(); QVERIFY(!m_folderModel->hasSelection()); m_folderModel->toggleSelected(1); QVERIFY(m_folderModel->isSelected(1)); m_folderModel->toggleSelected(1); QVERIFY(!m_folderModel->isSelected(1)); m_folderModel->setRangeSelected(1, 4); QVERIFY(m_folderModel->hasSelection()); for (int i = 1; i <= 4; i++) { QVERIFY(m_folderModel->isSelected(i)); } m_folderModel->updateSelection({5, 6}, false); for (int i = 1; i <= 4; i++) { QVERIFY(!m_folderModel->isSelected(i)); } QVERIFY(m_folderModel->isSelected(5)); QVERIFY(m_folderModel->isSelected(6)); m_folderModel->setRangeSelected(1, 4); m_folderModel->pinSelection(); m_folderModel->updateSelection({5, 6}, true); for (int i = 1; i <= 6; i++) { QVERIFY(m_folderModel->isSelected(i)); } m_folderModel->unpinSelection(); m_folderModel->updateSelection({5, 6}, true); for (int i = 1; i <= 6; i++) { if (i < 5) { QVERIFY(!m_folderModel->isSelected(i)); } else { QVERIFY(m_folderModel->isSelected(i)); } } } void FolderModelTest::tst_defaultValues() { FolderModel folderModel; QCOMPARE(folderModel.status(), FolderModel::Status::None); QVERIFY(folderModel.locked()); QVERIFY(!folderModel.sortDesc()); QVERIFY(folderModel.sortDirsFirst()); QVERIFY(!folderModel.parseDesktopFiles()); QVERIFY(!folderModel.previews()); QVERIFY(!folderModel.usedByContainment()); QCOMPARE(folderModel.sortMode(), 0); QCOMPARE(folderModel.filterMode(), (int)FolderModel::NoFilter); QVERIFY(folderModel.newMenu()); QCOMPARE(folderModel.filterCaseSensitivity(), Qt::CaseInsensitive); QVERIFY(folderModel.dynamicSortFilter()); QVERIFY(folderModel.isSortLocaleAware()); } void FolderModelTest::tst_actionMenu() { const QStringList lst { QStringLiteral("open"), QStringLiteral("cut"), QStringLiteral("open"), QStringLiteral("cut"), QStringLiteral("undo"), QStringLiteral("copy"), QStringLiteral("paste"), QStringLiteral("pasteto"), QStringLiteral("refresh"), QStringLiteral("rename"), QStringLiteral("trash"), QStringLiteral("del"), QStringLiteral("restoreFromTrash"), QStringLiteral("emptyTrash") }; for (const QString &str : lst) { QVERIFY(m_folderModel->action(str)); } } void FolderModelTest::tst_lockedChanged() { QSignalSpy s(m_folderModel, &FolderModel::lockedChanged); m_folderModel->setLocked(false); QCOMPARE(s.count(), 1); m_folderModel->setLocked(false); QCOMPARE(s.count(), 1); m_folderModel->setLocked(true); QCOMPARE(s.count(), 2); } void FolderModelTest::tst_multiScreen() { delete m_folderModel; // Custom instance for this test to set used by containment before marking component // as complete. m_folderModel = new FolderModel(this); m_folderModel->classBegin(); m_folderModel->setUrl(m_folderDir->path() + QDir::separator() + desktop ); m_folderModel->setUsedByContainment(true); m_folderModel->setScreen(0); m_folderModel->componentComplete(); auto *screenMapper = ScreenMapper::instance(); QSignalSpy s(m_folderModel, &FolderModel::listingCompleted); QVERIFY(s.wait(1000)); const auto count = m_folderModel->rowCount(); for (int i = 0; i < count; i++) { const auto index = m_folderModel->index(i, 0); const auto name = index.data(FolderModel::UrlRole).toUrl(); // all items are on the first screen by default QCOMPARE(screenMapper->screenForItem(name), 0); } // move one file to a new screen const auto movedItem = m_folderModel->index(0, 0).data(FolderModel::UrlRole).toUrl(); FolderModel secondFolderModel; secondFolderModel.classBegin(); secondFolderModel.setUrl(m_folderDir->path() + QDir::separator() + desktop ); secondFolderModel.setUsedByContainment(true); secondFolderModel.setScreen(1); secondFolderModel.componentComplete(); QSignalSpy s2(&secondFolderModel, &FolderModel::listingCompleted); QVERIFY(s2.wait(1000)); const auto count2 = secondFolderModel.rowCount(); QCOMPARE(count2, 0); screenMapper->addMapping(movedItem, 1); m_folderModel->invalidate(); secondFolderModel.invalidate(); s.wait(1000); s2.wait(1000); // we have one less item QCOMPARE(m_folderModel->rowCount(), count - 1); QCOMPARE(secondFolderModel.rowCount(), 1); QCOMPARE(secondFolderModel.index(0,0).data(FolderModel::UrlRole).toUrl(), movedItem); QCOMPARE(screenMapper->screenForItem(movedItem), 1); // remove extra screen, we have all items back screenMapper->removeScreen(1, stringToUrl(m_folderModel->url())); s.wait(500); QCOMPARE(m_folderModel->rowCount(), count); QCOMPARE(secondFolderModel.rowCount(), 0); QCOMPARE(screenMapper->screenForItem(movedItem), 0); // add back extra screen, the item is moved there screenMapper->addScreen(1, stringToUrl(m_folderModel->url())); s.wait(500); s2.wait(500); QCOMPARE(m_folderModel->rowCount(), count - 1); QCOMPARE(secondFolderModel.rowCount(), 1); QCOMPARE(secondFolderModel.index(0,0).data(FolderModel::UrlRole).toUrl(), movedItem); QCOMPARE(screenMapper->screenForItem(movedItem), 1); // create a new item, it appears on the first screen QDir dir(m_folderDir->path()); dir.cd(desktop); dir.mkdir("secondDir"); dir.cd("secondDir"); s.wait(1000); QCOMPARE(m_folderModel->rowCount(), count); QCOMPARE(secondFolderModel.rowCount(), 1); QCOMPARE(screenMapper->screenForItem(stringToUrl("file://" + dir.path())), 0); } void FolderModelTest::tst_multiScreenDifferenPath() { - auto *screenMapper = ScreenMapper::instance(); m_folderModel->setUsedByContainment(true); m_folderModel->setScreen(0); QSignalSpy s(m_folderModel, &FolderModel::listingCompleted); s.wait(1000); const auto count = m_folderModel->rowCount(); QCOMPARE(count, 10); const QLatin1String desktop2(QLatin1String("Desktop2")); createTestFolder(desktop2); FolderModel secondFolderModel; secondFolderModel.setUsedByContainment(true); secondFolderModel.setUrl(m_folderDir->path() + QDir::separator() + desktop2 ); secondFolderModel.setScreen(1); QSignalSpy s2(&secondFolderModel, &FolderModel::listingCompleted); s2.wait(1000); const auto count2 = secondFolderModel.rowCount(); QCOMPARE(count2, 10); // create a new item, it appears on the first screen QDir dir(m_folderDir->path()); dir.cd(desktop); dir.mkdir("secondDir"); s.wait(1000); QCOMPARE(m_folderModel->rowCount(), count + 1); QCOMPARE(secondFolderModel.rowCount(), count2); // create a new item, it appears on the second screen dir.cd(m_folderDir->path() + QDir::separator() + desktop2); dir.mkdir("secondDir2"); s.wait(1000); QCOMPARE(m_folderModel->rowCount(), count + 1); QCOMPARE(secondFolderModel.rowCount(), count2 + 1); } diff --git a/containments/desktop/plugins/folder/foldermodel.cpp b/containments/desktop/plugins/folder/foldermodel.cpp index 731fb2094..a0ecdb552 100644 --- a/containments/desktop/plugins/folder/foldermodel.cpp +++ b/containments/desktop/plugins/folder/foldermodel.cpp @@ -1,2108 +1,2108 @@ /*************************************************************************** * Copyright (C) 2006 David Faure * * Copyright (C) 2008 Fredrik Höglund * * Copyright (C) 2008 Rafael Fernández López * * Copyright (C) 2011 Marco Martin * * Copyright (C) 2014 by Eike Hein * * * * 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) any later version. * * * * 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 . * ***************************************************************************/ #include "foldermodel.h" #include "itemviewadapter.h" #include "positioner.h" #include "screenmapper.h" #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(FOLDERMODEL, "plasma.containments.desktop.folder.foldermodel") DirLister::DirLister(QObject *parent) : KDirLister(parent) { } DirLister:: ~DirLister() { } void DirLister::handleError(KIO::Job *job) { if (!autoErrorHandlingEnabled()) { emit error(job->errorString()); return; } KDirLister::handleError(job); } FolderModel::FolderModel(QObject *parent) : QSortFilterProxyModel(parent), m_dirWatch(nullptr), m_dragInProgress(false), m_urlChangedWhileDragging(false), m_dropTargetPositionsCleanup(new QTimer(this)), m_previewGenerator(nullptr), m_viewAdapter(nullptr), m_actionCollection(this), m_newMenu(nullptr), m_fileItemActions(nullptr), m_usedByContainment(false), m_locked(true), m_sortMode(0), m_sortDesc(false), m_sortDirsFirst(true), m_parseDesktopFiles(false), m_previews(false), m_filterMode(NoFilter), m_filterPatternMatchAll(true), + m_screenUsed(false), m_screenMapper(ScreenMapper::instance()), - m_complete(false), - m_screenUsed(false) + m_complete(false) { //needed to pass the job around with qml qmlRegisterType(); DirLister *dirLister = new DirLister(this); dirLister->setDelayedMimeTypes(true); dirLister->setAutoErrorHandlingEnabled(false, nullptr); connect(dirLister, &DirLister::error, this, &FolderModel::dirListFailed); connect(dirLister, &KCoreDirLister::itemsDeleted, this, &FolderModel::evictFromIsDirCache); connect(dirLister, &KCoreDirLister::started, this, std::bind(&FolderModel::setStatus, this, Status::Listing)); void (KCoreDirLister::*myCompletedSignal)() = &KCoreDirLister::completed; QObject::connect(dirLister, myCompletedSignal, this, [this] { setStatus(Status::Ready); emit listingCompleted(); }); void (KCoreDirLister::*myCanceledSignal)() = &KCoreDirLister::canceled; QObject::connect(dirLister, myCanceledSignal, this, [this] { setStatus(Status::Canceled); emit listingCanceled(); }); m_dirModel = new KDirModel(this); m_dirModel->setDirLister(dirLister); m_dirModel->setDropsAllowed(KDirModel::DropOnDirectory | KDirModel::DropOnLocalExecutable); // If we have dropped items queued for moving, go unsorted now. connect(this, &QAbstractItemModel::rowsAboutToBeInserted, this, [this]() { if (!m_dropTargetPositions.isEmpty()) { setSortMode(-1); } }); // Position dropped items at the desired target position. connect(this, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &parent, int first, int last) { for (int i = first; i <= last; ++i) { const auto idx = index(i, 0, parent); const auto url = itemForIndex(idx).url(); auto it = m_dropTargetPositions.find(url.fileName()); if (it != m_dropTargetPositions.end()) { const auto pos = it.value(); m_dropTargetPositions.erase(it); emit move(pos.x(), pos.y(), {url}); } } }); /* * Dropped files may not actually show up as new files, e.g. when we overwrite * an existing file. Or files that fail to be listed by the dirLister, or... * To ensure we don't grow the map indefinitely, clean it up periodically. * The cleanup timer is (re)started whenever we modify the map. We use a quite * high interval of 10s. This should ensure, that we don't accidentally wipe * the mapping when we actually still want to use it. Since the time between * adding an entry in the map and it showing up in the model should be * small, this should rarely, if ever happen. */ m_dropTargetPositionsCleanup->setInterval(10000); m_dropTargetPositionsCleanup->setSingleShot(true); connect(m_dropTargetPositionsCleanup, &QTimer::timeout, this, [this]() { if (!m_dropTargetPositions.isEmpty()) { qCDebug(FOLDERMODEL) << "clearing drop target positions after timeout:" << m_dropTargetPositions; m_dropTargetPositions.clear(); } }); m_selectionModel = new QItemSelectionModel(this, this); connect(m_selectionModel, &QItemSelectionModel::selectionChanged, this, &FolderModel::selectionChanged); setSourceModel(m_dirModel); setSortLocaleAware(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setDynamicSortFilter(true); sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); createActions(); } FolderModel::~FolderModel() { if (m_usedByContainment) { // disconnect so we don't handle signals from the screen mapper when // removeScreen is called m_screenMapper->disconnect(this); m_screenMapper->removeScreen(m_screen, resolvedUrl()); } } QHash< int, QByteArray > FolderModel::roleNames() const { return staticRoleNames(); } QHash< int, QByteArray > FolderModel::staticRoleNames() { QHash roleNames; roleNames[Qt::DisplayRole] = "display"; roleNames[Qt::DecorationRole] = "decoration"; roleNames[BlankRole] = "blank"; roleNames[OverlaysRole] = "overlays"; roleNames[SelectedRole] = "selected"; roleNames[IsDirRole] = "isDir"; roleNames[IsLinkRole] = "isLink"; roleNames[IsHiddenRole] = "isHidden"; roleNames[UrlRole] = "url"; roleNames[LinkDestinationUrl] = "linkDestinationUrl"; roleNames[SizeRole] = "size"; roleNames[TypeRole] = "type"; return roleNames; } void FolderModel::classBegin() { } void FolderModel::componentComplete() { m_complete = true; invalidate(); } void FolderModel::invalidateIfComplete() { if (!m_complete) { return; } invalidate(); } void FolderModel::invalidateFilterIfComplete() { if (!m_complete) { return; } invalidateFilter(); } void FolderModel::newFileMenuItemCreated(const QUrl &url) { if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { m_screenMapper->addMapping(url, m_screen, ScreenMapper::DelayedSignal); m_dropTargetPositions.insert(url.fileName(), m_menuPosition); m_menuPosition = {}; m_dropTargetPositionsCleanup->start(); } } QString FolderModel::url() const { return m_url; } void FolderModel::setUrl(const QString& url) { const QUrl &resolvedNewUrl = resolve(url); if (url == m_url) { m_dirModel->dirLister()->updateDirectory(resolvedNewUrl); return; } const auto oldUrl = resolvedUrl(); beginResetModel(); m_url = url; m_isDirCache.clear(); m_dirModel->dirLister()->openUrl(resolvedNewUrl); clearDragImages(); m_dragIndexes.clear(); endResetModel(); emit urlChanged(); emit resolvedUrlChanged(); m_errorString.clear(); emit errorStringChanged(); if (m_dirWatch) { delete m_dirWatch; m_dirWatch = nullptr; } if (resolvedNewUrl.isValid()) { m_dirWatch = new KDirWatch(this); connect(m_dirWatch, &KDirWatch::created, this, &FolderModel::iconNameChanged); connect(m_dirWatch, &KDirWatch::dirty, this, &FolderModel::iconNameChanged); m_dirWatch->addFile(resolvedNewUrl.toLocalFile() + QLatin1String("/.directory")); } if (m_dragInProgress) { m_urlChangedWhileDragging = true; } emit iconNameChanged(); if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { m_screenMapper->removeScreen(m_screen, oldUrl); m_screenMapper->addScreen(m_screen, resolvedUrl()); } } QUrl FolderModel::resolvedUrl() const { return m_dirModel->dirLister()->url(); } QUrl FolderModel::resolve(const QString& url) { QUrl resolvedUrl; if (url.startsWith(QLatin1Char('~'))) { resolvedUrl = QUrl::fromLocalFile(KShell::tildeExpand(url)); } else { resolvedUrl = QUrl::fromUserInput(url); } return resolvedUrl; } QString FolderModel::iconName() const { const KFileItem rootItem(m_dirModel->dirLister()->url()); if (!rootItem.isFinalIconKnown()) { rootItem.determineMimeType(); } return rootItem.iconName(); } FolderModel::Status FolderModel::status() const { return m_status; } void FolderModel::setStatus(Status status) { if (m_status != status) { m_status = status; emit statusChanged(); } } QString FolderModel::errorString() const { return m_errorString; } bool FolderModel::dragging() const { return m_dragInProgress; } bool FolderModel::usedByContainment() const { return m_usedByContainment; } void FolderModel::setUsedByContainment(bool used) { if (m_usedByContainment != used) { m_usedByContainment = used; QAction *action = m_actionCollection.action(QStringLiteral("refresh")); if (action) { action->setText(m_usedByContainment ? i18n("&Refresh Desktop") : i18n("&Refresh View")); action->setIcon(m_usedByContainment ? QIcon::fromTheme(QStringLiteral("user-desktop")) : QIcon::fromTheme(QStringLiteral("view-refresh"))); } m_screenMapper->disconnect(this); connect(m_screenMapper, &ScreenMapper::screensChanged, this, &FolderModel::invalidateFilterIfComplete); connect(m_screenMapper, &ScreenMapper::screenMappingChanged, this, &FolderModel::invalidateFilterIfComplete); emit usedByContainmentChanged(); } } bool FolderModel::locked() const { return m_locked; } void FolderModel::setLocked(bool locked) { if (m_locked != locked) { m_locked = locked; emit lockedChanged(); } } void FolderModel::dirListFailed(const QString& error) { m_errorString = error; emit errorStringChanged(); } int FolderModel::sortMode() const { return m_sortMode; } void FolderModel::setSortMode(int mode) { if (m_sortMode != mode) { m_sortMode = mode; if (mode == -1 /* Unsorted */) { setDynamicSortFilter(false); } else { invalidateIfComplete(); sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); setDynamicSortFilter(true); } emit sortModeChanged(); } } bool FolderModel::sortDesc() const { return m_sortDesc; } void FolderModel::setSortDesc(bool desc) { if (m_sortDesc != desc) { m_sortDesc = desc; if (m_sortMode != -1 /* Unsorted */) { invalidateIfComplete(); sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); } emit sortDescChanged(); } } bool FolderModel::sortDirsFirst() const { return m_sortDirsFirst; } void FolderModel::setSortDirsFirst(bool enable) { if (m_sortDirsFirst != enable) { m_sortDirsFirst = enable; if (m_sortMode != -1 /* Unsorted */) { invalidateIfComplete(); sort(m_sortMode, m_sortDesc ? Qt::DescendingOrder : Qt::AscendingOrder); } emit sortDirsFirstChanged(); } } bool FolderModel::parseDesktopFiles() const { return m_parseDesktopFiles; } void FolderModel::setParseDesktopFiles(bool enable) { if (m_parseDesktopFiles != enable) { m_parseDesktopFiles = enable; emit parseDesktopFilesChanged(); } } QObject* FolderModel::viewAdapter() const { return m_viewAdapter; } void FolderModel::setViewAdapter(QObject* adapter) { if (m_viewAdapter != adapter) { KAbstractViewAdapter *abstractViewAdapter = dynamic_cast(adapter); m_viewAdapter = abstractViewAdapter; if (m_viewAdapter && !m_previewGenerator) { m_previewGenerator = new KFilePreviewGenerator(abstractViewAdapter, this); m_previewGenerator->setPreviewShown(m_previews); m_previewGenerator->setEnabledPlugins(m_effectivePreviewPlugins); } emit viewAdapterChanged(); } } bool FolderModel::previews() const { return m_previews; } void FolderModel::setPreviews(bool previews) { if (m_previews != previews) { m_previews = previews; if (m_previewGenerator) { m_previewGenerator->setPreviewShown(m_previews); } emit previewsChanged(); } } QStringList FolderModel::previewPlugins() const { return m_previewPlugins; } void FolderModel::setPreviewPlugins(const QStringList& previewPlugins) { QStringList effectivePlugins = previewPlugins; if (effectivePlugins.isEmpty()) { effectivePlugins = KIO::PreviewJob::defaultPlugins(); } if (m_effectivePreviewPlugins != effectivePlugins) { m_effectivePreviewPlugins = effectivePlugins; if (m_previewGenerator) { m_previewGenerator->setPreviewShown(false); m_previewGenerator->setEnabledPlugins(m_effectivePreviewPlugins); m_previewGenerator->setPreviewShown(true); } } if (m_previewPlugins != previewPlugins) { m_previewPlugins = previewPlugins; emit previewPluginsChanged(); } } int FolderModel::filterMode() const { return m_filterMode; } void FolderModel::setFilterMode(int filterMode) { if (m_filterMode != (FilterMode)filterMode) { m_filterMode = (FilterMode)filterMode; invalidateFilterIfComplete(); emit filterModeChanged(); } } QString FolderModel::filterPattern() const { return m_filterPattern; } void FolderModel::setFilterPattern(const QString &pattern) { if (m_filterPattern == pattern) { return; } m_filterPattern = pattern; m_filterPatternMatchAll = (pattern == QLatin1String("*")); const QStringList patterns = pattern.split(QLatin1Char(' ')); m_regExps.clear(); m_regExps.reserve(patterns.count()); foreach (const QString &pattern, patterns) { QRegExp rx(pattern); rx.setPatternSyntax(QRegExp::Wildcard); rx.setCaseSensitivity(Qt::CaseInsensitive); m_regExps.append(rx); } invalidateFilterIfComplete(); emit filterPatternChanged(); } QStringList FolderModel::filterMimeTypes() const { return m_mimeSet.values(); } void FolderModel::setFilterMimeTypes(const QStringList &mimeList) { - const QSet &set = QSet::fromList(mimeList); + const QSet set(mimeList.constBegin(), mimeList.constEnd()); if (m_mimeSet != set) { m_mimeSet = set; invalidateFilterIfComplete(); emit filterMimeTypesChanged(); } } void FolderModel::setScreen(int screen) { m_screenUsed = (screen != -1); if (!m_screenUsed || m_screen == screen) return; m_screen = screen; if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { m_screenMapper->addScreen(screen, resolvedUrl()); } emit screenChanged(); } bool FolderModel::eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched) // Catching Shift modifier usage on open context menus to swap the // Trash/Delete actions. if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Shift) { m_actionCollection.action(QStringLiteral("trash"))->setVisible(false); m_actionCollection.action(QStringLiteral("del"))->setVisible(true); } } else if (event->type() == QEvent::KeyRelease) { QKeyEvent *keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Shift) { m_actionCollection.action(QStringLiteral("trash"))->setVisible(true); m_actionCollection.action(QStringLiteral("del"))->setVisible(false); } } return false; } KFileItem FolderModel::rootItem() const { return m_dirModel->dirLister()->rootItem(); } void FolderModel::up() { const QUrl &up = KIO::upUrl(resolvedUrl()); if (up.isValid()) { setUrl(up.toString()); } } void FolderModel::cd(int row) { if (row < 0) { return; } const QModelIndex idx = index(row, 0); bool isDir = data(idx, IsDirRole).toBool(); if (isDir) { const KFileItem item = itemForIndex(idx); if (m_parseDesktopFiles && item.isDesktopFile()) { const KDesktopFile file(item.targetUrl().path()); if (file.hasLinkType()) { setUrl(file.readUrl()); } } else { setUrl(item.targetUrl().toString()); } } } void FolderModel::run(int row) { if (row < 0) { return; } KFileItem item = itemForIndex(index(row, 0)); QUrl url(item.targetUrl()); // FIXME TODO: This can go once we depend on a KIO w/ fe1f50caaf2. if (url.scheme().isEmpty()) { url.setScheme(QStringLiteral("file")); } KRun *run = new KRun(url, nullptr); // On desktop:/ we want to be able to run .desktop files right away, // otherwise ask for security reasons. We also don't use the targetUrl() // from above since we don't want the resolved /home/foo/Desktop URL. run->setShowScriptExecutionPrompt(item.url().scheme() != QLatin1String("desktop") || item.url().adjusted(QUrl::RemoveFilename).path() != QLatin1String("/")); } void FolderModel::runSelected() { if (!m_selectionModel->hasSelection()) { return; } if (m_selectionModel->selectedIndexes().count() == 1) { run(m_selectionModel->selectedIndexes().constFirst().row()); return; } KFileItemActions fileItemActions(this); KFileItemList items; foreach (const QModelIndex &index, m_selectionModel->selectedIndexes()) { // Skip over directories. if (!index.data(IsDirRole).toBool()) { items << itemForIndex(index); } } fileItemActions.runPreferredApplications(items, QString()); } void FolderModel::rename(int row, const QString& name) { if (row < 0) { return; } QModelIndex idx = index(row, 0); m_dirModel->setData(mapToSource(idx), name, Qt::EditRole); } int FolderModel::fileExtensionBoundary(int row) { const QModelIndex idx = index(row, 0); const QString &name = data(idx, Qt::DisplayRole).toString(); int boundary = name.length(); if (data(idx, IsDirRole).toBool()) { return boundary; } QMimeDatabase db; const QString &ext = db.suffixForFileName(name); if (ext.isEmpty()) { boundary = name.lastIndexOf(QLatin1Char('.')); if (boundary < 1) { boundary = name.length(); } } else { boundary -= ext.length() + 1; } return boundary; } bool FolderModel::hasSelection() const { return m_selectionModel->hasSelection(); } bool FolderModel::isSelected(int row) { if (row < 0) { return false; } return m_selectionModel->isSelected(index(row, 0)); } void FolderModel::setSelected(int row) { if (row < 0) { return; } m_selectionModel->select(index(row, 0), QItemSelectionModel::Select); } void FolderModel::toggleSelected(int row) { if (row < 0) { return; } m_selectionModel->select(index(row, 0), QItemSelectionModel::Toggle); } void FolderModel::setRangeSelected(int anchor, int to) { if (anchor < 0 || to < 0) { return; } QItemSelection selection(index(anchor, 0), index(to, 0)); m_selectionModel->select(selection, QItemSelectionModel::ClearAndSelect); } void FolderModel::updateSelection(const QVariantList &rows, bool toggle) { QItemSelection newSelection; int iRow = -1; foreach (const QVariant &row, rows) { iRow = row.toInt(); if (iRow < 0) { return; } const QModelIndex &idx = index(iRow, 0); newSelection.select(idx, idx); } if (toggle) { QItemSelection pinnedSelection = m_pinnedSelection; pinnedSelection.merge(newSelection, QItemSelectionModel::Toggle); m_selectionModel->select(pinnedSelection, QItemSelectionModel::ClearAndSelect); } else { m_selectionModel->select(newSelection, QItemSelectionModel::ClearAndSelect); } } void FolderModel::clearSelection() { if (m_selectionModel->hasSelection()) { m_selectionModel->clear(); } } void FolderModel::pinSelection() { m_pinnedSelection = m_selectionModel->selection(); } void FolderModel::unpinSelection() { m_pinnedSelection = QItemSelection(); } void FolderModel::addItemDragImage(int row, int x, int y, int width, int height, const QVariant &image) { if (row < 0) { return; } delete m_dragImages.take(row); DragImage *dragImage = new DragImage(); dragImage->row = row; dragImage->rect = QRect(x, y, width, height); dragImage->image = image.value(); dragImage->blank = false; m_dragImages.insert(row, dragImage); } void FolderModel::clearDragImages() { qDeleteAll(m_dragImages); m_dragImages.clear(); } void FolderModel::setDragHotSpotScrollOffset(int x, int y) { m_dragHotSpotScrollOffset.setX(x); m_dragHotSpotScrollOffset.setY(y); } QPoint FolderModel::dragCursorOffset(int row) { DragImage *image = m_dragImages.value(row); if (!image) { return QPoint(0, 0); } return image->cursorOffset; } void FolderModel::addDragImage(QDrag *drag, int x, int y) { if (!drag || m_dragImages.isEmpty()) { return; } QRegion region; foreach (DragImage *image, m_dragImages) { image->blank = isBlank(image->row); image->rect.translate(-m_dragHotSpotScrollOffset.x(), -m_dragHotSpotScrollOffset.y()); if (!image->blank && !image->image.isNull()) { region = region.united(image->rect); } } QRect rect = region.boundingRect(); QPoint offset = rect.topLeft(); rect.translate(-offset.x(), -offset.y()); QImage dragImage(rect.size(), QImage::Format_RGBA8888); dragImage.fill(Qt::transparent); QPainter painter(&dragImage); QPoint pos; foreach (DragImage *image, m_dragImages) { if (!image->blank && !image->image.isNull()) { pos = image->rect.translated(-offset.x(), -offset.y()).topLeft(); image->cursorOffset.setX(pos.x() - (x - offset.x())); image->cursorOffset.setY(pos.y() - (y - offset.y())); painter.drawImage(pos, image->image); } // FIXME HACK: Operate on copy. image->rect.translate(m_dragHotSpotScrollOffset.x(), m_dragHotSpotScrollOffset.y()); } drag->setPixmap(QPixmap::fromImage(dragImage)); drag->setHotSpot(QPoint(x - offset.x(), y - offset.y())); } void FolderModel::dragSelected(int x, int y) { if (m_dragInProgress) { return; } m_dragInProgress = true; emit draggingChanged(); m_urlChangedWhileDragging = false; // Avoid starting a drag synchronously in a mouse handler or interferes with // child event filtering in parent items (and thus e.g. press-and-hold hand- // ling in a containment). QMetaObject::invokeMethod(this, "dragSelectedInternal", Qt::QueuedConnection, Q_ARG(int, x), Q_ARG(int, y)); } void FolderModel::dragSelectedInternal(int x, int y) { if (!m_viewAdapter || !m_selectionModel->hasSelection()) { m_dragInProgress = false; emit draggingChanged(); return; } ItemViewAdapter *adapter = qobject_cast(m_viewAdapter); QQuickItem *item = qobject_cast(adapter->adapterView()); QDrag *drag = new QDrag(item); addDragImage(drag, x, y); m_dragIndexes = m_selectionModel->selectedIndexes(); std::sort(m_dragIndexes.begin(), m_dragIndexes.end()); // TODO: Optimize to emit contiguous groups. emit dataChanged(m_dragIndexes.first(), m_dragIndexes.last(), QVector() << BlankRole); QModelIndexList sourceDragIndexes; sourceDragIndexes.reserve(m_dragIndexes.count()); foreach (const QModelIndex &index, m_dragIndexes) { sourceDragIndexes.append(mapToSource(index)); } drag->setMimeData(m_dirModel->mimeData(sourceDragIndexes)); // Due to spring-loading (aka auto-expand), the URL might change // while the drag is in-flight - in that case we don't want to // unnecessarily emit dataChanged() for (possibly invalid) indices // after it ends. const QUrl currentUrl(m_dirModel->dirLister()->url()); item->grabMouse(); drag->exec(supportedDragActions()); item->ungrabMouse(); m_dragInProgress = false; emit draggingChanged(); m_urlChangedWhileDragging = false; if (m_dirModel->dirLister()->url() == currentUrl) { const QModelIndex first(m_dragIndexes.first()); const QModelIndex last(m_dragIndexes.last()); m_dragIndexes.clear(); // TODO: Optimize to emit contiguous groups. emit dataChanged(first, last, QVector() << BlankRole); } } static bool isDropBetweenSharedViews(const QList &urls, const QUrl &folderUrl) { for (const auto &url : urls) { if (folderUrl.adjusted(QUrl::StripTrailingSlash) != url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash)) { return false; } } return true; } void FolderModel::drop(QQuickItem *target, QObject* dropEvent, int row, bool showMenuManually) { QMimeData *mimeData = qobject_cast(dropEvent->property("mimeData").value()); if (!mimeData) { return; } QModelIndex idx; KFileItem item; if (row > -1 && row < rowCount()) { idx = index(row, 0); item = itemForIndex(idx); } QUrl dropTargetUrl; // So we get to run mostLocalUrl() over the current URL. if (item.isNull()) { item = rootItem(); } if (item.isNull()) { dropTargetUrl = m_dirModel->dirLister()->url(); } else if (m_parseDesktopFiles && item.isDesktopFile()) { const KDesktopFile file(item.targetUrl().path()); if (file.hasLinkType()) { dropTargetUrl = QUrl(file.readUrl()); } else { dropTargetUrl = item.mostLocalUrl(); } } else { dropTargetUrl = item.mostLocalUrl(); } auto dropTargetFolderUrl = dropTargetUrl; if (dropTargetFolderUrl.fileName() == QLatin1Char('.')) { // the target URL for desktop:/ is e.g. 'file://home/user/Desktop/.' dropTargetFolderUrl = dropTargetFolderUrl.adjusted(QUrl::RemoveFilename); } // use dropTargetUrl to resolve desktop:/ to the actual file location which is also used by the mime data /* QMimeData operates on local URLs, but the dir lister and thus screen mapper and positioner may * use a fancy scheme like desktop:/ instead. Ensure we always use the latter to properly map URLs, * i.e. go from file:///home/user/Desktop/file to desktop:/file */ auto mappableUrl = [this, dropTargetFolderUrl](const QUrl &url) -> QUrl { if (dropTargetFolderUrl != m_dirModel->dirLister()->url()) { QString mappedUrl = url.toString(); const auto local = dropTargetFolderUrl.toString(); const auto internal = m_dirModel->dirLister()->url().toString(); if (mappedUrl.startsWith(local)) { mappedUrl.replace(0, local.size(), internal); } return ScreenMapper::stringToUrl(mappedUrl); } return url; }; const int x = dropEvent->property("x").toInt(); const int y = dropEvent->property("y").toInt(); const QPoint dropPos = {x, y}; if (m_dragInProgress && row == -1 && !m_urlChangedWhileDragging) { if (m_locked || mimeData->urls().isEmpty()) { return; } setSortMode(-1); for (const auto &url : mimeData->urls()) { m_dropTargetPositions.insert(url.fileName(), dropPos); m_screenMapper->addMapping(mappableUrl(url), m_screen, ScreenMapper::DelayedSignal); m_screenMapper->removeItemFromDisabledScreen(mappableUrl(url)); } emit move(x, y, mimeData->urls()); return; } if (mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-service")) && mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-path"))) { const QString remoteDBusClient = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-service")); const QString remoteDBusPath = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-path")); QDBusMessage message = QDBusMessage::createMethodCall(remoteDBusClient, remoteDBusPath, QStringLiteral("org.kde.ark.DndExtract"), QStringLiteral("extractSelectedFilesTo")); message.setArguments({dropTargetUrl.toDisplayString(QUrl::PreferLocalFile)}); QDBusConnection::sessionBus().call(message, QDBus::NoBlock); return; } if (idx.isValid() && !(flags(idx) & Qt::ItemIsDropEnabled)) { return; } // Catch drops from a Task Manager and convert to usable URL. if (!mimeData->hasUrls() && mimeData->hasFormat(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))) { QList urls = {QUrl(QString::fromUtf8(mimeData->data(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))))}; mimeData->setUrls(urls); } if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { if (isDropBetweenSharedViews(mimeData->urls(), dropTargetFolderUrl)) { setSortMode(-1); for (const auto &url : mimeData->urls()) { m_dropTargetPositions.insert(url.fileName(), dropPos); m_screenMapper->addMapping(mappableUrl(url), m_screen, ScreenMapper::DelayedSignal); m_screenMapper->removeItemFromDisabledScreen(mappableUrl(url)); } m_dropTargetPositionsCleanup->start(); return; } } Qt::DropAction proposedAction((Qt::DropAction)dropEvent->property("proposedAction").toInt()); Qt::DropActions possibleActions(dropEvent->property("possibleActions").toInt()); Qt::MouseButtons buttons(dropEvent->property("buttons").toInt()); Qt::KeyboardModifiers modifiers(dropEvent->property("modifiers").toInt()); auto pos = target->mapToScene(dropPos).toPoint(); pos = target->window()->mapToGlobal(pos); QDropEvent ev(pos, possibleActions, mimeData, buttons, modifiers); ev.setDropAction(proposedAction); KIO::DropJobFlag flag = showMenuManually? KIO::ShowMenuManually : KIO::DropJobDefaultFlags; KIO::DropJob *dropJob = KIO::drop(&ev, dropTargetUrl, flag); dropJob->uiDelegate()->setAutoErrorHandlingEnabled(true); // The QMimeData we extract from the DropArea's drop event is deleted as soon as this method // ends but we need to keep a copy for when popupMenuAboutToShow fires. QMimeData *mimeCopy = new QMimeData(); for (const QString &format : mimeData->formats()) { mimeCopy->setData(format, mimeData->data(format)); } connect(dropJob, &KIO::DropJob::popupMenuAboutToShow, this, [this, mimeCopy, x, y, dropJob](const KFileItemListProperties &) { emit popupMenuAboutToShow(dropJob, mimeCopy, x, y); mimeCopy->deleteLater(); }); /* * Position files that come from a drag'n'drop event at the drop event * target position. To do so, we first listen to copy job to figure out * the target URL. Then we store the position of this drop event in the * hash and eventually trigger a move request when we get notified about * the new file event from the source model. */ connect(dropJob, &KIO::DropJob::copyJobStarted, this, [this, dropPos, dropTargetUrl](KIO::CopyJob* copyJob) { auto map = [this, dropPos, dropTargetUrl](const QUrl &targetUrl) { m_dropTargetPositions.insert(targetUrl.fileName(), dropPos); m_dropTargetPositionsCleanup->start(); if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { // assign a screen for the item before the copy is actually done, so // filterAcceptsRow doesn't assign the default screen to it QUrl url = resolvedUrl(); // if the folderview's folder is a standard path, just use the targetUrl for mapping if (targetUrl.toString().startsWith(url.toString())) { m_screenMapper->addMapping(targetUrl, m_screen, ScreenMapper::DelayedSignal); } else if (targetUrl.toString().startsWith(dropTargetUrl.toString())) { // if the folderview's folder is a special path, like desktop:// , we need to convert // the targetUrl file:// path to a desktop:/ path for mapping auto destPath = dropTargetUrl.path(); auto filePath = targetUrl.path(); if (filePath.startsWith(destPath)) { url.setPath(filePath.remove(0, destPath.length())); m_screenMapper->addMapping(url, m_screen, ScreenMapper::DelayedSignal); } } } }; // remember drop target position for target URL and forget about the source URL connect(copyJob, &KIO::CopyJob::copyingDone, this, [ map](KIO::Job *, const QUrl &, const QUrl &targetUrl, const QDateTime &, bool, bool) { map(targetUrl); }); connect(copyJob, &KIO::CopyJob::copyingLinkDone, this, [ map](KIO::Job *, const QUrl &, const QString &, const QUrl &targetUrl) { map(targetUrl); }); }); } void FolderModel::dropCwd(QObject* dropEvent) { QMimeData *mimeData = qobject_cast(dropEvent->property("mimeData").value()); if (!mimeData) { return; } if (mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-service")) && mimeData->hasFormat(QStringLiteral("application/x-kde-ark-dndextract-path"))) { const QString remoteDBusClient = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-service")); const QString remoteDBusPath = mimeData->data(QStringLiteral("application/x-kde-ark-dndextract-path")); QDBusMessage message = QDBusMessage::createMethodCall(remoteDBusClient, remoteDBusPath, QStringLiteral("org.kde.ark.DndExtract"), QStringLiteral("extractSelectedFilesTo")); message.setArguments(QVariantList() << m_dirModel->dirLister()->url().adjusted(QUrl::PreferLocalFile).toString()); QDBusConnection::sessionBus().call(message, QDBus::NoBlock); } else { Qt::DropAction proposedAction((Qt::DropAction)dropEvent->property("proposedAction").toInt()); Qt::DropActions possibleActions(dropEvent->property("possibleActions").toInt()); Qt::MouseButtons buttons(dropEvent->property("buttons").toInt()); Qt::KeyboardModifiers modifiers(dropEvent->property("modifiers").toInt()); QDropEvent ev(QPoint(), possibleActions, mimeData, buttons, modifiers); ev.setDropAction(proposedAction); KIO::DropJob *dropJob = KIO::drop(&ev, m_dirModel->dirLister()->url().adjusted(QUrl::PreferLocalFile)); dropJob->uiDelegate()->setAutoErrorHandlingEnabled(true); } } void FolderModel::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndexList indices = selected.indexes(); indices.append(deselected.indexes()); QVector roles; roles.append(SelectedRole); foreach(const QModelIndex &index, indices) { emit dataChanged(index, index, roles); } if (!m_selectionModel->hasSelection()) { clearDragImages(); } else { foreach (const QModelIndex &idx, deselected.indexes()) { delete m_dragImages.take(idx.row()); } } updateActions(); } bool FolderModel::isBlank(int row) const { if (row < 0) { return true; } return data(index(row, 0), BlankRole).toBool(); } QVariant FolderModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } if (role == BlankRole) { return m_dragIndexes.contains(index); } else if (role == OverlaysRole) { const KFileItem item = itemForIndex(index); return item.overlays(); } else if (role == SelectedRole) { return m_selectionModel->isSelected(index); } else if (role == IsDirRole) { return isDir(mapToSource(index), m_dirModel); } else if (role == IsLinkRole) { const KFileItem item = itemForIndex(index); return item.isLink(); } else if (role == IsHiddenRole) { const KFileItem item = itemForIndex(index); return item.isHidden(); } else if (role == UrlRole) { return itemForIndex(index).url(); } else if (role == LinkDestinationUrl) { const KFileItem item = itemForIndex(index); if (m_parseDesktopFiles && item.isDesktopFile()) { const KDesktopFile file(item.targetUrl().path()); if (file.hasLinkType()) { return file.readUrl(); } } return item.targetUrl(); } else if (role == SizeRole) { bool isDir = data(index, IsDirRole).toBool(); if (!isDir) { return m_dirModel->data(mapToSource(QSortFilterProxyModel::index(index.row(), 1)), Qt::DisplayRole); } } else if (role == TypeRole) { return m_dirModel->data(mapToSource(QSortFilterProxyModel::index(index.row(), 6)), Qt::DisplayRole); } else if (role == FileNameRole) { return itemForIndex(index).url().fileName(); } return QSortFilterProxyModel::data(index, role); } int FolderModel::indexForUrl(const QUrl& url) const { return mapFromSource(m_dirModel->indexForUrl(url)).row(); } KFileItem FolderModel::itemForIndex(const QModelIndex &index) const { return m_dirModel->itemForIndex(mapToSource(index)); } bool FolderModel::isDir(const QModelIndex &index, const KDirModel *dirModel) const { KFileItem item = dirModel->itemForIndex(index); if (item.isDir()) { return true; } auto it = m_isDirCache.constFind(item.url()); if (it != m_isDirCache.constEnd()) { return *it; } if (m_parseDesktopFiles && item.isDesktopFile()) { // Check if the desktop file is a link to a directory KDesktopFile file(item.targetUrl().path()); if (!file.hasLinkType()) { return false; } const QUrl url(file.readUrl()); // Check if we already have a running StatJob for this URL. if (m_isDirJobs.contains(item.url())) { return false; } // Assume the root folder of a protocol is always a folder. // This avoids spinning up e.g. trash KIO slave just to check whether trash:/ is a folder. if (url.path() == QLatin1String("/")) { m_isDirCache.insert(item.url(), true); return true; } if (KProtocolInfo::protocolClass(url.scheme()) != QLatin1String(":local")) { return false; } KIO::StatJob *job = KIO::stat(url, KIO::HideProgressInfo); job->setProperty("org.kde.plasma.folder_url", item.url()); job->setSide(KIO::StatJob::SourceSide); job->setDetails(0); connect(job, &KJob::result, this, &FolderModel::statResult); m_isDirJobs.insert(item.url(), job); } return false; } void FolderModel::statResult(KJob *job) { KIO::StatJob *statJob = static_cast(job); const QUrl &url = statJob->property("org.kde.plasma.folder_url").toUrl(); const QModelIndex &idx = index(indexForUrl(url), 0); if (idx.isValid() && statJob->error() == KJob::NoError) { m_isDirCache[url] = statJob->statResult().isDir(); emit dataChanged(idx, idx, QVector() << IsDirRole); } m_isDirJobs.remove(url); } void FolderModel::evictFromIsDirCache(const KFileItemList& items) { foreach (const KFileItem &item, items) { m_screenMapper->removeFromMap(item.url()); m_isDirCache.remove(item.url()); } } bool FolderModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { const KDirModel *dirModel = static_cast(sourceModel()); if (m_sortDirsFirst || left.column() == KDirModel::Size) { bool leftIsDir = isDir(left, dirModel); bool rightIsDir = isDir(right, dirModel); if (leftIsDir && !rightIsDir) { return (sortOrder() == Qt::AscendingOrder); } if (!leftIsDir && rightIsDir) { return (sortOrder() == Qt::DescendingOrder); } } const KFileItem leftItem = dirModel->data(left, KDirModel::FileItemRole).value(); const KFileItem rightItem = dirModel->data(right, KDirModel::FileItemRole).value(); const int column = left.column(); int result = 0; switch (column) { case KDirModel::Size: { if (isDir(left, dirModel) && isDir(right, dirModel)) { const int leftChildCount = dirModel->data(left, KDirModel::ChildCountRole).toInt(); const int rightChildCount = dirModel->data(right, KDirModel::ChildCountRole).toInt(); if (leftChildCount < rightChildCount) result = -1; else if (leftChildCount > rightChildCount) result = +1; } else { const KIO::filesize_t leftSize = leftItem.size(); const KIO::filesize_t rightSize = rightItem.size(); if (leftSize < rightSize) result = -1; else if (leftSize > rightSize) result = +1; } break; } case KDirModel::ModifiedTime: { const long long leftTime = leftItem.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1); const long long rightTime = rightItem.entry().numberValue(KIO::UDSEntry::UDS_MODIFICATION_TIME, -1); if (leftTime < rightTime) result = -1; else if (leftTime > rightTime) result = +1; break; } case KDirModel::Type: result = QString::compare(dirModel->data(left, Qt::DisplayRole).toString(), dirModel->data(right, Qt::DisplayRole).toString()); break; default: break; } if (result != 0) return result < 0; QCollator collator; result = collator.compare(leftItem.text(), rightItem.text()); if (result != 0) return result < 0; result = collator.compare(leftItem.name(), rightItem.name()); if (result != 0) return result < 0; return QString::compare(leftItem.url().url(), rightItem.url().url(), Qt::CaseSensitive); } Qt::DropActions FolderModel::supportedDragActions() const { return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; } Qt::DropActions FolderModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction | Qt::LinkAction; } inline bool FolderModel::matchMimeType(const KFileItem &item) const { if (m_mimeSet.isEmpty()) { return false; } if (m_mimeSet.contains(QLatin1String("all/all")) || m_mimeSet.contains(QLatin1String("all/allfiles"))) { return true; } const QString mimeType = item.determineMimeType().name(); return m_mimeSet.contains(mimeType); } inline bool FolderModel::matchPattern(const KFileItem &item) const { if (m_filterPatternMatchAll) { return true; } const QString name = item.name(); QListIterator i(m_regExps); while (i.hasNext()) { if (i.next().exactMatch(name)) { return true; } } return false; } bool FolderModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { const KDirModel *dirModel = static_cast(sourceModel()); const KFileItem item = dirModel->itemForIndex(dirModel->index(sourceRow, KDirModel::Name, sourceParent)); if (m_usedByContainment && !m_screenMapper->sharedDesktops()) { const QUrl url = item.url(); const int screen = m_screenMapper->screenForItem(url); // don't do anything if the folderview is not associated with a screen if (m_screenUsed && screen == -1) { // The item is not associated with a screen, probably because this is the first // time we see it or the folderview was previously used as a regular applet. // Associated with this folderview if the view is on the first available screen if (m_screen == m_screenMapper->firstAvailableScreen(resolvedUrl())) { m_screenMapper->addMapping(url, m_screen, ScreenMapper::DelayedSignal); } else { return false; } } else if (m_screen != screen) { // the item belongs to a different screen, filter it out return false; } } if (m_filterMode == NoFilter) { return true; } if (m_filterMode == FilterShowMatches) { return (matchPattern(item) && matchMimeType(item)); } else { return !(matchPattern(item) && matchMimeType(item)); } } void FolderModel::createActions() { KIO::FileUndoManager *manager = KIO::FileUndoManager::self(); QAction *cut = KStandardAction::cut(this, &FolderModel::cut, this); QAction *copy = KStandardAction::copy(this, &FolderModel::copy, this); QAction *undo = KStandardAction::undo(manager, &KIO::FileUndoManager::undo, this); undo->setEnabled(manager->undoAvailable()); undo->setShortcutContext(Qt::WidgetShortcut); connect(manager, SIGNAL(undoAvailable(bool)), undo, SLOT(setEnabled(bool))); connect(manager, &KIO::FileUndoManager::undoTextChanged, this, &FolderModel::undoTextChanged); QAction *paste = KStandardAction::paste(this, &FolderModel::paste, this); QAction *pasteTo = KStandardAction::paste(this, &FolderModel::pasteTo, this); QAction *refresh = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("&Refresh View"), this); refresh->setShortcut(QKeySequence(QKeySequence::Refresh)); connect(refresh, &QAction::triggered, this, &FolderModel::refresh); QAction *rename = KStandardAction::renameFile(this, &FolderModel::requestRename, this); QAction *trash = KStandardAction::moveToTrash(this, &FolderModel::moveSelectedToTrash, this); QAction *emptyTrash = new QAction(QIcon::fromTheme(QStringLiteral("trash-empty")), i18n("&Empty Trash"), this); connect(emptyTrash, &QAction::triggered, this, &FolderModel::emptyTrashBin); QAction *restoreFromTrash = new QAction(i18nc("Restore from trash", "Restore"), this); connect(restoreFromTrash, &QAction::triggered, this, &FolderModel::restoreSelectedFromTrash); QAction *del = KStandardAction::deleteFile(this, &FolderModel::deleteSelected, this); QAction *actOpen = new QAction(QIcon::fromTheme(QStringLiteral("window-new")), i18n("&Open"), this); connect(actOpen, &QAction::triggered, this, &FolderModel::openSelected); m_actionCollection.addAction(QStringLiteral("open"), actOpen); m_actionCollection.addAction(QStringLiteral("cut"), cut); m_actionCollection.addAction(QStringLiteral("undo"), undo); m_actionCollection.addAction(QStringLiteral("copy"), copy); m_actionCollection.addAction(QStringLiteral("paste"), paste); m_actionCollection.addAction(QStringLiteral("pasteto"), pasteTo); m_actionCollection.addAction(QStringLiteral("refresh"), refresh); m_actionCollection.addAction(QStringLiteral("rename"), rename); m_actionCollection.addAction(QStringLiteral("trash"), trash); m_actionCollection.addAction(QStringLiteral("del"), del); m_actionCollection.addAction(QStringLiteral("restoreFromTrash"), restoreFromTrash); m_actionCollection.addAction(QStringLiteral("emptyTrash"), emptyTrash); m_newMenu = new KNewFileMenu(&m_actionCollection, QStringLiteral("newMenu"), this); m_newMenu->setModal(false); connect(m_newMenu, &KNewFileMenu::directoryCreated, this, &FolderModel::newFileMenuItemCreated); connect(m_newMenu, &KNewFileMenu::fileCreated, this, &FolderModel::newFileMenuItemCreated); m_copyToMenu = new KFileCopyToMenu(nullptr); } QAction* FolderModel::action(const QString &name) const { return m_actionCollection.action(name); } QObject* FolderModel::newMenu() const { return m_newMenu->menu(); } void FolderModel::updateActions() { const QModelIndexList indexes = m_selectionModel->selectedIndexes(); KFileItemList items; QList urls; bool hasRemoteFiles = false; bool isTrashLink = false; const bool isTrash = (resolvedUrl().scheme() == QLatin1String("trash")); if (indexes.isEmpty()) { items << rootItem(); } else { items.reserve(indexes.count()); urls.reserve(indexes.count()); for (const QModelIndex &index : indexes) { KFileItem item = itemForIndex(index); if (!item.isNull()) { hasRemoteFiles |= item.localPath().isEmpty(); items.append(item); urls.append(item.url()); } } } KFileItemListProperties itemProperties(items); // Check if we're showing the menu for the trash link if (items.count() == 1 && items.at(0).isDesktopFile()) { KDesktopFile file(items.at(0).localPath()); if (file.hasLinkType() && file.readUrl() == QLatin1String("trash:/")) { isTrashLink = true; } } if (m_newMenu) { m_newMenu->checkUpToDate(); m_newMenu->setPopupFiles(QList() << m_dirModel->dirLister()->url()); // we need to set here as well, when the menu is shown via AppletInterface::eventFilter m_menuPosition = QCursor::pos(); if (QAction *newMenuAction = m_actionCollection.action(QStringLiteral("newMenu"))) { newMenuAction->setEnabled(itemProperties.supportsWriting()); newMenuAction->setVisible(!isTrash); } } if (QAction *emptyTrash = m_actionCollection.action(QStringLiteral("emptyTrash"))) { if (isTrash || isTrashLink) { emptyTrash->setVisible(true); emptyTrash->setEnabled(!isTrashEmpty()); } else { emptyTrash->setVisible(false); } } if (QAction *restoreFromTrash = m_actionCollection.action(QStringLiteral("restoreFromTrash"))) { restoreFromTrash->setVisible(isTrash); } if (QAction *moveToTrash = m_actionCollection.action(QStringLiteral("trash"))) { moveToTrash->setVisible(!hasRemoteFiles && itemProperties.supportsMoving()); } if (QAction *del = m_actionCollection.action(QStringLiteral("del"))) { del->setVisible(itemProperties.supportsDeleting()); } if (QAction *cut = m_actionCollection.action(QStringLiteral("cut"))) { cut->setEnabled(itemProperties.supportsDeleting()); cut->setVisible(!isTrash); } if (QAction *paste = m_actionCollection.action(QStringLiteral("paste"))) { bool enable = false; const QString pasteText = KIO::pasteActionText(QApplication::clipboard()->mimeData(), &enable, rootItem()); if (enable) { paste->setText(pasteText); paste->setEnabled(true); } else { paste->setText(i18n("&Paste")); paste->setEnabled(false); } if (QAction *pasteTo = m_actionCollection.action(QStringLiteral("pasteto"))) { pasteTo->setVisible(itemProperties.isDirectory() && itemProperties.supportsWriting()); pasteTo->setEnabled(paste->isEnabled()); pasteTo->setText(paste->text()); } } if (QAction *rename = m_actionCollection.action(QStringLiteral("rename"))) { rename->setEnabled(itemProperties.supportsMoving()); rename->setVisible(!isTrash); } } void FolderModel::openContextMenu(QQuickItem *visualParent, Qt::KeyboardModifiers modifiers) { if (m_usedByContainment && !KAuthorized::authorize(QStringLiteral("action/kdesktop_rmb"))) { return; } updateActions(); const QModelIndexList indexes = m_selectionModel->selectedIndexes(); QMenu *menu = new QMenu(); if (!m_fileItemActions) { m_fileItemActions = new KFileItemActions(this); m_fileItemActions->setParentWidget(QApplication::desktop()); } if (indexes.isEmpty()) { menu->addAction(m_actionCollection.action(QStringLiteral("newMenu"))); menu->addSeparator(); menu->addAction(m_actionCollection.action(QStringLiteral("paste"))); menu->addAction(m_actionCollection.action(QStringLiteral("undo"))); menu->addAction(m_actionCollection.action(QStringLiteral("refresh"))); menu->addAction(m_actionCollection.action(QStringLiteral("emptyTrash"))); menu->addSeparator(); KFileItemListProperties itemProperties(KFileItemList() << rootItem()); m_fileItemActions->setItemListProperties(itemProperties); menu->addAction(m_fileItemActions->preferredOpenWithAction(QString())); } else { KFileItemList items; QList urls; items.reserve(indexes.count()); urls.reserve(indexes.count()); for (const QModelIndex &index : indexes) { KFileItem item = itemForIndex(index); if (!item.isNull()) { items.append(item); urls.append(item.url()); } } KFileItemListProperties itemProperties(items); // Start adding the actions: // "Open" and "Open with" actions m_fileItemActions->setItemListProperties(itemProperties); m_fileItemActions->addOpenWithActionsTo(menu); menu->addSeparator(); menu->addAction(m_actionCollection.action(QStringLiteral("cut"))); menu->addAction(m_actionCollection.action(QStringLiteral("copy"))); menu->addAction(m_actionCollection.action(QStringLiteral("paste"))); menu->addSeparator(); menu->addAction(m_actionCollection.action(QStringLiteral("rename"))); menu->addAction(m_actionCollection.action(QStringLiteral("restoreFromTrash"))); KConfigGroup cg(KSharedConfig::openConfig(), "KDE"); bool showDeleteCommand = cg.readEntry("ShowDeleteCommand", false); menu->addAction(m_actionCollection.action(QStringLiteral("emptyTrash"))); QAction *trashAction = m_actionCollection.action(QStringLiteral("trash")); menu->addAction(trashAction); trashAction->setVisible(!modifiers.testFlag(Qt::ShiftModifier)); QAction *deleteAction = m_actionCollection.action(QStringLiteral("del")); menu->addAction(deleteAction); deleteAction->setVisible(showDeleteCommand || !trashAction->isVisible()); menu->addSeparator(); // Service actions m_fileItemActions->addServiceActionsTo(menu); menu->addSeparator(); // Plugin actions m_fileItemActions->addPluginActionsTo(menu); // Copy To, Move To KSharedConfig::Ptr dolphin = KSharedConfig::openConfig(QStringLiteral("dolphinrc")); if (KConfigGroup(dolphin, "General").readEntry("ShowCopyMoveMenu", false)) { m_copyToMenu->setUrls(urls); m_copyToMenu->setReadOnly(!itemProperties.supportsMoving()); m_copyToMenu->addActionsTo(menu); menu->addSeparator(); } // Properties if (KPropertiesDialog::canDisplay(items)) { menu->addSeparator(); QAction *act = new QAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("&Properties"), menu); act->setShortcuts({Qt::ALT + Qt::Key_Return, Qt::ALT + Qt::Key_Enter}); QObject::connect(act, &QAction::triggered, this, &FolderModel::openPropertiesDialog); menu->addAction(act); } } if (visualParent) { m_menuPosition = visualParent->mapToGlobal(QPointF(0, visualParent->height())).toPoint(); } else { m_menuPosition = QCursor::pos(); } // Used to monitor Shift modifier usage while the menu is open, to // swap the Trash and Delete actions. menu->installEventFilter(this); menu->setAttribute(Qt::WA_TranslucentBackground); menu->winId(); //force surface creation before ensurePolish call in menu::Popup which happens before show menu->popup(m_menuPosition); connect(menu, &QMenu::aboutToHide, [menu]() { menu->deleteLater(); }); } void FolderModel::openPropertiesDialog() { const QModelIndexList indexes = m_selectionModel->selectedIndexes(); if (indexes.isEmpty()) { return; } KFileItemList items; items.reserve(indexes.count()); for (const QModelIndex &index : indexes) { KFileItem item = itemForIndex(index); if (!item.isNull()) { items.append(item); } } if (!KPropertiesDialog::canDisplay(items)) { return; } KPropertiesDialog::showDialog(items, nullptr, false /*non modal*/); } void FolderModel::linkHere(const QUrl &sourceUrl) { KIO::CopyJob *job = KIO::link(sourceUrl, m_dirModel->dirLister()->url()); KIO::FileUndoManager::self()->recordCopyJob(job); } QList FolderModel::selectedUrls() const { const auto indexes = m_selectionModel->selectedIndexes(); QList urls; urls.reserve(indexes.count()); for (const QModelIndex &index : indexes) { urls.append(itemForIndex(index).url()); } return urls; } void FolderModel::copy() { if (!m_selectionModel->hasSelection()) { return; } if (QAction *action = m_actionCollection.action(QStringLiteral("copy"))) { if (!action->isEnabled()) { return; } } QMimeData *mimeData = QSortFilterProxyModel::mimeData(m_selectionModel->selectedIndexes()); QApplication::clipboard()->setMimeData(mimeData); } void FolderModel::cut() { if (!m_selectionModel->hasSelection()) { return; } if (QAction *action = m_actionCollection.action(QStringLiteral("cut"))) { if (!action->isEnabled()) { return; } } QMimeData *mimeData = QSortFilterProxyModel::mimeData(m_selectionModel->selectedIndexes()); KIO::setClipboardDataCut(mimeData, true); QApplication::clipboard()->setMimeData(mimeData); } void FolderModel::paste() { if (QAction *action = m_actionCollection.action(QStringLiteral("paste"))) { if (!action->isEnabled()) { return; } } KIO::paste(QApplication::clipboard()->mimeData(), m_dirModel->dirLister()->url()); } void FolderModel::pasteTo() { const QList urls = selectedUrls(); Q_ASSERT(urls.count() == 1); KIO::paste(QApplication::clipboard()->mimeData(), urls.first()); } void FolderModel::refresh() { m_errorString.clear(); emit errorStringChanged(); m_dirModel->dirLister()->updateDirectory(m_dirModel->dirLister()->url()); } QObject *FolderModel::appletInterface() const { return m_appletInterface; } void FolderModel::setAppletInterface(QObject *appletInterface) { if (m_appletInterface != appletInterface) { Q_ASSERT(!m_appletInterface); m_appletInterface = appletInterface; if (appletInterface) { Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); if (applet) { Plasma::Containment *containment = applet->containment(); if (containment) { Plasma::Corona *corona = containment->corona(); if (corona) { m_screenMapper->setCorona(corona); } setScreen(containment->screen()); connect(containment, &Plasma::Containment::screenChanged, this, &FolderModel::setScreen); } } } emit appletInterfaceChanged(); } } void FolderModel::moveSelectedToTrash() { if (!m_selectionModel->hasSelection()) { return; } if (QAction *action = m_actionCollection.action(QStringLiteral("trash"))) { if (!action->isEnabled()) { return; } } const QList urls = selectedUrls(); KIO::JobUiDelegate uiDelegate; if (uiDelegate.askDeleteConfirmation(urls, KIO::JobUiDelegate::Trash, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::trash(urls); job->uiDelegate()->setAutoErrorHandlingEnabled(true); KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Trash, urls, QUrl(QStringLiteral("trash:/")), job); } } void FolderModel::deleteSelected() { if (!m_selectionModel->hasSelection()) { return; } if (QAction *action = m_actionCollection.action(QStringLiteral("del"))) { if (!action->isEnabled()) { return; } } const QList urls = selectedUrls(); KIO::JobUiDelegate uiDelegate; if (uiDelegate.askDeleteConfirmation(urls, KIO::JobUiDelegate::Delete, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::del(urls); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } } void FolderModel::openSelected() { if (!m_selectionModel->hasSelection()) { return; } const QList urls = selectedUrls(); for (const QUrl &url : urls) { (void) new KRun(url, nullptr); } } void FolderModel::undo() { if (QAction *action = m_actionCollection.action(QStringLiteral("undo"))) { // trigger() doesn't check enabled and would crash if invoked nonetheless. if (action->isEnabled()) { action->trigger(); } } } void FolderModel::emptyTrashBin() { KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(QApplication::desktop()); if (uiDelegate.askDeleteConfirmation(QList(), KIO::JobUiDelegate::EmptyTrash, KIO::JobUiDelegate::DefaultConfirmation)) { KIO::Job* job = KIO::emptyTrash(); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } } void FolderModel::restoreSelectedFromTrash() { if (!m_selectionModel->hasSelection()) { return; } const auto &urls = selectedUrls(); KIO::RestoreJob *job = KIO::restoreFromTrash(urls); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } bool FolderModel::isTrashEmpty() { KConfig trashConfig(QStringLiteral("trashrc"), KConfig::SimpleConfig); return trashConfig.group("Status").readEntry("Empty", true); } void FolderModel::undoTextChanged(const QString &text) { if (QAction *action = m_actionCollection.action(QStringLiteral("undo"))) { action->setText(text); } } diff --git a/imports/activitymanager/sortedactivitiesmodel.cpp b/imports/activitymanager/sortedactivitiesmodel.cpp index c883973b2..efd87832f 100644 --- a/imports/activitymanager/sortedactivitiesmodel.cpp +++ b/imports/activitymanager/sortedactivitiesmodel.cpp @@ -1,491 +1,491 @@ /* * Copyright (C) 2016 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. */ // Self #include "sortedactivitiesmodel.h" // C++ #include // Qt #include #include #include // KDE #include #include #include #include #include #define KWINDOWSYSTEM_NO_DEPRECATED #include #include #define PLASMACONFIG "plasma-org.kde.plasma.desktop-appletsrc" namespace { class BackgroundCache: public QObject { public: BackgroundCache() : initialized(false) , plasmaConfig(KSharedConfig::openConfig(PLASMACONFIG)) { using namespace std::placeholders; const QString configFile = QStandardPaths::writableLocation( QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + PLASMACONFIG; KDirWatch::self()->addFile(configFile); QObject::connect(KDirWatch::self(), &KDirWatch::dirty, this, &BackgroundCache::settingsFileChanged, Qt::QueuedConnection); QObject::connect(KDirWatch::self(), &KDirWatch::created, this, &BackgroundCache::settingsFileChanged, Qt::QueuedConnection); } void settingsFileChanged(const QString &file) { if (!file.endsWith(PLASMACONFIG)) { return; } if (initialized) { plasmaConfig->reparseConfiguration(); reload(); } } void subscribe(SortedActivitiesModel *model) { if (!initialized) { reload(); } models << model; } void unsubscribe(SortedActivitiesModel *model) { models.removeAll(model); if (models.isEmpty()) { initialized = false; forActivity.clear(); } } QString backgroundFromConfig(const KConfigGroup &config) const { auto wallpaperPlugin = config.readEntry("wallpaperplugin"); auto wallpaperConfig = config.group("Wallpaper").group(wallpaperPlugin).group("General"); if (wallpaperConfig.hasKey("Image")) { // Trying for the wallpaper auto wallpaper = wallpaperConfig.readEntry("Image", QString()); if (!wallpaper.isEmpty()) { return wallpaper; } } if (wallpaperConfig.hasKey("Color")) { auto backgroundColor = wallpaperConfig.readEntry("Color", QColor(0, 0, 0)); return backgroundColor.name(); } return QString(); } void reload() { auto newForActivity = forActivity; QHash lastScreenForActivity; // contains activities for which the wallpaper // has updated QStringList changedActivities; // Contains activities not covered by any containment QStringList ghostActivities = forActivity.keys(); // Traversing through all containments in search for // containments that define activities in plasma for (const auto& containmentId: plasmaConfigContainments().groupList()) { const auto containment = plasmaConfigContainments().group(containmentId); const auto lastScreen = containment.readEntry("lastScreen", 0); const auto activity = containment.readEntry("activityId", QString()); // Ignore the containment if the activity is not defined if (activity.isEmpty()) continue; // If we have already found the same activity from another // containment, we are using the new one only if // the previous one was a color and not a proper wallpaper, // or if the screen ID is closer to zero const bool processed = !ghostActivities.contains(activity) && newForActivity.contains(activity) && (lastScreenForActivity[activity] <= lastScreen); // qDebug() << "GREPME Searching containment " << containmentId // << "for the wallpaper of the " << activity << " activity - " // << "currently, we think that the wallpaper is " << processed << (processed ? newForActivity[activity] : QString()) // << "last screen is" << lastScreen // ; if (processed && newForActivity[activity][0] != '#') continue; // Marking the current activity as processed ghostActivities.removeAll(activity); const auto background = backgroundFromConfig(containment); // qDebug() << " GREPME Found wallpaper: " << background; if (background.isEmpty()) continue; // If we got this far and we already had a new wallpaper for // this activity, it means we now have a better one bool foundBetterWallpaper = changedActivities.contains(activity); if (foundBetterWallpaper || newForActivity[activity] != background) { if (!foundBetterWallpaper) { changedActivities << activity; } // qDebug() << " GREPME Setting: " << activity << " = " << background << "," << lastScreen; newForActivity[activity] = background; lastScreenForActivity[activity] = lastScreen; } } initialized = true; // Removing the activities from the list if we haven't found them // while traversing through the containments for (const auto& activity: ghostActivities) { newForActivity.remove(activity); } // If we have detected the changes, lets notify everyone if (!changedActivities.isEmpty()) { forActivity = newForActivity; for (auto model: models) { model->onBackgroundsUpdated(changedActivities); } } } KConfigGroup plasmaConfigContainments() { return plasmaConfig->group("Containments"); } QHash forActivity; QList models; bool initialized; KSharedConfig::Ptr plasmaConfig; }; static BackgroundCache &backgrounds() { // If you convert this to a shared pointer, // fix the connections to KDirWatcher static BackgroundCache cache; return cache; } } SortedActivitiesModel::SortedActivitiesModel(const QVector &states, QObject *parent) : QSortFilterProxyModel(parent) , m_activitiesModel(new KActivities::ActivitiesModel(states, this)) , m_activities(new KActivities::Consumer(this)) { setSourceModel(m_activitiesModel); setDynamicSortFilter(true); setSortRole(LastTimeUsed); sort(0, Qt::DescendingOrder); backgrounds().subscribe(this); const QList windows = KWindowSystem::stackingOrder(); for (const auto& window: windows) { KWindowInfo info(window, NET::WMVisibleName, NET::WM2Activities); const QStringList activities = info.activities(); if (activities.isEmpty() || activities.contains("00000000-0000-0000-0000-000000000000")) continue; for (const auto& activity: activities) { m_activitiesWindows[activity] << window; } } connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &SortedActivitiesModel::onWindowAdded); connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &SortedActivitiesModel::onWindowRemoved); connect(KWindowSystem::self(), SIGNAL(windowChanged(WId, NET::Properties, NET::Properties2)), this, SLOT(onWindowChanged(WId, NET::Properties, NET::Properties2))); } SortedActivitiesModel::~SortedActivitiesModel() { backgrounds().unsubscribe(this); } bool SortedActivitiesModel::inhibitUpdates() const { return m_inhibitUpdates; } void SortedActivitiesModel::setInhibitUpdates(bool inhibitUpdates) { if (m_inhibitUpdates != inhibitUpdates) { m_inhibitUpdates = inhibitUpdates; emit inhibitUpdatesChanged(m_inhibitUpdates); setDynamicSortFilter(!inhibitUpdates); } } uint SortedActivitiesModel::lastUsedTime(const QString &activity) const { if (m_activities->currentActivity() == activity) { return ~(uint)0; } else { KConfig config("kactivitymanagerd-switcher", KConfig::SimpleConfig); KConfigGroup times(&config, "LastUsed"); return times.readEntry(activity, (uint)0); } } bool SortedActivitiesModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const { const auto activityLeft = - sourceModel()->data(sourceLeft, KActivities::ActivitiesModel::ActivityId); + sourceModel()->data(sourceLeft, KActivities::ActivitiesModel::ActivityId).toString(); const auto activityRight = - sourceModel()->data(sourceRight, KActivities::ActivitiesModel::ActivityId); + sourceModel()->data(sourceRight, KActivities::ActivitiesModel::ActivityId).toString(); - const auto timeLeft = lastUsedTime(activityLeft.toString()); - const auto timeRight = lastUsedTime(activityRight.toString()); + const auto timeLeft = lastUsedTime(activityLeft); + const auto timeRight = lastUsedTime(activityRight); return (timeLeft < timeRight) || (timeLeft == timeRight && activityLeft < activityRight); } QHash SortedActivitiesModel::roleNames() const { if (!sourceModel()) return QHash(); auto roleNames = sourceModel()->roleNames(); roleNames[LastTimeUsed] = "lastTimeUsed"; roleNames[LastTimeUsedString] = "lastTimeUsedString"; roleNames[WindowCount] = "windowCount"; roleNames[HasWindows] = "hasWindows"; return roleNames; } QVariant SortedActivitiesModel::data(const QModelIndex &index, int role) const { if (role == KActivities::ActivitiesModel::ActivityBackground) { const auto activity = activityIdForIndex(index); return backgrounds().forActivity[activity]; } else if (role == LastTimeUsed || role == LastTimeUsedString) { const auto activity = activityIdForIndex(index); const auto time = lastUsedTime(activity); if (role == LastTimeUsed) { return QVariant(time); } else { const auto now = QDateTime::currentDateTime().toTime_t(); if (time == 0) return i18n("Used some time ago"); auto diff = now - time; // We do not need to be precise diff /= 60; const auto minutes = diff % 60; diff /= 60; const auto hours = diff % 24; diff /= 24; const auto days = diff % 30; diff /= 30; const auto months = diff % 12; diff /= 12; const auto years = diff; return (years > 0) ? i18n("Used more than a year ago") : (months > 0) ? i18ncp("amount in months", "Used a month ago", "Used %1 months ago", months) : (days > 0) ? i18ncp("amount in days", "Used a day ago", "Used %1 days ago", days) : (hours > 0) ? i18ncp("amount in hours", "Used an hour ago", "Used %1 hours ago", hours) : (minutes > 0) ? i18ncp("amount in minutes", "Used a minute ago", "Used %1 minutes ago", minutes) : i18n("Used a moment ago"); } } else if (role == HasWindows || role == WindowCount) { const auto activity = activityIdForIndex(index); if (role == HasWindows) { return (m_activitiesWindows[activity].size() > 0); } else { return m_activitiesWindows[activity].size(); } } else { return QSortFilterProxyModel::data(index, role); } } QString SortedActivitiesModel::activityIdForIndex(const QModelIndex &index) const { return data(index, KActivities::ActivitiesModel::ActivityId).toString(); } QString SortedActivitiesModel::activityIdForRow(int row) const { return activityIdForIndex(index(row, 0)); } int SortedActivitiesModel::rowForActivityId(const QString &activity) const { int position = -1; for (int row = 0; row < rowCount(); ++row) { if (activity == activityIdForRow(row)) { position = row; } } return position; } QString SortedActivitiesModel::relativeActivity(int relative) const { const auto currentActivity = m_activities->currentActivity(); if (!sourceModel()) return QString(); const auto currentRowCount = sourceModel()->rowCount(); //x % 0 is undefined in c++ if (currentRowCount == 0) { return QString(); } int currentActivityRow = 0; for (; currentActivityRow < currentRowCount; currentActivityRow++) { if (activityIdForRow(currentActivityRow) == currentActivity) break; } currentActivityRow = currentActivityRow + relative; //wrap to within bounds for both positive and negative currentActivityRows currentActivityRow = (currentRowCount + (currentActivityRow % currentRowCount)) % currentRowCount; return activityIdForRow(currentActivityRow); } void SortedActivitiesModel::onCurrentActivityChanged(const QString ¤tActivity) { if (m_previousActivity == currentActivity) return; const int previousActivityRow = rowForActivityId(m_previousActivity); emit rowChanged(previousActivityRow, { LastTimeUsed, LastTimeUsedString }); m_previousActivity = currentActivity; const int currentActivityRow = rowForActivityId(m_previousActivity); emit rowChanged(currentActivityRow, { LastTimeUsed, LastTimeUsedString }); } void SortedActivitiesModel::onBackgroundsUpdated(const QStringList &activities) { for (const auto &activity: activities) { const int row = rowForActivityId(activity); emit rowChanged(row, { KActivities::ActivitiesModel::ActivityBackground }); } } void SortedActivitiesModel::onWindowAdded(WId window) { KWindowInfo info(window, NET::Properties(), NET::WM2Activities); const QStringList activities = info.activities(); if (activities.isEmpty() || activities.contains("00000000-0000-0000-0000-000000000000")) return; for (const auto& activity: activities) { if (!m_activitiesWindows[activity].contains(window)) { m_activitiesWindows[activity] << window; rowChanged(rowForActivityId(activity), m_activitiesWindows.size() == 1 ? QVector{WindowCount, HasWindows} : QVector{WindowCount}); } } } void SortedActivitiesModel::onWindowRemoved(WId window) { for (const auto& activity: m_activitiesWindows.keys()) { if (m_activitiesWindows[activity].contains(window)) { m_activitiesWindows[activity].removeAll(window); rowChanged(rowForActivityId(activity), m_activitiesWindows.size() == 0 ? QVector{WindowCount, HasWindows} : QVector{WindowCount}); } } } void SortedActivitiesModel::onWindowChanged(WId window, NET::Properties properties, NET::Properties2 properties2) { Q_UNUSED(properties); if (properties2 & NET::WM2Activities) { onWindowRemoved(window); onWindowAdded(window); } } void SortedActivitiesModel::rowChanged(int row, const QVector &roles) { if (row == -1) return; emit dataChanged(index(row, 0), index(row, 0), roles); } diff --git a/kaccess/main.cpp b/kaccess/main.cpp index ff236b39e..b5f9b0ded 100644 --- a/kaccess/main.cpp +++ b/kaccess/main.cpp @@ -1,86 +1,86 @@ /* Copyright 2000 Matthias Hölzer-Klüpfel Copyright 2014 Frederik Gladhorn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "kaccess.h" #include #include #include #include #include #include extern "C" Q_DECL_EXPORT int kdemain(int argc, char * argv[]) { Kdelibs4ConfigMigrator migrate(QStringLiteral("kaccess")); migrate.setConfigFiles(QStringList() << QStringLiteral("kaccessrc")); migrate.migrate(); qunsetenv("SESSION_MANAGER"); //this application is currently only relevant on X, force to run under X //note if someone does port this we still need to run kaccess under X for xwayland apps qputenv("QT_QPA_PLATFORM", "xcb"); // verify the Xlib has matching XKB extension int major = XkbMajorVersion; int minor = XkbMinorVersion; if (!XkbLibraryVersion(&major, &minor)) { qWarning() << "Xlib XKB extension does not match"; return 1; } qDebug() << "Xlib XKB extension major=" << major << " minor=" << minor; // we need an application object for QX11Info QApplication app(argc, argv); KAboutData about(QStringLiteral("kaccess"), QString(), i18n("KDE Accessibility Tool"), {}, KAboutLicense::GPL_V2, i18n("(c) 2000, Matthias Hoelzer-Kluepfel")); about.addAuthor(i18n("Matthias Hoelzer-Kluepfel"), i18n("Author") , QStringLiteral("hoelzer@kde.org")); // set data as used for D-Bus by KAccessApp KAboutData::setApplicationData(about); KAccessApp acc; if (acc.isFailed()) { return 1; } // verify the X server has matching XKB extension // if yes, the XKB extension is initialized int opcode_rtrn; int error_rtrn; int xkb_opcode; if (!XkbQueryExtension(QX11Info::display(), &opcode_rtrn, &xkb_opcode, &error_rtrn, &major, &minor)) { - qWarning() << "X server has not matching XKB extension" << endl; + qWarning() << "X server has not matching XKB extension" << Qt::endl; return 1; } qDebug() << "X server XKB extension major=" << major << " minor=" << minor; app.installNativeEventFilter(&acc); //Without that, the application dies when the dialog is closed only once. app.setQuitOnLastWindowClosed(false); acc.setXkbOpcode(xkb_opcode); return app.exec(); } diff --git a/kcms/access/kcmaccess.cpp b/kcms/access/kcmaccess.cpp index 82259bbd6..dedafa83e 100644 --- a/kcms/access/kcmaccess.cpp +++ b/kcms/access/kcmaccess.cpp @@ -1,556 +1,556 @@ /* Copyright 2000 Matthias Hölzer-Klüpfel Copyright 2014 Frederik Gladhorn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "kcmaccess.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define XK_MISCELLANY #define XK_XKB_KEYS #include K_PLUGIN_FACTORY(KAccessConfigFactory, registerPlugin();) QString mouseKeysShortcut(Display *display) { // Calculate the keycode KeySym sym = XK_MouseKeys_Enable; KeyCode code = XKeysymToKeycode(display, sym); if (code == 0) { sym = XK_Pointer_EnableKeys; code = XKeysymToKeycode(display, sym); if (code == 0) return QString(); // No shortcut available? } // Calculate the modifiers by searching the keysym in the X keyboard mapping XkbDescPtr xkbdesc = XkbGetMap(display, XkbKeyTypesMask | XkbKeySymsMask, XkbUseCoreKbd); if (!xkbdesc) return QString(); // Failed to obtain the mapping from server bool found = false; unsigned char modifiers = 0; int groups = XkbKeyNumGroups(xkbdesc, code); for (int grp = 0; grp < groups && !found; grp++) { int levels = XkbKeyGroupWidth(xkbdesc, code, grp); for (int level = 0; level < levels && !found; level++) { if (sym == XkbKeySymEntry(xkbdesc, code, level, grp)) { // keysym found => determine modifiers int typeIdx = xkbdesc->map->key_sym_map[code].kt_index[grp]; XkbKeyTypePtr type = &(xkbdesc->map->types[typeIdx]); for (int i = 0; i < type->map_count && !found; i++) { if (type->map[i].active && (type->map[i].level == level)) { modifiers = type->map[i].mods.mask; found = true; } } } } } XkbFreeClientMap(xkbdesc, 0, true); if (!found) return QString(); // Somehow the keycode -> keysym mapping is flawed XEvent ev; ev.type = KeyPress; ev.xkey.display = display; ev.xkey.keycode = code; ev.xkey.state = 0; int key; KKeyServer::xEventToQt(&ev, &key); QString keyname = QKeySequence(key).toString(); unsigned int AltMask = KKeyServer::modXAlt(); unsigned int WinMask = KKeyServer::modXMeta(); unsigned int NumMask = KKeyServer::modXNumLock(); unsigned int ScrollMask = KKeyServer::modXScrollLock(); unsigned int MetaMask = XkbKeysymToModifiers(display, XK_Meta_L); unsigned int SuperMask = XkbKeysymToModifiers(display, XK_Super_L); unsigned int HyperMask = XkbKeysymToModifiers(display, XK_Hyper_L); unsigned int AltGrMask = XkbKeysymToModifiers(display, XK_Mode_switch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Shift) | XkbKeysymToModifiers(display, XK_ISO_Level3_Latch) | XkbKeysymToModifiers(display, XK_ISO_Level3_Lock); unsigned int mods = ShiftMask | ControlMask | AltMask | WinMask | LockMask | NumMask | ScrollMask; AltGrMask &= ~mods; MetaMask &= ~(mods | AltGrMask); SuperMask &= ~(mods | AltGrMask | MetaMask); HyperMask &= ~(mods | AltGrMask | MetaMask | SuperMask); if ((modifiers & AltGrMask) != 0) keyname = i18n("AltGraph") + QLatin1Char('+') + keyname; if ((modifiers & HyperMask) != 0) keyname = i18n("Hyper") + QLatin1Char('+') + keyname; if ((modifiers & SuperMask) != 0) keyname = i18n("Super") + QLatin1Char('+') + keyname; if ((modifiers & WinMask) != 0) keyname = QKeySequence(Qt::META).toString() + QLatin1Char('+') + keyname; if ((modifiers & AltMask) != 0) keyname = QKeySequence(Qt::ALT).toString() + QLatin1Char('+') + keyname; if ((modifiers & ControlMask) != 0) keyname = QKeySequence(Qt::CTRL).toString() + QLatin1Char('+') + keyname; if ((modifiers & ShiftMask) != 0) keyname = QKeySequence(Qt::SHIFT).toString() + QLatin1Char('+') + keyname; QString result; if ((modifiers & ScrollMask) != 0) if ((modifiers & LockMask) != 0) if ((modifiers & NumMask) != 0) result = i18n("Press %1 while NumLock, CapsLock and ScrollLock are active", keyname); else result = i18n("Press %1 while CapsLock and ScrollLock are active", keyname); else if ((modifiers & NumMask) != 0) result = i18n("Press %1 while NumLock and ScrollLock are active", keyname); else result = i18n("Press %1 while ScrollLock is active", keyname); else if ((modifiers & LockMask) != 0) if ((modifiers & NumMask) != 0) result = i18n("Press %1 while NumLock and CapsLock are active", keyname); else result = i18n("Press %1 while CapsLock is active", keyname); else if ((modifiers & NumMask) != 0) result = i18n("Press %1 while NumLock is active", keyname); else result = i18n("Press %1", keyname); return result; } KAccessConfig::KAccessConfig(QWidget *parent, const QVariantList& args) : KCModule(parent, args) { KAboutData *about = new KAboutData(QStringLiteral("kcmaccess"), i18n("KDE Accessibility Tool"), QStringLiteral("1.0"), QString(), KAboutLicense::GPL, i18n("(c) 2000, Matthias Hoelzer-Kluepfel")); about->addAuthor(i18n("Matthias Hoelzer-Kluepfel"), i18n("Author") , QStringLiteral("hoelzer@kde.org")); setAboutData(about); ui.setupUi(this); connect(ui.soundButton, &QPushButton::clicked, this, &KAccessConfig::selectSound); connect(ui.customBell, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.systemBell, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.customBell, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.soundEdit, &QLineEdit::textChanged, this, &KAccessConfig::configChanged); connect(ui.invertScreen, &QRadioButton::clicked, this, &KAccessConfig::configChanged); connect(ui.flashScreen, &QRadioButton::clicked, this, &KAccessConfig::configChanged); connect(ui.visibleBell, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.visibleBell, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.colorButton, &KColorButton::clicked, this, &KAccessConfig::changeFlashScreenColor); connect(ui.invertScreen, &QRadioButton::clicked, this, &KAccessConfig::invertClicked); connect(ui.flashScreen, &QRadioButton::clicked, this, &KAccessConfig::flashClicked); connect(ui.duration, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); // modifier key settings ------------------------------- connect(ui.stickyKeys, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.stickyKeysLock, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.stickyKeysAutoOff, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.stickyKeys, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.stickyKeysBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.toggleKeysBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.kNotifyModifiers, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.kNotifyModifiers, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.kNotifyModifiersButton, &QPushButton::clicked, this, &KAccessConfig::configureKNotify); // key filter settings --------------------------------- connect(ui.slowKeysDelay, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.slowKeys, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.slowKeys, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.slowKeysPressBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.slowKeysAcceptBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.slowKeysRejectBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.bounceKeysDelay, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.bounceKeys, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.bounceKeysRejectBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.bounceKeys, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); // gestures -------------------------------------------- QString shortcut = mouseKeysShortcut(QX11Info::display()); if (shortcut.isEmpty()) ui.gestures->setToolTip(i18n("Here you can activate keyboard gestures that turn on the following features: \n" "Sticky keys: Press Shift key 5 consecutive times\n" "Slow keys: Hold down Shift for 8 seconds")); else ui.gestures->setToolTip(i18n("Here you can activate keyboard gestures that turn on the following features: \n" "Mouse Keys: %1\n" "Sticky keys: Press Shift key 5 consecutive times\n" "Slow keys: Hold down Shift for 8 seconds", shortcut)); connect(ui.gestures, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.timeout, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.timeout, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.timeoutDelay, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.accessxBeep, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.gestureConfirmation, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.kNotifyAccess, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.kNotifyAccess, &QCheckBox::clicked, this, &KAccessConfig::checkAccess); connect(ui.kNotifyAccessButton, &QPushButton::clicked, this, &KAccessConfig::configureKNotify); // keynboard navigation connect(ui.mouseKeys, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.mk_delay, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.mk_interval, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.mk_time_to_max, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.mk_max_speed, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); connect(ui.mk_curve, static_cast(&QSpinBox::valueChanged), this, &KAccessConfig::configChanged); // screen reader connect(ui.screenReaderEnabled, &QCheckBox::clicked, this, &KAccessConfig::configChanged); connect(ui.launchOrcaConfiguration, &QPushButton::clicked, this, &KAccessConfig::launchOrcaConfiguration); } KAccessConfig::~KAccessConfig() { } void KAccessConfig::configureKNotify() { KNotifyConfigWidget::configure(this, QStringLiteral("kaccess")); } void KAccessConfig::launchOrcaConfiguration() { const QStringList gsettingArgs = { QStringLiteral("set"), QStringLiteral("org.gnome.desktop.a11y.applications"), QStringLiteral("screen-reader-enabled"), QStringLiteral("true") }; int ret = QProcess::execute(QStringLiteral("gsettings"), gsettingArgs); if (ret) { const QString errorStr = QLatin1String("gsettings ") + gsettingArgs.join(QLatin1Char(' ')); ui.orcaLaunchFeedbackLabel->setText(i18n("Could not set gsettings for Orca: \"%1\" failed", errorStr)); return; } qint64 pid = 0; bool started = QProcess::startDetached(QStringLiteral("orca"), {QStringLiteral("--setup")}, QString(), &pid); if (!started) { ui.orcaLaunchFeedbackLabel->setText(i18n("Error: Could not launch \"orca --setup\"")); } } void KAccessConfig::changeFlashScreenColor() { ui.invertScreen->setChecked(false); ui.flashScreen->setChecked(true); configChanged(); } void KAccessConfig::selectSound() { const QStringList list = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("sound/")); QString start; if (!list.isEmpty()) start = list[0]; const QString fname = QFileDialog::getOpenFileName(this, QString(), start); if (!fname.isEmpty()) ui.soundEdit->setText(fname); } void KAccessConfig::configChanged() { emit changed(true); } void KAccessConfig::load() { KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Bell"); ui.systemBell->setChecked(cg.readEntry("SystemBell", true)); ui.customBell->setChecked(cg.readEntry("ArtsBell", false)); ui.soundEdit->setText(cg.readPathEntry("ArtsBellFile", QString())); ui.visibleBell->setChecked(cg.readEntry("VisibleBell", false)); ui.invertScreen->setChecked(cg.readEntry("VisibleBellInvert", true)); ui.flashScreen->setChecked(!ui.invertScreen->isChecked()); ui.colorButton->setColor(cg.readEntry("VisibleBellColor", QColor(Qt::red))); ui.duration->setValue(cg.readEntry("VisibleBellPause", 500)); KConfigGroup keyboardGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Keyboard"); ui.stickyKeys->setChecked(keyboardGroup.readEntry("StickyKeys", false)); ui.stickyKeysLock->setChecked(keyboardGroup.readEntry("StickyKeysLatch", true)); ui.stickyKeysAutoOff->setChecked(keyboardGroup.readEntry("StickyKeysAutoOff", false)); ui.stickyKeysBeep->setChecked(keyboardGroup.readEntry("StickyKeysBeep", true)); ui.toggleKeysBeep->setChecked(keyboardGroup.readEntry("ToggleKeysBeep", false)); ui.kNotifyModifiers->setChecked(keyboardGroup.readEntry("kNotifyModifiers", false)); ui.slowKeys->setChecked(keyboardGroup.readEntry("SlowKeys", false)); ui.slowKeysDelay->setValue(keyboardGroup.readEntry("SlowKeysDelay", 500)); ui.slowKeysPressBeep->setChecked(keyboardGroup.readEntry("SlowKeysPressBeep", true)); ui.slowKeysAcceptBeep->setChecked(keyboardGroup.readEntry("SlowKeysAcceptBeep", true)); ui.slowKeysRejectBeep->setChecked(keyboardGroup.readEntry("SlowKeysRejectBeep", true)); ui.bounceKeys->setChecked(keyboardGroup.readEntry("BounceKeys", false)); ui.bounceKeysDelay->setValue(keyboardGroup.readEntry("BounceKeysDelay", 500)); ui.bounceKeysRejectBeep->setChecked(keyboardGroup.readEntry("BounceKeysRejectBeep", true)); ui.gestures->setChecked(keyboardGroup.readEntry("Gestures", false)); ui.timeout->setChecked(keyboardGroup.readEntry("AccessXTimeout", false)); ui.timeoutDelay->setValue(keyboardGroup.readEntry("AccessXTimeoutDelay", 30)); ui.accessxBeep->setChecked(keyboardGroup.readEntry("AccessXBeep", true)); ui.gestureConfirmation->setChecked(keyboardGroup.readEntry("GestureConfirmation", false)); ui.kNotifyAccess->setChecked(keyboardGroup.readEntry("kNotifyAccess", false)); KConfigGroup mouseGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Mouse"); ui.mouseKeys->setChecked(mouseGroup.readEntry("MouseKeys", false)); ui.mk_delay->setValue(mouseGroup.readEntry("MKDelay", 160)); const int interval = mouseGroup.readEntry("MKInterval", 5); ui.mk_interval->setValue(interval); // Default time to reach maximum speed: 5000 msec int time_to_max = mouseGroup.readEntry("MKTimeToMax", (5000+interval/2)/interval); time_to_max = mouseGroup.readEntry("MK-TimeToMax", time_to_max*interval); ui.mk_time_to_max->setValue(time_to_max); // Default maximum speed: 1000 pixels/sec // (The old default maximum speed from KDE <= 3.4 // (100000 pixels/sec) was way too fast) long max_speed = mouseGroup.readEntry("MKMaxSpeed", interval); max_speed = max_speed * 1000 / interval; if (max_speed > 2000) { max_speed = 2000; } max_speed = mouseGroup.readEntry("MK-MaxSpeed", int(max_speed)); ui.mk_max_speed->setValue(max_speed); ui.mk_curve->setValue(mouseGroup.readEntry("MKCurve", 0)); KConfigGroup screenReaderGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "ScreenReader"); ui.screenReaderEnabled->setChecked(screenReaderGroup.readEntry("Enabled", false)); checkAccess(); emit changed(false); } void KAccessConfig::save() { KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Bell"); cg.writeEntry("SystemBell", ui.systemBell->isChecked()); cg.writeEntry("ArtsBell", ui.customBell->isChecked()); cg.writePathEntry("ArtsBellFile", ui.soundEdit->text()); cg.writeEntry("VisibleBell", ui.visibleBell->isChecked()); cg.writeEntry("VisibleBellInvert", ui.invertScreen->isChecked()); cg.writeEntry("VisibleBellColor", ui.colorButton->color()); cg.writeEntry("VisibleBellPause", ui.duration->value()); cg.sync(); KConfigGroup keyboardGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Keyboard"); keyboardGroup.writeEntry("StickyKeys", ui.stickyKeys->isChecked()); keyboardGroup.writeEntry("StickyKeysLatch", ui.stickyKeysLock->isChecked()); keyboardGroup.writeEntry("StickyKeysAutoOff", ui.stickyKeysAutoOff->isChecked()); keyboardGroup.writeEntry("StickyKeysBeep", ui.stickyKeysBeep->isChecked()); keyboardGroup.writeEntry("ToggleKeysBeep", ui.toggleKeysBeep->isChecked()); keyboardGroup.writeEntry("kNotifyModifiers", ui.kNotifyModifiers->isChecked()); keyboardGroup.writeEntry("SlowKeys", ui.slowKeys->isChecked()); keyboardGroup.writeEntry("SlowKeysDelay", ui.slowKeysDelay->value()); keyboardGroup.writeEntry("SlowKeysPressBeep", ui.slowKeysPressBeep->isChecked()); keyboardGroup.writeEntry("SlowKeysAcceptBeep", ui.slowKeysAcceptBeep->isChecked()); keyboardGroup.writeEntry("SlowKeysRejectBeep", ui.slowKeysRejectBeep->isChecked()); keyboardGroup.writeEntry("BounceKeys", ui.bounceKeys->isChecked()); keyboardGroup.writeEntry("BounceKeysDelay", ui.bounceKeysDelay->value()); keyboardGroup.writeEntry("BounceKeysRejectBeep", ui.bounceKeysRejectBeep->isChecked()); keyboardGroup.writeEntry("Gestures", ui.gestures->isChecked()); keyboardGroup.writeEntry("AccessXTimeout", ui.timeout->isChecked()); keyboardGroup.writeEntry("AccessXTimeoutDelay", ui.timeoutDelay->value()); keyboardGroup.writeEntry("AccessXBeep", ui.accessxBeep->isChecked()); keyboardGroup.writeEntry("GestureConfirmation", ui.gestureConfirmation->isChecked()); keyboardGroup.writeEntry("kNotifyAccess", ui.kNotifyAccess->isChecked()); keyboardGroup.sync(); KConfigGroup mouseGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "Mouse"); const int interval = ui.mk_interval->value(); mouseGroup.writeEntry("MouseKeys", ui.mouseKeys->isChecked()); mouseGroup.writeEntry("MKDelay", ui.mk_delay->value()); mouseGroup.writeEntry("MKInterval", interval); mouseGroup.writeEntry("MK-TimeToMax", ui.mk_time_to_max->value()); mouseGroup.writeEntry("MKTimeToMax", (ui.mk_time_to_max->value() + interval/2)/interval); mouseGroup.writeEntry("MK-MaxSpeed", ui.mk_max_speed->value()); mouseGroup.writeEntry("MKMaxSpeed", (ui.mk_max_speed->value()*interval + 500)/1000); mouseGroup.writeEntry("MKCurve", ui.mk_curve->value()); mouseGroup.sync(); KConfigGroup screenReaderGroup(KSharedConfig::openConfig(QStringLiteral("kaccessrc")), "ScreenReader"); screenReaderGroup.writeEntry("Enabled", ui.screenReaderEnabled->isChecked()); if (ui.systemBell->isChecked() || ui.customBell->isChecked() || ui.visibleBell->isChecked()) { KConfig _cfg(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup cfg(&_cfg, "General"); cfg.writeEntry("UseSystemBell", true); cfg.sync(); } // make kaccess reread the configuration // turning a11y features off needs to be done by kaccess // so run it to clear any enabled features and it will exit if it should - QProcess::startDetached(QStringLiteral("kaccess")); + QProcess::startDetached(QStringLiteral("kaccess"), {}); emit changed(false); } void KAccessConfig::defaults() { ui.systemBell->setChecked(true); ui.customBell->setChecked(false); ui.soundEdit->setText(QString()); ui.visibleBell->setChecked(false); ui.invertScreen->setChecked(true); ui.flashScreen->setChecked(false); ui.colorButton->setColor(QColor(Qt::red)); ui.duration->setValue(500); ui.slowKeys->setChecked(false); ui.slowKeysDelay->setValue(500); ui.slowKeysPressBeep->setChecked(true); ui.slowKeysAcceptBeep->setChecked(true); ui.slowKeysRejectBeep->setChecked(true); ui.bounceKeys->setChecked(false); ui.bounceKeysDelay->setValue(500); ui.bounceKeysRejectBeep->setChecked(true); ui.stickyKeys->setChecked(false); ui.stickyKeysLock->setChecked(true); ui.stickyKeysAutoOff->setChecked(false); ui.stickyKeysBeep->setChecked(true); ui.toggleKeysBeep->setChecked(false); ui.kNotifyModifiers->setChecked(false); ui.gestures->setChecked(false); ui.timeout->setChecked(false); ui.timeoutDelay->setValue(30); ui.accessxBeep->setChecked(true); ui.gestureConfirmation->setChecked(true); ui.kNotifyAccess->setChecked(false); ui.mouseKeys->setChecked(false); ui.mk_delay->setValue(160); ui.mk_interval->setValue(5); ui.mk_time_to_max->setValue(5000); ui.mk_max_speed->setValue(1000); ui.mk_curve->setValue(0); ui.screenReaderEnabled->setChecked(false); checkAccess(); emit changed(true); } void KAccessConfig::invertClicked() { ui.flashScreen->setChecked(false); } void KAccessConfig::flashClicked() { ui.invertScreen->setChecked(false); } void KAccessConfig::checkAccess() { bool custom = ui.customBell->isChecked(); ui.soundEdit->setEnabled(custom); ui.soundButton->setEnabled(custom); ui.soundLabel->setEnabled(custom); bool visible = ui.visibleBell->isChecked(); ui.invertScreen->setEnabled(visible); ui.flashScreen->setEnabled(visible); ui.colorButton->setEnabled(visible); ui.duration->setEnabled(visible); bool sticky = ui.stickyKeys->isChecked(); ui.stickyKeysLock->setEnabled(sticky); ui.stickyKeysAutoOff->setEnabled(sticky); ui.stickyKeysBeep->setEnabled(sticky); bool slow = ui.slowKeys->isChecked(); ui.slowKeysDelay->setEnabled(slow); ui.slowKeysPressBeep->setEnabled(slow); ui.slowKeysAcceptBeep->setEnabled(slow); ui.slowKeysRejectBeep->setEnabled(slow); bool bounce = ui.bounceKeys->isChecked(); ui.bounceKeysDelay->setEnabled(bounce); ui.bounceKeysRejectBeep->setEnabled(bounce); bool useTimeout = ui.timeout->isChecked(); ui.timeoutDelay->setEnabled(useTimeout); } #include "kcmaccess.moc" diff --git a/kcms/activities/imports/dialog.cpp b/kcms/activities/imports/dialog.cpp index 42c6b0205..91e5216c8 100644 --- a/kcms/activities/imports/dialog.cpp +++ b/kcms/activities/imports/dialog.cpp @@ -1,301 +1,302 @@ /* * Copyright (C) 2015 - 2016 by Ivan Cukic * * 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 "dialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../kactivities-kcm-features.h" #include "kactivities/info.h" #include "kactivities/controller.h" #include "features_interface.h" #include "common/dbus/common.h" #include "utils/continue_with.h" #include "utils/d_ptr_implementation.h" #include "../utils.h" class Dialog::Private { public: Private(Dialog *parent) : q(parent) , activityName(QStringLiteral("activityName")) , activityDescription(QStringLiteral("activityDescription")) , activityIcon(QStringLiteral("activityIcon")) , activityWallpaper(QStringLiteral("activityWallpaper")) , activityIsPrivate(true) , activityShortcut(QStringLiteral("activityShortcut")) , features(new KAMD_DBUS_CLASS_INTERFACE(Features, Features, q)) { } Dialog *const q; QVBoxLayout *layout; QTabWidget *tabs; QQuickWidget *tabGeneral; KMessageWidget *message; QDialogButtonBox *buttons; QString defaultOKText; QQuickWidget *createTab(const QString &title, const QString &file) { auto view = new QQuickWidget(); view->setResizeMode(QQuickWidget::SizeRootObjectToView); view->setClearColor(QGuiApplication::palette().window().color()); view->rootContext()->setContextProperty(QStringLiteral("dialog"), q); if (setViewSource(view, QStringLiteral("/qml/activityDialog/") + file)) { tabs->addTab(view, title); } else { message->setText(i18n("Error loading the QML files. Check your installation.\nMissing %1", QStringLiteral(KAMD_KCM_DATADIR) + QStringLiteral("/qml/activityDialog/") + file)); message->setVisible(true); } return view; } void setFocus(QQuickWidget *widget) { // TODO: does not work... widget->setFocus(); auto root = widget->rootObject(); if (!root) return; QMetaObject::invokeMethod(widget->rootObject(), "setFocus", Qt::DirectConnection); } QString activityId; QString activityName; QString activityDescription; QString activityIcon; QString activityWallpaper; bool activityIsPrivate; QString activityShortcut; KActivities::Controller activities; org::kde::ActivityManager::Features *features; }; void Dialog::showDialog(const QString &id) { static Dialog *dialog = nullptr; // If we use the regular singleton here (static value instead of static ptr), // we will crash on exit because of Qt... if (!dialog) { dialog = new Dialog(); } dialog->init(id); dialog->show(); } Dialog::Dialog(QObject *parent) : QDialog() , d(this) { resize(550, 400); d->layout = new QVBoxLayout(this); // Message widget for showing errors d->message = new KMessageWidget(this); d->message->setMessageType(KMessageWidget::Error); d->message->setVisible(false); d->message->setWordWrap(true); d->layout->addWidget(d->message); // Tabs d->tabs = new QTabWidget(this); d->layout->addWidget(d->tabs); d->tabGeneral = d->createTab(i18n("General"), QStringLiteral("GeneralTab.qml")); // Buttons d->buttons = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); d->layout->QLayout::addWidget(d->buttons); connect(d->buttons->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, &Dialog::save); connect(d->buttons, &QDialogButtonBox::rejected, this, &Dialog::close); d->defaultOKText = d->buttons->button(QDialogButtonBox::Ok)->text(); } void Dialog::init(const QString &activityId) { setWindowTitle(activityId.isEmpty() ? i18nc("@title:window", "Create a New Activity") : i18nc("@title:window", "Activity Settings")); d->buttons->button(QDialogButtonBox::Ok)->setText( activityId.isEmpty() ? i18nc("@action:button", "Create") : d->defaultOKText); d->tabs->setCurrentIndex(0); setActivityId(activityId); setActivityName(QString()); setActivityDescription(QString()); setActivityIcon(QStringLiteral("activities")); setActivityIsPrivate(false); setActivityShortcut(QKeySequence()); if (!activityId.isEmpty()) { KActivities::Info activityInfo(activityId); setActivityName(activityInfo.name()); setActivityDescription(activityInfo.description()); setActivityIcon(activityInfo.icon()); // finding the key shortcut const auto shortcuts = KGlobalAccel::self()->globalShortcut( QStringLiteral("ActivityManager"), QStringLiteral("switch-to-activity-") + activityId); setActivityShortcut(shortcuts.isEmpty() ? QKeySequence() : shortcuts.first()); // is private? auto result = d->features->GetValue( QStringLiteral("org.kde.ActivityManager.Resources.Scoring/isOTR/") + activityId); auto watcher = new QDBusPendingCallWatcher(result, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [&] (QDBusPendingCallWatcher *watcher) mutable { QDBusPendingReply reply = *watcher; setActivityIsPrivate(reply.value().variant().toBool()); watcher->deleteLater(); }); } } Dialog::~Dialog() { } void Dialog::showEvent(QShowEvent *event) { + Q_UNUSED(event); // Setting the focus d->setFocus(d->tabGeneral); } #define IMPLEMENT_PROPERTY(Scope, Type, PType, PropName) \ Type Dialog::activity##PropName() const \ { \ auto root = d->tab##Scope->rootObject(); \ \ if (!root) { \ qDebug() << "Root does not exist"; \ return Type(); \ } \ \ return root->property("activity" #PropName).value(); \ } \ \ void Dialog::setActivity##PropName(PType value) \ { \ auto root = d->tab##Scope->rootObject(); \ \ if (!root) { \ qDebug() << "Root does not exist"; \ return; \ } \ \ root->setProperty("activity" #PropName, value); \ } IMPLEMENT_PROPERTY(General, QString, const QString &, Id) IMPLEMENT_PROPERTY(General, QString, const QString &, Name) IMPLEMENT_PROPERTY(General, QString, const QString &, Description) IMPLEMENT_PROPERTY(General, QString, const QString &, Icon) IMPLEMENT_PROPERTY(General, QString, const QString &, Wallpaper) IMPLEMENT_PROPERTY(General, QKeySequence, const QKeySequence &, Shortcut) IMPLEMENT_PROPERTY(General, bool, bool, IsPrivate) #undef IMPLEMENT_PROPERTY void Dialog::save() { if (activityId().isEmpty()) { create(); } else { saveChanges(activityId()); } } void Dialog::create() { using namespace kamd::utils; continue_with( d->activities.addActivity(activityName()), [this](const optional_view &activityId) { if (activityId.is_initialized()) { saveChanges(activityId.get()); } }); } void Dialog::saveChanges(const QString &activityId) { d->activities.setActivityName(activityId, activityName()); d->activities.setActivityDescription(activityId, activityDescription()); d->activities.setActivityIcon(activityId, activityIcon()); // setting the key shortcut QAction action(nullptr); action.setProperty("isConfigurationAction", true); action.setProperty("componentName", QStringLiteral("ActivityManager")); action.setObjectName(QStringLiteral("switch-to-activity-") + activityId); KGlobalAccel::self()->removeAllShortcuts(&action); KGlobalAccel::self()->setGlobalShortcut(&action, activityShortcut()); // is private? d->features->SetValue(QStringLiteral("org.kde.ActivityManager.Resources.Scoring/isOTR/") + activityId, QDBusVariant(activityIsPrivate())); close(); } diff --git a/kcms/cursortheme/xcursor/previewwidget.cpp b/kcms/cursortheme/xcursor/previewwidget.cpp index f0e5665c3..f61a42b29 100644 --- a/kcms/cursortheme/xcursor/previewwidget.cpp +++ b/kcms/cursortheme/xcursor/previewwidget.cpp @@ -1,327 +1,328 @@ /* * Copyright © 2003-2007 Fredrik Höglund * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License version 2 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "previewwidget.h" #include #include #include #include #include "cursortheme.h" namespace { // Preview cursors const char * const cursor_names[] = { "left_ptr", "left_ptr_watch", "wait", "pointer", "help", "ibeam", "size_all", "size_fdiag", "cross", "split_h", "size_ver", "size_hor", "size_bdiag", "split_v", }; const int numCursors = 9; // The number of cursors from the above list to be previewed const int cursorSpacing = 20; // Spacing between preview cursors const qreal widgetMinWidth = 10; // The minimum width of the preview widget const qreal widgetMinHeight = 48; // The minimum height of the preview widget } class PreviewCursor { public: PreviewCursor( const CursorTheme *theme, const QString &name, int size ); ~PreviewCursor(); const QPixmap &pixmap() const { return m_pixmap; } int width() const { return m_pixmap.width(); } int height() const { return m_pixmap.height(); } int boundingSize() const { return m_boundingSize; } inline QRect rect() const; void setPosition( const QPoint &p ) { m_pos = p; } void setPosition( int x, int y ) { m_pos = QPoint(x, y); } QPoint position() const { return m_pos; } operator const uint32_t () const { return m_cursor; } operator const QPixmap& () const { return pixmap(); } private: int m_boundingSize; QPixmap m_pixmap; uint32_t m_cursor; QPoint m_pos; }; PreviewCursor::PreviewCursor(const CursorTheme *theme, const QString &name, int size) : m_boundingSize(size > 0 ? size : theme->defaultCursorSize()) { // Create the preview pixmap QImage image = theme->loadImage(name, size); if (image.isNull()) return; m_pixmap = QPixmap::fromImage(image); // Load the cursor m_cursor = theme->loadCursor(name, size); // ### perhaps we should tag the cursor so it doesn't get // replaced when a new theme is applied } PreviewCursor::~PreviewCursor() { if (QX11Info::isPlatformX11() && m_cursor != XCB_CURSOR_NONE) { xcb_free_cursor(QX11Info::connection(), m_cursor); } } QRect PreviewCursor::rect() const { return QRect(m_pos, m_pixmap.size()) .adjusted(-(cursorSpacing / 2), -(cursorSpacing / 2), cursorSpacing / 2, cursorSpacing / 2); } // ------------------------------------------------------------------------------ PreviewWidget::PreviewWidget(QQuickItem *parent) : QQuickPaintedItem(parent), m_currentIndex(-1), m_currentSize(0) { setAcceptHoverEvents(true); current = nullptr; } PreviewWidget::~PreviewWidget() { qDeleteAll(list); list.clear(); } void PreviewWidget::setThemeModel(SortProxyModel *themeModel) { if (m_themeModel == themeModel) { return; } m_themeModel = themeModel; emit themeModelChanged(); } SortProxyModel *PreviewWidget::themeModel() { return m_themeModel; } void PreviewWidget::setCurrentIndex(int idx) { if (m_currentIndex == idx) { return; } m_currentIndex = idx; emit currentIndexChanged(); if (!m_themeModel) { return; } const CursorTheme *theme = m_themeModel->theme(m_themeModel->index(idx, 0)); setTheme(theme, m_currentSize); } int PreviewWidget::currentIndex() const { return m_currentIndex; } void PreviewWidget::setCurrentSize(int size) { if (m_currentSize == size) { return; } m_currentSize = size; emit currentSizeChanged(); if (!m_themeModel) { return; } const CursorTheme *theme = m_themeModel->theme(m_themeModel->index(m_currentIndex, 0)); setTheme(theme, size); } int PreviewWidget::currentSize() const { return m_currentSize; } void PreviewWidget::refresh() { if (!m_themeModel) { return; } const CursorTheme *theme = m_themeModel->theme(m_themeModel->index(m_currentIndex, 0)); setTheme(theme, m_currentSize); } void PreviewWidget::updateImplicitSize() { qreal totalWidth = 0; qreal maxHeight = 0; foreach (const PreviewCursor *c, list) { totalWidth += c->width(); maxHeight = qMax(c->height(), (int)maxHeight); } totalWidth += (list.count() - 1) * cursorSpacing; maxHeight = qMax(maxHeight, widgetMinHeight); setImplicitWidth(qMax(totalWidth, widgetMinWidth)); setImplicitHeight(qMax(height(), maxHeight)); } void PreviewWidget::layoutItems() { if (!list.isEmpty()) { const int spacing = 12; int nextX = spacing; int nextY = spacing; foreach (PreviewCursor *c, list) { c->setPosition(nextX, nextY); nextX += c->boundingSize() + spacing; if (nextX + c->boundingSize() > width()) { nextX = spacing; nextY += c->boundingSize() + spacing; } } } needLayout = false; } void PreviewWidget::setTheme(const CursorTheme *theme, const int size) { qDeleteAll(list); list.clear(); if (theme) { for (int i = 0; i < numCursors; i++) list << new PreviewCursor(theme, cursor_names[i], size); needLayout = true; updateImplicitSize(); } current = nullptr; update(); } void PreviewWidget::paint(QPainter *painter) { if (needLayout) layoutItems(); foreach(const PreviewCursor *c, list) { if (c->pixmap().isNull()) continue; painter->drawPixmap(c->position(), *c); } } void PreviewWidget::hoverMoveEvent(QHoverEvent *e) { if (needLayout) layoutItems(); for (const PreviewCursor *c : qAsConst(list)) { if (c->rect().contains(e->pos())) { if (c != current) { const uint32_t cursor = *c; if (QWindow *actualWindow = QQuickRenderControl::renderWindowFor(window())) { if (KWindowSystem::isPlatformX11() && cursor != XCB_CURSOR_NONE) { xcb_change_window_attributes(QX11Info::connection(), actualWindow->winId(), XCB_CW_CURSOR, &cursor); } } current = c; } return; } } setCursor(Qt::ArrowCursor); current = nullptr; } void PreviewWidget::hoverLeaveEvent(QHoverEvent *e) { + Q_UNUSED(e); if (QWindow *actualWindow = QQuickRenderControl::renderWindowFor(window())) { actualWindow->unsetCursor(); } } void PreviewWidget::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_UNUSED(newGeometry) Q_UNUSED(oldGeometry) if (!list.isEmpty()) { needLayout = true; } } diff --git a/kcms/cursortheme/xcursor/thememodel.cpp b/kcms/cursortheme/xcursor/thememodel.cpp index eb39e7436..c8bdcf716 100644 --- a/kcms/cursortheme/xcursor/thememodel.cpp +++ b/kcms/cursortheme/xcursor/thememodel.cpp @@ -1,426 +1,426 @@ /* * Copyright © 2005-2007 Fredrik Höglund * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License version 2 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "thememodel.h" #include "xcursortheme.h" #include #include // Check for older version #if !defined(XCURSOR_LIB_MAJOR) && defined(XCURSOR_MAJOR) # define XCURSOR_LIB_MAJOR XCURSOR_MAJOR # define XCURSOR_LIB_MINOR XCURSOR_MINOR #endif CursorThemeModel::CursorThemeModel(QObject *parent) : QAbstractTableModel(parent) { insertThemes(); } CursorThemeModel::~CursorThemeModel() { qDeleteAll(list); list.clear(); } QHash CursorThemeModel::roleNames() const { QHash roleNames = QAbstractTableModel::roleNames(); roleNames[CursorTheme::DisplayDetailRole] = "description"; roleNames[CursorTheme::IsWritableRole] = "isWritable"; return roleNames; } void CursorThemeModel::refreshList() { beginResetModel(); qDeleteAll(list); list.clear(); endResetModel(); insertThemes(); } QVariant CursorThemeModel::headerData(int section, Qt::Orientation orientation, int role) const { // Only provide text for the headers if (role != Qt::DisplayRole) return QVariant(); // Horizontal header labels if (orientation == Qt::Horizontal) { switch (section) { case NameColumn: return i18n("Name"); case DescColumn: return i18n("Description"); default: return QVariant(); } } // Numbered vertical header labels return QString(section); } QVariant CursorThemeModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() < 0 || index.row() >= list.count()) return QVariant(); const CursorTheme *theme = list.at(index.row()); // Text label if (role == Qt::DisplayRole) { switch (index.column()) { case NameColumn: return theme->title(); case DescColumn: return theme->description(); default: return QVariant(); } } // Description for the first name column if (role == CursorTheme::DisplayDetailRole && index.column() == NameColumn) return theme->description(); // Icon for the name column if (role == Qt::DecorationRole && index.column() == NameColumn) return theme->icon(); if (role == CursorTheme::IsWritableRole) { return theme->isWritable(); } return QVariant(); } void CursorThemeModel::sort(int column, Qt::SortOrder order) { Q_UNUSED(column); Q_UNUSED(order); // Sorting of the model isn't implemented, as the KCM currently uses // a sorting proxy model. } const CursorTheme *CursorThemeModel::theme(const QModelIndex &index) { if (!index.isValid()) return nullptr; if (index.row() < 0 || index.row() >= list.count()) return nullptr; return list.at(index.row()); } QModelIndex CursorThemeModel::findIndex(const QString &name) { uint hash = qHash(name); for (int i = 0; i < list.count(); i++) { const CursorTheme *theme = list.at(i); if (theme->hash() == hash) return index(i, 0); } return QModelIndex(); } QModelIndex CursorThemeModel::defaultIndex() { return findIndex(defaultName); } const QStringList CursorThemeModel::searchPaths() { if (!baseDirs.isEmpty()) return baseDirs; #if XCURSOR_LIB_MAJOR == 1 && XCURSOR_LIB_MINOR < 1 // These are the default paths Xcursor will scan for cursor themes QString path("~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons"); // If XCURSOR_PATH is set, use that instead of the default path char *xcursorPath = std::getenv("XCURSOR_PATH"); if (xcursorPath) path = xcursorPath; #else // Get the search path from Xcursor QString path = XcursorLibraryPath(); #endif // Separate the paths - baseDirs = path.split(':', QString::SkipEmptyParts); + baseDirs = path.split(':', Qt::SkipEmptyParts); // Remove duplicates QMutableStringListIterator i(baseDirs); while (i.hasNext()) { const QString path = i.next(); QMutableStringListIterator j(i); while (j.hasNext()) if (j.next() == path) j.remove(); } // Expand all occurrences of ~/ to the home dir baseDirs.replaceInStrings(QRegExp("^~\\/"), QDir::home().path() + '/'); return baseDirs; } bool CursorThemeModel::hasTheme(const QString &name) const { const uint hash = qHash(name); foreach (const CursorTheme *theme, list) if (theme->hash() == hash) return true; return false; } bool CursorThemeModel::isCursorTheme(const QString &theme, const int depth) { // Prevent infinite recursion if (depth > 10) return false; // Search each icon theme directory for 'theme' foreach (const QString &baseDir, searchPaths()) { QDir dir(baseDir); if (!dir.exists() || !dir.cd(theme)) continue; // If there's a cursors subdir, we'll assume this is a cursor theme if (dir.exists(QStringLiteral("cursors"))) return true; // If the theme doesn't have an index.theme file, it can't inherit any themes. if (!dir.exists(QStringLiteral("index.theme"))) continue; // Open the index.theme file, so we can get the list of inherited themes KConfig config(dir.path() + "/index.theme", KConfig::NoGlobals); KConfigGroup cg(&config, "Icon Theme"); // Recurse through the list of inherited themes, to check if one of them // is a cursor theme. QStringList inherits = cg.readEntry("Inherits", QStringList()); foreach (const QString &inherit, inherits) { // Avoid possible DoS if (inherit == theme) continue; if (isCursorTheme(inherit, depth + 1)) return true; } } return false; } bool CursorThemeModel::handleDefault(const QDir &themeDir) { QFileInfo info(themeDir.path()); // If "default" is a symlink if (info.isSymLink()) { QFileInfo target(info.symLinkTarget()); if (target.exists() && (target.isDir() || target.isSymLink())) defaultName = target.fileName(); return true; } // If there's no cursors subdir, or if it's empty if (!themeDir.exists(QStringLiteral("cursors")) || QDir(themeDir.path() + "/cursors") .entryList(QDir::Files | QDir::NoDotAndDotDot ).isEmpty()) { if (themeDir.exists(QStringLiteral("index.theme"))) { XCursorTheme theme(themeDir); if (!theme.inherits().isEmpty()) defaultName = theme.inherits().at(0); } return true; } defaultName = QStringLiteral("default"); return false; } void CursorThemeModel::processThemeDir(const QDir &themeDir) { bool haveCursors = themeDir.exists(QStringLiteral("cursors")); // Special case handling of "default", since it's usually either a // symlink to another theme, or an empty theme that inherits another // theme. if (defaultName.isNull() && themeDir.dirName() == QLatin1String("default")) { if (handleDefault(themeDir)) return; } // If the directory doesn't have a cursors subdir and lacks an // index.theme file it can't be a cursor theme. if (!themeDir.exists(QStringLiteral("index.theme")) && !haveCursors) return; // Create a cursor theme object for the theme dir XCursorTheme *theme = new XCursorTheme(themeDir); // Skip this theme if it's hidden. if (theme->isHidden()) { delete theme; return; } // If there's no cursors subdirectory we'll do a recursive scan // to check if the theme inherits a theme with one. if (!haveCursors) { bool foundCursorTheme = false; foreach (const QString &name, theme->inherits()) if ((foundCursorTheme = isCursorTheme(name))) break; if (!foundCursorTheme) { delete theme; return; } } // Append the theme to the list beginInsertRows(QModelIndex(), list.size(), list.size()); list.append(theme); endInsertRows(); } void CursorThemeModel::insertThemes() { // Scan each base dir for Xcursor themes and add them to the list. foreach (const QString &baseDir, searchPaths()) { QDir dir(baseDir); if (!dir.exists()) continue; // Process each subdir in the directory foreach (const QString &name, dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { // Don't process the theme if a theme with the same name already exists // in the list. Xcursor will pick the first one it finds in that case, // and since we use the same search order, the one Xcursor picks should // be the one already in the list. if (hasTheme(name) || !dir.cd(name)) continue; processThemeDir(dir); dir.cdUp(); // Return to the base dir } } // The theme Xcursor will end up using if no theme is configured if (defaultName.isNull() || !hasTheme(defaultName)) defaultName = QStringLiteral("KDE_Classic"); } bool CursorThemeModel::addTheme(const QDir &dir) { XCursorTheme *theme = new XCursorTheme(dir); // Don't add the theme to the list if it's hidden if (theme->isHidden()) { delete theme; return false; } // ### If the theme is hidden, the user will probably find it strange that it // doesn't appear in the list view. There also won't be a way for the user // to delete the theme using the KCM. Perhaps a warning about this should // be issued, and the user be given a chance to undo the installation. // If an item with the same name already exists in the list, // we'll remove it before inserting the new one. for (int i = 0; i < list.count(); i++) { if (list.at(i)->hash() == theme->hash()) { removeTheme(index(i, 0)); break; } } // Append the theme to the list beginInsertRows(QModelIndex(), rowCount(), rowCount()); list.append(theme); endInsertRows(); return true; } void CursorThemeModel::removeTheme(const QModelIndex &index) { if (!index.isValid()) return; beginRemoveRows(QModelIndex(), index.row(), index.row()); delete list.takeAt(index.row()); endRemoveRows(); } diff --git a/kcms/dateandtime/dtime.cpp b/kcms/dateandtime/dtime.cpp index 9cfcdd55b..d2c722441 100644 --- a/kcms/dateandtime/dtime.cpp +++ b/kcms/dateandtime/dtime.cpp @@ -1,489 +1,489 @@ /* * dtime.cpp * * Copyright (C) 1998 Luca Montecchiani * * Plasma analog-clock drawing code: * * Copyright 2007 by Aaron Seigo * Copyright 2007 by Riccardo Iaconelli * * 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) any later version. * * 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. * */ #include "dtime.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "timedated_interface.h" #include "helper.h" Dtime::Dtime(QWidget * parent, bool haveTimeDated): QWidget(parent), m_haveTimedated(haveTimeDated) { setupUi(this); connect(setDateTimeAuto, &QCheckBox::toggled, this, &Dtime::serverTimeCheck); connect(setDateTimeAuto, &QCheckBox::toggled, this, &Dtime::configChanged); timeServerList->setEditable(false); connect(timeServerList, static_cast(&QComboBox::activated), this, &Dtime::configChanged); connect(timeServerList, &QComboBox::editTextChanged, this, &Dtime::configChanged); connect(setDateTimeAuto, &QCheckBox::toggled, timeServerList, &QComboBox::setEnabled); timeServerList->setEnabled(false); timeServerList->setEditable(true); if (!haveTimeDated) { findNTPutility(); if (ntpUtility.isEmpty()) { QString toolTip = i18n("No NTP utility has been found. " "Install 'ntpdate' or 'rdate' command to enable automatic " "updating of date and time."); setDateTimeAuto->setEnabled(false); setDateTimeAuto->setToolTip(toolTip); timeServerList->setToolTip(toolTip); } } QVBoxLayout *v2 = new QVBoxLayout( timeBox ); v2->setContentsMargins( 0, 0, 0, 0 ); kclock = new Kclock( timeBox ); kclock->setObjectName(QStringLiteral("Kclock")); kclock->setMinimumSize(150,150); v2->addWidget( kclock ); v2->addSpacing( style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing) ); QHBoxLayout *v3 = new QHBoxLayout( ); v2->addLayout( v3 ); v3->addStretch(); timeEdit = new QTimeEdit( timeBox ); timeEdit->setWrapping(true); timeEdit->setDisplayFormat(KLocale::global()->use12Clock() ? "hh:mm:ss ap" : "HH:mm:ss"); v3->addWidget(timeEdit); v3->addStretch(); QString wtstr = i18n("Here you can change the system time. Click into the" " hours, minutes or seconds field to change the relevant value, either" " using the up and down buttons to the right or by entering a new value."); timeEdit->setWhatsThis( wtstr ); connect(timeEdit, &QTimeEdit::timeChanged, this, &Dtime::set_time); connect(cal, &KDatePicker::dateChanged, this, &Dtime::changeDate); connect(&internalTimer, &QTimer::timeout, this, &Dtime::timeout); kclock->setEnabled(false); //Timezone connect(tzonelist, &K4TimeZoneWidget::itemSelectionChanged, this, &Dtime::configChanged); tzonesearch->setTreeWidget(tzonelist); } void Dtime::currentZone() { KTimeZone localZone = KSystemTimeZones::local(); if (localZone.abbreviations().isEmpty()) { m_local->setText(i18nc("%1 is name of time zone", "Current local time zone: %1", K4TimeZoneWidget::displayName(localZone))); } else { m_local->setText(i18nc("%1 is name of time zone, %2 is its abbreviation", "Current local time zone: %1 (%2)", K4TimeZoneWidget::displayName(localZone), QString::fromUtf8(localZone.abbreviations().first()))); } } void Dtime::serverTimeCheck() { // Enable time and date if the ntp utility is missing bool enabled = ntpUtility.isEmpty() || !setDateTimeAuto->isChecked(); cal->setEnabled(enabled); timeEdit->setEnabled(enabled); //kclock->setEnabled(enabled); } void Dtime::findNTPutility(){ QByteArray envpath = qgetenv("PATH"); if (!envpath.isEmpty() && envpath.startsWith(':')) { envpath.remove(0, 1); } QStringList path = {"/sbin", "/usr/sbin"}; if (!envpath.isEmpty()) { path += QFile::decodeName(envpath).split(QLatin1Char(':')); } else { path += {"/bin", "/usr/bin"}; } const auto possible_ntputilities = {"ntpdate", "rdate"}; for (const QString &possible_ntputility : possible_ntputilities) { auto ntpUtility = QStandardPaths::findExecutable(possible_ntputility, path); if (!ntpUtility.isEmpty()) { qDebug() << "ntpUtility = " << ntpUtility; return; } } qDebug() << "ntpUtility not found!"; } void Dtime::set_time() { if( ontimeout ) return; internalTimer.stop(); time = timeEdit->time(); kclock->setTime( time ); emit timeChanged( true ); } void Dtime::changeDate(const QDate &d) { date = d; emit timeChanged( true ); } void Dtime::configChanged(){ emit timeChanged( true ); } void Dtime::load() { QString currentTimeZone; if (m_haveTimedated) { OrgFreedesktopTimedate1Interface timeDatedIface(QStringLiteral("org.freedesktop.timedate1"), QStringLiteral("/org/freedesktop/timedate1"), QDBusConnection::systemBus()); //the server list is not relevant for timesyncd, it fetches it from the network timeServerList->setVisible(false); timeServerLabel->setVisible(false); setDateTimeAuto->setEnabled(timeDatedIface.canNTP()); setDateTimeAuto->setChecked(timeDatedIface.nTP()); currentTimeZone = timeDatedIface.timezone(); } else { // The config is actually written to the system config, but the user does not have any local config, // since there is nothing writing it. KConfig _config( QStringLiteral("kcmclockrc"), KConfig::NoGlobals ); KConfigGroup config(&_config, "NTP"); timeServerList->clear(); timeServerList->addItems(config.readEntry("servers", i18n("Public Time Server (pool.ntp.org),\ asia.pool.ntp.org,\ europe.pool.ntp.org,\ north-america.pool.ntp.org,\ - oceania.pool.ntp.org")).split(',', QString::SkipEmptyParts)); + oceania.pool.ntp.org")).split(',', Qt::SkipEmptyParts)); setDateTimeAuto->setChecked(config.readEntry("enabled", false)); if (ntpUtility.isEmpty()) { timeServerList->setEnabled(false); } currentTimeZone = KSystemTimeZones::local().name(); } // Reset to the current date and time time = QTime::currentTime(); date = QDate::currentDate(); cal->setDate(date); // start internal timer internalTimer.start( 1000 ); timeout(); //Timezone currentZone(); tzonelist->setSelected(currentTimeZone, true); emit timeChanged(false); } QString Dtime::selectedTimeZone() const { QStringList selectedZones(tzonelist->selection()); if (!selectedZones.isEmpty()) { return selectedZones.first(); } return QString(); } QStringList Dtime::ntpServers() const { // Save the order, but don't duplicate! QStringList list; if( timeServerList->count() != 0) list.append(timeServerList->currentText()); for ( int i=0; icount();i++ ) { QString text = timeServerList->itemText(i); if( !list.contains(text) ) list.append(text); // Limit so errors can go away and not stored forever if( list.count() == 10) break; } return list; } bool Dtime::ntpEnabled() const { return setDateTimeAuto->isChecked(); } QDateTime Dtime::userTime() const { return QDateTime(date, QTime(timeEdit->time())); } void Dtime::processHelperErrors( int code ) { if( code & ClockHelper::NTPError ) { KMessageBox::error( this, i18n("Unable to contact time server: %1.", timeServer) ); setDateTimeAuto->setChecked( false ); } if( code & ClockHelper::DateError ) { KMessageBox::error( this, i18n("Can not set date.")); } if( code & ClockHelper::TimezoneError) KMessageBox::error( this, i18n("Error setting new time zone."), i18n("Time zone Error")); } void Dtime::timeout() { // get current time time = QTime::currentTime(); ontimeout = true; timeEdit->setTime(time); ontimeout = false; kclock->setTime( time ); } QString Dtime::quickHelp() const { return i18n("

Date & Time

This system settings module can be used to set the system date and" " time. As these settings do not only affect you as a user, but rather the whole system, you" " can only change these settings when you start the System Settings as root. If you do not have" " the root password, but feel the system time should be corrected, please contact your system" " administrator."); } Kclock::Kclock(QWidget *parent) : QWidget(parent) { m_theme = new Plasma::Svg(this); m_theme->setImagePath(QStringLiteral("widgets/clock")); m_theme->setContainsMultipleImages(true); } Kclock::~Kclock() { delete m_theme; } void Kclock::showEvent( QShowEvent *event ) { setClockSize( size() ); QWidget::showEvent( event ); } void Kclock::resizeEvent( QResizeEvent * ) { setClockSize( size() ); } void Kclock::setClockSize(const QSize &size) { int dim = qMin(size.width(), size.height()); QSize newSize = QSize(dim, dim) * devicePixelRatioF(); if (newSize != m_faceCache.size()) { m_faceCache = QPixmap(newSize); m_handsCache = QPixmap(newSize); m_glassCache = QPixmap(newSize); m_faceCache.setDevicePixelRatio(devicePixelRatioF()); m_handsCache.setDevicePixelRatio(devicePixelRatioF()); m_glassCache.setDevicePixelRatio(devicePixelRatioF()); m_theme->resize(QSize(dim, dim)); m_repaintCache = RepaintAll; } } void Kclock::setTime(const QTime &time) { if (time.minute() != this->time.minute() || time.hour() != this->time.hour()) { if (m_repaintCache == RepaintNone) { m_repaintCache = RepaintHands; } } this->time = time; update(); } void Kclock::drawHand(QPainter *p, const QRect &rect, const qreal verticalTranslation, const qreal rotation, const QString &handName) { // this code assumes the following conventions in the svg file: // - the _vertical_ position of the hands should be set with respect to the center of the face // - the _horizontal_ position of the hands does not matter // - the _shadow_ elements should have the same vertical position as their _hand_ element counterpart QRectF elementRect; QString name = handName + "HandShadow"; if (m_theme->hasElement(name)) { p->save(); elementRect = m_theme->elementRect(name); if( rect.height() < 64 ) elementRect.setWidth( elementRect.width() * 2.5 ); static const QPoint offset = QPoint(2, 3); p->translate(rect.x() + (rect.width() / 2) + offset.x(), rect.y() + (rect.height() / 2) + offset.y()); p->rotate(rotation); p->translate(-elementRect.width()/2, elementRect.y()-verticalTranslation); m_theme->paint(p, QRectF(QPointF(0, 0), elementRect.size()), name); p->restore(); } p->save(); name = handName + "Hand"; elementRect = m_theme->elementRect(name); if (rect.height() < 64) { elementRect.setWidth(elementRect.width() * 2.5); } p->translate(rect.x() + rect.width()/2, rect.y() + rect.height()/2); p->rotate(rotation); p->translate(-elementRect.width()/2, elementRect.y()-verticalTranslation); m_theme->paint(p, QRectF(QPointF(0, 0), elementRect.size()), name); p->restore(); } void Kclock::paintInterface(QPainter *p, const QRect &rect) { const bool m_showSecondHand = true; // compute hand angles const qreal minutes = 6.0 * time.minute() - 180; const qreal hours = 30.0 * time.hour() - 180 + ((time.minute() / 59.0) * 30.0); qreal seconds = 0; if (m_showSecondHand) { static const double anglePerSec = 6; seconds = anglePerSec * time.second() - 180; } // paint face and glass cache QRect faceRect = m_faceCache.rect(); QRect targetRect = QRect(QPoint(0, 0), QSize(qRound(m_faceCache.width() / devicePixelRatioF()), qRound(m_faceCache.height() / devicePixelRatioF()))); if (m_repaintCache == RepaintAll) { m_faceCache.fill(Qt::transparent); m_glassCache.fill(Qt::transparent); QPainter facePainter(&m_faceCache); QPainter glassPainter(&m_glassCache); facePainter.setRenderHint(QPainter::SmoothPixmapTransform); glassPainter.setRenderHint(QPainter::SmoothPixmapTransform); m_theme->paint(&facePainter, targetRect, QStringLiteral("ClockFace")); glassPainter.save(); QRectF elementRect = QRectF(QPointF(0, 0), m_theme->elementSize(QStringLiteral("HandCenterScrew"))); glassPainter.translate(faceRect.width() / (2 * devicePixelRatioF()) - elementRect.width() / 2, faceRect.height() / (2 * devicePixelRatioF()) - elementRect.height() / 2); m_theme->paint(&glassPainter, elementRect, QStringLiteral("HandCenterScrew")); glassPainter.restore(); m_theme->paint(&glassPainter, targetRect, QStringLiteral("Glass")); // get vertical translation, see drawHand() for more details m_verticalTranslation = m_theme->elementRect(QStringLiteral("ClockFace")).center().y(); } // paint hour and minute hands cache if (m_repaintCache == RepaintHands || m_repaintCache == RepaintAll) { m_handsCache.fill(Qt::transparent); QPainter handsPainter(&m_handsCache); handsPainter.drawPixmap(targetRect, m_faceCache, faceRect); handsPainter.setRenderHint(QPainter::SmoothPixmapTransform); drawHand(&handsPainter, targetRect, m_verticalTranslation, hours, QStringLiteral("Hour")); drawHand(&handsPainter, targetRect, m_verticalTranslation, minutes, QStringLiteral("Minute")); } // reset repaint cache flag m_repaintCache = RepaintNone; // paint caches and second hand if (targetRect.width() < rect.width()) { targetRect.moveLeft((rect.width() - targetRect.width()) / 2); } p->drawPixmap(targetRect, m_handsCache, faceRect); if (m_showSecondHand) { p->setRenderHint(QPainter::SmoothPixmapTransform); drawHand(p, targetRect, m_verticalTranslation, seconds, QStringLiteral("Second")); } p->drawPixmap(targetRect, m_glassCache, faceRect); } void Kclock::paintEvent( QPaintEvent * ) { QPainter paint(this); paint.setRenderHint(QPainter::Antialiasing); paintInterface(&paint, rect()); } diff --git a/kcms/desktoptheme/kcm.cpp b/kcms/desktoptheme/kcm.cpp index 162cfa60b..92332106f 100644 --- a/kcms/desktoptheme/kcm.cpp +++ b/kcms/desktoptheme/kcm.cpp @@ -1,271 +1,271 @@ /* This file is part of the KDE Project Copyright (c) 2014 Marco Martin Copyright (c) 2014 Vishesh Handa Copyright (c) 2016 David Rosca Copyright (c) 2018 Kai Uwe Broulik Copyright (c) 2019 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcm.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "desktopthemesettings.h" #include "filterproxymodel.h" #include "themesmodel.h" Q_LOGGING_CATEGORY(KCM_DESKTOP_THEME, "kcm_desktoptheme") K_PLUGIN_FACTORY_WITH_JSON(KCMDesktopThemeFactory, "kcm_desktoptheme.json", registerPlugin();) KCMDesktopTheme::KCMDesktopTheme(QObject *parent, const QVariantList &args) : KQuickAddons::ManagedConfigModule(parent, args) , m_settings(new DesktopThemeSettings(this)) , m_model(new ThemesModel(this)) , m_filteredModel(new FilterProxyModel(this)) , m_haveThemeExplorerInstalled(false) { qmlRegisterType(); qmlRegisterUncreatableType("org.kde.private.kcms.desktoptheme", 1, 0, "ThemesModel", "Cannot create ThemesModel"); qmlRegisterUncreatableType("org.kde.private.kcms.desktoptheme", 1, 0, "FilterProxyModel", "Cannot create FilterProxyModel"); KAboutData* about = new KAboutData(QStringLiteral("kcm_desktoptheme"), i18n("Plasma Style"), QStringLiteral("0.1"), QString(), KAboutLicense::LGPL); about->addAuthor(i18n("David Rosca"), QString(), QStringLiteral("nowrep@gmail.com")); setAboutData(about); setButtons(Apply | Default | Help); m_haveThemeExplorerInstalled = !QStandardPaths::findExecutable(QStringLiteral("plasmathemeexplorer")).isEmpty(); connect(m_model, &ThemesModel::pendingDeletionsChanged, this, &KCMDesktopTheme::settingsChanged); connect(m_model, &ThemesModel::selectedThemeChanged, this, [this](const QString &pluginName) { m_settings->setName(pluginName); }); connect(m_settings, &DesktopThemeSettings::nameChanged, this, [this] { m_model->setSelectedTheme(m_settings->name()); }); connect(m_model, &ThemesModel::selectedThemeChanged, m_filteredModel, &FilterProxyModel::setSelectedTheme); m_filteredModel->setSourceModel(m_model); } KCMDesktopTheme::~KCMDesktopTheme() { } DesktopThemeSettings *KCMDesktopTheme::desktopThemeSettings() const { return m_settings; } ThemesModel *KCMDesktopTheme::desktopThemeModel() const { return m_model; } FilterProxyModel *KCMDesktopTheme::filteredModel() const { return m_filteredModel; } bool KCMDesktopTheme::downloadingFile() const { return m_tempCopyJob; } void KCMDesktopTheme::installThemeFromFile(const QUrl &url) { if (url.isLocalFile()) { installTheme(url.toLocalFile()); return; } if (m_tempCopyJob) { return; } m_tempInstallFile.reset(new QTemporaryFile()); if (!m_tempInstallFile->open()) { emit showErrorMessage(i18n("Unable to create a temporary file.")); m_tempInstallFile.reset(); return; } m_tempCopyJob = KIO::file_copy(url, QUrl::fromLocalFile(m_tempInstallFile->fileName()), -1, KIO::Overwrite); m_tempCopyJob->uiDelegate()->setAutoErrorHandlingEnabled(true); emit downloadingFileChanged(); connect(m_tempCopyJob, &KIO::FileCopyJob::result, this, [this, url](KJob *job) { if (job->error() != KJob::NoError) { emit showErrorMessage(i18n("Unable to download the theme: %1", job->errorText())); return; } installTheme(m_tempInstallFile->fileName()); m_tempInstallFile.reset(); }); connect(m_tempCopyJob, &QObject::destroyed, this, &KCMDesktopTheme::downloadingFileChanged); } void KCMDesktopTheme::installTheme(const QString &path) { qCDebug(KCM_DESKTOP_THEME) << "Installing ... " << path; const QString program = QStringLiteral("kpackagetool5"); const QStringList arguments = { QStringLiteral("--type"), QStringLiteral("Plasma/Theme"), QStringLiteral("--install"), path}; qCDebug(KCM_DESKTOP_THEME) << program << arguments.join(QLatin1Char(' ')); QProcess *myProcess = new QProcess(this); connect(myProcess, static_cast(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus) if (exitCode == 0) { emit showSuccessMessage(i18n("Theme installed successfully.")); load(); } else { Q_EMIT showErrorMessage(i18n("Theme installation failed.")); } }); - connect(myProcess, static_cast(&QProcess::error), + connect(myProcess, &QProcess::errorOccurred, this, [this](QProcess::ProcessError e) { qCWarning(KCM_DESKTOP_THEME) << "Theme installation failed: " << e; Q_EMIT showErrorMessage(i18n("Theme installation failed.")); }); myProcess->start(program, arguments); } void KCMDesktopTheme::applyPlasmaTheme(QQuickItem *item, const QString &themeName) { if (!item) { return; } Plasma::Theme *theme = m_themes[themeName]; if (!theme) { theme = new Plasma::Theme(themeName, this); m_themes[themeName] = theme; } Q_FOREACH (Plasma::Svg *svg, item->findChildren()) { svg->setTheme(theme); svg->setUsingRenderingCache(false); } } void KCMDesktopTheme::load() { ManagedConfigModule::load(); m_model->load(); m_model->setSelectedTheme(m_settings->name()); } void KCMDesktopTheme::save() { ManagedConfigModule::save(); Plasma::Theme().setThemeName(m_settings->name()); processPendingDeletions(); } void KCMDesktopTheme::defaults() { ManagedConfigModule::defaults(); // can this be done more elegantly? const auto pendingDeletions = m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true); for (const QModelIndex &idx : pendingDeletions) { m_model->setData(idx, false, ThemesModel::PendingDeletionRole); } } bool KCMDesktopTheme::canEditThemes() const { return m_haveThemeExplorerInstalled; } void KCMDesktopTheme::editTheme(const QString &theme) { - QProcess::startDetached(QStringLiteral("plasmathemeexplorer -t ") % theme); + QProcess::startDetached(QStringLiteral("plasmathemeexplorer"), {QStringLiteral("-t"), theme}); } bool KCMDesktopTheme::isSaveNeeded() const { return !m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true).isEmpty(); } void KCMDesktopTheme::processPendingDeletions() { const QString program = QStringLiteral("plasmapkg2"); const auto pendingDeletions = m_model->match(m_model->index(0, 0), ThemesModel::PendingDeletionRole, true, -1 /*all*/); QVector persistentPendingDeletions; // turn into persistent model index so we can delete as we go std::transform(pendingDeletions.begin(), pendingDeletions.end(), std::back_inserter(persistentPendingDeletions), [](const QModelIndex &idx) { return QPersistentModelIndex(idx); }); for (const QPersistentModelIndex &idx : persistentPendingDeletions) { const QString pluginName = idx.data(ThemesModel::PluginNameRole).toString(); const QString displayName = idx.data(Qt::DisplayRole).toString(); Q_ASSERT(pluginName != m_settings->name()); const QStringList arguments = {QStringLiteral("-t"), QStringLiteral("theme"), QStringLiteral("-r"), pluginName}; QProcess *process = new QProcess(this); connect(process, static_cast(&QProcess::finished), this, [this, process, idx, pluginName, displayName](int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus) if (exitCode == 0) { m_model->removeRow(idx.row()); } else { emit showErrorMessage(i18n("Removing theme failed: %1", QString::fromLocal8Bit(process->readAllStandardOutput().trimmed()))); m_model->setData(idx, false, ThemesModel::PendingDeletionRole); } process->deleteLater(); }); process->start(program, arguments); process->waitForFinished(); // needed so it deletes fine when "OK" is clicked and the dialog destroyed } } #include "kcm.moc" diff --git a/kcms/fonts/kxftconfig.cpp b/kcms/fonts/kxftconfig.cpp index 66a2a5f61..57820a13e 100644 --- a/kcms/fonts/kxftconfig.cpp +++ b/kcms/fonts/kxftconfig.cpp @@ -1,945 +1,945 @@ /* Copyright (c) 2002 Craig Drummond This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kxftconfig.h" #ifdef HAVE_FONTCONFIG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; static int point2Pixel(double point) { return (int)(((point * QX11Info::appDpiY()) / 72.0) + 0.5); } static int pixel2Point(double pixel) { return (int)(((pixel * 72.0) / (double)QX11Info::appDpiY()) + 0.5); } static bool equal(double d1, double d2) { return (fabs(d1 - d2) < 0.0001); } static QString dirSyntax(const QString &d) { if (!d.isNull()) { QString ds(d); ds.replace("//", "/"); int slashPos = ds.lastIndexOf('/'); if (slashPos != (((int)ds.length()) - 1)) { ds.append('/'); } return ds; } return d; } inline bool fExists(const QString &p) { return QFileInfo(p).isFile(); } inline bool dWritable(const QString &p) { QFileInfo info(p); return info.isDir() && info.isWritable(); } static QString getDir(const QString &f) { QString d(f); int slashPos = d.lastIndexOf('/'); if (-1 != slashPos) { d.remove(slashPos + 1, d.length()); } return dirSyntax(d); } static QDateTime getTimeStamp(const QString &item) { return QFileInfo(item).lastModified(); } static QString getEntry(QDomElement element, const char *type, unsigned int numAttributes, ...) { - if (numAttributes == element.attributes().length()) { + if (numAttributes == uint(element.attributes().length())) { va_list args; unsigned int arg; bool ok = true; va_start(args, numAttributes); for (arg = 0; arg < numAttributes && ok; ++arg) { const char *attr = va_arg(args, const char *); const char *val = va_arg(args, const char *); if (!attr || !val || val != element.attribute(attr)) { ok = false; } } va_end(args); if (ok) { QDomNode n = element.firstChild(); if (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull() && type == e.tagName()) { return e.text(); } } } } return QString(); } static KXftConfig::SubPixel::Type strToType(const char *str) { if (0 == strcmp(str, "rgb")) { return KXftConfig::SubPixel::Rgb; } else if (0 == strcmp(str, "bgr")) { return KXftConfig::SubPixel::Bgr; } else if (0 == strcmp(str, "vrgb")) { return KXftConfig::SubPixel::Vrgb; } else if (0 == strcmp(str, "vbgr")) { return KXftConfig::SubPixel::Vbgr; } else if (0 == strcmp(str, "none")) { return KXftConfig::SubPixel::None; } else { return KXftConfig::SubPixel::NotSet; } } static KXftConfig::Hint::Style strToStyle(const char *str) { if (0 == strcmp(str, "hintslight")) { return KXftConfig::Hint::Slight; } else if (0 == strcmp(str, "hintmedium")) { return KXftConfig::Hint::Medium; } else if (0 == strcmp(str, "hintfull")) { return KXftConfig::Hint::Full; } else { return KXftConfig::Hint::None; } } KXftConfig::KXftConfig() : m_doc("fontconfig") , m_file(getConfigFile()) { qDebug() << "Using fontconfig file:" << m_file; reset(); } KXftConfig::~KXftConfig() { } // // Obtain location of config file to use. QString KXftConfig::getConfigFile() { FcStrList *list = FcConfigGetConfigFiles(FcConfigGetCurrent()); QStringList localFiles; FcChar8 *file; QString home(dirSyntax(QDir::homePath())); m_globalFiles.clear(); while ((file = FcStrListNext(list))) { QString f((const char *)file); if (fExists(f) && 0 == f.indexOf(home)) { localFiles.append(f); } else { m_globalFiles.append(f); } } FcStrListDone(list); // // Go through list of localFiles, looking for the preferred one... if (localFiles.count()) { QStringList::const_iterator it(localFiles.begin()), end(localFiles.end()); for (; it != end; ++it) if (-1 != (*it).indexOf(QRegExp("/\\.?fonts\\.conf$"))) { return *it; } return localFiles.front(); // Just return the 1st one... } else { // Hmmm... no known localFiles? if (FcGetVersion() >= 21000) { QString targetPath(KGlobal::dirs()->localxdgconfdir() + "fontconfig"); QDir target(targetPath); if (!target.exists()) { target.mkpath(targetPath); } return targetPath + "/fonts.conf"; } else { return home + "/.fonts.conf"; } } } bool KXftConfig::reset() { bool ok = false; m_madeChanges = false; m_hint.reset(); m_hinting.reset(); m_excludeRange.reset(); m_excludePixelRange.reset(); m_subPixel.reset(); m_antiAliasing.reset(); m_antiAliasingHasLocalConfig = false; m_subPixelHasLocalConfig = false; m_hintHasLocalConfig = false; QStringList::const_iterator it(m_globalFiles.begin()), end(m_globalFiles.end()); for (; it != end; ++it) { ok |= parseConfigFile(*it); } AntiAliasing globalAntialiasing; globalAntialiasing.state = m_antiAliasing.state; SubPixel globalSubPixel; globalSubPixel.type = m_subPixel.type; Hint globalHint; globalHint.style = m_hint.style; Exclude globalExcludeRange; globalExcludeRange.from = m_excludeRange.from; globalExcludeRange.to = m_excludePixelRange.to; Exclude globalExcludePixelRange; globalExcludePixelRange.from = m_excludePixelRange.from; globalExcludePixelRange.to = m_excludePixelRange.to; Hinting globalHinting; globalHinting.set = m_hinting.set; m_antiAliasing.reset(); m_subPixel.reset(); m_hint.reset(); m_hinting.reset(); m_excludeRange.reset(); m_excludePixelRange.reset(); ok |= parseConfigFile(m_file); if (m_antiAliasing.node.isNull()) { m_antiAliasing = globalAntialiasing; } else { m_antiAliasingHasLocalConfig = true; } if (m_subPixel.node.isNull()) { m_subPixel = globalSubPixel; } else { m_subPixelHasLocalConfig = true; } if (m_hint.node.isNull()) { m_hint = globalHint; } else { m_hintHasLocalConfig = true; } if (m_hinting.node.isNull()) { m_hinting = globalHinting; } if (m_excludeRange.node.isNull()) { m_excludeRange = globalExcludeRange; } if (m_excludePixelRange.node.isNull()) { m_excludePixelRange = globalExcludePixelRange; } return ok; } bool KXftConfig::apply() { bool ok = true; if (m_madeChanges) { // // Check if file has been written since we last read it. If it has, then re-read and add any // of our changes... if (fExists(m_file) && getTimeStamp(m_file) != m_time) { KXftConfig newConfig; newConfig.setExcludeRange(m_excludeRange.from, m_excludeRange.to); newConfig.setSubPixelType(m_subPixel.type); newConfig.setHintStyle(m_hint.style); newConfig.setAntiAliasing(m_antiAliasing.state); ok = newConfig.changed() ? newConfig.apply() : true; if (ok) { reset(); } else { m_time = getTimeStamp(m_file); } } else { // Ensure these are always equal... m_excludePixelRange.from = (int)point2Pixel(m_excludeRange.from); m_excludePixelRange.to = (int)point2Pixel(m_excludeRange.to); FcAtomic *atomic = FcAtomicCreate((const unsigned char *)(QFile::encodeName(m_file).data())); ok = false; if (atomic) { if (FcAtomicLock(atomic)) { FILE *f = fopen((char *)FcAtomicNewFile(atomic), "w"); if (f) { applySubPixelType(); applyHintStyle(); applyAntiAliasing(); applyExcludeRange(false); applyExcludeRange(true); // // Check document syntax... static const char qtXmlHeader[] = ""; static const char xmlHeader[] = ""; static const char qtDocTypeLine[] = ""; static const char docTypeLine[] = ""; QString str(m_doc.toString()); int idx; if (0 != str.indexOf("= 0 || to >= 0) && foundFalse) { m_excludeRange.from = from < to ? from : to; m_excludeRange.to = from < to ? to : from; m_excludeRange.node = n; } else if ((pixelFrom >= 0 || pixelTo >= 0) && foundFalse) { m_excludePixelRange.from = pixelFrom < pixelTo ? pixelFrom : pixelTo; m_excludePixelRange.to = pixelFrom < pixelTo ? pixelTo : pixelFrom; m_excludePixelRange.node = n; } } break; default: break; } } } n = n.nextSibling(); } } void KXftConfig::applySubPixelType() { if (SubPixel::NotSet == m_subPixel.type) { if (!m_subPixel.node.isNull()) { m_doc.documentElement().removeChild(m_subPixel.node); m_subPixel.node.clear(); } } else { QDomElement matchNode = m_doc.createElement("match"); QDomElement typeNode = m_doc.createElement("const"); QDomElement editNode = m_doc.createElement("edit"); QDomText typeText = m_doc.createTextNode(toStr(m_subPixel.type)); matchNode.setAttribute("target", "font"); editNode.setAttribute("mode", "assign"); editNode.setAttribute("name", "rgba"); editNode.appendChild(typeNode); typeNode.appendChild(typeText); matchNode.appendChild(editNode); if (m_subPixel.node.isNull()) { m_doc.documentElement().appendChild(matchNode); } else { m_doc.documentElement().replaceChild(matchNode, m_subPixel.node); } m_subPixel.node = matchNode; } } void KXftConfig::applyHintStyle() { applyHinting(); if (Hint::NotSet == m_hint.style) { if (!m_hint.node.isNull()) { m_doc.documentElement().removeChild(m_hint.node); m_hint.node.clear(); } if (!m_hinting.node.isNull()) { m_doc.documentElement().removeChild(m_hinting.node); m_hinting.node.clear(); } } else { QDomElement matchNode = m_doc.createElement("match"), typeNode = m_doc.createElement("const"), editNode = m_doc.createElement("edit"); QDomText typeText = m_doc.createTextNode(toStr(m_hint.style)); matchNode.setAttribute("target", "font"); editNode.setAttribute("mode", "assign"); editNode.setAttribute("name", "hintstyle"); editNode.appendChild(typeNode); typeNode.appendChild(typeText); matchNode.appendChild(editNode); if (m_hint.node.isNull()) { m_doc.documentElement().appendChild(matchNode); } else { m_doc.documentElement().replaceChild(matchNode, m_hint.node); } m_hint.node = matchNode; } } void KXftConfig::applyHinting() { QDomElement matchNode = m_doc.createElement("match"), typeNode = m_doc.createElement("bool"), editNode = m_doc.createElement("edit"); QDomText typeText = m_doc.createTextNode(m_hinting.set ? "true" : "false"); matchNode.setAttribute("target", "font"); editNode.setAttribute("mode", "assign"); editNode.setAttribute("name", "hinting"); editNode.appendChild(typeNode); typeNode.appendChild(typeText); matchNode.appendChild(editNode); if (m_hinting.node.isNull()) { m_doc.documentElement().appendChild(matchNode); } else { m_doc.documentElement().replaceChild(matchNode, m_hinting.node); } m_hinting.node = matchNode; } void KXftConfig::applyExcludeRange(bool pixel) { Exclude &range = pixel ? m_excludePixelRange : m_excludeRange; if (equal(range.from, 0) && equal(range.to, 0)) { if (!range.node.isNull()) { m_doc.documentElement().removeChild(range.node); range.node.clear(); } } else { QString fromString, toString; fromString.setNum(range.from); toString.setNum(range.to); QDomElement matchNode = m_doc.createElement("match"), fromTestNode = m_doc.createElement("test"), fromNode = m_doc.createElement("double"), toTestNode = m_doc.createElement("test"), toNode = m_doc.createElement("double"), editNode = m_doc.createElement("edit"), boolNode = m_doc.createElement("bool"); QDomText fromText = m_doc.createTextNode(fromString), toText = m_doc.createTextNode(toString), boolText = m_doc.createTextNode("false"); matchNode.setAttribute("target", "font"); // CPD: Is target "font" or "pattern" ???? fromTestNode.setAttribute("qual", "any"); fromTestNode.setAttribute("name", pixel ? "pixelsize" : "size"); fromTestNode.setAttribute("compare", "more_eq"); fromTestNode.appendChild(fromNode); fromNode.appendChild(fromText); toTestNode.setAttribute("qual", "any"); toTestNode.setAttribute("name", pixel ? "pixelsize" : "size"); toTestNode.setAttribute("compare", "less_eq"); toTestNode.appendChild(toNode); toNode.appendChild(toText); editNode.setAttribute("mode", "assign"); editNode.setAttribute("name", "antialias"); editNode.appendChild(boolNode); boolNode.appendChild(boolText); matchNode.appendChild(fromTestNode); matchNode.appendChild(toTestNode); matchNode.appendChild(editNode); if (!m_antiAliasing.node.isNull()) { m_doc.documentElement().removeChild(range.node); } if(range.node.isNull()) { m_doc.documentElement().appendChild(matchNode); } else { m_doc.documentElement().replaceChild(matchNode, range.node); } range.node = matchNode; } } bool KXftConfig::antiAliasingHasLocalConfig() const { return m_antiAliasingHasLocalConfig; } KXftConfig::AntiAliasing::State KXftConfig::getAntiAliasing() const { return m_antiAliasing.state; } void KXftConfig::setAntiAliasing(AntiAliasing::State state) { if (state != m_antiAliasing.state) { m_antiAliasing.state = state; m_madeChanges = true; } } void KXftConfig::applyAntiAliasing() { if (AntiAliasing::NotSet == m_antiAliasing.state) { if (!m_antiAliasing.node.isNull()) { m_doc.documentElement().removeChild(m_antiAliasing.node); m_antiAliasing.node.clear(); } } else { QDomElement matchNode = m_doc.createElement("match"); QDomElement typeNode = m_doc.createElement("bool"); QDomElement editNode = m_doc.createElement("edit"); QDomText typeText = m_doc.createTextNode(m_antiAliasing.state == AntiAliasing::Enabled ? "true" : "false"); matchNode.setAttribute("target", "font"); editNode.setAttribute("mode", "assign"); editNode.setAttribute("name", "antialias"); editNode.appendChild(typeNode); typeNode.appendChild(typeText); matchNode.appendChild(editNode); if (!m_antiAliasing.node.isNull()) { m_doc.documentElement().removeChild(m_antiAliasing.node); } m_doc.documentElement().appendChild(matchNode); m_antiAliasing.node = matchNode; } } // KXftConfig only parses one config file, user's .fonts.conf usually. // If that one doesn't exist, then KXftConfig doesn't know if antialiasing // is enabled or not. So try to find out the default value from the default font. // Maybe there's a better way *shrug*. bool KXftConfig::aliasingEnabled() { FcPattern *pattern = FcPatternCreate(); FcConfigSubstitute(nullptr, pattern, FcMatchPattern); FcDefaultSubstitute(pattern); FcResult result; FcPattern *f = FcFontMatch(nullptr, pattern, &result); FcBool antialiased = FcTrue; FcPatternGetBool(f, FC_ANTIALIAS, 0, &antialiased); FcPatternDestroy(f); FcPatternDestroy(pattern); return antialiased == FcTrue; } #endif diff --git a/kcms/fonts/previewimageprovider.cpp b/kcms/fonts/previewimageprovider.cpp index a769e7130..33aaa6ae8 100644 --- a/kcms/fonts/previewimageprovider.cpp +++ b/kcms/fonts/previewimageprovider.cpp @@ -1,136 +1,137 @@ /* Copyright (c) 2018 Julian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "kxftconfig.h" #include "previewimageprovider.h" #include "previewrenderengine.h" QImage combineImages(const QList& images, const QColor& bgnd, int spacing=0) { int width = 0; int height = 0; QImage::Format format = QImage::Format_Invalid; int devicePixelRatio = 1; for(const auto& image : images) { if(width < image.width()) { width = image.width(); } height += image.height() + spacing; format = image.format(); devicePixelRatio = image.devicePixelRatio(); } height -= spacing; // To correctly align the image pixels on a high dpi display, // the image dimensions need to be a multiple of devicePixelRatio width = (width + devicePixelRatio - 1) / devicePixelRatio * devicePixelRatio; height = (height + devicePixelRatio - 1) / devicePixelRatio * devicePixelRatio; QImage combinedImage(width, height, format); combinedImage.setDevicePixelRatio(devicePixelRatio); combinedImage.fill(bgnd); int offset = 0; QPainter p(&combinedImage); for(const auto& image : images) { p.drawImage(0, offset, image); offset += (image.height() + spacing) / devicePixelRatio; } return combinedImage; } PreviewImageProvider::PreviewImageProvider(const QFont& font) : QQuickImageProvider(QQuickImageProvider::Image) , m_font(font) { } QImage PreviewImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { + Q_UNUSED(requestedSize) if (!KWindowSystem::isPlatformX11()) { return QImage(); } int subPixelIndex = 0; int hintingIndex = 0; const auto idpart = id.splitRef(QLatin1Char('.'))[0]; const auto sections = idpart.split(QLatin1Char('_')); if (sections.size() >= 2) { subPixelIndex = sections[0].toInt() + KXftConfig::SubPixel::None; hintingIndex = sections[1].toInt() + KXftConfig::Hint::None; } else { return QImage(); } KXftConfig xft; KXftConfig::AntiAliasing::State oldAntialiasing = xft.getAntiAliasing(); double oldStart = 0; double oldEnd = 0; xft.getExcludeRange(oldStart, oldEnd); KXftConfig::SubPixel::Type oldSubPixelType = KXftConfig::SubPixel::NotSet; xft.getSubPixelType(oldSubPixelType); KXftConfig::Hint::Style oldHintStyle = KXftConfig::Hint::NotSet; xft.getHintStyle(oldHintStyle); xft.setAntiAliasing(KXftConfig::AntiAliasing::Enabled); xft.setExcludeRange(0, 0); KXftConfig::SubPixel::Type subPixelType = (KXftConfig::SubPixel::Type)subPixelIndex; xft.setSubPixelType(subPixelType); KXftConfig::Hint::Style hintStyle = (KXftConfig::Hint::Style)hintingIndex; xft.setHintStyle(hintStyle); xft.apply(); QColor text(QApplication::palette().color(QPalette::Text)); QColor bgnd(QApplication::palette().color(QPalette::Window)); PreviewRenderEngine eng(true); QList lines; lines << eng.drawAutoSize(m_font, text, bgnd, eng.getDefaultPreviewString()); QImage img = combineImages(lines, bgnd, lines[0].height()*.25); xft.setAntiAliasing(oldAntialiasing); xft.setExcludeRange(oldStart, oldEnd); xft.setSubPixelType(oldSubPixelType); xft.setHintStyle(oldHintStyle); xft.apply(); *size = img.size(); return img; } diff --git a/kcms/hardware/joystick/joydevice.cpp b/kcms/hardware/joystick/joydevice.cpp index 16196e3fd..adc1399e7 100644 --- a/kcms/hardware/joystick/joydevice.cpp +++ b/kcms/hardware/joystick/joydevice.cpp @@ -1,406 +1,406 @@ /*************************************************************************** * Copyright (C) 2003 by Martin Koller * * kollix@aon.at * * This file is part of the KDE Control Center Module for Joysticks * * * * 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) any later version. * * * * 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. * ***************************************************************************/ #include "joydevice.h" #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------- JoyDevice::JoyDevice(const QString &devicefile) : devName(devicefile), joyFd(-1), buttons(0), axes(0), amin(nullptr), amax(nullptr), corr(nullptr), origCorr(nullptr) { } //-------------------------------------------------------------- QString JoyDevice::errText(ErrorCode code) const { switch ( code ) { case SUCCESS: return QLatin1String(""); case OPEN_FAILED: { return i18n("The given device %1 could not be opened: %2", devName, strerror(errno)); } case NO_JOYSTICK: { return i18n("The given device %1 is not a joystick.", devName); } case ERR_GET_VERSION: { return i18n("Could not get kernel driver version for joystick device %1: %2", devName, strerror(errno)); } case WRONG_VERSION: { int version = 0; int fd = ::open(devName.toLatin1(), O_RDONLY); if ( fd != -1 ) { ::ioctl(fd, JSIOCGVERSION, &version); ::close(fd); } KLocalizedString loc = ki18n("The current running kernel driver version (%1.%2.%3) is not the one this module was compiled for (%4.%5.%6)."); loc = loc.subs(version >> 16); loc = loc.subs((version >> 8) & 0xFF); loc = loc.subs(version & 0xFF); loc = loc.subs(JS_VERSION >> 16); loc = loc.subs((JS_VERSION >> 8) & 0xFF); loc = loc.subs(JS_VERSION & 0xFF); return loc.toString(); } case ERR_GET_BUTTONS: { return i18n("Could not get number of buttons for joystick device %1: %2", devName, strerror(errno)); } case ERR_GET_AXES: { return i18n("Could not get number of axes for joystick device %1: %2", devName, strerror(errno)); } case ERR_GET_CORR: { return i18n("Could not get calibration values for joystick device %1: %2", devName, strerror(errno)); } case ERR_RESTORE_CORR: { return i18n("Could not restore calibration values for joystick device %1: %2", devName, strerror(errno)); } case ERR_INIT_CAL: { return i18n("Could not initialize calibration values for joystick device %1: %2", devName, strerror(errno)); } case ERR_APPLY_CAL: { return i18n("Could not apply calibration values for joystick device %1: %2", devName, strerror(errno)); } default: return i18n("internal error - code %1 unknown", int(code)); } } //-------------------------------------------------------------- JoyDevice::ErrorCode JoyDevice::open() { if ( joyFd != -1 ) return JoyDevice::SUCCESS; // already open int fd = ::open(devName.toLatin1(), O_RDONLY); if ( fd == -1 ) return JoyDevice::OPEN_FAILED; // we could open the devicefile, now check if a joystick is attached char name[128]; if ( ::ioctl(fd, JSIOCGNAME(sizeof(name)), &name) == -1 ) { ::close(fd); return JoyDevice::NO_JOYSTICK; } // check the kernel driver version int version; if ( ::ioctl(fd, JSIOCGVERSION, &version) == -1 ) { ::close(fd); return JoyDevice::ERR_GET_VERSION; } if ( version != JS_VERSION ) { ::close(fd); return JoyDevice::WRONG_VERSION; } char bt = 0, ax = 0; if ( ::ioctl(fd, JSIOCGBUTTONS, &bt) == -1 ) { ::close(fd); return JoyDevice::ERR_GET_BUTTONS; } if ( ::ioctl(fd, JSIOCGAXES, &ax) == -1 ) { ::close(fd); return JoyDevice::ERR_GET_AXES; } struct js_corr *oldCorr = new struct js_corr[ax]; if ( ::ioctl(fd, JSIOCGCORR, oldCorr) == -1 ) { ::close(fd); delete [] oldCorr; return JoyDevice::ERR_GET_CORR; } if (bt < 0) { return JoyDevice::ERR_GET_BUTTONS; } descr = name; joyFd = fd; axes = ax; buttons = bt; origCorr = oldCorr; corr = new struct js_corr[axes]; amin = new int[axes]; amax = new int[axes]; int i; for (i = 0; i < axes; i++) resetMinMax(i); return JoyDevice::SUCCESS; } //-------------------------------------------------------------- void JoyDevice::close() { if ( joyFd == -1 ) return; ::close(joyFd); joyFd = -1; descr = QLatin1String(""); delete [] amin; delete [] amax; amin = nullptr; amax = nullptr; delete [] corr; corr = nullptr; delete [] origCorr; origCorr = nullptr; } //-------------------------------------------------------------- int JoyDevice::axisMin(int axis) const { if ( (axis < 0) || (axis >= axes) ) return 0; return amin[axis]; } //-------------------------------------------------------------- int JoyDevice::axisMax(int axis) const { if ( (axis < 0) || (axis >= axes) ) return 0; return amax[axis]; } //-------------------------------------------------------------- JoyDevice::ErrorCode JoyDevice::initCalibration() { if ( joyFd == -1 ) return JoyDevice::ERR_INIT_CAL; int i; // Reset all current correction values for (i = 0; i < axes; i++) { corr[i].type = JS_CORR_NONE; corr[i].prec = 0; } if ( ::ioctl(joyFd, JSIOCSCORR, corr) == -1 ) return JoyDevice::ERR_INIT_CAL; for (i = 0; i < axes; i++) corr[i].type = JS_CORR_BROKEN; return JoyDevice::SUCCESS; } //-------------------------------------------------------------- JoyDevice::ErrorCode JoyDevice::applyCalibration() { if ( joyFd == -1 ) return JoyDevice::ERR_APPLY_CAL; if ( ::ioctl(joyFd, JSIOCSCORR, corr) == -1 ) return JoyDevice::ERR_APPLY_CAL; return JoyDevice::SUCCESS; } //-------------------------------------------------------------- void JoyDevice::resetMinMax(int axis, int value) { amin[axis] = value; amax[axis] = value; } //-------------------------------------------------------------- void JoyDevice::calcPrecision() { if ( !corr ) return; int i; for (i = 0; i < axes; i++) { corr[i].prec = amax[i] - amin[i]; qDebug() << "Precision for axis: " << i << ": " << corr[i].prec; } } //-------------------------------------------------------------- JoyDevice::ErrorCode JoyDevice::restoreCorr() { if ( joyFd == -1 ) return JoyDevice::SUCCESS; if ( ::ioctl(joyFd, JSIOCSCORR, origCorr) == -1 ) return JoyDevice::ERR_RESTORE_CORR; else return JoyDevice::SUCCESS; } //-------------------------------------------------------------- JoyDevice::~JoyDevice() { close(); } //-------------------------------------------------------------- bool JoyDevice::getEvent(JoyDevice::EventType &type, int &number, int &value) { number = value = 0; int ret; fd_set readSet; FD_ZERO(&readSet); FD_SET(joyFd, &readSet); struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 10000; ret = ::select(joyFd + 1, &readSet, nullptr, nullptr, &timeout); if ( ret == 1 ) // got an event from the joystick { struct js_event e; if ( ::read(joyFd, &e, sizeof(struct js_event)) == sizeof(struct js_event) ) { if ( e.type & JS_EVENT_BUTTON ) { type = JoyDevice::BUTTON; value = e.value; number = e.number; return true; } if ( e.type & JS_EVENT_AXIS ) { type = JoyDevice::AXIS; value = e.value; number = e.number; // store min, max values if ( e.value < amin[number] ) amin[number] = e.value; if ( e.value > amax[number] ) amax[number] = e.value; return true; } } } return false; // no event } //-------------------------------------------------------------- void JoyDevice::calcCorrection(int axis, int *min, int *center, int *max) { const int MIN = 0; const int MAX = 1; double a, b, c, d; a = center[MIN]; // inputs.cmin[1]; b = center[MAX]; // inputs.cmax[1]; c = 32767.0 / (center[MIN] - min[MAX]); // (inputs.cmin[1] - inputs.cmax[0]); d = 32767.0 / (max[MIN] - center[MAX]); // (inputs.cmin[2] - inputs.cmax[1]); corr[axis].coef[0] = (int)rint(a); corr[axis].coef[1] = (int)rint(b); corr[axis].coef[2] = (int)rint(c*16384.0); corr[axis].coef[3] = (int)rint(d*16384.0); qDebug() << "min min: " << min[0] << " max: " << min[1] ; qDebug() << "max min: " << max[0] << " max: " << max[1] ; qDebug() << "Correction values for axis: " << axis << ": " << corr[axis].coef[0] << ", " << corr[axis].coef[1] << ", " << corr[axis].coef[2] << ", " - << corr[axis].coef[3] << endl; + << corr[axis].coef[3] << Qt::endl; } //-------------------------------------------------------------- diff --git a/kcms/hardware/joystick/joywidget.cpp b/kcms/hardware/joystick/joywidget.cpp index 27cd9f57f..0b0fc1330 100644 --- a/kcms/hardware/joystick/joywidget.cpp +++ b/kcms/hardware/joystick/joywidget.cpp @@ -1,417 +1,417 @@ /*************************************************************************** * Copyright (C) 2003,2012 by Martin Koller * * kollix@aon.at * * This file is part of the KDE Control Center Module for Joysticks * * * * 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) any later version. * * * * 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. * ***************************************************************************/ #include "joywidget.h" #include "joydevice.h" #include "poswidget.h" #include "caldialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------- static QString PRESSED = I18N_NOOP("PRESSED"); //-------------------------------------------------------------- class TableWidget : public QTableWidget { public: TableWidget(int row, int col) : QTableWidget(row, col) {} QSize sizeHint() const override { return QSize(150, 100); // return a smaller size than the Qt default(256, 192) } }; //-------------------------------------------------------------- JoyWidget::JoyWidget(QWidget *parent) : QWidget(parent), idle(nullptr), joydev(nullptr) { QVBoxLayout *mainVbox = new QVBoxLayout(this); mainVbox->setSpacing(KDialog::spacingHint()); mainVbox->setContentsMargins(0, 0, 0, 0); // create area to show an icon + message if no joystick was detected { messageBox = new KMessageWidget(this); messageBox->setMessageType(KMessageWidget::Error); messageBox->setCloseButtonVisible(false); messageBox->hide(); messageBox->setWordWrap(true); mainVbox->addWidget(messageBox); } QHBoxLayout *devHbox = new QHBoxLayout; devHbox->setSpacing(KDialog::spacingHint()); devHbox->addWidget(new QLabel(i18n("Device:"))); devHbox->addWidget(device = new KComboBox(true)); device->setInsertPolicy(QComboBox::NoInsert); KUrlCompletion *kc = new KUrlCompletion(KUrlCompletion::FileCompletion); device->setCompletionObject(kc); device->setAutoDeleteCompletionObject(true); connect(device, SIGNAL(activated(QString)), this, SLOT(deviceChanged(QString))); connect(device, SIGNAL(returnPressed(QString)), this, SLOT(deviceChanged(QString))); devHbox->setStretchFactor(device, 3); QHBoxLayout *hbox = new QHBoxLayout; hbox->setSpacing(KDialog::spacingHint()); mainVbox->addLayout(devHbox); mainVbox->addLayout(hbox); QVBoxLayout *vboxLeft = new QVBoxLayout; vboxLeft->setSpacing(KDialog::spacingHint()); vboxLeft->addWidget(new QLabel(i18nc("Cue for deflection of the stick", "Position:"))); vboxLeft->addWidget(xyPos = new PosWidget); vboxLeft->addWidget(trace = new QCheckBox(i18n("Show trace"))); connect(trace, &QAbstractButton::toggled, this, &JoyWidget::traceChanged); QVBoxLayout *vboxMid = new QVBoxLayout; vboxMid->setSpacing(KDialog::spacingHint()); QVBoxLayout *vboxRight = new QVBoxLayout; vboxRight->setSpacing(KDialog::spacingHint()); // calculate the column width we need QFontMetrics fm(font()); - int colWidth = qMax(fm.width(PRESSED), fm.width(QStringLiteral("-32767"))) + 10; // -32767 largest string + int colWidth = qMax(fm.horizontalAdvance(PRESSED), fm.horizontalAdvance(QStringLiteral("-32767"))) + 10; // -32767 largest string vboxMid->addWidget(new QLabel(i18n("Buttons:"))); buttonTbl = new TableWidget(0, 1); buttonTbl->setSelectionMode(QAbstractItemView::NoSelection); buttonTbl->setEditTriggers(QAbstractItemView::NoEditTriggers); buttonTbl->setHorizontalHeaderLabels(QStringList(i18n("State"))); buttonTbl->setSortingEnabled(false); buttonTbl->horizontalHeader()->setSectionsClickable(false); buttonTbl->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); buttonTbl->horizontalHeader()->resizeSection(0, colWidth); buttonTbl->verticalHeader()->setSectionsClickable(false); vboxMid->addWidget(buttonTbl); vboxRight->addWidget(new QLabel(i18n("Axes:"))); axesTbl = new TableWidget(0, 1); axesTbl->setSelectionMode(QAbstractItemView::NoSelection); axesTbl->setEditTriggers(QAbstractItemView::NoEditTriggers); axesTbl->setHorizontalHeaderLabels(QStringList(i18n("Value"))); axesTbl->setSortingEnabled(false); axesTbl->horizontalHeader()->setSectionsClickable(false); axesTbl->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); axesTbl->horizontalHeader()->resizeSection(0, colWidth); axesTbl->verticalHeader()->setSectionsClickable(false); vboxRight->addWidget(axesTbl); hbox->addLayout(vboxLeft); hbox->addLayout(vboxMid); hbox->addLayout(vboxRight); // calibrate button calibrate = new QPushButton(i18n("Calibrate")); connect(calibrate, &QAbstractButton::clicked, this, &JoyWidget::calibrateDevice); calibrate->setEnabled(false); vboxLeft->addStretch(); vboxLeft->addWidget(calibrate); // set up a timer for idle processing of joystick events idle = new QTimer(this); connect(idle, &QTimer::timeout, this, &JoyWidget::checkDevice); // check which devicefiles we have init(); } //-------------------------------------------------------------- JoyWidget::~JoyWidget() { delete joydev; } //-------------------------------------------------------------- void JoyWidget::init() { // check which devicefiles we have int i; bool first = true; char dev[30]; device->clear(); buttonTbl->setRowCount(0); axesTbl->setRowCount(0); for (i = 0; i < 5; i++) // check the first 5 devices { sprintf(dev, "/dev/js%d", i); // first look in /dev JoyDevice *joy = new JoyDevice(dev); if ( joy->open() != JoyDevice::SUCCESS ) { delete joy; sprintf(dev, "/dev/input/js%d", i); // then look in /dev/input joy = new JoyDevice(dev); if ( joy->open() != JoyDevice::SUCCESS ) { delete joy; continue; // try next number } } // we found one device->addItem(QStringLiteral("%1 (%2)").arg(joy->text()).arg(joy->device())); // display values for first device if ( first ) { showDeviceProps(joy); // this sets the joy object into this->joydev first = false; } else delete joy; } /* KDE 4: Remove this check(and i18n) when all KCM wrappers properly test modules */ if ( device->count() == 0 ) { messageBox->show(); messageBox->setText(QStringLiteral("%1").arg( i18n("No joystick device automatically found on this computer.
" "Checks were done in /dev/js[0-4] and /dev/input/js[0-4]
" "If you know that there is one attached, please enter the correct device file."))); } } //-------------------------------------------------------------- void JoyWidget::traceChanged(bool state) { xyPos->showTrace(state); } //-------------------------------------------------------------- void JoyWidget::restoreCurrDev() { if ( !joydev ) // no device open { device->setEditText(QLatin1String("")); calibrate->setEnabled(false); } else { // try to find the current open device in the combobox list int index = device->findText(joydev->device(), Qt::MatchContains); if ( index == -1 ) // the current open device is one the user entered (not in the list) device->setEditText(joydev->device()); else device->setEditText(device->itemText(index)); } } //-------------------------------------------------------------- void JoyWidget::deviceChanged(const QString &dev) { // find "/dev" in given string int start, stop; QString devName; if ( (start = dev.indexOf(QLatin1String("/dev"))) == -1 ) { KMessageBox::sorry(this, i18n("The given device name is invalid (does not contain /dev).\n" "Please select a device from the list or\n" "enter a device file, like /dev/js0."), i18n("Unknown Device")); restoreCurrDev(); return; } if ( (stop = dev.indexOf(QLatin1Char(')'), start)) != -1 ) // seems to be text selected from our list devName = dev.mid(start, stop - start); else devName = dev.mid(start); if ( joydev && (devName == joydev->device()) ) return; // user selected the current device; ignore it JoyDevice *joy = new JoyDevice(devName); JoyDevice::ErrorCode ret = joy->open(); if ( ret != JoyDevice::SUCCESS ) { KMessageBox::error(this, joy->errText(ret), i18n("Device Error")); delete joy; restoreCurrDev(); return; } showDeviceProps(joy); } //-------------------------------------------------------------- void JoyWidget::showDeviceProps(JoyDevice *joy) { joydev = joy; buttonTbl->setRowCount(joydev->numButtons()); axesTbl->setRowCount(joydev->numAxes()); if ( joydev->numAxes() >= 2 ) { axesTbl->setVerticalHeaderItem(0, new QTableWidgetItem(i18n("1(x)"))); axesTbl->setVerticalHeaderItem(1, new QTableWidgetItem(i18n("2(y)"))); } calibrate->setEnabled(true); idle->start(0); // make both tables use the same space for header; this looks nicer // TODO: Don't know how to do this in Qt4; the following does no longer work // Probably by setting a sizeHint for every single header item ? /* buttonTbl->verticalHeader()->setFixedWidth(qMax(buttonTbl->verticalHeader()->width(), axesTbl->verticalHeader()->width())); axesTbl->verticalHeader()->setFixedWidth(buttonTbl->verticalHeader()->width()); */ } //-------------------------------------------------------------- void JoyWidget::checkDevice() { if ( !joydev ) return; // no open device yet JoyDevice::EventType type; int number, value; if ( !joydev->getEvent(type, number, value) ) return; if ( type == JoyDevice::BUTTON ) { if ( ! buttonTbl->item(number, 0) ) buttonTbl->setItem(number, 0, new QTableWidgetItem()); if ( value == 0 ) // button release buttonTbl->item(number, 0)->setText(QStringLiteral("-")); else buttonTbl->item(number, 0)->setText(PRESSED); } if ( type == JoyDevice::AXIS ) { if ( number == 0 ) // x-axis xyPos->changeX(value); if ( number == 1 ) // y-axis xyPos->changeY(value); if ( ! axesTbl->item(number, 0) ) axesTbl->setItem(number, 0, new QTableWidgetItem()); axesTbl->item(number, 0)->setText(QStringLiteral("%1").arg(int(value))); } } //-------------------------------------------------------------- void JoyWidget::calibrateDevice() { if ( !joydev ) return; // just to be save JoyDevice::ErrorCode ret = joydev->initCalibration(); if ( ret != JoyDevice::SUCCESS ) { KMessageBox::error(this, joydev->errText(ret), i18n("Communication Error")); return; } if ( KMessageBox::messageBox(this, KMessageBox::Information, i18n("Calibration is about to check the precision.

" "Please move all axes to their center position and then " "do not touch the joystick anymore.

" "Click OK to start the calibration.
"), i18n("Calibration"), KStandardGuiItem::ok(), KStandardGuiItem::cancel()) != KMessageBox::Ok ) return; idle->stop(); // stop the joystick event getting; this must be done inside the calibrate dialog CalDialog dlg(this, joydev); dlg.calibrate(); // user canceled somewhere during calibration, therefore the device is in a bad state if ( dlg.result() == QDialog::Rejected ) joydev->restoreCorr(); idle->start(0); // continue with event getting } //-------------------------------------------------------------- void JoyWidget::resetCalibration() { if ( !joydev ) return; // just to be save JoyDevice::ErrorCode ret = joydev->restoreCorr(); if ( ret != JoyDevice::SUCCESS ) { KMessageBox::error(this, joydev->errText(ret), i18n("Communication Error")); } else { KMessageBox::information(this, i18n("Restored all calibration values for joystick device %1.", joydev->device()), i18n("Calibration Success")); } } //-------------------------------------------------------------- diff --git a/kcms/icons/main.cpp b/kcms/icons/main.cpp index aec4a091a..d8adf8178 100644 --- a/kcms/icons/main.cpp +++ b/kcms/icons/main.cpp @@ -1,504 +1,504 @@ /* * main.cpp * * Copyright (c) 1999 Matthias Hoelzer-Kluepfel * Copyright (c) 2000 Antonio Larrosa * Copyright (C) 2000 Geert Jansen * KDE Frameworks 5 port Copyright (C) 2013 Jonathan Riddell * Copyright (C) 2018 Kai Uwe Broulik * Copyright (C) 2019 Benjamin Port * * Requires the Qt widget libraries, available at no cost at * https://www.qt.io/ * * 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) any later version. * * 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. */ #include "main.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for unlink #include "iconssettings.h" #include "iconsmodel.h" #include "iconsizecategorymodel.h" #include "config.h" // for CMAKE_INSTALL_FULL_LIBEXECDIR K_PLUGIN_FACTORY_WITH_JSON(IconsFactory, "kcm_icons.json", registerPlugin();) IconModule::IconModule(QObject *parent, const QVariantList &args) : KQuickAddons::ManagedConfigModule(parent, args) , m_settings(new IconsSettings(this)) , m_model(new IconsModel(m_settings, this)) , m_iconSizeCategoryModel(new IconSizeCategoryModel(this)) { qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); // to be able to access its enums qmlRegisterUncreatableType("org.kde.private.kcms.icons", 1, 0, "KIconLoader", QString()); KAboutData* about = new KAboutData(QStringLiteral("kcm5_icons"), i18n("Icons"), QStringLiteral("1.0"), i18n("Icons Control Panel Module"), KAboutLicense::GPL, i18n("(c) 2000-2003 Geert Jansen")); about->addAuthor(i18n("Geert Jansen"), QString(), QStringLiteral("jansen@kde.org")); about->addAuthor(i18n("Antonio Larrosa Jimenez"), QString(), QStringLiteral("larrosa@kde.org")); about->addCredit(i18n("Torsten Rahn"), QString(), QStringLiteral("torsten@kde.org")); about->addAuthor(i18n("Jonathan Riddell"), QString(), QStringLiteral("jr@jriddell.org")); about->addAuthor(i18n("Kai Uwe Broulik"), QString(), QStringLiteral("kde@privat.broulik.de>")); setAboutData(about); setButtons(Apply | Default); connect(m_model, &IconsModel::pendingDeletionsChanged, this, &IconModule::settingsChanged); // When user has a lot of themes installed, preview pixmaps might get evicted prematurely QPixmapCache::setCacheLimit(50 * 1024); // 50 MiB } IconModule::~IconModule() { } IconsSettings *IconModule::iconsSettings() const { return m_settings; } IconsModel *IconModule::iconsModel() const { return m_model; } IconSizeCategoryModel *IconModule::iconSizeCategoryModel() const { return m_iconSizeCategoryModel; } bool IconModule::downloadingFile() const { return m_tempCopyJob; } QList IconModule::availableIconSizes(int group) const { const auto themeName = m_settings->theme(); if (!m_kiconThemeCache.contains(m_settings->theme())) { m_kiconThemeCache.insert(themeName, new KIconTheme(themeName)); } return m_kiconThemeCache[themeName]->querySizes(static_cast(group)); } void IconModule::load() { ManagedConfigModule::load(); m_model->load(); // Model has been cleared so pretend the theme name changed to force view update emit m_settings->ThemeChanged(); } void IconModule::save() { bool needToExportToKDE4 = m_settings->isSaveNeeded(); // keep track of Group of icons size that has changed QList notifyList; for (int i = 0; i < m_iconSizeCategoryModel->rowCount(); ++i) { const QModelIndex index = m_iconSizeCategoryModel->index(i, 0); const QString key = index.data(IconSizeCategoryModel::ConfigKeyRole).toString(); if (m_settings->findItem(key)->isSaveNeeded()) { notifyList << index.data(IconSizeCategoryModel::KIconLoaderGroupRole).toInt(); } } ManagedConfigModule::save(); if (needToExportToKDE4) { exportToKDE4(); } processPendingDeletions(); // Notify the group(s) where icon sizes have changed for (auto group : qAsConst(notifyList)) { KIconLoader::emitChange(KIconLoader::Group(group)); } } bool IconModule::isSaveNeeded() const { return !m_model->pendingDeletions().isEmpty(); } void IconModule::processPendingDeletions() { const QStringList pendingDeletions = m_model->pendingDeletions(); for (const QString &themeName : pendingDeletions) { Q_ASSERT(themeName != m_settings->theme()); KIconTheme theme(themeName); auto *job = KIO::del(QUrl::fromLocalFile(theme.dir()), KIO::HideProgressInfo); // needs to block for it to work on "OK" where the dialog (kcmshell) closes job->exec(); } m_model->removeItemsPendingDeletion(); } void IconModule::ghnsEntriesChanged(const QQmlListReference &changedEntries) { if (changedEntries.count() == 0) { return; } // reload the display icontheme items KIconLoader::global()->newIconLoader(); m_model->load(); QPixmapCache::clear(); } void IconModule::installThemeFromFile(const QUrl &url) { if (url.isLocalFile()) { installThemeFile(url.toLocalFile()); return; } if (m_tempCopyJob) { return; } m_tempInstallFile.reset(new QTemporaryFile()); if (!m_tempInstallFile->open()) { emit showErrorMessage(i18n("Unable to create a temporary file.")); m_tempInstallFile.reset(); return; } m_tempCopyJob = KIO::file_copy(url,QUrl::fromLocalFile(m_tempInstallFile->fileName()), -1, KIO::Overwrite); m_tempCopyJob->uiDelegate()->setAutoErrorHandlingEnabled(true); emit downloadingFileChanged(); connect(m_tempCopyJob, &KIO::FileCopyJob::result, this, [this, url](KJob *job) { if (job->error() != KJob::NoError) { emit showErrorMessage(i18n("Unable to download the icon theme archive: %1", job->errorText())); return; } installThemeFile(m_tempInstallFile->fileName()); m_tempInstallFile.reset(); }); connect(m_tempCopyJob, &QObject::destroyed, this, &IconModule::downloadingFileChanged); } void IconModule::installThemeFile(const QString &path) { const QStringList themesNames = findThemeDirs(path); if (themesNames.isEmpty()) { emit showErrorMessage(i18n("The file is not a valid icon theme archive.")); return; } if (!installThemes(themesNames, path)) { emit showErrorMessage(i18n("A problem occurred during the installation process; however, most of the themes in the archive have been installed")); return; } emit showSuccessMessage(i18n("Theme installed successfully.")); KIconLoader::global()->newIconLoader(); m_model->load(); } void IconModule::exportToKDE4() { //TODO: killing the kde4 icon cache: possible? (kde4migration doesn't let access the cache folder) Kdelibs4Migration migration; QString configFilePath = migration.saveLocation("config"); if (configFilePath.isEmpty()) { return; } configFilePath += QLatin1String("kdeglobals"); KSharedConfigPtr kglobalcfg = KSharedConfig::openConfig(QStringLiteral("kdeglobals")); KConfig kde4config(configFilePath, KConfig::SimpleConfig); KConfigGroup kde4IconGroup(&kde4config, "Icons"); kde4IconGroup.writeEntry("Theme", m_settings->theme()); //Synchronize icon effects for (int row = 0; row < m_iconSizeCategoryModel->rowCount(); row++) { QModelIndex idx(m_iconSizeCategoryModel->index(row, 0)); QString group = m_iconSizeCategoryModel->data(idx, IconSizeCategoryModel::ConfigSectionRole).toString(); const QString groupName = group + QLatin1String("Icons"); KConfigGroup cg(kglobalcfg, groupName); KConfigGroup kde4Cg(&kde4config, groupName); // HACK copyTo only copies keys, it doesn't replace the entire group // which means if we removed the effects in our config it won't remove // them from the kde4 config, hence revert all of them prior to copying const QStringList keys = cg.keyList() + kde4Cg.keyList(); for (const QString &key : keys) { kde4Cg.revertToDefault(key); } // now copy over the new values cg.copyTo(&kde4Cg); } kde4config.sync(); QProcess *cachePathProcess = new QProcess(this); connect(cachePathProcess, QOverload::of(&QProcess::finished), this, [cachePathProcess](int exitCode, QProcess::ExitStatus status) { if (status == QProcess::NormalExit && exitCode == 0) { QString path = cachePathProcess->readAllStandardOutput().trimmed(); path.append(QLatin1String("icon-cache.kcache")); QFile::remove(path); } //message kde4 apps that icon theme has changed for (int i = 0; i < KIconLoader::LastGroup; i++) { QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KGlobalSettings"), QStringLiteral("org.kde.KGlobalSettings"), QStringLiteral("notifyChange")); message.setArguments({ 4, // KGlobalSettings::IconChanged KIconLoader::Group(i) }); QDBusConnection::sessionBus().send(message); } cachePathProcess->deleteLater(); }); - cachePathProcess->start(QStringLiteral("kde4-config --path cache")); + cachePathProcess->start(QStringLiteral("kde4-config"), { QStringLiteral("--path"), QStringLiteral("cache")}); } QStringList IconModule::findThemeDirs(const QString &archiveName) { QStringList foundThemes; KTar archive(archiveName); archive.open(QIODevice::ReadOnly); const KArchiveDirectory *themeDir = archive.directory(); KArchiveEntry *possibleDir = nullptr; KArchiveDirectory *subDir = nullptr; // iterate all the dirs looking for an index.theme or index.desktop file const QStringList entries = themeDir->entries(); for (const QString &entry : entries) { possibleDir = const_cast(themeDir->entry(entry)); if (!possibleDir->isDirectory()) { continue; } subDir = dynamic_cast(possibleDir); if (!subDir) { continue; } if (subDir->entry(QStringLiteral("index.theme")) || subDir->entry(QStringLiteral("index.desktop"))) { foundThemes.append(subDir->name()); } } archive.close(); return foundThemes; } bool IconModule::installThemes(const QStringList &themes, const QString &archiveName) { bool everythingOk = true; const QString localThemesDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/icons/./")); emit showProgress(i18n("Installing icon themes...")); KTar archive(archiveName); archive.open(QIODevice::ReadOnly); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); const KArchiveDirectory *rootDir = archive.directory(); KArchiveDirectory *currentTheme = nullptr; for (const QString &theme : themes) { emit showProgress(i18n("Installing %1 theme...", theme)); qApp->processEvents(QEventLoop::ExcludeUserInputEvents); currentTheme = dynamic_cast(const_cast(rootDir->entry(theme))); if (!currentTheme) { // we tell back that something went wrong, but try to install as much // as possible everythingOk = false; continue; } currentTheme->copyTo(localThemesDir + theme); } archive.close(); emit hideProgress(); return everythingOk; } QVariantList IconModule::previewIcons(const QString &themeName, int size, qreal dpr, int limit) { static QVector s_previewIcons{ {QStringLiteral("system-run"), QStringLiteral("exec")}, {QStringLiteral("folder")}, {QStringLiteral("document"), QStringLiteral("text-x-generic")}, {QStringLiteral("user-trash"), QStringLiteral("user-trash-empty")}, {QStringLiteral("help-browser"), QStringLiteral("system-help"), QStringLiteral("help-about"), QStringLiteral("help-contents")}, {QStringLiteral("preferences-system"), QStringLiteral("systemsettings"), QStringLiteral("configure")}, {QStringLiteral("text-html")}, {QStringLiteral("image-x-generic"), QStringLiteral("image-png"), QStringLiteral("image-jpeg")}, {QStringLiteral("video-x-generic"), QStringLiteral("video-x-theora+ogg"), QStringLiteral("video-mp4")}, {QStringLiteral("x-office-document")}, {QStringLiteral("x-office-spreadsheet")}, {QStringLiteral("x-office-presentation"), QStringLiteral("application-presentation")}, {QStringLiteral("user-home")}, {QStringLiteral("user-desktop"), QStringLiteral("desktop")}, {QStringLiteral("folder-image"), QStringLiteral("folder-images"), QStringLiteral("folder-pictures"), QStringLiteral("folder-picture")}, {QStringLiteral("folder-documents")}, {QStringLiteral("folder-download"), QStringLiteral("folder-downloads")}, {QStringLiteral("folder-video"), QStringLiteral("folder-videos")} }; // created on-demand as it is quite expensive to do and we don't want to do it every loop iteration either QScopedPointer theme; QVariantList pixmaps; for (const QStringList &iconNames : s_previewIcons) { const QString cacheKey = themeName + QLatin1Char('@') + QString::number(size) + QLatin1Char('@') + QString::number(dpr,'f',1) + QLatin1Char('@') + iconNames.join(QLatin1Char(',')); QPixmap pix; if (!QPixmapCache::find(cacheKey, &pix)) { if (!theme) { theme.reset(new KIconTheme(themeName)); } pix = getBestIcon(*theme.data(), iconNames, size, dpr); // Inserting a pixmap even if null so we know whether we searched for it already QPixmapCache::insert(cacheKey, pix); } if (pix.isNull()) { continue; } pixmaps.append(pix); if (limit > -1 && pixmaps.count() >= limit) { break; } } return pixmaps; } QPixmap IconModule::getBestIcon(KIconTheme &theme, const QStringList &iconNames, int size, qreal dpr) { QSvgRenderer renderer; const int iconSize = size * dpr; // not using initializer list as we want to unwrap inherits() const QStringList themes = QStringList() << theme.internalName() << theme.inherits(); for (const QString &themeName : themes) { KIconTheme theme(themeName); for (const QString &iconName : iconNames) { QString path = theme.iconPath(QStringLiteral("%1.png").arg(iconName), iconSize, KIconLoader::MatchBest); if (!path.isEmpty()) { QPixmap pixmap(path); pixmap.setDevicePixelRatio(dpr); return pixmap; } //could not find the .png, try loading the .svg or .svgz path = theme.iconPath(QStringLiteral("%1.svg").arg(iconName), iconSize, KIconLoader::MatchBest); if (path.isEmpty()) { path = theme.iconPath(QStringLiteral("%1.svgz").arg(iconName), iconSize, KIconLoader::MatchBest); } if (path.isEmpty()) { continue; } if (!renderer.load(path)) { continue; } QPixmap pixmap(iconSize, iconSize); pixmap.setDevicePixelRatio(dpr); pixmap.fill(QColor(Qt::transparent)); QPainter p(&pixmap); p.setViewport(0, 0, size, size); renderer.render(&p); return pixmap; } } return QPixmap(); } int IconModule::pluginIndex(const QString &themeName) const { const auto results = m_model->match(m_model->index(0, 0), ThemeNameRole, themeName); if (results.count() == 1) { return results.first().row(); } return -1; } #include "main.moc" diff --git a/kcms/keyboard/bindings.h b/kcms/keyboard/bindings.h index d803eb7c6..cde1bc7cc 100644 --- a/kcms/keyboard/bindings.h +++ b/kcms/keyboard/bindings.h @@ -1,51 +1,51 @@ /* * Copyright (C) 2010 Andriy Rysin (rysin@kde.org) * * 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) any later version. * * 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. */ #ifndef BINDINGS_H_ #define BINDINGS_H_ #include struct Rules; -struct LayoutUnit; +class LayoutUnit; template class QList; class KeyboardLayoutActionCollection : public KActionCollection { public: KeyboardLayoutActionCollection(QObject* parent, bool configAction); ~KeyboardLayoutActionCollection() override; QAction* getToggleAction(); // KAction* getAction(const LayoutUnit& layoutUnit); QAction* createLayoutShortcutActon(const LayoutUnit& layoutUnit, const Rules* rules, bool autoload); // KAction* setShortcut(LayoutUnit& layoutUnit, const QKeySequence& keySequence, const Rules* rules); void setLayoutShortcuts(QList& layoutUnits, const Rules* rules); void setToggleShortcut(const QKeySequence& keySequence); void loadLayoutShortcuts(QList& layoutUnits, const Rules* rules); void resetLayoutShortcuts(); private: bool configAction; }; //KActionCollection* createGlobalActionCollection(QObject *parent, KAction** mainAction); //KAction* createLayoutShortcutActon(KActionCollection* actionCollection, const LayoutUnit& layoutUnit, const Rules* rules); #endif /* BINDINGS_H_ */ diff --git a/kcms/keyboard/kcm_add_layout_dialog.cpp b/kcms/keyboard/kcm_add_layout_dialog.cpp index b9c86a5ce..eeb7fe6c2 100644 --- a/kcms/keyboard/kcm_add_layout_dialog.cpp +++ b/kcms/keyboard/kcm_add_layout_dialog.cpp @@ -1,168 +1,168 @@ /* * Copyright (C) 2010 Andriy Rysin (rysin@kde.org) * * 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) any later version. * * 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. */ #include "kcm_add_layout_dialog.h" #include #include #include "xkb_rules.h" #include "flags.h" #include "iso_codes.h" #include "tastenbrett.h" #include "ui_kcm_add_layout_dialog.h" #include AddLayoutDialog::AddLayoutDialog(const Rules* rules_, Flags* flags_, const QString& model_, const QStringList &options_, bool showLabel, QWidget* parent): QDialog(parent), rules(rules_), flags(flags_), model(model_), options(options_), selectedLanguage(QStringLiteral("no_language")) { layoutDialogUi = new Ui_AddLayoutDialog(); layoutDialogUi->setupUi(this); QSet languages; foreach(const LayoutInfo* layoutInfo, rules->layoutInfos) { - QSet langs = QSet::fromList(layoutInfo->languages); + QSet langs = QSet(layoutInfo->languages.constBegin(), layoutInfo->languages.constEnd()); languages.unite( langs ); } IsoCodes isoCodes(IsoCodes::iso_639_3); foreach(const QString& lang, languages) { const IsoCodeEntry* isoCodeEntry = isoCodes.getEntry(IsoCodes::attr_iso_639_3_id, lang); // const IsoCodeEntry* isoCodeEntry = isoCodes.getEntry(IsoCodes::attr_iso_639_2B_code, lang); // if( isoCodeEntry == NULL ) { // isoCodeEntry = isoCodes.getEntry(IsoCodes::attr_iso_639_2T_code, lang); // } QString name = isoCodeEntry != nullptr ? i18n(isoCodeEntry->value(IsoCodes::attr_name).toUtf8()) : lang; layoutDialogUi->languageComboBox->addItem(name, lang); } layoutDialogUi->languageComboBox->model()->sort(0); layoutDialogUi->languageComboBox->insertItem(0, i18n("Any language"), ""); layoutDialogUi->languageComboBox->setCurrentIndex(0); if( showLabel ) { layoutDialogUi->labelEdit->setMaxLength(LayoutUnit::MAX_LABEL_LENGTH); } else { layoutDialogUi->labelLabel->setVisible(false); layoutDialogUi->labelEdit->setVisible(false); } languageChanged(0); connect(layoutDialogUi->languageComboBox, static_cast(&QComboBox::activated), this, &AddLayoutDialog::languageChanged); connect(layoutDialogUi->layoutComboBox, static_cast(&QComboBox::activated), this, &AddLayoutDialog::layoutChanged); connect(layoutDialogUi->prevbutton, &QPushButton::clicked, this, &AddLayoutDialog::preview); layoutDialogUi->prevbutton->setVisible(Tastenbrett::exists()); } void AddLayoutDialog::languageChanged(int langIdx) { QString lang = layoutDialogUi->languageComboBox->itemData(langIdx).toString(); if( lang == selectedLanguage ) return; QPixmap emptyPixmap(layoutDialogUi->layoutComboBox->iconSize()); emptyPixmap.fill(Qt::transparent); layoutDialogUi->layoutComboBox->clear(); int defaultIndex = -1; int i = 0; foreach(const LayoutInfo* layoutInfo, rules->layoutInfos) { if( lang.isEmpty() || layoutInfo->isLanguageSupportedByLayout(lang) ) { if( flags ) { QIcon icon(flags->getIcon(layoutInfo->name)); if( icon.isNull() ) { icon = QIcon(emptyPixmap); // align text with no icons } layoutDialogUi->layoutComboBox->addItem(icon, layoutInfo->description, layoutInfo->name); } else { layoutDialogUi->layoutComboBox->addItem(layoutInfo->description, layoutInfo->name); } // try to guess best default layout selection for given language if( ! lang.isEmpty() && defaultIndex == -1 && layoutInfo->isLanguageSupportedByDefaultVariant(lang) ) { defaultIndex = i; } i++; } } if( defaultIndex == -1 ) { defaultIndex = 0; } layoutDialogUi->layoutComboBox->model()->sort(0); layoutDialogUi->layoutComboBox->setCurrentIndex(defaultIndex); layoutChanged(defaultIndex); selectedLanguage = lang; } void AddLayoutDialog::layoutChanged(int layoutIdx) { QString layoutName = layoutDialogUi->layoutComboBox->itemData(layoutIdx).toString(); if( layoutName == selectedLayout ) return; QString lang = layoutDialogUi->languageComboBox->itemData(layoutDialogUi->languageComboBox->currentIndex()).toString(); layoutDialogUi->variantComboBox->clear(); const LayoutInfo* layoutInfo = rules->getLayoutInfo(layoutName); foreach(const VariantInfo* variantInfo, layoutInfo->variantInfos) { if( lang.isEmpty() || layoutInfo->isLanguageSupportedByVariant(variantInfo, lang) ) { layoutDialogUi->variantComboBox->addItem(variantInfo->description, variantInfo->name); } } layoutDialogUi->variantComboBox->model()->sort(0); if( lang.isEmpty() || layoutInfo->isLanguageSupportedByDefaultVariant(lang) ) { layoutDialogUi->variantComboBox->insertItem(0, i18nc("variant", "Default"), ""); } layoutDialogUi->variantComboBox->setCurrentIndex(0); layoutDialogUi->labelEdit->setText(layoutName); selectedLayout = layoutName; } void AddLayoutDialog::accept() { selectedLayoutUnit.setLayout(layoutDialogUi->layoutComboBox->itemData(layoutDialogUi->layoutComboBox->currentIndex()).toString()); selectedLayoutUnit.setVariant(layoutDialogUi->variantComboBox->itemData(layoutDialogUi->variantComboBox->currentIndex()).toString()); QString label = layoutDialogUi->labelEdit->text(); if( label == selectedLayoutUnit.layout() ) { label = QLatin1String(""); } selectedLayoutUnit.setDisplayName( label ); selectedLayoutUnit.setShortcut(layoutDialogUi->kkeysequencewidget->keySequence()); QDialog::accept(); } void AddLayoutDialog::preview() { int index = layoutDialogUi->variantComboBox->currentIndex(); QString variant = layoutDialogUi->variantComboBox->itemData(index).toString(); Tastenbrett::launch(model, selectedLayout, variant, options.join(',')); } diff --git a/kcms/keyboard/keyboard_config.cpp b/kcms/keyboard/keyboard_config.cpp index 10ed657c6..0a0ce4b23 100644 --- a/kcms/keyboard/keyboard_config.cpp +++ b/kcms/keyboard/keyboard_config.cpp @@ -1,213 +1,213 @@ /* * Copyright (C) 2010 Andriy Rysin (rysin@kde.org) * * 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) any later version. * * 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. */ #include "keyboard_config.h" #include "debug.h" #include #include static const char* const SWITCHING_POLICIES[] = {"Global", "Desktop", "WinClass", "Window", nullptr }; static const char LIST_SEPARATOR[] = ","; //static const char* DEFAULT_LAYOUT = "us"; static const char DEFAULT_MODEL[] = "pc104"; static const QString CONFIG_FILENAME(QStringLiteral("kxkbrc")); static const QString CONFIG_GROUPNAME(QStringLiteral("Layout")); const int KeyboardConfig::NO_LOOPING = -1; KeyboardConfig::KeyboardConfig() { setDefaults(); } QString KeyboardConfig::getSwitchingPolicyString(SwitchingPolicy switchingPolicy) { return SWITCHING_POLICIES[switchingPolicy]; } static int findStringIndex(const char* const strings[], const QString& toFind, int defaultIndex) { for(int i=0; strings[i] != nullptr; i++) { if( toFind == strings[i] ) { return i; } } return defaultIndex; } void KeyboardConfig::setDefaults() { keyboardModel = DEFAULT_MODEL; resetOldXkbOptions = false; xkbOptions.clear(); // init layouts options configureLayouts = false; layouts.clear(); // layouts.append(LayoutUnit(DEFAULT_LAYOUT)); layoutLoopCount = NO_LOOPING; // switch control options switchingPolicy = SWITCH_POLICY_GLOBAL; // stickySwitching = false; // stickySwitchingDepth = 2; // display options showIndicator = true; indicatorType = SHOW_LABEL; showSingle = false; } static KeyboardConfig::IndicatorType getIndicatorType(bool showFlag, bool showLabel) { if( showFlag ) { if( showLabel ) return KeyboardConfig::SHOW_LABEL_ON_FLAG; else return KeyboardConfig::SHOW_FLAG; } else { return KeyboardConfig::SHOW_LABEL; } } void KeyboardConfig::load() { KConfigGroup config(KSharedConfig::openConfig( CONFIG_FILENAME, KConfig::NoGlobals ), CONFIG_GROUPNAME); keyboardModel = config.readEntry("Model", ""); resetOldXkbOptions = config.readEntry("ResetOldOptions", false); QString options = config.readEntry("Options", ""); - xkbOptions = options.split(LIST_SEPARATOR, QString::SkipEmptyParts); + xkbOptions = options.split(LIST_SEPARATOR, Qt::SkipEmptyParts); configureLayouts = config.readEntry("Use", false); QString layoutsString = config.readEntry("LayoutList", ""); - QStringList layoutStrings = layoutsString.split(LIST_SEPARATOR, QString::SkipEmptyParts); + QStringList layoutStrings = layoutsString.split(LIST_SEPARATOR, Qt::SkipEmptyParts); // if( layoutStrings.isEmpty() ) { // layoutStrings.append(DEFAULT_LAYOUT); // } layouts.clear(); if (layoutStrings.isEmpty()) { QList x11layouts = X11Helper::getLayoutsList(); for (const LayoutUnit& layoutUnit : x11layouts) { layouts.append(layoutUnit); } } else { for (const QString& layoutString : layoutStrings) { layouts.append(LayoutUnit(layoutString)); } } configureLayouts = !layouts.isEmpty(); layoutLoopCount = config.readEntry("LayoutLoopCount", NO_LOOPING); QString layoutMode = config.readEntry("SwitchMode", "Global"); switchingPolicy = static_cast(findStringIndex(SWITCHING_POLICIES, layoutMode, SWITCH_POLICY_GLOBAL)); showIndicator = config.readEntry("ShowLayoutIndicator", true); bool showFlag = config.readEntry("ShowFlag", false); bool showLabel = config.readEntry("ShowLabel", true); indicatorType = getIndicatorType(showFlag, showLabel); showSingle = config.readEntry("ShowSingle", false); QString labelsStr = config.readEntry("DisplayNames", ""); - QStringList labels = labelsStr.split(LIST_SEPARATOR, QString::KeepEmptyParts); + QStringList labels = labelsStr.split(LIST_SEPARATOR, Qt::KeepEmptyParts); for(int i=0; i KeyboardConfig::getDefaultLayouts() const { QList defaultLayoutList; int i = 0; foreach(const LayoutUnit& layoutUnit, layouts) { defaultLayoutList.append(layoutUnit); if( layoutLoopCount != KeyboardConfig::NO_LOOPING && i >= layoutLoopCount-1 ) break; i++; } return defaultLayoutList; } QList KeyboardConfig::getExtraLayouts() const { if( layoutLoopCount == KeyboardConfig::NO_LOOPING ) return QList(); return layouts.mid(layoutLoopCount, layouts.size()); } diff --git a/kcms/kfontinst/apps/Printer.cpp b/kcms/kfontinst/apps/Printer.cpp index b42b9a842..10da9bcc1 100644 --- a/kcms/kfontinst/apps/Printer.cpp +++ b/kcms/kfontinst/apps/Printer.cpp @@ -1,513 +1,514 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "config-fontinst.h" #include "Printer.h" #include "FcEngine.h" #include "ActionLabel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config-workspace.h" #if defined(Q_WS_X11) || defined(Q_WS_QWS) #include #include #include FT_FREETYPE_H #endif #ifdef HAVE_LOCALE_H #include #include #include #include #endif #include "CreateParent.h" // Enable the following to allow printing of non-installed fonts. Does not seem to work :-( //#define KFI_PRINT_APP_FONTS using namespace KFI; static const int constMarginLineBefore=1; static const int constMarginLineAfter=2; static const int constMarginFont=4; inline bool sufficientSpace(int y, int pageHeight, const QFontMetrics &fm) { return (y+constMarginFont+fm.height())fontMetrics().height(), required=titleFontHeight+constMarginLineBefore+constMarginLineAfter; for(unsigned int s=0; sizes[s]; ++s) { font.setPointSize(sizes[s]); required+=QFontMetrics(font, painter->device()).height(); if(sizes[s+1]) required+=constMarginFont; } if(0==size) { font.setPointSize(CFcEngine::constDefaultAlphaSize); int fontHeight=QFontMetrics(font, painter->device()).height(); required+=(3*(constMarginFont+fontHeight))+ constMarginLineBefore+constMarginLineAfter; } return (y+required)num_charmaps; ++cmap) if(face->charmaps[cmap] && FT_ENCODING_ADOBE_CUSTOM==face->charmaps[cmap]->encoding) { FT_Select_Charmap(face, FT_ENCODING_ADOBE_CUSTOM); break; } for(unsigned int i=1; i<65535; ++i) if(FT_Get_Char_Index(face, i)) { newStr+=QChar(i); if(newStr.length()>255) break; } return newStr; } static QString usableStr(FT_Face face, const QString &str) { unsigned int slen=str.length(), ch; QString newStr; for(ch=0; ch &items, int size, QObject *parent) : QThread(parent) , itsPrinter(printer) , itsItems(items) , itsSize(size) , itsCancelled(false) { } CPrintThread::~CPrintThread() { } void CPrintThread::cancel() { itsCancelled=true; } void CPrintThread::run() { QPainter painter; QFont sans("sans", 12, QFont::Bold); bool changedFontEmbeddingSetting(false); QString str(CFcEngine(false).getPreviewString()); if(!itsPrinter->fontEmbeddingEnabled()) { itsPrinter->setFontEmbeddingEnabled(true); changedFontEmbeddingSetting=true; } itsPrinter->setResolution(72); painter.begin(itsPrinter); int pageWidth=painter.device()->width(), pageHeight=painter.device()->height(), y=0, oneSize[2]={itsSize, 0}; const int *sizes=oneSize; bool firstFont(true); if(0==itsSize) sizes=CFcEngine::constScalableSizes; painter.setClipping(true); painter.setClipRect(0, 0, pageWidth, pageHeight); QList::ConstIterator it(itsItems.constBegin()), end(itsItems.constEnd()); for(int i=0; it!=end && !itsCancelled; ++it, ++i) { QString name(FC::createName((*it).family, (*it).styleInfo)); emit progress(i, name); unsigned int s=0; QFont font; #ifdef KFI_PRINT_APP_FONTS QString family; if(-1!=appFont[(*it).family]) { family=QFontDatabase::applicationFontFamilies(appFont[(*it).family]).first(); font=QFont(family); } #else font=CFcEngine::getQFont((*it).family, (*it).styleInfo, CFcEngine::constDefaultAlphaSize); #endif painter.setFont(sans); if(!firstFont && !sufficientSpace(y, &painter, font, sizes, pageHeight, itsSize)) { itsPrinter->newPage(); y=0; } painter.setFont(sans); y+=painter.fontMetrics().height(); painter.drawText(0, y, name); y+=constMarginLineBefore; painter.drawLine(0, y, pageWidth, y); y+=constMarginLineAfter; bool onlyDrawChars=false; Qt::TextElideMode em=Qt::LeftToRight==QApplication::layoutDirection() ? Qt::ElideRight : Qt::ElideLeft; if(0==itsSize) { font.setPointSize(CFcEngine::constDefaultAlphaSize); painter.setFont(font); QFontMetrics fm(font, painter.device()); bool lc=hasStr(font, CFcEngine::getLowercaseLetters()), uc=hasStr(font, CFcEngine::getUppercaseLetters()); onlyDrawChars=!lc && !uc; if(lc || uc) y+=CFcEngine::constDefaultAlphaSize; if(lc) { painter.drawText(0, y, fm.elidedText(CFcEngine::getLowercaseLetters(), em, pageWidth)); y+=constMarginFont+CFcEngine::constDefaultAlphaSize; } if(uc) { painter.drawText(0, y, fm.elidedText(CFcEngine::getUppercaseLetters(), em, pageWidth)); y+=constMarginFont+CFcEngine::constDefaultAlphaSize; } if(lc || uc) { QString validPunc(usableStr(font, CFcEngine::getPunctuation())); if(validPunc.length()>=(CFcEngine::getPunctuation().length()/2)) { painter.drawText(0, y, fm.elidedText(CFcEngine::getPunctuation(), em, pageWidth)); y+=constMarginFont+constMarginLineBefore; } painter.drawLine(0, y, pageWidth, y); y+=constMarginLineAfter; } } for(; sizes[s]; ++s) { y+=sizes[s]; font.setPointSize(sizes[s]); painter.setFont(font); QFontMetrics fm(font, painter.device()); if(sufficientSpace(y, pageHeight, fm)) { painter.drawText(0, y, fm.elidedText(previewString(font, str, onlyDrawChars), em, pageWidth)); if(sizes[s+1]) y+=constMarginFont; } else break; } y+=(s<1 || sizes[s-1]<25 ? 14 : 28); firstFont=false; } emit progress(itsItems.count(), QString()); painter.end(); // // Did we change the users font settings? If so, reset to their previous values... if(changedFontEmbeddingSetting) itsPrinter->setFontEmbeddingEnabled(false); } CPrinter::CPrinter(QWidget *parent) : QDialog(parent) { setWindowTitle(i18n("Print")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel); connect(buttonBox, &QDialogButtonBox::rejected, this, &CPrinter::slotCancelClicked); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QFrame *page = new QFrame(this); QGridLayout *layout=new QGridLayout(page); itsStatusLabel=new QLabel(page); itsProgress=new QProgressBar(page); layout->addWidget(itsActionLabel = new CActionLabel(this), 0, 0, 2, 1); layout->addWidget(itsStatusLabel, 0, 1); layout->addWidget(itsProgress, 1, 1); itsProgress->setRange(0, 100); layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding), 2, 0); mainLayout->addWidget(page); mainLayout->addWidget(buttonBox); setMinimumSize(420, 80); } CPrinter::~CPrinter() { } void CPrinter::print(const QList &items, int size) { #ifdef HAVE_LOCALE_H char *oldLocale=setlocale(LC_NUMERIC, "C"); #endif QPrinter printer; QPrintDialog *dialog = new QPrintDialog(&printer, parentWidget()); if(dialog->exec()) { CPrintThread *thread = new CPrintThread(&printer, items, size, this); itsProgress->setRange(0, items.count()); itsProgress->setValue(0); progress(0, QString()); connect(thread, &CPrintThread::progress, this, &CPrinter::progress); connect(thread, &QThread::finished, this, &QDialog::accept); connect(this, &CPrinter::cancelled, thread, &CPrintThread::cancel); itsActionLabel->startAnimation(); thread->start(); exec(); delete thread; } delete dialog; #ifdef HAVE_LOCALE_H if(oldLocale) setlocale(LC_NUMERIC, oldLocale); #endif } void CPrinter::progress(int p, const QString &label) { if(!label.isEmpty()) itsStatusLabel->setText(label); itsProgress->setValue(p); } void CPrinter::slotCancelClicked() { itsStatusLabel->setText(i18n("Canceling...")); emit cancelled(); } void CPrinter::closeEvent(QCloseEvent *e) { Q_UNUSED(e) e->ignore(); slotCancelClicked(); } int main(int argc, char **argv) { QApplication app(argc, argv); KLocalizedString::setApplicationDomain(KFI_CATALOGUE); KAboutData aboutData("kfontprint", i18n("Font Printer"), WORKSPACE_VERSION_STRING, i18n("Simple font printer"), KAboutLicense::GPL, i18n("(C) Craig Drummond, 2007")); KAboutData::setApplicationData(aboutData); QGuiApplication::setWindowIcon(QIcon::fromTheme("kfontprint")); QCommandLineParser parser; const QCommandLineOption embedOption(QLatin1String("embed"), i18n("Makes the dialog transient for an X app specified by winid"), QLatin1String("winid")); parser.addOption(embedOption); const QCommandLineOption sizeOption(QLatin1String("size"), i18n("Size index to print fonts"), QLatin1String("index")); parser.addOption(sizeOption); const QCommandLineOption pfontOption(QLatin1String("pfont"), i18n("Font to print, specified as \"Family,Style\" where Style is a 24-bit decimal number composed as: "), QLatin1String("font")); parser.addOption(pfontOption); const QCommandLineOption listfileOption(QLatin1String("listfile"), i18n("File containing list of fonts to print"), QLatin1String("file")); parser.addOption(listfileOption); const QCommandLineOption deletefileOption(QLatin1String("deletefile"), i18n("Remove file containing list of fonts to print")); parser.addOption(deletefileOption); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); QList fonts; int size(parser.value(sizeOption).toInt()); if(size>-1 && size<256) { QString listFile(parser.value(listfileOption)); if(!listFile.isEmpty()) { QFile f(listFile); if(f.open(QIODevice::ReadOnly)) { QTextStream str(&f); while (!str.atEnd()) { QString family(str.readLine()), style(str.readLine()); if(!family.isEmpty() && !style.isEmpty()) fonts.append(Misc::TFont(family, style.toUInt())); else break; } f.close(); } if(parser.isSet(deletefileOption)) ::unlink(listFile.toLocal8Bit().constData()); } else { QStringList fl(parser.values(pfontOption)); QStringList::ConstIterator it(fl.begin()), end(fl.end()); for(; it!=end; ++it) { QString f(*it); int commaPos=f.lastIndexOf(','); if(-1!=commaPos) fonts.append(Misc::TFont(f.left(commaPos), f.mid(commaPos+1).toUInt())); } } if(!fonts.isEmpty()) { CPrinter(createParent(parser.value(embedOption).toInt(nullptr, 16))).print(fonts, size); return 0; } } return -1; } diff --git a/kcms/kfontinst/dbus/Folder.cpp b/kcms/kfontinst/dbus/Folder.cpp index a4f2320de..4c8c5a140 100644 --- a/kcms/kfontinst/dbus/Folder.cpp +++ b/kcms/kfontinst/dbus/Folder.cpp @@ -1,417 +1,417 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2009 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Folder.h" #include "FcConfig.h" #include "Misc.h" #include "KfiConstants.h" #include "XmlStrings.h" #include "Style.h" #include "File.h" #include "config-fontinst.h" #define DISABLED_FONTS "disabledfonts" #define KFI_DBUG qDebug() << time(nullptr) namespace KFI { bool Folder::CfgFile::modified() { return timestamp!=Misc::getTimeStamp(name); } void Folder::CfgFile::updateTimeStamp() { timestamp=Misc::getTimeStamp(name); } Folder::~Folder() { saveDisabled(); } void Folder::init(bool system, bool systemBus) { itsIsSystem=system; if(!system) { FcStrList *list=FcConfigGetFontDirs(FcInitLoadConfigAndFonts()); QStringList dirs; FcChar8 *fcDir; while((fcDir=FcStrListNext(list))) dirs.append(Misc::dirSyntax((const char *)fcDir)); itsLocation=Misc::getFolder(Misc::dirSyntax(QDir::homePath()+"/.fonts/"), Misc::dirSyntax(QDir::homePath()), dirs); } else itsLocation=KFI_DEFAULT_SYS_FONTS_FOLDER; if((!system && !systemBus) || (system && systemBus)) FcConfig::addDir(itsLocation, system); itsDisabledCfg.dirty=false; if(itsDisabledCfg.name.isEmpty()) { QString fileName("/" DISABLED_FONTS ".xml"); if(system) itsDisabledCfg.name=QString::fromLatin1(KFI_ROOT_CFG_DIR)+fileName; else { QString path=QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/'); if(!Misc::dExists(path)) Misc::createDir(path); itsDisabledCfg.name=path+fileName; } itsDisabledCfg.timestamp=0; } } bool Folder::allowToggling() const { return Misc::fExists(itsDisabledCfg.name) ? Misc::fWritable(itsDisabledCfg.name) : Misc::dWritable(Misc::getDir(itsDisabledCfg.name)); } void Folder::loadDisabled() { if(itsDisabledCfg.dirty) saveDisabled(); QFile f(itsDisabledCfg.name); KFI_DBUG << itsDisabledCfg.name; itsDisabledCfg.dirty=false; if(f.open(QIODevice::ReadOnly)) { QDomDocument doc; if(doc.setContent(&f)) for(QDomNode n=doc.documentElement().firstChild(); !n.isNull(); n=n.nextSibling()) { QDomElement e=n.toElement(); if(FONT_TAG==e.tagName()) { Family fam(e, false); if(!fam.name().isEmpty()) { Style style(e, false); if(KFI_NO_STYLE_INFO!=style.value()) { QList files; if(e.hasAttribute(PATH_ATTR)) { File file(e, true); if(!file.path().isEmpty()) files.append(file); else itsDisabledCfg.dirty=true; } else { for(QDomNode n=e.firstChild(); !n.isNull(); n=n.nextSibling()) { QDomElement ent=n.toElement(); if(FILE_TAG==ent.tagName()) { File file(ent, true); if(!file.path().isEmpty()) files.append(file); else { KFI_DBUG << "Set dirty from load"; itsDisabledCfg.dirty=true; } } } } if(files.count()>0) { QList::ConstIterator it(files.begin()), end(files.end()); FamilyCont::ConstIterator f(itsFonts.insert(fam)); StyleCont::ConstIterator s((*f).add(style)); for(; it!=end; ++it) (*s).add(*it); } } } } } f.close(); itsDisabledCfg.updateTimeStamp(); } saveDisabled(); } void Folder::saveDisabled() { if(itsDisabledCfg.dirty) { if(!itsIsSystem || Misc::root()) { KFI_DBUG << itsDisabledCfg.name; QSaveFile file; file.setFileName(itsDisabledCfg.name); if (!file.open(QIODevice::WriteOnly)) { KFI_DBUG << "Exit - cant open save file"; qApp->exit(0); } QTextStream str(&file); - str << "<" DISABLED_FONTS ">" << endl; + str << "<" DISABLED_FONTS ">" << Qt::endl; FamilyCont::ConstIterator it(itsFonts.begin()), end(itsFonts.end()); for(; it!=end; ++it) (*it).toXml(true, str); - str << "" << endl; + str << "" << Qt::endl; str.flush(); if(!file.commit()) { KFI_DBUG << "Exit - cant finalize save file"; qApp->exit(0); } } itsDisabledCfg.updateTimeStamp(); itsDisabledCfg.dirty=false; } } QStringList Folder::toXml(int max) { QStringList rv; FamilyCont::ConstIterator it(itsFonts.begin()), end(itsFonts.end()); QString string; QTextStream str(&string); for(int i=0; it!=end; ++it, ++i) { if(0==(i%max)) { if(i) { - str << "" << endl; + str << "" << Qt::endl; rv.append(string); string=QString(); } - str << "<" FONTLIST_TAG " " << SYSTEM_ATTR "=\"" << (itsIsSystem ? "true" : "false") << "\">" << endl; + str << "<" FONTLIST_TAG " " << SYSTEM_ATTR "=\"" << (itsIsSystem ? "true" : "false") << "\">" << Qt::endl; } (*it).toXml(false, str); } if(!string.isEmpty()) { - str << "" << endl; + str << "" << Qt::endl; rv.append(string); } return rv; } Families Folder::list() { Families fam(itsIsSystem); FamilyCont::ConstIterator it(itsFonts.begin()), end(itsFonts.end()); for(int i=0; it!=end; ++it, ++i) fam.items.insert(*it); return fam; } bool Folder::contains(const QString &family, quint32 style) { FamilyCont::ConstIterator fam=itsFonts.find(Family(family)); if(fam==itsFonts.end()) return false; StyleCont::ConstIterator st=(*fam).styles().find(Style(style)); return st!=(*fam).styles().end(); } void Folder::add(const Family &family) { FamilyCont::ConstIterator existingFamily=itsFonts.find(family); if(existingFamily==itsFonts.end()) itsFonts.insert(family); else { StyleCont::ConstIterator it(family.styles().begin()), end(family.styles().end()); for(; it!=end; ++it) { StyleCont::ConstIterator existingStyle=(*existingFamily).styles().find(*it); if(existingStyle==(*existingFamily).styles().end()) (*existingFamily).add(*it); else { FileCont::ConstIterator fit((*it).files().begin()), fend((*it).files().end()); for(; fit!=fend; ++fit) { FileCont::ConstIterator f=(*existingStyle).files().find(*fit); if(f==(*existingStyle).files().end()) (*existingStyle).add(*fit); } (*existingStyle).setWritingSystems((*existingStyle).writingSystems()|(*it).writingSystems()); if(!(*existingStyle).scalable() && (*it).scalable()) (*existingStyle).setScalable(true); } } } } void Folder::configure(bool force) { KFI_DBUG << "EMPTY MODIFIED " << itsModifiedDirs.isEmpty(); if(force || !itsModifiedDirs.isEmpty()) { saveDisabled(); QSet::ConstIterator it(itsModifiedDirs.constBegin()), end(itsModifiedDirs.constEnd()); QSet dirs; for(; it!=end; ++it) if(Misc::fExists((*it)+"fonts.dir")) dirs.insert(KShell::quoteArg(*it)); if(!dirs.isEmpty()) - QProcess::startDetached(QLatin1String(KFONTINST_LIB_EXEC_DIR"/fontinst_x11"), dirs.toList()); + QProcess::startDetached(QStringLiteral(KFONTINST_LIB_EXEC_DIR"/fontinst_x11"), dirs.values()); itsModifiedDirs.clear(); KFI_DBUG << "RUN FC"; Misc::doCmd("fc-cache"); KFI_DBUG << "DONE"; } } Folder::Flat Folder::flatten() const { FamilyCont::ConstIterator fam=itsFonts.begin(), famEnd=itsFonts.end(); Flat rv; for(; fam!=famEnd; ++fam) { StyleCont::ConstIterator style((*fam).styles().begin()), styleEnd((*fam).styles().end()); for(; style!=styleEnd; ++style) { FileCont::ConstIterator file((*style).files().begin()), fileEnd((*style).files().end()); for(; file!=fileEnd; ++file) rv.insert(FlatFont(*fam, *style, *file)); } } return rv; } Families Folder::Flat::build(bool system) const { ConstIterator it(begin()), e(end()); Families families(system); for(; it!=e; ++it) { Family f((*it).family); Style s((*it).styleInfo, (*it).scalable, (*it).writingSystems); FamilyCont::ConstIterator fam=families.items.constFind(f); if(families.items.constEnd()==fam) { s.add((*it).file); f.add(s); families.items.insert(f); } else { StyleCont::ConstIterator st=(*fam).styles().constFind(s); if((*fam).styles().constEnd()==st) { s.add((*it).file); (*fam).add(s); } else (*st).add((*it).file); } } return families; } } diff --git a/kcms/kfontinst/dbus/FontInst.cpp b/kcms/kfontinst/dbus/FontInst.cpp index 3564eb1b6..bfe2b3460 100644 --- a/kcms/kfontinst/dbus/FontInst.cpp +++ b/kcms/kfontinst/dbus/FontInst.cpp @@ -1,974 +1,975 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2009 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include "FontInst.h" #include "fontinstadaptor.h" #include "Misc.h" #include "Fc.h" #include "WritingSystems.h" #include "Utils.h" #include "FontinstIface.h" #define KFI_DBUG qDebug() << time(nullptr) namespace KFI { static void decompose(const QString &name, QString &family, QString &style) { int commaPos=name.lastIndexOf(','); family=-1==commaPos ? name : name.left(commaPos); style=-1==commaPos ? KFI_WEIGHT_REGULAR : name.mid(commaPos+2); } static bool isSystem=false; static Folder theFolders[FontInst::FOLDER_COUNT]; static const int constSystemReconfigured=-1; static const int constConnectionsTimeout = 30 * 1000; static const int constFontListTimeout = 10 * 1000; typedef void (*SignalHandler)(int); static void registerSignalHandler(SignalHandler handler) { if (!handler) handler = SIG_DFL; sigset_t mask; sigemptyset(&mask); #ifdef SIGSEGV signal(SIGSEGV, handler); sigaddset(&mask, SIGSEGV); #endif #ifdef SIGFPE signal(SIGFPE, handler); sigaddset(&mask, SIGFPE); #endif #ifdef SIGILL signal(SIGILL, handler); sigaddset(&mask, SIGILL); #endif #ifdef SIGABRT signal(SIGABRT, handler); sigaddset(&mask, SIGABRT); #endif sigprocmask(SIG_UNBLOCK, &mask, nullptr); } void signalHander(int) { static bool inHandler=false; if(!inHandler) { inHandler=true; theFolders[isSystem ? FontInst::FOLDER_SYS : FontInst::FOLDER_USER].saveDisabled(); inHandler=false; } } FontInst::FontInst() { isSystem=Misc::root(); registerTypes(); new FontinstAdaptor(this); QDBusConnection bus=QDBusConnection::sessionBus(); KFI_DBUG << "Connecting to session bus"; if(!bus.registerService(OrgKdeFontinstInterface::staticInterfaceName())) { KFI_DBUG << "Failed to register service!"; ::exit(-1); } if(!bus.registerObject(FONTINST_PATH, this)) { KFI_DBUG << "Failed to register object!"; ::exit(-1); } registerSignalHandler(signalHander); itsConnectionsTimer=new QTimer(this); itsFontListTimer=new QTimer(this); connect(itsConnectionsTimer, &QTimer::timeout, this, &FontInst::connectionsTimeout); connect(itsFontListTimer, &QTimer::timeout, this, &FontInst::fontListTimeout); itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) theFolders[i].init(FOLDER_SYS==i, isSystem); updateFontList(false); } FontInst::~FontInst() { for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) theFolders[i].saveDisabled(); } void FontInst::list(int folders, int pid) { KFI_DBUG << folders << pid; itsConnections.insert(pid); updateFontList(false); QList fonts; for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) if(0==folders || folders&(1<start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); emit fontList(pid, fonts); } void FontInst::statFont(const QString &name, int folders, int pid) { KFI_DBUG << name << folders << pid; bool checkSystem=0==folders || folders&SYS_MASK || isSystem, checkUser=0==folders || (folders&USR_MASK && !isSystem); FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; itsConnections.insert(pid); if( (checkSystem && findFont(name, FOLDER_SYS, fam, st)) || (checkUser && findFont(name, FOLDER_USER, fam, st, !checkSystem)) ) { Family rv((*fam).name()); rv.add(*st); KFI_DBUG << "Found font, emit details..."; emit fontStat(pid, rv); } else { KFI_DBUG << "Font not found, emit empty details..."; emit fontStat(pid, Family(name)); } } void FontInst::install(const QString &file, bool createAfm, bool toSystem, int pid, bool checkConfig) { KFI_DBUG << file << createAfm << toSystem << pid << checkConfig; itsConnections.insert(pid); if(checkConfig) updateFontList(); EFolder folder=isSystem || toSystem ? FOLDER_SYS : FOLDER_USER; Family font; Utils::EFileType type=Utils::check(file, font); int result=Utils::FILE_BITMAP==type && !FC::bitmapsEnabled() ? STATUS_BITMAPS_DISABLED : Utils::FILE_INVALID==type ? STATUS_NOT_FONT_FILE : STATUS_OK; if(STATUS_OK==result) { if(Utils::FILE_AFM!=type && Utils::FILE_PFM!=type) for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT) && STATUS_OK==result; ++i) if(theFolders[i].contains(font.name(), (*font.styles().begin()).value())) result=STATUS_ALREADY_INSTALLED; if(STATUS_OK==result) { QString name(Misc::modifyName(Misc::getFile(file))), destFolder(Misc::getDestFolder(theFolders[folder].location(), name)); result=Utils::FILE_AFM!=type && Utils::FILE_PFM!=type && Misc::fExists(destFolder+name) ? (int)KIO::ERR_FILE_ALREADY_EXIST : (int)STATUS_OK; if(STATUS_OK==result) { if(toSystem && !isSystem) { KFI_DBUG << "Send request to system helper" << file << destFolder << name; QVariantMap args; args["method"] = "install"; args["file"] = file; args["name"] = name; args["destFolder"] = destFolder; args["createAfm"] = createAfm; args["type"] = (int)type; result=performAction(args); } else { if(!Misc::dExists(destFolder)) result=Misc::createDir(destFolder) ? (int)STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED; if(STATUS_OK==result) result=QFile::copy(file, destFolder+name) ? (int)STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED; if(STATUS_OK==result) { Misc::setFilePerms(QFile::encodeName(destFolder+name)); if((Utils::FILE_SCALABLE==type || Utils::FILE_PFM==type) && createAfm) Utils::createAfm(destFolder+name, type); } } if(STATUS_OK==result && Utils::FILE_AFM!=type && Utils::FILE_PFM!=type) { StyleCont::ConstIterator st(font.styles().begin()); FileCont::ConstIterator f((*st).files().begin()); File df(destFolder+name, (*f).foundry(), (*f).index()); (*st).clearFiles(); (*st).add(df); theFolders[folder].add(font); theFolders[folder].addModifiedDir(destFolder); emit fontsAdded(Families(font, FOLDER_SYS==folder)); } } } } emit status(pid, result); itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); } void FontInst::uninstall(const QString &family, quint32 style, bool fromSystem, int pid, bool checkConfig) { KFI_DBUG << family << style << fromSystem << pid << checkConfig; itsConnections.insert(pid); if(checkConfig) updateFontList(); EFolder folder=isSystem || fromSystem ? FOLDER_SYS : FOLDER_USER; FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; int result=findFont(family, style, folder, fam, st) ? (int)STATUS_OK : (int)KIO::ERR_DOES_NOT_EXIST; if(STATUS_OK==result) { Family del((*fam).name()); Style s((*st).value(), (*st).scalable(), (*st).writingSystems()); FileCont files((*st).files()); FileCont::ConstIterator it(files.begin()), end(files.end()); if(fromSystem && !isSystem) { QStringList fileList; bool wasDisabled(false); for(; it!=end; ++it) { fileList.append((*it).path()); theFolders[FOLDER_SYS].addModifiedDir(Misc::getDir((*it).path())); if(!wasDisabled && Misc::isHidden(Misc::getFile((*it).path()))) wasDisabled=true; } QVariantMap args; args["method"] = "uninstall"; args["files"] = fileList; result=performAction(args); if(STATUS_OK==result) { FileCont empty; s.setFiles(files); (*st).setFiles(empty); if(wasDisabled) theFolders[FOLDER_SYS].setDisabledDirty(); } } else { for(; it!=end; ++it) if(!Misc::fExists((*it).path()) || QFile::remove((*it).path())) { // Also remove any AFM or PFM files... QStringList other; Misc::getAssociatedFiles((*it).path(), other); QStringList::ConstIterator oit(other.constBegin()), oend(other.constEnd()); for(; oit!=oend; ++oit) QFile::remove(*oit); theFolders[folder].addModifiedDir(Misc::getDir((*it).path())); (*st).remove(*it); s.add(*it); if(!theFolders[folder].disabledDirty() && Misc::isHidden(Misc::getFile((*it).path()))) theFolders[folder].setDisabledDirty(); } } if(STATUS_OK==result) { if((*st).files().isEmpty()) { (*fam).remove(*st); if((*fam).styles().isEmpty()) theFolders[folder].removeFont(*fam); } else result=STATUS_PARTIAL_DELETE; del.add(s); } emit fontsRemoved(Families(del, FOLDER_SYS==folder)); } KFI_DBUG << "status" << result; emit status(pid, result); itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); } void FontInst::uninstall(const QString &name, bool fromSystem, int pid, bool checkConfig) { KFI_DBUG << name << fromSystem << pid << checkConfig; FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; if(findFont(name, fromSystem || isSystem ? FOLDER_SYS : FOLDER_USER, fam, st)) uninstall((*fam).name(), (*st).value(), fromSystem, pid, checkConfig); else emit status(pid, KIO::ERR_DOES_NOT_EXIST); } void FontInst::move(const QString &family, quint32 style, bool toSystem, int pid, bool checkConfig) { KFI_DBUG << family << style << toSystem << pid << checkConfig; itsConnections.insert(pid); if(checkConfig) updateFontList(); if(isSystem) emit status(pid, KIO::ERR_UNSUPPORTED_ACTION); else { FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; EFolder from=toSystem ? FOLDER_USER : FOLDER_SYS, to=toSystem ? FOLDER_SYS : FOLDER_USER; if(findFont(family, style, from, fam, st)) { FileCont::ConstIterator it((*st).files().begin()), end((*st).files().end()); QStringList files; for(; it!=end; ++it) { files.append((*it).path()); theFolders[from].addModifiedDir(Misc::getDir((*it).path())); // Actual 'to' folder does not really matter, as we only want to call fc-cache // ...actual folders only matter for xreating fonts.dir, etc, and we wont be doing this... theFolders[to].addModifiedDir(theFolders[to].location()); } QVariantMap args; args["method"] = "move"; args["files"] = files; args["toSystem"] = toSystem; args["dest"] = theFolders[to].location(); args["uid"] = getuid(); args["gid"] = getgid(); int result=performAction(args); if(STATUS_OK==result) updateFontList(); emit status(pid, result); } else { KFI_DBUG << "does not exist"; emit status(pid, KIO::ERR_DOES_NOT_EXIST); } } itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); } static bool renameFontFile(const QString &from, const QString &to, int uid=-1, int gid=-1) { if (!QFile::rename(from, to)) return false; QByteArray dest(QFile::encodeName(to)); Misc::setFilePerms(dest); if(-1!=uid && -1!=gid) ::chown(dest.data(), uid, gid); return true; } void FontInst::enable(const QString &family, quint32 style, bool inSystem, int pid, bool checkConfig) { KFI_DBUG << family << style << inSystem << pid << checkConfig; toggle(true, family, style, inSystem, pid, checkConfig); } void FontInst::disable(const QString &family, quint32 style, bool inSystem, int pid, bool checkConfig) { KFI_DBUG << family << style << inSystem << pid << checkConfig; toggle(false, family, style, inSystem, pid, checkConfig); } void FontInst::removeFile(const QString &family, quint32 style, const QString &file, bool fromSystem, int pid, bool checkConfig) { KFI_DBUG << family << style << file << fromSystem << pid << checkConfig; itsConnections.insert(pid); if(checkConfig) updateFontList(); // First find the family/style EFolder folder=isSystem || fromSystem ? FOLDER_SYS : FOLDER_USER; FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; int result=findFont(family, style, folder, fam, st) ? (int)STATUS_OK : (int)KIO::ERR_DOES_NOT_EXIST; if(STATUS_OK==result) { // Family/style found - now check that the requested file is *within* the same folder as one // of the files linked to this font... FileCont files((*st).files()); FileCont::ConstIterator it(files.begin()), end(files.end()); QString dir(Misc::getDir(file)); result=KIO::ERR_DOES_NOT_EXIST; for(; it!=end && STATUS_OK!=result; ++it) if(Misc::getDir((*it).path())==dir) result=STATUS_OK; if(STATUS_OK==result) { // OK, found folder - so can now proceed to delete the file... if(fromSystem && !isSystem) { QVariantMap args; args["method"] = "removeFile"; args["file"] = file; result=performAction(args); } else { result=Misc::fExists(file) ? QFile::remove(file) ? (int)STATUS_OK : (int)KIO::ERR_WRITE_ACCESS_DENIED : (int)KIO::ERR_DOES_NOT_EXIST; } if(STATUS_OK==result) theFolders[folder].addModifiedDir(dir); } } emit status(pid, result); } void FontInst::reconfigure(int pid, bool force) { KFI_DBUG << pid << force; bool sysModified(theFolders[FOLDER_SYS].isModified()); saveDisabled(); KFI_DBUG << theFolders[FOLDER_USER].isModified() << sysModified; if(!isSystem && (force || theFolders[FOLDER_USER].isModified())) theFolders[FOLDER_USER].configure(force); if(sysModified) { if(isSystem) { theFolders[FOLDER_SYS].configure(); } else { QVariantMap args; args["method"] = "reconfigure"; performAction(args); theFolders[FOLDER_SYS].clearModified(); } } itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); updateFontList(); emit status(pid, isSystem ? constSystemReconfigured : STATUS_OK); } QString FontInst::folderName(bool sys) { return theFolders[sys || isSystem ? FOLDER_SYS : FOLDER_USER].location(); } void FontInst::saveDisabled() { if(isSystem) theFolders[FOLDER_SYS].saveDisabled(); else for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) if(FOLDER_SYS==i && !isSystem) { if(theFolders[i].disabledDirty()) { QVariantMap args; args["method"] = "saveDisabled"; performAction(args); theFolders[i].saveDisabled(); } } else theFolders[i].saveDisabled(); } void FontInst::connectionsTimeout() { bool canExit(true); KFI_DBUG << "exiting"; checkConnections(); for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) { if(theFolders[i].disabledDirty()) canExit=false; theFolders[i].saveDisabled(); } if(0==itsConnections.count()) { if(canExit) qApp->exit(0); else // Try again later... itsConnectionsTimer->start(constConnectionsTimeout); } } void FontInst::fontListTimeout() { updateFontList(true); itsFontListTimer->start(constFontListTimeout); } void FontInst::updateFontList(bool emitChanges) { // For some reason just the "!FcConfigUptoDate(0)" check does not always work :-( FcBool fcModified=!FcConfigUptoDate(nullptr); if(fcModified || theFolders[FOLDER_SYS].fonts().isEmpty() || (!isSystem && theFolders[FOLDER_USER].fonts().isEmpty()) || theFolders[FOLDER_SYS].disabledDirty() || (!isSystem && theFolders[FOLDER_USER].disabledDirty())) { KFI_DBUG << "Need to refresh font lists"; if(fcModified) { KFI_DBUG << "Re-init FC"; if(!FcInitReinitialize()) KFI_DBUG << "Re-init failed????"; } Folder::Flat old[FOLDER_COUNT]; if(emitChanges) { KFI_DBUG << "Flatten existing font lists"; for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) old[i]=theFolders[i].flatten(); } saveDisabled(); for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) theFolders[i].clearFonts(); KFI_DBUG << "update list of fonts"; FcPattern *pat = FcPatternCreate(); FcObjectSet *os = FcObjectSetBuild(FC_FILE, FC_FAMILY, FC_FAMILYLANG, FC_WEIGHT, FC_LANG, FC_CHARSET, FC_SCALABLE, #ifndef KFI_FC_NO_WIDTHS FC_WIDTH, #endif FC_SLANT, FC_INDEX, FC_FOUNDRY, (void*)nullptr); FcFontSet *list=FcFontList(nullptr, pat, os); FcPatternDestroy(pat); FcObjectSetDestroy(os); theFolders[FOLDER_SYS].loadDisabled(); if(!isSystem) theFolders[FOLDER_USER].loadDisabled(); if(list) { QString home(Misc::dirSyntax(QDir::homePath())); for (int i = 0; i < list->nfont; i++) { QString fileName(Misc::fileSyntax(FC::getFcString(list->fonts[i], FC_FILE))); if(!fileName.isEmpty() && Misc::fExists(fileName)) // && 0!=fileName.indexOf(constDefomaLocation)) { QString family, foundry; quint32 styleVal; int index; qulonglong writingSystems(WritingSystems::instance()->get(list->fonts[i])); FcBool scalable=FcFalse; if(FcResultMatch!=FcPatternGetBool(list->fonts[i], FC_SCALABLE, 0, &scalable)) scalable=FcFalse; FC::getDetails(list->fonts[i], family, styleVal, index, foundry); FamilyCont::ConstIterator fam=theFolders[isSystem || 0!=fileName.indexOf(home) ? FOLDER_SYS : FOLDER_USER].addFont(Family(family)); StyleCont::ConstIterator style=(*fam).add(Style(styleVal)); FileCont::ConstIterator file=(*style).add(File(fileName, foundry, index)); + Q_UNUSED(file); (*style).setWritingSystems((*style).writingSystems()|writingSystems); if(scalable) (*style).setScalable(); } } FcFontSetDestroy(list); } if(emitChanges) { KFI_DBUG << "Look for differences"; for(int i=0; i<(isSystem ? 1 : FOLDER_COUNT); ++i) { KFI_DBUG << "Flatten, and take copies..."; Folder::Flat newList=theFolders[i].flatten(), onlyNew=newList; KFI_DBUG << "Determine differences..."; onlyNew.subtract(old[i]); old[i].subtract(newList); KFI_DBUG << "Emit changes..."; Families families=onlyNew.build(isSystem || i==FOLDER_SYS); if(!families.items.isEmpty()) emit fontsAdded(families); families=old[i].build(isSystem || i==FOLDER_SYS); if(!families.items.isEmpty()) emit fontsRemoved(families); } } KFI_DBUG << "updated list of fonts"; } } void FontInst::toggle(bool enable, const QString &family, quint32 style, bool inSystem, int pid, bool checkConfig) { KFI_DBUG; itsConnections.insert(pid); if(checkConfig) updateFontList(); EFolder folder=isSystem || inSystem ? FOLDER_SYS : FOLDER_USER; FamilyCont::ConstIterator fam; StyleCont::ConstIterator st; int result=findFont(family, style, folder, fam, st) ? (int)STATUS_OK : (int)KIO::ERR_DOES_NOT_EXIST; if(STATUS_OK==result) { FileCont files((*st).files()), toggledFiles; FileCont::ConstIterator it(files.begin()), end(files.end()); QHash movedFonts; QHash movedAssoc; QSet modifiedDirs; for(; it!=end && STATUS_OK==result; ++it) { QString to=Misc::getDir((*it).path())+ QString(enable ? Misc::unhide(Misc::getFile((*it).path())) : Misc::hide(Misc::getFile((*it).path()))); if(to!=(*it).path()) { KFI_DBUG << "MOVE:" << (*it).path() << " to " << to; // If the font is a system font, and we're not root, then just go through the actions here - so // that we can build the list of changes that would happen... if((inSystem && !isSystem) || renameFontFile((*it).path(), to)) { modifiedDirs.insert(Misc::getDir(enable ? to : (*it).path())); toggledFiles.insert(File(to, (*it).foundry(), (*it).index())); // Now try to move an associated AFM or PFM files... QStringList assoc; movedFonts[*it]=to; Misc::getAssociatedFiles((*it).path(), assoc); QStringList::ConstIterator ait(assoc.constBegin()), aend(assoc.constEnd()); for(; ait!=aend && STATUS_OK==result; ++ait) { to=Misc::getDir(*ait)+ QString(enable ? Misc::unhide(Misc::getFile(*ait)) : Misc::hide(Misc::getFile(*ait))); if(to!=*ait) { if((inSystem && !isSystem) || renameFontFile(*ait, to)) { movedAssoc[*ait]=to; } else { result=KIO::ERR_WRITE_ACCESS_DENIED; } } } } else { result=KIO::ERR_WRITE_ACCESS_DENIED; } } } if(inSystem && !isSystem) { Family toggleFam((*fam).name()); toggleFam.add(*st); QVariantMap args; args["method"] = "toggle"; QString xml; QTextStream str(&xml); toggleFam.toXml(false, str); args["xml"] = xml; args["enable"] = enable; result=performAction(args); } if(STATUS_OK==result) { Family addFam((*fam).name()), delFam((*fam).name()); Style addStyle((*st).value(), (*st).scalable(), (*st).writingSystems()), delStyle((*st).value(), (*st).scalable(), (*st).writingSystems()); addStyle.setFiles(toggledFiles); addFam.add(addStyle); delStyle.setFiles(files); delFam.add(delStyle); (*st).setFiles(toggledFiles); theFolders[folder].addModifiedDirs(modifiedDirs); emit fontsAdded(Families(addFam, FOLDER_SYS==folder)); emit fontsRemoved(Families(delFam, FOLDER_SYS==folder)); theFolders[folder].setDisabledDirty(); } else // un-move fonts! { QHash::ConstIterator fit(movedFonts.constBegin()), fend(movedFonts.constEnd()); QHash::ConstIterator ait(movedAssoc.constBegin()), aend(movedAssoc.constEnd()); for(; fit!=fend; ++fit) renameFontFile(fit.value(), fit.key().path()); for(; ait!=aend; ++ait) renameFontFile(ait.value(), ait.key()); } } emit status(pid, result); itsConnectionsTimer->start(constConnectionsTimeout); itsFontListTimer->start(constFontListTimeout); } void FontInst::addModifedSysFolders(const Family &family) { StyleCont::ConstIterator style(family.styles().begin()), styleEnd(family.styles().end()); for(; style!=styleEnd; ++style) { FileCont::ConstIterator file((*style).files().begin()), fileEnd((*style).files().end()); for(; file!=fileEnd; ++file) theFolders[FOLDER_SYS].addModifiedDir(Misc::getDir((*file).path())); } } void FontInst::checkConnections() { KFI_DBUG; QSet::ConstIterator it(itsConnections.begin()), end(itsConnections.end()); QSet remove; for(; it!=end; ++it) if(0!=kill(*it, 0)) remove.insert(*it); itsConnections.subtract(remove); } bool FontInst::findFontReal(const QString &family, const QString &style, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st) { KFI_DBUG; Family f(family); fam=theFolders[folder].fonts().find(f); if(theFolders[folder].fonts().end()==fam) return false; StyleCont::ConstIterator end((*fam).styles().end()); for(st=(*fam).styles().begin(); st!=end; ++st) if(FC::createStyleName((*st).value())==style) return true; return false; } bool FontInst::findFont(const QString &font, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st, bool updateList) { KFI_DBUG; QString family, style; decompose(font, family, style); if(!findFontReal(family, style, folder, fam, st)) { if(updateList) { // Not found, so refresh font list and try again... updateFontList(); return findFontReal(family, style, folder, fam, st); } else { return false; } } return true; } bool FontInst::findFontReal(const QString &family, quint32 style, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st) { KFI_DBUG; fam=theFolders[folder].fonts().find(Family(family)); if(theFolders[folder].fonts().end()==fam) return false; else { st=(*fam).styles().find(style); return (*fam).styles().end()!=st; } } bool FontInst::findFont(const QString &family, quint32 style, EFolder folder, FamilyCont::ConstIterator &fam, StyleCont::ConstIterator &st, bool updateList) { KFI_DBUG; if(!findFontReal(family, style, folder, fam, st)) { if(updateList) { // Not found, so refresh font list and try again... updateFontList(); return findFontReal(family, style, folder, fam, st); } else { return false; } } return true; } int FontInst::performAction(const QVariantMap &args) { KAuth::Action action("org.kde.fontinst.manage"); action.setHelperId("org.kde.fontinst"); action.setArguments(args); KFI_DBUG << "Call " << args["method"].toString() << " on helper"; itsFontListTimer->stop(); itsConnectionsTimer->stop(); KAuth::ExecuteJob* j = action.execute(); j->exec(); if (j->error()) { qWarning() << "kauth action failed" << j->errorString() << j->errorText(); //error is a KAuth::ActionReply::Error rest of this code expects KIO error codes which are extended by EStatus switch (j->error()) { case KAuth::ActionReply::Error::UserCancelledError: return KIO::ERR_USER_CANCELED; case KAuth::ActionReply::Error::AuthorizationDeniedError: /*fall through*/ case KAuth::ActionReply::Error::NoSuchActionError: return KIO::ERR_CANNOT_AUTHENTICATE; default: return KIO::ERR_INTERNAL; } return KIO::ERR_INTERNAL; } KFI_DBUG << "Success!"; return STATUS_OK; } } diff --git a/kcms/kfontinst/kcmfontinst/DuplicatesDialog.cpp b/kcms/kfontinst/kcmfontinst/DuplicatesDialog.cpp index 29b94075a..85be96c77 100644 --- a/kcms/kfontinst/kcmfontinst/DuplicatesDialog.cpp +++ b/kcms/kfontinst/kcmfontinst/DuplicatesDialog.cpp @@ -1,691 +1,691 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "DuplicatesDialog.h" #include "ActionLabel.h" #include "Misc.h" #include "Fc.h" #include "FcEngine.h" #include "FontList.h" #include "JobRunner.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KFI { enum EDialogColumns { COL_FILE, COL_TRASH, COL_SIZE, COL_DATE, COL_LINK }; CDuplicatesDialog::CDuplicatesDialog(QWidget *parent, CFontList *fl) : QDialog(parent), itsFontList(fl) { setWindowTitle(i18n("Duplicate Fonts")); itsButtonBox = new QDialogButtonBox(QDialogButtonBox::Cancel); connect(itsButtonBox, &QDialogButtonBox::clicked, this, &CDuplicatesDialog::slotButtonClicked); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); setModal(true); QFrame *page = new QFrame(this); mainLayout->addWidget(page); mainLayout->addWidget(itsButtonBox); QGridLayout *layout=new QGridLayout(page); layout->setContentsMargins(0, 0, 0, 0); itsLabel=new QLabel(page); itsView=new CFontFileListView(page); itsView->hide(); layout->addWidget(itsActionLabel = new CActionLabel(this), 0, 0); layout->addWidget(itsLabel, 0, 1); itsLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); layout->addWidget(itsView, 1, 0, 1, 2); itsFontFileList=new CFontFileList(this); connect(itsFontFileList, SIGNAL(finished()), SLOT(scanFinished())); connect(itsView, &CFontFileListView::haveDeletions, this, &CDuplicatesDialog::enableButtonOk); } int CDuplicatesDialog::exec() { itsActionLabel->startAnimation(); itsLabel->setText(i18n("Scanning for duplicate fonts. Please wait...")); itsFontFileList->start(); return QDialog::exec(); } void CDuplicatesDialog::scanFinished() { itsActionLabel->stopAnimation(); if(itsFontFileList->wasTerminated()) { itsFontFileList->wait(); reject(); } else { CFontFileList::TFontMap duplicates; itsFontFileList->getDuplicateFonts(duplicates); if(0==duplicates.count()) { itsButtonBox->setStandardButtons(QDialogButtonBox::Close); itsLabel->setText(i18n("No duplicate fonts found.")); } else { QSize sizeB4(size()); itsButtonBox->setStandardButtons(QDialogButtonBox::Ok|QDialogButtonBox::Close); QPushButton *okButton = itsButtonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); okButton->setText(i18n("Delete Marked Files")); okButton->setEnabled(false); itsLabel->setText(i18np("%1 duplicate font found.", "%1 duplicate fonts found.", duplicates.count())); itsView->show(); CFontFileList::TFontMap::ConstIterator it(duplicates.begin()), end(duplicates.end()); QFont boldFont(font()); boldFont.setBold(true); for(; it!=end; ++it) { QStringList details; details << FC::createName(it.key().family, it.key().styleInfo); CFontFileListView::StyleItem *top=new CFontFileListView::StyleItem(itsView, details, it.key().family, it.key().styleInfo); QSet::ConstIterator fit((*it).begin()), fend((*it).end()); int tt(0), t1(0); for(; fit!=fend; ++fit) { QFileInfo info(*fit); details.clear(); details.append(*fit); details.append(""); details.append(KFormat().formatByteSize(info.size())); details.append(QLocale().toString(info.created())); if(info.isSymLink()) details.append(info.symLinkTarget()); new QTreeWidgetItem(top, details); if(Misc::checkExt(*fit, "pfa") || Misc::checkExt(*fit, "pfb")) t1++; else tt++; } top->setData(COL_FILE, Qt::DecorationRole, QVariant(SmallIcon(t1>tt ? "application-x-font-type1" : "application-x-font-ttf"))); top->setFont(COL_FILE, boldFont); } QTreeWidgetItem *item= nullptr; for(int i=0; (item=itsView->topLevelItem(i)); ++i) item->setExpanded(true); itsView->setSortingEnabled(true); itsView->header()->resizeSections(QHeaderView::ResizeToContents); int width=(itsView->frameWidth()+8)*2 + style()->pixelMetric(QStyle::PM_LayoutLeftMargin) + style()->pixelMetric(QStyle::PM_LayoutRightMargin); for(int i=0; iheader()->count(); ++i) width+=itsView->header()->sectionSize(i); width=qMin(QApplication::desktop()->screenGeometry(this).width(), width); resize(width, height()); QSize sizeNow(size()); if(sizeNow.width()>sizeB4.width()) { int xmod=(sizeNow.width()-sizeB4.width())/2, ymod=(sizeNow.height()-sizeB4.height())/2; move(pos().x()-xmod, pos().y()-ymod); } } } } enum EStatus { STATUS_NO_FILES, STATUS_ALL_REMOVED, STATUS_ERROR, STATUS_USER_CANCELLED }; void CDuplicatesDialog::slotButtonClicked(QAbstractButton *button) { switch(itsButtonBox->standardButton(button)) { case QDialogButtonBox::Ok: { QSet files=itsView->getMarkedFiles(); int fCount=files.count(); if(1==fCount ? KMessageBox::Yes==KMessageBox::warningYesNo(this, - i18n("Are you sure you wish to delete:\n%1", files.toList().first())) + i18n("Are you sure you wish to delete:\n%1", *files.begin())) : KMessageBox::Yes==KMessageBox::warningYesNoList(this, - i18n("Are you sure you wish to delete:"), files.toList())) + i18n("Are you sure you wish to delete:"), files.values())) { itsFontList->setSlowUpdates(true); CJobRunner runner(this); connect(&runner, &CJobRunner::configuring, itsFontList, &CFontList::unsetSlowUpdates); runner.exec(CJobRunner::CMD_REMOVE_FILE, itsView->getMarkedItems(), false); itsFontList->setSlowUpdates(false); itsView->removeFiles(); files=itsView->getMarkedFiles(); if(fCount!=files.count()) CFcEngine::setDirty(); if(0==files.count()) accept(); } break; } case QDialogButtonBox::Cancel: case QDialogButtonBox::Close: if(!itsFontFileList->wasTerminated()) { if(itsFontFileList->isRunning()) { if(KMessageBox::Yes==KMessageBox::warningYesNo(this, i18n("Cancel font scan?"))) { itsLabel->setText(i18n("Canceling...")); if(itsFontFileList->isRunning()) itsFontFileList->terminate(); else reject(); } } else reject(); } break; default: break; } } void CDuplicatesDialog::enableButtonOk(bool on) { QPushButton *okButton = itsButtonBox->button(QDialogButtonBox::Ok); if (okButton) okButton->setEnabled(on); } static uint qHash(const CFontFileList::TFile &key) { return qHash(key.name.toLower()); } CFontFileList::CFontFileList(CDuplicatesDialog *parent) : QThread(parent), itsTerminated(false) { } void CFontFileList::start() { if(!isRunning()) { itsTerminated=false; QThread::start(); } } void CFontFileList::terminate() { itsTerminated=true; } void CFontFileList::getDuplicateFonts(TFontMap &map) { map=itsMap; if(!map.isEmpty()) { TFontMap::Iterator it(map.begin()), end(map.end()); // Now re-iterate, and remove any entries that only have 1 file... for(it=map.begin(); it!=end; ) if((*it).count()<2) it=map.erase(it); else ++it; } } void CFontFileList::run() { const QList &families(((CDuplicatesDialog *)parent())->fontList()->families()); QList::ConstIterator it(families.begin()), end(families.end()); for(; it!=end; ++it) { QList::ConstIterator fontIt((*it)->fonts().begin()), fontEnd((*it)->fonts().end()); for(; fontIt!=fontEnd; ++fontIt) if(!(*fontIt)->isBitmap()) { Misc::TFont font((*fontIt)->family(), (*fontIt)->styleInfo()); FileCont::ConstIterator fileIt((*fontIt)->files().begin()), fileEnd((*fontIt)->files().end()); for(; fileIt!=fileEnd; ++fileIt) if(!Misc::isMetrics((*fileIt).path()) && !Misc::isBitmap((*fileIt).path())) itsMap[font].insert((*fileIt).path()); } } // if we have 2 fonts: /wibble/a.ttf and /wibble/a.TTF fontconfig only returns the 1st, so we // now iterate over fontconfig's list, and look for other matching fonts... if(!itsMap.isEmpty() && !itsTerminated) { // Create a map of folder -> set TFontMap::Iterator it(itsMap.begin()), end(itsMap.end()); QHash > folderMap; for(int n=0; it!=end && !itsTerminated; ++it) { QStringList add; QSet::const_iterator fIt((*it).begin()), fEnd((*it).end()); for(; fIt!=fEnd && !itsTerminated; ++fIt, ++n) folderMap[Misc::getDir(*fIt)].insert(TFile(Misc::getFile(*fIt), it)); } // Go through our folder map, and check for file duplicates... QHash >::Iterator folderIt(folderMap.begin()), folderEnd(folderMap.end()); for(; folderIt!=folderEnd && !itsTerminated; ++folderIt) fileDuplicates(folderIt.key(), *folderIt); } emit finished(); } void CFontFileList::fileDuplicates(const QString &folder, const QSet &files) { QDir dir(folder); dir.setFilter(QDir::Files|QDir::Hidden); QFileInfoList list(dir.entryInfoList()); for (int i = 0; i < list.size() && !itsTerminated; ++i) { QFileInfo fileInfo(list.at(i)); // Check if this file is already know about - this will do a case-sensitive comparison if(!files.contains(TFile(fileInfo.fileName()))) { // OK, not found - this means it is a duplicate, but different case. So, find the // FontMap iterator, and update its list of files. QSet::ConstIterator entry=files.find(TFile(fileInfo.fileName(), true)); if(entry!=files.end()) (*((*entry).it)).insert(fileInfo.absoluteFilePath()); } } } inline void markItem(QTreeWidgetItem *item) { item->setData(COL_TRASH, Qt::DecorationRole, QVariant(SmallIcon("list-remove"))); } inline void unmarkItem(QTreeWidgetItem *item) { item->setData(COL_TRASH, Qt::DecorationRole, QVariant()); } inline bool isMarked(QTreeWidgetItem *item) { return item->data(COL_TRASH, Qt::DecorationRole).isValid(); } CFontFileListView::CFontFileListView(QWidget *parent) : QTreeWidget(parent) { QStringList headers; headers.append(i18n("Font/File")); headers.append(""); headers.append(i18n("Size")); headers.append(i18n("Date")); headers.append(i18n("Links To")); setHeaderLabels(headers); headerItem()->setData(COL_TRASH, Qt::DecorationRole, QVariant(SmallIcon("user-trash"))); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); setSelectionMode(ExtendedSelection); sortByColumn(COL_FILE, Qt::AscendingOrder); setSelectionBehavior(SelectRows); setSortingEnabled(true); setAllColumnsShowFocus(true); setAlternatingRowColors(true); itsMenu=new QMenu(this); if(!Misc::app(KFI_VIEWER).isEmpty()) itsMenu->addAction(QIcon::fromTheme("kfontview"), i18n("Open in Font Viewer"), this, &CFontFileListView::openViewer); itsMenu->addAction(QIcon::fromTheme("document-properties"), i18n("Properties"), this, &CFontFileListView::properties); itsMenu->addSeparator(); itsUnMarkAct=itsMenu->addAction(i18n("Unmark for Deletion"), this, &CFontFileListView::unmark); itsMarkAct=itsMenu->addAction(QIcon::fromTheme("edit-delete"), i18n("Mark for Deletion"), this, &CFontFileListView::mark); connect(this, SIGNAL(itemSelectionChanged()), SLOT(selectionChanged())); connect(this, SIGNAL(itemClicked(QTreeWidgetItem*,int)), SLOT(clicked(QTreeWidgetItem*,int))); } QSet CFontFileListView::getMarkedFiles() { QTreeWidgetItem *root=invisibleRootItem(); QSet files; for(int t=0; tchildCount(); ++t) { QList removeFiles; QTreeWidgetItem *font=root->child(t); for(int c=0; cchildCount(); ++c) { QTreeWidgetItem *file=font->child(c); if(isMarked(file)) files.insert(file->text(0)); } } return files; } CJobRunner::ItemList CFontFileListView::getMarkedItems() { QTreeWidgetItem *root=invisibleRootItem(); CJobRunner::ItemList items; QString home(Misc::dirSyntax(QDir::homePath())); for(int t=0; tchildCount(); ++t) { QList removeFiles; StyleItem *style=(StyleItem *)root->child(t); for(int c=0; cchildCount(); ++c) { QTreeWidgetItem *file=style->child(c); if(isMarked(file)) items.append(CJobRunner::Item(file->text(0), style->family(), style->value(), 0!=file->text(0).indexOf(home))); } } return items; } void CFontFileListView::removeFiles() { QTreeWidgetItem *root=invisibleRootItem(); QList removeFonts; for(int t=0; tchildCount(); ++t) { QList removeFiles; QTreeWidgetItem *font=root->child(t); for(int c=0; cchildCount(); ++c) { QTreeWidgetItem *file=font->child(c); if(!Misc::fExists(file->text(0))) removeFiles.append(file); } QList::ConstIterator it(removeFiles.begin()), end(removeFiles.end()); for(; it!=end; ++it) delete (*it); if(0==font->childCount()) removeFonts.append(font); } QList::ConstIterator it(removeFonts.begin()), end(removeFonts.end()); for(; it!=end; ++it) delete (*it); } void CFontFileListView::openViewer() { // Number of fonts user has selected, before we ask if they really want to view them all... static const int constMaxBeforePrompt=10; QList items(selectedItems()); QTreeWidgetItem *item; QSet files; foreach(item, items) if(item->parent()) // Then it is a file, not font name :-) files.insert(item->text(0)); if(!files.isEmpty() && (files.count()::ConstIterator it(files.begin()), end(files.end()); for(; it!=end; ++it) { QStringList args; args << (*it); QProcess::startDetached(Misc::app(KFI_VIEWER), args); } } } void CFontFileListView::properties() { QList items(selectedItems()); QTreeWidgetItem *item; KFileItemList files; QMimeDatabase db; foreach(item, items) if(item->parent()) files.append(KFileItem(QUrl::fromLocalFile(item->text(0)), db.mimeTypeForFile(item->text(0)).name(), item->text(COL_LINK).isEmpty() ? S_IFREG : S_IFLNK)); if(!files.isEmpty()) { KPropertiesDialog dlg(files, this); dlg.exec(); } } void CFontFileListView::mark() { QList items(selectedItems()); QTreeWidgetItem *item; foreach(item, items) if(item->parent()) markItem(item); checkFiles(); } void CFontFileListView::unmark() { QList items(selectedItems()); QTreeWidgetItem *item; foreach(item, items) if(item->parent()) unmarkItem(item); checkFiles(); } void CFontFileListView::selectionChanged() { QList items(selectedItems()); QTreeWidgetItem *item; foreach(item, items) if(!item->parent() && item->isSelected()) item->setSelected(false); } void CFontFileListView::clicked(QTreeWidgetItem *item, int col) { if(item && COL_TRASH==col && item->parent()) { if(isMarked(item)) unmarkItem(item); else markItem(item); checkFiles(); } } void CFontFileListView::contextMenuEvent(QContextMenuEvent *ev) { QTreeWidgetItem *item(itemAt(ev->pos())); if(item && item->parent()) { if(!item->isSelected()) item->setSelected(true); bool haveUnmarked(false), haveMarked(false); QList items(selectedItems()); QTreeWidgetItem *item; foreach(item, items) { if(item->parent() && item->isSelected()) { if(isMarked(item)) haveMarked=true; else haveUnmarked=true; } if(haveUnmarked && haveMarked) break; } itsMarkAct->setEnabled(haveUnmarked); itsUnMarkAct->setEnabled(haveMarked); itsMenu->popup(ev->globalPos()); } } void CFontFileListView::checkFiles() { // Need to check that if we mark a file that is linked to, then we also need // to mark the sym link. QSet marked(getMarkedFiles()); if(marked.count()) { QTreeWidgetItem *root=invisibleRootItem(); for(int t=0; tchildCount(); ++t) { QTreeWidgetItem *font=root->child(t); for(int c=0; cchildCount(); ++c) { QTreeWidgetItem *file=font->child(c); QString link(font->child(c)->text(COL_LINK)); if(!link.isEmpty() && marked.contains(link)) if(!isMarked(file)) markItem(file); } } emit haveDeletions(true); } else emit haveDeletions(false); } } diff --git a/kcms/kfontinst/kcmfontinst/FontList.cpp b/kcms/kfontinst/kcmfontinst/FontList.cpp index dc818308a..5b18911c7 100644 --- a/kcms/kfontinst/kcmfontinst/FontList.cpp +++ b/kcms/kfontinst/kcmfontinst/FontList.cpp @@ -1,2060 +1,2060 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "FontList.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "FcEngine.h" #include "Fc.h" #include "KfiConstants.h" #include "GroupList.h" #include "FontInstInterface.h" #include "XmlStrings.h" #include "Family.h" #include "Style.h" #include "File.h" namespace KFI { const QStringList CFontList::fontMimeTypes(QStringList() << "font/ttf" << "font/otf" << "application/x-font-ttf" << "application/x-font-otf" << "application/x-font-type1" << "application/x-font-pcf" << "application/x-font-bdf" << "application/vnd.kde.fontspackage"); static const int constMaxSlowed = 250; static void decompose(const QString &name, QString &family, QString &style) { int commaPos=name.lastIndexOf(','); family=-1==commaPos ? name : name.left(commaPos); style=-1==commaPos ? KFI_WEIGHT_REGULAR : name.mid(commaPos+2); } static void addFont(CFontItem *font, CJobRunner::ItemList &urls, QStringList &fontNames, QSet *fonts, QSet &usedFonts, bool getEnabled, bool getDisabled) { if(!usedFonts.contains(font) && ( (getEnabled && font->isEnabled()) || (getDisabled && !font->isEnabled()) ) ) { urls.append(CJobRunner::Item(font->url(), font->name(), !font->isEnabled())); fontNames.append(font->name()); usedFonts.insert(font); if(fonts) fonts->insert(Misc::TFont(font->family(), font->styleInfo())); } } static QString replaceEnvVar(const QString &text) { QString mod(text); int endPos(text.indexOf('/')); if(endPos==-1) endPos=text.length()-1; else endPos--; if(endPos>0) { QString envVar(text.mid(1, endPos)); const char *val=getenv(envVar.toLatin1().constData()); if(val) mod=Misc::fileSyntax(QFile::decodeName(val)+mod.mid(endPos+1)); } return mod; } // // Convert from list such as: // // Arial // Arial, Bold // Courier // Times // Times, Italic // // To: // // Arial (Regular, Bold) // Coutier // Times (Regular, Italic) QStringList CFontList::compact(const QStringList &fonts) { QString lastFamily, entry; QStringList::ConstIterator it(fonts.begin()), end(fonts.end()); QStringList compacted; QSet usedStyles; for(; it!=end; ++it) { QString family, style; decompose(*it, family, style); if(family!=lastFamily) { usedStyles.clear(); if(entry.length()) { entry+=')'; compacted.append(entry); } entry=QString(family+" ("); lastFamily=family; } if(!usedStyles.contains(style)) { usedStyles.clear(); if(entry.length() && '('!=entry[entry.length()-1]) entry+=", "; entry+=style; usedStyles.insert(style); } } if(entry.length()) { entry+=')'; compacted.append(entry); } return compacted; } QString capitaliseFoundry(const QString &foundry) { QString f(foundry.toLower()); if(f==QLatin1String("ibm")) return QLatin1String("IBM"); else if(f==QLatin1String("urw")) return QLatin1String("URW"); else if(f==QLatin1String("itc")) return QLatin1String("ITC"); else if(f==QLatin1String("nec")) return QLatin1String("NEC"); else if(f==QLatin1String("b&h")) return QLatin1String("B&H"); else if(f==QLatin1String("dec")) return QLatin1String("DEC"); else { QChar *ch(f.data()); int len(f.length()); bool isSpace(true); while(len--) { if (isSpace) *ch=ch->toUpper(); isSpace=ch->isSpace(); ++ch; } } return f; } inline bool isSysFolder(const QString §) { return i18n(KFI_KIO_FONTS_SYS)==sect || KFI_KIO_FONTS_SYS==sect; } CFontItem::CFontItem(CFontModelItem *p, const Style &s, bool sys) : CFontModelItem(p), itsStyleName(FC::createStyleName(s.value())), itsStyle(s) { refresh(); if(!Misc::root()) setIsSystem(sys); } void CFontItem::refresh() { FileCont::ConstIterator it(itsStyle.files().begin()), end(itsStyle.files().end()); itsEnabled=false; for(; it!=end; ++it) if(!Misc::isHidden(Misc::getFile((*it).path()))) { itsEnabled=true; break; } } CFamilyItem::CFamilyItem(CFontList &p, const Family &f, bool sys) : CFontModelItem(nullptr), itsStatus(ENABLED), itsRealStatus(ENABLED), itsRegularFont(nullptr), itsParent(p) { itsName=f.name(); addFonts(f.styles(), sys); //updateStatus(); } CFamilyItem::~CFamilyItem() { qDeleteAll(itsFonts); itsFonts.clear(); } bool CFamilyItem::addFonts(const StyleCont &styles, bool sys) { StyleCont::ConstIterator it(styles.begin()), end(styles.end()); bool modified=false; for(; it!=end; ++it) { CFontItem *font=findFont((*it).value(), sys); if(!font) { // New font style! itsFonts.append(new CFontItem(this, *it, sys)); modified=true; } else { int before=(*it).files().size(); font->addFiles((*it).files()); if((*it).files().size()!=before) { modified=true; font->refresh(); } } } return modified; } CFontItem * CFamilyItem::findFont(quint32 style, bool sys) { CFontItemCont::ConstIterator fIt(itsFonts.begin()), fEnd(itsFonts.end()); for(; fIt!=fEnd; ++fIt) if((*(*fIt)).styleInfo()==style && (*(*fIt)).isSystem()==sys) return (*fIt); return nullptr; } void CFamilyItem::getFoundries(QSet &foundries) const { CFontItemCont::ConstIterator it(itsFonts.begin()), end(itsFonts.end()); for(; it!=end; ++it) { FileCont::ConstIterator fIt((*it)->files().begin()), fEnd((*it)->files().end()); for(; fIt!=fEnd; ++fIt) if(!(*fIt).foundry().isEmpty()) foundries.insert(capitaliseFoundry((*fIt).foundry())); } } bool CFamilyItem::usable(const CFontItem *font, bool root) { return ( root || (font->isSystem() && itsParent.allowSys()) || (!font->isSystem() && itsParent.allowUser())); } void CFamilyItem::addFont(CFontItem *font, bool update) { itsFonts.append(font); if(update) { updateStatus(); updateRegularFont(font); } } void CFamilyItem::removeFont(CFontItem *font, bool update) { itsFonts.removeAll(font); if(update) updateStatus(); if(itsRegularFont==font) { itsRegularFont=nullptr; if(update) updateRegularFont(nullptr); } delete font; } void CFamilyItem::refresh() { updateStatus(); itsRegularFont=nullptr; updateRegularFont(nullptr); } bool CFamilyItem::updateStatus() { bool root(Misc::root()); EStatus oldStatus(itsStatus); CFontItemCont::ConstIterator it(itsFonts.begin()), end(itsFonts.end()); int en(0), dis(0), allEn(0), allDis(0); bool oldSys(isSystem()), sys(false); itsFontCount=0; for(; it!=end; ++it) if(usable(*it, root)) { if((*it)->isEnabled()) en++; else dis++; if(!sys) sys=(*it)->isSystem(); itsFontCount++; } else if((*it)->isEnabled()) allEn++; else allDis++; allEn+=en; allDis+=dis; itsStatus=en && dis ? PARTIAL : en ? ENABLED : DISABLED; itsRealStatus=allEn && allDis ? PARTIAL : allEn ? ENABLED : DISABLED; if(!root) setIsSystem(sys); return itsStatus!=oldStatus || isSystem()!=oldSys; } bool CFamilyItem::updateRegularFont(CFontItem *font) { static const quint32 constRegular=FC::createStyleVal(FC_WEIGHT_REGULAR, KFI_FC_WIDTH_NORMAL, FC_SLANT_ROMAN); CFontItem *oldFont(itsRegularFont); bool root(Misc::root()); if(font && usable(font, root)) { if(itsRegularFont) { int regDiff=abs((long)(itsRegularFont->styleInfo()-constRegular)), fontDiff=abs((long)(font->styleInfo()-constRegular)); if(fontDiffstyleInfo()-constRegular)); if(diff &families) { // printf("**** fontList:%d/%d %d\n", pid, getpid(), families.count()); if(pid==getpid()) { QList::ConstIterator it(families.begin()), end(families.end()); int count(families.size()); for(int i=0; it!=end; ++it, ++i) { fontsAdded(*it); emit listingPercent(i*100/count); } emit listingPercent(100); } } void CFontList::unsetSlowUpdates() { setSlowUpdates(false); } void CFontList::load() { for(int t=0; tlist(FontInst::SYS_MASK|FontInst::USR_MASK, getpid()); } void CFontList::setSlowUpdates(bool slow) { if(itsSlowUpdates!=slow) { if(!slow) for(int i=0; i families; QDataStream ds(&encodedData, QIODevice::WriteOnly); for(; it!=end; ++it) if((*it).isValid()) { if((static_cast((*it).internalPointer()))->isFont()) { CFontItem *font=static_cast((*it).internalPointer()); families.insert(font->family()); } else { CFamilyItem *fam=static_cast((*it).internalPointer()); families.insert(fam->name()); } } ds << families; mimeData->setData(KFI_FONT_DRAG_MIME, encodedData); return mimeData; } QStringList CFontList::mimeTypes() const { QStringList types; types << "text/uri-list"; return types; } QVariant CFontList::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) switch(role) { case Qt::DisplayRole: switch(section) { case COL_FONT: return i18n("Font"); case COL_STATUS: return i18n("Status"); default: break; } break; // case Qt::DecorationRole: // if(COL_STATUS==section) // return QIcon::fromTheme("fontstatus"); // break; case Qt::TextAlignmentRole: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); case Qt::ToolTipRole: if(COL_STATUS==section) return i18n("This column shows the status of the font family, and of the " "individual font styles."); break; case Qt::WhatsThisRole: return whatsThis(); default: break; } return QVariant(); } QModelIndex CFontList::index(int row, int column, const QModelIndex &parent) const { if(parent.isValid()) // Then font... { CFamilyItem *fam=static_cast(parent.internalPointer()); if(rowfonts().count()) return createIndex(row, column, fam->fonts().at(row)); } else // Family.... if(row(index.internalPointer()); if(mi->isFamily()) return QModelIndex(); else { CFontItem *font=static_cast(index.internalPointer()); return createIndex(itsFamilies.indexOf(((CFamilyItem *)font->parent())), 0, font->parent()); } } int CFontList::rowCount(const QModelIndex &parent) const { if(parent.isValid()) { CFontModelItem *mi=static_cast(parent.internalPointer()); if(mi->isFont()) return 0; CFamilyItem *fam=static_cast(parent.internalPointer()); return fam->fonts().count(); } else return itsFamilies.count(); } void CFontList::refresh(bool allowSys, bool allowUser) { itsAllowSys=allowSys; itsAllowUser=allowUser; CFamilyItemCont::ConstIterator it(itsFamilies.begin()), end(itsFamilies.end()); for(; it!=end; ++it) (*it)->refresh(); } void CFontList::getFamilyStats(QSet &enabled, QSet &disabled, QSet &partial) { CFamilyItemCont::ConstIterator it(itsFamilies.begin()), end(itsFamilies.end()); for(; it!=end; ++it) { switch((*it)->realStatus()) { case CFamilyItem::ENABLED: enabled.insert((*it)->name()); break; case CFamilyItem::PARTIAL: partial.insert((*it)->name()); break; case CFamilyItem::DISABLED: disabled.insert((*it)->name()); break; } } } void CFontList::getFoundries(QSet &foundries) const { CFamilyItemCont::ConstIterator it(itsFamilies.begin()), end(itsFamilies.end()); for(; it!=end; ++it) (*it)->getFoundries(foundries); } QString CFontList::whatsThis() const { return i18n("

This list shows your installed fonts. The fonts are grouped by family, and the" " number in square brackets represents the number of styles in which the family is" " available. e.g.

" "
    " "
  • Times [4]" "
    • Regular
    • " "
    • Bold
    • " "
    • Bold Italic
    • " "
    • Italic
    • " "
    " "
  • " "
"); } void CFontList::fontsAdded(const KFI::Families &families) { // printf("**** FONT ADDED:%d\n", families.items.count()); if(itsSlowUpdates) storeSlowedMessage(families, MSG_ADD); else addFonts(families.items, families.isSystem && !Misc::root()); } void CFontList::fontsRemoved(const KFI::Families &families) { // printf("**** FONT REMOVED:%d\n", families.items.count()); if(itsSlowUpdates) storeSlowedMessage(families, MSG_DEL); else removeFonts(families.items, families.isSystem && !Misc::root()); } void CFontList::storeSlowedMessage(const Families &families, EMsgType type) { int folder=families.isSystem ? FontInst::FOLDER_SYS : FontInst::FOLDER_USER; bool playOld=false; for(int i=0; iconstMaxSlowed) playOld=true; if(playOld) actionSlowedUpdates(families.isSystem); FamilyCont::ConstIterator family(families.items.begin()), fend(families.items.end()); for(; family!=fend; ++family) { FamilyCont::ConstIterator f=itsSlowedMsgs[type][folder].find(*family); if(f!=itsSlowedMsgs[type][folder].end()) { StyleCont::ConstIterator style((*family).styles().begin()), send((*family).styles().end()); for(; style!=send; ++style) { StyleCont::ConstIterator st=(*f).styles().find(*style); if(st==(*f).styles().end()) (*f).add(*style); else (*st).addFiles((*style).files()); } } else itsSlowedMsgs[type][folder].insert(*family); } } void CFontList::actionSlowedUpdates(bool sys) { int folder=sys ? FontInst::FOLDER_SYS : FontInst::FOLDER_USER; for(int i=0; i modifiedFamilies; for(; family!=end; ++family) { if((*family).styles().count()>0) { CFamilyItem *famItem=findFamily((*family).name()); if(famItem) { int rowFrom=famItem->fonts().count(); if(famItem->addFonts((*family).styles(), sys)) { int rowTo=famItem->fonts().count(); if(rowTo!=rowFrom) { beginInsertRows(createIndex(famItem->rowNumber(), 0, famItem), rowFrom, rowTo); endInsertRows(); } modifiedFamilies.insert(famItem); } } else { famItem=new CFamilyItem(*this, *family, sys); itsFamilies.append(famItem); itsFamilyHash[famItem->name()]=famItem; modifiedFamilies.insert(famItem); } } } int famRowTo=itsFamilies.count(); if(famRowTo!=famRowFrom) { beginInsertRows(QModelIndex(), famRowFrom, famRowTo); endInsertRows(); } if(!modifiedFamilies.isEmpty()) { QSet::Iterator it(modifiedFamilies.begin()), end(modifiedFamilies.end()); for(; it!=end; ++it) (*it)->refresh(); } // if(emitLayout) // emit layoutChanged(); } void CFontList::removeFonts(const FamilyCont &families, bool sys) { // if(!itsSlowUpdates) // emit layoutAboutToBeChanged(); FamilyCont::ConstIterator family(families.begin()), end(families.end()); QSet modifiedFamilies; for(; family!=end; ++family) { if((*family).styles().count()>0) { CFamilyItem *famItem=findFamily((*family).name()); if(famItem) { StyleCont::ConstIterator it((*family).styles().begin()), end((*family).styles().end()); for(; it!=end; ++it) { CFontItem *fontItem=famItem->findFont((*it).value(), sys); if(fontItem) { int before=fontItem->files().count(); fontItem->removeFiles((*it).files()); if(fontItem->files().count()!=before) { if(fontItem->files().isEmpty()) { int row=-1; if(1!=famItem->fonts().count()) { row=fontItem->rowNumber(); beginRemoveRows(createIndex(famItem->rowNumber(), 0, famItem), row, row); } famItem->removeFont(fontItem, false); if(-1!=row) endRemoveRows(); } else fontItem->refresh(); } } } if(famItem->fonts().isEmpty()) { int row=famItem->rowNumber(); beginRemoveRows(QModelIndex(), row, row); itsFamilyHash.remove(famItem->name()); itsFamilies.removeAt(row); endRemoveRows(); } else modifiedFamilies.insert(famItem); } } } if(!modifiedFamilies.isEmpty()) { QSet::Iterator it(modifiedFamilies.begin()), end(modifiedFamilies.end()); for(; it!=end; ++it) (*it)->refresh(); } // if(!itsSlowUpdates) // emit layoutChanged(); } CFamilyItem * CFontList::findFamily(const QString &familyName) { CFamilyItemHash::Iterator it=itsFamilyHash.find(familyName); return it==itsFamilyHash.end() ? nullptr : *it; } inline bool matchString(const QString &str, const QString &pattern) { return pattern.isEmpty() || -1!=str.indexOf(pattern, 0, Qt::CaseInsensitive); } CFontListSortFilterProxy::CFontListSortFilterProxy(QObject *parent, QAbstractItemModel *model) : QSortFilterProxyModel(parent), itsGroup(nullptr), itsFilterCriteria(CFontFilter::CRIT_FAMILY), itsFilterWs(0), itsFcQuery(nullptr) { setSourceModel(model); setSortCaseSensitivity(Qt::CaseInsensitive); setFilterKeyColumn(0); setDynamicSortFilter(false); itsTimer=new QTimer(this); connect(itsTimer, &QTimer::timeout, this, &CFontListSortFilterProxy::timeout); connect(model, &QAbstractItemModel::layoutChanged, this, &QSortFilterProxyModel::invalidate); itsTimer->setSingleShot(true); } QVariant CFontListSortFilterProxy::data(const QModelIndex &idx, int role) const { if (!idx.isValid()) return QVariant(); static const int constMaxFiles=20; QModelIndex index(mapToSource(idx)); CFontModelItem *mi=static_cast(index.internalPointer()); if(!mi) return QVariant(); switch(role) { case Qt::ToolTipRole: if(CFontFilter::CRIT_FILENAME==itsFilterCriteria || CFontFilter::CRIT_LOCATION==itsFilterCriteria || CFontFilter::CRIT_FONTCONFIG==itsFilterCriteria) { if(mi->isFamily()) { CFamilyItem *fam=static_cast(index.internalPointer()); CFontItemCont::ConstIterator it(fam->fonts().begin()), end(fam->fonts().end()); FileCont allFiles; QString tip(""+fam->name()+""); bool markMatch(CFontFilter::CRIT_FONTCONFIG==itsFilterCriteria); tip+="

"; for(; it!=end; ++it) allFiles+=(*it)->files(); //qSort(allFiles); FileCont::ConstIterator fit(allFiles.begin()), fend(allFiles.end()); for(int i=0; fit!=fend && ifile()) tip+=""; else tip+=""; if(allFiles.count()>constMaxFiles) tip+=""; tip+="
"+Misc::contractHome((*fit).path())+"
"+Misc::contractHome((*fit).path())+"
"+i18n("...plus %1 more", allFiles.count()-constMaxFiles)+"

"; return tip; } else { CFontItem *font=static_cast(index.internalPointer()); QString tip(""+font->name()+""); const FileCont &files(font->files()); bool markMatch(CFontFilter::CRIT_FONTCONFIG==itsFilterCriteria); tip+="

"; //qSort(files); FileCont::ConstIterator fit(files.begin()), fend(files.end()); for(int i=0; fit!=fend && ifile()) tip+=""; else tip+=""; if(files.count()>constMaxFiles) tip+=""; tip+="
"+Misc::contractHome((*fit).path())+"
"+Misc::contractHome((*fit).path() )+"
"+i18n("...plus %1 more", files.count()-constMaxFiles)+"

"; return tip; } } break; case Qt::FontRole: if(COL_FONT==index.column() && mi->isSystem()) { QFont font; font.setItalic(true); return font; } break; case Qt::ForegroundRole: if(COL_FONT==index.column() && ( (mi->isFont() && !(static_cast(index.internalPointer()))->isEnabled()) || (mi->isFamily() && CFamilyItem::DISABLED==(static_cast(index.internalPointer()))->status())) ) return KColorScheme(QPalette::Active).foreground(KColorScheme::NegativeText).color(); break; case Qt::DisplayRole: if(COL_FONT==index.column()) { if(mi->isFamily()) { CFamilyItem *fam=static_cast(index.internalPointer()); return i18n("%1 [%2]", fam->name(), fam->fontCount()); } else return (static_cast(index.internalPointer()))->style(); } break; case Qt::DecorationRole: if(mi->isFamily()) { CFamilyItem *fam=static_cast(index.internalPointer()); switch(index.column()) { case COL_STATUS: switch(fam->status()) { case CFamilyItem::PARTIAL: return QIcon::fromTheme("dialog-ok"); case CFamilyItem::ENABLED: return QIcon::fromTheme("dialog-ok"); case CFamilyItem::DISABLED: return QIcon::fromTheme("dialog-cancel"); } break; default: break; } } else if(COL_STATUS==index.column()) return QIcon::fromTheme( (static_cast(index.internalPointer()))->isEnabled() ? "dialog-ok" : "dialog-cancel"); break; case Qt::SizeHintRole: if(mi->isFamily()) { const int s = KIconLoader::global()->currentSize(KIconLoader::Small); return QSize(s, s + 4); } default: break; } return QVariant(); } bool CFontListSortFilterProxy::acceptFont(CFontItem *fnt, bool checkFontText) const { if(itsGroup && (CGroupListItem::ALL!=itsGroup->type() || (!filterText().isEmpty() && checkFontText))) { bool fontMatch(!checkFontText); if(!fontMatch) switch(itsFilterCriteria) { case CFontFilter::CRIT_FONTCONFIG: fontMatch=itsFcQuery ? fnt->name()==itsFcQuery->font() // || fnt->files().contains(itsFcQuery->file()) : false; break; case CFontFilter::CRIT_STYLE: fontMatch=matchString(fnt->style(), itsFilterText); break; case CFontFilter::CRIT_FOUNDRY: { FileCont::ConstIterator it(fnt->files().begin()), end(fnt->files().end()); for(; it!=end && !fontMatch; ++it) fontMatch=0==(*it).foundry().compare(itsFilterText, Qt::CaseInsensitive); break; } case CFontFilter::CRIT_FILENAME: { FileCont::ConstIterator it(fnt->files().begin()), end(fnt->files().end()); for(; it!=end && !fontMatch; ++it) { QString file(Misc::getFile((*it).path())); int pos(Misc::isHidden(file) ? 1 : 0); if(pos==file.indexOf(itsFilterText, pos, Qt::CaseInsensitive)) fontMatch=true; } break; } case CFontFilter::CRIT_LOCATION: { FileCont::ConstIterator it(fnt->files().begin()), end(fnt->files().end()); for(; it!=end && !fontMatch; ++it) if(0==Misc::getDir((*it).path()).indexOf(itsFilterText, 0, Qt::CaseInsensitive)) fontMatch=true; break; } case CFontFilter::CRIT_FILETYPE: { FileCont::ConstIterator it(fnt->files().begin()), end(fnt->files().end()); QStringList::ConstIterator mimeEnd(itsFilterTypes.constEnd()); for(; it!=end && !fontMatch; ++it) { QStringList::ConstIterator mime(itsFilterTypes.constBegin()); for(; mime!=mimeEnd; ++mime) if(Misc::checkExt((*it).path(), *mime)) fontMatch=true; } break; } case CFontFilter::CRIT_WS: fontMatch=fnt->writingSystems()&itsFilterWs; break; default: break; } return fontMatch && itsGroup->hasFont(fnt); } return true; } bool CFontListSortFilterProxy::acceptFamily(CFamilyItem *fam) const { CFontItemCont::ConstIterator it(fam->fonts().begin()), end(fam->fonts().end()); bool familyMatch(CFontFilter::CRIT_FAMILY==itsFilterCriteria && matchString(fam->name(), itsFilterText)); for(; it!=end; ++it) if(acceptFont(*it, !familyMatch)) return true; return false; } bool CFontListSortFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex index(sourceModel()->index(sourceRow, 0, sourceParent)); if(index.isValid()) { CFontModelItem *mi=static_cast(index.internalPointer()); if(mi->isFont()) { CFontItem *font=static_cast(index.internalPointer()); return acceptFont(font, !(CFontFilter::CRIT_FAMILY==itsFilterCriteria && matchString(font->family(), itsFilterText))); } else return acceptFamily(static_cast(index.internalPointer())); } return false; } bool CFontListSortFilterProxy::lessThan(const QModelIndex &left, const QModelIndex &right) const { if(left.isValid() && right.isValid()) { CFontModelItem *lmi=static_cast(left.internalPointer()), *rmi=static_cast(right.internalPointer()); if(lmi->isFont()isFont()) return true; if(lmi->isFont()) { CFontItem *lfi=static_cast(left.internalPointer()), *rfi=static_cast(right.internalPointer()); if(COL_STATUS==filterKeyColumn()) { if(lfi->isEnabled()isEnabled() || (lfi->isEnabled()==rfi->isEnabled() && lfi->styleInfo()styleInfo())) return true; } else if(lfi->styleInfo()styleInfo()) return true; } else { CFamilyItem *lfi=static_cast(left.internalPointer()), *rfi=static_cast(right.internalPointer()); if(COL_STATUS==filterKeyColumn()) { if(lfi->status()status() || (lfi->status()==rfi->status() && QString::localeAwareCompare(lfi->name(), rfi->name())<0)) return true; } else if(QString::localeAwareCompare(lfi->name(), rfi->name())<0) return true; } } return false; } void CFontListSortFilterProxy::setFilterGroup(CGroupListItem *grp) { if(grp!=itsGroup) { // bool wasNull=!itsGroup; itsGroup=grp; // if(!(wasNull && itsGroup && CGroupListItem::ALL==itsGroup->type())) invalidate(); } } void CFontListSortFilterProxy::setFilterText(const QString &text) { if(text!=itsFilterText) { // // If we are filtering on file location, then expand ~ to /home/user, etc. if (CFontFilter::CRIT_LOCATION==itsFilterCriteria && !text.isEmpty() && ('~'==text[0] || '$'==text[0])) if('~'==text[0]) itsFilterText=1==text.length() ? QDir::homePath() : QString(text).replace(0, 1, QDir::homePath()); else itsFilterText=replaceEnvVar(text); else itsFilterText=text; if(itsFilterText.isEmpty()) { itsTimer->stop(); timeout(); } else itsTimer->start(CFontFilter::CRIT_FONTCONFIG==itsFilterCriteria ? 750 : 400); } } void CFontListSortFilterProxy::setFilterCriteria(CFontFilter::ECriteria crit, qulonglong ws, const QStringList &ft) { if(crit!=itsFilterCriteria || ws!=itsFilterWs || ft!=itsFilterTypes) { itsFilterWs=ws; itsFilterCriteria=crit; itsFilterTypes=ft; if(CFontFilter::CRIT_LOCATION==itsFilterCriteria) setFilterText(itsFilterText); itsTimer->stop(); timeout(); } } void CFontListSortFilterProxy::timeout() { if(CFontFilter::CRIT_FONTCONFIG==itsFilterCriteria) { int commaPos=itsFilterText.indexOf(','); QString query(itsFilterText); if(-1!=commaPos) { QString style(query.mid(commaPos+1)); query.truncate(commaPos); query=query.trimmed(); query+=":style="; style=style.trimmed(); query+=style; } else query=query.trimmed(); if(!itsFcQuery) { itsFcQuery=new CFcQuery(this); connect(itsFcQuery, &CFcQuery::finished, this, &CFontListSortFilterProxy::fcResults); } itsFcQuery->run(query); } else { invalidate(); emit refresh(); } } void CFontListSortFilterProxy::fcResults() { if(CFontFilter::CRIT_FONTCONFIG==itsFilterCriteria) { invalidate(); emit refresh(); } } CFontListView::CFontListView(QWidget *parent, CFontList *model) : QTreeView(parent), itsProxy(new CFontListSortFilterProxy(this, model)), itsModel(model), itsAllowDrops(false) { setModel(itsProxy); itsModel=model; header()->setStretchLastSection(false); resizeColumnToContents(COL_STATUS); header()->setSectionResizeMode(COL_STATUS, QHeaderView::Fixed); header()->setSectionResizeMode(COL_FONT, QHeaderView::Stretch); setSelectionMode(QAbstractItemView::ExtendedSelection); setSelectionBehavior(QAbstractItemView::SelectRows); setSortingEnabled(true); sortByColumn(COL_FONT, Qt::AscendingOrder); setAllColumnsShowFocus(true); setAlternatingRowColors(true); setAcceptDrops(true); setDropIndicatorShown(false); setDragEnabled(true); setDragDropMode(QAbstractItemView::DragDrop); header()->setSectionsClickable(true); header()->setSortIndicatorShown(true); connect(this, &QTreeView::collapsed, this, &CFontListView::itemCollapsed); connect(header(), &QHeaderView::sectionClicked, this, &CFontListView::setSortColumn); connect(itsProxy, &CFontListSortFilterProxy::refresh, this, &CFontListView::refresh); connect(itsModel, &CFontList::listingPercent, this, &CFontListView::listingPercent); setWhatsThis(model->whatsThis()); header()->setWhatsThis(whatsThis()); itsMenu=new QMenu(this); itsDeleteAct=itsMenu->addAction(QIcon::fromTheme("edit-delete"), i18n("Delete"), this, &CFontListView::del); itsMenu->addSeparator(); itsEnableAct=itsMenu->addAction(QIcon::fromTheme("font-enable"), i18n("Enable"), this, &CFontListView::enable); itsDisableAct=itsMenu->addAction(QIcon::fromTheme("font-disable"), i18n("Disable"), this, &CFontListView::disable); if(!Misc::app(KFI_VIEWER).isEmpty()) itsMenu->addSeparator(); itsPrintAct=Misc::app(KFI_VIEWER).isEmpty() ? nullptr : itsMenu->addAction(QIcon::fromTheme("document-print"), i18n("Print..."), this, &CFontListView::print); itsViewAct=Misc::app(KFI_VIEWER).isEmpty() ? nullptr : itsMenu->addAction(QIcon::fromTheme("kfontview"), i18n("Open in Font Viewer"), this, &CFontListView::view); itsMenu->addSeparator(); itsMenu->addAction(QIcon::fromTheme("view-refresh"), i18n("Reload"), model, &CFontList::load); } void CFontListView::getFonts(CJobRunner::ItemList &urls, QStringList &fontNames, QSet *fonts, bool selected, bool getEnabled, bool getDisabled) { QModelIndexList selectedItems(selected ? selectedIndexes() : allIndexes()); QSet usedFonts; QModelIndex index; foreach(index, selectedItems) if(index.isValid()) { QModelIndex realIndex(itsProxy->mapToSource(index)); if(realIndex.isValid()) { if((static_cast(realIndex.internalPointer()))->isFont()) { CFontItem *font=static_cast(realIndex.internalPointer()); addFont(font, urls, fontNames, fonts, usedFonts, getEnabled, getDisabled); } else { CFamilyItem *fam=static_cast(realIndex.internalPointer()); for(int ch=0; chfontCount(); ++ch) { QModelIndex child(itsProxy->mapToSource(index.child(ch, 0))); if(child.isValid() && (static_cast(child.internalPointer()))->isFont()) { CFontItem *font=static_cast(child.internalPointer()); addFont(font, urls, fontNames, fonts, usedFonts, getEnabled, getDisabled); } } } } } fontNames=CFontList::compact(fontNames); } QSet CFontListView::getFiles() { QModelIndexList items(allIndexes()); QModelIndex index; QSet files; foreach(index, items) if(index.isValid()) { QModelIndex realIndex(itsProxy->mapToSource(index)); if(realIndex.isValid()) if((static_cast(realIndex.internalPointer()))->isFont()) { CFontItem *font=static_cast(realIndex.internalPointer()); FileCont::ConstIterator it(font->files().begin()), end(font->files().end()); for(; it!=end; ++it) { QStringList assoc; files.insert((*it).path()); Misc::getAssociatedFiles((*it).path(), assoc); QStringList::ConstIterator ait(assoc.constBegin()), aend(assoc.constEnd()); for(; ait!=aend; ++ait) files.insert(*ait); } } } return files; } void CFontListView::getPrintableFonts(QSet &items, bool selected) { QModelIndexList selectedItems(selected ? selectedIndexes() : allIndexes()); QModelIndex index; foreach(index, selectedItems) { CFontItem *font=nullptr; if(index.isValid() && 0==index.column()) { QModelIndex realIndex(itsProxy->mapToSource(index)); if(realIndex.isValid()) { if((static_cast(realIndex.internalPointer()))->isFont()) font=static_cast(realIndex.internalPointer()); else { CFamilyItem *fam=static_cast(realIndex.internalPointer()); font=fam->regularFont(); } } } if(font && !font->isBitmap() && font->isEnabled()) items.insert(Misc::TFont(font->family(), font->styleInfo())); } } void CFontListView::setFilterGroup(CGroupListItem *grp) { CGroupListItem *oldGrp(itsProxy->filterGroup()); itsProxy->setFilterGroup(grp); itsAllowDrops=grp && !grp->isCustom(); if(!Misc::root()) { bool refreshStats(false); if(!grp || !oldGrp) refreshStats=true; else { // Check to see whether we have changed from listing all fonts, // listing just system or listing personal fonts. CGroupListItem::EType aType(CGroupListItem::CUSTOM==grp->type() || CGroupListItem::ALL==grp->type() || CGroupListItem::UNCLASSIFIED==grp->type() ? CGroupListItem::CUSTOM : grp->type()), bType(CGroupListItem::CUSTOM==oldGrp->type() || CGroupListItem::ALL==oldGrp->type() || CGroupListItem::UNCLASSIFIED==oldGrp->type() ? CGroupListItem::CUSTOM : oldGrp->type()); refreshStats=aType!=bType; } if(refreshStats) itsModel->refresh(!grp || !grp->isPersonal(), !grp || !grp->isSystem()); } // when switching groups, for some reason it is not always sorted. setSortingEnabled(true); } void CFontListView::listingPercent(int percent) { // when the font list is first loaded, for some reason it is not always sorted. // re-enabling sorting here seems to fix the issue - BUG 221610 if(100==percent) setSortingEnabled(true); } void CFontListView::refreshFilter() { - itsProxy->clear(); + itsProxy->invalidate(); } void CFontListView::filterText(const QString &text) { itsProxy->setFilterText(text); } void CFontListView::filterCriteria(int crit, qulonglong ws, const QStringList &ft) { itsProxy->setFilterCriteria((CFontFilter::ECriteria)crit, ws, ft); } void CFontListView::stats(int &enabled, int &disabled, int &partial) { enabled=disabled=partial=0; for(int i=0; irowCount(); ++i) { QModelIndex idx(itsProxy->index(i, 0, QModelIndex())); if(!idx.isValid()) break; QModelIndex sourceIdx(itsProxy->mapToSource(idx)); if(!sourceIdx.isValid()) break; if((static_cast(sourceIdx.internalPointer()))->isFamily()) switch((static_cast(sourceIdx.internalPointer()))->status()) { case CFamilyItem::ENABLED: enabled++; break; case CFamilyItem::DISABLED: disabled++; break; case CFamilyItem::PARTIAL: partial++; break; } } } void CFontListView::selectedStatus(bool &enabled, bool &disabled) { QModelIndexList selected(selectedIndexes()); QModelIndex index; enabled=disabled=false; foreach(index, selected) { QModelIndex realIndex(itsProxy->mapToSource(index)); if(realIndex.isValid()) { if((static_cast(realIndex.internalPointer()))->isFamily()) { switch((static_cast(realIndex.internalPointer()))->status()) { case CFamilyItem::ENABLED: enabled=true; break; case CFamilyItem::DISABLED: disabled=true; break; case CFamilyItem::PARTIAL: enabled=true; disabled=true; break; } } else { if((static_cast(realIndex.internalPointer()))->isEnabled()) enabled=true; else disabled=true; } } if(enabled && disabled) break; } } QModelIndexList CFontListView::allFonts() { QModelIndexList rv; int rowCount(itsProxy->rowCount()); for(int i=0; iindex(i, 0, QModelIndex())); int childRowCount(itsProxy->rowCount(idx)); for(int j=0; jindex(j, 0, idx)); if(child.isValid()) rv.append(itsProxy->mapToSource(child)); } } return rv; } void CFontListView::selectFirstFont() { if(0==selectedIndexes().count()) for(int i=0; iindex(0, i, QModelIndex())); if(idx.isValid()) selectionModel()->select(idx, QItemSelectionModel::Select); } } void CFontListView::setSortColumn(int col) { if(col!=itsProxy->filterKeyColumn()) { itsProxy->setFilterKeyColumn(col); itsProxy->invalidate(); } } void CFontListView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QAbstractItemView::selectionChanged(selected, deselected); if(itsModel->slowUpdates()) return; emit itemsSelected(getSelectedItems()); } QModelIndexList CFontListView::getSelectedItems() { // // Go through current selection, and for any 'font' items that are selected, // ensure 'family' item is not... QModelIndexList selectedItems(selectedIndexes()), deselectList; QModelIndex index; QSet selectedFamilies; bool en(false), dis(false); foreach(index, selectedItems) if(index.isValid()) { QModelIndex realIndex(itsProxy->mapToSource(index)); if(realIndex.isValid()) { if((static_cast(realIndex.internalPointer()))->isFont()) { CFontItem *font=static_cast(realIndex.internalPointer()); if(font->isEnabled()) en=true; else dis=true; if(!selectedFamilies.contains(font->parent())) { selectedFamilies.insert(font->parent()); for(int i=0; imapFromSource( itsModel->createIndex(font->parent()->rowNumber(), i, font->parent()))); } } else { switch((static_cast(realIndex.internalPointer()))->status()) { case CFamilyItem::ENABLED: en=true; break; case CFamilyItem::DISABLED: dis=true; break; case CFamilyItem::PARTIAL: en=dis=true; break; } } } } if(deselectList.count()) foreach(index, deselectList) selectionModel()->select(index, QItemSelectionModel::Deselect); QModelIndexList sel; QSet pointers; selectedItems=selectedIndexes(); foreach(index, selectedItems) { QModelIndex idx(itsProxy->mapToSource(index)); if(!pointers.contains(idx.internalPointer())) { pointers.insert(idx.internalPointer()); sel.append(idx); } } return sel; } void CFontListView::itemCollapsed(const QModelIndex &idx) { if(idx.isValid()) { QModelIndex index(itsProxy->mapToSource(idx)); if(index.isValid() && (static_cast(index.internalPointer()))->isFamily()) { CFamilyItem *fam=static_cast(index.internalPointer()); CFontItemCont::ConstIterator it(fam->fonts().begin()), end(fam->fonts().end()); for(; it!=end; ++it) for(int i=0; iselect(itsProxy->mapFromSource(itsModel->createIndex((*it)->rowNumber(), i, *it)), QItemSelectionModel::Deselect); } } } static bool isScalable(const QString &str) { QByteArray cFile(QFile::encodeName(str)); return Misc::checkExt(cFile, "ttf") || Misc::checkExt(cFile, "otf") || Misc::checkExt(cFile, "ttc") || Misc::checkExt(cFile, "pfa") || Misc::checkExt(cFile, "pfb"); } void CFontListView::view() { // Number of fonts user has selected, before we ask if they really want to view them all... static const int constMaxBeforePrompt=10; QModelIndexList selectedItems(selectedIndexes()); QModelIndex index; QSet fonts; foreach(index, selectedItems) { QModelIndex realIndex(itsProxy->mapToSource(index)); if(realIndex.isValid()) { if((static_cast(realIndex.internalPointer()))->isFont()) { CFontItem *font(static_cast(realIndex.internalPointer())); fonts.insert(font); } else { CFontItem *font((static_cast(realIndex.internalPointer()))->regularFont()); if(font) fonts.insert(font); } } } if(fonts.count() && (fonts.count()::ConstIterator it(fonts.begin()), end(fonts.end()); QStringList args; for(; it!=end; ++it) { QString file; int index(0); if(!(*it)->isEnabled()) { // For a disabled font, we need to find the first scalable font entry in its file list... FileCont::ConstIterator fit((*it)->files().begin()), fend((*it)->files().end()); for(; fit!=fend; ++fit) if(isScalable((*fit).path())) { file=(*fit).path(); index=(*fit).index(); break; } if(file.isEmpty()) { file=(*it)->fileName(); index=(*it)->index(); } } args << FC::encode((*it)->family(), (*it)->styleInfo(), file, index).url(); } QProcess::startDetached(Misc::app(KFI_VIEWER), args); } } QModelIndexList CFontListView::allIndexes() { QModelIndexList rv; int rowCount(itsProxy->rowCount()); for(int i=0; iindex(i, 0, QModelIndex())); int childRowCount(itsProxy->rowCount(idx)); rv.append(idx); for(int j=0; jindex(j, 0, idx)); if(child.isValid()) rv.append(child); } } return rv; } void CFontListView::startDrag(Qt::DropActions supportedActions) { QModelIndexList indexes(selectedIndexes()); if (indexes.count()) { QMimeData *data = model()->mimeData(indexes); if (!data) return; QModelIndex index(itsProxy->mapToSource(indexes.first())); const char *icon="application-x-font-pcf"; if(index.isValid()) { CFontItem *font=(static_cast(index.internalPointer()))->isFont() ? static_cast(index.internalPointer()) : (static_cast(index.internalPointer()))->regularFont(); if(font && !font->isBitmap()) // if("application/x-font-type1"==font->mimetype()) // icon="application-x-font-type1"; // else icon="application-x-font-ttf"; } QPoint hotspot; QPixmap pix(DesktopIcon(icon, KIconLoader::SizeMedium)); hotspot.setX(0); // pix.width()/2); hotspot.setY(0); // pix.height()/2); QDrag *drag = new QDrag(this); drag->setPixmap(pix); drag->setMimeData(data); drag->setHotSpot(hotspot); drag->start(supportedActions); } } void CFontListView::dragEnterEvent(QDragEnterEvent *event) { if(itsAllowDrops && event->mimeData()->hasFormat("text/uri-list")) // "application/x-kde-urilist" ?? event->acceptProposedAction(); } void CFontListView::dropEvent(QDropEvent *event) { if(itsAllowDrops && event->mimeData()->hasFormat("text/uri-list")) { event->acceptProposedAction(); QList urls(event->mimeData()->urls()); QList::ConstIterator it(urls.begin()), end(urls.end()); QSet kurls; QMimeDatabase db; for (; it!=end; ++it) { QMimeType mime = db.mimeTypeForUrl(*it); foreach (const QString &fontMime, CFontList::fontMimeTypes) { if (mime.inherits(fontMime)) { kurls.insert(*it); break; } } } if(!kurls.isEmpty()) emit fontsDropped(kurls); } } void CFontListView::contextMenuEvent(QContextMenuEvent *ev) { bool valid(indexAt(ev->pos()).isValid()); itsDeleteAct->setEnabled(valid); bool en(false), dis(false); QModelIndexList selectedItems(selectedIndexes()); QModelIndex index; foreach(index, selectedItems) { QModelIndex realIndex(itsProxy->mapToSource(index)); if(realIndex.isValid()) { if((static_cast(realIndex.internalPointer()))->isFont()) { if((static_cast(realIndex.internalPointer())->isEnabled())) en=true; else dis=true; } else { switch((static_cast(realIndex.internalPointer()))->status()) { case CFamilyItem::ENABLED: en=true; break; case CFamilyItem::DISABLED: dis=true; break; case CFamilyItem::PARTIAL: en=dis=true; break; } } } if(en && dis) break; } itsEnableAct->setEnabled(dis); itsDisableAct->setEnabled(en); if(itsPrintAct) itsPrintAct->setEnabled(en|dis); if(itsViewAct) itsViewAct->setEnabled(en|dis); itsMenu->popup(ev->globalPos()); } bool CFontListView::viewportEvent(QEvent *event) { executeDelayedItemsLayout(); return QTreeView::viewportEvent(event); } } diff --git a/kcms/kfontinst/kcmfontinst/GroupList.cpp b/kcms/kfontinst/kcmfontinst/GroupList.cpp index 9d8d3d532..a254573e2 100644 --- a/kcms/kfontinst/kcmfontinst/GroupList.cpp +++ b/kcms/kfontinst/kcmfontinst/GroupList.cpp @@ -1,1026 +1,1026 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "GroupList.h" #include "FontList.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "FcEngine.h" #include "Misc.h" #include "KfiConstants.h" namespace KFI { #define GROUPS_DOC "groups" #define GROUP_TAG "group" #define NAME_ATTR "name" #define FAMILY_TAG "family" enum EGroupColumns { COL_GROUP_NAME, NUM_GROUP_COLS }; CGroupListItem::CGroupListItem(const QString &name) : itsName(name), itsType(CUSTOM), itsHighlighted(false), itsStatus(CFamilyItem::ENABLED) { itsData.validated=false; } CGroupListItem::CGroupListItem(EType type, CGroupList *p) : itsType(type), itsHighlighted(false), itsStatus(CFamilyItem::ENABLED) { switch(itsType) { case ALL: itsName=i18n("All Fonts"); break; case PERSONAL: itsName=i18n("Personal Fonts"); break; case SYSTEM: itsName=i18n("System Fonts"); break; default: itsName=i18n("Unclassified"); } itsData.parent=p; } bool CGroupListItem::hasFont(const CFontItem *fnt) const { switch(itsType) { case CUSTOM: return itsFamilies.contains(fnt->family()); case PERSONAL: return !fnt->isSystem(); case SYSTEM: return fnt->isSystem(); case ALL: return true; case UNCLASSIFIED: { QList::ConstIterator it(itsData.parent->itsGroups.begin()), end(itsData.parent->itsGroups.end()); for(; it!=end; ++it) if((*it)->isCustom() && (*it)->families().contains(fnt->family())) return false; return true; } default: return false; } return false; } void CGroupListItem::updateStatus(QSet &enabled, QSet &disabled, QSet &partial) { QSet families(itsFamilies); if(0!=families.intersect(partial).count()) itsStatus=CFamilyItem::PARTIAL; else { families=itsFamilies; bool haveEnabled(0!=families.intersect(enabled).count()); families=itsFamilies; bool haveDisabled(0!=families.intersect(disabled).count()); if(haveEnabled && haveDisabled) itsStatus=CFamilyItem::PARTIAL; else if(haveEnabled && !haveDisabled) itsStatus=CFamilyItem::ENABLED; else itsStatus=CFamilyItem::DISABLED; } } bool CGroupListItem::load(QDomElement &elem) { if(elem.hasAttribute(NAME_ATTR)) { itsName=elem.attribute(NAME_ATTR); addFamilies(elem); return true; } return false; } bool CGroupListItem::addFamilies(QDomElement &elem) { int b4(itsFamilies.count()); for(QDomNode n=elem.firstChild(); !n.isNull(); n=n.nextSibling()) { QDomElement ent=n.toElement(); if(FAMILY_TAG==ent.tagName()) itsFamilies.insert(ent.text()); } return b4!=itsFamilies.count(); } void CGroupListItem::save(QTextStream &str) { - str << " <" GROUP_TAG " " NAME_ATTR "=\"" << Misc::encodeText(itsName, str) << "\">" << endl; + str << " <" GROUP_TAG " " NAME_ATTR "=\"" << Misc::encodeText(itsName, str) << "\">" << Qt::endl; if(!itsFamilies.isEmpty()) { QSet::ConstIterator it(itsFamilies.begin()), end(itsFamilies.end()); for(; it!=end; ++it) - str << " <" FAMILY_TAG ">" << Misc::encodeText(*it, str) << "" << endl; + str << " <" FAMILY_TAG ">" << Misc::encodeText(*it, str) << "" << Qt::endl; } - str << " " << endl; + str << " " << Qt::endl; } CGroupList::CGroupList(QWidget *parent) : QAbstractItemModel(parent), itsTimeStamp(0), itsModified(false), itsParent(parent), itsSortOrder(Qt::AscendingOrder) { itsSpecialGroups[CGroupListItem::ALL]=new CGroupListItem(CGroupListItem::ALL, this); itsGroups.append(itsSpecialGroups[CGroupListItem::ALL]); if(Misc::root()) itsSpecialGroups[CGroupListItem::PERSONAL]= itsSpecialGroups[CGroupListItem::SYSTEM]=NULL; else { itsSpecialGroups[CGroupListItem::PERSONAL]=new CGroupListItem(CGroupListItem::PERSONAL, this); itsGroups.append(itsSpecialGroups[CGroupListItem::PERSONAL]); itsSpecialGroups[CGroupListItem::SYSTEM]=new CGroupListItem(CGroupListItem::SYSTEM, this); itsGroups.append(itsSpecialGroups[CGroupListItem::SYSTEM]); } itsSpecialGroups[CGroupListItem::UNCLASSIFIED]= new CGroupListItem(CGroupListItem::UNCLASSIFIED, this); // Locate groups.xml file - normall will be ~/.config/fontgroups.xml QString path(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + '/'); if(!Misc::dExists(path)) Misc::createDir(path); itsFileName=path+'/'+KFI_GROUPS_FILE; rescan(); } CGroupList::~CGroupList() { save(); qDeleteAll(itsGroups); itsGroups.clear(); } int CGroupList::columnCount(const QModelIndex &) const { return NUM_GROUP_COLS; } void CGroupList::update(const QModelIndex &unHighlight, const QModelIndex &highlight) { if(unHighlight.isValid()) { CGroupListItem *grp=static_cast(unHighlight.internalPointer()); if(grp) grp->setHighlighted(false); emit dataChanged(unHighlight, unHighlight); } if(highlight.isValid()) { CGroupListItem *grp=static_cast(highlight.internalPointer()); if(grp) grp->setHighlighted(true); emit dataChanged(highlight, highlight); } } void CGroupList::updateStatus(QSet &enabled, QSet &disabled, QSet &partial) { QList::Iterator it(itsGroups.begin()), end(itsGroups.end()); for(; it!=end; ++it) if((*it)->isCustom()) (*it)->updateStatus(enabled, disabled, partial); emit layoutChanged(); } inline QColor midColour(const QColor &a, const QColor &b) { return QColor((a.red()+b.red())>>1, (a.green()+b.green())>>1, (a.blue()+b.blue())>>1); } QVariant CGroupList::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); CGroupListItem *grp=static_cast(index.internalPointer()); if(grp) switch(index.column()) { case COL_GROUP_NAME: switch(role) { case Qt::FontRole: if(CGroupListItem::SYSTEM==grp->type()) { QFont font; font.setItalic(true); return font; } break; case Qt::SizeHintRole: { const int s = KIconLoader::global()->currentSize(KIconLoader::Small); return QSize(s, s + 4); } case Qt::EditRole: case Qt::DisplayRole: return grp->name(); case Qt::DecorationRole: if(grp->highlighted()) switch(grp->type()) { case CGroupListItem::ALL: // Removing from a group return QIcon::fromTheme("list-remove"); case CGroupListItem::PERSONAL: // Copying/moving case CGroupListItem::SYSTEM: // Copying/moving return QIcon::fromTheme(Qt::LeftToRight==QApplication::layoutDirection() ? "go-next" : "go-previous"); case CGroupListItem::CUSTOM: // Adding to a group return QIcon::fromTheme("list-add"); default: break; } else switch(grp->type()) { case CGroupListItem::ALL: return QIcon::fromTheme("font"); case CGroupListItem::PERSONAL: return QIcon::fromTheme("user-identity"); case CGroupListItem::SYSTEM: return QIcon::fromTheme("computer"); case CGroupListItem::UNCLASSIFIED: return QIcon::fromTheme("fontstatus"); case CGroupListItem::CUSTOM: if(0==grp->families().count()) return QIcon::fromTheme("image-missing"); switch(grp->status()) { case CFamilyItem::PARTIAL: return QIcon::fromTheme("dialog-ok"); case CFamilyItem::ENABLED: return QIcon::fromTheme("dialog-ok"); case CFamilyItem::DISABLED: return QIcon::fromTheme("dialog-cancel"); } break; } default: break; } break; } return QVariant(); } bool CGroupList::setData(const QModelIndex &index, const QVariant &value, int role) { if(Qt::EditRole==role && index.isValid()) { QString name(value.toString().trimmed()); if(!name.isEmpty()) { CGroupListItem *grp=static_cast(index.internalPointer()); if(grp && grp->isCustom() && grp->name()!=name && !exists(name, false)) { grp->setName(name); itsModified=true; save(); sort(0, itsSortOrder); return true; } } } return false; } Qt::ItemFlags CGroupList::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::ItemIsEnabled; CGroupListItem *grp=static_cast(index.internalPointer()); return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDropEnabled | (grp && grp->type()==CGroupListItem::CUSTOM ? Qt::ItemIsEditable : Qt::NoItemFlags); } QVariant CGroupList::headerData(int section, Qt::Orientation orientation, int role) const { if (Qt::Horizontal==orientation && COL_GROUP_NAME==section) switch(role) { case Qt::DisplayRole: return i18n("Group"); case Qt::TextAlignmentRole: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); case Qt::WhatsThisRole: return whatsThis(); default: break; } return QVariant(); } QModelIndex CGroupList::index(int row, int column, const QModelIndex &parent) const { if(!parent.isValid()) { CGroupListItem *grp=itsGroups.value(row); if(grp) return createIndex(row, column, grp); } return QModelIndex(); } QModelIndex CGroupList::parent(const QModelIndex &) const { return QModelIndex(); } int CGroupList::rowCount(const QModelIndex &) const { return itsGroups.count(); } void CGroupList::rescan() { save(); load(); sort(0, itsSortOrder); } void CGroupList::load() { time_t ts=Misc::getTimeStamp(itsFileName); if(!ts || ts!=itsTimeStamp) { clear(); itsTimeStamp=ts; if(load(itsFileName)) itsModified=false; } } bool CGroupList::load(const QString &file) { QFile f(file); bool rv(false); if(f.open(QIODevice::ReadOnly)) { QDomDocument doc; if(doc.setContent(&f)) for(QDomNode n=doc.documentElement().firstChild(); !n.isNull(); n=n.nextSibling()) { QDomElement e=n.toElement(); if(GROUP_TAG==e.tagName() && e.hasAttribute(NAME_ATTR)) { QString name(e.attribute(NAME_ATTR)); CGroupListItem *item=find(name); if(!item) { item=new CGroupListItem(name); if(!itsGroups.contains(itsSpecialGroups[CGroupListItem::UNCLASSIFIED])) itsGroups.append(itsSpecialGroups[CGroupListItem::UNCLASSIFIED]); itsGroups.append(item); rv=true; } if(item->addFamilies(e)) rv=true; } } } return rv; } bool CGroupList::save() { if(itsModified && save(itsFileName, nullptr)) { itsTimeStamp=Misc::getTimeStamp(itsFileName); return true; } return false; } bool CGroupList::save(const QString &fileName, CGroupListItem *grp) { QSaveFile file(fileName); if (file.open(QIODevice::WriteOnly)) { QTextStream str(&file); - str << "<" GROUPS_DOC ">" << endl; + str << "<" GROUPS_DOC ">" << Qt::endl; if(grp) grp->save(str); else { QList::Iterator it(itsGroups.begin()), end(itsGroups.end()); for(; it!=end; ++it) if((*it)->isCustom()) (*it)->save(str); } - str << "" << endl; + str << "" << Qt::endl; itsModified=false; return file.commit(); } return false; } void CGroupList::merge(const QString &file) { if(load(file)) { itsModified=true; sort(0, itsSortOrder); } } void CGroupList::clear() { beginResetModel(); itsGroups.removeFirst(); // Remove all if(itsSpecialGroups[CGroupListItem::SYSTEM]) { itsGroups.removeFirst(); // Remove personal itsGroups.removeFirst(); // Remove system } if(itsGroups.contains(itsSpecialGroups[CGroupListItem::UNCLASSIFIED])) itsGroups.removeFirst(); // Remove unclassif... qDeleteAll(itsGroups); itsGroups.clear(); itsGroups.append(itsSpecialGroups[CGroupListItem::ALL]); if(itsSpecialGroups[CGroupListItem::SYSTEM]) { itsGroups.append(itsSpecialGroups[CGroupListItem::PERSONAL]); itsGroups.append(itsSpecialGroups[CGroupListItem::SYSTEM]); } // Don't add 'Unclassif' until we have some user groups endResetModel(); } QModelIndex CGroupList::index(CGroupListItem::EType t) { return createIndex(t, 0, itsSpecialGroups[t]); } void CGroupList::createGroup(const QString &name) { if(!exists(name)) { if(!itsGroups.contains(itsSpecialGroups[CGroupListItem::UNCLASSIFIED])) itsGroups.append(itsSpecialGroups[CGroupListItem::UNCLASSIFIED]); itsGroups.append(new CGroupListItem(name)); itsModified=true; save(); sort(0, itsSortOrder); } } bool CGroupList::removeGroup(const QModelIndex &idx) { if(idx.isValid()) { CGroupListItem *grp=static_cast(idx.internalPointer()); if(grp && grp->isCustom() && KMessageBox::Continue==KMessageBox::warningContinueCancel(itsParent, i18n("

Do you really want to remove \'%1\'?

" "

This will only remove the group, and not " "the actual fonts.

", grp->name()), i18n("Remove Group"), KGuiItem(i18n("Remove"), "list-remove", i18n("Remove group")))) { itsModified=true; itsGroups.removeAll(grp); int stdGroups=1 +// All (itsSpecialGroups[CGroupListItem::SYSTEM] ? 2 : 0)+ // Personal, System 1; // Unclassified if(stdGroups==itsGroups.count() && itsGroups.contains(itsSpecialGroups[CGroupListItem::UNCLASSIFIED])) itsGroups.removeAll(itsSpecialGroups[CGroupListItem::UNCLASSIFIED]); delete grp; save(); sort(0, itsSortOrder); return true; } } return false; } void CGroupList::removeFromGroup(const QModelIndex &group, const QSet &families) { if(group.isValid()) { CGroupListItem *grp=static_cast(group.internalPointer()); if(grp && grp->isCustom()) { QSet::ConstIterator it(families.begin()), end(families.end()); bool update(false); for(; it!=end; ++it) if(removeFromGroup(grp, *it)) update=true; if(update) emit refresh(); } } } QString CGroupList::whatsThis() const { return i18n("

Font Groups

This list displays the font groups available on your system. " "There are 2 main types of font groups:" "

  • Standard are special groups used by the font manager.
      %1
  • " "
  • Custom are groups created by you. To add a font family to one of " "these groups simply drag it from the list of fonts, and drop " "onto the desired group. To remove a family from the group, drag " "the font onto the \"All Fonts\" group.
  • " "

", Misc::root() ? i18n("
  • All Fonts contains all the fonts installed on your system.
  • " "
  • Unclassified contains all fonts that have not yet been placed " "within a \"Custom\" group.
  • ") : i18n("
  • All Fonts contains all the fonts installed on your system - " "both \"System\" and \"Personal\".
  • " "
  • System contains all fonts that are installed system-wide (i.e. " "available to all users).
  • " "
  • Personal contains your personal fonts.
  • " "
  • Unclassified contains all fonts that have not yet been placed " "within a \"Custom\" group.
  • ")); } void CGroupList::addToGroup(const QModelIndex &group, const QSet &families) { if(group.isValid()) { CGroupListItem *grp=static_cast(group.internalPointer()); if(grp && grp->isCustom()) { QSet::ConstIterator it(families.begin()), end(families.end()); bool update(false); for(; it!=end; ++it) if(!grp->hasFamily(*it)) { grp->addFamily(*it); update=true; itsModified=true; } if(update) emit refresh(); } } } void CGroupList::removeFamily(const QString &family) { QList::ConstIterator it(itsGroups.begin()), end(itsGroups.end()); for(; it!=end; ++it) removeFromGroup(*it, family); } bool CGroupList::removeFromGroup(CGroupListItem *grp, const QString &family) { if(grp && grp->isCustom() && grp->hasFamily(family)) { grp->removeFamily(family); itsModified=true; return true; } return false; } static bool groupNameLessThan(const CGroupListItem *f1, const CGroupListItem *f2) { return f1 && f2 && (f1->type()type() || (f1->type()==f2->type() && QString::localeAwareCompare(f1->name(), f2->name())<0)); } static bool groupNameGreaterThan(const CGroupListItem *f1, const CGroupListItem *f2) { return f1 && f2 && (f1->type()type() || (f1->type()==f2->type() && QString::localeAwareCompare(f1->name(), f2->name())>0)); } void CGroupList::sort(int, Qt::SortOrder order) { itsSortOrder=order; std::sort(itsGroups.begin(), itsGroups.end(), Qt::AscendingOrder==order ? groupNameLessThan : groupNameGreaterThan); emit layoutChanged(); } Qt::DropActions CGroupList::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList CGroupList::mimeTypes() const { QStringList types; types << KFI_FONT_DRAG_MIME; return types; } CGroupListItem * CGroupList::find(const QString &name) { QList::ConstIterator it(itsGroups.begin()), end(itsGroups.end()); for(; it!=end; ++it) if((*it)->name()==name) return (*it); return nullptr; } bool CGroupList::exists(const QString &name, bool showDialog) { if(nullptr!=find(name)) { if(showDialog) KMessageBox::error(itsParent, i18n("A group named \'%1\' already " "exists.", name)); return true; } return false; } class CGroupListViewDelegate : public QStyledItemDelegate { public: CGroupListViewDelegate(QObject *p) : QStyledItemDelegate(p) { } ~CGroupListViewDelegate() override { } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const override { CGroupListItem *grp=static_cast(idx.internalPointer()); QStyleOptionViewItem opt(option); if(grp && grp->isUnclassified()) opt.rect.adjust(0, 0, 0, -1); QStyledItemDelegate::paint(painter, opt, idx); if(grp && grp->isUnclassified()) { opt.rect.adjust(2, 0, -2, 1); painter->setPen(QApplication::palette().color(QPalette::Text)); painter->drawLine(opt.rect.bottomLeft(), opt.rect.bottomRight()); } } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &idx) const override { QSize sz(QStyledItemDelegate::sizeHint(option, idx)); CGroupListItem *grp=static_cast(idx.internalPointer()); if(grp && grp->isUnclassified()) sz.setHeight(sz.height()+1); return sz; } static bool isCloseEvent(QKeyEvent *event) { return Qt::Key_Tab==event->key() || Qt::Key_Backtab==event->key() || Qt::Key_Enter==event->key() || Qt::Key_Return==event->key(); } bool eventFilter(QObject *editor, QEvent *event) override { if(editor && event && QEvent::KeyPress==event->type() && isCloseEvent(static_cast(event)) && qobject_cast(editor)) { QString text=static_cast(editor)->text().trimmed(); if(!text.isEmpty() && !static_cast(static_cast(parent())->model())->exists(text, false)) { emit commitData(static_cast(editor)); emit closeEditor(static_cast(editor)); return true; } } return false; } }; CGroupListView::CGroupListView(QWidget *parent, CGroupList *model) : QTreeView(parent) { setModel(model); setItemDelegate(new CGroupListViewDelegate(this)); sortByColumn(COL_GROUP_NAME, Qt::AscendingOrder); setSelectionMode(QAbstractItemView::SingleSelection); setSortingEnabled(true); setAllColumnsShowFocus(true); setAlternatingRowColors(true); setAcceptDrops(true); setDragDropMode(QAbstractItemView::DropOnly); setDropIndicatorShown(true); setDragEnabled(false); header()->setSortIndicatorShown(true); setRootIsDecorated(false); itsMenu=new QMenu(this); itsDeleteAct=itsMenu->addAction(QIcon::fromTheme("list-remove"), i18n("Remove"), this, &CGroupListView::del); itsMenu->addSeparator(); itsEnableAct=itsMenu->addAction(QIcon::fromTheme("font-enable"), i18n("Enable"), this, &CGroupListView::enable); itsDisableAct=itsMenu->addAction(QIcon::fromTheme("font-disable"), i18n("Disable"), this, &CGroupListView::disable); itsMenu->addSeparator(); itsRenameAct=itsMenu->addAction(QIcon::fromTheme("edit-rename"), i18n("Rename..."), this, &CGroupListView::rename); if(!Misc::app(KFI_PRINTER).isEmpty()) { itsMenu->addSeparator(); itsPrintAct=itsMenu->addAction(QIcon::fromTheme("document-print"), i18n("Print..."), this, &CGroupListView::print); } else itsPrintAct= nullptr; itsMenu->addSeparator(); itsExportAct=itsMenu->addAction(QIcon::fromTheme("document-export"), i18n("Export..."), this, &CGroupListView::zip); setWhatsThis(model->whatsThis()); header()->setWhatsThis(whatsThis()); connect(this, &CGroupListView::addFamilies, model, &CGroupList::addToGroup); connect(this, SIGNAL(removeFamilies(QModelIndex,QSet)), model, SLOT(removeFromGroup(QModelIndex,QSet))); } CGroupListItem::EType CGroupListView::getType() { QModelIndexList selectedItems(selectedIndexes()); if(!selectedItems.isEmpty() && selectedItems.last().isValid()) { CGroupListItem *grp=static_cast(selectedItems.last().internalPointer()); return grp->type(); } return CGroupListItem::ALL; } void CGroupListView::controlMenu(bool del, bool en, bool dis, bool p, bool exp) { itsDeleteAct->setEnabled(del); itsRenameAct->setEnabled(del); itsEnableAct->setEnabled(en); itsDisableAct->setEnabled(dis); if(itsPrintAct) itsPrintAct->setEnabled(p); itsExportAct->setEnabled(exp); } void CGroupListView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndexList deselectedItems(deselected.indexes()); QAbstractItemView::selectionChanged(selected, deselected); QModelIndexList selectedItems(selectedIndexes()); if(0==selectedItems.count() && 1==deselectedItems.count()) selectionModel()->select(deselectedItems.last(), QItemSelectionModel::Select); else emit itemSelected(selectedItems.count() ? selectedItems.last() : QModelIndex()); } void CGroupListView::rename() { QModelIndex index(currentIndex()); if(index.isValid()) edit(index); } void CGroupListView::emitMoveFonts() { emit moveFonts(); } void CGroupListView::contextMenuEvent(QContextMenuEvent *ev) { if(indexAt(ev->pos()).isValid()) itsMenu->popup(ev->globalPos()); } void CGroupListView::dragEnterEvent(QDragEnterEvent *event) { if(event->mimeData()->hasFormat(KFI_FONT_DRAG_MIME)) event->acceptProposedAction(); } void CGroupListView::dragMoveEvent(QDragMoveEvent *event) { if(event->mimeData()->hasFormat(KFI_FONT_DRAG_MIME)) { QModelIndex index(indexAt(event->pos())); if(index.isValid()) { if(COL_GROUP_NAME!=index.column()) index=((CGroupList *)model())->createIdx(index.row(), COL_GROUP_NAME, index.internalPointer()); CGroupListItem *dest=static_cast(index.internalPointer()); CGroupListItem::EType type=getType(); if(dest) if(!selectedIndexes().contains(index)) { bool ok(true); if(dest->isCustom()) emit info(i18n("Add to \"%1\".", dest->name())); else if(CGroupListItem::CUSTOM==type && dest->isAll()) emit info(i18n("Remove from current group.")); else if(!Misc::root() && dest->isPersonal() && CGroupListItem::SYSTEM==type) emit info(i18n("Move to personal folder.")); else if(!Misc::root() && dest->isSystem() && CGroupListItem::PERSONAL==type) emit info(i18n("Move to system folder.")); else ok=false; if(ok) { drawHighlighter(index); event->acceptProposedAction(); return; } } } event->ignore(); drawHighlighter(QModelIndex()); emit info(QString()); } } void CGroupListView::dragLeaveEvent(QDragLeaveEvent *) { drawHighlighter(QModelIndex()); emit info(QString()); } void CGroupListView::dropEvent(QDropEvent *event) { emit info(QString()); drawHighlighter(QModelIndex()); if(event->mimeData()->hasFormat(KFI_FONT_DRAG_MIME)) { event->acceptProposedAction(); QSet families; QByteArray encodedData(event->mimeData()->data(KFI_FONT_DRAG_MIME)); QDataStream ds(&encodedData, QIODevice::ReadOnly); QModelIndex from(selectedIndexes().last()), to(indexAt(event->pos())); ds >> families; // Are we moving/copying, removing a font from the current group? if(to.isValid() && from.isValid()) { if( ((static_cast(from.internalPointer()))->isSystem() && (static_cast(to.internalPointer()))->isPersonal()) || ((static_cast(from.internalPointer()))->isPersonal() && (static_cast(to.internalPointer()))->isSystem())) QTimer::singleShot(0, this, &CGroupListView::emitMoveFonts); else if((static_cast(from.internalPointer()))->isCustom() && !(static_cast(to.internalPointer()))->isCustom()) emit removeFamilies(from, families); else emit addFamilies(to, families); } if(isUnclassified()) emit unclassifiedChanged(); } } void CGroupListView::drawHighlighter(const QModelIndex &idx) { if(itsCurrentDropItem!=idx) { ((CGroupList *)model())->update(itsCurrentDropItem, idx); itsCurrentDropItem=idx; } } bool CGroupListView::viewportEvent(QEvent *event) { executeDelayedItemsLayout(); return QTreeView::viewportEvent(event); } } diff --git a/kcms/kfontinst/kcmfontinst/KCmFontInst.cpp b/kcms/kfontinst/kcmfontinst/KCmFontInst.cpp index 851e88930..dfbf413f4 100644 --- a/kcms/kfontinst/kcmfontinst/KCmFontInst.cpp +++ b/kcms/kfontinst/kcmfontinst/KCmFontInst.cpp @@ -1,1269 +1,1269 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KCmFontInst.h" #include "KfiConstants.h" #include "PrintDialog.h" #include "FcEngine.h" #include "FontPreview.h" #include "FontsPackage.h" #include "Misc.h" #include "FontList.h" #include "DuplicatesDialog.h" #include "FontFilter.h" #include "PreviewSelectAction.h" #include "PreviewList.h" #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 #include #include #include #include #include #include #include #include #define CFG_GROUP "Main Settings" #define CFG_PREVIEW_SPLITTER_SIZES "PreviewSplitterSizes" #define CFG_GROUP_SPLITTER_SIZES "GroupSplitterSizes" #define CFG_FONT_SIZE "FontSize" K_PLUGIN_FACTORY(FontInstallFactory, registerPlugin(); ) namespace KFI { static QString partialIcon(bool load=true) { QString name = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/kfi/partial.png"; if(Misc::fExists(name)) { if(!load) QFile::remove(name); } else if(load) { QString pth; QPixmap pix=KIconLoader::global()->loadIcon("dialog-ok", KIconLoader::Small, KIconLoader::SizeSmall, KIconLoader::DisabledState); pix.save(name, "PNG"); } return name; } class CPushButton : public QPushButton { public: CPushButton(const KGuiItem &item, QWidget *parent) : QPushButton(parent) { KGuiItem::assign(this, item); theirHeight=qMax(theirHeight, QPushButton::sizeHint().height()); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } QSize sizeHint() const override { QSize sh(QPushButton::sizeHint()); sh.setHeight(theirHeight); if(sh.width()addAppDir(KFI_NAME); KAboutData *about = new KAboutData(QStringLiteral("fontinst"), i18n("Font Management"), QStringLiteral("1.0"), QString(), KAboutLicense::GPL, i18n("(C) Craig Drummond, 2000 - 2009")); about->addAuthor(i18n("Craig Drummond"), i18n("Developer and maintainer"), QStringLiteral("craig@kde.org")); setAboutData(about); KConfigGroup cg(&itsConfig, CFG_GROUP); itsGroupSplitter=new QSplitter(this); itsGroupSplitter->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); QWidget *groupWidget=new QWidget(itsGroupSplitter), *fontWidget=new QWidget(itsGroupSplitter); itsPreviewSplitter=new QSplitter(fontWidget); itsPreviewSplitter->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); QWidget *fontControlWidget=new QWidget(fontWidget); QGridLayout *groupsLayout=new QGridLayout(groupWidget); QBoxLayout *mainLayout=new QBoxLayout(QBoxLayout::TopToBottom, this), *fontsLayout=new QBoxLayout(QBoxLayout::TopToBottom, fontWidget), *fontControlLayout=new QBoxLayout(QBoxLayout::LeftToRight, fontControlWidget); mainLayout->setContentsMargins(0, 0, 0, 0); groupsLayout->setContentsMargins(0, 0, 0, 0); fontsLayout->setContentsMargins(0, 0, 0, 0); fontControlLayout->setContentsMargins(0, 0, 0, 0); itsFilter=new CFontFilter(this); // Details - Groups... itsGroupList=new CGroupList(groupWidget); itsGroupListView=new CGroupListView(groupWidget, itsGroupList); QPushButton *createGroup=new CPushButton(KGuiItem(QString(), "list-add", i18n("Create New Group...")), groupWidget); itsDeleteGroupControl=new CPushButton(KGuiItem(QString(), "list-remove", i18n("Remove Group...")), groupWidget); itsEnableGroupControl=new CPushButton(KGuiItem(QString(), "font-enable", i18n("Enable Fonts in Group...")), groupWidget); itsDisableGroupControl=new CPushButton(KGuiItem(QString(), "font-disable", i18n("Disable Fonts in Group...")), groupWidget); groupsLayout->addWidget(itsGroupListView, 0, 0, 1, 5); groupsLayout->addWidget(createGroup, 1, 0); groupsLayout->addWidget(itsDeleteGroupControl, 1, 1); groupsLayout->addWidget(itsEnableGroupControl, 1, 2); groupsLayout->addWidget(itsDisableGroupControl, 1, 3); groupsLayout->addItem(new QSpacerItem(itsDisableGroupControl->width(), groupsLayout->spacing(), QSizePolicy::Expanding, QSizePolicy::Fixed), 1, 4); itsPreviewWidget = new QWidget(this); QBoxLayout *previewWidgetLayout = new QBoxLayout(QBoxLayout::TopToBottom, itsPreviewWidget); previewWidgetLayout->setContentsMargins(0, 0, 0, 0); previewWidgetLayout->setSpacing(0); // Preview QFrame *previewFrame=new QFrame(itsPreviewWidget); QBoxLayout *previewFrameLayout=new QBoxLayout(QBoxLayout::LeftToRight, previewFrame); previewFrameLayout->setContentsMargins(0, 0, 0, 0); previewFrameLayout->setSpacing(0); previewFrame->setFrameShape(QFrame::StyledPanel); previewFrame->setFrameShadow(QFrame::Sunken); previewFrame->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); itsPreview=new CFontPreview(previewFrame); itsPreview->setWhatsThis(i18n("This displays a preview of the selected font.")); itsPreview->setContextMenuPolicy(Qt::CustomContextMenu); previewFrameLayout->addWidget(itsPreview); previewWidgetLayout->addWidget(previewFrame); itsPreview->engine()->readConfig(itsConfig); // List-style preview... itsPreviewList = new CPreviewListView(itsPreview->engine(), itsPreviewWidget); previewWidgetLayout->addWidget(itsPreviewList); itsPreviewList->setVisible(false); // Font List... itsFontList=new CFontList(fontWidget); itsFontListView=new CFontListView(itsPreviewSplitter, itsFontList); itsFontListView->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); itsScanDuplicateFontsControl=new CPushButton(KGuiItem(i18n("Find Duplicates..."), "edit-duplicate", i18n("Scan for Duplicate Fonts...")), fontControlWidget); itsAddFontControl=new CPushButton(KGuiItem(i18n("Install from File..."), "document-import", i18n("Install fonts from a local file")), fontControlWidget); itsGetNewFontsControl=new CPushButton(KGuiItem(i18n("Get New Fonts..."), "get-hot-new-stuff", i18n("Download new fonts")), fontControlWidget); itsDeleteFontControl=new CPushButton(KGuiItem(QString(), "edit-delete", i18n("Delete Selected Fonts...")), fontControlWidget); itsPreviewSplitter->addWidget(itsPreviewWidget); itsPreviewSplitter->setCollapsible(1, true); QWidget *statusRow = new QWidget(this); QBoxLayout *statusRowLayout=new QBoxLayout(QBoxLayout::LeftToRight, statusRow); itsStatusLabel = new QLabel(statusRow); itsStatusLabel->setAlignment(Qt::AlignVCenter|Qt::AlignRight); itsListingProgress=new CProgressBar(statusRow, itsStatusLabel->height()); itsListingProgress->setRange(0, 100); statusRowLayout->addWidget(itsListingProgress); statusRowLayout->addWidget(itsStatusLabel); // Layout widgets... mainLayout->addWidget(itsFilter); mainLayout->addWidget(itsGroupSplitter); mainLayout->addWidget(statusRow); fontControlLayout->addWidget(itsDeleteFontControl); fontControlLayout->addStretch(); fontControlLayout->addWidget(itsScanDuplicateFontsControl); fontControlLayout->addWidget(itsAddFontControl); fontControlLayout->addWidget(itsGetNewFontsControl); fontsLayout->addWidget(itsPreviewSplitter); fontsLayout->addWidget(fontControlWidget); // Set size of widgets... itsPreviewSplitter->setChildrenCollapsible(false); itsGroupSplitter->setChildrenCollapsible(false); itsGroupSplitter->setStretchFactor(0, 0); itsGroupSplitter->setStretchFactor(1, 1); itsPreviewSplitter->setStretchFactor(0, 1); itsPreviewSplitter->setStretchFactor(1, 0); // Set sizes for 3 views... QList defaultSizes; defaultSizes+=300; defaultSizes+=220; itsPreviewSplitter->setSizes(cg.readEntry(CFG_PREVIEW_SPLITTER_SIZES, defaultSizes)); itsPreviewHidden=itsPreviewSplitter->sizes().at(1)<8; defaultSizes.clear(); defaultSizes+=110; defaultSizes+=350; itsGroupSplitter->setSizes(cg.readEntry(CFG_GROUP_SPLITTER_SIZES, defaultSizes)); // Preview widget pop-up menu itsPreviewMenu = new QMenu(itsPreview); QAction *zoomIn=KStandardAction::create(KStandardAction::ZoomIn, itsPreview, SLOT(zoomIn()), this), *zoomOut=KStandardAction::create(KStandardAction::ZoomOut, itsPreview, SLOT(zoomOut()), this); itsPreviewMenu->addAction(zoomIn); itsPreviewMenu->addAction(zoomOut); itsPreviewMenu->addSeparator(); CPreviewSelectAction *prevSel=new CPreviewSelectAction(itsPreviewMenu); itsPreviewMenu->addAction(prevSel); QAction *changeTextAct=new QAction(QIcon::fromTheme("edit-rename"), i18n("Change Preview Text..."), this); itsPreviewMenu->addAction(changeTextAct), itsPreviewListMenu = new QMenu(itsPreviewList); itsPreviewListMenu->addAction(changeTextAct), // Connect signals... connect(itsPreview, &CFontPreview::atMax, zoomIn, &QAction::setDisabled); connect(itsPreview, &CFontPreview::atMin, zoomOut, &QAction::setDisabled); connect(prevSel, &CPreviewSelectAction::range, itsPreview, &CFontPreview::setUnicodeRange); connect(changeTextAct, &QAction::triggered, this, &CKCmFontInst::changeText); connect(itsFilter, &CFontFilter::queryChanged, itsFontListView, &CFontListView::filterText); connect(itsFilter, &CFontFilter::criteriaChanged, itsFontListView, &CFontListView::filterCriteria); connect(itsGroupListView, &CGroupListView::del, this, &CKCmFontInst::removeGroup); connect(itsGroupListView, &CGroupListView::print, this, &CKCmFontInst::printGroup); connect(itsGroupListView, &CGroupListView::enable, this, &CKCmFontInst::enableGroup); connect(itsGroupListView, &CGroupListView::disable, this, &CKCmFontInst::disableGroup); connect(itsGroupListView, &CGroupListView::moveFonts, this, &CKCmFontInst::moveFonts); connect(itsGroupListView, &CGroupListView::zip, this, &CKCmFontInst::zipGroup); connect(itsGroupListView, &CGroupListView::itemSelected, this, &CKCmFontInst::groupSelected); connect(itsGroupListView, &CGroupListView::info, this, &CKCmFontInst::showInfo); connect(itsGroupList, &CGroupList::refresh, this, &CKCmFontInst::refreshFontList); connect(itsFontList, &CFontList::listingPercent, this, &CKCmFontInst::listingPercent); connect(itsFontList, &QAbstractItemModel::layoutChanged, this, &CKCmFontInst::setStatusBar); connect(itsFontListView, &CFontListView::del, this, &CKCmFontInst::deleteFonts); connect(itsFontListView, SIGNAL(print()), SLOT(print())); connect(itsFontListView, &CFontListView::enable, this, &CKCmFontInst::enableFonts); connect(itsFontListView, &CFontListView::disable, this, &CKCmFontInst::disableFonts); connect(itsFontListView, SIGNAL(fontsDropped(QSet)), SLOT(addFonts(QSet))); connect(itsFontListView, &CFontListView::itemsSelected, this, &CKCmFontInst::fontsSelected); connect(itsFontListView, &CFontListView::refresh, this, &CKCmFontInst::setStatusBar); connect(itsGroupListView, &CGroupListView::unclassifiedChanged, itsFontListView, &CFontListView::refreshFilter); connect(createGroup, &QAbstractButton::clicked, this, &CKCmFontInst::addGroup); connect(itsDeleteGroupControl, &QAbstractButton::clicked, this, &CKCmFontInst::removeGroup); connect(itsEnableGroupControl, &QAbstractButton::clicked, this, &CKCmFontInst::enableGroup); connect(itsDisableGroupControl, &QAbstractButton::clicked, this, &CKCmFontInst::disableGroup); connect(itsAddFontControl, SIGNAL(clicked()), SLOT(addFonts())); connect(itsGetNewFontsControl, &QAbstractButton::clicked, this, &CKCmFontInst::downloadFonts); connect(itsDeleteFontControl, &QAbstractButton::clicked, this, &CKCmFontInst::deleteFonts); connect(itsScanDuplicateFontsControl, &QAbstractButton::clicked, this, &CKCmFontInst::duplicateFonts); //connect(validateFontsAct, SIGNAL(triggered(bool)), SLOT(validateFonts())); connect(itsPreview, &QWidget::customContextMenuRequested, this, &CKCmFontInst::previewMenu); connect(itsPreviewList, &CPreviewListView::showMenu, this, &CKCmFontInst::previewMenu); connect(itsPreviewSplitter, &QSplitter::splitterMoved, this, &CKCmFontInst::splitterMoved); selectMainGroup(); itsFontList->load(); } CKCmFontInst::~CKCmFontInst() { KConfigGroup cg(&itsConfig, CFG_GROUP); cg.writeEntry(CFG_PREVIEW_SPLITTER_SIZES, itsPreviewSplitter->sizes()); cg.writeEntry(CFG_GROUP_SPLITTER_SIZES, itsGroupSplitter->sizes()); delete itsTempDir; partialIcon(false); } QString CKCmFontInst::quickHelp() const { return Misc::root() ? i18n("

    Font Installer

    This module allows you to" " install TrueType, Type1, and Bitmap" " fonts.

    You may also install fonts using Konqueror:" " type fonts:/ into Konqueror's location bar" " and this will display your installed fonts. To install a" " font, simply copy one into the folder.

    ") : i18n("

    Font Installer

    This module allows you to" " install TrueType, Type1, and Bitmap" " fonts.

    You may also install fonts using Konqueror:" " type fonts:/ into Konqueror's location bar" " and this will display your installed fonts. To install a" " font, simply copy it into the appropriate folder - " " \"%1\" for fonts available to just yourself, or " " \"%2\" for system-wide fonts (available to all).

    ", i18n(KFI_KIO_FONTS_USER), i18n(KFI_KIO_FONTS_SYS)); } void CKCmFontInst::previewMenu(const QPoint &pos) { if(itsPreviewList->isHidden()) itsPreviewMenu->popup(itsPreview->mapToGlobal(pos)); else itsPreviewListMenu->popup(itsPreviewList->mapToGlobal(pos)); } void CKCmFontInst::splitterMoved() { if(itsPreviewWidget->width()>8 && itsPreviewHidden) { itsPreviewHidden=false; fontsSelected(itsFontListView->getSelectedItems()); } else if(!itsPreviewHidden && itsPreviewWidget->width()<8) itsPreviewHidden=true; } void CKCmFontInst::fontsSelected(const QModelIndexList &list) { if(!itsPreviewHidden) { if(!list.isEmpty()) { if(list.count()<2) { CFontModelItem *mi=static_cast(list.last().internalPointer()); CFontItem *font=mi->parent() ? static_cast(mi) : (static_cast(mi))->regularFont(); if(font) itsPreview->showFont(font->isEnabled() ? font->family() : font->fileName(), font->styleInfo(), font->index()); } else itsPreviewList->showFonts(list); } itsPreviewList->setVisible(list.count()>1); itsPreview->parentWidget()->setVisible(list.count()<2); } itsDeleteFontControl->setEnabled(list.count()); } void CKCmFontInst::addFonts() { QFileDialog dlg(this, i18n("Add Fonts")); dlg.setFileMode(QFileDialog::ExistingFiles); dlg.setMimeTypeFilters(CFontList::fontMimeTypes); QList list; if (dlg.exec() == QDialog::Accepted) list = dlg.selectedUrls(); if(!list.isEmpty()) { QSet urls; QList::Iterator it(list.begin()), end(list.end()); for(; it!=end; ++it) { if(KFI_KIO_FONTS_PROTOCOL!=(*it).scheme()) // Do not try to install from fonts:/ !!! { auto job = KIO::mostLocalUrl(*it); KJobWidgets::setWindow(job, this); job->exec(); QUrl url = job->mostLocalUrl(); if(url.isLocalFile()) { QString file(url.toLocalFile()); if(Misc::isPackage(file)) // If its a package we need to unzip 1st... urls+=FontsPackage::extract(url.toLocalFile(), &itsTempDir); else if(!Misc::isMetrics(url)) urls.insert(url); } else if(!Misc::isMetrics(url)) urls.insert(url); } } if(!urls.isEmpty()) addFonts(urls); delete itsTempDir; itsTempDir=nullptr; } } void CKCmFontInst::groupSelected(const QModelIndex &index) { CGroupListItem *grp=nullptr; if(index.isValid()) grp=static_cast(index.internalPointer()); else return; itsFontListView->setFilterGroup(grp); setStatusBar(); // // Check fonts listed within group are still valid! if(grp->isCustom() && !grp->validated()) { QSet remList; QSet::Iterator it(grp->families().begin()), end(grp->families().end()); for(; it!=end; ++it) if(!itsFontList->hasFamily(*it)) remList.insert(*it); it=remList.begin(); end=remList.end(); for(; it!=end; ++it) itsGroupList->removeFromGroup(grp, *it); grp->setValidated(); } itsGetNewFontsControl->setEnabled(grp->isPersonal() || grp->isAll()); } void CKCmFontInst::print(bool all) { // // In order to support printing of newly installed/enabled fonts, the actual printing // is carried out by the kfontinst helper app. This way we know Qt's font list will be // up to date. if((!itsPrintProc || QProcess::NotRunning==itsPrintProc->state()) && !Misc::app(KFI_PRINTER).isEmpty()) { QSet fonts; itsFontListView->getPrintableFonts(fonts, !all); if(!fonts.isEmpty()) { CPrintDialog dlg(this); KConfigGroup cg(&itsConfig, CFG_GROUP); if(dlg.exec(cg.readEntry(CFG_FONT_SIZE, 1))) { static const int constSizes[]={0, 12, 18, 24, 36, 48}; QSet::ConstIterator it(fonts.begin()), end(fonts.end()); QTemporaryFile tmpFile; bool useFile(fonts.count()>16), startProc(true); QStringList args; if(!itsPrintProc) itsPrintProc=new QProcess(this); else itsPrintProc->kill(); QString title = QGuiApplication::applicationDisplayName(); if (title.isEmpty()) title = QCoreApplication::applicationName(); // // If we have lots of fonts to print, pass kfontinst a temporary groups file to print // instead of passing font by font... if(useFile) { if(tmpFile.open()) { QTextStream str(&tmpFile); for(; it!=end; ++it) - str << (*it).family << endl - << (*it).styleInfo << endl; + str << (*it).family << Qt::endl + << (*it).styleInfo << Qt::endl; args << "--embed" << QString().sprintf("0x%x", (unsigned int)window()->winId()) << "--qwindowtitle" << title << "--qwindowicon" << "preferences-desktop-font-installer" << "--size" << QString().setNum(constSizes[dlg.chosenSize() < 6 ? dlg.chosenSize() : 2]) << "--listfile" << tmpFile.fileName() << "--deletefile"; } else { KMessageBox::error(this, i18n("Failed to save list of fonts to print.")); startProc=false; } } else { args << "--embed" << QString().sprintf("0x%x", (unsigned int)window()->winId()) << "--qwindowtitle" << title << "--qwindowicon" << "preferences-desktop-font-installer" << "--size" << QString().setNum(constSizes[dlg.chosenSize()<6 ? dlg.chosenSize() : 2]); for(; it!=end; ++it) args << "--pfont" << QString((*it).family.toUtf8()+','+QString().setNum((*it).styleInfo)); } if(startProc) { itsPrintProc->start(Misc::app(KFI_PRINTER), args); if(itsPrintProc->waitForStarted(1000)) { if(useFile) tmpFile.setAutoRemove(false); } else KMessageBox::error(this, i18n("Failed to start font printer.")); } cg.writeEntry(CFG_FONT_SIZE, dlg.chosenSize()); } } else KMessageBox::information(this, i18n("There are no printable fonts.\n" "You can only print non-bitmap and enabled fonts."), i18n("Cannot Print")); } } void CKCmFontInst::deleteFonts() { CJobRunner::ItemList urls; QStringList fontNames; QSet fonts; itsDeletedFonts.clear(); itsFontListView->getFonts(urls, fontNames, &fonts, true); if(urls.isEmpty()) KMessageBox::information(this, i18n("You did not select anything to delete."), i18n("Nothing to Delete")); else { QSet::ConstIterator it(fonts.begin()), end(fonts.end()); bool doIt=false; for(; it!=end; ++it) itsDeletedFonts.insert((*it).family); switch(fontNames.count()) { case 0: break; case 1: doIt = KMessageBox::Continue==KMessageBox::warningContinueCancel(this, i18n("

    Do you really want to " "delete

    \'%1\'?

    ", fontNames.first()), i18n("Delete Font"), KStandardGuiItem::del()); break; default: doIt = KMessageBox::Continue==KMessageBox::warningContinueCancelList(this, i18np("Do you really want to delete this font?", "Do you really want to delete these %1 fonts?", fontNames.count()), fontNames, i18n("Delete Fonts"), KStandardGuiItem::del()); } if(doIt) { itsStatusLabel->setText(i18n("Deleting font(s)...")); doCmd(CJobRunner::CMD_DELETE, urls); } } } void CKCmFontInst::moveFonts() { CJobRunner::ItemList urls; QStringList fontNames; itsDeletedFonts.clear(); itsFontListView->getFonts(urls, fontNames, nullptr, true); if(urls.isEmpty()) KMessageBox::information(this, i18n("You did not select anything to move."), i18n("Nothing to Move")); else { bool doIt=false; switch(fontNames.count()) { case 0: break; case 1: doIt = KMessageBox::Continue==KMessageBox::warningContinueCancel(this, i18n("

    Do you really want to " "move

    \'%1\'

    from %2 to %3?

    ", fontNames.first(), itsGroupListView->isSystem() ? i18n(KFI_KIO_FONTS_SYS) : i18n(KFI_KIO_FONTS_USER), itsGroupListView->isSystem() ? i18n(KFI_KIO_FONTS_USER) : i18n(KFI_KIO_FONTS_SYS)), i18n("Move Font"), KGuiItem(i18n("Move"))); break; default: doIt = KMessageBox::Continue==KMessageBox::warningContinueCancelList(this, i18np("

    Do you really want to move this font from %2 to %3?

    ", "

    Do you really want to move these %1 fonts from %2 to %3?

    ", fontNames.count(), itsGroupListView->isSystem() ? i18n(KFI_KIO_FONTS_SYS) : i18n(KFI_KIO_FONTS_USER), itsGroupListView->isSystem() ? i18n(KFI_KIO_FONTS_USER) : i18n(KFI_KIO_FONTS_SYS)), fontNames, i18n("Move Fonts"), KGuiItem(i18n("Move"))); } if(doIt) { itsStatusLabel->setText(i18n("Moving font(s)...")); doCmd(CJobRunner::CMD_MOVE, urls, !itsGroupListView->isSystem()); } } } void CKCmFontInst::zipGroup() { QModelIndex idx(itsGroupListView->currentIndex()); if(idx.isValid()) { CGroupListItem *grp=static_cast(idx.internalPointer()); if(grp) { QFileDialog dlg(this, i18n("Export Group")); dlg.setAcceptMode(QFileDialog::AcceptSave); dlg.setDirectoryUrl(QUrl::fromLocalFile(grp->name())); dlg.setMimeTypeFilters(QStringList() << QStringLiteral("application/zip")); QString fileName; if (dlg.exec() == QDialog::Accepted) fileName = dlg.selectedFiles().value(0); if(!fileName.isEmpty()) { KZip zip(fileName); if(zip.open(QIODevice::WriteOnly)) { QSet files; files=itsFontListView->getFiles(); if(!files.isEmpty()) { QMap map=Misc::getFontFileMap(files); QMap::ConstIterator it(map.constBegin()), end(map.constEnd()); for(; it!=end; ++it) zip.addLocalFile(it.value(), it.key()); zip.close(); } else KMessageBox::error(this, i18n("No files?")); } else KMessageBox::error(this, i18n("Failed to open %1 for writing", fileName)); } } } } void CKCmFontInst::enableFonts() { toggleFonts(true); } void CKCmFontInst::disableFonts() { toggleFonts(false); } void CKCmFontInst::addGroup() { bool ok; QString name(QInputDialog::getText(this, i18n("Create New Group"), i18n("Name of new group:"), QLineEdit::Normal, i18n("New Group"), &ok)); if(ok && !name.isEmpty()) itsGroupList->createGroup(name); } void CKCmFontInst::removeGroup() { if(itsGroupList->removeGroup(itsGroupListView->currentIndex())) selectMainGroup(); } void CKCmFontInst::enableGroup() { toggleGroup(true); } void CKCmFontInst::disableGroup() { toggleGroup(false); } void CKCmFontInst::changeText() { bool status; QString oldStr(itsPreview->engine()->getPreviewString()), newStr(QInputDialog::getText(this, i18n("Preview Text"), i18n("Please enter new text:"), QLineEdit::Normal, oldStr, &status)); if(status && oldStr!=newStr) { itsPreview->engine()->setPreviewString(newStr); itsPreview->showFont(); itsPreviewList->refreshPreviews(); } } void CKCmFontInst::duplicateFonts() { CDuplicatesDialog(this, itsFontList).exec(); } //void CKCmFontInst::validateFonts() //{ //} void CKCmFontInst::downloadFonts() { KNS3::DownloadDialog *newStuff = new KNS3::DownloadDialog("kfontinst.knsrc", this); newStuff->exec(); if(!newStuff->changedEntries().isEmpty()) // We have new fonts, so need to reconfigure fontconfig... { // Ask dbus helper for the current fonts folder name... // We then sym-link our knewstuff3 download folder into the fonts folder... QString destFolder=CJobRunner::folderName(false); if(!destFolder.isEmpty()) { destFolder+="kfontinst"; if(!QFile::exists(destFolder)) { QFile _file(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "kfontinst"); _file.link(destFolder); } } doCmd(CJobRunner::CMD_UPDATE, CJobRunner::ItemList()); } delete newStuff; } void CKCmFontInst::print() { print(false); } void CKCmFontInst::printGroup() { print(true); } void CKCmFontInst::listingPercent(int p) { if(0==p) { showInfo(i18n("Scanning font list...")); itsListingProgress->show(); } else if(100==p && p!=itsListingProgress->value()) { removeDeletedFontsFromGroups(); QSet foundries; itsFontList->getFoundries(foundries); itsFilter->setFoundries(foundries); refreshFamilies(); itsListingProgress->hide(); itsFontListView->selectFirstFont(); } itsListingProgress->setValue(p); } void CKCmFontInst::refreshFontList() { itsFontListView->refreshFilter(); refreshFamilies(); } void CKCmFontInst::refreshFamilies() { QSet enabledFamilies, disabledFamilies, partialFamilies; itsFontList->getFamilyStats(enabledFamilies, disabledFamilies, partialFamilies); itsGroupList->updateStatus(enabledFamilies, disabledFamilies, partialFamilies); setStatusBar(); } void CKCmFontInst::showInfo(const QString &info) { if(info.isEmpty()) if(itsLastStatusBarMsg.isEmpty()) setStatusBar(); else { itsStatusLabel->setText(itsLastStatusBarMsg); itsLastStatusBarMsg=QString(); } else { if(itsLastStatusBarMsg.isEmpty()) itsLastStatusBarMsg=itsStatusLabel->text(); itsStatusLabel->setText(info); } } void CKCmFontInst::setStatusBar() { if(itsFontList->slowUpdates()) return; int enabled=0, disabled=0, partial=0; bool selectedEnabled=false, selectedDisabled=false; itsStatusLabel->setToolTip(QString()); if(0==itsFontList->families().count()) itsStatusLabel->setText(i18n("No fonts")); else { itsFontListView->stats(enabled, disabled, partial); itsFontListView->selectedStatus(selectedEnabled, selectedDisabled); QString text(i18np("1 Font", "%1 Fonts", enabled+disabled+partial)); if(disabled||partial) { text+=QString(" (%2").arg(KIconLoader::global()->iconPath("dialog-ok", -KIconLoader::SizeSmall)).arg(enabled) +QString(" %2").arg(KIconLoader::global()->iconPath("dialog-cancel", -KIconLoader::SizeSmall)).arg(disabled); if(partial) text+=QString(" %2").arg(partialIcon()).arg(partial); text+=QLatin1Char(')'); itsStatusLabel->setToolTip(partial ? i18n("" "" "" "" "" "
    Enabled:%1
    Disabled:%2
    Partially enabled:%3
    Total:%4
    ", enabled, disabled, partial, enabled+disabled+partial) : i18n("" "" "" "" "
    Enabled:%1
    Disabled:%2
    Total:%3
    ", enabled, disabled, enabled+disabled)); } itsStatusLabel->setText(disabled||partial ? "

    "+text+"

    " : text); } CGroupListItem::EType type(itsGroupListView->getType()); bool isStd(CGroupListItem::CUSTOM==type); itsAddFontControl->setEnabled(CGroupListItem::ALL==type||CGroupListItem::UNCLASSIFIED==type|| CGroupListItem::PERSONAL==type||CGroupListItem::SYSTEM==type); itsDeleteGroupControl->setEnabled(isStd); itsEnableGroupControl->setEnabled(disabled||partial); itsDisableGroupControl->setEnabled(isStd && (enabled||partial)); itsGroupListView->controlMenu(itsDeleteGroupControl->isEnabled(), itsEnableGroupControl->isEnabled(), itsDisableGroupControl->isEnabled(), enabled||partial, enabled||disabled); itsDeleteFontControl->setEnabled(selectedEnabled||selectedDisabled); } void CKCmFontInst::addFonts(const QSet &src) { if(!src.isEmpty() && !itsGroupListView->isCustom()) { bool system; if(Misc::root()) system=true; else { switch(itsGroupListView->getType()) { case CGroupListItem::ALL: case CGroupListItem::UNCLASSIFIED: switch(KMessageBox::questionYesNoCancel(this, i18n("Do you wish to install the font(s) for personal use " "(only available to you), or " "system-wide (available to all users)?"), i18n("Where to Install"), KGuiItem(i18n(KFI_KIO_FONTS_USER)), KGuiItem(i18n(KFI_KIO_FONTS_SYS)))) { case KMessageBox::Yes: system=false; break; case KMessageBox::No: system=true; break; default: case KMessageBox::Cancel: return; } break; case CGroupListItem::PERSONAL: system=false; break; case CGroupListItem::SYSTEM: system=true; break; default: return; } } QSet copy; QSet::ConstIterator it, end(src.end()); // // Check if font has any associated AFM or PFM file... itsStatusLabel->setText(i18n("Looking for any associated files...")); if(!itsProgress) { itsProgress = new QProgressDialog(this); itsProgress->setWindowTitle(i18n("Scanning Files...")); itsProgress->setLabelText(i18n("Looking for additional files to install...")); itsProgress->setModal(true); itsProgress->setAutoReset(true); itsProgress->setAutoClose(true); } itsProgress->setCancelButton(nullptr); itsProgress->setMinimumDuration(500); itsProgress->setRange(0, src.size()); itsProgress->setValue(0); int steps=src.count()<200 ? 1 : src.count()/10; for(it=src.begin(); it!=end; ++it) { QList associatedUrls; itsProgress->setLabelText(i18n("Looking for files associated with %1", (*it).url())); itsProgress->setValue(itsProgress->value()+1); if(1==steps || 0==(itsProgress->value()%steps)) { bool dialogVisible(itsProgress->isVisible()); QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents); if(dialogVisible && !itsProgress->isVisible()) // User closed dialog! re-open!!! itsProgress->show(); } CJobRunner::getAssociatedUrls(*it, associatedUrls, false, this); copy.insert(*it); QList::Iterator aIt(associatedUrls.begin()), aEnd(associatedUrls.end()); for(; aIt!=aEnd; ++aIt) copy.insert(*aIt); } itsProgress->close(); CJobRunner::ItemList installUrls; end=copy.end(); for(it=copy.begin(); it!=end; ++it) installUrls.append(*it); itsStatusLabel->setText(i18n("Installing font(s)...")); doCmd(CJobRunner::CMD_INSTALL, installUrls, system); } } void CKCmFontInst::removeDeletedFontsFromGroups() { if(!itsDeletedFonts.isEmpty()) { QSet::Iterator it(itsDeletedFonts.begin()), end(itsDeletedFonts.end()); for(; it!=end; ++it) if(!itsFontList->hasFamily(*it)) itsGroupList->removeFamily(*it); itsDeletedFonts.clear(); } } void CKCmFontInst::selectGroup(CGroupListItem::EType grp) { QModelIndex current(itsGroupListView->currentIndex()); if(current.isValid()) { CGroupListItem *grpItem=static_cast(current.internalPointer()); if(grpItem && grp==grpItem->type()) return; else itsGroupListView->selectionModel()->select(current, QItemSelectionModel::Deselect); } QModelIndex idx(itsGroupList->index(grp)); itsGroupListView->selectionModel()->select(idx, QItemSelectionModel::Select); itsGroupListView->setCurrentIndex(idx); groupSelected(idx); itsFontListView->refreshFilter(); setStatusBar(); } void CKCmFontInst::toggleGroup(bool enable) { QModelIndex idx(itsGroupListView->currentIndex()); if(idx.isValid()) { CGroupListItem *grp=static_cast(idx.internalPointer()); if(grp) toggleFonts(enable, grp->name()); } } void CKCmFontInst::toggleFonts(bool enable, const QString &grp) { CJobRunner::ItemList urls; QStringList fonts; itsFontListView->getFonts(urls, fonts, nullptr, grp.isEmpty(), !enable, enable); if(urls.isEmpty()) KMessageBox::information(this, enable ? i18n("You did not select anything to enable.") : i18n("You did not select anything to disable."), enable ? i18n("Nothing to Enable") : i18n("Nothing to Disable")); else toggleFonts(urls, fonts, enable, grp); } void CKCmFontInst::toggleFonts(CJobRunner::ItemList &urls, const QStringList &fonts, bool enable, const QString &grp) { bool doIt=false; switch(fonts.count()) { case 0: break; case 1: doIt = KMessageBox::Continue==KMessageBox::warningContinueCancel(this, grp.isEmpty() ? enable ? i18n("

    Do you really want to " "enable

    \'%1\'?

    ", fonts.first()) : i18n("

    Do you really want to " "disable

    \'%1\'?

    ", fonts.first()) : enable ? i18n("

    Do you really want to " "enable

    \'%1\', " "contained within group \'%2\'?

    ", fonts.first(), grp) : i18n("

    Do you really want to " "disable

    \'%1\', " "contained within group \'%2\'?

    ", fonts.first(), grp), enable ? i18n("Enable Font") : i18n("Disable Font"), enable ? KGuiItem(i18n("Enable"), "font-enable", i18n("Enable Font")) : KGuiItem(i18n("Disable"), "font-disable", i18n("Disable Font"))); break; default: doIt = KMessageBox::Continue==KMessageBox::warningContinueCancelList(this, grp.isEmpty() ? enable ? i18np("Do you really want to enable this font?", "Do you really want to enable these %1 fonts?", urls.count()) : i18np("Do you really want to disable this font?", "Do you really want to disable these %1 fonts?", urls.count()) : enable ? i18np("

    Do you really want to enable this font " "contained within group \'%2\'?

    ", "

    Do you really want to enable these %1 fonts " "contained within group \'%2\'?

    ", urls.count(), grp) : i18np("

    Do you really want to disable this font " "contained within group \'%2\'?

    ", "

    Do you really want to disable these %1 fonts " "contained within group \'%2\'?

    ", urls.count(), grp), fonts, enable ? i18n("Enable Fonts") : i18n("Disable Fonts"), enable ? KGuiItem(i18n("Enable"), "font-enable", i18n("Enable Fonts")) : KGuiItem(i18n("Disable"), "font-disable", i18n("Disable Fonts"))); } if(doIt) { if(enable) itsStatusLabel->setText(i18n("Enabling font(s)...")); else itsStatusLabel->setText(i18n("Disabling font(s)...")); doCmd(enable ? CJobRunner::CMD_ENABLE : CJobRunner::CMD_DISABLE, urls); } } void CKCmFontInst::selectMainGroup() { selectGroup(/*Misc::root() ? */CGroupListItem::ALL/* : CGroupListItem::PERSONAL*/); } void CKCmFontInst::doCmd(CJobRunner::ECommand cmd, const CJobRunner::ItemList &urls, bool system) { itsFontList->setSlowUpdates(true); CJobRunner runner(this); connect(&runner, &CJobRunner::configuring, itsFontList, &CFontList::unsetSlowUpdates); runner.exec(cmd, urls, system); itsFontList->setSlowUpdates(false); refreshFontList(); if(CJobRunner::CMD_DELETE==cmd) itsFontListView->clearSelection(); CFcEngine::setDirty(); setStatusBar(); delete itsTempDir; itsTempDir=nullptr; itsFontListView->repaint(); removeDeletedFontsFromGroups(); } } #include "KCmFontInst.moc" diff --git a/kcms/kfontinst/kio/KioFonts.cpp b/kcms/kfontinst/kio/KioFonts.cpp index b9895826d..56d394384 100644 --- a/kcms/kfontinst/kio/KioFonts.cpp +++ b/kcms/kfontinst/kio/KioFonts.cpp @@ -1,821 +1,821 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KioFonts.h" #include "KfiConstants.h" #include "FontInstInterface.h" #include "FontInst.h" #include "Fc.h" #include "Misc.h" #include "XmlStrings.h" #include "Family.h" #include "Style.h" #include "File.h" #include "debug.h" #define MAX_IPC_SIZE (1024*32) #define KFI_DBUG qCDebug(KCM_KFONTINST_KIO) << '(' << time(NULL) << ')' static const int constReconfigTimeout = 10; extern "C" { Q_DECL_EXPORT int kdemain(int argc, char **argv) { if (argc != 4) { fprintf(stderr, "Usage: kio_" KFI_KIO_FONTS_PROTOCOL " protocol domain-socket1 domain-socket2\n"); exit(-1); } QCoreApplication app(argc, argv); QCoreApplication::setApplicationName("kio_" KFI_KIO_FONTS_PROTOCOL); KFI::CKioFonts slave(argv[2], argv[3]); slave.dispatchLoop(); return 0; } } namespace KFI { inline bool isSysFolder(const QString &folder) { return i18n(KFI_KIO_FONTS_SYS)==folder || KFI_KIO_FONTS_SYS==folder; } inline bool isUserFolder(const QString &folder) { return i18n(KFI_KIO_FONTS_USER)==folder || KFI_KIO_FONTS_USER==folder; } static CKioFonts::EFolder getFolder(const QStringList &list) { if(list.size()>0) { QString folder=list[0]; if(isSysFolder(folder)) return CKioFonts::FOLDER_SYS; else if(isUserFolder(folder)) return CKioFonts::FOLDER_USER; return CKioFonts::FOLDER_UNKNOWN; } return CKioFonts::FOLDER_ROOT; } static int getSize(const QString &file) { QT_STATBUF buff; QByteArray f(QFile::encodeName(file)); if(-1!=QT_LSTAT(f.constData(), &buff)) { if (S_ISLNK(buff.st_mode)) { char buffer2[1000]; int n=readlink(f.constData(), buffer2, 999); if(n!= -1) buffer2[n]='\0'; if(-1==QT_STAT(f.constData(), &buff)) return -1; } return buff.st_size; } return -1; } static bool writeAll(int fd, const char *buf, size_t len) { while(len>0) { ssize_t written=write(fd, buf, len); if (written<0 && EINTR!=errno) return false; buf+=written; len-=written; } return true; } static bool isScalable(const QString &str) { return Misc::checkExt(str, "ttf") || Misc::checkExt(str, "otf") || Misc::checkExt(str, "ttc") || Misc::checkExt(str, "pfa") || Misc::checkExt(str, "pfb"); } static const char * const constExtensions[]= {".ttf", KFI_FONTS_PACKAGE, ".otf", ".pfa", ".pfb", ".ttc", ".pcf", ".pcf.gz", ".bdf", ".bdf.gz", nullptr }; static QString removeKnownExtension(const QUrl &url) { QString fname(url.fileName()); int pos; for(int i=0; constExtensions[i]; ++i) if(-1!=(pos=fname.lastIndexOf(QString::fromLatin1(constExtensions[i]), -1, Qt::CaseInsensitive))) return fname.left(pos); return fname; } CKioFonts::CKioFonts(const QByteArray &pool, const QByteArray &app) : KIO::SlaveBase(KFI_KIO_FONTS_PROTOCOL, pool, app) , itsInterface(new FontInstInterface()) , itsTempDir(nullptr) { KFI_DBUG; } CKioFonts::~CKioFonts() { KFI_DBUG; delete itsInterface; delete itsTempDir; } void CKioFonts::listDir(const QUrl &url) { KFI_DBUG << url; - QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); + QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), Qt::SkipEmptyParts)); EFolder folder=Misc::root() ? FOLDER_SYS : getFolder(pathList); KIO::UDSEntry entry; int size=0; switch(folder) { case FOLDER_ROOT: KFI_DBUG << "List root folder"; size=2; totalSize(2); createUDSEntry(entry, FOLDER_SYS); listEntry(entry); createUDSEntry(entry, FOLDER_USER); listEntry(entry); break; case FOLDER_SYS: case FOLDER_USER: size=listFolder(entry, folder); break; default: break; } if(FOLDER_UNKNOWN!=folder) { finished(); } else error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } void CKioFonts::put(const QUrl &url, int /*permissions*/, KIO::JobFlags /*flags*/) { KFI_DBUG << url; - QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); + QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), Qt::SkipEmptyParts)); EFolder folder(getFolder(pathList)); if(!Misc::root() && FOLDER_ROOT==folder) error(KIO::ERR_SLAVE_DEFINED, i18n("Can only install fonts to either \"%1\" or \"%2\".", i18n(KFI_KIO_FONTS_USER), i18n(KFI_KIO_FONTS_SYS))); else if(Misc::isPackage(url.fileName())) error(KIO::ERR_SLAVE_DEFINED, i18n("You cannot install a fonts package directly.\n" "Please extract %1, and install the components individually.", url.toDisplayString())); else { if(!itsTempDir) { itsTempDir=new QTemporaryDir(QDir::tempPath() + QString::fromLatin1("/kio_fonts_")+QString::number(getpid())); itsTempDir->setAutoRemove(true); } QString tempFile(itsTempDir->filePath(url.fileName())); QFile dest(tempFile); if (dest.open(QIODevice::WriteOnly)) { int result; // Loop until we got 0 (end of data) do { QByteArray buffer; dataReq(); // Request for data result = readData(buffer); if(result > 0 && !writeAll(dest.handle(), buffer.constData(), buffer.size())) { if(ENOSPC==errno) // disk full { error(KIO::ERR_DISK_FULL, dest.fileName()); result = -2; // means: remove dest file } else { error(KIO::ERR_CANNOT_WRITE, dest.fileName()); result = -1; } } } while(result>0); if (result<0) { dest.close(); ::exit(255); } handleResp(itsInterface->install(tempFile, Misc::root() || FOLDER_SYS==folder), url.fileName(), tempFile, FOLDER_SYS==folder); QFile::remove(tempFile); } else error(EACCES==errno ? KIO::ERR_WRITE_ACCESS_DENIED : KIO::ERR_CANNOT_OPEN_FOR_WRITING, dest.fileName()); } } void CKioFonts::get(const QUrl &url) { KFI_DBUG << url; QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); EFolder folder(getFolder(pathList)); Family family(getFont(url, folder)); if(!family.name().isEmpty() && 1==family.styles().count()) { StyleCont::ConstIterator style(family.styles().begin()); FileCont::ConstIterator it((*style).files().begin()), end((*style).files().end()); // // The thumbnail job always downloads non-local files to /tmp/... and passes this file name to // the thumbnail creator. However, in the case of fonts which are split among many files, this // wont work. Therefore, when the thumbnail code asks for the font to download, just return // the family and style info for enabled fonts, and the filename for disabled fonts. This way // the font-thumbnail creator can read this and just ask Xft/fontconfig for the font data. if("1"==metaData("thumbnail")) { QByteArray array; QTextStream stream(&array, QIODevice::WriteOnly); emit mimeType("text/plain"); bool hidden(true); for(; it!=end && hidden; ++it) if(!Misc::isHidden(Misc::getFile((*it).path()))) hidden=false; if(hidden) { // // OK, its a disabled font - if possible try to return the location of the font file // itself. bool found=false; it=(*style).files().begin(); end=(*style).files().end(); for(; it!=end && hidden; ++it) if(isScalable((*it).path())) { KFI_DBUG << "hasMetaData(\"thumbnail\"), so return FILE: " << (*it).path() << " / " << (*it).index(); - stream << KFI_PATH_KEY << (*it).path() << endl - << KFI_FACE_KEY << (*it).index() << endl; + stream << KFI_PATH_KEY << (*it).path() << Qt::endl + << KFI_FACE_KEY << (*it).index() << Qt::endl; found=true; break; } if(!found) { KFI_DBUG << "hasMetaData(\"thumbnail\"), so return Url: " << url; stream << url.toDisplayString(); } } else { KFI_DBUG << "hasMetaData(\"thumbnail\"), so return DETAILS: " << family.name() << " / " << (*style).value(); - stream << KFI_NAME_KEY << family.name() << endl - << KFI_STYLE_KEY << (*style).value() << endl; + stream << KFI_NAME_KEY << family.name() << Qt::endl + << KFI_STYLE_KEY << (*style).value() << Qt::endl; } totalSize(array.size()); data(array); processedSize(array.size()); data(QByteArray()); processedSize(array.size()); finished(); KFI_DBUG << "Finished thumbnail..."; return; } QSet files; QString realPath; QT_STATBUF buff; bool multiple=false; for(; it!=end; ++it) { QStringList assoc; files.insert((*it).path()); Misc::getAssociatedFiles((*it).path(), assoc); QStringList::ConstIterator ait(assoc.constBegin()), aend(assoc.constEnd()); for(; ait!=aend; ++ait) files.insert(*ait); } if(1==files.count()) realPath=(*files.begin()); else // Font is made up of multiple files - so create .zip of them all! { QTemporaryFile tmpFile; if(tmpFile.open()) { KZip zip(tmpFile.fileName()); tmpFile.setAutoRemove(false); realPath=tmpFile.fileName(); if(zip.open(QIODevice::WriteOnly)) { QMap map=Misc::getFontFileMap(files); QMap::ConstIterator it(map.constBegin()), end(map.constEnd()); for(; it!=end; ++it) zip.addLocalFile(it.value(), it.key()); multiple=true; zip.close(); } } } QByteArray realPathC(QFile::encodeName(realPath)); KFI_DBUG << "real: " << realPathC; if (-2==QT_STAT(realPathC.constData(), &buff)) error(EACCES==errno ? KIO::ERR_ACCESS_DENIED : KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); else if (S_ISDIR(buff.st_mode)) error(KIO::ERR_IS_DIRECTORY, url.toDisplayString()); else if (!S_ISREG(buff.st_mode)) error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.toDisplayString()); else { int fd = QT_OPEN(realPathC.constData(), O_RDONLY); if (fd < 0) error(KIO::ERR_CANNOT_OPEN_FOR_READING, url.toDisplayString()); else { // Determine the mimetype of the file to be retrieved, and emit it. // This is mandatory in all slaves (for KRun/BrowserRun to work). // This code can be optimized by using QFileInfo instead of buff above // and passing it to mimeTypeForFile() instead of realPath. QMimeDatabase db; emit mimeType(db.mimeTypeForFile(realPath).name()); totalSize(buff.st_size); KIO::filesize_t processed=0; char buffer[MAX_IPC_SIZE]; QByteArray array; while(1) { int n=::read(fd, buffer, MAX_IPC_SIZE); if (-1==n) { if (EINTR==errno) continue; error(KIO::ERR_CANNOT_READ, url.toDisplayString()); ::close(fd); if(multiple) ::unlink(realPathC); return; } if (0==n) break; // Finished array=array.fromRawData(buffer, n); data(array); array.clear(); processed+=n; processedSize(processed); } data(QByteArray()); ::close(fd); processedSize(buff.st_size); finished(); } } if(multiple) ::unlink(realPathC); } else error(KIO::ERR_CANNOT_READ, url.toDisplayString()); } void CKioFonts::copy(const QUrl &, const QUrl &, int, KIO::JobFlags) { error(KIO::ERR_SLAVE_DEFINED, i18n("Cannot copy fonts")); } void CKioFonts::rename(const QUrl &, const QUrl &, KIO::JobFlags) { error(KIO::ERR_SLAVE_DEFINED, i18n("Cannot move fonts")); } void CKioFonts::del(const QUrl &url, bool isFile) { KFI_DBUG << url; QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); EFolder folder(getFolder(pathList)); QString name(removeKnownExtension(url)); if(!isFile) error(KIO::ERR_SLAVE_DEFINED, i18n("Only fonts may be deleted.")); else if(!Misc::root() && FOLDER_ROOT==folder) error(KIO::ERR_SLAVE_DEFINED, i18n("Can only remove fonts from either \"%1\" or \"%2\".", i18n(KFI_KIO_FONTS_USER), i18n(KFI_KIO_FONTS_SYS))); else if(!name.isEmpty()) handleResp(itsInterface->uninstall(name, Misc::root() || FOLDER_SYS==folder), name); else error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); } void CKioFonts::stat(const QUrl &url) { KFI_DBUG << url; - QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); + QStringList pathList(url.adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), Qt::SkipEmptyParts)); EFolder folder=getFolder(pathList); KIO::UDSEntry entry; bool ok=true; switch(pathList.count()) { case 0: createUDSEntry(entry, FOLDER_ROOT); break; case 1: if(Misc::root()) ok=createStatEntry(entry, url, FOLDER_SYS); else if(FOLDER_SYS==folder || FOLDER_USER==folder) createUDSEntry(entry, folder); else { error(KIO::ERR_SLAVE_DEFINED, i18n("Please specify \"%1\" or \"%2\".", i18n(KFI_KIO_FONTS_USER), i18n(KFI_KIO_FONTS_SYS))); return; } break; default: ok=createStatEntry(entry, url, folder); } if(ok) { statEntry(entry); finished(); } else { error(KIO::ERR_DOES_NOT_EXIST, url.toDisplayString()); return; } } void CKioFonts::special(const QByteArray &a) { if(!a.isEmpty()) error(KIO::ERR_UNSUPPORTED_ACTION, i18n("No special methods supported.")); else { setTimeoutSpecialCommand(-1); itsInterface->reconfigure(); } } int CKioFonts::listFolder(KIO::UDSEntry &entry, EFolder folder) { KFI_DBUG << folder; int styleCount(0); KFI::Families families(itsInterface->list(FOLDER_SYS==folder)); FamilyCont::ConstIterator family(families.items.begin()), end(families.items.end()); KFI_DBUG << "Num families:" << families.items.count(); for(; family!=end; ++family) { StyleCont::ConstIterator styleIt((*family).styles().begin()), styleEnd((*family).styles().end()); styleCount+=(*family).styles().count(); for(; styleIt!=styleEnd; ++styleIt) { createUDSEntry(entry, folder, *family, *styleIt); listEntry(entry); } } totalSize(styleCount); return styleCount; } QString CKioFonts::getUserName(uid_t uid) { if (!itsUserCache.contains(uid)) { struct passwd *user = getpwuid(uid); if(user) itsUserCache.insert(uid, QString::fromLatin1(user->pw_name)); else return QString::number(uid); } return itsUserCache[uid]; } QString CKioFonts::getGroupName(gid_t gid) { if (!itsGroupCache.contains(gid)) { struct group *grp = getgrgid(gid); if(grp) itsGroupCache.insert(gid, QString::fromLatin1(grp->gr_name)); else return QString::number(gid); } return itsGroupCache[gid]; } bool CKioFonts::createStatEntry(KIO::UDSEntry &entry, const QUrl &url, EFolder folder) { Family fam(getFont(url, folder)); if(!fam.name().isEmpty() && 1==fam.styles().count()) { createUDSEntry(entry, folder, fam, *fam.styles().begin()); return true; } return false; } void CKioFonts::createUDSEntry(KIO::UDSEntry &entry, EFolder folder) { KFI_DBUG << QString(FOLDER_SYS==folder ? i18n(KFI_KIO_FONTS_SYS) : i18n(KFI_KIO_FONTS_USER)); entry.clear(); entry.fastInsert(KIO::UDSEntry::UDS_NAME, FOLDER_ROOT==folder || Misc::root() ? i18n("Fonts") : FOLDER_SYS==folder ? i18n(KFI_KIO_FONTS_SYS) : i18n(KFI_KIO_FONTS_USER)); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, !Misc::root() && FOLDER_SYS==folder ? 0444 : 0744); entry.fastInsert(KIO::UDSEntry::UDS_USER, Misc::root() || FOLDER_SYS==folder ? QString::fromLatin1("root") : getUserName(getuid())); entry.fastInsert(KIO::UDSEntry::UDS_GROUP, Misc::root() || FOLDER_SYS==folder ? QString::fromLatin1("root") : getGroupName(getgid())); entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR); entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QString::fromLatin1("inode/directory")); } bool CKioFonts::createUDSEntry(KIO::UDSEntry &entry, EFolder folder, const Family &family, const Style &style) { int size=0; QString name(FC::createName(family.name(), style.value())); FileCont::ConstIterator file(style.files().begin()), fileEnd(style.files().end()); QList files; bool hidden=true, haveExtraFiles=false; KFI_DBUG << name; for(; file!=fileEnd; ++file) { size+=getSize((*file).path()); // TODO: Make scalable a property of the file? // Then isScalable() is not needed!!! if(isScalable((*file).path())) files.prepend(*file); else files.append(*file); if(hidden && !Misc::isHidden(Misc::getFile((*file).path()))) hidden=false; QStringList assoc; Misc::getAssociatedFiles((*file).path(), assoc); QStringList::ConstIterator oit(assoc.constBegin()), oend(assoc.constEnd()); if(!haveExtraFiles && !assoc.isEmpty()) haveExtraFiles=true; for(; oit!=oend; ++oit) size+=getSize(*oit); } entry.clear(); entry.fastInsert(KIO::UDSEntry::UDS_NAME, name); entry.fastInsert(KIO::UDSEntry::UDS_SIZE, size); entry.fastInsert(UDS_EXTRA_FC_STYLE, style.value()); QList::ConstIterator it(files.constBegin()), end(files.constEnd()); for(; it!=end; ++it) { QByteArray cPath(QFile::encodeName((*it).path())); QT_STATBUF buff; if(-1!=QT_LSTAT(cPath, &buff)) { QString fileName(Misc::getFile((*it).path())), mt; int dotPos(fileName.lastIndexOf('.')); QString extension(-1==dotPos ? QString() : fileName.mid(dotPos)); if(QString::fromLatin1(".gz")==extension) { dotPos=fileName.lastIndexOf('.', dotPos-1); extension=-1==dotPos ? QString() : fileName.mid(dotPos); } if(QString::fromLatin1(".ttf")==extension || QString::fromLatin1(".ttc")==extension) mt="application/x-font-ttf"; else if(QString::fromLatin1(".otf")==extension) mt="application/x-font-otf"; else if(QString::fromLatin1(".pfa")==extension || QString::fromLatin1(".pfb")==extension) mt="application/x-font-type1"; else if(QString::fromLatin1(".pcf.gz")==extension || QString::fromLatin1(".pcf")==extension) mt="application/x-font-pcf"; else if(QString::fromLatin1(".bdf.gz")==extension || QString::fromLatin1(".bdf")==extension) mt="application/x-font-bdf"; else { // File extension check failed, use QMimeDatabase to read contents... QMimeDatabase db; QMimeType mime = db.mimeTypeForFile((*it).path()); QStringList patterns = mime.globPatterns(); mt = mime.name(); if(patterns.size()>0) extension=(*patterns.begin()).remove("*"); } entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, buff.st_mode&S_IFMT); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, buff.st_mode&07777); entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, buff.st_mtime); entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, buff.st_atime); entry.fastInsert(KIO::UDSEntry::UDS_USER, getUserName(buff.st_uid)); entry.fastInsert(KIO::UDSEntry::UDS_GROUP, getGroupName(buff.st_gid)); entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, mt); if(hidden) { entry.fastInsert(KIO::UDSEntry::UDS_HIDDEN, 1); entry.fastInsert(UDS_EXTRA_FILE_NAME, (*it).path()); entry.fastInsert(UDS_EXTRA_FILE_FACE, (*it).path()); } QString path(QString::fromLatin1("/")); if(!Misc::root()) { path+=FOLDER_SYS==folder ? i18n(KFI_KIO_FONTS_SYS) : i18n(KFI_KIO_FONTS_USER); path+=QString::fromLatin1("/"); } path+=name; if(files.count()>1 || haveExtraFiles) path+=QString::fromLatin1(KFI_FONTS_PACKAGE); else path+=extension; QUrl url(QUrl::fromLocalFile(path)); url.setScheme(KFI_KIO_FONTS_PROTOCOL); entry.fastInsert(KIO::UDSEntry::UDS_URL, url.url()); return true; } } return false; } Family CKioFonts::getFont(const QUrl &url, EFolder folder) { QString name(removeKnownExtension(url)); KFI_DBUG << url << name; return itsInterface->statFont(name, FOLDER_SYS==folder); } void CKioFonts::handleResp(int resp, const QString &file, const QString &tempFile, bool destIsSystem) { switch(resp) { case FontInst::STATUS_NO_SYS_CONNECTION: error(KIO::ERR_SLAVE_DEFINED, i18n("Failed to start the system daemon")); break; case FontInst::STATUS_SERVICE_DIED: error(KIO::ERR_SLAVE_DEFINED, i18n("Backend died")); break; case FontInst::STATUS_BITMAPS_DISABLED: error(KIO::ERR_SLAVE_DEFINED, i18n("%1 is a bitmap font, and these have been disabled on your system.", file)); break; case FontInst::STATUS_ALREADY_INSTALLED: error(KIO::ERR_SLAVE_DEFINED, i18n("%1 contains the font %2, which is already installed on your system.", file, FC::getName(tempFile))); break; case FontInst::STATUS_NOT_FONT_FILE: error(KIO::ERR_SLAVE_DEFINED, i18n("%1 is not a font.", file)); break; case FontInst::STATUS_PARTIAL_DELETE: error(KIO::ERR_SLAVE_DEFINED, i18n("Could not remove all files associated with %1", file)); break; case KIO::ERR_FILE_ALREADY_EXIST: { QString name(Misc::modifyName(file)), destFolder(Misc::getDestFolder(itsInterface->folderName(destIsSystem), name)); error(KIO::ERR_SLAVE_DEFINED, i18n("%1 already exists.", destFolder+name)); break; } case FontInst::STATUS_OK: finished(); break; default: error(resp, file); } if(FontInst::STATUS_OK==resp) setTimeoutSpecialCommand(constReconfigTimeout); } } diff --git a/kcms/kfontinst/lib/FcEngine.cpp b/kcms/kfontinst/lib/FcEngine.cpp index 8e2ce6b87..36ecf65c5 100644 --- a/kcms/kfontinst/lib/FcEngine.cpp +++ b/kcms/kfontinst/lib/FcEngine.cpp @@ -1,1564 +1,1561 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "FcEngine.h" #include #include #include #include #include #include #include #include #include #include "File.h" #include #include #include #include //#define KFI_FC_DEBUG #define KFI_PREVIEW_GROUP "KFontInst Preview Settings" #define KFI_PREVIEW_STRING_KEY "String" namespace KFI { bool CFcEngine::theirFcDirty(true); const int CFcEngine::constScalableSizes[]={8, 10, 12, 24, 36, 48, 64, 72, 96, 0 }; const int CFcEngine::constDefaultAlphaSize=24; static int fcToQtWeight(int weight) { switch(weight) { case FC_WEIGHT_THIN: return 0; case FC_WEIGHT_EXTRALIGHT: return QFont::Light>>1; case FC_WEIGHT_LIGHT: return QFont::Light; default: case FC_WEIGHT_REGULAR: return QFont::Normal; case FC_WEIGHT_MEDIUM: #ifdef KFI_HAVE_MEDIUM_WEIGHT return (QFont::Normal+QFont::DemiBold)>>1; #endif return QFont::Normal; case FC_WEIGHT_DEMIBOLD: return QFont::DemiBold; case FC_WEIGHT_BOLD: return QFont::Bold; case FC_WEIGHT_EXTRABOLD: return (QFont::Bold+QFont::Black)>>1; case FC_WEIGHT_BLACK: return QFont::Black; } } #ifndef KFI_FC_NO_WIDTHS static int fcToQtWidth(int weight) { switch(weight) { case KFI_FC_WIDTH_ULTRACONDENSED: return QFont::UltraCondensed; case KFI_FC_WIDTH_EXTRACONDENSED: return QFont::ExtraCondensed; case KFI_FC_WIDTH_CONDENSED: return QFont::Condensed; case KFI_FC_WIDTH_SEMICONDENSED: return QFont::SemiCondensed; default: case KFI_FC_WIDTH_NORMAL: return QFont::Unstretched; case KFI_FC_WIDTH_SEMIEXPANDED: return QFont::SemiExpanded; case KFI_FC_WIDTH_EXPANDED: return QFont::Expanded; case KFI_FC_WIDTH_EXTRAEXPANDED: return QFont::ExtraExpanded; case KFI_FC_WIDTH_ULTRAEXPANDED: return QFont::UltraExpanded; } } #endif static bool fcToQtSlant(int slant) { return FC_SLANT_ROMAN==slant ? false : true; } inline bool equal(double d1, double d2) { return (fabs(d1 - d2) < 0.0001); } inline bool equalWeight(int a, int b) { return a==b || FC::weight(a)==FC::weight(b); } #ifndef KFI_FC_NO_WIDTHS inline bool equalWidth(int a, int b) { return a==b || FC::width(a)==FC::width(b); } #endif inline bool equalSlant(int a, int b) { return a==b || FC::slant(a)==FC::slant(b); } static void closeFont(XftFont *&font) { if(font) XftFontClose(QX11Info::display(), font); font= nullptr; } class CFcEngine::Xft { public: struct Pix { Pix() : currentW(0), currentH(0), allocatedW(0), allocatedH(0) { } static int getSize(int s) { static const int constBlockSize=64; return ((s/constBlockSize)+(s%constBlockSize ? 1 : 0))*constBlockSize; } bool allocate(int w, int h) { int requiredW=getSize(w), requiredH=getSize(h); currentW=w; currentH=h; if(requiredW!=allocatedW || requiredH!=allocatedH) { free(); if(w && h) { allocatedW=requiredW; allocatedH=requiredH; x11=XCreatePixmap(QX11Info::display(), RootWindow(QX11Info::display(), 0), allocatedW, allocatedH, DefaultDepth(QX11Info::display(), 0)); return true; } } return false; } void free() { if(allocatedW && allocatedH) { XFreePixmap(QX11Info::display(), x11); allocatedW=allocatedH=0; } } int currentW, currentH, allocatedW, allocatedH; Pixmap x11; }; Xft(); ~Xft(); bool init(const QColor &txt, const QColor &bgnd, int w, int h); void freeColors(); bool drawChar32Centre(XftFont *xftFont, quint32 ch, int w, int h) const; bool drawChar32(XftFont *xftFont, quint32 ch,int &x, int &y, int w, int h, int fontHeight, QRect &r) const; bool drawString(XftFont *xftFont, const QString &text, int x, int &y, int h) const; void drawString(const QString &text, int x, int &y, int h) const; bool drawGlyph(XftFont *xftFont, FT_UInt i, int &x, int &y, int w, int h, int fontHeight,bool oneLine, QRect &r) const; bool drawAllGlyphs(XftFont *xftFont, int fontHeight, int &x, int &y, int w, int h, bool oneLine=false, int max=-1, QRect *used= nullptr) const; bool drawAllChars(XftFont *xftFont, int fontHeight, int &x, int &y, int w, int h, bool oneLine=false, int max=-1, QRect *used= nullptr) const; QImage toImage(int w, int h) const; private: XftDraw *itsDraw; XftColor itsTxtColor, itsBgndColor; Pix itsPix; QImage::Format imageFormat; }; CFcEngine::Xft::Xft() { itsDraw= nullptr; itsTxtColor.color.alpha=0x0000; init(Qt::black, Qt::white, 64, 64); } CFcEngine::Xft::~Xft() { freeColors(); if(itsDraw) { XftDrawDestroy(itsDraw); itsDraw= nullptr; } } bool CFcEngine::Xft::init(const QColor &txt, const QColor &bnd, int w, int h) { if(itsDraw && (txt.red()<<8 != itsTxtColor.color.red || txt.green()<<8 != itsTxtColor.color.green || txt.blue()<<8 != itsTxtColor.color.blue || bnd.red()<<8 != itsBgndColor.color.red || bnd.green()<<8 != itsBgndColor.color.green || bnd.blue()<<8 != itsBgndColor.color.blue)) freeColors(); if(0x0000==itsTxtColor.color.alpha) { XRenderColor xrenderCol; Visual *visual=DefaultVisual(QX11Info::display(), 0); Colormap colorMap=DefaultColormap(QX11Info::display(), 0); xrenderCol.red=bnd.red()<<8; xrenderCol.green=bnd.green()<<8; xrenderCol.blue=bnd.green()<<8; xrenderCol.alpha=0xFFFF; XftColorAllocValue(QX11Info::display(), visual, colorMap, &xrenderCol, &itsBgndColor); xrenderCol.red=txt.red()<<8; xrenderCol.green=txt.green()<<8; xrenderCol.blue=txt.green()<<8; xrenderCol.alpha=0xFFFF; XftColorAllocValue(QX11Info::display(), visual, colorMap, &xrenderCol, &itsTxtColor); } XVisualInfo defaultVinfo; defaultVinfo.depth = DefaultDepth(QX11Info::display(), 0); // normal/failsafe imageFormat = QImage::Format_RGB32; // 24bit switch (defaultVinfo.depth) { case 32: imageFormat = QImage::Format_ARGB32_Premultiplied; break; case 30: imageFormat = QImage::Format_RGB30; break; case 16: imageFormat = QImage::Format_RGB16; break; case 8: imageFormat = QImage::Format_Grayscale8; break; default: break; } if (defaultVinfo.depth == 30 || defaultVinfo.depth == 32) { // detect correct format int num_vinfo = 0; defaultVinfo.visual = DefaultVisual(QX11Info::display(), 0); defaultVinfo.screen = 0; defaultVinfo.visualid = XVisualIDFromVisual(defaultVinfo.visual); XVisualInfo *vinfo = XGetVisualInfo(QX11Info::display(), VisualIDMask|VisualScreenMask|VisualDepthMask, &defaultVinfo, &num_vinfo); for (int i = 0; i < num_vinfo; ++i) { if (vinfo[i].visual == defaultVinfo.visual) { if (defaultVinfo.depth == 30) { if (vinfo[i].red_mask == 0x3ff) imageFormat = QImage::Format_BGR30; else if (vinfo[i].blue_mask == 0x3ff) imageFormat = QImage::Format_RGB30; } else if (defaultVinfo.depth == 32) { if (vinfo[i].blue_mask == 0xff) imageFormat = QImage::Format_ARGB32_Premultiplied; else if (vinfo[i].red_mask == 0x3ff) imageFormat = QImage::Format_A2BGR30_Premultiplied; else if (vinfo[i].blue_mask == 0x3ff) imageFormat = QImage::Format_A2RGB30_Premultiplied; } break; } } XFree(vinfo); } if(itsPix.allocate(w, h) && itsDraw) XftDrawChange(itsDraw, itsPix.x11); if(!itsDraw) itsDraw=XftDrawCreate(QX11Info::display(), itsPix.x11, DefaultVisual(QX11Info::display(), 0), DefaultColormap(QX11Info::display(), 0)); if(itsDraw) XftDrawRect(itsDraw, &itsBgndColor, 0, 0, w, h); return itsDraw; } void CFcEngine::Xft::freeColors() { XftColorFree(QX11Info::display(), DefaultVisual(QX11Info::display(), 0), DefaultColormap(QX11Info::display(), 0), &itsTxtColor); XftColorFree(QX11Info::display(), DefaultVisual(QX11Info::display(), 0), DefaultColormap(QX11Info::display(), 0), &itsBgndColor); itsTxtColor.color.alpha=0x0000; } bool CFcEngine::Xft::drawChar32Centre(XftFont *xftFont, quint32 ch, int w, int h) const { if(XftCharExists(QX11Info::display(), xftFont, ch)) { XGlyphInfo extents; XftTextExtents32(QX11Info::display(), xftFont, &ch, 1, &extents); int rx(((w-extents.width)/2)+extents.x), ry(((h-extents.height)/2)+(extents.y)); XftDrawString32(itsDraw, &itsTxtColor, xftFont, rx, ry, &ch, 1); return true; } return false; } static const int constBorder=2; bool CFcEngine::Xft::drawChar32(XftFont *xftFont, quint32 ch, int &x, int &y, int w, int h, int fontHeight, QRect &r) const { r=QRect(); if(XftCharExists(QX11Info::display(), xftFont, ch)) { XGlyphInfo extents; XftTextExtents32(QX11Info::display(), xftFont, &ch, 1, &extents); if(extents.x>0) x+=extents.x; if(x+extents.width+constBorder>w) { x=0; if(extents.x>0) x+=extents.x; y+=fontHeight+constBorder; } if(y0) { y+=extents.height; return true; } return false; } void CFcEngine::Xft::drawString(const QString &text, int x, int &y, int h) const { QFont qt(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); XftFont *xftFont=XftFontOpen(QX11Info::display(), 0, FC_FAMILY, FcTypeString, (const FcChar8 *)(qt.family().toUtf8().data()), FC_WEIGHT, FcTypeInteger, qt.bold() ? FC_WEIGHT_BOLD : FC_WEIGHT_REGULAR, FC_SLANT, FcTypeInteger, qt.italic() ? FC_SLANT_ITALIC : FC_SLANT_ROMAN, FC_SIZE, FcTypeDouble, (double)qt.pointSize(), NULL); if(xftFont) { drawString(xftFont, text, x, y, h); closeFont(xftFont); } } bool CFcEngine::Xft::drawGlyph(XftFont *xftFont, FT_UInt i, int &x, int &y, int w, int h, int fontHeight, bool oneLine, QRect &r) const { XGlyphInfo extents; XftGlyphExtents(QX11Info::display(), xftFont, &i, 1, &extents); if(0==extents.width || 0==extents.height) { r=QRect(0, 0, 0, 0); return true; } if(x+extents.width+2>w) { if(oneLine) return false; x=0; y+=fontHeight+2; } if(ynum_glyphs && y0) { if(used) { if(used->isEmpty()) *used=r; else *used=used->united(r); } if(max>0 && ++drawn>=max) break; } } else break; if(oneLine) x=0; XftUnlockFace(xftFont); } } return rv; } bool CFcEngine::Xft::drawAllChars(XftFont *xftFont, int fontHeight, int &x, int &y, int w, int h, bool oneLine, int max, QRect *used) const { bool rv(false); if(xftFont) { FT_Face face=XftLockFace(xftFont); if(face) { int space(fontHeight/10), drawn(0); QRect r; if(!space) space=1; rv=true; y+=fontHeight; FT_Select_Charmap(face, FT_ENCODING_UNICODE); for(int cmap=0; cmapnum_charmaps; ++cmap) if(face->charmaps[cmap] && FT_ENCODING_ADOBE_CUSTOM==face->charmaps[cmap]->encoding) { FT_Select_Charmap(face, FT_ENCODING_ADOBE_CUSTOM); break; } for(unsigned int i=1; i<65535 && y0) { if(used) { if(used->isEmpty()) *used=r; else *used=used->united(r); } if(max>0 && ++drawn>=max) break; } } else break; } } if(oneLine) x=0; XftUnlockFace(xftFont); } } return rv; } void cleanupXImage(void *data) { xcb_image_destroy((xcb_image_t*)data); } QImage CFcEngine::Xft::toImage(int w, int h) const { if (!XftDrawPicture(itsDraw)) { return QImage(); } xcb_image_t *xImage = xcb_image_get(QX11Info::connection(), itsPix.x11, 0, 0, itsPix.currentW, itsPix.currentH, ~0, XCB_IMAGE_FORMAT_Z_PIXMAP); if (!xImage) { return QImage(); } return QImage(xImage->data, xImage->width, xImage->height, xImage->stride, imageFormat, &cleanupXImage, xImage); } inline int point2Pixel(int point) { return (point*QX11Info::appDpiX()+36)/72; } static bool hasStr(XftFont *font, QString &str) { unsigned int slen=str.length(), ch; for(ch=0; chcharset, str[ch].unicode())) return false; return true; } static QString usableStr(XftFont *font, QString &str) { unsigned int slen=str.length(), ch; QString newStr; for(ch=0; chcharset, str[ch].unicode())) newStr+=str[ch]; return newStr; } static bool isFileName(const QString &name, quint32 style) { return QChar('/')==name[0] || KFI_NO_STYLE_INFO==style; } static void setTransparentBackground(QImage &img, const QColor &col) { // Convert background to transparent, and text to correct colour... img=img.convertToFormat(QImage::Format_ARGB32); for(int x=0; xh) { origHeight=h; h=bSize+8; } } if(xft()->init(needAlpha ? Qt::black : txt, needAlpha ? Qt::white : bgnd, constInitialWidth, h)) { XftFont *xftFont=getFont(fSize); QString text(itsPreviewString); if(xftFont) { bool rv=false; int usedWidth=0; if(hasStr(xftFont, text) || hasStr(xftFont, text=text.toUpper()) || hasStr(xftFont, text=text.toLower())) { XGlyphInfo extents; const FcChar16 *str=(FcChar16 *)(text.utf16()); XftTextExtents16(QX11Info::display(), xftFont, str, text.length(), &extents); int y=(h-extents.height)/2; rv=xft()->drawString(xftFont, text, constOffset, y, h); usedWidth=extents.width; } else { int x=constOffset, y=constOffset; QRect used; rv=xft()->drawAllGlyphs(xftFont, fSize, x, y, constInitialWidth, h, true, text.length(), &used); if(rv) usedWidth=used.width(); } if(rv) { img=xft()->toImage(constInitialWidth, h); if(!img.isNull()) { if(origHeight) { int width=(int)((usedWidth*(double)(((double)h)/((double)origHeight)))+0.5); img=img.scaledToHeight(origHeight, Qt::SmoothTransformation) .copy(0, 0, width+(2*constOffset)init(needAlpha ? Qt::black : txt, needAlpha ? Qt::white : bgnd, w, h)) { bool rv=false; if(hasStr(xftFont, text) || hasStr(xftFont, text=text.toUpper()) || hasStr(xftFont, text=text.toLower())) { XGlyphInfo extents; const FcChar16 *str=(FcChar16 *)(text.utf16()); XftTextExtents16(QX11Info::display(), xftFont, str, text.length(), &extents); int x=0, y=0; rv=xft()->drawString(xftFont, text, x, y, h); } else { int x=0, y=0; QRect used; rv=xft()->drawAllGlyphs(xftFont, h, x, y, w, h, true, text.length(), &used); } if(rv) { img=xft()->toImage(w, h); if(!img.isNull()) { img=img.copy(0, 0, w, h); if(needAlpha) setTransparentBackground(img, txt); } } } closeFont(xftFont); } } } return img; } QImage CFcEngine::draw(const QString &name, quint32 style, int faceNo, const QColor &txt, const QColor &bgnd, int w, int h, bool thumb, const QList &range, QList *chars) { QImage img; const qreal dpr = qApp->devicePixelRatio(); w = w * dpr; h = h * dpr; bool rv=false; if(chars) chars->clear(); if(!name.isEmpty() && ((name==itsName && style==itsStyle && File::equalIndex(faceNo, itsIndex)) || parse(name, style, faceNo)) ) { // // We allow kio_thumbnail to cache our thumbs. Normal is 128x128, and large is 256x256 // ...if kio_thumbnail asks us for a bigger size, then it is probably the file info dialog, in // which case treat it as a normal preview... if(thumb && (h>256 || w!=h)) thumb=false; int x=0, y=0; getSizes(); if(!itsSizes.isEmpty()) { int imgWidth(thumb && itsScalable ? w*4 : w), imgHeight(thumb && itsScalable ? h*4 : h); bool needAlpha(bgnd.alpha()<255); if(xft()->init(needAlpha ? Qt::black : txt, needAlpha ? Qt::white : bgnd, imgWidth, imgHeight)) { XftFont *xftFont=nullptr; int line1Pos(0), line2Pos(0); QRect used(0, 0, 0, 0); if(thumb) { QString text(itsScalable ? i18nc("First letter of the alphabet (in upper then lower case)", "Aa") : i18nc("All letters of the alphabet (in upper/lower case pairs), followed by numbers", "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789")); // // Calculate size of text... int fSize=h; if(!itsScalable) // Then need to get nearest size... { int bSize=0; for(int s=0; s=(text.length()/2)) break; } if(itsScalable ? valid.length()!=text.length() : valid.length()<(text.length()/2)) xft()->drawAllChars(xftFont, fSize, x, y, imgWidth, imgHeight, true, itsScalable ? 2 : -1, itsScalable ? &used : nullptr); else { QVector ucs4(valid.toUcs4()); QRect r; for(int ch=0; chdrawChar32(xftFont, ucs4[ch], x, y, imgWidth, imgHeight, fSize, r)) { if(used.isEmpty()) used=r; else used=used.united(r); } else break; } closeFont(xftFont); } } else if(0==range.count()) { QString lowercase(getLowercaseLetters()), uppercase(getUppercaseLetters()), punctuation(getPunctuation()); drawName(x, y, h); y+=4; line1Pos=y; y+=8; xftFont=getFont(alphaSize()); if(xftFont) { bool lc(hasStr(xftFont, lowercase)), uc(hasStr(xftFont, uppercase)), drawGlyphs=!lc && !uc; if(drawGlyphs) y-=8; else { QString validPunc(usableStr(xftFont, punctuation)); bool punc(validPunc.length()>=(punctuation.length()/2)); if(lc) xft()->drawString(xftFont, lowercase, x, y, h); if(uc) xft()->drawString(xftFont, uppercase, x, y, h); if(punc) xft()->drawString(xftFont, validPunc, x, y, h); if(lc || uc || punc) line2Pos=y+2; y+=8; } QString previewString(getPreviewString()); if(!drawGlyphs) { if(!lc && uc) previewString=previewString.toUpper(); if(!uc && lc) previewString=previewString.toLower(); } closeFont(xftFont); for(int s=0; sascent+xftFont->descent; rv=true; if(drawGlyphs) xft()->drawAllChars(xftFont, fontHeight, x, y, w, h, itsSizes.count()>1); else xft()->drawString(xftFont, previewString, x, y, h); closeFont(xftFont); } } } else if(1==range.count() && (range.first().null() || 0==range.first().to)) { if(range.first().null()) { drawName(x, y, h); if((xftFont=getFont(alphaSize()))) { int fontHeight=xftFont->ascent+xftFont->descent; xft()->drawAllGlyphs(xftFont, fontHeight, x, y, w, h, false); rv=true; closeFont(xftFont); } } else if((xftFont=getFont(int(imgWidth*0.85)))) { rv=xft()->drawChar32Centre(xftFont, (*(range.begin())).from, imgWidth, imgHeight); closeFont(xftFont); } } else { QList::ConstIterator it(range.begin()), end(range.end()); if((xftFont=getFont(alphaSize()))) { rv=true; drawName(x, y, h); y+=alphaSize(); bool stop=false; int fontHeight=xftFont->ascent+xftFont->descent, xOrig(x), yOrig(y); QRect r; for(it=range.begin(); it!=end && !stop; ++it) for(quint32 c=(*it).from; c<=(*it).to && !stop; ++c) { if(xft()->drawChar32(xftFont, c, x, y, w, h, fontHeight, r)) { if(chars && !r.isEmpty()) chars->append(TChar(r, c)); } else stop=true; } if(x==xOrig && y==yOrig) { // No characters found within the selected range... xft()->drawString(i18n("No characters found."), x, y, h); rv=true; } closeFont(xftFont); } } if(rv) { img=xft()->toImage(imgWidth, imgHeight); if(!img.isNull() && line1Pos) { QPainter p(&img); p.setPen(txt); p.drawLine(0, line1Pos, w-1, line1Pos); if(line2Pos) p.drawLine(0, line2Pos, w-1, line2Pos); } if(!img.isNull()) { if(itsScalable && !used.isEmpty() && (used.width(){}[]"); //krazy:exclude=i18ncheckarg } #ifdef KFI_USE_TRANSLATED_FAMILY_NAME // // Try to get the 'string' that matches the users KDE locale.. QString CFcEngine::getFcLangString(FcPattern *pat, const char *val, const char *valLang) { QString rv; QStringList kdeLangs=KLocale::global()->languageList(), fontLangs; QStringList::ConstIterator it(kdeLangs.begin()), end(kdeLangs.end()); // Create list of langs that this font's 'val' is encoded in... for(int i=0; true; ++i) { QString lang=getFcString(pat, valLang, i); if(lang.isEmpty()) break; else fontLangs.append(lang); } // Now go through the user's KDE locale, and try to find a font match... for(; it!=end; ++it) { int index=fontLangs.findIndex(*it); if(-1!=index) { rv=getFcString(pat, val, index); if(!rv.isEmpty()) break; } } if(rv.isEmpty()) rv=getFcString(pat, val, 0); return rv; } #endif QFont CFcEngine::getQFont(const QString &family, quint32 style, int size) { int weight, width, slant; FC::decomposeStyleVal(style, weight, width, slant); QFont font(family, size, fcToQtWeight(weight), fcToQtSlant(slant)); #ifndef KFI_FC_NO_WIDTHS font.setStretch(fcToQtWidth(width)); #endif return font; } bool CFcEngine::parse(const QString &name, quint32 style, int face) { if(name.isEmpty()) return false; reinit(); itsName=name; itsStyle=style; itsSizes.clear(); itsInstalled=!isFileName(name, style); if(!itsInstalled) { int count; FcPattern *pat=FcFreeTypeQuery((const FcChar8 *)(QFile::encodeName(itsName).data()), face<1 ? 0 : face, nullptr, &count); if(!pat) return false; itsDescriptiveName=FC::createName(pat); FcPatternDestroy(pat); } else itsDescriptiveName=FC::createName(itsName, itsStyle); itsIndex=face<1 ? 0 : face; if(!itsInstalled) // Then add to fontconfig's list, so that Xft can display it... addFontFile(itsName); return true; } XftFont * CFcEngine::queryFont() { static const int constQuerySize=8; #ifdef KFI_FC_DEBUG qDebug(); #endif XftFont *f=getFont(constQuerySize); if(f && !isCorrect(f, true)) closeFont(f); if(itsInstalled && !f) { // Perhaps it is a newly installed font? If so try re-initialising fontconfig... theirFcDirty=true; reinit(); f=getFont(constQuerySize); // This time don't bother checking family - we've re-inited fc anyway, so things should be // up to date... And for "Symbol" Fc returns "Standard Symbols L", so wont match anyway! if(f && !isCorrect(f, false)) closeFont(f); } #ifdef KFI_FC_DEBUG qDebug() << "ret" << (int)f; #endif return f; } XftFont * CFcEngine::getFont(int size) { XftFont *f=nullptr; #ifdef KFI_FC_DEBUG qDebug() << itsName << ' ' << itsStyle << ' ' << size; #endif if(itsInstalled) { int weight, width, slant; FC::decomposeStyleVal(itsStyle, weight, width, slant); #ifndef KFI_FC_NO_WIDTHS if(KFI_NULL_SETTING!=width) f=XftFontOpen(QX11Info::display(), 0, FC_FAMILY, FcTypeString, (const FcChar8 *)(itsName.toUtf8().data()), FC_WEIGHT, FcTypeInteger, weight, FC_SLANT, FcTypeInteger, slant, FC_WIDTH, FcTypeInteger, width, FC_PIXEL_SIZE, FcTypeDouble, (double)size, NULL); else #endif f=XftFontOpen(QX11Info::display(), 0, FC_FAMILY, FcTypeString, (const FcChar8 *)(itsName.toUtf8().data()), FC_WEIGHT, FcTypeInteger, weight, FC_SLANT, FcTypeInteger, slant, FC_PIXEL_SIZE, FcTypeDouble, (double)size, NULL); } else { FcPattern *pattern = FcPatternBuild(nullptr, FC_FILE, FcTypeString, QFile::encodeName(itsName).constData(), FC_INDEX, FcTypeInteger, itsIndex<0 ? 0 : itsIndex, FC_PIXEL_SIZE, FcTypeDouble, (double)size, NULL); f=XftFontOpenPattern(QX11Info::display(), pattern); } #ifdef KFI_FC_DEBUG qDebug() << "ret: " << (int)f; #endif return f; } bool CFcEngine::isCorrect(XftFont *f, bool checkFamily) { int iv, weight, width, slant; FcChar8 *str; if(itsInstalled) FC::decomposeStyleVal(itsStyle, weight, width, slant); #ifdef KFI_FC_DEBUG QString xxx; QTextStream s(&xxx); if(f) { if(itsInstalled) { s << "weight:"; if(FcResultMatch==FcPatternGetInteger(f->pattern, FC_WEIGHT, 0, &iv)) s << iv << '/' << weight; else s << "no"; s << " slant:"; if(FcResultMatch==FcPatternGetInteger(f->pattern, FC_SLANT, 0, &iv)) s << iv << '/' << slant; else s << "no"; s << " width:"; if(FcResultMatch==FcPatternGetInteger(f->pattern, FC_WIDTH, 0, &iv)) s << iv << '/' << width; else s << "no"; s << " fam:"; if(checkFamily) if(FcResultMatch==FcPatternGetString(f->pattern, FC_FAMILY, 0, &str) && str) s << QString::fromUtf8((char *)str) << '/' << itsName; else s << "no"; else s << "ok"; } else s << "NOT Installed... "; } else s << "No font!!! "; qDebug() << "isCorrect? " << xxx; #endif return f ? itsInstalled ? FcResultMatch==FcPatternGetInteger(f->pattern, FC_WEIGHT, 0, &iv) && equalWeight(iv, weight) && FcResultMatch==FcPatternGetInteger(f->pattern, FC_SLANT, 0, &iv) && equalSlant(iv, slant) && #ifndef KFI_FC_NO_WIDTHS (KFI_NULL_SETTING==width || (FcResultMatch==FcPatternGetInteger(f->pattern, FC_WIDTH, 0, &iv) && equalWidth(iv, width))) && #endif (!checkFamily || (FcResultMatch==FcPatternGetString(f->pattern, FC_FAMILY, 0, &str) && str && QString::fromUtf8((char *)str)==itsName)) : (itsIndex<0 || (FcResultMatch==FcPatternGetInteger(f->pattern, FC_INDEX, 0, &iv) && itsIndex==iv)) && FcResultMatch==FcPatternGetString(f->pattern, FC_FILE, 0, &str) && str && QString::fromUtf8((char *)str)==itsName : false; } void CFcEngine::getSizes() { if(!itsSizes.isEmpty()) return; #ifdef KFI_FC_DEBUG qDebug(); #endif XftFont *f=queryFont(); int alphaSize(itsSizes.size()>itsAlphaSizeIndex && itsAlphaSizeIndex>=0 ? itsSizes[itsAlphaSizeIndex] : constDefaultAlphaSize); itsScalable=FcTrue; itsAlphaSizeIndex=0; if(f) { - bool gotSizes=false; double px(0.0); if(itsInstalled) { if(FcResultMatch!=FcPatternGetBool(f->pattern, FC_SCALABLE, 0, &itsScalable)) itsScalable=FcFalse; if(!itsScalable) { FcPattern *pat=nullptr; FcObjectSet *os = FcObjectSetBuild(FC_PIXEL_SIZE, (void*)nullptr); int weight, width, slant; FC::decomposeStyleVal(itsStyle, weight, width, slant); #ifndef KFI_FC_NO_WIDTHS if(KFI_NULL_SETTING!=width) pat=FcPatternBuild(nullptr, FC_FAMILY, FcTypeString, (const FcChar8 *)(itsName.toUtf8().data()), FC_WEIGHT, FcTypeInteger, weight, FC_SLANT, FcTypeInteger, slant, FC_WIDTH, FcTypeInteger, width, NULL); else #endif pat=FcPatternBuild(nullptr, FC_FAMILY, FcTypeString, (const FcChar8 *)(itsName.toUtf8().data()), FC_WEIGHT, FcTypeInteger, weight, FC_SLANT, FcTypeInteger, slant, NULL); FcFontSet *set=FcFontList(nullptr, pat, os); FcPatternDestroy(pat); FcObjectSetDestroy(os); if (set) { int size(0); #ifdef KFI_FC_DEBUG qDebug() << "got fixed sizes: " << set->nfont; #endif itsSizes.reserve(set->nfont); for (int i = 0; i < set->nfont; i++) if(FcResultMatch==FcPatternGetDouble(set->fonts[i], FC_PIXEL_SIZE, 0, &px)) { - gotSizes=true; itsSizes.push_back((int)px); #ifdef KFI_FC_DEBUG qDebug() << "got fixed: " << px; #endif if (px<=alphaSize) itsAlphaSizeIndex=size; size++; } FcFontSetDestroy(set); } } } else { FT_Face face=XftLockFace(f); if(face) { itsIndexCount=face->num_faces; if(!(itsScalable=FT_IS_SCALABLE(face))) { int numSizes=face->num_fixed_sizes, size; - gotSizes=true; itsSizes.reserve(numSizes); #ifdef KFI_FC_DEBUG qDebug() << "numSizes fixed: " << numSizes; #endif for (size=0; size= 20105 double px=face->available_sizes[size].y_ppem>>6; #else double px=face->available_sizes[size].width; #endif #ifdef KFI_FC_DEBUG qDebug() << "px: " << px; #endif itsSizes.push_back((int)px); if (px<=alphaSize) itsAlphaSizeIndex=size; } } XftUnlockFace(f); } } closeFont(f); } if(itsScalable) { itsSizes.reserve(sizeof(constScalableSizes)/sizeof(int)); for (int i=0; constScalableSizes[i]; ++i) { int px=point2Pixel(constScalableSizes[i]); if (px<=alphaSize) itsAlphaSizeIndex=i; itsSizes.push_back(px); } } #ifdef KFI_FC_DEBUG qDebug() << "end"; #endif } void CFcEngine::drawName(int x, int &y, int h) { QString title(itsDescriptiveName.isEmpty() ? i18n("ERROR: Could not determine font's name.") : itsDescriptiveName); if(1==itsSizes.size()) title=i18np("%2 [1 pixel]", "%2 [%1 pixels]", itsSizes[0], title); xft()->drawString(title, x, y, h); } void CFcEngine::addFontFile(const QString &file) { if(!itsAddedFiles.contains(file)) { FcInitReinitialize(); FcConfigAppFontAddFile(FcConfigGetCurrent(), (const FcChar8 *)(QFile::encodeName(file).data())); itsAddedFiles.append(file); } } void CFcEngine::reinit() { if(theirFcDirty) { FcInitReinitialize(); theirFcDirty=false; } } CFcEngine::Xft * CFcEngine::xft() { if(!itsXft) itsXft=new Xft; return itsXft; } } diff --git a/kcms/kfontinst/lib/Style.cpp b/kcms/kfontinst/lib/Style.cpp index 84f37f139..83f94a4f1 100644 --- a/kcms/kfontinst/lib/Style.cpp +++ b/kcms/kfontinst/lib/Style.cpp @@ -1,189 +1,189 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2009 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include "Fc.h" #include "Style.h" #include "WritingSystems.h" #include "XmlStrings.h" namespace KFI { Style::Style(const QDomElement &elem, bool loadFiles) { bool ok; int weight(KFI_NULL_SETTING), width(KFI_NULL_SETTING), slant(KFI_NULL_SETTING), tmp(KFI_NULL_SETTING); if(elem.hasAttribute(WEIGHT_ATTR)) { tmp=elem.attribute(WEIGHT_ATTR).toInt(&ok); if(ok) weight=tmp; } if(elem.hasAttribute(WIDTH_ATTR)) { tmp=elem.attribute(WIDTH_ATTR).toInt(&ok); if(ok) width=tmp; } if(elem.hasAttribute(SLANT_ATTR)) { tmp=elem.attribute(SLANT_ATTR).toInt(&ok); if(ok) slant=tmp; } itsScalable=!elem.hasAttribute(SCALABLE_ATTR) || elem.attribute(SCALABLE_ATTR)!="false"; itsValue=FC::createStyleVal(weight, width, slant); itsWritingSystems=0; if(elem.hasAttribute(LANGS_ATTR)) - itsWritingSystems=WritingSystems::instance()->get(elem.attribute(LANGS_ATTR).split(LANG_SEP, QString::SkipEmptyParts)); + itsWritingSystems=WritingSystems::instance()->get(elem.attribute(LANGS_ATTR).split(LANG_SEP, Qt::SkipEmptyParts)); if(loadFiles) { if(elem.hasAttribute(PATH_ATTR)) { File file(elem, false); if(!file.path().isEmpty()) itsFiles.insert(file); } else { for(QDomNode n=elem.firstChild(); !n.isNull(); n=n.nextSibling()) { QDomElement ent=n.toElement(); if(FILE_TAG==ent.tagName()) { File file(ent, false); if(!file.path().isEmpty()) itsFiles.insert(file); } } } } } QString Style::toXml(bool disabled, const QString &family, QTextStream &s) const { QStringList files; FileCont::ConstIterator it(itsFiles.begin()), end(itsFiles.end()); for(; it!=end; ++it) { QString f((*it).toXml(disabled, s)); if(!f.isEmpty()) files.append(f); } if(files.count()>0) { QString str(" <" FONT_TAG " "); int weight, width, slant; KFI::FC::decomposeStyleVal(itsValue, weight, width, slant); if(!family.isEmpty()) str+=FAMILY_ATTR"=\""+family+"\" "; if(KFI_NULL_SETTING!=weight) str+=WEIGHT_ATTR"=\""+QString::number(weight)+"\" "; if(KFI_NULL_SETTING!=width) str+=WIDTH_ATTR"=\""+QString::number(width)+"\" "; if(KFI_NULL_SETTING!=slant) str+=SLANT_ATTR"=\""+QString::number(slant)+"\" "; if(!itsScalable) str+=SCALABLE_ATTR"=\"false\" "; QStringList ws(WritingSystems::instance()->getLangs(itsWritingSystems)); if(!ws.isEmpty()) str+=LANGS_ATTR"=\""+ws.join(LANG_SEP)+"\" "; if(1==files.count()) str+=(*files.begin())+"/>"; else { QStringList::ConstIterator it(files.begin()), end(files.end()); str+=">\n"; for(; it!=end; ++it) str+=" <" FILE_TAG " "+(*it)+"/>\n"; str+=" "; } return str; } return QString(); } } QDBusArgument & operator<<(QDBusArgument &argument, const KFI::Style &obj) { argument.beginStructure(); argument << obj.value() << obj.scalable() << obj.writingSystems(); argument.beginArray(qMetaTypeId()); KFI::FileCont::ConstIterator it(obj.files().begin()), end(obj.files().end()); for(; it!=end; ++it) argument << *it; argument.endArray(); argument.endStructure(); return argument; } const QDBusArgument & operator>>(const QDBusArgument &argument, KFI::Style &obj) { quint32 value; bool scalable; qulonglong ws; argument.beginStructure(); argument >> value >> scalable >> ws; obj=KFI::Style(value, scalable, ws); argument.beginArray(); while(!argument.atEnd()) { KFI::File f; argument >> f; obj.add(f); } argument.endArray(); argument.endStructure(); return argument; } diff --git a/kcms/kfontinst/thumbnail/FontThumbnail.cpp b/kcms/kfontinst/thumbnail/FontThumbnail.cpp index 6e1f603ee..604a714f2 100644 --- a/kcms/kfontinst/thumbnail/FontThumbnail.cpp +++ b/kcms/kfontinst/thumbnail/FontThumbnail.cpp @@ -1,125 +1,125 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "FontThumbnail.h" #include "KfiConstants.h" #include "FcEngine.h" #include #include #include #include #include #include #include #include #include #include "debug.h" #define KFI_DBUG qCDebug(KCM_KFONTINST_THUMBNAIL) extern "C" { Q_DECL_EXPORT ThumbCreator *new_creator() { return new KFI::CFontThumbnail; } } namespace KFI { CFontThumbnail::CFontThumbnail() { } bool CFontThumbnail::create(const QString &path, int width, int height, QImage &img) { QString realPath(path); QTemporaryDir *tempDir = nullptr; - KFI_DBUG << "Create font thumbnail for:" << path << endl; + KFI_DBUG << "Create font thumbnail for:" << path << Qt::endl; // Is this a appliaction/vnd.kde.fontspackage file? If so, extract 1 scalable font... QMimeDatabase db; if (Misc::isPackage(path) || "application/zip" == db.mimeTypeForFile(path, QMimeDatabase::MatchContent).name()) { KZip zip(path); if(zip.open(QIODevice::ReadOnly)) { const KArchiveDirectory *zipDir=zip.directory(); if(zipDir) { QStringList fonts(zipDir->entries()); if(!fonts.isEmpty()) { QStringList::ConstIterator it(fonts.begin()), end(fonts.end()); for(; it!=end; ++it) { const KArchiveEntry *entry=zipDir->entry(*it); if(entry && entry->isFile()) { delete tempDir; tempDir=new QTemporaryDir(QDir::tempPath() + "/" KFI_TMP_DIR_PREFIX); tempDir->setAutoRemove(true); ((KArchiveFile *)entry)->copyTo(tempDir->path()); QString mime(db.mimeTypeForFile(tempDir->filePath(entry->name())).name()); if(mime=="font/ttf" || mime=="font/otf" || mime=="application/x-font-ttf" || mime=="application/x-font-otf" || mime=="application/x-font-type1") { realPath=tempDir->filePath(entry->name()); break; } else ::unlink(QFile::encodeName(tempDir->filePath(entry->name())).data()); } } } } } } QColor bgnd(Qt::black); bgnd.setAlpha(0); img=itsEngine.draw(realPath, KFI_NO_STYLE_INFO, 0, QApplication::palette().text().color(), bgnd, width, height, true); delete tempDir; return !img.isNull(); } ThumbCreator::Flags CFontThumbnail::flags() const { return None; } } diff --git a/kcms/kfontinst/viewpart/CharTip.cpp b/kcms/kfontinst/viewpart/CharTip.cpp index c1064bc34..78789978f 100644 --- a/kcms/kfontinst/viewpart/CharTip.cpp +++ b/kcms/kfontinst/viewpart/CharTip.cpp @@ -1,296 +1,296 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* * Inspired by konq_filetip.cc * * Copyright (C) 1998, 1999 Torben Weis * Copyright (C) 2000, 2001, 2002 David Faure * Copyright (C) 2004 Martin Koller */ #include "CharTip.h" #include "FontPreview.h" #include "UnicodeCategories.h" #include #include #include #include #include #include #include #include -#include +#include namespace KFI { EUnicodeCategory getCategory(quint32 ucs2) { for(int i=0; UNICODE_INVALID!=constUnicodeCategoryList[i].category; ++i) if(constUnicodeCategoryList[i].start<=ucs2 && constUnicodeCategoryList[i].end>=ucs2) return constUnicodeCategoryList[i].category; return UNICODE_UNASSIGNED; } static QString toStr(EUnicodeCategory cat) { switch (cat) { case UNICODE_CONTROL: return i18n("Other, Control"); case UNICODE_FORMAT: return i18n("Other, Format"); case UNICODE_UNASSIGNED: return i18n("Other, Not Assigned"); case UNICODE_PRIVATE_USE: return i18n("Other, Private Use"); case UNICODE_SURROGATE: return i18n("Other, Surrogate"); case UNICODE_LOWERCASE_LETTER: return i18n("Letter, Lowercase"); case UNICODE_MODIFIER_LETTER: return i18n("Letter, Modifier"); case UNICODE_OTHER_LETTER: return i18n("Letter, Other"); case UNICODE_TITLECASE_LETTER: return i18n("Letter, Titlecase"); case UNICODE_UPPERCASE_LETTER: return i18n("Letter, Uppercase"); case UNICODE_COMBINING_MARK: return i18n("Mark, Spacing Combining"); case UNICODE_ENCLOSING_MARK: return i18n("Mark, Enclosing"); case UNICODE_NON_SPACING_MARK: return i18n("Mark, Non-Spacing"); case UNICODE_DECIMAL_NUMBER: return i18n("Number, Decimal Digit"); case UNICODE_LETTER_NUMBER: return i18n("Number, Letter"); case UNICODE_OTHER_NUMBER: return i18n("Number, Other"); case UNICODE_CONNECT_PUNCTUATION: return i18n("Punctuation, Connector"); case UNICODE_DASH_PUNCTUATION: return i18n("Punctuation, Dash"); case UNICODE_CLOSE_PUNCTUATION: return i18n("Punctuation, Close"); case UNICODE_FINAL_PUNCTUATION: return i18n("Punctuation, Final Quote"); case UNICODE_INITIAL_PUNCTUATION: return i18n("Punctuation, Initial Quote"); case UNICODE_OTHER_PUNCTUATION: return i18n("Punctuation, Other"); case UNICODE_OPEN_PUNCTUATION: return i18n("Punctuation, Open"); case UNICODE_CURRENCY_SYMBOL: return i18n("Symbol, Currency"); case UNICODE_MODIFIER_SYMBOL: return i18n("Symbol, Modifier"); case UNICODE_MATH_SYMBOL: return i18n("Symbol, Math"); case UNICODE_OTHER_SYMBOL: return i18n("Symbol, Other"); case UNICODE_LINE_SEPARATOR: return i18n("Separator, Line"); case UNICODE_PARAGRAPH_SEPARATOR: return i18n("Separator, Paragraph"); case UNICODE_SPACE_SEPARATOR: return i18n("Separator, Space"); default: return ""; } } CCharTip::CCharTip(CFontPreview *parent) : QFrame(nullptr, Qt::ToolTip | Qt::WindowStaysOnTopHint | Qt::X11BypassWindowManagerHint), itsParent(parent) { itsPixmapLabel = new QLabel(this); itsLabel = new QLabel(this); itsTimer = new QTimer(this); QBoxLayout* layout = new QBoxLayout(QBoxLayout::LeftToRight, this); layout->setContentsMargins(8, 8, 8, 8); layout->setSpacing(0); layout->addWidget(itsPixmapLabel); layout->addWidget(itsLabel); setPalette(QToolTip::palette()); setFrameShape(QFrame::Box); setFrameShadow(QFrame::Plain); hide(); } CCharTip::~CCharTip() { } void CCharTip::setItem(const CFcEngine::TChar &ch) { hideTip(); itsItem=ch; itsTimer->disconnect(this); connect(itsTimer, &QTimer::timeout, this, &CCharTip::showTip); itsTimer->setSingleShot(true); itsTimer->start(300); } void CCharTip::showTip() { if(!itsParent->underMouse()) return; static const int constPixSize=96; EUnicodeCategory cat(getCategory(itsItem.ucs4)); QString details(""); details+=""; details+=""; QString str(QString::fromUcs4(&(itsItem.ucs4), 1)); details+=""; details+=""; details+="
    "+i18n("Category")+" "+ toStr(cat)+"
    "+i18n("UCS-4")+" "+ QString().sprintf("U+%4.4X", itsItem.ucs4)+" 
    "+i18n("UTF-16")+" "; const ushort *utf16(str.utf16()); for(int i=0; utf16[i]; ++i) { if(i) details+=' '; details+=QString().sprintf("0x%4.4X", utf16[i]); } details+="
    "+i18n("UTF-8")+" "; QByteArray utf8(str.toUtf8()); for(int i=0; i below is just to stop Qt converting the xml entry into // a character! if ((0x0001 <= itsItem.ucs4 && itsItem.ucs4 <= 0xD7FF) || (0xE000 <= itsItem.ucs4 && itsItem.ucs4 <= 0xFFFD) || (0x10000 <= itsItem.ucs4 && itsItem.ucs4 <= 0x10FFFF)) details+="
    "+i18n("XML Decimal Entity")+" "+ QString().sprintf("&#%d;", itsItem.ucs4)+"
    "; itsLabel->setText(details); QList range; range.append(CFcEngine::TRange(itsItem.ucs4, 0)); QColor bgnd(Qt::white); bgnd.setAlpha(0); QImage img=itsParent->engine()->draw(itsParent->itsFontName, itsParent->itsStyleInfo, itsParent->itsCurrentFace-1, palette().text().color(), bgnd, constPixSize, constPixSize, false, range, nullptr); if(!img.isNull()) itsPixmapLabel->setPixmap(QPixmap::fromImage(img)); else itsPixmapLabel->setPixmap(QPixmap()); itsTimer->disconnect(this); connect(itsTimer, &QTimer::timeout, this, &CCharTip::hideTip); itsTimer->setSingleShot(true); itsTimer->start(15000); qApp->installEventFilter(this); reposition(); show(); } void CCharTip::hideTip() { itsTimer->stop(); qApp->removeEventFilter(this); hide(); } void CCharTip::reposition() { QRect rect(itsItem); rect.moveTopRight(itsParent->mapToGlobal(rect.topRight())); QPoint pos(rect.center()); - QRect desk(QApplication::desktop()->screenGeometry(rect.center())); + QRect desk(QApplication::screenAt(rect.center())->geometry()); if ((rect.center().x() + width()) > desk.right()) { if (pos.x() - width() < 0) pos.setX(0); else pos.setX( pos.x() - width() ); } // should the tooltip be shown above or below the ivi ? if (rect.bottom() + height() > desk.bottom()) pos.setY( rect.top() - height() ); else pos.setY(rect.bottom() + 1); move(pos); update(); } void CCharTip::resizeEvent(QResizeEvent *event) { QFrame::resizeEvent(event); reposition(); } bool CCharTip::eventFilter(QObject *, QEvent *e) { switch (e->type()) { case QEvent::Leave: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::KeyPress: case QEvent::KeyRelease: case QEvent::FocusIn: case QEvent::FocusOut: case QEvent::Wheel: hideTip(); default: break; } return false; } } diff --git a/kcms/kfontinst/viewpart/FontViewPart.cpp b/kcms/kfontinst/viewpart/FontViewPart.cpp index f62a270d0..b41e36924 100644 --- a/kcms/kfontinst/viewpart/FontViewPart.cpp +++ b/kcms/kfontinst/viewpart/FontViewPart.cpp @@ -1,577 +1,577 @@ /* * KFontInst - KDE Font Installer * * Copyright 2003-2007 Craig Drummond * * ---- * * 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) any later version. * * 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "FontViewPart.h" #include "Misc.h" #include "KfiConstants.h" #include "FcEngine.h" #include "PreviewSelectAction.h" #include "FontInstInterface.h" #include "FontInst.h" #include "config-workspace.h" #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 #include "config-fontinst.h" // Enable the following to allow printing of non-installed fonts. Does not seem to work :-( //#define KFI_PRINT_APP_FONTS namespace KFI { static QString getFamily(const QString &font) { int commaPos=font.lastIndexOf(','); return -1==commaPos ? font : font.left(commaPos); } K_PLUGIN_FACTORY(CFontViewPartFactory, registerPlugin();) CFontViewPart::CFontViewPart(QWidget *parentWidget, QObject *parent, const QList &) : KParts::ReadOnlyPart(parent), itsConfig(KSharedConfig::openConfig()), itsProc(nullptr), itsTempDir(nullptr), itsInterface(new FontInstInterface()), itsOpening(false) { // create browser extension (for printing when embedded into browser) itsExtension = new BrowserExtension(this); itsFrame=new QFrame(parentWidget); QFrame *previewFrame=new QFrame(itsFrame); QWidget *controls=new QWidget(itsFrame); // QGroupBox *metaBox=new QGroupBox(i18n("Information:"), controls); itsFaceWidget=new QWidget(controls); QBoxLayout *mainLayout=new QBoxLayout(QBoxLayout::TopToBottom, itsFrame); QBoxLayout *previewLayout=new QBoxLayout(QBoxLayout::LeftToRight, previewFrame), *controlsLayout=new QBoxLayout(QBoxLayout::LeftToRight, controls), *faceLayout=new QBoxLayout(QBoxLayout::LeftToRight, itsFaceWidget); // QBoxLayout *metaLayout=new QBoxLayout(QBoxLayout::LeftToRight, metaBox); // itsMetaLabel=new QLabel(metaBox); // itsMetaLabel->setAlignment(Qt::AlignTop); // metaLayout->addWidget(itsMetaLabel); previewLayout->setContentsMargins(0, 0, 0, 0); previewLayout->setSpacing(0); faceLayout->setContentsMargins(0, 0, 0, 0); controlsLayout->setContentsMargins(0, 0, 0, 0); previewLayout->setSpacing(0); itsFrame->setFrameShape(QFrame::NoFrame); itsFrame->setFocusPolicy(Qt::ClickFocus); previewFrame->setFrameShape(QFrame::StyledPanel); previewFrame->setFrameShadow(QFrame::Sunken); KAboutData aboutData(KFI_NAME, i18n("FontViewPart"), WORKSPACE_VERSION_STRING); setComponentData(aboutData); itsPreview=new CFontPreview(previewFrame); itsPreview->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); itsFaceLabel=new QLabel(i18n("Show Face:"), itsFaceWidget); itsFaceSelector=new QSpinBox(itsFaceWidget); itsFaceSelector->setValue(1); itsInstallButton=new QPushButton(i18n("Install..."), controls); itsInstallButton->setEnabled(false); previewLayout->addWidget(itsPreview); faceLayout->addWidget(itsFaceLabel); faceLayout->addWidget(itsFaceSelector); faceLayout->addItem(new QSpacerItem(faceLayout->spacing(), 0, QSizePolicy::Fixed, QSizePolicy::Fixed)); itsFaceWidget->hide(); itsPreview->engine()->readConfig(*itsConfig); //controlsLayout->addWidget(metaBox); //controlsLayout->addStretch(2); controlsLayout->addWidget(itsFaceWidget); controlsLayout->addStretch(1); controlsLayout->addWidget(itsInstallButton); mainLayout->addWidget(previewFrame); mainLayout->addWidget(controls); connect(itsPreview, &CFontPreview::status, this, &CFontViewPart::previewStatus); connect(itsInstallButton, &QAbstractButton::clicked, this, &CFontViewPart::install); connect(itsFaceSelector, SIGNAL(valueChanged(int)), SLOT(showFace(int))); itsChangeTextAction=actionCollection()->addAction("changeText"); itsChangeTextAction->setIcon(QIcon::fromTheme("edit-rename")); itsChangeTextAction->setText(i18n("Change Text...")); connect(itsChangeTextAction, &QAction::triggered, this, &CFontViewPart::changeText); CPreviewSelectAction *displayTypeAction=new CPreviewSelectAction(this, CPreviewSelectAction::BlocksAndScripts); actionCollection()->addAction("displayType", displayTypeAction); connect(displayTypeAction, &CPreviewSelectAction::range, this, &CFontViewPart::displayType); QAction *zoomIn=actionCollection()->addAction(KStandardAction::ZoomIn, itsPreview, SLOT(zoomIn())), *zoomOut=actionCollection()->addAction(KStandardAction::ZoomOut, itsPreview, SLOT(zoomOut())); connect(itsPreview, &CFontPreview::atMax, zoomIn, &QAction::setDisabled); connect(itsPreview, &CFontPreview::atMin, zoomOut, &QAction::setDisabled); setXMLFile("kfontviewpart.rc"); setWidget(itsFrame); itsExtension->enablePrint(false); FontInst::registerTypes(); connect(itsInterface, &OrgKdeFontinstInterface::status, this, &CFontViewPart::dbusStatus); connect(itsInterface, &OrgKdeFontinstInterface::fontStat, this, &CFontViewPart::fontStat); } CFontViewPart::~CFontViewPart() { delete itsTempDir; itsTempDir=nullptr; delete itsInterface; itsInterface=nullptr; } static inline QUrl mostLocalUrl(const QUrl &url, QWidget *widget) { auto job = KIO::mostLocalUrl(url); KJobWidgets::setWindow(job, widget); job->exec(); return job->mostLocalUrl(); } bool CFontViewPart::openUrl(const QUrl &url) { if (!url.isValid() || !closeUrl()) return false; // itsMetaLabel->setText(QString()); // itsMetaInfo.clear(); itsFontDetails=FC::decode(url); if(!itsFontDetails.family.isEmpty() || KFI_KIO_FONTS_PROTOCOL==url.scheme() || mostLocalUrl(url, itsFrame).isLocalFile()) { setUrl(url); emit started(nullptr); setLocalFilePath(this->url().path()); bool ret=openFile(); if (ret) emit completed(); return ret; } else return ReadOnlyPart::openUrl(url); } bool CFontViewPart::openFile() { // NOTE: Can't do the real open here, as we don't seem to be able to use KIO::NetAccess functions // during initial start-up. Bug report 111535 indicates that calling "konqueror " crashes. itsInstallButton->setEnabled(false); QTimer::singleShot(0, this, &CFontViewPart::timeout); return true; } static inline bool statUrl(const QUrl &url, KIO::UDSEntry *udsEntry) { auto job = KIO::stat(url); job->exec(); if (job->error()) return false; *udsEntry = job->statResult(); return true; } void CFontViewPart::timeout() { if(!itsInstallButton) return; bool isFonts(KFI_KIO_FONTS_PROTOCOL==url().scheme()), showFs(false), package(false); int fileIndex(-1); QString fontFile; // itsMetaUrl=url(); delete itsTempDir; itsTempDir=nullptr; itsOpening=true; if(!itsFontDetails.family.isEmpty()) { emit setWindowCaption(FC::createName(itsFontDetails.family, itsFontDetails.styleInfo)); fontFile=FC::getFile(url()); fileIndex=FC::getIndex(url()); } else if(isFonts) { KIO::UDSEntry udsEntry; bool found = statUrl(url(), &udsEntry); if(!found) { // Check if url is "fonts:/ if so try fonts:/System/, then fonts:/Personal - QStringList pathList(url().adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), QString::SkipEmptyParts)); + QStringList pathList(url().adjusted(QUrl::StripTrailingSlash).path().split(QLatin1Char('/'), Qt::SkipEmptyParts)); if(pathList.count()==1) { found = statUrl(QUrl(QString("fonts:/"+i18n(KFI_KIO_FONTS_SYS)+QLatin1Char('/')+pathList[0])), &udsEntry); if (!found) found = statUrl(QUrl(QString("fonts:/"+i18n(KFI_KIO_FONTS_USER)+QLatin1Char('/')+pathList[0])), &udsEntry); } } if(found) { if(udsEntry.numberValue(KIO::UDSEntry::UDS_HIDDEN, 0)) { fontFile=udsEntry.stringValue(UDS_EXTRA_FILE_NAME); fileIndex=udsEntry.numberValue(UDS_EXTRA_FILE_FACE, 0); } itsFontDetails.family=getFamily(udsEntry.stringValue(KIO::UDSEntry::UDS_NAME)); itsFontDetails.styleInfo=udsEntry.numberValue(UDS_EXTRA_FC_STYLE); emit setWindowCaption(udsEntry.stringValue(KIO::UDSEntry::UDS_NAME)); } else { previewStatus(false); return; } } else { QString path(localFilePath()); // Is this a application/vnd.kde.fontspackage file? If so, extract 1 scalable font... if((package=Misc::isPackage(path))) { KZip zip(path); if(zip.open(QIODevice::ReadOnly)) { const KArchiveDirectory *zipDir=zip.directory(); if(zipDir) { QStringList fonts(zipDir->entries()); if(!fonts.isEmpty()) { QStringList::ConstIterator it(fonts.begin()), end(fonts.end()); for(; it!=end; ++it) { const KArchiveEntry *entry=zipDir->entry(*it); if(entry && entry->isFile()) { delete itsTempDir; itsTempDir=new QTemporaryDir(QDir::tempPath() + "/" KFI_TMP_DIR_PREFIX); itsTempDir->setAutoRemove(true); ((KArchiveFile *)entry)->copyTo(itsTempDir->path()); QMimeDatabase db; QString mime(db.mimeTypeForFile(itsTempDir->filePath(entry->name())).name()); if(mime=="font/ttf" || mime=="font/otf" || mime=="application/x-font-ttf" || mime=="application/x-font-otf" || mime=="application/x-font-type1") { fontFile=itsTempDir->filePath(entry->name()); //setLocalFilePath(itsTempDir->path()+QLatin1Char('/')+entry->name()); // itsMetaUrl=QUrl::fromLocalFile(localFilePath()); break; } else ::unlink(QFile::encodeName(itsTempDir->filePath(entry->name())).data()); } } } } } } } itsInstallButton->setEnabled(false); if(itsFontDetails.family.isEmpty()) emit setWindowCaption(url().toDisplayString()); else FcInitReinitialize(); itsPreview->showFont(!package && itsFontDetails.family.isEmpty() ? localFilePath() : fontFile.isEmpty() ? itsFontDetails.family : fontFile, itsFontDetails.styleInfo, fileIndex); if(!isFonts && itsPreview->engine()->getNumIndexes()>1) { showFs=true; itsFaceSelector->setRange(1, itsPreview->engine()->getNumIndexes()); itsFaceSelector->setSingleStep(1); itsFaceSelector->blockSignals(true); itsFaceSelector->setValue(1); itsFaceSelector->blockSignals(false); } itsFaceWidget->setVisible(showFs); } void CFontViewPart::previewStatus(bool st) { if(itsOpening) { bool printable(false); if(st) { checkInstallable(); if(Misc::app(KFI_PRINTER).isEmpty()) printable=false; if(KFI_KIO_FONTS_PROTOCOL==url().scheme()) printable=!Misc::isHidden(url()); else if(!FC::decode(url()).family.isEmpty()) printable=!Misc::isHidden(FC::getFile(url())); #ifdef KFI_PRINT_APP_FONTS else { // TODO: Make this work! Plus, printing of disabled TTF/OTF's should also be possible! KMimeType::Ptr mime=KMimeType::findByUrl(QUrl::fromLocalFile(localFilePath()), 0, false, true); printable=mime->is("application/x-font-ttf") || mime->is("application/x-font-otf"); } #endif } itsExtension->enablePrint(st && printable); itsOpening=false; } itsChangeTextAction->setEnabled(st); // if(st) // getMetaInfo(itsFaceSelector->isVisible() && itsFaceSelector->value()>0 // ? itsFaceSelector->value()-1 : 0); // else if(!st) KMessageBox::error(itsFrame, i18n("Could not read font.")); } void CFontViewPart::install() { if(!itsProc || QProcess::NotRunning==itsProc->state()) { QStringList args; if(!itsProc) itsProc=new QProcess(this); else itsProc->kill(); QString title = QGuiApplication::applicationDisplayName(); if (title.isEmpty()) title = QCoreApplication::applicationName(); args << "--embed" << QString().sprintf("0x%x", (unsigned int)(itsFrame->window()->winId())) << "--qwindowtitle" << title << "--qwindowicon" << "kfontview" << url().toDisplayString(); connect(itsProc, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(installlStatus())); itsProc->start(Misc::app(KFI_INSTALLER), args); itsInstallButton->setEnabled(false); } } void CFontViewPart::installlStatus() { checkInstallable(); } void CFontViewPart::dbusStatus(int pid, int status) { if(pid==getpid() && FontInst::STATUS_OK!=status) itsInstallButton->setEnabled(false); } void CFontViewPart::fontStat(int pid, const KFI::Family &font) { if(pid==getpid()) itsInstallButton->setEnabled(!Misc::app(KFI_INSTALLER).isEmpty() && font.styles().count()==0); } void CFontViewPart::changeText() { bool status; QString oldStr(itsPreview->engine()->getPreviewString()), newStr(QInputDialog::getText(itsFrame, i18n("Preview String"), i18n("Please enter new string:"), QLineEdit::Normal, oldStr, &status)); if(status && newStr!=oldStr) { itsPreview->engine()->setPreviewString(newStr); itsPreview->engine()->writeConfig(*itsConfig); itsPreview->showFont(); } } void CFontViewPart::print() { QStringList args; QString title = QGuiApplication::applicationDisplayName(); if (title.isEmpty()) title = QCoreApplication::applicationName(); if(!itsFontDetails.family.isEmpty()) { args << "--embed" << QString().sprintf("0x%x", (unsigned int)(itsFrame->window()->winId())) << "--qwindowtitle" << title << "--qwindowicon" << "kfontview" << "--size" << "0" << "--pfont" << QString(itsFontDetails.family+','+QString().setNum(itsFontDetails.styleInfo)); } #ifdef KFI_PRINT_APP_FONTS else args << "--embed" << QString().sprintf("0x%x", (unsigned int)(itsFrame->window()->winId())) << "--qwindowtitle" << title << "--qwindowicon" << "kfontview" << "--size " << "0" << localFilePath() << QString().setNum(KFI_NO_STYLE_INFO); #endif if(!args.isEmpty()) QProcess::startDetached(Misc::app(KFI_PRINTER), args); } void CFontViewPart::displayType(const QList &range) { itsPreview->setUnicodeRange(range); itsChangeTextAction->setEnabled(0==range.count()); } void CFontViewPart::showFace(int face) { itsPreview->showFace(face-1); } void CFontViewPart::checkInstallable() { if(itsFontDetails.family.isEmpty()) { if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(OrgKdeFontinstInterface::staticInterfaceName())) QProcess::startDetached(QLatin1String(KFONTINST_LIB_EXEC_DIR"/fontinst"), QStringList()); itsInstallButton->setEnabled(false); itsInterface->statFont(itsPreview->engine()->descriptiveName(), FontInst::SYS_MASK|FontInst::USR_MASK, getpid()); } } #if 0 void CFontViewPart::getMetaInfo(int face) { if(itsMetaInfo[face].isEmpty()) { // Pass as much inofmration as possible to analyzer... if(KFI_KIO_FONTS_PROTOCOL!=itsMetaUrl.protocol()) { itsMetaUrl.removeQueryItem(KFI_KIO_FACE); if(face>0) itsMetaUrl.addQueryItem(KFI_KIO_FACE, QString().setNum(face)); } KFileMetaInfo meta(itsMetaUrl); if(meta.isValid() && meta.keys().count()) { QStringList keys(meta.keys()); QStringList::const_iterator it(keys.begin()), end(keys.end()); itsMetaInfo[face]=""; for(; it!=end; ++it) { KFileMetaInfoItem mi(meta.item(*it)); itsMetaInfo[face]+=""; } itsMetaInfo[face]+="
    "+mi.name()+"
    "+ mi.value().toString()+"
    "; itsMetaLabel->setText(itsMetaInfo[face]); } else itsMetaLabel->setText(i18n("

    No information

    ")); } else itsMetaLabel->setText(itsMetaInfo[face]); } #endif BrowserExtension::BrowserExtension(CFontViewPart *parent) : KParts::BrowserExtension(parent) { setURLDropHandlingEnabled(true); } void BrowserExtension::enablePrint(bool enable) { if(enable!=isActionEnabled("print") && (!enable || !Misc::app(KFI_PRINTER).isEmpty())) emit enableAction("print", enable); } void BrowserExtension::print() { if(!Misc::app(KFI_PRINTER).isEmpty()) static_cast(parent())->print(); } } #include "FontViewPart.moc" diff --git a/kcms/krdb/krdb.cpp b/kcms/krdb/krdb.cpp index e98a35a5c..c4e205bd8 100644 --- a/kcms/krdb/krdb.cpp +++ b/kcms/krdb/krdb.cpp @@ -1,656 +1,650 @@ /**************************************************************************** ** ** ** KRDB - puts current KDE color scheme into preprocessor statements ** cats specially written application default files and uses xrdb -merge to ** write to RESOURCE_MANAGER. Thus it gives a simple way to make non-KDE ** applications fit in with the desktop ** ** Copyright (C) 1998 by Mark Donohoe ** Copyright (C) 1999 by Dirk A. Mueller (reworked for KDE 2.0) ** Copyright (C) 2001 by Matthias Ettrich (add support for GTK applications ) ** Copyright (C) 2001 by Waldo Bastian ** Copyright (C) 2002 by Karol Szwed ** This application is freely distributable under the GNU Public License. ** *****************************************************************************/ #include #include #include #include #include #include #undef Unsorted #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "krdb.h" #if HAVE_X11 #include #include #endif inline const char * gtkEnvVar(int version) { return 2==version ? "GTK2_RC_FILES" : "GTK_RC_FILES"; } inline const char * sysGtkrc(int version) { if(2==version) { if(access("/etc/opt/gnome/gtk-2.0", F_OK) == 0) return "/etc/opt/gnome/gtk-2.0/gtkrc"; else return "/etc/gtk-2.0/gtkrc"; } else { if(access("/etc/opt/gnome/gtk", F_OK) == 0) return "/etc/opt/gnome/gtk/gtkrc"; else return "/etc/gtk/gtkrc"; } } inline const char * userGtkrc(int version) { return 2==version ? "/.gtkrc-2.0" : "/.gtkrc"; } // ----------------------------------------------------------------------------- static QString writableGtkrc(int version) { QString gtkrc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation); QDir dir; dir.mkpath(gtkrc); gtkrc += 2==version?"/gtkrc-2.0":"/gtkrc"; return gtkrc; } // ----------------------------------------------------------------------------- static void applyGtkStyles(bool active, int version) { + Q_UNUSED(active); QString gtkkde = writableGtkrc(version); QByteArray gtkrc = getenv(gtkEnvVar(version)); QStringList list = QFile::decodeName(gtkrc).split( QLatin1Char(':')); QString userHomeGtkrc = QDir::homePath()+userGtkrc(version); if (!list.contains(userHomeGtkrc)) list.prepend(userHomeGtkrc); QLatin1String systemGtkrc = QLatin1String(sysGtkrc(version)); if (!list.contains(systemGtkrc)) list.prepend(systemGtkrc); list.removeAll(QLatin1String("")); list.removeAll(gtkkde); list.append(gtkkde); // Pass env. var to kdeinit. QString name = gtkEnvVar(version); QString value = list.join(QLatin1Char(':')); UpdateLaunchEnvJob(name, value); } // ----------------------------------------------------------------------------- static void applyQtColors( KSharedConfigPtr kglobalcfg, QSettings& settings, QPalette& newPal ) { QStringList actcg, inactcg, discg; /* export kde color settings */ int i; for (i = 0; i < QPalette::NColorRoles; i++) actcg << newPal.color(QPalette::Active, (QPalette::ColorRole) i).name(); for (i = 0; i < QPalette::NColorRoles; i++) inactcg << newPal.color(QPalette::Inactive, (QPalette::ColorRole) i).name(); for (i = 0; i < QPalette::NColorRoles; i++) discg << newPal.color(QPalette::Disabled, (QPalette::ColorRole) i).name(); settings.setValue(QStringLiteral("/qt/Palette/active"), actcg); settings.setValue(QStringLiteral("/qt/Palette/inactive"), inactcg); settings.setValue(QStringLiteral("/qt/Palette/disabled"), discg); // export kwin's colors to qtrc for kstyle to use KConfigGroup wmCfgGroup(kglobalcfg, "WM"); // active colors QColor clr = newPal.color( QPalette::Active, QPalette::Window ); clr = wmCfgGroup.readEntry("activeBackground", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/activeBackground"), clr.name()); if (QPixmap::defaultDepth() > 8) clr = clr.darker(110); clr = wmCfgGroup.readEntry("activeBlend", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/activeBlend"), clr.name()); clr = newPal.color( QPalette::Active, QPalette::HighlightedText ); clr = wmCfgGroup.readEntry("activeForeground", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/activeForeground"), clr.name()); clr = newPal.color( QPalette::Active,QPalette::Window ); clr = wmCfgGroup.readEntry("frame", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/frame"), clr.name()); clr = wmCfgGroup.readEntry("activeTitleBtnBg", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/activeTitleBtnBg"), clr.name()); // inactive colors clr = newPal.color(QPalette::Inactive, QPalette::Window); clr = wmCfgGroup.readEntry("inactiveBackground", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveBackground"), clr.name()); if (QPixmap::defaultDepth() > 8) clr = clr.darker(110); clr = wmCfgGroup.readEntry("inactiveBlend", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveBlend"), clr.name()); clr = newPal.color(QPalette::Inactive, QPalette::Window).darker(); clr = wmCfgGroup.readEntry("inactiveForeground", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveForeground"), clr.name()); clr = newPal.color(QPalette::Inactive, QPalette::Window); clr = wmCfgGroup.readEntry("inactiveFrame", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveFrame"), clr.name()); clr = wmCfgGroup.readEntry("inactiveTitleBtnBg", clr); settings.setValue(QStringLiteral("/qt/KWinPalette/inactiveTitleBtnBg"), clr.name()); KConfigGroup kdeCfgGroup(kglobalcfg, "KDE"); settings.setValue(QStringLiteral("/qt/KDE/contrast"), kdeCfgGroup.readEntry("contrast", 7)); } // ----------------------------------------------------------------------------- static void applyQtSettings( KSharedConfigPtr kglobalcfg, QSettings& settings ) { /* export font settings */ // NOTE keep this in sync with kfontsettingsdata in plasma-integration (cf. also Bug 378262) QFont defaultFont(QStringLiteral("Noto Sans"), 10, -1); defaultFont.setStyleHint(QFont::SansSerif); const KConfigGroup configGroup(KSharedConfig::openConfig(), QStringLiteral("General")); const QString fontInfo = configGroup.readEntry(QStringLiteral("font"), QString()); if (!fontInfo.isEmpty()) { defaultFont.fromString(fontInfo); } settings.setValue(QStringLiteral("/qt/font"), defaultFont.toString()); /* export effects settings */ KConfigGroup kdeCfgGroup(kglobalcfg, "General"); bool effectsEnabled = kdeCfgGroup.readEntry("EffectsEnabled", false); bool fadeMenus = kdeCfgGroup.readEntry("EffectFadeMenu", false); bool fadeTooltips = kdeCfgGroup.readEntry("EffectFadeTooltip", false); bool animateCombobox = kdeCfgGroup.readEntry("EffectAnimateCombo", false); QStringList guieffects; if (effectsEnabled) { guieffects << QStringLiteral("general"); if (fadeMenus) guieffects << QStringLiteral("fademenu"); if (animateCombobox) guieffects << QStringLiteral("animatecombo"); if (fadeTooltips) guieffects << QStringLiteral("fadetooltip"); } else guieffects << QStringLiteral("none"); settings.setValue(QStringLiteral("/qt/GUIEffects"), guieffects); } // ----------------------------------------------------------------------------- static void addColorDef(QString& s, const char* n, const QColor& col) { QString tmp; tmp.sprintf("#define %s #%02x%02x%02x\n", n, col.red(), col.green(), col.blue()); s += tmp; } // ----------------------------------------------------------------------------- static void copyFile(QFile& tmp, QString const& filename, bool ) { QFile f( filename ); if ( f.open(QIODevice::ReadOnly) ) { QByteArray buf( 8192, ' ' ); while ( !f.atEnd() ) { int read = f.read( buf.data(), buf.size() ); if ( read > 0 ) tmp.write( buf.data(), read ); } } } // ----------------------------------------------------------------------------- -static QString item( int i ) { - return QString::number( i / 255.0, 'f', 3 ); -} - -static QString color( const QColor& col ) -{ - return QStringLiteral( "{ %1, %2, %3 }" ).arg( item( col.red() ) ).arg( item( col.green() ) ).arg( item( col.blue() ) ); -} - static void createGtkrc( const QPalette& cg, bool exportGtkTheme, const QString& gtkTheme, int version ) { + Q_UNUSED(cg); // lukas: why does it create in ~/.kde/share/config ??? // pfeiffer: so that we don't overwrite the user's gtkrc. // it is found via the GTK_RC_FILES environment variable. QSaveFile saveFile( writableGtkrc(version) ); if ( !saveFile.open(QIODevice::WriteOnly) ) return; QTextStream t ( &saveFile ); t.setCodec( QTextCodec::codecForLocale () ); t << i18n( "# created by KDE Plasma, %1\n" "#\n", QDateTime::currentDateTime().toString()); if ( 2==version ) { // we should maybe check for MacOS settings here + using Qt::endl; t << endl; t << "gtk-alternative-button-order = 1" << endl; t << endl; } if (exportGtkTheme) { QString gtkStyle; if (gtkTheme.toLower() == QLatin1String("oxygen")) gtkStyle = QStringLiteral("oxygen-gtk"); else gtkStyle = gtkTheme; bool exist_gtkrc = false; QByteArray gtkrc = getenv(gtkEnvVar(version)); QStringList listGtkrc = QFile::decodeName(gtkrc).split(QLatin1Char(':')); if (listGtkrc.contains(saveFile.fileName())) listGtkrc.removeAll(saveFile.fileName()); listGtkrc.append(QDir::homePath() + userGtkrc(version)); listGtkrc.append(QDir::homePath() + "/.gtkrc-2.0-kde"); listGtkrc.append(QDir::homePath() + "/.gtkrc-2.0-kde4"); listGtkrc.removeAll(QLatin1String("")); listGtkrc.removeDuplicates(); for (int i = 0; i < listGtkrc.size(); ++i) { if ((exist_gtkrc = QFile::exists(listGtkrc.at(i)))) break; } if (!exist_gtkrc) { QString gtk2ThemeFilename; gtk2ThemeFilename = QStringLiteral("%1/.themes/%2/gtk-2.0/gtkrc").arg(QDir::homePath()).arg(gtkStyle); if (!QFile::exists(gtk2ThemeFilename)) { QStringList gtk2ThemePath; gtk2ThemeFilename.clear(); QByteArray xdgDataDirs = getenv("XDG_DATA_DIRS"); gtk2ThemePath.append(QDir::homePath() + "/.local"); gtk2ThemePath.append(QFile::decodeName(xdgDataDirs).split(QLatin1Char(':'))); gtk2ThemePath.removeDuplicates(); for (int i = 0; i < gtk2ThemePath.size(); ++i) { gtk2ThemeFilename = QStringLiteral("%1/themes/%2/gtk-2.0/gtkrc").arg(gtk2ThemePath.at(i)).arg(gtkStyle); if (QFile::exists(gtk2ThemeFilename)) break; else gtk2ThemeFilename.clear(); } } if (!gtk2ThemeFilename.isEmpty()) { - t << "include \"" << gtk2ThemeFilename << "\"" << endl; - t << endl; - t << "gtk-theme-name=\"" << gtkStyle << "\"" << endl; - t << endl; + t << "include \"" << gtk2ThemeFilename << "\"" << Qt::endl; + t << Qt::endl; + t << "gtk-theme-name=\"" << gtkStyle << "\"" << Qt::endl; + t << Qt::endl; } } } saveFile.commit(); } // ----------------------------------------------------------------------------- void runRdb( uint flags ) { // Obtain the application palette that is about to be set. bool exportColors = flags & KRdbExportColors; bool exportQtColors = flags & KRdbExportQtColors; bool exportQtSettings = flags & KRdbExportQtSettings; bool exportXftSettings = flags & KRdbExportXftSettings; bool exportGtkTheme = flags & KRdbExportGtkTheme; KSharedConfigPtr kglobalcfg = KSharedConfig::openConfig( QStringLiteral("kdeglobals") ); KConfigGroup kglobals(kglobalcfg, "KDE"); QPalette newPal = KColorScheme::createApplicationPalette(kglobalcfg); QTemporaryFile tmpFile; if (!tmpFile.open()) { qDebug() << "Couldn't open temp file"; exit(0); } KConfigGroup generalCfgGroup(kglobalcfg, "General"); QString gtkTheme; if (kglobals.hasKey("widgetStyle")) gtkTheme = kglobals.readEntry("widgetStyle"); else gtkTheme = QStringLiteral("oxygen"); createGtkrc( newPal, exportGtkTheme, gtkTheme, 1 ); createGtkrc( newPal, exportGtkTheme, gtkTheme, 2 ); // Export colors to non-(KDE/Qt) apps (e.g. Motif, GTK+ apps) if (exportColors) { KConfigGroup g(KSharedConfig::openConfig(), "WM"); QString preproc; QColor backCol = newPal.color( QPalette::Active, QPalette::Window ); addColorDef(preproc, "FOREGROUND" , newPal.color( QPalette::Active, QPalette::WindowText ) ); addColorDef(preproc, "BACKGROUND" , backCol); addColorDef(preproc, "HIGHLIGHT" , backCol.lighter(100+(2*KColorScheme::contrast()+4)*16/1)); addColorDef(preproc, "LOWLIGHT" , backCol.darker(100+(2*KColorScheme::contrast()+4)*10)); addColorDef(preproc, "SELECT_BACKGROUND" , newPal.color( QPalette::Active, QPalette::Highlight)); addColorDef(preproc, "SELECT_FOREGROUND" , newPal.color( QPalette::Active, QPalette::HighlightedText)); addColorDef(preproc, "WINDOW_BACKGROUND" , newPal.color( QPalette::Active, QPalette::Base ) ); addColorDef(preproc, "WINDOW_FOREGROUND" , newPal.color( QPalette::Active, QPalette::Text ) ); addColorDef(preproc, "INACTIVE_BACKGROUND", g.readEntry("inactiveBackground", QColor(224, 223, 222))); addColorDef(preproc, "INACTIVE_FOREGROUND", g.readEntry("inactiveBackground", QColor(224, 223, 222))); addColorDef(preproc, "ACTIVE_BACKGROUND" , g.readEntry("activeBackground", QColor(48, 174, 232))); addColorDef(preproc, "ACTIVE_FOREGROUND" , g.readEntry("activeBackground", QColor(48, 174, 232))); //--------------------------------------------------------------- tmpFile.write( preproc.toLatin1(), preproc.length() ); QStringList list; const QStringList adPaths = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kdisplay/app-defaults/"), QStandardPaths::LocateDirectory); for (QStringList::ConstIterator it = adPaths.constBegin(); it != adPaths.constEnd(); ++it) { QDir dSys( *it ); if ( dSys.exists() ) { dSys.setFilter( QDir::Files ); dSys.setSorting( QDir::Name ); dSys.setNameFilters(QStringList(QStringLiteral("*.ad"))); list += dSys.entryList(); } } for (QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it) copyFile(tmpFile, QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdisplay/app-defaults/"+(*it)), true); } // Merge ~/.Xresources or fallback to ~/.Xdefaults QString homeDir = QDir::homePath(); QString xResources = homeDir + "/.Xresources"; // very primitive support for ~/.Xresources by appending it if ( QFile::exists( xResources ) ) copyFile(tmpFile, xResources, true); else copyFile(tmpFile, homeDir + "/.Xdefaults", true); // Export the Xcursor theme & size settings KConfigGroup mousecfg(KSharedConfig::openConfig( QStringLiteral("kcminputrc") ), "Mouse" ); QString theme = mousecfg.readEntry("cursorTheme", QString("breeze_cursors")); QString size = mousecfg.readEntry("cursorSize", QString("24")); QString contents; if (!theme.isNull()) contents = "Xcursor.theme: " + theme + '\n'; if (!size.isNull()) contents += "Xcursor.size: " + size + '\n'; if (exportXftSettings) { contents += QLatin1String("Xft.antialias: "); if(generalCfgGroup.readEntry("XftAntialias", true)) contents += QLatin1String("1\n"); else contents += QLatin1String("0\n"); QString hintStyle = generalCfgGroup.readEntry("XftHintStyle", "hintslight"); contents += QLatin1String("Xft.hinting: "); if(hintStyle.isEmpty()) contents += QLatin1String("-1\n"); else { if(hintStyle!=QLatin1String("hintnone")) contents += QLatin1String("1\n"); else contents += QLatin1String("0\n"); contents += "Xft.hintstyle: " + hintStyle + '\n'; } QString subPixel = generalCfgGroup.readEntry("XftSubPixel", "rgb"); if(!subPixel.isEmpty()) contents += "Xft.rgba: " + subPixel + '\n'; KConfig _cfgfonts( QStringLiteral("kcmfonts") ); KConfigGroup cfgfonts(&_cfgfonts, "General"); int dpi; //even though this sets up the X rdb, we want to use the value the //user has set to use when under wayland - as X apps will be scaled by the compositor if (KWindowSystem::isPlatformWayland()) { dpi = cfgfonts.readEntry( "forceFontDPIWayland", 0); if (dpi == 0) { //with wayland we want xwayland to run at 96 dpi (unless set otherwise) as we have wayland scaling on top dpi = 96; } } else { dpi = cfgfonts.readEntry( "forceFontDPI", 0); } if( dpi != 0 ) contents += "Xft.dpi: " + QString::number(dpi) + '\n'; else { KProcess proc; proc << QStringLiteral("xrdb") << QStringLiteral("-quiet") << QStringLiteral("-remove") << QStringLiteral("-nocpp"); proc.start(); if (proc.waitForStarted()) { proc.write( QByteArray( "Xft.dpi\n" ) ); proc.closeWriteChannel(); proc.waitForFinished(); } } } if (contents.length() > 0) tmpFile.write( contents.toLatin1(), contents.length() ); tmpFile.flush(); KProcess proc; #ifndef NDEBUG proc << QStringLiteral("xrdb") << QStringLiteral("-merge") << tmpFile.fileName(); #else proc << "xrdb" << "-quiet" << "-merge" << tmpFile.fileName(); #endif proc.execute(); applyGtkStyles(exportColors, 1); applyGtkStyles(exportColors, 2); /* Qt exports */ if ( exportQtColors || exportQtSettings ) { QSettings* settings = new QSettings(QStringLiteral("Trolltech")); if ( exportQtColors ) applyQtColors( kglobalcfg, *settings, newPal ); // For kcmcolors if ( exportQtSettings ) applyQtSettings( kglobalcfg, *settings ); // For kcmstyle delete settings; QCoreApplication::processEvents(); #if HAVE_X11 if (qApp->platformName() == QLatin1String("xcb")) { // We let KIPC take care of ourselves, as we are in a KDE app with // QApp::setDesktopSettingsAware(false); // Instead of calling QApp::x11_apply_settings() directly, we instead // modify the timestamp which propagates the settings changes onto // Qt-only apps without adversely affecting ourselves. // Cheat and use the current timestamp, since we just saved to qtrc. QDateTime settingsstamp = QDateTime::currentDateTime(); static Atom qt_settings_timestamp = 0; if (!qt_settings_timestamp) { QString atomname(QStringLiteral("_QT_SETTINGS_TIMESTAMP_")); atomname += XDisplayName( nullptr ); // Use the $DISPLAY envvar. qt_settings_timestamp = XInternAtom( QX11Info::display(), atomname.toLatin1(), False); } QBuffer stamp; QDataStream s(&stamp.buffer(), QIODevice::WriteOnly); s << settingsstamp; XChangeProperty( QX11Info::display(), QX11Info::appRootWindow(), qt_settings_timestamp, qt_settings_timestamp, 8, PropModeReplace, (unsigned char*) stamp.buffer().data(), stamp.buffer().size() ); QApplication::flush(); } #endif } //Legacy support: //Try to sync kde4 settings with ours Kdelibs4Migration migration; //kf5 congig groups for general and icons KConfigGroup generalGroup(kglobalcfg, "General"); KConfigGroup iconsGroup(kglobalcfg, "Icons"); const QString colorSchemeName = generalGroup.readEntry("ColorScheme", QStringLiteral("Breeze")); QString colorSchemeSrcFile; if (colorSchemeName != QLatin1String("Default")) { //fix filename, copied from ColorsCM::saveScheme() QString colorSchemeFilename = colorSchemeName; colorSchemeFilename.remove('\''); // So Foo's does not become FooS QRegExp fixer(QStringLiteral("[\\W,.-]+(.?)")); int offset; while ((offset = fixer.indexIn(colorSchemeFilename)) >= 0) colorSchemeFilename.replace(offset, fixer.matchedLength(), fixer.cap(1).toUpper()); colorSchemeFilename.replace(0, 1, colorSchemeFilename.at(0).toUpper()); //clone the color scheme colorSchemeSrcFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "color-schemes/" + colorSchemeFilename + ".colors"); const QString dest = migration.saveLocation("data", QStringLiteral("color-schemes")) + colorSchemeName + ".colors"; QFile::remove(dest); QFile::copy(colorSchemeSrcFile, dest); } //Apply the color scheme QString configFilePath = migration.saveLocation("config") + "kdeglobals"; if (configFilePath.isEmpty()) { return; } KConfig kde4config(configFilePath, KConfig::SimpleConfig); KConfigGroup kde4generalGroup(&kde4config, "General"); kde4generalGroup.writeEntry("ColorScheme", colorSchemeName); //fonts QString font = generalGroup.readEntry("font", QString()); if (!font.isEmpty()) { kde4generalGroup.writeEntry("font", font); } font = generalGroup.readEntry("desktopFont", QString()); if (!font.isEmpty()) { kde4generalGroup.writeEntry("desktopFont", font); } font = generalGroup.readEntry("menuFont", QString()); if (!font.isEmpty()) { kde4generalGroup.writeEntry("menuFont", font); } font = generalGroup.readEntry("smallestReadableFont", QString()); if (!font.isEmpty()) { kde4generalGroup.writeEntry("smallestReadableFont", font); } font = generalGroup.readEntry("taskbarFont", QString()); if (!font.isEmpty()) { kde4generalGroup.writeEntry("taskbarFont", font); } font = generalGroup.readEntry("toolBarFont", QString()); if (!font.isEmpty()) { kde4generalGroup.writeEntry("toolBarFont", font); } //TODO: does exist any way to check if a qt4 widget style is present from a qt5 app? //kde4generalGroup.writeEntry("widgetStyle", "qtcurve"); kde4generalGroup.sync(); KConfigGroup kde4IconGroup(&kde4config, "Icons"); QString iconTheme = iconsGroup.readEntry("Theme", QString()); if (!iconTheme.isEmpty()) { kde4IconGroup.writeEntry("Theme", iconTheme); } kde4IconGroup.sync(); if (!colorSchemeSrcFile.isEmpty()) { //copy all the groups in the color scheme in kdeglobals KSharedConfigPtr kde4ColorConfig = KSharedConfig::openConfig(colorSchemeSrcFile, KConfig::SimpleConfig); foreach (const QString &grp, kde4ColorConfig->groupList()) { KConfigGroup cg(kde4ColorConfig, grp); KConfigGroup cg2(&kde4config, grp); cg.copyTo(&cg2); } } //widgets settings KConfigGroup kglobals4(&kde4config, "KDE"); kglobals4.writeEntry("ShowIconsInMenuItems", kglobals.readEntry("ShowIconsInMenuItems", true)); kglobals4.writeEntry("ShowIconsOnPushButtons", kglobals.readEntry("ShowIconsOnPushButtons", true)); kglobals4.writeEntry("contrast", kglobals.readEntry("contrast", 4)); //FIXME: this should somehow check if the kde4 version of the style is installed kde4generalGroup.writeEntry("widgetStyle", kglobals.readEntry("widgetStyle", "breeze")); //toolbar style KConfigGroup toolbars4(&kde4config, "Toolbar style"); KConfigGroup toolbars5(kglobalcfg, "Toolbar style"); toolbars4.writeEntry("ToolButtonStyle", toolbars5.readEntry("ToolButtonStyle", "TextBesideIcon")); toolbars4.writeEntry("ToolButtonStyleOtherToolbars", toolbars5.readEntry("ToolButtonStyleOtherToolbars", "TextBesideIcon")); } diff --git a/kcms/lookandfeel/kcm.cpp b/kcms/lookandfeel/kcm.cpp index 3e644ad4a..a3d0fc9ef 100644 --- a/kcms/lookandfeel/kcm.cpp +++ b/kcms/lookandfeel/kcm.cpp @@ -1,725 +1,725 @@ /* This file is part of the KDE Project Copyright (c) 2014 Marco Martin Copyright (c) 2014 Vishesh Handa Copyright (c) 2019 Cyril Rossi This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcm.h" #include "../krdb/krdb.h" #include "config-kcm.h" #include "config-workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "lookandfeelsettings.h" #ifdef HAVE_XCURSOR # include "../cursortheme/xcursor/xcursortheme.h" # include #endif #ifdef HAVE_XFIXES # include #endif KCMLookandFeel::KCMLookandFeel(QObject *parent, const QVariantList &args) : KQuickAddons::ManagedConfigModule(parent, args) , m_settings(new LookAndFeelSettings(this)) , m_config(QStringLiteral("kdeglobals")) , m_configGroup(m_config.group("KDE")) , m_applyColors(true) , m_applyWidgetStyle(true) , m_applyIcons(true) , m_applyPlasmaTheme(true) , m_applyCursors(true) , m_applyWindowSwitcher(true) , m_applyDesktopSwitcher(true) , m_resetDefaultLayout(false) , m_applyWindowDecoration(true) { qmlRegisterType(); qmlRegisterType(); qmlRegisterType(); KAboutData *about = new KAboutData(QStringLiteral("kcm_lookandfeel"), i18n("Global Theme"), QStringLiteral("0.1"), QString(), KAboutLicense::LGPL); about->addAuthor(i18n("Marco Martin"), QString(), QStringLiteral("mart@kde.org")); setAboutData(about); setButtons(Apply | Default); m_model = new QStandardItemModel(this); QHash roles = m_model->roleNames(); roles[PluginNameRole] = "pluginName"; roles[DescriptionRole] = "description"; roles[ScreenshotRole] = "screenshot"; roles[FullScreenPreviewRole] = "fullScreenPreview"; roles[HasSplashRole] = "hasSplash"; roles[HasLockScreenRole] = "hasLockScreen"; roles[HasRunCommandRole] = "hasRunCommand"; roles[HasLogoutRole] = "hasLogout"; roles[HasColorsRole] = "hasColors"; roles[HasWidgetStyleRole] = "hasWidgetStyle"; roles[HasIconsRole] = "hasIcons"; roles[HasPlasmaThemeRole] = "hasPlasmaTheme"; roles[HasCursorsRole] = "hasCursors"; roles[HasWindowSwitcherRole] = "hasWindowSwitcher"; roles[HasDesktopSwitcherRole] = "hasDesktopSwitcher"; m_model->setItemRoleNames(roles); loadModel(); } KCMLookandFeel::~KCMLookandFeel() { } void KCMLookandFeel::reloadModel() { loadModel(); } QStandardItemModel *KCMLookandFeel::lookAndFeelModel() const { return m_model; } int KCMLookandFeel::pluginIndex(const QString &pluginName) const { const auto results = m_model->match(m_model->index(0, 0), PluginNameRole, pluginName); if (results.count() == 1) { return results.first().row(); } return -1; } QList KCMLookandFeel::availablePackages(const QStringList &components) { QList packages; QStringList paths; const QStringList dataPaths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); paths.reserve(dataPaths.count()); for (const QString &path : dataPaths) { QDir dir(path + QStringLiteral("/plasma/look-and-feel")); paths << dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot); } for (const QString &path : paths) { KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); pkg.setPath(path); pkg.setFallbackPackage(KPackage::Package()); if (components.isEmpty()) { packages << pkg; } else { for (const auto &component : components) { if (!pkg.filePath(component.toUtf8()).isEmpty()) { packages << pkg; break; } } } } return packages; } LookAndFeelSettings *KCMLookandFeel::lookAndFeelSettings() const { return m_settings; } void KCMLookandFeel::loadModel() { m_model->clear(); const QList pkgs = availablePackages({"defaults", "layouts"}); for (const KPackage::Package &pkg : pkgs) { if (!pkg.metadata().isValid()) { continue; } QStandardItem *row = new QStandardItem(pkg.metadata().name()); row->setData(pkg.metadata().pluginId(), PluginNameRole); row->setData(pkg.metadata().description(), DescriptionRole); row->setData(pkg.filePath("preview"), ScreenshotRole); row->setData(pkg.filePath("fullscreenpreview"), FullScreenPreviewRole); //What the package provides row->setData(!pkg.filePath("splashmainscript").isEmpty(), HasSplashRole); row->setData(!pkg.filePath("lockscreenmainscript").isEmpty(), HasLockScreenRole); row->setData(!pkg.filePath("runcommandmainscript").isEmpty(), HasRunCommandRole); row->setData(!pkg.filePath("logoutmainscript").isEmpty(), HasLogoutRole); if (!pkg.filePath("defaults").isEmpty()) { KSharedConfigPtr conf = KSharedConfig::openConfig(pkg.filePath("defaults")); KConfigGroup cg(conf, "kdeglobals"); cg = KConfigGroup(&cg, "General"); bool hasColors = !cg.readEntry("ColorScheme", QString()).isEmpty(); if (!hasColors) { hasColors = !pkg.filePath("colors").isEmpty(); } row->setData(hasColors, HasColorsRole); cg = KConfigGroup(&cg, "KDE"); row->setData(!cg.readEntry("widgetStyle", QString()).isEmpty(), HasWidgetStyleRole); cg = KConfigGroup(conf, "kdeglobals"); cg = KConfigGroup(&cg, "Icons"); row->setData(!cg.readEntry("Theme", QString()).isEmpty(), HasIconsRole); cg = KConfigGroup(conf, "kdeglobals"); cg = KConfigGroup(&cg, "Theme"); row->setData(!cg.readEntry("name", QString()).isEmpty(), HasPlasmaThemeRole); cg = KConfigGroup(conf, "kcminputrc"); cg = KConfigGroup(&cg, "Mouse"); row->setData(!cg.readEntry("cursorTheme", QString()).isEmpty(), HasCursorsRole); cg = KConfigGroup(conf, "kwinrc"); cg = KConfigGroup(&cg, "WindowSwitcher"); row->setData(!cg.readEntry("LayoutName", QString()).isEmpty(), HasWindowSwitcherRole); cg = KConfigGroup(conf, "kwinrc"); cg = KConfigGroup(&cg, "DesktopSwitcher"); row->setData(!cg.readEntry("LayoutName", QString()).isEmpty(), HasDesktopSwitcherRole); } m_model->appendRow(row); } m_model->sort(0 /*column*/); //Model has been cleared so pretend the selected look and fell changed to force view update emit m_settings->lookAndFeelPackageChanged(); } void KCMLookandFeel::load() { ManagedConfigModule::load(); m_package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); m_package.setPath(m_settings->lookAndFeelPackage()); } void KCMLookandFeel::save() { KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); package.setPath(m_settings->lookAndFeelPackage()); if (!package.isValid()) { return; } ManagedConfigModule::save(); if (m_resetDefaultLayout) { QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"), QStringLiteral("/PlasmaShell"), QStringLiteral("org.kde.PlasmaShell"), QStringLiteral("loadLookAndFeelDefaultLayout")); QList args; args << m_settings->lookAndFeelPackage(); message.setArguments(args); QDBusConnection::sessionBus().call(message, QDBus::NoBlock); } if (!package.filePath("defaults").isEmpty()) { KSharedConfigPtr conf = KSharedConfig::openConfig(package.filePath("defaults")); KConfigGroup cg(conf, "kdeglobals"); cg = KConfigGroup(&cg, "KDE"); if (m_applyWidgetStyle) { QString widgetStyle = cg.readEntry("widgetStyle", QString()); // Some global themes refer to breeze's widgetStyle with a lowercase b. if (widgetStyle == QStringLiteral("breeze")) { widgetStyle = QStringLiteral("Breeze"); } setWidgetStyle(widgetStyle); } if (m_applyColors) { QString colorsFile = package.filePath("colors"); KConfigGroup cg(conf, "kdeglobals"); cg = KConfigGroup(&cg, "General"); QString colorScheme = cg.readEntry("ColorScheme", QString()); if (!colorsFile.isEmpty()) { if (!colorScheme.isEmpty()) { setColors(colorScheme, colorsFile); } else { setColors(package.metadata().name(), colorsFile); } } else if (!colorScheme.isEmpty()) { colorScheme.remove(QLatin1Char('\'')); // So Foo's does not become FooS QRegExp fixer(QStringLiteral("[\\W,.-]+(.?)")); int offset; while ((offset = fixer.indexIn(colorScheme)) >= 0) { colorScheme.replace(offset, fixer.matchedLength(), fixer.cap(1).toUpper()); } colorScheme.replace(0, 1, colorScheme.at(0).toUpper()); //NOTE: why this loop trough all the scheme files? //the scheme theme name is an heuristic, there is no plugin metadata whatsoever. //is based on the file name stripped from weird characters or the //eventual id- prefix store.kde.org puts, so we can just find a //theme that ends as the specified name bool schemeFound = false; const QStringList schemeDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes"), QStandardPaths::LocateDirectory); for (const QString &dir : schemeDirs) { const QStringList fileNames = QDir(dir).entryList(QStringList()<exec()); as.setAutostarts(true); if (qEnvironmentVariableIsSet("KDE_FULL_SESSION")) { auto *job = new KIO::ApplicationLauncherJob(service); job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr)); job->start(); } } } } } //TODO: option to enable/disable apply? they don't seem required by UI design const auto *item = m_model->item(pluginIndex(m_settings->lookAndFeelPackage())); if (item->data(HasSplashRole).toBool()) { setSplashScreen(m_settings->lookAndFeelPackage()); } setLockScreen(m_settings->lookAndFeelPackage()); m_configGroup.sync(); m_package.setPath(m_settings->lookAndFeelPackage()); runRdb(KRdbExportQtColors | KRdbExportGtkTheme | KRdbExportColors | KRdbExportQtSettings | KRdbExportXftSettings); } void KCMLookandFeel::setWidgetStyle(const QString &style) { if (style.isEmpty()) { return; } // Some global themes use styles that may not be installed. // Test if style can be installed before updating the config. QScopedPointer newStyle(QStyleFactory::create(style)); if (newStyle) { m_configGroup.writeEntry("widgetStyle", style); m_configGroup.sync(); //FIXME: changing style on the fly breaks QQuickWidgets KGlobalSettings::self()->emitChange(KGlobalSettings::StyleChanged); } } void KCMLookandFeel::setColors(const QString &scheme, const QString &colorFile) { if (scheme.isEmpty() && colorFile.isEmpty()) { return; } KSharedConfigPtr conf = KSharedConfig::openConfig(colorFile, KSharedConfig::CascadeConfig); foreach (const QString &grp, conf->groupList()) { KConfigGroup cg(conf, grp); KConfigGroup cg2(&m_config, grp); cg.copyTo(&cg2); } KConfigGroup configGroup(&m_config, "General"); configGroup.writeEntry("ColorScheme", scheme); configGroup.sync(); KGlobalSettings::self()->emitChange(KGlobalSettings::PaletteChanged); } void KCMLookandFeel::setIcons(const QString &theme) { if (theme.isEmpty()) { return; } KConfigGroup cg(&m_config, "Icons"); cg.writeEntry("Theme", theme); cg.sync(); for (int i=0; i < KIconLoader::LastGroup; i++) { KIconLoader::emitChange(KIconLoader::Group(i)); } } void KCMLookandFeel::setPlasmaTheme(const QString &theme) { if (theme.isEmpty()) { return; } KConfig config(QStringLiteral("plasmarc")); KConfigGroup cg(&config, "Theme"); cg.writeEntry("name", theme); cg.sync(); } void KCMLookandFeel::setCursorTheme(const QString themeName) { //TODO: use pieces of cursor kcm when moved to plasma-desktop if (themeName.isEmpty()) { return; } KConfig config(QStringLiteral("kcminputrc")); KConfigGroup cg(&config, "Mouse"); cg.writeEntry("cursorTheme", themeName); cg.sync(); #ifdef HAVE_XCURSOR // Require the Xcursor version that shipped with X11R6.9 or greater, since // in previous versions the Xfixes code wasn't enabled due to a bug in the // build system (freedesktop bug #975). #if defined(HAVE_XFIXES) && XFIXES_MAJOR >= 2 && XCURSOR_LIB_VERSION >= 10105 const int cursorSize = cg.readEntry("cursorSize", 24); QDir themeDir = cursorThemeDir(themeName, 0); if (!themeDir.exists()) { return; } XCursorTheme theme(themeDir); if (!CursorTheme::haveXfixes()) { return; } UpdateLaunchEnvJob launchEnvJob(QStringLiteral("XCURSOR_THEME"), themeName); // Update the Xcursor X resources runRdb(0); // Notify all applications that the cursor theme has changed KGlobalSettings::self()->emitChange(KGlobalSettings::CursorChanged); // Reload the standard cursors QStringList names; // Qt cursors names << QStringLiteral("left_ptr") << QStringLiteral("up_arrow") << QStringLiteral("cross") << QStringLiteral("wait") << QStringLiteral("left_ptr_watch") << QStringLiteral("ibeam") << QStringLiteral("size_ver") << QStringLiteral("size_hor") << QStringLiteral("size_bdiag") << QStringLiteral("size_fdiag") << QStringLiteral("size_all") << QStringLiteral("split_v") << QStringLiteral("split_h") << QStringLiteral("pointing_hand") << QStringLiteral("openhand") << QStringLiteral("closedhand") << QStringLiteral("forbidden") << QStringLiteral("whats_this") << QStringLiteral("copy") << QStringLiteral("move") << QStringLiteral("link"); // X core cursors names << QStringLiteral("X_cursor") << QStringLiteral("right_ptr") << QStringLiteral("hand1") << QStringLiteral("hand2") << QStringLiteral("watch") << QStringLiteral("xterm") << QStringLiteral("crosshair") << QStringLiteral("left_ptr_watch") << QStringLiteral("center_ptr") << QStringLiteral("sb_h_double_arrow") << QStringLiteral("sb_v_double_arrow") << QStringLiteral("fleur") << QStringLiteral("top_left_corner") << QStringLiteral("top_side") << QStringLiteral("top_right_corner") << QStringLiteral("right_side") << QStringLiteral("bottom_right_corner") << QStringLiteral("bottom_side") << QStringLiteral("bottom_left_corner") << QStringLiteral("left_side") << QStringLiteral("question_arrow") << QStringLiteral("pirate"); foreach (const QString &name, names) { XFixesChangeCursorByName(QX11Info::display(), theme.loadCursor(name, cursorSize), QFile::encodeName(name)); } #else KMessageBox::information(this, i18n("You have to restart the Plasma session for these changes to take effect."), i18n("Cursor Settings Changed"), "CursorSettingsChanged"); #endif #endif } QDir KCMLookandFeel::cursorThemeDir(const QString &theme, const int depth) { // Prevent infinite recursion if (depth > 10) { return QDir(); } // Search each icon theme directory for 'theme' foreach (const QString &baseDir, cursorSearchPaths()) { QDir dir(baseDir); if (!dir.exists() || !dir.cd(theme)) { continue; } // If there's a cursors subdir, we'll assume this is a cursor theme if (dir.exists(QStringLiteral("cursors"))) { return dir; } // If the theme doesn't have an index.theme file, it can't inherit any themes. if (!dir.exists(QStringLiteral("index.theme"))) { continue; } // Open the index.theme file, so we can get the list of inherited themes KConfig config(dir.path() + QStringLiteral("/index.theme"), KConfig::NoGlobals); KConfigGroup cg(&config, "Icon Theme"); // Recurse through the list of inherited themes, to check if one of them // is a cursor theme. QStringList inherits = cg.readEntry("Inherits", QStringList()); foreach (const QString &inherit, inherits) { // Avoid possible DoS if (inherit == theme) { continue; } if (cursorThemeDir(inherit, depth + 1).exists()) { return dir; } } } return QDir(); } const QStringList KCMLookandFeel::cursorSearchPaths() { if (!m_cursorSearchPaths.isEmpty()) return m_cursorSearchPaths; #ifdef HAVE_XCURSOR #if XCURSOR_LIB_MAJOR == 1 && XCURSOR_LIB_MINOR < 1 // These are the default paths Xcursor will scan for cursor themes QString path("~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons"); // If XCURSOR_PATH is set, use that instead of the default path char *xcursorPath = std::getenv("XCURSOR_PATH"); if (xcursorPath) path = xcursorPath; #else // Get the search path from Xcursor QString path = XcursorLibraryPath(); #endif // Separate the paths - m_cursorSearchPaths = path.split(QLatin1Char(':'), QString::SkipEmptyParts); + m_cursorSearchPaths = path.split(QLatin1Char(':'), Qt::SkipEmptyParts); // Remove duplicates QMutableStringListIterator i(m_cursorSearchPaths); while (i.hasNext()) { const QString path = i.next(); QMutableStringListIterator j(i); while (j.hasNext()) if (j.next() == path) j.remove(); } // Expand all occurrences of ~/ to the home dir m_cursorSearchPaths.replaceInStrings(QRegExp(QStringLiteral("^~\\/")), QDir::home().path() + QLatin1Char('/')); return m_cursorSearchPaths; #else return QStringList(); #endif } void KCMLookandFeel::setSplashScreen(const QString &theme) { if (theme.isEmpty()) { return; } KConfig config(QStringLiteral("ksplashrc")); KConfigGroup cg(&config, "KSplash"); cg.writeEntry("Theme", theme); //TODO: a way to set none as spash in the l&f cg.writeEntry("Engine", "KSplashQML"); cg.sync(); } void KCMLookandFeel::setLockScreen(const QString &theme) { if (theme.isEmpty()) { return; } KConfig config(QStringLiteral("kscreenlockerrc")); KConfigGroup cg(&config, "Greeter"); cg.writeEntry("Theme", theme); cg.sync(); } void KCMLookandFeel::setWindowSwitcher(const QString &theme) { if (theme.isEmpty()) { return; } KConfig config(QStringLiteral("kwinrc")); KConfigGroup cg(&config, "TabBox"); cg.writeEntry("LayoutName", theme); cg.sync(); } void KCMLookandFeel::setDesktopSwitcher(const QString &theme) { if (theme.isEmpty()) { return; } KConfig config(QStringLiteral("kwinrc")); KConfigGroup cg(&config, "TabBox"); cg.writeEntry("DesktopLayout", theme); cg.writeEntry("DesktopListLayout", theme); cg.sync(); } void KCMLookandFeel::setWindowDecoration(const QString &library, const QString &theme) { if (library.isEmpty()) { return; } KConfig config(QStringLiteral("kwinrc")); KConfigGroup cg(&config, "org.kde.kdecoration2"); cg.writeEntry("library", library); cg.writeEntry("theme", theme); cg.sync(); // Reload KWin. QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig")); QDBusConnection::sessionBus().send(message); } void KCMLookandFeel::setResetDefaultLayout(bool reset) { if (m_resetDefaultLayout == reset) { return; } m_resetDefaultLayout = reset; emit resetDefaultLayoutChanged(); if (reset) { setNeedsSave(true); } } bool KCMLookandFeel::resetDefaultLayout() const { return m_resetDefaultLayout; } diff --git a/kcms/mouse/kcm/configcontainer.cpp b/kcms/mouse/kcm/configcontainer.cpp index 23f28ffd1..3a38b9487 100644 --- a/kcms/mouse/kcm/configcontainer.cpp +++ b/kcms/mouse/kcm/configcontainer.cpp @@ -1,74 +1,75 @@ /* * Copyright 2018 Roman Gilg * * 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) any later version. * * 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. */ #include "configcontainer.h" #include "configplugin.h" #include "inputbackend.h" #include extern "C" { Q_DECL_EXPORT void kcminit_mouse() { InputBackend *backend = InputBackend::implementation(); backend->kcmInit(); delete backend; } } ConfigContainer::ConfigContainer(QWidget *parent, const QVariantList &args) : KCModule(parent, args) { m_plugin = ConfigPlugin::implementation(this); } QSize ConfigContainer::minimumSizeHint() const { return m_plugin->minimumSizeHint(); } QSize ConfigContainer::sizeHint() const { return m_plugin->sizeHint(); } void ConfigContainer::resizeEvent(QResizeEvent *event) { + Q_UNUSED(event); m_plugin->resize(this->size()); } void ConfigContainer::load() { m_plugin->load(); } void ConfigContainer::save() { m_plugin->save(); } void ConfigContainer::defaults() { m_plugin->defaults(); } void ConfigContainer::hideEvent(QHideEvent *e) { m_plugin->hideEvent(e); KCModule::hideEvent(e); } diff --git a/kcms/solid_actions/ActionItem.cpp b/kcms/solid_actions/ActionItem.cpp index f5e39a3e6..a93e2cb8a 100644 --- a/kcms/solid_actions/ActionItem.cpp +++ b/kcms/solid_actions/ActionItem.cpp @@ -1,157 +1,157 @@ /*************************************************************************** * Copyright (C) 2009 by Ben Cooksley * * * * 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) any later version. * * * * 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 * ***************************************************************************/ #include "ActionItem.h" #include "SolidActionData.h" #include #include #include #include #include ActionItem::ActionItem(const QString& pathToDesktop, const QString& action, QObject *parent) { Q_UNUSED(parent); desktopMasterPath = pathToDesktop; actionName = action; // Create the desktop file desktopFileMaster = new KDesktopFile(desktopMasterPath); desktopWritePath = desktopFileMaster->locateLocal(desktopMasterPath); desktopFileWrite = new KDesktopFile(desktopWritePath); // Now we can fill the action groups list configGroups.append(desktopFileMaster->desktopGroup()); - actionGroups.insertMulti(ActionItem::GroupDesktop, &configGroups.last()); + actionGroups.insert(ActionItem::GroupDesktop, &configGroups.last()); configGroups.append(desktopFileMaster->actionGroup(actionName)); - actionGroups.insertMulti(ActionItem::GroupAction, &configGroups.last()); + actionGroups.insert(ActionItem::GroupAction, &configGroups.last()); configGroups.append(desktopFileWrite->desktopGroup()); - actionGroups.insertMulti(ActionItem::GroupDesktop, &configGroups.last()); + actionGroups.insert(ActionItem::GroupDesktop, &configGroups.last()); configGroups.append(desktopFileWrite->actionGroup(actionName)); - actionGroups.insertMulti(ActionItem::GroupAction, &configGroups.last()); + actionGroups.insert(ActionItem::GroupAction, &configGroups.last()); const QString predicateString = readKey(ActionItem::GroupDesktop, QStringLiteral("X-KDE-Solid-Predicate"), QLatin1String("")); predicateItem = Solid::Predicate::fromString( predicateString ); } ActionItem::~ActionItem() { delete desktopFileWrite; delete desktopFileMaster; } /// Public functions below bool ActionItem::isUserSupplied() const { return hasKey(ActionItem::GroupDesktop, QStringLiteral("X-KDE-Action-Custom")); } QString ActionItem::icon() const { return readKey(ActionItem::GroupAction, QStringLiteral("Icon"), QLatin1String("")); } QString ActionItem::exec() const { return readKey(ActionItem::GroupAction, QStringLiteral("Exec"), QLatin1String("")); } QString ActionItem::name() const { return readKey(ActionItem::GroupAction, QStringLiteral("Name"), QLatin1String("")); } Solid::Predicate ActionItem::predicate() const { return predicateItem; } QString ActionItem::involvedTypes() const { SolidActionData * actData = SolidActionData::instance(); const QSet devTypeList = predicateItem.usedTypes(); QStringList deviceTypes; for (Solid::DeviceInterface::Type devType : devTypeList) { deviceTypes << actData->nameFromInterface( devType ); } return deviceTypes.join(QLatin1String(", ")); } void ActionItem::setIcon(const QString& nameOfIcon) { setKey(ActionItem::GroupAction, QStringLiteral("Icon"), nameOfIcon); } void ActionItem::setName(const QString& nameOfAction) { setKey(ActionItem::GroupAction, QStringLiteral("Name"), nameOfAction); } void ActionItem::setExec(const QString& execUrl) { setKey(ActionItem::GroupAction, QStringLiteral("Exec"), execUrl); } void ActionItem::setPredicate( const QString& newPredicate ) { setKey(ActionItem::GroupDesktop, QStringLiteral("X-KDE-Solid-Predicate"), newPredicate); predicateItem = Solid::Predicate::fromString( newPredicate ); } /// Private functions below QString ActionItem::readKey(GroupType keyGroup, const QString& keyName, const QString& defaultValue) const { return configItem(ActionItem::DesktopRead, keyGroup, keyName)->readEntry(keyName, defaultValue); } void ActionItem::setKey(GroupType keyGroup, const QString& keyName, const QString& keyContents) { configItem(ActionItem::DesktopWrite, keyGroup)->writeEntry(keyName, keyContents); } bool ActionItem::hasKey(GroupType keyGroup, const QString& keyName) const { return configItem(ActionItem::DesktopRead, keyGroup, keyName)->hasKey(keyName); } KConfigGroup * ActionItem::configItem(DesktopAction actionType, GroupType keyGroup, const QString& keyName) const { int countAccess = 0; if (actionType == ActionItem::DesktopRead) { const auto values = actionGroups.values(keyGroup); for (KConfigGroup *possibleGroup : values) { if (possibleGroup->hasKey(keyName)) { return possibleGroup; } } } else if (actionType == ActionItem::DesktopWrite) { if (isUserSupplied()) { countAccess = 1; } return actionGroups.values(keyGroup)[countAccess]; } return actionGroups.values(keyGroup)[0]; // Implement a backstop so a valid value is always returned } diff --git a/kcms/touchpad/applet/touchpadengine.cpp b/kcms/touchpad/applet/touchpadengine.cpp index 631f74f33..ea87e63d2 100644 --- a/kcms/touchpad/applet/touchpadengine.cpp +++ b/kcms/touchpad/applet/touchpadengine.cpp @@ -1,86 +1,86 @@ /* * Copyright (C) 2013 Alexander Mezin * * 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) any later version. * * 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. */ #include "touchpadengine.h" #include #include #include "touchpadinterface.h" #include "touchpadservice.h" #include "kded5interface.h" TouchpadEngine::TouchpadEngine(QObject *parent, const QVariantList &args) - : Plasma::DataEngine(parent, args), m_source("touchpad"), m_daemon(0) + : Plasma::DataEngine(parent, args), m_source("touchpad"), m_daemon(nullptr) { init(); } void TouchpadEngine::init() { OrgKdeKded5Interface kded(QLatin1String("org.kde.kded5"), QLatin1String("/kded"), QDBusConnection::sessionBus()); kded.loadModule("touchpad").waitForFinished(); m_daemon = new OrgKdeTouchpadInterface("org.kde.kded5", "/modules/touchpad", QDBusConnection::sessionBus(), this); if (!m_daemon->isValid()) { return; } connect(m_daemon, SIGNAL(workingTouchpadFoundChanged(bool)), SLOT(workingTouchpadFoundChanged(bool))); connect(m_daemon, SIGNAL(enabledChanged(bool)), SLOT(enabledChanged(bool))); connect(m_daemon, SIGNAL(mousePluggedInChanged(bool)), SLOT(mousePluggedInChanged(bool))); workingTouchpadFoundChanged(m_daemon->workingTouchpadFound()); enabledChanged(m_daemon->isEnabled()); mousePluggedInChanged(m_daemon->isMousePluggedIn()); } void TouchpadEngine::workingTouchpadFoundChanged(bool value) { setData(m_source, "workingTouchpadFound", value); } void TouchpadEngine::mousePluggedInChanged(bool value) { setData(m_source, "mousePluggedIn", value); } void TouchpadEngine::enabledChanged(bool value) { setData(m_source, "enabled", value); } Plasma::Service *TouchpadEngine::serviceForSource(const QString &source) { if (source == m_source) { return new TouchpadService(m_daemon, source, this); } return Plasma::DataEngine::serviceForSource(source); } TouchpadEngine::~TouchpadEngine() { } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(touchpad, TouchpadEngine, "plasma-dataengine-touchpad.json") #include "touchpadengine.moc" diff --git a/kcms/touchpad/backends/x11/synapticstouchpad.cpp b/kcms/touchpad/backends/x11/synapticstouchpad.cpp index 3eb3171ee..6b7e7ddfb 100644 --- a/kcms/touchpad/backends/x11/synapticstouchpad.cpp +++ b/kcms/touchpad/backends/x11/synapticstouchpad.cpp @@ -1,273 +1,273 @@ /* * Copyright (C) 2013 Alexander Mezin * * 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) any later version. * * 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. */ /* //krazy:excludeall=copyright * This file incorporates work covered by the following copyright and * permission notice: * * Copyright © 2002-2005,2007 Peter Osterlund * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of Red Hat * not be used in advertising or publicity pertaining to distribution * of the software without specific, written prior permission. Red * Hat makes no representations about the suitability of this software * for any purpose. It is provided "as is" without express or implied * warranty. * * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * Authors: * Peter Osterlund (petero2@telia.com) */ #include #include #include "synapticstouchpad.h" #include #include #include #define SYN_MAX_BUTTONS 12 const struct Parameter synapticsProperties[] = { {"LeftEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 0}, {"RightEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 1}, {"TopEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 2}, {"BottomEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_EDGES, 32, 3}, {"FingerLow", PT_INT, 0, 255, SYNAPTICS_PROP_FINGER, 32, 0}, {"FingerHigh", PT_INT, 0, 255, SYNAPTICS_PROP_FINGER, 32, 1}, {"MaxTapTime", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_TIME, 32, 0}, {"MaxTapMove", PT_INT, 0, 2000, SYNAPTICS_PROP_TAP_MOVE, 32, 0}, {"MaxDoubleTapTime", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_DURATIONS,32, 1}, {"SingleTapTimeout", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_DURATIONS,32, 0}, {"ClickTime", PT_INT, 0, 1000, SYNAPTICS_PROP_TAP_DURATIONS,32, 2}, {"FastTaps", PT_BOOL, 0, 1, SYNAPTICS_PROP_TAP_FAST, 8, 0}, {"EmulateMidButtonTime", PT_INT, 0, 1000, SYNAPTICS_PROP_MIDDLE_TIMEOUT,32, 0}, {"EmulateTwoFingerMinZ", PT_INT, 0, 1000, SYNAPTICS_PROP_TWOFINGER_PRESSURE, 32, 0}, {"EmulateTwoFingerMinW", PT_INT, 0, 15, SYNAPTICS_PROP_TWOFINGER_WIDTH, 32, 0}, {"VertScrollDelta", PT_INT, -1000, 1000, SYNAPTICS_PROP_SCROLL_DISTANCE, 32, 0}, {"HorizScrollDelta", PT_INT, -1000, 1000, SYNAPTICS_PROP_SCROLL_DISTANCE, 32, 1}, {"VertEdgeScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_EDGE, 8, 0}, {"HorizEdgeScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_EDGE, 8, 1}, {"CornerCoasting", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_EDGE, 8, 2}, {"VertTwoFingerScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_TWOFINGER, 8, 0}, {"HorizTwoFingerScroll", PT_BOOL, 0, 1, SYNAPTICS_PROP_SCROLL_TWOFINGER, 8, 1}, {"MinSpeed", PT_DOUBLE, 0, 255.0, SYNAPTICS_PROP_SPEED, 0, /*float */ 0}, {"MaxSpeed", PT_DOUBLE, 0, 255.0, SYNAPTICS_PROP_SPEED, 0, /*float */ 1}, {"AccelFactor", PT_DOUBLE, 0, 1.0, SYNAPTICS_PROP_SPEED, 0, /*float */ 2}, /*{"TouchpadOff", PT_INT, 0, 2, SYNAPTICS_PROP_OFF, 8, 0},*/ {"LockedDrags", PT_BOOL, 0, 1, SYNAPTICS_PROP_LOCKED_DRAGS, 8, 0}, {"LockedDragTimeout", PT_INT, 0, 30000, SYNAPTICS_PROP_LOCKED_DRAGS_TIMEOUT, 32, 0}, {"RTCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 0}, {"RBCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 1}, {"LTCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 2}, {"LBCornerButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 3}, {"OneFingerTapButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 4}, {"TwoFingerTapButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 5}, {"ThreeFingerTapButton", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_TAP_ACTION, 8, 6}, {"ClickFinger1", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION, 8, 0}, {"ClickFinger2", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION, 8, 1}, {"ClickFinger3", PT_INT, 0, SYN_MAX_BUTTONS, SYNAPTICS_PROP_CLICK_ACTION, 8, 2}, {"CircularScrolling", PT_BOOL, 0, 1, SYNAPTICS_PROP_CIRCULAR_SCROLLING, 8, 0}, {"CircScrollDelta", PT_DOUBLE, .01, 3, SYNAPTICS_PROP_CIRCULAR_SCROLLING_DIST, 0 /* float */, 0}, {"CircScrollTrigger", PT_INT, 0, 8, SYNAPTICS_PROP_CIRCULAR_SCROLLING_TRIGGER, 8, 0}, {"PalmDetect", PT_BOOL, 0, 1, SYNAPTICS_PROP_PALM_DETECT, 8, 0}, {"PalmMinWidth", PT_INT, 0, 15, SYNAPTICS_PROP_PALM_DIMENSIONS, 32, 0}, {"PalmMinZ", PT_INT, 0, 255, SYNAPTICS_PROP_PALM_DIMENSIONS, 32, 1}, {"CoastingSpeed", PT_DOUBLE, 0, 255, SYNAPTICS_PROP_COASTING_SPEED, 0 /* float*/, 0}, {"CoastingFriction", PT_DOUBLE, 0, 255, SYNAPTICS_PROP_COASTING_SPEED, 0 /* float*/, 1}, {"PressureMotionMinZ", PT_INT, 1, 255, SYNAPTICS_PROP_PRESSURE_MOTION, 32, 0}, {"PressureMotionMaxZ", PT_INT, 1, 255, SYNAPTICS_PROP_PRESSURE_MOTION, 32, 1}, {"PressureMotionMinFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 0 /*float*/, 0}, {"PressureMotionMaxFactor", PT_DOUBLE, 0, 10.0,SYNAPTICS_PROP_PRESSURE_MOTION_FACTOR, 0 /*float*/, 1}, {"GrabEventDevice", PT_BOOL, 0, 1, SYNAPTICS_PROP_GRAB, 8, 0}, {"TapAndDragGesture", PT_BOOL, 0, 1, SYNAPTICS_PROP_GESTURES, 8, 0}, {"AreaLeftEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 0}, {"AreaRightEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 1}, {"AreaTopEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 2}, {"AreaBottomEdge", PT_INT, 0, 10000, SYNAPTICS_PROP_AREA, 32, 3}, {"HorizHysteresis", PT_INT, 0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32, 0}, {"VertHysteresis", PT_INT, 0, 10000, SYNAPTICS_PROP_NOISE_CANCELLATION, 32, 1}, {"ClickPad", PT_BOOL, 0, 1, SYNAPTICS_PROP_CLICKPAD, 8, 0}, {"RightButtonAreaLeft", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 0}, {"RightButtonAreaRight", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 1}, {"RightButtonAreaTop", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 2}, {"RightButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 3}, {"MiddleButtonAreaLeft", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 4}, {"MiddleButtonAreaRight", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 5}, {"MiddleButtonAreaTop", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 6}, {"MiddleButtonAreaBottom", PT_INT, INT_MIN, INT_MAX, SYNAPTICS_PROP_SOFTBUTTON_AREAS, 32, 7}, { NULL, PT_INT, 0, 0, nullptr, 0, 0 } }; SynapticsTouchpad::SynapticsTouchpad(Display *display, int deviceId): XlibTouchpad(display, deviceId), m_resX(1), m_resY(1) { m_capsAtom.intern(m_connection, SYNAPTICS_PROP_CAPABILITIES); m_touchpadOffAtom.intern(m_connection, SYNAPTICS_PROP_OFF); XcbAtom resolutionAtom(m_connection, SYNAPTICS_PROP_RESOLUTION); XcbAtom edgesAtom(m_connection, SYNAPTICS_PROP_EDGES); loadSupportedProperties(synapticsProperties); m_toRadians.append("CircScrollDelta"); PropertyInfo edges(m_display, m_deviceId, edgesAtom, 0); if (edges.i && edges.nitems == 4) { int w = qAbs(edges.i[1] - edges.i[0]); int h = qAbs(edges.i[3] - edges.i[2]); m_resX = w / 90; m_resY = h / 50; qDebug() << "Width: " << w << " height: " << h; qDebug() << "Approx. resX: " << m_resX << " resY: " << m_resY; } PropertyInfo resolution(m_display, m_deviceId, resolutionAtom, 0); if (resolution.i && resolution.nitems == 2 && resolution.i[0] > 1 && resolution.i[1] > 1) { m_resY = qMin(static_cast(resolution.i[0]), static_cast(INT_MAX)); m_resX = qMin(static_cast(resolution.i[1]), static_cast(INT_MAX)); qDebug() << "Touchpad resolution: x: " << m_resX << " y: " << m_resY; } m_scaleByResX.append("HorizScrollDelta"); m_scaleByResY.append("VertScrollDelta"); m_scaleByResX.append("MaxTapMove"); m_scaleByResY.append("MaxTapMove"); m_resX = qMax(10, m_resX); m_resY = qMax(10, m_resY); qDebug() << "Final resolution x:" << m_resX << " y:" << m_resY; m_negate["HorizScrollDelta"] = "InvertHorizScroll"; m_negate["VertScrollDelta"] = "InvertVertScroll"; m_supported.append(m_negate.values()); m_supported.append("Coasting"); PropertyInfo caps(m_display, m_deviceId, m_capsAtom.atom(), 0); if (!caps.b) { return; } enum TouchpadCapabilitiy { TouchpadHasLeftButton, TouchpadHasMiddleButton, TouchpadHasRightButton, TouchpadTwoFingerDetect, TouchpadThreeFingerDetect, TouchpadPressureDetect, TouchpadPalmDetect, TouchpadCapsCount }; QVector cap(TouchpadCapsCount, false); - qCopy(caps.b, caps.b + qMin(cap.size(), static_cast(caps.nitems)), + std::copy(caps.b, caps.b + qMin(cap.size(), static_cast(caps.nitems)), cap.begin()); if (!cap[TouchpadTwoFingerDetect]) { m_supported.removeAll("HorizTwoFingerScroll"); m_supported.removeAll("VertTwoFingerScroll"); m_supported.removeAll("TwoFingerTapButton"); } if (!cap[TouchpadThreeFingerDetect]) { m_supported.removeAll("ThreeFingerTapButton"); } if (!cap[TouchpadPressureDetect]) { m_supported.removeAll("FingerHigh"); m_supported.removeAll("FingerLow"); m_supported.removeAll("PalmMinZ"); m_supported.removeAll("PressureMotionMinZ"); m_supported.removeAll("PressureMotionMinFactor"); m_supported.removeAll("PressureMotionMaxZ"); m_supported.removeAll("PressureMotionMaxFactor"); m_supported.removeAll("EmulateTwoFingerMinZ"); } if (!cap[TouchpadPalmDetect]) { m_supported.removeAll("PalmDetect"); m_supported.removeAll("PalmMinWidth"); m_supported.removeAll("PalmMinZ"); m_supported.removeAll("EmulateTwoFingerMinW"); } for (QMap::Iterator i = m_negate.begin(); i != m_negate.end(); ++i) { if (!m_supported.contains(i.key())) { m_supported.removeAll(i.value()); } } m_paramList = synapticsProperties; } void SynapticsTouchpad::setTouchpadOff(int touchpadOff) { PropertyInfo off(m_display, m_deviceId, m_touchpadOffAtom.atom(), 0); if (off.b && *(off.b) != touchpadOff) { *(off.b) = touchpadOff; off.set(); } flush(); } int SynapticsTouchpad::touchpadOff() { PropertyInfo off(m_display, m_deviceId, m_touchpadOffAtom.atom(), 0); return off.value(0).toInt(); } XcbAtom &SynapticsTouchpad::touchpadOffAtom() { return m_touchpadOffAtom; } double SynapticsTouchpad::getPropertyScale(const QString &name) const { if (m_scaleByResX.contains(name) && m_scaleByResY.contains(name)) { return std::sqrt(static_cast(m_resX) * m_resX + static_cast(m_resY) * m_resY); } else if (m_scaleByResX.contains(name)) { return m_resX; } else if (m_scaleByResY.contains(name)) { return m_resY; } else if (m_toRadians.contains(name)) { return M_PI_4 / 45.0; } return 1.0; } diff --git a/kcms/users/src/kcm.cpp b/kcms/users/src/kcm.cpp index d0719f9eb..d1f724d83 100644 --- a/kcms/users/src/kcm.cpp +++ b/kcms/users/src/kcm.cpp @@ -1,133 +1,133 @@ /* Copyright 2016-2018 Jan Grulich Copyright 2019 Nicolas Fella Copyright 2020 Carson Black This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include "kcm.h" // KDE #include #include #include // Qt #include #include #include #include #include #include #include #include "user.h" #include "accounts_interface.h" Q_LOGGING_CATEGORY(kcm_users, "kcm_users") K_PLUGIN_FACTORY_WITH_JSON(KCMUserFactory, "kcm_users.json", registerPlugin();) KCMUser::KCMUser(QObject *parent, const QVariantList &args) : KQuickAddons::ConfigModule(parent, args), - m_model(new UserModel()), - m_dbusInterface(new OrgFreedesktopAccountsInterface(QStringLiteral("org.freedesktop.Accounts"), QStringLiteral("/org/freedesktop/Accounts"), QDBusConnection::systemBus(), this)) + m_dbusInterface(new OrgFreedesktopAccountsInterface(QStringLiteral("org.freedesktop.Accounts"), QStringLiteral("/org/freedesktop/Accounts"), QDBusConnection::systemBus(), this)), + m_model(new UserModel()) { KAboutData* about = new KAboutData(QStringLiteral("kcm_users"), i18n("Manage user accounts"), QStringLiteral("0.1"), QString(), KAboutLicense::GPL); about->addAuthor(i18n("Nicolas Fella"), QString(), QStringLiteral("nicolas.fella@gmx.de")); about->addAuthor(i18n("Carson Black"), QString(), QStringLiteral("uhhadd@gmail.com")); setAboutData(about); setButtons(Apply); auto font = QApplication::font("QLabel"); auto fm = QFontMetrics(font); setColumnWidth(fm.capHeight()*30); } bool KCMUser::createUser(const QString& name, const QString& realName, const QString& password, bool isAdmin) { QDBusPendingReply reply = m_dbusInterface->CreateUser(name, realName, isAdmin); reply.waitForFinished(); if (reply.isValid()) { User* createdUser = new User(this); createdUser->setPath(reply.value()); createdUser->setPassword(password); delete createdUser; return true; } return false; } bool KCMUser::deleteUser(int id, bool deleteHome) { QDBusPendingReply<> reply = m_dbusInterface->DeleteUser(id, deleteHome); reply.waitForFinished(); if (reply.isError()) { return false; } else { return true; } } KCMUser::~KCMUser() { } void KCMUser::save() { KQuickAddons::ConfigModule::save(); Q_EMIT apply(); } // Grab the initials of a string QString KCMUser::initializeString(const QString& stringToGrabInitialsOf) { if (stringToGrabInitialsOf.isEmpty()) return ""; auto normalized = stringToGrabInitialsOf.normalized(QString::NormalizationForm_D); if (normalized.contains(" ")) { QStringList split = normalized.split(" "); auto first = split.first(); auto last = split.last(); if (first.isEmpty()) { return QString(last.front()); } if (last.isEmpty()) { return QString(first.front()); } return first.front()+last.front(); } else { return QString(normalized.front()); } } QString KCMUser::plonkImageInTempfile(const QImage& image) { auto file = new QTemporaryFile(qApp); if (file->open()) { image.save(file, "PNG"); } return file->fileName(); } void KCMUser::load() { Q_EMIT reset(); } #include "kcm.moc" diff --git a/kcms/users/src/user.cpp b/kcms/users/src/user.cpp index 07d5f4f93..4c720036c 100644 --- a/kcms/users/src/user.cpp +++ b/kcms/users/src/user.cpp @@ -1,258 +1,258 @@ /* Copyright 2019 Nicolas Fella Copyright 2020 Carson Black This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 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 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include #include #include #include #include "user_interface.h" User::User(QObject* parent) : QObject(parent) {} int User::uid() const { return mUid; } void User::setUid(int value) { if (mUid == value) { return; } mUid = value; Q_EMIT uidChanged(value); } QString User::name() const { return mName; } void User::setName(const QString &value) { if (mName == value) { return; } mName = value; Q_EMIT nameChanged(value); } QString User::realName() const { return mRealName; } void User::setRealName(const QString &value) { if (mRealName == value) { return; } mRealName = value; Q_EMIT realNameChanged(value); } QString User::email() const { return mEmail; } void User::setEmail(const QString &value) { if (mEmail == value) { return;Q_EMIT } mEmail = value; Q_EMIT emailChanged(value); } QUrl User::face() const { return mFace; } bool User::faceValid() const { return mFaceValid; } void User::setFace(const QUrl &value) { if (mFace == value) { return; } mFace = value; mFaceValid = QFile::exists(value.path()); Q_EMIT faceValidChanged(mFaceValid); Q_EMIT faceChanged(value); } bool User::administrator() const { return mAdministrator; } void User::setAdministrator(bool value) { if (mAdministrator == value) { return; } mAdministrator = value; Q_EMIT administratorChanged(value); } void User::setPath(const QDBusObjectPath &path) { if (!m_dbusIface.isNull()) delete m_dbusIface; m_dbusIface = new OrgFreedesktopAccountsUserInterface(QStringLiteral("org.freedesktop.Accounts"), path.path(), QDBusConnection::systemBus(), this); if (!m_dbusIface->isValid() || m_dbusIface->lastError().isValid() || m_dbusIface->systemAccount()) { return; } mPath = path; auto update = [=]() { bool userDataChanged = false; if (mUid != m_dbusIface->uid()) { mUid = m_dbusIface->uid(); userDataChanged = true; Q_EMIT uidChanged(mUid); } if (mName != m_dbusIface->userName()) { mName = m_dbusIface->userName(); userDataChanged = true; Q_EMIT nameChanged(mName); } if (mFace != QUrl(m_dbusIface->iconFile())) { mFace = QUrl(m_dbusIface->iconFile()); mFaceValid = QFileInfo::exists(mFace.toString()); userDataChanged = true; Q_EMIT faceChanged(mFace); Q_EMIT faceValidChanged(mFaceValid); } if (mRealName != m_dbusIface->realName()) { mRealName = m_dbusIface->realName(); userDataChanged = true; Q_EMIT realNameChanged(mRealName); } if (mEmail != m_dbusIface->email()) { mEmail = m_dbusIface->email(); userDataChanged = true; Q_EMIT emailChanged(mEmail); } const auto administrator = (m_dbusIface->accountType() == 1); if (mAdministrator != administrator) { mAdministrator = administrator; userDataChanged = true; Q_EMIT administratorChanged(mAdministrator); } const auto loggedIn = (mUid == getuid()); if (mLoggedIn != loggedIn) { mLoggedIn = loggedIn; userDataChanged = true; } if (userDataChanged) { Q_EMIT dataChanged(); } }; connect(m_dbusIface, &OrgFreedesktopAccountsUserInterface::Changed, [=]() { update(); }); update(); } static char saltCharacter() { char saltCharacters[] = "ABCDEFGHIJKLMNOPQRSTUVXYZ" "abcdefghijklmnopqrstuvxyz" "./0123456789"; int index = QRandomGenerator::system()->bounded(0, (sizeof(saltCharacters)/sizeof(*saltCharacters))); return saltCharacters[index]; } static QString saltPassword(const QString &plain) { QString salt; salt.append("$6$"); for (auto i = 0; i < 16; i++) { salt.append(saltCharacter()); } salt.append("$"); auto stdStrPlain = plain.toStdString(); auto cStrPlain = stdStrPlain.c_str(); auto stdStrSalt = salt.toStdString(); auto cStrSalt = stdStrSalt.c_str(); auto salted = crypt(cStrPlain, cStrSalt); return QString::fromUtf8(salted); } void User::setPassword(const QString &password) { m_dbusIface->SetPassword(saltPassword(password), QString()); } QDBusObjectPath User::path() const { return mPath; } void User::apply() { auto job = new UserApplyJob(m_dbusIface, mName, mEmail, mRealName, mFace.toString().replace("file://", ""), mAdministrator ? 1 : 0); job->start(); } bool User::loggedIn() const { return mLoggedIn; } UserApplyJob::UserApplyJob(QPointer dbusIface, QString name, QString email, QString realname, QString icon, int type) : KJob(), - m_dbusIface(dbusIface), m_name(name), m_email(email), m_realname(realname), m_icon(icon), - m_type(type) + m_type(type), + m_dbusIface(dbusIface) { } void UserApplyJob::start() { const std::map (OrgFreedesktopAccountsUserInterface::*)(const QString&)> set = { {m_name, &OrgFreedesktopAccountsUserInterface::SetUserName}, {m_email, &OrgFreedesktopAccountsUserInterface::SetEmail}, {m_realname, &OrgFreedesktopAccountsUserInterface::SetRealName}, {m_icon, &OrgFreedesktopAccountsUserInterface::SetIconFile} }; // Do our dbus invocations with blocking calls, since the accounts service // will return permission denied if there's a polkit dialog open while a // request is made. for (auto const &x: set) { (m_dbusIface->*(x.second))(x.first).waitForFinished(); } m_dbusIface->SetAccountType(m_type).waitForFinished(); emitResult(); -} \ No newline at end of file +} diff --git a/solid-device-automounter/kcm/DeviceAutomounterKCM.cpp b/solid-device-automounter/kcm/DeviceAutomounterKCM.cpp index 5e3120eae..5762141e9 100644 --- a/solid-device-automounter/kcm/DeviceAutomounterKCM.cpp +++ b/solid-device-automounter/kcm/DeviceAutomounterKCM.cpp @@ -1,221 +1,219 @@ /************************************************************************** * Copyright (C) 2009-2010 Trever Fischer * * Copyright (C) 2015 Kai UWe Broulik * * * * 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) any later version. * * * * 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 . * ***************************************************************************/ #include "DeviceAutomounterKCM.h" #include #include #include #include #include #include #include #include #include #include #include #include "AutomounterSettings.h" #include "LayoutSettings.h" #include "DeviceModel.h" K_PLUGIN_FACTORY(DeviceAutomounterKCMFactory, registerPlugin();) DeviceAutomounterKCM::DeviceAutomounterKCM(QWidget *parent, const QVariantList &args) : KCModule(parent, args)//DeviceAutomounterKCMFactory::componentData(), parent) , m_settings(new AutomounterSettings(this)) , m_devices(new DeviceModel(m_settings, this)) { KAboutData *about = new KAboutData(QStringLiteral("kcm_device_automounter"), i18n("Device Automounter"), QStringLiteral("2.0"), QString(), KAboutLicense::GPL_V2, i18n("(c) 2009 Trever Fischer, (c) 2015 Kai Uwe Broulik")); about->addAuthor(i18n("Trever Fischer"), i18n("Original Author")); about->addAuthor(i18n("Kai Uwe Broulik"), i18n("Plasma 5 Port"), QStringLiteral("kde@privat.broulik.de")); setAboutData(about); setupUi(this); addConfig(m_settings, this); deviceView->setModel(m_devices); deviceView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); deviceView->header()->setSectionResizeMode(0, QHeaderView::Stretch); connect(kcfg_AutomountOnLogin, &QCheckBox::stateChanged, this, [this](int state) { m_devices->setAutomaticMountOnLogin(state == Qt::Checked); }); connect(kcfg_AutomountOnPlugin, &QCheckBox::stateChanged, this, [this](int state) { m_devices->setAutomaticMountOnPlugin(state == Qt::Checked); }); connect(deviceView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &DeviceAutomounterKCM::updateForgetDeviceButton); connect(forgetDevice, &QAbstractButton::clicked, this, &DeviceAutomounterKCM::forgetSelectedDevices); forgetDevice->setEnabled(false); } DeviceAutomounterKCM::~DeviceAutomounterKCM() { saveLayout(); } void DeviceAutomounterKCM::updateForgetDeviceButton() { const auto selectedIndex = deviceView->selectionModel()->selectedIndexes(); for (const QModelIndex &idx : selectedIndex) { if (idx.data(DeviceModel::TypeRole) == DeviceModel::Detatched) { forgetDevice->setEnabled(true); return; } } forgetDevice->setEnabled(false); } void DeviceAutomounterKCM::forgetSelectedDevices() { QItemSelectionModel *selected = deviceView->selectionModel(); int offset = 0; while (!selected->selectedIndexes().isEmpty() && selected->selectedIndexes().size() > offset) { if (selected->selectedIndexes()[offset].data(DeviceModel::TypeRole) == DeviceModel::Attached) { offset++; } else { m_devices->forgetDevice(selected->selectedIndexes()[offset].data(DeviceModel::UdiRole).toString()); } } emit markAsChanged(); } void DeviceAutomounterKCM::load() { KCModule::load(); - bool automountEnabled = m_settings->automountEnabled(); - kcfg_AutomountUnknownDevices->setEnabled(m_settings->automountEnabled()); kcfg_AutomountOnLogin->setEnabled(m_settings->automountEnabled()); kcfg_AutomountOnPlugin->setEnabled(m_settings->automountEnabled()); m_devices->reload(); loadLayout(); } void DeviceAutomounterKCM::save() { KCModule::save(); saveLayout(); const bool enabled = kcfg_AutomountEnabled->isChecked(); QStringList validDevices; for (int i = 0; i < m_devices->rowCount(); ++i) { const QModelIndex &idx = m_devices->index(i, 0); for (int j = 0; j < m_devices->rowCount(idx); ++j) { QModelIndex dev = m_devices->index(j, 1, idx); const QString device = dev.data(DeviceModel::UdiRole).toString(); validDevices << device; if (dev.data(Qt::CheckStateRole).toInt() == Qt::Checked) { m_settings->deviceSettings(device).writeEntry("ForceLoginAutomount", true); } else { m_settings->deviceSettings(device).writeEntry("ForceLoginAutomount", false); } dev = dev.sibling(j, 2); if (dev.data(Qt::CheckStateRole).toInt() == Qt::Checked) { m_settings->deviceSettings(device).writeEntry("ForceAttachAutomount", true); } else { m_settings->deviceSettings(device).writeEntry("ForceAttachAutomount", false); } } } const auto knownDevices = m_settings->knownDevices(); for (const QString &possibleDevice : knownDevices) { if (!validDevices.contains(possibleDevice)) { m_settings->deviceSettings(possibleDevice).deleteGroup(); } } m_settings->save(); // Now tell kded to automatically load the module if loaded QDBusConnection dbus = QDBusConnection::sessionBus(); QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5"), QStringLiteral("setModuleAutoloading")); msg.setArguments({QVariant(QStringLiteral("device_automounter")), QVariant(enabled)}); dbus.call(msg, QDBus::NoBlock); // Load or unload right away msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5"), enabled ? QStringLiteral("loadModule") : QStringLiteral("unloadModule")); msg.setArguments({QVariant(QStringLiteral("device_automounter"))}); dbus.call(msg, QDBus::NoBlock); } void DeviceAutomounterKCM::saveLayout() { QList widths; const int nbColumn = m_devices->columnCount(); widths.reserve(nbColumn); for (int i = 0; i < nbColumn; ++i) { widths << deviceView->columnWidth(i); } LayoutSettings::setHeaderWidths(widths); //Check DeviceModel.cpp, thats where the magic row numbers come from. LayoutSettings::setAttachedExpanded(deviceView->isExpanded(m_devices->index(0,0))); LayoutSettings::setDetatchedExpanded(deviceView->isExpanded(m_devices->index(1,0))); LayoutSettings::self()->save(); } void DeviceAutomounterKCM::loadLayout() { LayoutSettings::self()->load(); //Reset it first, just in case there isn't any layout saved for a particular column. int nbColumn = m_devices->columnCount(); for (int i = 0; i < nbColumn; ++i) { deviceView->resizeColumnToContents(i); } QList widths = LayoutSettings::headerWidths(); nbColumn = m_devices->columnCount(); for (int i = 0; i < nbColumn && i < widths.size(); ++i) { deviceView->setColumnWidth(i, widths[i]); } deviceView->setExpanded(m_devices->index(0,0), LayoutSettings::attachedExpanded()); deviceView->setExpanded(m_devices->index(1,0), LayoutSettings::detatchedExpanded()); } #include "DeviceAutomounterKCM.moc"