diff --git a/kcms/notifications/kcm.cpp b/kcms/notifications/kcm.cpp index 9c48ef7bd..a77fae621 100644 --- a/kcms/notifications/kcm.cpp +++ b/kcms/notifications/kcm.cpp @@ -1,208 +1,209 @@ /* * Copyright (C) 2019 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) 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 "kcm.h" #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include "sourcesmodel.h" #include "filterproxymodel.h" #include K_PLUGIN_FACTORY_WITH_JSON(KCMNotificationsFactory, "kcm_notifications.json", registerPlugin();) KCMNotifications::KCMNotifications(QObject *parent, const QVariantList &args) : KQuickAddons::ConfigModule(parent, args) , m_sourcesModel(new SourcesModel(this)) , m_filteredModel(new FilterProxyModel(this)) , m_settings(new NotificationManager::Settings(this)) { const char uri[] = "org.kde.private.kcms.notifications"; qmlRegisterUncreatableType(uri, 1, 0, "SourcesModel", QStringLiteral("Cannot create instances of SourcesModel")); qmlRegisterType(); qmlProtectModule(uri, 1); KAboutData *about = new KAboutData(QStringLiteral("kcm_notifications"), i18n("Notifications"), QStringLiteral("5.0"), QString(), KAboutLicense::GPL); about->addAuthor(i18n("Kai Uwe Broulik"), QString(), QStringLiteral("kde@privat.broulik.de")); setAboutData(about); m_filteredModel->setSourceModel(m_sourcesModel); QStringList stringArgs; stringArgs.reserve(args.count() + 1); // need to add a fake argv[0] for QCommandLineParser stringArgs.append(QStringLiteral("kcm_notifications")); for (const QVariant &arg : args) { stringArgs.append(arg.toString()); } QCommandLineParser parser; QCommandLineOption desktopEntryOption(QStringLiteral("desktop-entry"), QString(), QStringLiteral("desktop-entry")); parser.addOption(desktopEntryOption); QCommandLineOption notifyRcNameOption(QStringLiteral("notifyrc"), QString(), QStringLiteral("notifyrcname")); parser.addOption(notifyRcNameOption); QCommandLineOption eventIdOption(QStringLiteral("event-id"), QString(), QStringLiteral("event-id")); parser.addOption(eventIdOption); parser.parse(stringArgs); setInitialDesktopEntry(parser.value(desktopEntryOption)); setInitialNotifyRcName(parser.value(notifyRcNameOption)); setInitialEventId(parser.value(eventIdOption)); } KCMNotifications::~KCMNotifications() { } SourcesModel *KCMNotifications::sourcesModel() const { return m_sourcesModel; } FilterProxyModel *KCMNotifications::filteredModel() const { return m_filteredModel; } NotificationManager::Settings *KCMNotifications::settings() const { return m_settings; } QString KCMNotifications::initialDesktopEntry() const { return m_initialDesktopEntry; } void KCMNotifications::setInitialDesktopEntry(const QString &desktopEntry) { if (m_initialDesktopEntry != desktopEntry) { m_initialDesktopEntry = desktopEntry; emit initialDesktopEntryChanged(); } } QString KCMNotifications::initialNotifyRcName() const { return m_initialNotifyRcName; } void KCMNotifications::setInitialNotifyRcName(const QString ¬ifyRcName) { if (m_initialNotifyRcName != notifyRcName) { m_initialNotifyRcName = notifyRcName; emit initialNotifyRcNameChanged(); } } QString KCMNotifications::initialEventId() const { return m_initialEventId; } void KCMNotifications::setInitialEventId(const QString &eventId) { if (m_initialEventId != eventId) { m_initialEventId = eventId; emit initialEventIdChanged(); } } void KCMNotifications::configureEvents(const QString ¬ifyRcName, const QString &eventId, QQuickItem *ctx) { // We're not using KNotifyConfigWidget::configure here as we want to handle the // saving ourself (so we Apply with all other KCM settings) but there's no way // to access the config object :( // We also need access to the QDialog so we can set the KCM as transient parent. QDialog *dialog = new QDialog(nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Configure Notifications")); if (ctx && ctx->window()) { dialog->winId(); // so it creates windowHandle - dialog->windowHandle()->setTransientParent(ctx->window()); + dialog->windowHandle()->setTransientParent(QQuickRenderControl::renderWindowFor(ctx->window())); dialog->setModal(true); } KNotifyConfigWidget *w = new KNotifyConfigWidget(dialog); QDialogButtonBox *buttonBox = new QDialogButtonBox(dialog); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel); buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(w); layout->addWidget(buttonBox); dialog->setLayout(layout); // TODO we should only save settings when clicking Apply in the main UI connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, w, &KNotifyConfigWidget::save); connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, w, &KNotifyConfigWidget::save); connect(w, &KNotifyConfigWidget::changed, buttonBox->button(QDialogButtonBox::Apply), &QPushButton::setEnabled); connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); w->setApplication(notifyRcName); w->selectEvent(eventId); dialog->show(); } void KCMNotifications::load() { m_settings->load(); } void KCMNotifications::save() { m_settings->save(); } void KCMNotifications::defaults() { m_settings->defaults(); } #include "kcm.moc" diff --git a/kcms/notifications/sourcesmodel.cpp b/kcms/notifications/sourcesmodel.cpp index 6de564867..3293a9540 100644 --- a/kcms/notifications/sourcesmodel.cpp +++ b/kcms/notifications/sourcesmodel.cpp @@ -1,350 +1,348 @@ /* * Copyright (C) 2007 Matthew Woehlke * Copyright (C) 2007 Jeremy Whiting * Copyright (C) 2016 Olivier Churlaud * Copyright (C) 2019 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) 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 "sourcesmodel.h" #include #include #include #include #include #include #include #include #include #include #include -#include - SourcesModel::SourcesModel(QObject *parent) : QAbstractItemModel(parent) { } SourcesModel::~SourcesModel() = default; QPersistentModelIndex SourcesModel::makePersistentModelIndex(const QModelIndex &idx) const { return QPersistentModelIndex(idx); } QPersistentModelIndex SourcesModel::persistentIndexForDesktopEntry(const QString &desktopEntry) const { const auto matches = match(index(0, 0), SourcesModel::DesktopEntryRole, desktopEntry, 1, Qt::MatchFixedString); if (matches.isEmpty()) { return QPersistentModelIndex(); } return QPersistentModelIndex(matches.first()); } QPersistentModelIndex SourcesModel::persistentIndexForNotifyRcName(const QString ¬ifyRcName) const { const auto matches = match(index(0, 0), SourcesModel::NotifyRcNameRole, notifyRcName, 1, Qt::MatchFixedString); if (matches.isEmpty()) { return QPersistentModelIndex(); } return QPersistentModelIndex(matches.first()); } int SourcesModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 1; } int SourcesModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) { return 0; } if (!parent.isValid()) { return m_data.count(); } if (parent.internalId()) { return 0; } return m_data.at(parent.row()).events.count(); } QVariant SourcesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.internalId()) { // event const auto &event = m_data.at(index.internalId() - 1).events.at(index.row()); switch (role) { case Qt::DisplayRole: return event.name; case Qt::DecorationRole: return event.iconName; case EventIdRole: return event.eventId; case ActionsRole: return event.actions; } return QVariant(); } const auto &source = m_data.at(index.row()); switch (role) { case Qt::DisplayRole: return source.display(); case Qt::DecorationRole: return source.iconName; case SourceTypeRole: return source.desktopEntry.isEmpty() ? ServiceType : ApplicationType; case NotifyRcNameRole: return source.notifyRcName; case DesktopEntryRole: return source.desktopEntry; } return QVariant(); } bool SourcesModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) { return false; } bool dirty = false; if (index.internalId()) { // event auto &event = m_data[index.internalId() - 1].events[index.row()]; switch (role) { case ActionsRole: { const QStringList newActions = value.toStringList(); if (event.actions != newActions) { event.actions = newActions; dirty = true; } break; } } } if (dirty) { emit dataChanged(index, index, {role}); } return dirty; } QModelIndex SourcesModel::index(int row, int column, const QModelIndex &parent) const { if (row < 0 || column != 0) { return QModelIndex(); } if (parent.isValid()) { const auto events = m_data.at(parent.row()).events; if (row < events.count()) { return createIndex(row, column, parent.row() + 1); } return QModelIndex(); } if (row < m_data.count()) { return createIndex(row, column, nullptr); } return QModelIndex(); } QModelIndex SourcesModel::parent(const QModelIndex &child) const { if (child.internalId()) { return createIndex(child.internalId() - 1, 0, nullptr); } return QModelIndex(); } QHash SourcesModel::roleNames() const { return { {Qt::DisplayRole, QByteArrayLiteral("display")}, {Qt::DecorationRole, QByteArrayLiteral("decoration")}, {SourceTypeRole, QByteArrayLiteral("sourceType")}, {NotifyRcNameRole, QByteArrayLiteral("notifyRcName")}, {DesktopEntryRole, QByteArrayLiteral("desktopEntry")}, {EventIdRole, QByteArrayLiteral("eventId")}, {ActionsRole, QByteArrayLiteral("actions")} }; } void SourcesModel::load() { beginResetModel(); m_data.clear(); QCollator collator; QVector appsData; QVector servicesData; QStringList notifyRcFiles; QStringList desktopEntries; // old code did KGlobal::dirs()->findAllResources("data", QStringLiteral("*/*.notifyrc")) but in KF5 // only notifyrc files in knotifications5/ folder are supported const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications5"), QStandardPaths::LocateDirectory); for (const QString &dir : dirs) { const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.notifyrc")); for (const QString &file : fileNames) { if (notifyRcFiles.contains(file)) { continue; } notifyRcFiles.append(file); KConfig *config = new KConfig(file, KConfig::NoGlobals); config->addConfigSources(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("knotifications5/") + file)); KConfigGroup globalGroup(config, QLatin1String("Global")); const QRegularExpression regExp(QStringLiteral("^Event/([^/]*)$")); const QStringList groups = config->groupList().filter(regExp); const QString notifyRcName = file.section(QLatin1Char('.'), 0, -2); const QString desktopEntry = globalGroup.readEntry(QStringLiteral("DesktopEntry")); if (!desktopEntry.isEmpty()) { if (desktopEntries.contains(desktopEntry)) { continue; } desktopEntries.append(desktopEntry); } SourceData source{ // The old KCM read the Name and Comment from global settings disregarding // any user settings and just used user-specific files for actions config // I'm pretty sure there's a readEntry equivalent that does that without // reading the config stuff twice, assuming we care about this to begin with globalGroup.readEntry(QStringLiteral("Name")), globalGroup.readEntry(QStringLiteral("Comment")), globalGroup.readEntry(QStringLiteral("IconName")), notifyRcName, desktopEntry, {} // events }; QVector events; for (const QString &group : groups) { KConfigGroup cg(config, group); const QString eventId = regExp.match(group).captured(1); // TODO context stuff // TODO load defaults thing EventData event{ cg.readEntry("Name"), cg.readEntry("Comment"), cg.readEntry("IconName"), eventId, // TODO Flags? cg.readEntry("Action").split(QLatin1Char('|')) }; events.append(event); } std::sort(events.begin(), events.end(), [&collator](const EventData &a, const EventData &b) { return collator.compare(a.name, b.name) < 0; }); source.events = events; if (!source.desktopEntry.isEmpty()) { appsData.append(source); } else { servicesData.append(source); } } } const auto services = KServiceTypeTrader::self()->query(QStringLiteral("Application"), QStringLiteral("exist Exec and TRUE == [X-GNOME-UsesNotifications]")); for (const auto &service : services) { if (service->noDisplay()) { continue; } if (desktopEntries.contains(service->desktopEntryName())) { continue; } SourceData source{ service->name(), service->comment(), service->icon(), QString(), //notifyRcFile service->desktopEntryName(), {} // events }; appsData.append(source); desktopEntries.append(service->desktopEntryName()); } KSharedConfig::Ptr plasmanotifyrc = KSharedConfig::openConfig(QStringLiteral("plasmanotifyrc")); KConfigGroup applicationsGroup = plasmanotifyrc->group("Applications"); const QStringList seenApps = applicationsGroup.groupList(); for (const QString &app : seenApps) { if (desktopEntries.contains(app)) { continue; } KService::Ptr service = KService::serviceByDesktopName(app); if (!service || service->noDisplay()) { continue; } SourceData source{ service->name(), service->comment(), service->icon(), QString(), //notifyRcFile service->desktopEntryName(), {} }; appsData.append(source); desktopEntries.append(service->desktopEntryName()); } auto sortData = [&collator](const SourceData &a, const SourceData &b) { return collator.compare(a.display(), b.display()) < 0; }; std::sort(appsData.begin(), appsData.end(), sortData); std::sort(servicesData.begin(), servicesData.end(), sortData); m_data << appsData << servicesData; endResetModel(); }