Skip to content

Commit

Permalink
Add experimental/WIP Qt Quick GUI
Browse files Browse the repository at this point in the history
  • Loading branch information
Martchus committed Aug 13, 2024
1 parent 94f4876 commit 1d51980
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 1 deletion.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ option(NO_FILE_ITEM_ACTION_PLUGIN "whether building the file item action plugin
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 Plasmoid for the Plasma desktop should be skipped" "${PLASMOID_DISABLED_BY_DEFAULT}")
option(QUICK_GUI "enables/disables building the experimental Qt Quick GUI (disabled by default)" OFF)

# allow using non-default configuration
set(CONFIGURATION_PACKAGE_SUFFIX "" CACHE STRING "sets the suffix for find_package() calls to packages configured via c++utilities")
Expand Down
26 changes: 25 additions & 1 deletion tray/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR)
# metadata
set(META_PROJECT_TYPE application)
set(META_APP_NAME "Syncthing Tray")
set(META_HAS_QUICK_GUI ON)
set(META_USE_QQC2 ON)

# use testfiles directory from syncthingconnector
set(META_SRCDIR_REFS "${CMAKE_CURRENT_SOURCE_DIR}\n${CMAKE_CURRENT_SOURCE_DIR}/../syncthingconnector")
Expand Down Expand Up @@ -138,6 +140,11 @@ endif ()
# apply basic configuration
include(BasicConfig)

if (QUICK_GUI)
find_package(${PACKAGE_NAMESPACE_PREFIX}qtquickforkawesome${CONFIGURATION_PACKAGE_SUFFIX_QTFORKAWESOME} 0.1.0 REQUIRED)
use_qt_quick_fork_awesome()
endif ()

# add an option to unify left- and right-click context menus useful on Mac OS
if (APPLE)
set(UNIFY_TRAY_MENUS_BY_DEFAULT ON)
Expand Down Expand Up @@ -173,15 +180,32 @@ Name=Restart Syncthing (local instance)\n\
Exec=${SYNCTHINGCTL_TARGET_NAME} restart")
set(DESKTOP_FILE_ADDITIONAL_ENTRIES "${DESKTOP_FILE_ADDITIONAL_ENTRIES}${ACTIONS}\n")

# include modules to apply configuration
# configure Qt GUI GUI
include(QtGuiConfig)
if (QUICK_GUI)
list(APPEND ADDITIONAL_QT_MODULES Svg)
list(APPEND ADDITIONAL_QT_REPOS svg)
endif ()

# include further modules to apply configuration
include(QtConfig)
include(WindowsResources)
include(AppTarget)
include(ShellCompletion)
include(Doxygen)
include(ConfigHeader)

# add Qml module
if (QUICK_GUI)
qt_add_qml_module(${META_TARGET_NAME}
URI "Main"
VERSION 1.0
NO_PLUGIN
QML_FILES gui/qml/Main.qml
RESOURCE_PREFIX "/qt/qml/"
)
endif ()

# create desktop file using previously defined meta data
add_desktop_file()

Expand Down
63 changes: 63 additions & 0 deletions tray/application/main.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#ifdef GUI_QTQUICK
#define QT_UTILITIES_GUI_QTQUICK
#endif

#include "./singleinstance.h"

#include "../gui/trayicon.h"
Expand Down Expand Up @@ -37,6 +41,21 @@
#include <QSettings>
#include <QStringBuilder>

#ifdef GUI_QTQUICK
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickStyle>

#include <qtforkawesome/renderer.h>
#include <qtquickforkawesome/imageprovider.h>

#include <syncthingmodel/syncthingdirectorymodel.h>
#include <syncthingmodel/syncthingdevicemodel.h>
#include <syncthingmodel/syncthingrecentchangesmodel.h>

#include <syncthingconnector/syncthingconnection.h>
#endif

#include <iostream>

#ifdef Q_OS_ANDROID
Expand Down Expand Up @@ -211,6 +230,9 @@ static int runApplication(int argc, const char *const *argv)
#endif

parser.setMainArguments({ &qtConfigArgs.qtWidgetsGuiArg(),
#ifdef GUI_QTQUICK
&qtConfigArgs.qtQuickGuiArg(),
#endif
#ifdef SYNCTHINGTRAY_USE_LIBSYNCTHING
&cliArg, &syncthingArg,
#endif
Expand All @@ -227,6 +249,47 @@ static int runApplication(int argc, const char *const *argv)
return EXIT_SUCCESS;
}

#ifdef GUI_QTQUICK
if (qtConfigArgs.qtQuickGuiArg().isPresent()) {
qputenv("QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT", "1");
SET_QT_APPLICATION_INFO;
auto app = QApplication(argc, const_cast<char **>(argv));
auto &settings = Settings::values();
Settings::restore();
settings.qt.disableNotices();
settings.qt.apply();
qtConfigArgs.applySettings(true);
qtConfigArgs.applySettingsForQuickGui();

auto engine = QQmlApplicationEngine();
auto renderer = QtForkAwesome::Renderer();
auto context = engine.rootContext();
auto connection = Data::SyncthingConnection();
auto dirModel = Data::SyncthingDirectoryModel(connection);
auto devModel = Data::SyncthingDeviceModel(connection);
auto changesModel = Data::SyncthingRecentChangesModel(connection);
networkAccessManager().setParent(&app);
connection.connect(settings.connection.primary);
context->setContextProperty(QStringLiteral("connection"), &connection);
context->setContextProperty(QStringLiteral("dirModel"), &dirModel);
context->setContextProperty(QStringLiteral("devModel"), &devModel);
context->setContextProperty(QStringLiteral("changesModel"), &changesModel);
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated, &app,
[](QObject *obj, const QUrl &objUrl) {
if (!obj) {
std::cerr << "Unable to load " << objUrl.toString().toStdString() << '\n';
QCoreApplication::exit(EXIT_FAILURE);
}
},
Qt::QueuedConnection);
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QGuiApplication::quit);
engine.addImageProvider(QStringLiteral("fa"), new QtForkAwesome::QuickImageProvider(renderer));
engine.loadFromModule("Main", "Main");
return app.exec();
}
#endif

