diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,7 @@ add_subdirectory(engineexplorer) add_subdirectory(plasmoidviewer) add_subdirectory(themeexplorer) +add_subdirectory(lookandfeelexplorer) #add_subdirectory(remote-widgets-browser) #add_subdirectory(wallpaperviewer) if (KDevPlatform_FOUND AND Qt5WebKit_FOUND AND Qt5WebKitWidgets_FOUND) diff --git a/lookandfeelexplorer/CMakeLists.txt b/lookandfeelexplorer/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/CMakeLists.txt @@ -0,0 +1,10 @@ +project(lookandfeelexplorer) +add_definitions(-DTRANSLATION_DOMAIN=\"org.kde.plasma.lookandfeelexplorer\") + +add_subdirectory(src) + +kpackage_install_package(package org.kde.plasma.lookandfeelexplorer genericqml) +# install(FILES metadata.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) + +install(FILES package/metadata.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} RENAME org.kde.plasma.lookandfeelexplorer.desktop) + diff --git a/lookandfeelexplorer/Messages.sh b/lookandfeelexplorer/Messages.sh new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/Messages.sh @@ -0,0 +1,3 @@ +#! /bin/sh +$XGETTEXT `find . -name "*.cpp" -o -name "*.qml"` -o $podir/org.kde.plasma.lookandfeelexplorer.pot + diff --git a/lookandfeelexplorer/package/contents/ui/FormField.qml b/lookandfeelexplorer/package/contents/ui/FormField.qml new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/package/contents/ui/FormField.qml @@ -0,0 +1,37 @@ +/* + * Copyright 2016 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 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. + */ + +import QtQuick 2.3 +import QtQuick.Controls 1.2 + +TextField { + id: field + property string key + text: lnfLogic[key] + onAccepted: { + lnfLogic.name = text; + timer.running = false; + } + onTextChanged: timer.restart() + Timer { + id: timer + interval: 1000 + onTriggered: lnfLogic[key] = field.text + } +} diff --git a/lookandfeelexplorer/package/contents/ui/FormLabel.qml b/lookandfeelexplorer/package/contents/ui/FormLabel.qml new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/package/contents/ui/FormLabel.qml @@ -0,0 +1,33 @@ +/* + * Copyright 2015 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 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. + */ + +import QtQuick 2.3 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.2 +import org.kde.kirigami 1.0 as Kirigami + +Kirigami.Label { + Layout.alignment: Qt.AlignRight + property Item buddy + MouseArea { + anchors.fill: parent + onClicked: buddy.focus = true + } +} + diff --git a/lookandfeelexplorer/package/contents/ui/MetadataEditor.qml b/lookandfeelexplorer/package/contents/ui/MetadataEditor.qml new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/package/contents/ui/MetadataEditor.qml @@ -0,0 +1,192 @@ +/* + * Copyright 2015 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 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. + */ + +import QtQuick 2.3 +import QtQuick.Layouts 1.1 +import QtQuick.Controls 1.2 +import QtQuick.Dialogs 1.2 +import org.kde.plasma.core 2.0 as PlasmaCore + +Dialog { + id: dialog + property alias pluginName: pluginNameField.text + property alias name: nameField.text + property alias comment: commentField.text + property alias author: authorField.text + property alias email: emailField.text + property alias license: licenseField.editText + property alias website: websiteField.text + + property bool canEdit: false + + width: 500 + title: i18n("New Theme") + + onVisibleChanged: { + nameField.focus = true + } + + //all this reimplementing shouldn't be necessary, + //but unfortunately native standard buttons management + //is completely broken + contentItem: Rectangle { + implicitWidth: layout.Layout.minimumWidth + units.smallSpacing*2 + implicitHeight: layout.Layout.minimumHeight + units.smallSpacing*2 + + Keys.onPressed: { + if (event.key == Qt.Key_Enter || event.key == Qt.Key_Return) { + dialog.accept(); + } else if (event.key == Qt.Key_Escape) { + dialog.reject(); + } + } + + SystemPalette { + id: palette + } + color: palette.window + + ColumnLayout { + id: layout + anchors { + fill: parent + margins: units.smallSpacing + } + Label { + id: errorMessage + text: "" + wrapMode: Text.WordWrap + Layout.fillWidth: true + } + GridLayout { + Layout.fillWidth: true + columns: 2 + columnSpacing: units.smallSpacing + + FormLabel { + text: i18n("Theme Plugin Name:") + buddy: pluginNameField + } + TextField { + id: pluginNameField + Layout.fillWidth: true + onTextChanged: { + for (var i = 0; i < lnfLogic.lnfList.count; ++i) { + if (pluginNameField.text == lnfLogic.lnfList.get(i).packageNameRole) { + dialog.canEdit = false; + errorMessage.text = i18n("This theme plugin name already exists"); + return; + } + } + errorMessage.text = ""; + dialog.canEdit = true; + } + } + FormLabel { + text: i18n("Theme Name:") + buddy: nameField + } + TextField { + id: nameField + Layout.fillWidth: true + onTextChanged: { + for (var i = 0; i < lnfLogic.lnfList.count; ++i) { + if (nameField.text == lnfLogic.lnfList.get(i).displayRole) { + dialog.canEdit = false; + errorMessage.text = i18n("This theme name already exists"); + return; + } + } + errorMessage.text = ""; + dialog.canEdit = true; + } + } + FormLabel { + text: i18n("Comment:") + buddy: commentField + } + TextField { + id: commentField + Layout.fillWidth: true + } + FormLabel { + text: i18n("Author:") + buddy: authorField + } + TextField { + id: authorField + Layout.fillWidth: true + } + FormLabel { + text: i18n("Email:") + buddy: emailField + } + TextField { + id: emailField + Layout.fillWidth: true + } + FormLabel { + text: i18n("License:") + buddy: licenseField + } + ComboBox { + id: licenseField + Layout.fillWidth: true + editable: true + editText: "LGPL 2.1+" + model: ["LGPL 2.1+", "GPL 2+", "GPL 3+", "LGPL 3+", "BSD"] + } + FormLabel { + text: i18n("Website:") + buddy: websiteField + } + TextField { + id: websiteField + Layout.fillWidth: true + } + } + Item { + Layout.fillHeight: true + } + RowLayout { + Layout.alignment: Qt.AlignRight + Button { + text: i18n("Ok") + onClicked: dialog.accept() + isDefault: true + enabled: canEdit && nameField.text && authorField.text && emailField.text && websiteField.text + } + Button { + text: i18n("Cancel") + onClicked: dialog.reject() + } + } + } + } + + onAccepted: { + lnfLogic.createNewTheme(pluginNameField.text, nameField.text, commentField.text, authorField.text, emailField.text, licenseField.editText, websiteField.text); + for (var i = 0; i < lnfLogic.lnfList.count; ++i) { + if (nameField.text == lnfLogic.lnfList.get(i).packageNameRole) { + themeSelector.currentIndex = i; + break; + } + } + } +} diff --git a/lookandfeelexplorer/package/contents/ui/main.qml b/lookandfeelexplorer/package/contents/ui/main.qml --- a/lookandfeelexplorer/package/contents/ui/main.qml +++ b/lookandfeelexplorer/package/contents/ui/main.qml @@ -27,17 +27,31 @@ Kirigami.AbstractApplicationWindow { id: root - width: units.gridUnit * 40 - height: units.gridUnit * 28 + width: units.gridUnit * 50 + height: units.gridUnit * 26 visible: true - toolBar: ToolBar { - RowLayout { - anchors.fill: parent - ToolButton { + globalDrawer: Kirigami.GlobalDrawer { + title: i18n("Look And Feel") + titleIcon: "preferences-desktop-theme" + topContent: ComboBox { + id: themeSelector + anchors { + left: parent.left + right: parent.right + } + //FIXME: why crashes? + //model: lnfLogic.lnfList + textRole: "displayRole" + onCurrentIndexChanged: { + lnfLogic.theme = lnfLogic.lnfList.get(currentIndex).packageNameRole; + } + } + actions: [ + Kirigami.Action { text: i18n("New Theme...") iconName: "document-new" - onClicked: { + onTriggered: { if (!root.metadataEditor) { root.metadataEditor = metadataEditorComponent.createObject(root); } @@ -50,28 +64,13 @@ root.metadataEditor.website = ""; root.metadataEditor.open(); } - } - Kirigami.Label { - text: i18n("Theme:") - } - ComboBox { - id: themeSelector - //FIXME: why crashes? - //model: lnfLogic.lnfList - textRole: "displayRole" - onCurrentIndexChanged: { - lnfLogic.theme = lnfLogic.lnfList.get(currentIndex).packageNameRole; - } - } - ToolButton { - text: i18n("Open Folder") + }, + Kirigami.Action { + text: i18n("Open Theme Folder") iconName: "document-open-folder" - onClicked: Qt.openUrlExternally(lnfLogic.themeFolder); + onTriggered: Qt.openUrlExternally(lnfLogic.themeFolder); } - Item { - Layout.fillWidth: true - } - } + ] } property QtObject metadataEditor @@ -163,13 +162,13 @@ key: "license" } Button { - text: i18n("Take layout from current Plasma setup") + text: i18n("Layout from current Plasma setup") onClicked: lnfLogic.dumpCurrentPlasmaLayout(); Layout.columnSpan: 2 Layout.alignment: Qt.AlignRight } Button { - text: i18n("Take defaults from current setup") + text: i18n("Defaults from current setup") onClicked: lnfLogic.dumpDefaultsConfigFile(lnfLogic.theme); Layout.columnSpan: 2 Layout.alignment: Qt.AlignRight diff --git a/lookandfeelexplorer/package/metadata.desktop b/lookandfeelexplorer/package/metadata.desktop new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/package/metadata.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Comment=Explore and edit your Plasma Look And Feel themes +Encoding=UTF-8 +Keywords= +Name=Plasma Look And Feel Explorer +Type=Application +Icon=preferences-desktop-theme +Categories=Qt;KDE;Development; +Exec=lookandfeelexplorer +X-KDE-ParentApp= +X-KDE-PluginInfo-Author=Marco Martin +X-KDE-PluginInfo-Category=Themes +X-KDE-PluginInfo-Email=mart@kde.org +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Name=org.kde.plasma.lookandfeelexplorer +X-KDE-PluginInfo-Version= +X-KDE-PluginInfo-Website= +X-KDE-ServiceTypes=KPackage/GenericQML + +X-Plasma-MainScript=ui/main.qml +X-Plasma-RemoteLocation= diff --git a/lookandfeelexplorer/src/CMakeLists.txt b/lookandfeelexplorer/src/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/src/CMakeLists.txt @@ -0,0 +1,29 @@ + + +set(lookandfeelexplorer_SRCS + main.cpp + lnflogic.cpp + lnflistmodel.cpp +) + +add_executable(lookandfeelexplorer ${lookandfeelexplorer_SRCS}) +target_compile_definitions(lookandfeelexplorer PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") + +#find_package(ActiveApp REQUIRED) + +target_link_libraries(lookandfeelexplorer + Qt5::Gui + Qt5::Quick + Qt5::Widgets + Qt5::DBus + KF5::Archive + KF5::Declarative + KF5::I18n + KF5::Package + KF5::Plasma + KF5::KIOCore + KF5::KIOWidgets + KF5::QuickAddons +) + +install(TARGETS lookandfeelexplorer ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/lookandfeelexplorer/src/lnflistmodel.h b/lookandfeelexplorer/src/lnflistmodel.h new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/src/lnflistmodel.h @@ -0,0 +1,89 @@ +/* + * LnfListModel + * Copyright (C) 2016 Marco Martin + * Copyright (C) 2002 Karol Szwed + * Copyright (C) 2002 Daniel Molkentin + * Copyright (C) 2007 Urs Wolfer + * Copyright (C) 2009 by Davide Bettio + + * Portions Copyright (C) 2007 Paolo Capriotti + * Portions Copyright (C) 2007 Ivan Cukic + * Portions Copyright (C) 2008 by Petri Damsten + * Portions Copyright (C) 2000 TrollTech AS. + * + * 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. + */ + +#ifndef LNFLISTMODEL_H +#define LNFLISTMODEL_H + +#include + +namespace Plasma +{ + class FrameSvg; +} + +//Theme selector code by Andre Duffeck (modified to add package description) +class ThemeInfo +{ +public: + QString name; + QString package; + QString description; + QString author; + QString version; + QString themeRoot; +}; + +class LnfListModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) +public: + enum { PackageNameRole = Qt::UserRole, + PackageDescriptionRole = Qt::UserRole + 1, + PackageAuthorRole = Qt::UserRole + 2, + PackageVersionRole = Qt::UserRole + 3 + }; + + LnfListModel(QObject *parent = 0); + virtual ~LnfListModel(); + + virtual QHash roleNames() const; + + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QModelIndex indexOf(const QString &path) const; + void reload(); + void clearThemeList(); + int count() const + { + return rowCount(); + } + + Q_INVOKABLE QVariantMap get(int index) const; + +Q_SIGNALS: + void countChanged(); + +private: + QHash m_roleNames; + + QList m_themes; +}; + + +#endif diff --git a/lookandfeelexplorer/src/lnflistmodel.cpp b/lookandfeelexplorer/src/lnflistmodel.cpp new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/src/lnflistmodel.cpp @@ -0,0 +1,186 @@ +/* + * LnfListModel + * Copyright (C) 2016 Marco Martin + * Copyright (C) 2002 Karol Szwed + * Copyright (C) 2002 Daniel Molkentin + * Copyright (C) 2007 Urs Wolfer + * Copyright (C) 2009 by Davide Bettio + + * Portions Copyright (C) 2007 Paolo Capriotti + * Portions Copyright (C) 2007 Ivan Cukic + * Portions Copyright (C) 2008 by Petri Damsten + * Portions Copyright (C) 2000 TrollTech AS. + * + * 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 "lnflistmodel.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +LnfListModel::LnfListModel( QObject *parent ) +: QAbstractListModel( parent ) +{ + m_roleNames.insert(Qt::DisplayRole, "displayRole"); + m_roleNames.insert(PackageNameRole, "packageNameRole"); + m_roleNames.insert(PackageDescriptionRole, "packageDescriptionRole"); + m_roleNames.insert(PackageAuthorRole, "packageAuthorRole"); + m_roleNames.insert(PackageVersionRole, "packageVersionRole"); + + reload(); +} + +LnfListModel::~LnfListModel() +{ + clearThemeList(); +} + +QHash LnfListModel::roleNames() const +{ + return m_roleNames; +} + +void LnfListModel::clearThemeList() +{ + m_themes.clear(); +} + +void LnfListModel::reload() +{ + reset(); + clearThemeList(); + + // get all desktop themes + QStringList themes; + const QStringList &packs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "plasma/look-and-feel", QStandardPaths::LocateDirectory); + foreach (const QString &ppath, packs) { + const QDir cd(ppath); + const QStringList &entries = cd.entryList(QDir::Dirs | QDir::Hidden); + foreach (const QString pack, entries) { + const QString _metadata = ppath+QLatin1Char('/')+pack+QStringLiteral("/metadata.desktop"); + if ((pack != "." && pack != "..") && + (QFile::exists(_metadata))) { + themes << _metadata; + } + } + } + + foreach (const QString &theme, themes) { + int themeSepIndex = theme.lastIndexOf('/', -1); + QString themeRoot = theme.left(themeSepIndex); + int themeNameSepIndex = themeRoot.lastIndexOf('/', -1); + QString packageName = themeRoot.right(themeRoot.length() - themeNameSepIndex - 1); + + KDesktopFile df(theme); + + if (df.noDisplay()) { + continue; + } + + QString name = df.readName(); + if (name.isEmpty()) { + name = packageName; + } + const QString comment = df.readComment(); + const QString author = df.desktopGroup().readEntry("X-KDE-PluginInfo-Author",QString()); + const QString version = df.desktopGroup().readEntry("X-KDE-PluginInfo-Version",QString()); + + ThemeInfo info; + info.name = name; + info.package = packageName; + info.description = comment; + info.author = author; + info.version = version; + info.themeRoot = themeRoot; + m_themes << info; + } + + beginResetModel(); + endResetModel(); + emit countChanged(); +} + +int LnfListModel::rowCount(const QModelIndex &) const +{ + return m_themes.size(); +} + +QVariant LnfListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (index.row() >= m_themes.size() || index.row() < 0) { + return QVariant(); + } + + switch (role) { + case Qt::DisplayRole: + return m_themes.value(index.row()).name; + case PackageNameRole: + return m_themes.value(index.row()).package; + case PackageDescriptionRole: + return m_themes.value(index.row()).description; + case PackageAuthorRole: + return m_themes.value(index.row()).author; + case PackageVersionRole: + return m_themes.value(index.row()).version; + default: + return QVariant(); + } +} + +QVariantMap LnfListModel::get(int row) const +{ + QVariantMap item; + + QModelIndex idx = index(row, 0); + + item["display"] = data(idx, Qt::DisplayRole); + item["packageNameRole"] = data(idx, PackageNameRole); + item["packageDescriptionRole"] = data(idx, PackageDescriptionRole); + item["packageAuthorRole"] = data(idx, PackageAuthorRole); + item["packageVersionRole"] = data(idx, PackageVersionRole); + + return item; +} + +QModelIndex LnfListModel::indexOf(const QString &name) const +{ + QListIterator it(m_themes); + int i = -1; + while (it.hasNext()) { + ++i; + if (it.next().package == name) { + return index(i, 0); + } + } + + return QModelIndex(); +} + +#include "moc_lnflistmodel.cpp" diff --git a/lookandfeelexplorer/src/lnflogic.h b/lookandfeelexplorer/src/lnflogic.h new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/src/lnflogic.h @@ -0,0 +1,109 @@ +/* + * Copyright 2016 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 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. + */ + +#ifndef LNFLOGIC_H +#define LNFLOGIC_H + +#include +#include + + +class LnfListModel; +class QDBusPendingCallWatcher; + +class LnfLogic : public QObject +{ + Q_OBJECT + + Q_PROPERTY(LnfListModel *lnfList READ lnfList CONSTANT) + Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged) + Q_PROPERTY(bool isWritable READ isWritable NOTIFY themeChanged) + Q_PROPERTY(QString themeFolder READ themeFolder NOTIFY themeChanged) + Q_PROPERTY(QString name READ name WRITE setName NOTIFY themeChanged) + Q_PROPERTY(QString comment READ comment WRITE setComment NOTIFY themeChanged) + Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY themeChanged) + Q_PROPERTY(QString email READ email WRITE setEmail NOTIFY themeChanged) + Q_PROPERTY(QString version READ version WRITE setVersion NOTIFY themeChanged) + Q_PROPERTY(QString website READ website WRITE setWebsite NOTIFY themeChanged) + Q_PROPERTY(QString license READ license WRITE setLicense NOTIFY themeChanged) + Q_PROPERTY(QString thumbnailPath READ thumbnailPath NOTIFY themeChanged) + +public: + enum ErrorLevel { + Info, + Error + }; + Q_ENUMS(ErrorLevel) + + explicit LnfLogic(QObject *parent = 0); + ~LnfLogic(); + + void writeMetadata(const QString &key, const QString &value); + + LnfListModel *lnfList(); + + void setTheme(const QString &theme); + QString theme() const; + + bool isWritable() const; + QString themeFolder() const; + + QString name() const; + void setName(const QString &name); + + QString comment() const; + void setComment(const QString &comment); + + QString author() const; + void setAuthor(const QString &author); + + QString email() const; + void setEmail(const QString &email); + + QString version() const; + void setVersion(const QString &version); + + QString website() const; + void setWebsite(const QString &website); + + QString license() const; + void setLicense(const QString &license); + + QString thumbnailPath() const; + + void dumpPlasmaLayout(const QString &pluginName); + + Q_INVOKABLE void createNewTheme(const QString &pluginName, const QString &name, const QString &comment, const QString &author, const QString &email, const QString &license, const QString &website); + Q_INVOKABLE void processThumbnail(const QString &path); + Q_INVOKABLE QString openFile(); + + Q_INVOKABLE void dumpCurrentPlasmaLayout(); + Q_INVOKABLE void dumpDefaultsConfigFile(const QString &pluginName); + +Q_SIGNALS: + void themeChanged(); + void messageRequested(ErrorLevel level, const QString &message); + +private: + QString m_themeName; + KPackage::Package m_package; + LnfListModel *m_lnfListModel; +}; + +#endif // LNFLOGIC_H diff --git a/lookandfeelexplorer/src/lnflogic.cpp b/lookandfeelexplorer/src/lnflogic.cpp new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/src/lnflogic.cpp @@ -0,0 +1,338 @@ +/* + * Copyright 2016 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 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 "lnflogic.h" +#include "lnflistmodel.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +LnfLogic::LnfLogic(QObject *parent) + : QObject(parent), + m_themeName(QStringLiteral("org.kde.breeze.desktop")), + m_lnfListModel(new LnfListModel(this)) +{ + m_package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); +} + +LnfLogic::~LnfLogic() +{ +} + +void LnfLogic::createNewTheme(const QString &pluginName, const QString &name, const QString &comment, const QString &author, const QString &email, const QString &license, const QString &website) +{ + const QString metadataPath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QLatin1Literal("/plasma/look-and-feel/") % pluginName % QLatin1Literal("/metadata.desktop")); + KConfig c(metadataPath); + + KConfigGroup cg(&c, "Desktop Entry"); + cg.writeEntry("Name", name); + cg.writeEntry("Comment", comment); + cg.writeEntry("X-KDE-PluginInfo-Name", pluginName); + cg.writeEntry("X-KDE-PluginInfo-Author", author); + cg.writeEntry("X-KDE-PluginInfo-Email", email); + cg.writeEntry("X-KDE-PluginInfo-Website", website); + cg.writeEntry("X-KDE-PluginInfo-Category", "Plasma Look And Feel"); + cg.writeEntry("X-KDE-PluginInfo-License", license); + cg.writeEntry("X-KDE-PluginInfo-EnabledByDefault", "true"); + cg.writeEntry("X-KDE-PluginInfo-Version", "0.1"); + cg.sync(); + + dumpPlasmaLayout(pluginName); + dumpDefaultsConfigFile(pluginName); + + m_lnfListModel->reload(); +} + +void LnfLogic::dumpPlasmaLayout(const QString &pluginName) +{ + //TODO: async and error management + QDBusMessage message = QDBusMessage::createMethodCall("org.kde.plasmashell", "/PlasmaShell", + "org.kde.PlasmaShell", "dumpCurrentLayoutJS"); + QDBusPendingCall pcall = QDBusConnection::sessionBus().asyncCall(message); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); + + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, + this, [=](QDBusPendingCallWatcher *watcher) { + const QDBusMessage &msg = watcher->reply(); + if (watcher->isError()) { + emit messageRequested(ErrorLevel::Error, i18n("Cannot retrieve the current Plasma layout.")); + return; + } + const QString layout = msg.arguments().first().toString(); + //TODO: error checking + QDir themeDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QLatin1Literal("/plasma/look-and-feel/") % pluginName); + if (!themeDir.mkpath("contents/layouts")) { + qWarning() << "Impossible to create the layouts directory in the look and feel package"; + emit messageRequested(ErrorLevel::Error, i18n("Impossible to create the layouts directory in the look and feel package")); + return; + } + + QFile layoutFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QLatin1Literal("/plasma/look-and-feel/") % pluginName % QLatin1Literal("/contents/layouts/org.kde.plasma.desktop-layout.js")); + if (layoutFile.open(QIODevice::WriteOnly)) { + layoutFile.write(layout.toUtf8()); + layoutFile.close(); + } else { + qWarning() << "Impossible to write to org.kde.plasma.desktop-layout.js"; + emit messageRequested(ErrorLevel::Error, i18n("Impossible to write to org.kde.plasma.desktop-layout.js")); + return; + } + emit messageRequested(ErrorLevel::Info, i18n("Plasma Layout successfully duplicated")); + }); +} + +void LnfLogic::dumpDefaultsConfigFile(const QString &pluginName) +{ + //write the defaults file, read from kde config files and save to the defaultsrc + KConfig defaultsConfig(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QLatin1Literal("/plasma/look-and-feel/") % pluginName % "/contents/defaults"); + + KConfigGroup defaultsConfigGroup(&defaultsConfig, "kdeglobals"); + defaultsConfigGroup = KConfigGroup(&defaultsConfigGroup, "KDE"); + + //widget style + KConfigGroup systemCG(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); + defaultsConfigGroup.writeEntry("widgetStyle", systemCG.readEntry("widgetStyle", QStringLiteral("breeze"))); + + //color scheme (TODO: create an in-place color scheme?) + defaultsConfigGroup = KConfigGroup(&defaultsConfig, "kdeglobals"); + defaultsConfigGroup = KConfigGroup(&defaultsConfigGroup, "General"); + systemCG = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "General"); + defaultsConfigGroup.writeEntry("ColorScheme", systemCG.readEntry("ColorScheme", QStringLiteral("Breeze"))); + + //plasma theme + defaultsConfigGroup = KConfigGroup(&defaultsConfig, "plasmarc"); + defaultsConfigGroup = KConfigGroup(&defaultsConfigGroup, "Theme"); + systemCG = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), "Theme"); + defaultsConfigGroup.writeEntry("name", systemCG.readEntry("name", QStringLiteral("default"))); + + //cursor theme + defaultsConfigGroup = KConfigGroup(&defaultsConfig, "kcminputrc"); + defaultsConfigGroup = KConfigGroup(&defaultsConfigGroup, "Mouse"); + systemCG = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("kcminputrc")), "Mouse"); + defaultsConfigGroup.writeEntry("cursorTheme", systemCG.readEntry("cursorTheme", QStringLiteral("breeze_cursors"))); + + //KWin window switcher theme + defaultsConfigGroup = KConfigGroup(&defaultsConfig, "kwinrc"); + defaultsConfigGroup = KConfigGroup(&defaultsConfigGroup, "WindowSwitcher"); + systemCG = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("kwinrc")), "WindowSwitcher"); + defaultsConfigGroup.writeEntry("LayoutName", systemCG.readEntry("LayoutName", QStringLiteral("org.kde.breeze.desktop"))); + + //KWin desktop switcher theme + defaultsConfigGroup = KConfigGroup(&defaultsConfig, "kwinrc"); + defaultsConfigGroup = KConfigGroup(&defaultsConfigGroup, "DesktopSwitcher"); + systemCG = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("kwinrc")), "DesktopSwitcher"); + defaultsConfigGroup.writeEntry("LayoutName", systemCG.readEntry("LayoutName", QStringLiteral("org.kde.breeze.desktop"))); + + emit messageRequested(ErrorLevel::Info, i18n("Defaults config file saved from your current setup")); +} + +void LnfLogic::dumpCurrentPlasmaLayout() +{ + dumpPlasmaLayout(m_themeName); +} + +void LnfLogic::writeMetadata(const QString &key, const QString &value) +{ + KConfig c(m_package.filePath("metadata")); + KConfigGroup cg(&c, "Desktop Entry"); + + cg.writeEntry(key, value); + + //HACK + m_package.setPath(QString()); + m_package.setPath(m_themeName); +} + +LnfListModel *LnfLogic::lnfList() +{ + return m_lnfListModel; +} + +QString LnfLogic::themeFolder() const +{ + return m_package.path(); +} + +bool LnfLogic::isWritable() const +{ + return QFile::exists(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/plasma/look-and-feel/" + m_themeName); +} + +QString LnfLogic::theme() const +{ + return m_themeName; +} + +void LnfLogic::setTheme(const QString& theme) +{ + if (theme == m_themeName) { + return; + } + + m_themeName = theme; + m_package.setPath(theme); + emit themeChanged(); +} + +QString LnfLogic::name() const +{ + return m_package.metadata().name(); +} + +void LnfLogic::setName(const QString &name) +{ + writeMetadata("Name", name); + emit themeChanged(); +} + +QString LnfLogic::comment() const +{ + return m_package.metadata().description(); +} + +void LnfLogic::setComment(const QString &comment) +{ + writeMetadata("Comment", comment); + emit themeChanged(); +} + +QString LnfLogic::author() const +{ + if (m_package.metadata().authors().isEmpty()) { + return QString(); + } + return m_package.metadata().authors().first().name(); +} + +void LnfLogic::setAuthor(const QString &author) +{ + writeMetadata("X-KDE-PluginInfo-Author", author); + emit themeChanged(); +} + +QString LnfLogic::email() const +{ + if (m_package.metadata().authors().isEmpty()) { + return QString(); + } + return m_package.metadata().authors().first().emailAddress(); +} + +void LnfLogic::setEmail(const QString &email) +{ + writeMetadata("X-KDE-PluginInfo-Email", email); + emit themeChanged(); +} + +QString LnfLogic::version() const +{ + return m_package.metadata().version(); +} + +void LnfLogic::setVersion(const QString &version) +{ + writeMetadata("X-KDE-PluginInfo-Version", version); + emit themeChanged(); +} + +QString LnfLogic::website() const +{ + return m_package.metadata().website(); +} + +void LnfLogic::setWebsite(const QString &website) +{ + writeMetadata("X-KDE-PluginInfo-Website", website); + emit themeChanged(); +} + +QString LnfLogic::license() const +{ + return m_package.metadata().license(); +} + +void LnfLogic::setLicense(const QString &license) +{ + writeMetadata("X-KDE-PluginInfo-License", license); + emit themeChanged(); +} + +QString LnfLogic::thumbnailPath() const +{ + //don't fallback + QString path = m_package.filePath("previews", QStringLiteral("preview.png")); + if (path.contains(m_package.path())) { + return path; + } + + return QString(); +} + +void LnfLogic::processThumbnail(const QString &path) +{ + if (path.isEmpty()) { + return; + } + + QDir themeDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QLatin1Literal("/plasma/look-and-feel/") % m_themeName); + if (!themeDir.mkpath("contents/previews")) { + qWarning() << "Impossible to create the layouts directory in the look and feel package"; + } + + QFile imageFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) % QLatin1Literal("/plasma/look-and-feel/") % m_themeName % QLatin1Literal("/contents/previews/preview.png")); + if (!imageFile.open(QIODevice::WriteOnly)) { + qWarning() << "Impossible to write to the thumbnail file"; + return; + } + + QImage image(QUrl(path).path()); + if (image.isNull()) { + qWarning() << "invalid image"; + return; + } + image = image.scaledToWidth(512, Qt::SmoothTransformation); + + image.save(&imageFile, "PNG"); // writes image into ba in PNG format + imageFile.close(); + emit themeChanged(); +} + +QString LnfLogic::openFile() +{ + return QFileDialog::getOpenFileName(0, + i18n("Open Image"), QStandardPaths::writableLocation(QStandardPaths::HomeLocation), i18n("Image Files (*.png *.jpg *.bmp)")); +} + +#include "moc_lnflogic.cpp" diff --git a/lookandfeelexplorer/src/main.cpp b/lookandfeelexplorer/src/main.cpp new file mode 100644 --- /dev/null +++ b/lookandfeelexplorer/src/main.cpp @@ -0,0 +1,100 @@ +/* + * Copyright 2015 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 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 +#include "lnflogic.h" +#include "lnflistmodel.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + QCommandLineParser parser; + QApplication app(argc, argv); + + const QString description = i18n("Plasma Look And Feel Explorer"); + const char version[] = PROJECT_VERSION; + + app.setApplicationVersion(version); + parser.addVersionOption(); + parser.addHelpOption(); + parser.setApplicationDescription(description); + + QCommandLineOption themeOption(QCommandLineOption(QStringList() << "l" << "lookandfeel", i18n("Look And Feel to open"), "lookandfeel")); + + parser.addOption(themeOption); + + parser.process(app); + + const QString packagePath("org.kde.plasma.lookandfeelexplorer"); + + //usually we have an ApplicationWindow here, so we do not need to create a window by ourselves + KDeclarative::QmlObject *obj = new KDeclarative::QmlObject(); + obj->setTranslationDomain(packagePath); + obj->setInitializationDelayed(true); + obj->loadPackage(packagePath); + obj->engine()->rootContext()->setContextProperty("commandlineArguments", parser.positionalArguments()); + + qmlRegisterType(); + qmlRegisterType(); + + LnfLogic *lnfLogic = new LnfLogic; + obj->engine()->rootContext()->setContextProperty("lnfLogic", QVariant::fromValue(lnfLogic)); + + obj->completeInitialization(); + + if (!obj->package().metadata().isValid()) { + return -1; + } + + KPluginMetaData data = obj->package().metadata(); + // About data + KAboutData aboutData(data.pluginId(), data.name(), data.version(), data.description(), KAboutLicense::byKeyword(data.license()).key()); + + for (auto author : data.authors()) { + aboutData.addAuthor(author.name(), author.task(), author.emailAddress(), author.webAddress(), author.ocsUsername()); + } + + //The root is not a window? + //have to use a normal QQuickWindow since the root item is already created + QWindow *window = qobject_cast(obj->rootObject()); + if (window) { + window->setTitle(obj->package().metadata().name()); + window->setIcon(QIcon::fromTheme(obj->package().metadata().iconName())); + } else { + qWarning() << "Error loading the ApplicationWindow"; + } + + return app.exec(); +} +