diff --git a/.gitignore b/.gitignore index 09459de..44fb05c 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,6 @@ Makefile* testfiles/output.* # experimental -plasmoid/ scripts/ # clang-format @@ -54,5 +53,5 @@ scripts/ /model/.clang-format /testhelper/.clang-format /tray/.clang-format -/plasmoid/.clang-format +/plasmoid/lib/.clang-format /widgets/.clang-format diff --git a/CMakeLists.txt b/CMakeLists.txt index 329f95b..a01b7da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,8 +17,11 @@ project(${META_PROJECT_NAME}) # disable Dolphin integration under Android, Windows and MacOS by default if(ANDROID OR WIN32 OR APPLE) set(FILE_ITEM_ACTION_PLUGIN_DISABLED_BY_DEFAULT ON) + set(PLASMOID_DISABLED_BY_DEFAULT ON) else() set(FILE_ITEM_ACTION_PLUGIN_DISABLED_BY_DEFAULT OFF) + # plasmoid is still incomplete, so disable it by default + set(PLASMOID_DISABLED_BY_DEFAULT ON) endif() # options for partial build @@ -27,6 +30,7 @@ option(NO_TRAY "whether building the tray should be skipped" OFF) option(NO_FILE_ITEM_ACTION_PLUGIN "whether building the file item action plugin should be skipped" "${FILE_ITEM_ACTION_PLUGIN_DISABLED_BY_DEFAULT}") option(NO_MODEL "whether building models should be skipped, implies NO_TRAY" OFF) option(NO_WIDGETS "whether building widgets should be skipped, implies NO_TRAY" OFF) +option(NO_PLASMOID "whether building the Plasma 5 plasmoid should be skipped" "${PLASMOID_DISABLED_BY_DEFAULT}") # add subdirectories enable_testing() @@ -45,6 +49,9 @@ if(NOT NO_MODEL) if(NOT NO_TRAY) add_subdirectory(tray) endif() + if(NOT NO_PLASMOID) + add_subdirectory(plasmoid) + endif() endif() endif() if(NOT NO_FILE_ITEM_ACTION_PLUGIN) diff --git a/README.md b/README.md index 7a06b6f..99f5a56 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ Qt 5-based tray application for [Syncthing](https://github.com/syncthing/syncthi support * No desktop environment specific libraries required * Tested under \* - * Plasma 5 - * Openbox/qt5ct/Tint2 - * Awesome/qt5ct - * Cinnamon - * Windows 10 + * Plasma 5 + * Openbox/qt5ct/Tint2 + * Awesome/qt5ct + * Cinnamon + * Windows 10 * Can be shown as regular window if tray icon support is not available \* If you can confirm it works under other desktop environments, please add it @@ -18,16 +18,16 @@ to the list. Maybe someone could check whether it works under Mac OS X. ## Features * Provides quick access to most frequently used features but does not intend to replace the official web UI - * Check state of directories and devices - * Check current traffic statistics - * Display further details about directories and devices, like last file, last - scan, items out of sync, ... - * Display ongoing downloads - * Display Syncthing log - * Trigger re-scan of a specific directory or all directories at once - * Open a directory with the default file browser - * Pause/resume a specific device or all devices at once - * Pause/resume a specific directory + * Check state of directories and devices + * Check current traffic statistics + * Display further details about directories and devices, like last file, last + scan, items out of sync, ... + * Display ongoing downloads + * Display Syncthing log + * Trigger re-scan of a specific directory or all directories at once + * Open a directory with the default file browser + * Pause/resume a specific device or all devices at once + * Pause/resume a specific directory * Shows Syncthing notifications * Can read the local Syncthing configuration file for quick setup when just connecting to local instance * Can show the status of the Syncthing systemd unit and allows to start and stop it (see section *Use of systemd*) @@ -35,25 +35,26 @@ to the list. Maybe someone could check whether it works under Mac OS X. * Can launch Syncthing and syncthing-inotify automatically when started and display stdout/stderr (useful under Windows) * Provides quick access to the official web UI - * Utilizes either Qt WebKit or Qt WebEngine - * Can be built without web view support as well (then the web UI is opened in the regular browser) + * Utilizes either Qt WebKit or Qt WebEngine + * Can be built without web view support as well (then the web UI is opened in the regular browser) * Allows quickly switching between multiple Syncthing instances * Shows notifications via Qt or uses D-Bus notification daemon directly * Also features a simple command line utility `syncthingctl` to check Syncthing status and trigger rescan/pause/resume/restart * Also bundles a KIO plugin which shows the status of a Syncthing directory and allows to trigger Syncthing actions in Dolphin file manager - * Rescan selected items - * Rescan entire Syncthing directory - * Pause/resume Syncthing directory - * See also screenshots section + * Rescan selected items + * Rescan entire Syncthing directory + * Pause/resume Syncthing directory + * See also screenshots section * English and German localization ## Planned features The tray is still under development; the following features are planned: + * Show recently processed items -* Improve notification handling * Create Plasmoid for Plasma 5 desktop + * An incomplete/experimental version is already available * Provide built-in support for file system watches ## Screenshots @@ -135,7 +136,6 @@ to the CMake arguments. Then only core and network are required. #### Building this straight 0. Install (preferably the latest version of) g++ or clang, the required Qt 5 modules and CMake. 1. Get the sources. For the lastest version from Git clone the following repositories: - ``` cd $SOURCES git clone https://github.com/Martchus/cpp-utilities.git c++utilities @@ -144,7 +144,6 @@ to the CMake arguments. Then only core and network are required. git clone https://github.com/Martchus/subdirs.git ``` 2. Build and install everything in one step: - ``` cd $BUILD_DIR cmake \ diff --git a/plasmoid/CMakeLists.txt b/plasmoid/CMakeLists.txt new file mode 100644 index 0000000..6756b55 --- /dev/null +++ b/plasmoid/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 2.8.12) + +# meta data +set(META_PROJECT_NAME syncthingplasmoid) +set(META_APP_NAME "Syncthing Plasmoid") +set(META_APP_AUTHOR "Martchus") +set(META_APP_DESCRIPTION "Plasmoid to interact with Syncthing") +set(META_PROJECT_TYPE qtplugin) +set(META_ID "martchus.syncthing-plasmoid") + +# find ECM (required by KF5Plasma) +find_package(ECM REQUIRED NO_MODULE) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_MODULE_PATH}) +# find KF5Plasma +find_package(KF5Plasma REQUIRED) + +# add subdirs +add_subdirectory(lib) + +plasma_install_package(package ${META_ID}) + +set(PLASMOID_TESTDIR "${CMAKE_CURRENT_BINARY_DIR}/testdir" CACHE STRING "specifies the Plasmoid test directory") +file(MAKE_DIRECTORY "${PLASMOID_TESTDIR}") +add_custom_target(init_plasmoid_testing + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/scripts/inittesting.sh" + WORKING_DIRECTORY "${PLASMOID_TESTDIR}" +) diff --git a/plasmoid/lib/CMakeLists.txt b/plasmoid/lib/CMakeLists.txt new file mode 100644 index 0000000..9215dc2 --- /dev/null +++ b/plasmoid/lib/CMakeLists.txt @@ -0,0 +1,51 @@ +# source files +set(HEADER_FILES + syncthingapplet.h +) +set(SRC_FILES + syncthingapplet.cpp +) +set(PLASMOID_FILES + ../package/metadata.desktop + ../package/contents/ui/CompactRepresentation.qml + ../package/contents/ui/FullRepresentation.qml + ../package/contents/ui/DirectoriesPage.qml + ../package/contents/ui/DevicesPage.qml + ../package/contents/ui/DownloadsPage.qml + ../package/contents/ui/TopLevelView.qml + ../package/contents/ui/TopLevelItem.qml + ../package/contents/ui/DetailView.qml + ../package/contents/ui/DetailItem.qml + ../package/contents/ui/main.qml +) +list(APPEND QML_FILES ${PLASMOID_FILES}) + +# find c++utilities +find_package(c++utilities 4.6.0 REQUIRED) +list(APPEND CMAKE_MODULE_PATH ${CPP_UTILITIES_MODULE_DIRS}) + +# find qtutilities +find_package(qtutilities 5.0.0 REQUIRED) +use_qt_utilities() + +# find backend libraries +find_package(syncthingconnector ${META_APP_VERSION} REQUIRED) +use_syncthingconnector() +find_package(syncthingmodel ${META_APP_VERSION} REQUIRED) +use_syncthingmodel() +find_package(syncthingwidgets ${META_APP_VERSION} REQUIRED) +use_syncthingwidgets() + +# link also explicitely against the following Qt 5 modules +list(APPEND ADDITIONAL_QT_MODULES Network Qml) +list(APPEND ADDITIONAL_KF_MODULES Plasma) + +include(BasicConfig) +include(QtGuiConfig) +include(QtConfig) +include(WindowsResources) +include(LibraryTarget) +include(ConfigHeader) + +# what ever this does, it is done +kcoreaddons_desktop_to_json("${META_PROJECT_NAME}" ../package/metadata.desktop) diff --git a/plasmoid/lib/global.h b/plasmoid/lib/global.h new file mode 100644 index 0000000..c41817a --- /dev/null +++ b/plasmoid/lib/global.h @@ -0,0 +1,27 @@ +// Created via CMake from template global.h.in +// WARNING! Any changes to this file will be overwritten by the next CMake run! + +#ifndef SYNCTHINGPLASMOID_GLOBAL +#define SYNCTHINGPLASMOID_GLOBAL + +#include + +#ifdef SYNCTHINGPLASMOID_STATIC +#define SYNCTHINGPLASMOID_EXPORT +#define SYNCTHINGPLASMOID_IMPORT +#else +#define SYNCTHINGPLASMOID_EXPORT LIB_EXPORT +#define SYNCTHINGPLASMOID_IMPORT LIB_IMPORT +#endif + +/*! + * \def SYNCTHINGPLASMOID_EXPORT + * \brief Marks the symbol to be exported by the syncthingplasmoid library. + */ + +/*! + * \def SYNCTHINGPLASMOID_IMPORT + * \brief Marks the symbol to be imported from the syncthingplasmoid library. + */ + +#endif // SYNCTHINGPLASMOID_GLOBAL diff --git a/plasmoid/lib/syncthingapplet.cpp b/plasmoid/lib/syncthingapplet.cpp new file mode 100644 index 0000000..531b2da --- /dev/null +++ b/plasmoid/lib/syncthingapplet.cpp @@ -0,0 +1,194 @@ +#include "./syncthingapplet.h" + +#include "../../widgets/misc/otherdialogs.h" +#include "../../widgets/misc/textviewdialog.h" +#include "../../widgets/settings/settings.h" +#include "../../widgets/settings/settingsdialog.h" +#include "../../widgets/webview/webviewdialog.h" + +#include "../../model/syncthingicons.h" + +#include "../../connector/utils.h" + +#include "resources/config.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +using namespace std; +using namespace Data; +using namespace Plasma; +using namespace Dialogs; +using namespace QtGui; + +SyncthingApplet::SyncthingApplet(QObject *parent, const QVariantList &data) + : Applet(parent, data) + , m_aboutDlg(nullptr) + , m_connection() + , m_dirModel(m_connection) + , m_devModel(m_connection) + , m_downloadModel(m_connection) + , m_connectionSettingsDlg(nullptr) +#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW + , m_webViewDlg(nullptr) +#endif +{ + qRegisterMetaType("Data::SyncthingStatus"); + qmlRegisterUncreatableMetaObject(Data::staticMetaObject, "martchus.syncthingplasmoid", 0, 6, "Data", QStringLiteral("only enums")); +} + +SyncthingApplet::~SyncthingApplet() +{ + delete m_connectionSettingsDlg; +#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW + delete m_webViewDlg; +#endif +} + +QIcon SyncthingApplet::statusIcon() const +{ + return m_statusInfo.statusIcon(); +} + +QString SyncthingApplet::incomingTraffic() const +{ + return trafficString(m_connection.totalIncomingTraffic(), m_connection.totalIncomingRate()); +} + +QString SyncthingApplet::outgoingTraffic() const +{ + return trafficString(m_connection.totalOutgoingTraffic(), m_connection.totalOutgoingRate()); +} + +QString SyncthingApplet::statusText() const +{ + return m_statusInfo.statusText(); +} + +QString SyncthingApplet::additionalStatusText() const +{ + return m_statusInfo.additionalStatusText(); +} + +void SyncthingApplet::init() +{ + LOAD_QT_TRANSLATIONS; + + Applet::init(); + + connect(&m_connection, &SyncthingConnection::statusChanged, this, &SyncthingApplet::handleConnectionStatusChanged); + connect(&m_connection, &SyncthingConnection::trafficChanged, this, &SyncthingApplet::trafficChanged); + + // restore settings + Settings::restore(); + applyConnectionSettings(); +} + +void SyncthingApplet::showConnectionSettingsDlg() +{ + if (!m_connectionSettingsDlg) { + m_connectionSettingsDlg = new Dialogs::SettingsDialog; + m_connectionSettingsDlg->setTabBarAlwaysVisible(false); + auto *const webViewPage = new QtGui::WebViewOptionPage; + auto *const webViewWidget = webViewPage->widget(); + webViewWidget->setWindowTitle(tr("Web view")); + webViewWidget->setWindowIcon(QIcon::fromTheme(QStringLiteral("internet-web-browser"))); + auto *const category = new Dialogs::OptionCategory(m_connectionSettingsDlg); + category->assignPages({ new QtGui::ConnectionOptionPage(&m_connection), webViewPage }); + m_connectionSettingsDlg->setSingleCategory(category); + m_connectionSettingsDlg->resize(860, 620); + connect(m_connectionSettingsDlg, &Dialogs::SettingsDialog::applied, this, &SyncthingApplet::applyConnectionSettings); + connect(m_connectionSettingsDlg, &Dialogs::SettingsDialog::applied, &Settings::save); + } + Dialogs::centerWidget(m_connectionSettingsDlg); + m_connectionSettingsDlg->show(); + m_connectionSettingsDlg->activateWindow(); +} + +void SyncthingApplet::showWebUI() +{ +#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW + if (Settings::values().webView.disabled) { +#endif + QDesktopServices::openUrl(m_connection.syncthingUrl()); +#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW + } else { + if (!m_webViewDlg) { + m_webViewDlg = new WebViewDialog; + //if(m_selectedConnection) { + m_webViewDlg->applySettings(Settings::values().connection.primary); + //} + connect(m_webViewDlg, &WebViewDialog::destroyed, this, &SyncthingApplet::handleWebViewDeleted); + } + m_webViewDlg->show(); + m_webViewDlg->activateWindow(); + } +#endif +} + +void SyncthingApplet::showLog() +{ + auto *const dlg = TextViewDialog::forLogEntries(m_connection); + dlg->setAttribute(Qt::WA_DeleteOnClose, true); + centerWidget(dlg); + dlg->show(); +} + +void SyncthingApplet::showOwnDeviceId() +{ + auto *const dlg = ownDeviceIdDialog(m_connection); + dlg->setAttribute(Qt::WA_DeleteOnClose, true); + centerWidget(dlg); + dlg->show(); +} + +void SyncthingApplet::showAboutDialog() +{ + if (!m_aboutDlg) { + m_aboutDlg = new AboutDialog(nullptr, QString(), QStringLiteral(APP_AUTHOR "\nSyncthing icons from Syncthing project"), QString(), QString(), + QStringLiteral(APP_DESCRIPTION), QImage(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg"))); + m_aboutDlg->setWindowTitle(tr("About") + QStringLiteral(" - " APP_NAME)); + m_aboutDlg->setWindowIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg"))); + m_aboutDlg->setAttribute(Qt::WA_DeleteOnClose); + connect(m_aboutDlg, &QObject::destroyed, this, &SyncthingApplet::handleAboutDialogDeleted); + } + centerWidget(m_aboutDlg); + m_aboutDlg->show(); + m_aboutDlg->activateWindow(); +} + +void SyncthingApplet::applyConnectionSettings() +{ + m_connection.connect(Settings::values().connection.primary); + if (m_webViewDlg) { + m_webViewDlg->applySettings(Settings::values().connection.primary); + } +} + +void SyncthingApplet::handleConnectionStatusChanged() +{ + m_statusInfo.update(m_connection); + emit connectionStatusChanged(); +} + +void SyncthingApplet::handleAboutDialogDeleted() +{ + m_aboutDlg = nullptr; +} + +void SyncthingApplet::handleWebViewDeleted() +{ + m_webViewDlg = nullptr; +} + +K_EXPORT_PLASMA_APPLET_WITH_JSON(syncthing, SyncthingApplet, "metadata.json") + +#include "syncthingapplet.moc" diff --git a/plasmoid/lib/syncthingapplet.h b/plasmoid/lib/syncthingapplet.h new file mode 100644 index 0000000..2fbe877 --- /dev/null +++ b/plasmoid/lib/syncthingapplet.h @@ -0,0 +1,119 @@ +#ifndef SYNCTHINGAPPLET_H +#define SYNCTHINGAPPLET_H + +#include "../../widgets/misc/statusinfo.h" + +#include "../../model/syncthingdevicemodel.h" +#include "../../model/syncthingdirectorymodel.h" +#include "../../model/syncthingdownloadmodel.h" + +#include "../../connector/syncthingconnection.h" + +#include + +#include + +namespace Dialogs { +class SettingsDialog; +} + +namespace Data { +class SyncthingConnection; +class SyncthingDirectoryModel; +class SyncthingDeviceModel; +class SyncthingDownloadModel; +} + +namespace QtGui { +class WebViewDialog; +} + +class SyncthingApplet : public Plasma::Applet { + Q_OBJECT + Q_PROPERTY(Data::SyncthingConnection *connection READ connection NOTIFY connectionChanged) + Q_PROPERTY(Data::SyncthingDirectoryModel *dirModel READ dirModel NOTIFY dirModelChanged) + Q_PROPERTY(Data::SyncthingDeviceModel *devModel READ devModel NOTIFY devModelChanged) + Q_PROPERTY(Data::SyncthingDownloadModel *downloadModel READ downloadModel NOTIFY downloadModelChanged) + Q_PROPERTY(QString statusText READ statusText NOTIFY connectionStatusChanged) + Q_PROPERTY(QString additionalStatusText READ additionalStatusText NOTIFY connectionStatusChanged) + Q_PROPERTY(QIcon statusIcon READ statusIcon NOTIFY connectionStatusChanged) + Q_PROPERTY(QString incomingTraffic READ incomingTraffic NOTIFY trafficChanged) + Q_PROPERTY(QString outgoingTraffic READ outgoingTraffic NOTIFY trafficChanged) + +public: + SyncthingApplet(QObject *parent, const QVariantList &data); + ~SyncthingApplet() override; + +public: + Data::SyncthingConnection *connection() const; + Data::SyncthingDirectoryModel *dirModel() const; + Data::SyncthingDeviceModel *devModel() const; + Data::SyncthingDownloadModel *downloadModel() const; + QString statusText() const; + QString additionalStatusText() const; + QIcon statusIcon() const; + QString incomingTraffic() const; + QString outgoingTraffic() const; + +public Q_SLOTS: + void init() Q_DECL_OVERRIDE; + void showConnectionSettingsDlg(); + void showWebUI(); + void showLog(); + void showOwnDeviceId(); + void showAboutDialog(); + +Q_SIGNALS: + /// \remarks Never emitted, just to silence "... depends on non-NOTIFYable ..." + void connectionChanged(); + /// \remarks Never emitted, just to silence "... depends on non-NOTIFYable ..." + void dirModelChanged(); + /// \remarks Never emitted, just to silence "... depends on non-NOTIFYable ..." + void devModelChanged(); + /// \remarks Never emitted, just to silence "... depends on non-NOTIFYable ..." + void downloadModelChanged(); + void connectionStatusChanged(); + void trafficChanged(); + +private Q_SLOTS: + void applyConnectionSettings(); + void handleConnectionStatusChanged(); + void handleAboutDialogDeleted(); +#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW + void handleWebViewDeleted(); +#endif + +private: + Dialogs::AboutDialog *m_aboutDlg; + Data::SyncthingConnection m_connection; + QtGui::StatusInfo m_statusInfo; + Data::SyncthingDirectoryModel m_dirModel; + Data::SyncthingDeviceModel m_devModel; + Data::SyncthingDownloadModel m_downloadModel; + Dialogs::SettingsDialog *m_connectionSettingsDlg; +#ifndef SYNCTHINGWIDGETS_NO_WEBVIEW + QtGui::WebViewDialog *m_webViewDlg; +#endif +}; + +inline Data::SyncthingConnection *SyncthingApplet::connection() const +{ + return const_cast(&m_connection); +} + +inline Data::SyncthingDirectoryModel *SyncthingApplet::dirModel() const +{ + return const_cast(&m_dirModel); +} + +inline Data::SyncthingDeviceModel *SyncthingApplet::devModel() const +{ + return const_cast(&m_devModel); +} + +inline Data::SyncthingDownloadModel *SyncthingApplet::downloadModel() const +{ + return const_cast(&m_downloadModel); +} + +#endif // SYNCTHINGAPPLET_H diff --git a/plasmoid/package/contents/ui/CompactRepresentation.qml b/plasmoid/package/contents/ui/CompactRepresentation.qml new file mode 100644 index 0000000..bba9d99 --- /dev/null +++ b/plasmoid/package/contents/ui/CompactRepresentation.qml @@ -0,0 +1,18 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import martchus.syncthingplasmoid 0.6 as SyncthingPlasmoid + +MouseArea { + Layout.fillWidth: false + hoverEnabled: true + onClicked: plasmoid.expanded = !plasmoid.expanded + + PlasmaCore.IconItem { + id: icon + anchors.fill: parent + source: plasmoid.nativeInterface.statusIcon + active: parent.containsMouse + } +} diff --git a/plasmoid/package/contents/ui/DetailItem.qml b/plasmoid/package/contents/ui/DetailItem.qml new file mode 100644 index 0000000..2289e04 --- /dev/null +++ b/plasmoid/package/contents/ui/DetailItem.qml @@ -0,0 +1,27 @@ +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.1 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras + +RowLayout { + Item { + width: 21 + } + PlasmaComponents.Label { + Layout.preferredWidth: 100 + text: name + font.pointSize: 8 + height: contentHeight + elide: Text.ElideRight + } + PlasmaComponents.Label { + Layout.fillWidth: true + text: detail + font.pointSize: 8 + height: contentHeight + elide: Text.ElideRight + } +} diff --git a/plasmoid/package/contents/ui/DetailView.qml b/plasmoid/package/contents/ui/DetailView.qml new file mode 100644 index 0000000..fedb235 --- /dev/null +++ b/plasmoid/package/contents/ui/DetailView.qml @@ -0,0 +1,13 @@ +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.1 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras + +ListView { + currentIndex: -1 + interactive: false + height: contentHeight +} diff --git a/plasmoid/package/contents/ui/DevicesPage.qml b/plasmoid/package/contents/ui/DevicesPage.qml new file mode 100644 index 0000000..a6ee73c --- /dev/null +++ b/plasmoid/package/contents/ui/DevicesPage.qml @@ -0,0 +1,86 @@ +import QtQuick 2.3 +import QtQuick.Layouts 1.1 +import QtQml.Models 2.2 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras + +Item { + property alias view: deviceView + anchors.fill: parent + objectName: "DevicesPage" + + PlasmaExtras.ScrollArea { + anchors.fill: parent + + TopLevelView { + id: deviceView + model: plasmoid.nativeInterface.devModel + + delegate: TopLevelItem { + id: item + + ColumnLayout { + width: parent.width + spacing: 0 + + RowLayout { + RowLayout { + spacing: 5 + PlasmaCore.IconItem { + Layout.preferredWidth: 16 + Layout.preferredHeight: 16 + anchors.verticalCenter: parent.verticalCenter + source: statusIcon + } + PlasmaComponents.Label { + anchors.verticalCenter: parent.verticalCenter + elide: Text.ElideRight + text: name + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + RowLayout { + id: toolButtonsLayout + spacing: 0 + + PlasmaComponents.Label { + height: implicitHeight + text: statusString + color: statusColor + anchors.verticalCenter: parent.verticalCenter + } + Item { + width: 3 + } + PlasmaComponents.ToolButton { + id: barcodeToolButton + iconSource: paused ? "media-playback-start" : "media-playback-pause"; + tooltip: paused ? qsTr("Resume") : qsTr("Pause") + onClicked: paused + ? plasmoid.nativeInterface.connection.resumeDevice([devId]) + : plasmoid.nativeInterface.connection.pauseDevice([devId]) + } + } + } + + DetailView { + id: detailsView + visible: item.expanded + + model: DelegateModel { + model: plasmoid.nativeInterface.devModel + rootIndex: detailsView.model.modelIndex(index) + delegate: DetailItem { } + } + } + + } + } + } + } +} diff --git a/plasmoid/package/contents/ui/DirectoriesPage.qml b/plasmoid/package/contents/ui/DirectoriesPage.qml new file mode 100644 index 0000000..1eb34a7 --- /dev/null +++ b/plasmoid/package/contents/ui/DirectoriesPage.qml @@ -0,0 +1,100 @@ +import QtQuick 2.3 +import QtQuick.Layouts 1.1 +import QtQml.Models 2.2 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras + +Item { + property alias view: directoryView + anchors.fill: parent + objectName: "DirectoriesPage" + + PlasmaExtras.ScrollArea { + anchors.fill: parent + + TopLevelView { + id: directoryView + model: plasmoid.nativeInterface.dirModel + + delegate: TopLevelItem { + id: item + + ColumnLayout { + width: parent.width + spacing: 0 + + RowLayout { + id: itemSummary + + RowLayout { + spacing: 5 + PlasmaCore.IconItem { + Layout.preferredWidth: 16 + Layout.preferredHeight: 16 + anchors.verticalCenter: parent.verticalCenter + source: statusIcon + } + PlasmaComponents.Label { + anchors.verticalCenter: parent.verticalCenter + elide: Text.ElideRight + text: name + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + RowLayout { + id: toolButtonsLayout + spacing: 0 + + PlasmaComponents.Label { + height: implicitHeight + text: statusString + color: statusColor + anchors.verticalCenter: parent.verticalCenter + } + Item { + width: 3 + } + PlasmaComponents.ToolButton { + iconSource: "view-refresh" + tooltip: qsTr("Rescan") + onClicked: plasmoid.nativeInterface.connection.rescan(dirId) + } + PlasmaComponents.ToolButton { + id: barcodeToolButton + iconSource: paused ? "media-playback-start" : "media-playback-pause"; + tooltip: paused ? qsTr("Resume") : qsTr("Pause") + onClicked: paused + ? plasmoid.nativeInterface.connection.resumeDirectories([dirId]) + : plasmoid.nativeInterface.connection.pauseDirectories([dirId]) + } + PlasmaComponents.ToolButton { + iconSource: "folder" + tooltip: qsTr("Open in file browser") + onClicked: { + Qt.openUrlExternally(path) + plasmoid.expanded = false + } + } + } + } + + DetailView { + id: detailsView + visible: item.expanded + + model: DelegateModel { + model: plasmoid.nativeInterface.dirModel + rootIndex: detailsView.model.modelIndex(index) + delegate: DetailItem { } + } + } + } + } + } + } +} diff --git a/plasmoid/package/contents/ui/DownloadsPage.qml b/plasmoid/package/contents/ui/DownloadsPage.qml new file mode 100644 index 0000000..e51ea4b --- /dev/null +++ b/plasmoid/package/contents/ui/DownloadsPage.qml @@ -0,0 +1,39 @@ +import QtQuick 2.3 +import QtQuick.Layouts 1.1 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras + +Item { + property alias view: downloadView + anchors.fill: parent + objectName: "DownloadsPage" + + PlasmaExtras.ScrollArea { + anchors.fill: parent + + PlasmaComponents.Label { + text: "TODO: download delegate/model" + } + + TopLevelView { + id: downloadView + model: plasmoid.nativeInterface.downloadModel + + delegate: TopLevelItem { + RowLayout { + PlasmaCore.IconItem { + id: listIcon + source: fileIcon + } + PlasmaComponents.Label { + height: implicitHeight + elide: Text.ElideRight + text: name + } + } + } + } + } +} diff --git a/plasmoid/package/contents/ui/FullRepresentation.qml b/plasmoid/package/contents/ui/FullRepresentation.qml new file mode 100644 index 0000000..7334390 --- /dev/null +++ b/plasmoid/package/contents/ui/FullRepresentation.qml @@ -0,0 +1,287 @@ +import QtQuick 2.3 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.1 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras +import martchus.syncthingplasmoid 0.6 as SyncthingPlasmoid + +ColumnLayout { + id: root + Layout.minimumWidth: units.gridUnit * 26 + Layout.minimumHeight: units.gridUnit * 34 + + Keys.onPressed: { + // FIXME: currently only works after clicking the tab buttons + // TODO: add more shortcuts + switch(event.key) { + case Qt.Key_Up: + mainTabGroup.currentTab.item.view.decrementCurrentIndex(); + event.accepted = true; + break; + case Qt.Key_Down: + mainTabGroup.currentTab.item.view.incrementCurrentIndex(); + event.accepted = true; + break; + case Qt.Key_Enter: + case Qt.Key_Return: + var currentItem = mainTabGroup.currentTab.item.view.currentItem; + if (currentItem) { + currentItem.expanded = !currentItem.expanded + } + break; + case Qt.Key_Escape: + break; + default: + break; + } + } + + // heading and right-corner buttons + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: false + + PlasmaExtras.Heading { + id: headingLabel + level: 1 + text: qsTr("Syncthing") + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + RowLayout { + id: toolBar + anchors.verticalCenter: parent.verticalCenter + PlasmaComponents.ToolButton { + id: connectButton + states: [ + State { + name: "disconnected" + PropertyChanges { + target: connectButton + text: qsTr("Connect") + iconSource: "view-refresh" + } + }, + State { + name: "paused" + PropertyChanges { + target: connectButton + text: qsTr("Resume") + iconSource: "media-playback-start" + } + }, + State { + name: "idle" + PropertyChanges { + target: connectButton + text: qsTr("Pause") + iconSource: "media-playback-pause" + } + } + ] + state: { + switch(plasmoid.nativeInterface.connection.status) { + case SyncthingPlasmoid.Data.Disconnected: + case SyncthingPlasmoid.Data.Reconnecting: + return "disconnected"; + case SyncthingPlasmoid.Data.Paused: + return "paused"; + default: + return "idle"; + } + } + tooltip: text + onClicked: function() { + switch(plasmoid.nativeInterface.connection.status) { + case SyncthingPlasmoid.Data.Disconnected: + case SyncthingPlasmoid.Data.Reconnecting: + plasmoid.nativeInterface.connection.connect(); + break; + case SyncthingPlasmoid.Data.Paused: + plasmoid.nativeInterface.connection.resumeAllDevs(); + break; + default: + plasmoid.nativeInterface.connection.pauseAllDevs(); + break; + } + } + } + PlasmaComponents.ToolButton { + id: startStopButton + text: qsTr("TODO: Start/stop") + tooltip: text + iconSource: "system-run" + onClicked: plasmoid.nativeInterface.toggleSyncthingRunning() + } + PlasmaComponents.ToolButton { + tooltip: qsTr("Show own device ID") + iconSource: "view-barcode" + onClicked: { + plasmoid.nativeInterface.showOwnDeviceId() + plasmoid.expanded = false + } + } + PlasmaComponents.ToolButton { + tooltip: qsTr("Show Syncthing log") + iconSource: "text-x-generic" + onClicked: { + plasmoid.nativeInterface.showLog() + plasmoid.expanded = false + } + } + PlasmaComponents.ToolButton { + tooltip: qsTr("Rescan all directories") + iconSource: "view-refresh" + onClicked: plasmoid.nativeInterface.connection.rescanAllDirs() + } + PlasmaComponents.ToolButton { + tooltip: qsTr("Settings") + iconSource: "preferences-other" + onClicked: { + plasmoid.nativeInterface.showConnectionSettingsDlg() + plasmoid.expanded = false + } + } + PlasmaComponents.ToolButton { + tooltip: qsTr("Web UI") + iconSource: "internet-web-browser" + onClicked: { + plasmoid.nativeInterface.showWebUI(); + plasmoid.expanded = false + } + } + } + } + + PlasmaCore.SvgItem { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 2 + elementId: "horizontal-line" + svg: PlasmaCore.Svg { + imagePath: "widgets/line" + } + } + + // traffic and connection selection + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: false + + PlasmaCore.IconItem { + source: "network-card" + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + } + ColumnLayout { + Layout.fillHeight: true + spacing: 1 + + PlasmaComponents.Label { + text: qsTr("In") + } + PlasmaComponents.Label { + text: qsTr("Out") + } + } + ColumnLayout { + Layout.fillHeight: true + spacing: 1 + + PlasmaComponents.Label { + text: plasmoid.nativeInterface.incomingTraffic; + } + PlasmaComponents.Label { + text: plasmoid.nativeInterface.outgoingTraffic; + } + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + ToolButton { + text: qsTr("TODO: connection selection") + iconSource: "network-connect" + // FIXME: figure out why menu doesn't work in plasmoidviewer + menu: Menu { + MenuItem { + text: qsTr("foo") + } + MenuItem { + text: qsTr("bar") + } + } + } + } + + PlasmaCore.SvgItem { + Layout.preferredWidth: parent.width + Layout.preferredHeight: 2 + elementId: "horizontal-line" + svg: PlasmaCore.Svg { + imagePath: "widgets/line" + } + } + + // tab "widget" + RowLayout { + spacing: 0 + + PlasmaComponents.TabBar { + id: tabBar + tabPosition: Qt.LeftEdge + anchors.top: parent.top + + PlasmaComponents.TabButton { + //text: qsTr("Directories") + iconSource: "folder-symbolic" + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + tab: dirsPage + } + PlasmaComponents.TabButton { + //text: qsTr("Devices") + iconSource: "network-server-symbolic" + tab: devicesPage + } + PlasmaComponents.TabButton { + //text: qsTr("Downloads") + iconSource: "folder-download-symbolic" + tab: downloadsPage + } + } + PlasmaCore.SvgItem { + Layout.preferredWidth: 2 + Layout.fillHeight: true + elementId: "vertical-line" + svg: PlasmaCore.Svg { + imagePath: "widgets/line" + } + } + PlasmaComponents.TabGroup { + id: mainTabGroup + currentTab: dirsPage + Layout.fillWidth: true + Layout.fillHeight: true + + PlasmaExtras.ConditionalLoader { + id: dirsPage + when: mainTabGroup.currentTab == dirsPage + source: Qt.resolvedUrl("DirectoriesPage.qml") + } + PlasmaExtras.ConditionalLoader { + id: devicesPage + when: mainTabGroup.currentTab == devicesPage + source: Qt.resolvedUrl("DevicesPage.qml") + } + PlasmaExtras.ConditionalLoader { + id: downloadsPage + when: mainTabGroup.currentTab == downloadsPage + source: Qt.resolvedUrl("DownloadsPage.qml") + } + } + } +} diff --git a/plasmoid/package/contents/ui/TopLevelItem.qml b/plasmoid/package/contents/ui/TopLevelItem.qml new file mode 100644 index 0000000..c0003cf --- /dev/null +++ b/plasmoid/package/contents/ui/TopLevelItem.qml @@ -0,0 +1,15 @@ +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.1 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras + +PlasmaComponents.ListItem { + id: item + property bool expanded: false + enabled: true + onClicked: expanded = !expanded + onContainsMouseChanged: view.currentIndex = containsMouse ? index : -1 +} diff --git a/plasmoid/package/contents/ui/TopLevelView.qml b/plasmoid/package/contents/ui/TopLevelView.qml new file mode 100644 index 0000000..0124c5d --- /dev/null +++ b/plasmoid/package/contents/ui/TopLevelView.qml @@ -0,0 +1,20 @@ +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.1 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.extras 2.0 as PlasmaExtras + +ListView { + anchors.fill: parent + boundsBehavior: Flickable.StopAtBounds + interactive: contentHeight > height + keyNavigationEnabled: true + keyNavigationWraps: true + currentIndex: -1 + + highlightMoveDuration: 0 + highlightResizeDuration: 0 + highlight: PlasmaComponents.Highlight { } +} diff --git a/plasmoid/package/contents/ui/main.qml b/plasmoid/package/contents/ui/main.qml new file mode 100644 index 0000000..51519a0 --- /dev/null +++ b/plasmoid/package/contents/ui/main.qml @@ -0,0 +1,51 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import org.kde.plasma.plasmoid 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.kquickcontrolsaddons 2.0 +import org.kde.plasma.extras 2.0 as PlasmaExtras + +Item { + id: syncthingApplet + + Plasmoid.switchWidth: units.gridUnit * 20 + Plasmoid.switchHeight: units.gridUnit * 30 + Plasmoid.preferredRepresentation: Plasmoid.fullRepresentation + Plasmoid.compactRepresentation: CompactRepresentation { } + Plasmoid.fullRepresentation: FullRepresentation { + focus: true + } + Plasmoid.icon: "syncthingtray" + Plasmoid.toolTipMainText: plasmoid.nativeInterface.statusText + Plasmoid.toolTipSubText: plasmoid.nativeInterface.additionalStatusText + Plasmoid.hideOnWindowDeactivate: true + + function action_showWebUI() { + plasmoid.nativeInterface.showWebUI() + } + + function action_showSettings() { + plasmoid.nativeInterface.showConnectionSettingsDlg() + } + + function action_rescanAllDirs() { + plasmoid.nativeInterface.connection.rescanAllDirs() + } + + function action_showLog() { + plasmoid.nativeInterface.showLog() + } + + function action_showAboutDialog() { + plasmoid.nativeInterface.connection.showAboutDialog() + } + + Component.onCompleted: { + plasmoid.removeAction("configure"); + plasmoid.setAction("showWebUI", qsTr("Web UI"), "internet-web-browser"); + plasmoid.setAction("showSettings", qsTr("Settings"), "configure"); + plasmoid.setAction("showLog", qsTr("Log"), "text-x-generic"); + plasmoid.setAction("showAboutDialog", qsTr("About"), "help-about"); + } +} diff --git a/plasmoid/package/metadata.desktop b/plasmoid/package/metadata.desktop new file mode 100644 index 0000000..f175626 --- /dev/null +++ b/plasmoid/package/metadata.desktop @@ -0,0 +1,21 @@ +[Desktop Entry] +Encoding=UTF-8 +Name=Syncthing Plasmoid +Icon=syncthingtray +Type=Service +Keywords=syncthing;sync; +X-KDE-ParentApp= +X-KDE-PluginInfo-Author=Martchus +X-KDE-PluginInfo-Email=martchus@gmx.net +X-KDE-PluginInfo-License=GPLv2 +X-KDE-PluginInfo-Name=martchus.syncthingplasmoid +X-KDE-PluginInfo-Version=0.1 +X-KDE-PluginInfo-Website=https://github.com/martchus/syncthingtray +X-KDE-ServiceTypes=Plasma/Applet +X-Plasma-NotificationArea=true +X-Plasma-API=declarativeappletscript +X-Plasma-MainScript=ui/main.qml +X-Plasma-RemoteLocation= +X-KDE-PluginInfo-EnabledByDefault=true +X-KDE-PluginInfo-Category=System Information +X-KDE-Library=syncthingplasmoid diff --git a/plasmoid/testing.md b/plasmoid/testing.md new file mode 100644 index 0000000..4f298dd --- /dev/null +++ b/plasmoid/testing.md @@ -0,0 +1,36 @@ +# Testing and debugging Plasma 5 plasmoid with Qt Creator + +The following instructions allow to test the Plasmoid by installing it in a test directory +rather than the regular home to separate testing from production. + +1. Build as usual, ensure `NO_PLASMOID` is turned off +2. Add build step to execute custom target `init_plasmoid_testing` which + will install the Plasmoid in a test directory which is "$BUILD_DIR/plasmoid/testdir" + by default +3. Add new config for run in Qt Creator and set `plasmoidviewer` (or `plasmawindowed`) + as executable +4. In execution environment, set + * `QT_PLUGIN_PATH` to directory containing plugin `\*.so`-file + * `QT_DEBUG_PLUGINS` to 1 for verbose plugin detection + * `HOME` to the test directory from step 2 so plasmoidviewer finds the Plasmoid + in the test directory +5. Set `--applet martchus.syncthingplasmoid` as CLI argument +6. Ignore warning that executable is no debug build, it is sufficiant when + the plugin is a debug build (see next section for QML debugging) + +## Enable QML debugging + +To enable QML debugging, it is required to rebuild `plasmoidviewer` with QML debugging +enabled. + +1. Get plasma-sdk: `git clone https://anongit.kde.org/plasma-sdk.git` +2. Create a debug build of `plasmoidviewer` and ensure `QT_QML_DEBUG` is defined when + compiling `plasmoidviewer`, eg. by adding the following lines to its project file: + ``` + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_definitions(plasmoidviewer PRIVATE -DQT_QML_DEBUG) + endif() + ``` +3. Prepend the build directory containing the `plasmoidviewer` binary to the path variable + in the build environment of Syncthing Tray. +4. Enable QML debugging in the *Run* section.