// quit unless Qt Widgets GUI should be shown
if (!qtConfigArgs.qtWidgetsGuiArg().isPresent()) {
return EXIT_SUCCESS;
Expand Down
206 changes: 206 additions & 0 deletions tray/gui/qml/Main.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls

ApplicationWindow {
id: window
visible: true
width: 700
height: 500
header: ToolBar {
RowLayout {
anchors.fill: parent
anchors.leftMargin: drawer.effectiveWidth
Label {
text: pageStack.currentPage.title
elide: Label.ElideRight
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
Layout.fillWidth: true
}
}
}

readonly property bool inPortrait: window.width < window.height
readonly property int spacing: 7
readonly property string faUrlBase: "image://fa/"

Dialog {
id: aboutDialog
modal: true
focus: true
parent: null
anchors.centerIn: parent
standardButtons: Dialog.Ok
width: 300
title: qsTr("About")
contentItem: ColumnLayout {
Image {
readonly property double size: 128
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: size
Layout.preferredHeight: size
source: "qrc:/icons/hicolor/scalable/app/syncthingtray.svg"
sourceSize.width: size
sourceSize.height: size
}
Label {
text: Qt.application.name
Layout.alignment: Qt.AlignHCenter
}
Label {
text: "Version " + Qt.application.version
Layout.alignment: Qt.AlignHCenter
}
Label {
text: "Developed by " + Qt.application.organization
Layout.alignment: Qt.AlignHCenter
}
}
}

Drawer {
id: drawer
width: Math.min(0.66 * window.width, 200)
height: window.height
interactive: inPortrait || window.width < 600
modal: interactive
position: initialPosition
visible: !interactive

readonly property double initialPosition: interactive ? 0 : 1
readonly property int effectiveWidth: !interactive ? width : 0

ListView {
id: drawerListView
anchors.fill: parent
footer: ItemDelegate {
width: parent.width
text: Qt.application.version
icon.source: window.faUrlBase + "info-circle"
onClicked: aboutDialog.visible = true
}
footerPositioning: ListView.OverlayFooter
model: ListModel {
ListElement {
name: qsTr("Folders")
iconName: "folder"
}
ListElement {
name: qsTr("Devices")
iconName: "sitemap"
}
ListElement {
name: qsTr("Recent changes")
iconName: "history"
}
ListElement {
name: qsTr("Syncthing")
iconName: "syncthing"
}
ListElement {
name: qsTr("App settings")
iconName: "cog"
}
}
delegate: ItemDelegate {
text: name
icon.source: window.faUrlBase + iconName
width: parent.width
onClicked: {
drawerListView.currentIndex = index
drawer.position = drawer.initialPosition
}
}
ScrollIndicator.vertical: ScrollIndicator { }
}
}

Flickable {
id: flickable
anchors.fill: parent
anchors.leftMargin: drawer.effectiveWidth

StackLayout {
id: pageStack
anchors.fill: parent
currentIndex: drawerListView.currentIndex

readonly property Page currentPage: children[currentIndex]

Page {
id: dirsPage
title: qsTr("Folder overview")
Layout.fillWidth: true
Layout.fillHeight: true
ListView {
anchors.fill: parent
model: DelegateModel {
model: dirModel
delegate: ItemDelegate {
width: parent.width
text: name
}
}
ScrollIndicator.vertical: ScrollIndicator { }
}
}

Page {
id: devsPage
title: qsTr("Device overview")
Layout.fillWidth: true
Layout.fillHeight: true
ListView {
anchors.fill: parent
model: DelegateModel {
model: devModel
delegate: ItemDelegate {
width: parent.width
text: name
}
}
ScrollIndicator.vertical: ScrollIndicator { }
}
}

Page {
id: changesPage
title: qsTr("Recent changes")
Layout.fillWidth: true
Layout.fillHeight: true
ListView {
anchors.fill: parent
model: DelegateModel {
model: changesModel
delegate: ItemDelegate {
width: parent.width
text: path
}
}
ScrollIndicator.vertical: ScrollIndicator { }
}
}

Page {
id: syncthingPage
title: qsTr("Syncthing")
Layout.fillWidth: true
Layout.fillHeight: true
Label {
text: "TODO: webview"
}
}

Page {
id: settingsPage
title: qsTr("App settings")
Layout.fillWidth: true
Layout.fillHeight: true
Label {
text: "TODO: settings UI"
}
}
}
}
}

0 comments on commit 1d51980

Please sign in to comment.