diff --git a/src/kcm.h b/src/kcm.h --- a/src/kcm.h +++ b/src/kcm.h @@ -27,18 +27,21 @@ #include -class QQuickView; +class QQuickItem; class QStandardItemModel; class KCMPlymouth : public KQuickAddons::ConfigModule { Q_OBJECT Q_PROPERTY(QStandardItemModel *themesModel READ themesModel CONSTANT) Q_PROPERTY(QString selectedPlugin READ selectedPlugin WRITE setSelectedPlugin NOTIFY selectedPluginChanged) + Q_PROPERTY(int selectedPluginIndex READ selectedPluginIndex NOTIFY selectedPluginIndexChanged) + Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged) public: enum Roles { - PluginNameRole = Qt::UserRole +1, + DescriptionRole = Qt::UserRole + 1, + PluginNameRole, ScreenhotRole, UninstallableRole }; @@ -50,8 +53,12 @@ QString selectedPlugin() const; void setSelectedPlugin(const QString &plugin); - Q_INVOKABLE int selectedPluginIndex() const; - Q_INVOKABLE void getNewStuff(); + int selectedPluginIndex() const; + + bool busy() const; + void setBusy(const bool &busy); + + Q_INVOKABLE void getNewStuff(QQuickItem *ctx); Q_INVOKABLE void uninstall(const QString &plugin); public Q_SLOTS: @@ -61,10 +68,17 @@ Q_SIGNALS: void selectedPluginChanged(); + void selectedPluginIndexChanged(); + + void busyChanged(); + + void showSuccessMessage(const QString &message); + void showErrorMessage(const QString &message); private: QStandardItemModel *m_model; QString m_selectedPlugin; + bool m_busy; QPointer m_newStuffDialog; }; diff --git a/src/kcm.cpp b/src/kcm.cpp --- a/src/kcm.cpp +++ b/src/kcm.cpp @@ -29,15 +29,13 @@ #include #include #include -#include -#include #include -#include +#include +#include #include #include -#include #include #include @@ -53,24 +51,23 @@ KCMPlymouth::KCMPlymouth(QObject* parent, const QVariantList& args) : KQuickAddons::ConfigModule(parent, args) { - //This flag seems to be needed in order for QQuickWidget to work - //see https://bugreports.qt-project.org/browse/QTBUG-40765 - //also, it seems to work only if set in the kcm, not in the systemsettings' main - qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); qmlRegisterType(); qmlRegisterType(); - KAboutData* about = new KAboutData(QStringLiteral("kcm_plymouth"), i18n("Configure Plymouth Splash Screen"), + KAboutData* about = new KAboutData(QStringLiteral("kcm_plymouth"), i18n("Boot Splash Screen"), QStringLiteral(PLYMOUTH_KCM_VERSION), QString(), KAboutLicense::LGPL); about->addAuthor(i18n("Marco Martin"), QString(), QStringLiteral("mart@kde.org")); setAboutData(about); - setButtons(Apply | Default); + setButtons(Apply); m_model = new QStandardItemModel(this); - QHash roles = m_model->roleNames(); - roles[PluginNameRole] = "pluginName"; - roles[ScreenhotRole] = "screenshot"; - roles[UninstallableRole] = "uninstallable"; - m_model->setItemRoleNames(roles); + + m_model->setItemRoleNames({ + {Qt::DisplayRole, QByteArrayLiteral("display")}, + {DescriptionRole, QByteArrayLiteral("description")}, + {PluginNameRole, QByteArrayLiteral("pluginName")}, + {ScreenhotRole, QByteArrayLiteral("screenshot")}, + {UninstallableRole, QByteArrayLiteral("uninstallable")} + }); //setAuthActionName("org.kde.kcontrol.kcmplymouth.save"); //setNeedsAuthorization(true); @@ -81,11 +78,13 @@ delete m_newStuffDialog.data(); } -void KCMPlymouth::getNewStuff() +void KCMPlymouth::getNewStuff(QQuickItem *ctx) { if (!m_newStuffDialog) { m_newStuffDialog = new KNS3::DownloadDialog( QLatin1String("plymouth.knsrc") ); - m_newStuffDialog.data()->setWindowTitle(i18n("Download New Splash Screens")); + m_newStuffDialog->setWindowTitle(i18n("Download New Boot Splash Screens")); + m_newStuffDialog->setWindowModality(Qt::WindowModal); + m_newStuffDialog->winId(); // so it creates the windowHandle(); connect(m_newStuffDialog.data(), &KNS3::DownloadDialog::accepted, this, &KCMPlymouth::load); connect(m_newStuffDialog.data(), &KNS3::DownloadDialog::finished, m_newStuffDialog.data(), &KNS3::DownloadDialog::deleteLater); @@ -97,7 +96,12 @@ KIO::file_copy(QUrl(entry.previewUrl(KNSCore::EntryInternal::PreviewBig1)), QUrl::fromLocalFile(QString(entry.installedFiles().first() + QStringLiteral(".png"))), -1, KIO::Overwrite | KIO::HideProgressInfo); }); } - m_newStuffDialog.data()->show(); + + if (ctx && ctx->window()) { + m_newStuffDialog->windowHandle()->setTransientParent(ctx->window()); + } + + m_newStuffDialog->show(); } QStandardItemModel *KCMPlymouth::themesModel() @@ -119,12 +123,28 @@ const bool firstTime = m_selectedPlugin.isNull(); m_selectedPlugin = plugin; emit selectedPluginChanged(); + emit selectedPluginIndexChanged(); if (!firstTime) { setNeedsSave(true); } } +bool KCMPlymouth::busy() const +{ + return m_busy; +} + +void KCMPlymouth::setBusy(const bool &busy) +{ + if (m_busy == busy) { + return; + } + + m_busy = busy; + emit busyChanged(); +} + int KCMPlymouth::selectedPluginIndex() const { for (int i = 0; i < m_model->rowCount(); ++i) { @@ -144,17 +164,28 @@ } KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral(PLYMOUTH_CONFIG_PATH)), "Daemon"); - m_selectedPlugin = cg.readEntry("Theme"); KConfigGroup installedCg(KSharedConfig::openConfig(QStringLiteral("kplymouththemeinstallerrc")), "DownloadedThemes"); dir.setFilter(QDir::NoDotAndDotDot|QDir::Dirs); - QFileInfoList list = dir.entryInfoList(); - for (int i = 0; i < list.size(); ++i) { - QFileInfo fileInfo = list.at(i); - QStandardItem* row = new QStandardItem(fileInfo.fileName()); - row->setData(fileInfo.fileName(), PluginNameRole); - row->setData(installedCg.entryMap().contains(fileInfo.fileName()), UninstallableRole); + + const auto list = dir.entryInfoList(); + for (const QFileInfo &fileInfo : list) { + const QString pluginName = fileInfo.fileName(); QDir themeDir(fileInfo.filePath()); + + KConfig file(themeDir.filePath(pluginName + QLatin1String(".plymouth")), KConfig::SimpleConfig); + KConfigGroup grp = file.group("Plymouth Theme"); + + QString displayName = grp.readEntry("Name", QString()); + if (displayName.isEmpty()) { + displayName = pluginName; + } + + QStandardItem *row = new QStandardItem(displayName); + row->setData(pluginName, PluginNameRole); + row->setData(grp.readEntry("Description", QString()), DescriptionRole); + row->setData(installedCg.entryMap().contains(fileInfo.fileName()), UninstallableRole); + //the theme has a preview if (QFile::exists(themeDir.path() + QStringLiteral("/preview.png"))) { row->setData(QString(themeDir.path() + QStringLiteral("/preview.png")), ScreenhotRole); @@ -170,12 +201,15 @@ m_model->appendRow(row); } + setSelectedPlugin(cg.readEntry("Theme")); + setNeedsSave(false); } void KCMPlymouth::save() { + setBusy(true); QVariantMap helperargs; helperargs[QStringLiteral("theme")] = m_selectedPlugin; @@ -188,8 +222,12 @@ KAuth::ExecuteJob *job = action.execute(); bool rc = job->exec(); if (!rc) { - KMessageBox::error(nullptr, i18n("Unable to authenticate/execute the action: %1, %2", job->error(), job->errorString())); + if (job->error() == KAuth::ActionReply::UserCancelledError) { + emit showErrorMessage(i18n("Unable to authenticate/execute the action: %1 (%2)", job->error(), job->errorString())); + } + load(); } + setBusy(false); } void KCMPlymouth::uninstall(const QString &plugin) @@ -205,10 +243,11 @@ KAuth::ExecuteJob *job = action.execute(); bool rc = job->exec(); if (!rc) { - KMessageBox::error(nullptr, i18n("Unable to authenticate/execute the action: %1, %2", job->error(), job->errorString())); + emit showErrorMessage(i18n("Unable to authenticate/execute the action: %1 (%2)", job->error(), job->errorString())); } else { KConfigGroup installedCg(KSharedConfig::openConfig(QStringLiteral("kplymouththemeinstallerrc")), "DownloadedThemes"); installedCg.deleteEntry(plugin); + emit showSuccessMessage(i18n("Theme uninstalled successfully.")); load(); } } diff --git a/src/kcm_plymouth.desktop b/src/kcm_plymouth.desktop --- a/src/kcm_plymouth.desktop +++ b/src/kcm_plymouth.desktop @@ -11,7 +11,7 @@ X-KDE-System-Settings-Parent-Category=session X-KDE-Weight=80 -Name=Boot Splash +Name=Boot Splash Screen Name[ca]=Pantalla de presentació d'inici Name[ca@valencia]=Pantalla de presentació d'inici Name[cs]=Startovací obrazovka @@ -47,7 +47,7 @@ Name[x-test]=xxBoot Splashxx Name[zh_CN]=启动画面 Name[zh_TW]=啟動畫面 -Comment=Plymouth Splash Screen +Comment=Configure Plymouth boot splash screen Comment[ca]=Pantalla de presentació del Plymouth Comment[ca@valencia]=Pantalla de presentació del Plymouth Comment[cs]=Úvodní obrazovka Plymouth @@ -83,7 +83,7 @@ Comment[x-test]=xxPlymouth Splash Screenxx Comment[zh_CN]=Plymouth 闪屏 Comment[zh_TW]=Plymouth 啟動畫面 -X-KDE-Keywords=plymouth,splash,splash screen +X-KDE-Keywords=plymouth,splash,splash screen,boot splash X-KDE-Keywords[ca]=plymouth,splash,pantalla de presentació X-KDE-Keywords[ca@valencia]=plymouth,splash,pantalla de presentació X-KDE-Keywords[da]=plymouth,splash,opstartsskærm diff --git a/src/package/contents/ui/main.qml b/src/package/contents/ui/main.qml --- a/src/package/contents/ui/main.qml +++ b/src/package/contents/ui/main.qml @@ -20,167 +20,81 @@ import QtQuick 2.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.2 -import QtQuick.Controls 1.0 as QtControls -import org.kde.kquickcontrolsaddons 2.0 -import QtQuick.Controls.Private 1.0 -//We need units from it -import org.kde.plasma.core 2.0 as PlasmaCore -import org.kde.plasma.components 2.0 as PlasmaComponents -import org.kde.kcm 1.0 +import QtQuick.Controls 2.3 as QtControls +import org.kde.kirigami 2.4 as Kirigami +import org.kde.kcm 1.1 as KCM -Item { - id: root - implicitWidth: units.gridUnit * 20 - implicitHeight: units.gridUnit * 20 +KCM.GridViewKCM { + KCM.ConfigModule.quickHelp: i18n("This module lets you choose the Plymouth boot splash screen.") - ConfigModule.quickHelp: i18nd("kcm_plymouth", "This module lets you configure the look of the whole workspace with some ready to go presets.") + view.model: kcm.themesModel + view.currentIndex: kcm.selectedPluginIndex - SystemPalette {id: syspal} + view.delegate: KCM.GridDelegate { + id: delegate - ColumnLayout { - anchors.fill: parent - QtControls.Label { - text: i18nd("kcm_plymouth", "Select a global splash screen for the system") - wrapMode: Text.WordWrap - Layout.fillWidth: true + text: model.display + toolTip: model.description + + thumbnailAvailable: !!model.screenshot + thumbnail: Image { + anchors.fill: parent + source: model.screenshot } - QtControls.ScrollView { - Layout.fillWidth: true - Layout.fillHeight: true - GridView { - id: grid - model: kcm.themesModel - cellWidth: Math.floor(root.width / Math.max(Math.floor(root.width / (units.gridUnit*12)), 3)) - units.gridUnit - cellHeight: cellWidth / 1.6 - onCountChanged: { - grid.currentIndex = kcm.selectedPluginIndex(); - grid.positionViewAtIndex(grid.currentIndex, GridView.Visible) - } - delegate: Item { - width: grid.cellWidth - height: grid.cellHeight + actions: [ + Kirigami.Action { + iconName: "edit-delete" + tooltip: i18n("Uninstall") + enabled: model.uninstallable + onTriggered: kcm.uninstall(model.pluginName) + } + ] + onClicked: { + kcm.selectedPlugin = model.pluginName; + view.forceActiveFocus(); + } + } - Rectangle { - anchors { - fill: parent - margins: units.smallSpacing - } - Connections { - target: kcm - onSelectedPluginChanged: { - if (kcm.selectedPlugin == model.pluginName) { - grid.currentIndex = index - } - } - } - QIconItem { - id: icon - anchors.centerIn: parent - width: units.iconSizes.large - height: width - icon: "view-preview" - visible: image.status != Image.Ready - } - Image { - id: image - anchors { - fill: parent - margins: units.smallSpacing * 2 - } - source: model.screenshot + footer: ColumnLayout { + Kirigami.InlineMessage { + id: infoLabel + Layout.fillWidth: true + showCloseButton: true + } - Rectangle { - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - height: childrenRect.height - gradient: Gradient { - GradientStop { - position: 0.0 - color: "transparent" - } - GradientStop { - position: 1.0 - color: Qt.rgba(0, 0, 0, 0.5) - } - } - QtControls.Label { - anchors { - horizontalCenter: parent.horizontalCenter - } - color: "white" - text: model.display - } - } - } - Rectangle { - opacity: grid.currentIndex == index ? 1.0 : 0 - anchors.fill: parent - border.width: units.smallSpacing * 2 - border.color: syspal.highlight - color: "transparent" - Behavior on opacity { - PropertyAnimation { - duration: units.longDuration - easing.type: Easing.OutQuad - } - } - } - MouseArea { - anchors.fill: parent - hoverEnabled: true - onClicked: { - grid.currentIndex = index - kcm.selectedPlugin = model.pluginName - } - Timer { - interval: 1000 // FIXME TODO: Use platform value for tooltip activation delay. + QtControls.Button { + id: getNewButton + Layout.alignment: Qt.AlignRight + text: i18n("Get New Boot Splash Screens...") + icon.name: "get-hot-new-stuff" + onClicked: kcm.getNewStuff(this) + } - running: parent.containsMouse && !parent.pressedButtons + QtControls.ProgressBar { + id: progressBar + Layout.fillWidth: true + visible: false + indeterminate: true + } + } - onTriggered: { - Tooltip.showText(parent, Qt.point(parent.mouseX, parent.mouseY), model.display); - } - } - PlasmaComponents.ToolButton { - anchors { - top: parent.top - right: parent.right - margins: units.smallSpacing - } - visible: model.uninstallable - iconSource: "list-remove" - tooltip: i18nd("kcm_plymouth", "Uninstall") - flat: false - onClicked: { - kcm.uninstall(model.pluginName); - } - opacity: parent.containsMouse ? 1 : 0 - Behavior on opacity { - PropertyAnimation { - duration: units.longDuration - easing.type: Easing.OutQuad - } - } - } - } - } - } - } + Connections { + target: kcm + onShowSuccessMessage: { + infoLabel.type = Kirigami.MessageType.Positive; + infoLabel.text = message; + infoLabel.visible = true; } - RowLayout { - Item { - Layout.fillWidth: true - } - QtControls.Button { - anchors.right: parent.right - text: i18nd("kcm_plymouth", "Get New Boot Splash Screens...") - iconName: "get-hot-new-stuff" - onClicked: kcm.getNewStuff(); - } + onShowErrorMessage: { + infoLabel.type = Kirigami.MessageType.Error; + infoLabel.text = message; + infoLabel.visible = true; + } + onBusyChanged: { + view.enabled = !kcm.busy; + getNewButton.enabled = !kcm.busy; + progressBar.visible = kcm.busy; } } }