From 1368167d4fa5e2e8c100bc67c88c9f845a3aef58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 12:47:48 +0200 Subject: [PATCH 01/83] Implement a mockup login screen with QML (linux) --- src/ui/linux/TogglDesktop/CMakeLists.txt | 1 - src/ui/linux/TogglDesktop/MainWindow.qml | 151 +++++++++++++++++++++++ src/ui/linux/TogglDesktop/Resources.qrc | 1 + src/ui/linux/TogglDesktop/main.cpp | 19 +-- 4 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/MainWindow.qml diff --git a/src/ui/linux/TogglDesktop/CMakeLists.txt b/src/ui/linux/TogglDesktop/CMakeLists.txt index 48477f1c24..f222851ce7 100644 --- a/src/ui/linux/TogglDesktop/CMakeLists.txt +++ b/src/ui/linux/TogglDesktop/CMakeLists.txt @@ -61,7 +61,6 @@ set(BINARY_SOURCE_FILES timeentryeditorwidget.ui timeentrylistwidget.ui timerwidget.ui - ) # Set up compilation targets diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml new file mode 100644 index 0000000000..d3ac98b538 --- /dev/null +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -0,0 +1,151 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Layouts 1.12 + +import QtQuick.Controls 2.12 + +Window { + visible: true + minimumWidth: 360 + width: 360 + minimumHeight: 640 + height: 640 + + property bool loggingIn: !loginSwitch.checked + property bool signingUp: !loggingIn + + Rectangle { + anchors.fill: parent + color: "#202020" + } + + TextMetrics { + id: termsAndConditionsMetrics + text: "Agree to conditions and terms TBD" + } + + Column { + y: parent.height / 5 + anchors.horizontalCenter: parent.horizontalCenter + width: termsAndConditionsMetrics.width + 30 + spacing: 18 + add: Transition { + NumberAnimation { + properties: "opacity" + from: 0.0 + to: 1.0 + duration: 120 + } + NumberAnimation { + properties: "x,y" + duration: 0 + } + } + move: Transition { + NumberAnimation { + properties: "y" + duration: 120 + } + } + + Image { + anchors.horizontalCenter: parent.horizontalCenter + source: "qrc:/images/logo.png" + } + + Item { height: 3; width: 1 } + + RowLayout { + width: parent.width + Text { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + text: "Login" + color: loggingIn ? "white" : "#9a9a9a" + } + Switch { + id: loginSwitch + onCheckedChanged: username.forceActiveFocus() + } + Text { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + text: "Sign up" + color: signingUp ? "white" : "#9a9a9a" + } + } + + RowLayout { + width: parent.width + visible: signingUp + Button { + id: signupWithEmail + Layout.fillWidth: true + checkable: true + autoExclusive: true + checked: true + text: "With email" + } + Button { + id: signupWithGoogle + Layout.fillWidth: true + checkable: true + autoExclusive: true + text: "With Google" + } + } + + TextField { + width: parent.width + id: username + focus: true + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Username" + visible: loggingIn || !signupWithGoogle.checked + } + + TextField { + width: parent.width + id: password + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Password" + echoMode: TextField.Password + visible: loggingIn || !signupWithGoogle.checked + } + + ComboBox { + id: country + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + visible: signingUp + model: [ "United States", "Estonia", "Czechia" ] + currentIndex: -1 + displayText: currentIndex < 0 ? "Please select your country" : currentText + } + + Row { + id: termsAndConditionsRow + width: parent.width + visible: signingUp + anchors.horizontalCenter: parent.horizontalCenter + CheckBox { + id: termsAndConditions + } + Text { + id: termsAndConditionsText + anchors.verticalCenter: parent.verticalCenter + text: "Agree to conditions and terms TBD" + color: "#d2d2d2" + } + } + + Button { + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + text: signingUp ? "Sign up" : "Log in" + enabled: signingUp ? (!signupWithEmail.checked || (username.text.length > 0 && password.text.length > 0)) && + country.currentIndex > -1 && termsAndConditions.checked + : username.text.length > 0 && password.text.length > 0 + } + } +} diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index 0222db948f..f7536833d8 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -21,5 +21,6 @@ images/group_icon_open.svg images/down_arrow.png ../../windows/TogglDesktop/TogglDesktop/Resources/warning-icon.png + MainWindow.qml diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index c0a62f7f71..d0d35c28e7 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include #include #include @@ -22,8 +24,6 @@ #include "./mainwindowcontroller.h" #include "./toggl.h" -MainWindowController *w = nullptr; - class TogglApplication : public SingleApplication { public: TogglApplication(int &argc, char **argv) // NOLINT @@ -100,18 +100,11 @@ int main(int argc, char *argv[]) try { parser.process(a); - w = new MainWindowController(nullptr, - parser.value(logPathOption), - parser.value(dbPathOption), - parser.value(scriptPathOption)); + QQmlApplicationEngine engine; + qDebug() << "Load"; + engine.load(QUrl(QStringLiteral("qrc:/MainWindow.qml"))); + qDebug() << "Loaded"; - a.w = w; - - if (parser.isSet(forceOption)) { - w->hide(); - } else { - w->show(); - } return a.exec(); } catch (std::exception &e) { // NOLINT TogglApi::notifyBugsnag("std::exception", e.what(), "main"); From f508c393c072cd7b9b7f01ff64cd4303fb19c7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 13:15:54 +0200 Subject: [PATCH 02/83] Enable displaying errors (linux) --- src/ui/linux/TogglDesktop/MainWindow.qml | 34 +++++++++++++++++++++++- src/ui/linux/TogglDesktop/main.cpp | 12 +++++++-- src/ui/linux/TogglDesktop/toggl.cpp | 14 ++++++++++ src/ui/linux/TogglDesktop/toggl.h | 15 ++++++++--- 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index d3ac98b538..45dad0b3d7 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 Window { + id: window visible: true minimumWidth: 360 width: 360 @@ -71,7 +72,7 @@ Window { Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter text: "Sign up" - color: signingUp ? "white" : "#9a9a9a" + color: signingUp ? "white;" : "#9a9a9a" } } @@ -146,6 +147,37 @@ Window { enabled: signingUp ? (!signupWithEmail.checked || (username.text.length > 0 && password.text.length > 0)) && country.currentIndex > -1 && termsAndConditions.checked : username.text.length > 0 && password.text.length > 0 + onClicked: loggingIn ? toggl.login(username.text, password.text) : toggl.signup(username.text, password.text, country.currentIndex) + } + } + + Rectangle { + id: errorOverlay + width: parent.width + color: "red" + height: 48 + visible: false + Connections { + target: toggl + onDisplayError: { + errorOverlay.visible = true + errorText.text = errmsg + } + } + RowLayout { + anchors.fill: parent + anchors.margins: 6 + Text { + id: errorText + Layout.fillHeight: true + Layout.fillWidth: true + verticalAlignment: Text.AlignVCenter + } + Button { + text: "x" + onClicked: errorOverlay.visible = false + implicitWidth: implicitHeight + } } } } diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index d0d35c28e7..bbb6ad63eb 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -101,9 +102,16 @@ int main(int argc, char *argv[]) try { parser.process(a); QQmlApplicationEngine engine; - qDebug() << "Load"; engine.load(QUrl(QStringLiteral("qrc:/MainWindow.qml"))); - qDebug() << "Loaded"; + engine.rootContext()->setContextProperty("toggl", new TogglApi(nullptr)); + if (!TogglApi::instance->startEvents()) { + QMessageBox( + QMessageBox::Warning, + "Error", + "The application could not start. Please inspect the log file.", + QMessageBox::Ok|QMessageBox::Cancel).exec(); + return 1; + } return a.exec(); } catch (std::exception &e) { // NOLINT diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index 4b251fa7f8..aacd03b353 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -208,6 +208,16 @@ void on_countries( CountryView::importAll(first)); } +void TogglApi::setCountries(QVector list) { + for (auto i : countries_) + i->deleteLater(); + countries_.clear(); + for (auto i : list) { + countries_.append(i); + } + emit countriesChanged(); +} + TogglApi::TogglApi( QObject *parent, QString logPathOverride, @@ -321,6 +331,10 @@ bool TogglApi::notifyBugsnag( return Bugsnag::notify(errorClass, message, context, &metadata); } +QQmlListProperty TogglApi::countries() { + return QQmlListProperty(this, countries_); +} + bool TogglApi::startEvents() { return toggl_ui_start(ctx); } diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index 6acc91ad98..da88b05b9c 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -19,6 +20,7 @@ class CountryView; class TogglApi : public QObject { Q_OBJECT + Q_PROPERTY(QQmlListProperty countries READ countries NOTIFY countriesChanged) public: TogglApi( @@ -40,6 +42,16 @@ class TogglApi : public QObject { bool shutdown; + QQmlListProperty countries(); +signals: + void countriesChanged(); +private: + QList countries_; + + public slots: + void setCountries(QVector list); + +/////////////////////////////////////////////////////// bool startEvents(); void clear(); @@ -310,9 +322,6 @@ class TogglApi : public QObject { void setProjectColors( QVector list); - void setCountries( - QVector list); - private: void *ctx; From 02e78d338344569d663c78def185a1371e2569bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 13:17:27 +0200 Subject: [PATCH 03/83] Animate the error overlay (linux) --- src/ui/linux/TogglDesktop/MainWindow.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index 45dad0b3d7..3e3c33c22b 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -156,11 +156,12 @@ Window { width: parent.width color: "red" height: 48 - visible: false + y: -height + Behavior on y { NumberAnimation { duration: 120 } } Connections { target: toggl onDisplayError: { - errorOverlay.visible = true + errorOverlay.y = 0 errorText.text = errmsg } } @@ -175,7 +176,7 @@ Window { } Button { text: "x" - onClicked: errorOverlay.visible = false + onClicked: errorOverlay.y = -errorOverlay.height implicitWidth: implicitHeight } } From a640e0b2dd7a4662d114c5d8afa8f288f011581d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 13:22:17 +0200 Subject: [PATCH 04/83] Enable hitting Return on text fields to log in (linux) --- src/ui/linux/TogglDesktop/MainWindow.qml | 33 +++++++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index 3e3c33c22b..54eddd8ee8 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -15,6 +15,23 @@ Window { property bool loggingIn: !loginSwitch.checked property bool signingUp: !loggingIn + property bool everythingFilled: signingUp ? (!signupWithEmail.checked || (username.text.length > 0 && password.text.length > 0)) && + country.currentIndex > -1 && termsAndConditions.checked + : username.text.length > 0 && password.text.length > 0 + + function act() { + if (!everythingFilled) + return + if (loggingIn) + toggl.login(username.text, password.text) + else if (signingUp) { + if (signupWithEmail.checked) + toggl.signup(username.text, password.text, country.currentIndex) + else if (signupWithGoogle.checked) + toggl.googleSignup(username.text, password.text, country.currentIndex) + } + } + Rectangle { anchors.fill: parent color: "#202020" @@ -103,6 +120,7 @@ Window { anchors.horizontalCenter: parent.horizontalCenter placeholderText: "Username" visible: loggingIn || !signupWithGoogle.checked + onAccepted: act() } TextField { @@ -112,6 +130,7 @@ Window { placeholderText: "Password" echoMode: TextField.Password visible: loggingIn || !signupWithGoogle.checked + onAccepted: act() } ComboBox { @@ -144,10 +163,16 @@ Window { width: parent.width anchors.horizontalCenter: parent.horizontalCenter text: signingUp ? "Sign up" : "Log in" - enabled: signingUp ? (!signupWithEmail.checked || (username.text.length > 0 && password.text.length > 0)) && - country.currentIndex > -1 && termsAndConditions.checked - : username.text.length > 0 && password.text.length > 0 - onClicked: loggingIn ? toggl.login(username.text, password.text) : toggl.signup(username.text, password.text, country.currentIndex) + enabled: everythingFilled + onClicked: act() + } + + Button { + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + text: "Log in with Google (not implemented)" + visible: loggingIn + enabled: false } } From 4751430156c2737b3281b4073b07c91f5b1c2d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 13:29:04 +0200 Subject: [PATCH 05/83] Move views to separate files, handle onDisplayError signal (linux) --- src/ui/linux/TogglDesktop/ErrorOverlay.qml | 33 ++++ src/ui/linux/TogglDesktop/LoginView.qml | 162 ++++++++++++++++++ src/ui/linux/TogglDesktop/MainWindow.qml | 189 ++------------------- src/ui/linux/TogglDesktop/Resources.qrc | 2 + 4 files changed, 207 insertions(+), 179 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/ErrorOverlay.qml create mode 100644 src/ui/linux/TogglDesktop/LoginView.qml diff --git a/src/ui/linux/TogglDesktop/ErrorOverlay.qml b/src/ui/linux/TogglDesktop/ErrorOverlay.qml new file mode 100644 index 0000000000..5aed37113b --- /dev/null +++ b/src/ui/linux/TogglDesktop/ErrorOverlay.qml @@ -0,0 +1,33 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 + +import QtQuick.Controls 2.12 + +Rectangle { + id: errorOverlay + color: "red" + y: -height + Behavior on y { NumberAnimation { duration: 120 } } + Connections { + target: toggl + onDisplayError: { + errorOverlay.y = 0 + errorText.text = errmsg + } + } + RowLayout { + anchors.fill: parent + anchors.margins: 6 + Text { + id: errorText + Layout.fillHeight: true + Layout.fillWidth: true + verticalAlignment: Text.AlignVCenter + } + Button { + text: "x" + onClicked: errorOverlay.y = -errorOverlay.height + implicitWidth: implicitHeight + } + } +} diff --git a/src/ui/linux/TogglDesktop/LoginView.qml b/src/ui/linux/TogglDesktop/LoginView.qml new file mode 100644 index 0000000000..4be4e42f65 --- /dev/null +++ b/src/ui/linux/TogglDesktop/LoginView.qml @@ -0,0 +1,162 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 + +import QtQuick.Controls 2.12 + +Item { + anchors.fill: parent + + property bool loggingIn: !loginSwitch.checked + property bool signingUp: !loggingIn + + property bool everythingFilled: signingUp ? (!signupWithEmail.checked || (username.text.length > 0 && password.text.length > 0)) && + country.currentIndex > -1 && termsAndConditions.checked + : username.text.length > 0 && password.text.length > 0 + + function act() { + if (!everythingFilled) + return + if (loggingIn) + toggl.login(username.text, password.text) + else if (signingUp) { + if (signupWithEmail.checked) + toggl.signup(username.text, password.text, country.currentIndex) + else if (signupWithGoogle.checked) + toggl.googleSignup(username.text, password.text, country.currentIndex) + } + } + + Column { + y: parent.height / 5 + anchors.horizontalCenter: parent.horizontalCenter + width: termsAndConditionsMetrics.width + 30 + spacing: 18 + add: Transition { + NumberAnimation { + properties: "opacity" + from: 0.0 + to: 1.0 + duration: 120 + } + NumberAnimation { + properties: "x,y" + duration: 0 + } + } + move: Transition { + NumberAnimation { + properties: "y" + duration: 120 + } + } + + Image { + anchors.horizontalCenter: parent.horizontalCenter + source: "qrc:/images/logo.png" + } + + Item { height: 3; width: 1 } + + RowLayout { + width: parent.width + Text { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + text: "Login" + color: loggingIn ? "white" : "#9a9a9a" + } + Switch { + id: loginSwitch + onCheckedChanged: username.forceActiveFocus() + } + Text { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + text: "Sign up" + color: signingUp ? "white;" : "#9a9a9a" + } + } + + RowLayout { + width: parent.width + visible: signingUp + Button { + id: signupWithEmail + Layout.fillWidth: true + checkable: true + autoExclusive: true + checked: true + text: "With email" + } + Button { + id: signupWithGoogle + Layout.fillWidth: true + checkable: true + autoExclusive: true + text: "With Google" + } + } + + TextField { + width: parent.width + id: username + focus: true + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Username" + visible: loggingIn || !signupWithGoogle.checked + onAccepted: act() + } + + TextField { + width: parent.width + id: password + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Password" + echoMode: TextField.Password + visible: loggingIn || !signupWithGoogle.checked + onAccepted: act() + } + + ComboBox { + id: country + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + visible: signingUp + model: [ "United States", "Estonia", "Czechia" ] + currentIndex: -1 + displayText: currentIndex < 0 ? "Please select your country" : currentText + } + + Row { + id: termsAndConditionsRow + width: parent.width + visible: signingUp + anchors.horizontalCenter: parent.horizontalCenter + CheckBox { + id: termsAndConditions + } + Text { + id: termsAndConditionsText + anchors.verticalCenter: parent.verticalCenter + text: "Agree to conditions and terms TBD" + color: "#d2d2d2" + } + } + + Button { + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + text: signingUp ? "Sign up" : "Log in" + enabled: everythingFilled + onClicked: act() + } + + Button { + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + text: "Log in with Google (not implemented)" + visible: loggingIn + enabled: false + } + } +} diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index 54eddd8ee8..5973ee02e7 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -2,8 +2,6 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Layouts 1.12 -import QtQuick.Controls 2.12 - Window { id: window visible: true @@ -12,26 +10,6 @@ Window { minimumHeight: 640 height: 640 - property bool loggingIn: !loginSwitch.checked - property bool signingUp: !loggingIn - - property bool everythingFilled: signingUp ? (!signupWithEmail.checked || (username.text.length > 0 && password.text.length > 0)) && - country.currentIndex > -1 && termsAndConditions.checked - : username.text.length > 0 && password.text.length > 0 - - function act() { - if (!everythingFilled) - return - if (loggingIn) - toggl.login(username.text, password.text) - else if (signingUp) { - if (signupWithEmail.checked) - toggl.signup(username.text, password.text, country.currentIndex) - else if (signupWithGoogle.checked) - toggl.googleSignup(username.text, password.text, country.currentIndex) - } - } - Rectangle { anchors.fill: parent color: "#202020" @@ -42,168 +20,21 @@ Window { text: "Agree to conditions and terms TBD" } - Column { - y: parent.height / 5 - anchors.horizontalCenter: parent.horizontalCenter - width: termsAndConditionsMetrics.width + 30 - spacing: 18 - add: Transition { - NumberAnimation { - properties: "opacity" - from: 0.0 - to: 1.0 - duration: 120 - } - NumberAnimation { - properties: "x,y" - duration: 0 - } - } - move: Transition { - NumberAnimation { - properties: "y" - duration: 120 - } - } - - Image { - anchors.horizontalCenter: parent.horizontalCenter - source: "qrc:/images/logo.png" - } - - Item { height: 3; width: 1 } - - RowLayout { - width: parent.width - Text { - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: "Login" - color: loggingIn ? "white" : "#9a9a9a" - } - Switch { - id: loginSwitch - onCheckedChanged: username.forceActiveFocus() - } - Text { - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter - text: "Sign up" - color: signingUp ? "white;" : "#9a9a9a" - } - } - - RowLayout { - width: parent.width - visible: signingUp - Button { - id: signupWithEmail - Layout.fillWidth: true - checkable: true - autoExclusive: true - checked: true - text: "With email" - } - Button { - id: signupWithGoogle - Layout.fillWidth: true - checkable: true - autoExclusive: true - text: "With Google" - } - } - - TextField { - width: parent.width - id: username - focus: true - anchors.horizontalCenter: parent.horizontalCenter - placeholderText: "Username" - visible: loggingIn || !signupWithGoogle.checked - onAccepted: act() - } - - TextField { - width: parent.width - id: password - anchors.horizontalCenter: parent.horizontalCenter - placeholderText: "Password" - echoMode: TextField.Password - visible: loggingIn || !signupWithGoogle.checked - onAccepted: act() - } - - ComboBox { - id: country - width: parent.width - anchors.horizontalCenter: parent.horizontalCenter - visible: signingUp - model: [ "United States", "Estonia", "Czechia" ] - currentIndex: -1 - displayText: currentIndex < 0 ? "Please select your country" : currentText - } - - Row { - id: termsAndConditionsRow - width: parent.width - visible: signingUp - anchors.horizontalCenter: parent.horizontalCenter - CheckBox { - id: termsAndConditions - } - Text { - id: termsAndConditionsText - anchors.verticalCenter: parent.verticalCenter - text: "Agree to conditions and terms TBD" - color: "#d2d2d2" - } - } - - Button { - width: parent.width - anchors.horizontalCenter: parent.horizontalCenter - text: signingUp ? "Sign up" : "Log in" - enabled: everythingFilled - onClicked: act() + Connections { + target: toggl + onDisplayLogin: { + if (open) + mainView.source = "LoginView.qml" } + } - Button { - width: parent.width - anchors.horizontalCenter: parent.horizontalCenter - text: "Log in with Google (not implemented)" - visible: loggingIn - enabled: false - } + Loader { + id: mainView + anchors.fill: parent } - Rectangle { - id: errorOverlay + ErrorOverlay { width: parent.width - color: "red" height: 48 - y: -height - Behavior on y { NumberAnimation { duration: 120 } } - Connections { - target: toggl - onDisplayError: { - errorOverlay.y = 0 - errorText.text = errmsg - } - } - RowLayout { - anchors.fill: parent - anchors.margins: 6 - Text { - id: errorText - Layout.fillHeight: true - Layout.fillWidth: true - verticalAlignment: Text.AlignVCenter - } - Button { - text: "x" - onClicked: errorOverlay.y = -errorOverlay.height - implicitWidth: implicitHeight - } - } } } diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index f7536833d8..bca6457ed9 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -22,5 +22,7 @@ images/down_arrow.png ../../windows/TogglDesktop/TogglDesktop/Resources/warning-icon.png MainWindow.qml + LoginView.qml + ErrorOverlay.qml From af09fac050460aaa92977e0c51829e5e12dc1ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 13:36:34 +0200 Subject: [PATCH 06/83] Start implementing time entry view (linux) --- src/ui/linux/TogglDesktop/MainWindow.qml | 4 ++++ src/ui/linux/TogglDesktop/Resources.qrc | 1 + src/ui/linux/TogglDesktop/TimeEntryListView.qml | 5 +++++ src/ui/linux/TogglDesktop/toggl.cpp | 16 +++++++++++++++- src/ui/linux/TogglDesktop/toggl.h | 5 ++++- 5 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/TimeEntryListView.qml diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index 5973ee02e7..21860b8256 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -26,6 +26,10 @@ Window { if (open) mainView.source = "LoginView.qml" } + onDisplayTimeEntryList: { + if (open) + mainView.source = "TimeEntryListView.qml" + } } Loader { diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index bca6457ed9..095ddff025 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -24,5 +24,6 @@ MainWindow.qml LoginView.qml ErrorOverlay.qml + TimeEntryListView.qml diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml new file mode 100644 index 0000000000..9c36e13c5b --- /dev/null +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 + +Item { + +} diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index aacd03b353..299114b40e 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -100,10 +100,11 @@ void on_display_time_entry_list( if (open) { TogglApi::instance->aboutToDisplayTimeEntryList(); } + TogglApi::instance->importTimeEntries(TimeEntryView::importAll(first)); TogglApi::instance->displayTimeEntryList( open, - TimeEntryView::importAll(first), show_load_more_button); + } void on_display_time_entry_autocomplete( @@ -335,6 +336,19 @@ QQmlListProperty TogglApi::countries() { return QQmlListProperty(this, countries_); } +QQmlListProperty TogglApi::timeEntries() { + return QQmlListProperty(this, timeEntries_); +} + +void TogglApi::importTimeEntries(QVector list) { + for (auto i : timeEntries_) + i->deleteLater(); + timeEntries_.clear(); + for (auto i : list) + timeEntries_.append(i); + emit timeEntriesChanged(); +} + bool TogglApi::startEvents() { return toggl_ui_start(ctx); } diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index da88b05b9c..9152fb39f6 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -43,10 +43,14 @@ class TogglApi : public QObject { bool shutdown; QQmlListProperty countries(); + QQmlListProperty timeEntries(); + void importTimeEntries(QVector list); signals: void countriesChanged(); + void timeEntriesChanged(); private: QList countries_; + QList timeEntries_; public slots: void setCountries(QVector list); @@ -272,7 +276,6 @@ class TogglApi : public QObject { void aboutToDisplayTimeEntryList(); void displayTimeEntryList( const bool open, - QVector list, const bool show_load_more_button); void aboutToDisplayTimeEntryEditor(); From 4426e52bbb96932eb686c47cec56d4350a567d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 14:08:01 +0200 Subject: [PATCH 07/83] Display basic time entry info, implement mockup Timer (linux) --- .../linux/TogglDesktop/TimeEntryListView.qml | 69 +++++++++++++++++- src/ui/linux/TogglDesktop/main.cpp | 4 +- src/ui/linux/TogglDesktop/timeentryview.h | 73 ++++++++++--------- src/ui/linux/TogglDesktop/toggl.cpp | 4 +- src/ui/linux/TogglDesktop/toggl.h | 1 + 5 files changed, 114 insertions(+), 37 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 9c36e13c5b..e245e46934 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -1,5 +1,70 @@ -import QtQuick 2.0 +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 Item { - + anchors.fill: parent + ColumnLayout { + anchors.fill: parent + Rectangle { + Layout.fillWidth: true + color: "black" + height: 64 + ColumnLayout { + id: timerContainer + height: parent.height + width: parent.width - startButton.width + Text { + Layout.fillWidth: true + Layout.fillHeight: true + verticalAlignment: Text.AlignVCenter + text: "Current TE" + color: "white" + } + } + Rectangle { + id: startButton + width: parent.height + height: parent.height + anchors.left: timerContainer.right + color: "green" + Text { + text: "Start" + anchors.centerIn: parent + } + } + } + ListView { + id: timeEntryList + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + model: toggl.timeEntries + delegate: Item { + height: 48 + width: timeEntryList.width + Rectangle { + anchors.fill: parent + anchors.margins: 2 + radius: 2 + color: "white" + RowLayout { + anchors.fill: parent + Text { + Layout.fillHeight: true + Layout.fillWidth: true + text: modelData.Description + verticalAlignment: Text.AlignVCenter + } + Button { + implicitWidth: implicitHeight + contentItem: Image { + source: "qrc:/images/continue.svg" + } + } + } + } + } + } + } } diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index bbb6ad63eb..134601fe25 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -102,8 +102,10 @@ int main(int argc, char *argv[]) try { parser.process(a); QQmlApplicationEngine engine; - engine.load(QUrl(QStringLiteral("qrc:/MainWindow.qml"))); + qmlRegisterType("toggl", 1, 0, "Country"); + qmlRegisterType("toggl", 1, 0, "TimeEntry"); engine.rootContext()->setContextProperty("toggl", new TogglApi(nullptr)); + engine.load(QUrl(QStringLiteral("qrc:/MainWindow.qml"))); if (!TogglApi::instance->startEvents()) { QMessageBox( QMessageBox::Warning, diff --git a/src/ui/linux/TogglDesktop/timeentryview.h b/src/ui/linux/TogglDesktop/timeentryview.h index 12b9343e2d..e7e758a6af 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.h +++ b/src/ui/linux/TogglDesktop/timeentryview.h @@ -8,6 +8,13 @@ #include "./toggl_api.h" +#define PROPERTY(type, name) \ +private: \ + Q_PROPERTY(type name READ name ## Get CONSTANT) \ +public: \ + type name; \ + type name ## Get() const { return name; } + class TimeEntryView : public QObject { Q_OBJECT @@ -20,40 +27,40 @@ class TimeEntryView : public QObject { bool confirmlessDelete(); const QString lastUpdate(); - int64_t DurationInSeconds; - QString Description; - QString ProjectAndTaskLabel; - QString ProjectLabel; - QString TaskLabel; - QString ClientLabel; - uint64_t WID; - uint64_t PID; - uint64_t TID; - QString Duration; - QString Color; - QString GUID; - bool Billable; - QString Tags; - uint64_t Started; - uint64_t Ended; - QString StartTimeString; - QString EndTimeString; - uint64_t UpdatedAt; - QString DateHeader; - QString DateDuration; - bool IsHeader; - bool CanAddProjects; - bool CanSeeBillable; - uint64_t DefaultWID; - QString WorkspaceName; - QString Error; - bool Unsynced; + PROPERTY(int64_t, DurationInSeconds) + PROPERTY(QString, Description) + PROPERTY(QString, ProjectAndTaskLabel) + PROPERTY(QString, ProjectLabel) + PROPERTY(QString, TaskLabel) + PROPERTY(QString, ClientLabel) + PROPERTY(uint64_t, WID) + PROPERTY(uint64_t, PID) + PROPERTY(uint64_t, TID) + PROPERTY(QString, Duration) + PROPERTY(QString, Color) + PROPERTY(QString, GUID) + PROPERTY(bool, Billable) + PROPERTY(QString, Tags) + PROPERTY(uint64_t, Started) + PROPERTY(uint64_t, Ended) + PROPERTY(QString, StartTimeString) + PROPERTY(QString, EndTimeString) + PROPERTY(uint64_t, UpdatedAt) + PROPERTY(QString, DateHeader) + PROPERTY(QString, DateDuration) + PROPERTY(bool, IsHeader) + PROPERTY(bool, CanAddProjects) + PROPERTY(bool, CanSeeBillable) + PROPERTY(uint64_t, DefaultWID) + PROPERTY(QString, WorkspaceName) + PROPERTY(QString, Error) + PROPERTY(bool, Unsynced) // Group mode items - bool Group; - bool GroupOpen; - QString GroupName; - QString GroupDuration; - u_int64_t GroupItemCount; + PROPERTY(bool, Group) + PROPERTY(bool, GroupOpen) + PROPERTY(QString, GroupName) + PROPERTY(QString, GroupDuration) + PROPERTY(u_int64_t, GroupItemCount) }; #endif // SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYVIEW_H_ diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index 299114b40e..11cdff1039 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -344,8 +344,10 @@ void TogglApi::importTimeEntries(QVector list) { for (auto i : timeEntries_) i->deleteLater(); timeEntries_.clear(); - for (auto i : list) + for (auto i : list) { + qDebug() << "Importing" << i->Description; timeEntries_.append(i); + } emit timeEntriesChanged(); } diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index 9152fb39f6..6791bba58c 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -21,6 +21,7 @@ class CountryView; class TogglApi : public QObject { Q_OBJECT Q_PROPERTY(QQmlListProperty countries READ countries NOTIFY countriesChanged) + Q_PROPERTY(QQmlListProperty timeEntries READ timeEntries NOTIFY timeEntriesChanged) public: TogglApi( From 2582333ae12ac6f3ea212d745b44de6ae70edcc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 14:28:54 +0200 Subject: [PATCH 08/83] Make the Timer work almost right (linux) --- src/ui/linux/TogglDesktop/Resources.qrc | 1 + .../linux/TogglDesktop/TimeEntryListView.qml | 27 +------ src/ui/linux/TogglDesktop/TimerView.qml | 76 +++++++++++++++++++ src/ui/linux/TogglDesktop/toggl.cpp | 8 +- src/ui/linux/TogglDesktop/toggl.h | 2 +- 5 files changed, 83 insertions(+), 31 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/TimerView.qml diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index 095ddff025..b69cdd4b19 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -25,5 +25,6 @@ LoginView.qml ErrorOverlay.qml TimeEntryListView.qml + TimerView.qml diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index e245e46934..26dfc92352 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -6,34 +6,11 @@ Item { anchors.fill: parent ColumnLayout { anchors.fill: parent - Rectangle { + TimerView { Layout.fillWidth: true - color: "black" height: 64 - ColumnLayout { - id: timerContainer - height: parent.height - width: parent.width - startButton.width - Text { - Layout.fillWidth: true - Layout.fillHeight: true - verticalAlignment: Text.AlignVCenter - text: "Current TE" - color: "white" - } - } - Rectangle { - id: startButton - width: parent.height - height: parent.height - anchors.left: timerContainer.right - color: "green" - Text { - text: "Start" - anchors.centerIn: parent - } - } } + ListView { id: timeEntryList Layout.fillWidth: true diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml new file mode 100644 index 0000000000..b44acd4d32 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -0,0 +1,76 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Rectangle { + color: "black" + height: 64 + + property bool running: false + property var runningTimeEntry: null + + Connections { + target: toggl + onDisplayStoppedTimerState: { + running = false + runningTimeEntry = null + } + onDisplayRunningTimerState: { + running = true + runningTimeEntry = view + } + } + + RowLayout { + id: timerContainer + height: parent.height + width: parent.width - startButton.width + ColumnLayout { + Layout.fillHeight: true + Layout.fillWidth: true + Text { + visible: running + Layout.fillWidth: true + Layout.fillHeight: true + verticalAlignment: Text.AlignVCenter + text: runningTimeEntry ? runningTimeEntry.Description : "" + color: "white" + } + TextField { + id: description + visible: !running + Layout.fillWidth: true + } + } + Text { + Layout.fillHeight: true + verticalAlignment: Text.AlignVCenter + text: running ? runningTimeEntry.Duration : "00:00" + color: "white" + } + } + Rectangle { + id: startButton + width: parent.height + height: parent.height + anchors.left: timerContainer.right + anchors.leftMargin: 3 + color: running ? "red" : "green" + Text { + text: running ? "Stop" : "Start" + anchors.centerIn: parent + } + MouseArea { + anchors.fill: parent + onClicked: { + if (running) { + toggl.stop() + } + else if (description.text.length > 0) { + toggl.start(description.text, "00:00", 0, 0, "", false) + description.text = "" + } + } + } + } +} diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index 11cdff1039..5ffee8a4e3 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -345,7 +345,6 @@ void TogglApi::importTimeEntries(QVector list) { i->deleteLater(); timeEntries_.clear(); for (auto i : list) { - qDebug() << "Importing" << i->Description; timeEntries_.append(i); } emit timeEntriesChanged(); @@ -595,12 +594,11 @@ QString TogglApi::userEmail() { return res; } -QString TogglApi::start( - const QString description, +QString TogglApi::start(const QString description, const QString duration, const uint64_t task_id, const uint64_t project_id, - const char_t *tags, + const QString tags, const bool_t billable) { char *guid = toggl_start(ctx, description.toStdString().c_str(), @@ -608,7 +606,7 @@ QString TogglApi::start( task_id, project_id, nullptr /* project guid */, - tags /* tags */, + tags.toStdString().c_str() /* tags */, false); QString res(""); if (guid) { diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index 6791bba58c..9b69dcc6ec 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -77,7 +77,7 @@ class TogglApi : public QObject { const QString duration, const uint64_t task_id, const uint64_t project_id, - const char_t *tags, + const QString tags, const bool_t billable); bool stop(); From bb5843407c6c8de433da66f647a5bf2868d7a41a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 14:31:42 +0200 Subject: [PATCH 09/83] Make starting and stopping work mostly as expected --- src/ui/linux/TogglDesktop/TimerView.qml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index b44acd4d32..b16f8d780b 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -9,6 +9,12 @@ Rectangle { property bool running: false property var runningTimeEntry: null + function start() { + if (!running && description.text.length > 0) { + toggl.start(description.text, "", 0, 0, "", false) + } + } + Connections { target: toggl onDisplayStoppedTimerState: { @@ -40,6 +46,7 @@ Rectangle { id: description visible: !running Layout.fillWidth: true + onAccepted: start() } } Text { @@ -66,9 +73,8 @@ Rectangle { if (running) { toggl.stop() } - else if (description.text.length > 0) { - toggl.start(description.text, "00:00", 0, 0, "", false) - description.text = "" + else { + start() } } } From 4a77a4625a1eed55390577f852605071e7a0e4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 14:58:52 +0200 Subject: [PATCH 10/83] Enable editing the duration but it doesn't work right (linux) --- src/ui/linux/TogglDesktop/TimerView.qml | 37 +++++++++++++++++++++---- src/ui/linux/TogglDesktop/toggl.h | 2 +- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index b16f8d780b..a542bf3a9c 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -3,6 +3,7 @@ import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 Rectangle { + id: root color: "black" height: 64 @@ -11,7 +12,9 @@ Rectangle { function start() { if (!running && description.text.length > 0) { - toggl.start(description.text, "", 0, 0, "", false) + var dur = duration.text === "00:00" ? "" : duration.text + toggl.start(description.text, dur, 0, 0, "", false) + description.text = "" } } @@ -44,16 +47,40 @@ Rectangle { } TextField { id: description + focus: true visible: !running Layout.fillWidth: true onAccepted: start() } } - Text { + ColumnLayout { Layout.fillHeight: true - verticalAlignment: Text.AlignVCenter - text: running ? runningTimeEntry.Duration : "00:00" - color: "white" + Text { + visible: running + Layout.fillHeight: true + verticalAlignment: Text.AlignVCenter + text: runningTimeEntry ? runningTimeEntry.Duration : "" + Timer { + running: root.running + interval: 100 + repeat: true + onTriggered: { + parent.text = toggl.formatDurationInSecondsHHMMSS(new Date().getTime() / 1000 - runningTimeEntry.Started) + } + } + + color: "white" + } + TextField { + id: duration + visible: !running + Layout.preferredWidth: 64 + text: "00:00" + onAccepted: start() + validator: RegExpValidator { + regExp: /[0-9][0-9]:[0-9][0-9]/ + } + } } } Rectangle { diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index 9b69dcc6ec..548fee913f 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -232,7 +232,7 @@ class TogglApi : public QObject { bool runScriptFile(const QString filename); - static const QString formatDurationInSecondsHHMMSS( + Q_INVOKABLE static const QString formatDurationInSecondsHHMMSS( const int64_t duration); QRect const getWindowsFrameSetting(); From 7323d6d4cf15360dd3bee892d264c31b6bdf6d2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 15:11:55 +0200 Subject: [PATCH 11/83] Make the timer look prettier (linux) --- .../linux/TogglDesktop/TimeEntryListView.qml | 1 + src/ui/linux/TogglDesktop/TimerView.qml | 36 ++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 26dfc92352..2413449e52 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -6,6 +6,7 @@ Item { anchors.fill: parent ColumnLayout { anchors.fill: parent + spacing: 0 TimerView { Layout.fillWidth: true height: 64 diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index a542bf3a9c..71458c209b 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -33,10 +33,12 @@ Rectangle { RowLayout { id: timerContainer height: parent.height - width: parent.width - startButton.width + width: parent.width - startButton.width - 6 + x: 6 ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true + spacing: 0 Text { visible: running Layout.fillWidth: true @@ -44,6 +46,7 @@ Rectangle { verticalAlignment: Text.AlignVCenter text: runningTimeEntry ? runningTimeEntry.Description : "" color: "white" + font.pointSize: 12 } TextField { id: description @@ -52,6 +55,37 @@ Rectangle { Layout.fillWidth: true onAccepted: start() } + RowLayout { + visible: runningTimeEntry && runningTimeEntry.ProjectLabel.length > 0 + Button { + implicitWidth: 12 + implicitHeight: 12 + text: "x" + } + Text { + text: runningTimeEntry ? runningTimeEntry.ProjectLabel : "" + color: runningTimeEntry && runningTimeEntry.Color.length > 0 ? runningTimeEntry.Color : "white" + font.pointSize: 8 + } + Text { + visible: runningTimeEntry && runningTimeEntry.TaskLabel.length > 0 + text: runningTimeEntry && runningTimeEntry.TaskLabel.length ? "• " + runningTimeEntry.TaskLabel : "" + color: "white" + } + } + RowLayout { + visible: runningTimeEntry && runningTimeEntry.ClientLabel.length > 0 + Button { + implicitWidth: 12 + implicitHeight: 12 + text: "x" + } + Text { + text: runningTimeEntry ? runningTimeEntry.ClientLabel : "" + color: "white" + font.pointSize: 8 + } + } } ColumnLayout { Layout.fillHeight: true From 1d2b1fca2a085777a1ba22bdea08f167f5f71a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 15:36:40 +0200 Subject: [PATCH 12/83] Enable working with the time entry list (linux) --- .../linux/TogglDesktop/TimeEntryListView.qml | 43 ++++++++++++++++--- src/ui/linux/TogglDesktop/timeentryview.h | 2 +- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 2413449e52..6c79f438c3 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -28,17 +28,50 @@ Item { color: "white" RowLayout { anchors.fill: parent - Text { + ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true - text: modelData.Description - verticalAlignment: Text.AlignVCenter + Text { + Layout.fillWidth: true + text: modelData.Description + verticalAlignment: Text.AlignVCenter + } + RowLayout { + Layout.fillWidth: true + Text { + text: modelData.ClientLabel + font.pixelSize: 8 + } + Text { + text: modelData.ProjectLabel + color: modelData.Color + font.pixelSize: 8 + } + Text { + text: modelData.TaskLabel + font.pixelSize: 8 + } + Item { + Layout.fillWidth: true + } + } + } + Button { + visible: modelData.Group + implicitWidth: implicitHeight + text: modelData.GroupItemCount + onClicked: toggl.toggleEntriesGroup(modelData.GroupName) } Button { implicitWidth: implicitHeight - contentItem: Image { - source: "qrc:/images/continue.svg" + contentItem: Text { + anchors.centerIn: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: "▸" } + onClicked: toggl.continueTimeEntry(modelData.GUID) + } } } diff --git a/src/ui/linux/TogglDesktop/timeentryview.h b/src/ui/linux/TogglDesktop/timeentryview.h index e7e758a6af..68def12ca9 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.h +++ b/src/ui/linux/TogglDesktop/timeentryview.h @@ -60,7 +60,7 @@ class TimeEntryView : public QObject { PROPERTY(bool, GroupOpen) PROPERTY(QString, GroupName) PROPERTY(QString, GroupDuration) - PROPERTY(u_int64_t, GroupItemCount) + PROPERTY(quint64, GroupItemCount) }; #endif // SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYVIEW_H_ From 2b16502b43c22ebe03d13c281fb9b0b2f6105f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 15:43:26 +0200 Subject: [PATCH 13/83] Make the timer a lot prettier (linux) --- src/ui/linux/TogglDesktop/TimerView.qml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 71458c209b..3c110f3f4e 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -44,9 +44,9 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true verticalAlignment: Text.AlignVCenter - text: runningTimeEntry ? runningTimeEntry.Description : "" + text: runningTimeEntry ? " " + runningTimeEntry.Description : "" color: "white" - font.pointSize: 12 + font.pixelSize: 12 } TextField { id: description @@ -54,6 +54,11 @@ Rectangle { visible: !running Layout.fillWidth: true onAccepted: start() + background: Item {} + font.pixelSize: 12 + placeholderText: "Placeholder" + placeholderTextColor: "light gray" + color: "white" } RowLayout { visible: runningTimeEntry && runningTimeEntry.ProjectLabel.length > 0 @@ -65,7 +70,7 @@ Rectangle { Text { text: runningTimeEntry ? runningTimeEntry.ProjectLabel : "" color: runningTimeEntry && runningTimeEntry.Color.length > 0 ? runningTimeEntry.Color : "white" - font.pointSize: 8 + font.pixelSize: 8 } Text { visible: runningTimeEntry && runningTimeEntry.TaskLabel.length > 0 @@ -83,7 +88,7 @@ Rectangle { Text { text: runningTimeEntry ? runningTimeEntry.ClientLabel : "" color: "white" - font.pointSize: 8 + font.pixelSize: 8 } } } @@ -109,11 +114,15 @@ Rectangle { id: duration visible: !running Layout.preferredWidth: 64 - text: "00:00" onAccepted: start() validator: RegExpValidator { regExp: /[0-9][0-9]:[0-9][0-9]/ } + font.pixelSize: 12 + background: Item {} + placeholderText: "00:00" + placeholderTextColor: "light gray" + color: "white" } } } From a0c5288004455c6626b0b45dd9245f6d9b7f7257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 15:57:38 +0200 Subject: [PATCH 14/83] Implement a basic menu (linux) --- src/ui/linux/TogglDesktop/CMakeLists.txt | 2 +- src/ui/linux/TogglDesktop/LoginView.qml | 2 +- src/ui/linux/TogglDesktop/MainWindow.qml | 18 +++++++++++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/ui/linux/TogglDesktop/CMakeLists.txt b/src/ui/linux/TogglDesktop/CMakeLists.txt index f222851ce7..0615e09e09 100644 --- a/src/ui/linux/TogglDesktop/CMakeLists.txt +++ b/src/ui/linux/TogglDesktop/CMakeLists.txt @@ -69,7 +69,7 @@ add_executable(TogglDesktop ${BINARY_SOURCE_FILES}) # And list the dependencies between them (and system libraries) target_link_libraries(TogglDesktop PRIVATE TogglDesktopLibrary - Qt5::Widgets Qt5::DBus Qt5::WebEngine + Qt5::Widgets Qt5::DBus Qt5::WebEngine Qt5::Qml Qt5::Quick OAuth2 ${QXT_LIBRARIES} Bugsnag diff --git a/src/ui/linux/TogglDesktop/LoginView.qml b/src/ui/linux/TogglDesktop/LoginView.qml index 4be4e42f65..83b97e2295 100644 --- a/src/ui/linux/TogglDesktop/LoginView.qml +++ b/src/ui/linux/TogglDesktop/LoginView.qml @@ -73,7 +73,7 @@ Item { Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter text: "Sign up" - color: signingUp ? "white;" : "#9a9a9a" + color: signingUp ? "white" : "#9a9a9a" } } diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index 21860b8256..b78eb42aec 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -2,7 +2,9 @@ import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Layouts 1.12 -Window { +import QtQuick.Controls 1.4 + +ApplicationWindow { id: window visible: true minimumWidth: 360 @@ -10,6 +12,20 @@ Window { minimumHeight: 640 height: 640 + menuBar: MenuBar { + Menu { + title: "Toggl Desktop" + MenuItem { + text: "Log out" + onTriggered: toggl.logout() + } + MenuItem { + text: "Exit" + onTriggered: Qt.quit() + } + } + } + Rectangle { anchors.fill: parent color: "#202020" From 2dcf92aae2153f1abb94fe43153f1838b0119625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 17:32:12 +0200 Subject: [PATCH 15/83] Add headers and scrollbars to the items in the list (linux) --- .../linux/TogglDesktop/TimeEntryListView.qml | 105 ++++++++++-------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 6c79f438c3..a20c3835eb 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -12,66 +12,77 @@ Item { height: 64 } - ListView { + ScrollView { id: timeEntryList Layout.fillWidth: true Layout.fillHeight: true - clip: true - model: toggl.timeEntries - delegate: Item { - height: 48 - width: timeEntryList.width - Rectangle { - anchors.fill: parent - anchors.margins: 2 - radius: 2 + property real viewportWidth: width - ScrollBar.vertical.width + ListView { + clip: true + model: toggl.timeEntries + section.property: "DateHeader" + section.delegate: Text { + width: 100 + height: 20 + text: section color: "white" - RowLayout { + } + + delegate: Item { + height: 48 + width: timeEntryList.viewportWidth + Rectangle { anchors.fill: parent - ColumnLayout { - Layout.fillHeight: true - Layout.fillWidth: true - Text { - Layout.fillWidth: true - text: modelData.Description - verticalAlignment: Text.AlignVCenter - } - RowLayout { + anchors.margins: 2 + radius: 2 + color: "white" + RowLayout { + anchors.fill: parent + ColumnLayout { + Layout.fillHeight: true Layout.fillWidth: true Text { - text: modelData.ClientLabel - font.pixelSize: 8 - } - Text { - text: modelData.ProjectLabel - color: modelData.Color - font.pixelSize: 8 - } - Text { - text: modelData.TaskLabel - font.pixelSize: 8 + Layout.fillWidth: true + text: modelData.Description + verticalAlignment: Text.AlignVCenter } - Item { + RowLayout { Layout.fillWidth: true + Text { + text: modelData.ClientLabel + font.pixelSize: 8 + } + Text { + text: modelData.ProjectLabel + color: modelData.Color + font.pixelSize: 8 + } + Text { + text: modelData.TaskLabel + font.pixelSize: 8 + } + Item { + Layout.fillWidth: true + } } } - } - Button { - visible: modelData.Group - implicitWidth: implicitHeight - text: modelData.GroupItemCount - onClicked: toggl.toggleEntriesGroup(modelData.GroupName) - } - Button { - implicitWidth: implicitHeight - contentItem: Text { - anchors.centerIn: parent - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: "▸" + Button { + visible: modelData.Group + implicitWidth: implicitHeight + text: modelData.GroupItemCount + onClicked: toggl.toggleEntriesGroup(modelData.GroupName) } - onClicked: toggl.continueTimeEntry(modelData.GUID) + Button { + implicitWidth: implicitHeight + contentItem: Text { + anchors.centerIn: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: "▸" + } + onClicked: toggl.continueTimeEntry(modelData.GUID) + } } } } From 8d71d0aaad319085ca0a9a38828c4e44fb0ba8ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 14 Oct 2019 18:22:50 +0200 Subject: [PATCH 16/83] Make everything look a bit more similar to the current app (linux) --- src/ui/linux/TogglDesktop/MainWindow.qml | 4 ++ .../linux/TogglDesktop/TimeEntryListView.qml | 37 +++++++++++++------ src/ui/linux/TogglDesktop/TimerView.qml | 9 +++-- src/ui/linux/TogglDesktop/timeentryview.cpp | 2 +- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index b78eb42aec..e0d7b3ae43 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -12,6 +12,10 @@ ApplicationWindow { minimumHeight: 640 height: 640 + SystemPalette { + id: palette + } + menuBar: MenuBar { Menu { title: "Toggl Desktop" diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index a20c3835eb..6660a3aa06 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -2,8 +2,9 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -Item { +Rectangle { anchors.fill: parent + color: palette.window ColumnLayout { anchors.fill: parent spacing: 0 @@ -22,28 +23,32 @@ Item { model: toggl.timeEntries section.property: "DateHeader" section.delegate: Text { - width: 100 - height: 20 - text: section - color: "white" + y: 2 + width: timeEntryList.viewportWidth + height: 30 + textFormat: Text.RichText + // this is actually rendering the width underneath so we force a repaint on each width change + text: " " + section + "
" + width + "
" + color: palette.windowText } delegate: Item { - height: 48 + height: 74 width: timeEntryList.viewportWidth Rectangle { anchors.fill: parent - anchors.margins: 2 - radius: 2 + anchors.margins: 0.5 color: "white" RowLayout { anchors.fill: parent + Item { width: 1 } ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true Text { Layout.fillWidth: true - text: modelData.Description + text: modelData.Description.length > 0 ? modelData.Description : "(no description)" + font.pixelSize: 12 verticalAlignment: Text.AlignVCenter } RowLayout { @@ -69,7 +74,13 @@ Item { Button { visible: modelData.Group implicitWidth: implicitHeight - text: modelData.GroupItemCount + contentItem: Text { + anchors.centerIn: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: modelData.GroupOpen ? "▵" : modelData.GroupItemCount + } + checked: modelData.GroupOpen onClicked: toggl.toggleEntriesGroup(modelData.GroupName) } Button { @@ -81,8 +92,12 @@ Item { text: "▸" } onClicked: toggl.continueTimeEntry(modelData.GUID) - } + Text { + Layout.alignment: Qt.AlignVCenter + text: "00:00:00" + } + Item { width: 1 } } } } diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 3c110f3f4e..ccb60d45f1 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -44,7 +44,7 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true verticalAlignment: Text.AlignVCenter - text: runningTimeEntry ? " " + runningTimeEntry.Description : "" + text: runningTimeEntry && runningTimeEntry.Description.length > 0 ? " " + runningTimeEntry.Description : " (no description)" color: "white" font.pixelSize: 12 } @@ -56,7 +56,7 @@ Rectangle { onAccepted: start() background: Item {} font.pixelSize: 12 - placeholderText: "Placeholder" + placeholderText: "What are you doing?" placeholderTextColor: "light gray" color: "white" } @@ -132,10 +132,13 @@ Rectangle { height: parent.height anchors.left: timerContainer.right anchors.leftMargin: 3 - color: running ? "red" : "green" + color: running ? "#e20000" : "#47bc00" Text { + font.weight: Font.DemiBold text: running ? "Stop" : "Start" anchors.centerIn: parent + font.pixelSize: 12 + color: "white" } MouseArea { anchors.fill: parent diff --git a/src/ui/linux/TogglDesktop/timeentryview.cpp b/src/ui/linux/TogglDesktop/timeentryview.cpp index c94b4f75fa..2e46789be5 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.cpp +++ b/src/ui/linux/TogglDesktop/timeentryview.cpp @@ -28,7 +28,7 @@ TimeEntryView *TimeEntryView::importOne(TogglTimeEntryView *view) { result->StartTimeString = QString(view->StartTimeString); result->EndTimeString = QString(view->EndTimeString); result->UpdatedAt = view->UpdatedAt; - result->DateHeader = QString(view->DateHeader); + result->DateHeader = QString("%1%2").arg(view->DateHeader).arg(view->DateDuration); result->DateDuration = QString(view->DateDuration); result->IsHeader = view->IsHeader; result->CanSeeBillable = view->CanSeeBillable; From d38ce518f5baf4b33de5a8f4819f8a25c4396eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 15 Oct 2019 10:03:42 +0200 Subject: [PATCH 17/83] Force native font rendering (linux) --- src/ui/linux/TogglDesktop/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index 134601fe25..14334e5483 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -101,6 +101,8 @@ int main(int argc, char *argv[]) try { parser.process(a); + qputenv("QML_DISABLE_DISTANCEFIELD", "1"); + QQmlApplicationEngine engine; qmlRegisterType("toggl", 1, 0, "Country"); qmlRegisterType("toggl", 1, 0, "TimeEntry"); From 7fa2a6cee8e4bf1a6ecf1d1443e345721a85fc6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 15 Oct 2019 11:20:07 +0200 Subject: [PATCH 18/83] Handle signup country ID right (linux) --- src/ui/linux/TogglDesktop/LoginView.qml | 8 +++++--- src/ui/linux/TogglDesktop/countryview.h | 15 ++++++++------- src/ui/linux/TogglDesktop/timeentryview.h | 8 +------- src/ui/linux/TogglDesktop/toggl.cpp | 5 +++-- src/ui/linux/TogglDesktop/toggl.h | 6 +++--- 5 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/ui/linux/TogglDesktop/LoginView.qml b/src/ui/linux/TogglDesktop/LoginView.qml index 83b97e2295..b58afd6a86 100644 --- a/src/ui/linux/TogglDesktop/LoginView.qml +++ b/src/ui/linux/TogglDesktop/LoginView.qml @@ -20,9 +20,9 @@ Item { toggl.login(username.text, password.text) else if (signingUp) { if (signupWithEmail.checked) - toggl.signup(username.text, password.text, country.currentIndex) + toggl.signup(username.text, password.text, country.selectedID) else if (signupWithGoogle.checked) - toggl.googleSignup(username.text, password.text, country.currentIndex) + toggl.googleSignup(username.text, password.text, country.selectedID) } } @@ -122,8 +122,10 @@ Item { width: parent.width anchors.horizontalCenter: parent.horizontalCenter visible: signingUp - model: [ "United States", "Estonia", "Czechia" ] + model: toggl.countries + textRole: "Text" currentIndex: -1 + property int selectedID: toggl.countries[currentIndex].ID displayText: currentIndex < 0 ? "Please select your country" : currentText } diff --git a/src/ui/linux/TogglDesktop/countryview.h b/src/ui/linux/TogglDesktop/countryview.h index dbf904523e..92cd94bd6d 100644 --- a/src/ui/linux/TogglDesktop/countryview.h +++ b/src/ui/linux/TogglDesktop/countryview.h @@ -5,6 +5,7 @@ #include #include "./toggl_api.h" +#include "./common.h" class CountryView : public QObject { @@ -31,13 +32,13 @@ class CountryView : public QObject return result; } - uint64_t ID; - QString Text; - QString Name; - bool VatApplicable; - QString VatPercentage; - QString VatRegex; - QString Code; + PROPERTY(uint64_t, ID) + PROPERTY(QString, Text) + PROPERTY(QString, Name) + PROPERTY(bool, VatApplicable) + PROPERTY(QString, VatPercentage) + PROPERTY(QString, VatRegex) + PROPERTY(QString, Code) signals: diff --git a/src/ui/linux/TogglDesktop/timeentryview.h b/src/ui/linux/TogglDesktop/timeentryview.h index 68def12ca9..cbfb9bc33b 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.h +++ b/src/ui/linux/TogglDesktop/timeentryview.h @@ -7,13 +7,7 @@ #include #include "./toggl_api.h" - -#define PROPERTY(type, name) \ -private: \ - Q_PROPERTY(type name READ name ## Get CONSTANT) \ -public: \ - type name; \ - type name ## Get() const { return name; } +#include "./common.h" class TimeEntryView : public QObject { Q_OBJECT diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index 5ffee8a4e3..6ea36301f0 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -65,6 +65,7 @@ void on_display_login( if (open) { TogglApi::instance->aboutToDisplayLogin(); } + TogglApi::instance->getCountries(); TogglApi::instance->displayLogin(open, user_id); Bugsnag::user.id = QString("%1").arg(user_id); } @@ -332,8 +333,8 @@ bool TogglApi::notifyBugsnag( return Bugsnag::notify(errorClass, message, context, &metadata); } -QQmlListProperty TogglApi::countries() { - return QQmlListProperty(this, countries_); +QList TogglApi::countries() { + return countries_; } QQmlListProperty TogglApi::timeEntries() { diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index 548fee913f..c2ed508f6b 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -20,7 +20,7 @@ class CountryView; class TogglApi : public QObject { Q_OBJECT - Q_PROPERTY(QQmlListProperty countries READ countries NOTIFY countriesChanged) + Q_PROPERTY(QList countries READ countries NOTIFY countriesChanged) Q_PROPERTY(QQmlListProperty timeEntries READ timeEntries NOTIFY timeEntriesChanged) public: @@ -43,14 +43,14 @@ class TogglApi : public QObject { bool shutdown; - QQmlListProperty countries(); + QList countries(); QQmlListProperty timeEntries(); void importTimeEntries(QVector list); signals: void countriesChanged(); void timeEntriesChanged(); private: - QList countries_; + QList countries_; QList timeEntries_; public slots: From a4c228cf6057dd13685085c1ed710c19a9636b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 15 Oct 2019 11:21:39 +0200 Subject: [PATCH 19/83] Don't do anything on Google signup (linux) --- src/ui/linux/TogglDesktop/LoginView.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ui/linux/TogglDesktop/LoginView.qml b/src/ui/linux/TogglDesktop/LoginView.qml index b58afd6a86..6978909465 100644 --- a/src/ui/linux/TogglDesktop/LoginView.qml +++ b/src/ui/linux/TogglDesktop/LoginView.qml @@ -21,8 +21,9 @@ Item { else if (signingUp) { if (signupWithEmail.checked) toggl.signup(username.text, password.text, country.selectedID) - else if (signupWithGoogle.checked) - toggl.googleSignup(username.text, password.text, country.selectedID) + else if (signupWithGoogle.checked) { + // don't do anything in this case + } } } From bd2d9b589e62f222fe9afa57ed91f06ffdeccbbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 15 Oct 2019 11:24:26 +0200 Subject: [PATCH 20/83] Clip the error overlay to avoid red flickers (linux) --- src/ui/linux/TogglDesktop/MainWindow.qml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index e0d7b3ae43..577f2662b9 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -33,6 +33,12 @@ ApplicationWindow { Rectangle { anchors.fill: parent color: "#202020" + clip: true + + ErrorOverlay { + width: parent.width + height: 48 + } } TextMetrics { @@ -56,9 +62,4 @@ ApplicationWindow { id: mainView anchors.fill: parent } - - ErrorOverlay { - width: parent.width - height: 48 - } } From 7bf9820de95807bcba19275e54f1954b35c3df9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 15 Oct 2019 12:11:22 +0200 Subject: [PATCH 21/83] Add a readonly Time Entry View (linux) --- src/ui/linux/TogglDesktop/MainWindow.qml | 5 + src/ui/linux/TogglDesktop/Resources.qrc | 1 + .../linux/TogglDesktop/TimeEntryEditView.qml | 111 ++++++++++++++++++ .../linux/TogglDesktop/TimeEntryListView.qml | 9 ++ src/ui/linux/TogglDesktop/common.h | 13 ++ src/ui/linux/TogglDesktop/timeentryview.h | 2 +- 6 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 src/ui/linux/TogglDesktop/TimeEntryEditView.qml create mode 100644 src/ui/linux/TogglDesktop/common.h diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index 577f2662b9..d3ccd87d92 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -56,6 +56,11 @@ ApplicationWindow { if (open) mainView.source = "TimeEntryListView.qml" } + onDisplayTimeEntryEditor: { + if (open) { + mainView.setSource("qrc:/TimeEntryEditView.qml", { timeEntry: view } ) + } + } } Loader { diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index b69cdd4b19..c46d8e1e74 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -26,5 +26,6 @@ ErrorOverlay.qml TimeEntryListView.qml TimerView.qml + TimeEntryEditView.qml diff --git a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml new file mode 100644 index 0000000000..609d105818 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml @@ -0,0 +1,111 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Rectangle { + anchors.fill: parent + color: palette.window + + property var timeEntry: null + + + ColumnLayout { + anchors.fill: parent + anchors.margins: 3 + + TextField { + Layout.fillWidth: true + text: timeEntry.Description + } + TextField { + Layout.fillWidth: true + text: timeEntry.ClientLabel + " . " + timeEntry.ProjectLabel + } + + Button { + Layout.alignment: Qt.AlignRight + text: "Add project" + } + + GridLayout { + Layout.fillWidth: true + columns: 4 + Text { + text: "Duration:" + } + TextField { + Layout.fillWidth: true + Layout.columnSpan: 3 + text: timeEntry.Duration + } + + Text { + text: "Start-end time:" + } + TextField { + Layout.fillWidth: true + text: timeEntry.StartTimeString + } + Text { + text: "-" + } + TextField { + Layout.fillWidth: true + text: timeEntry.EndTimeString + } + + Text { + text: "Date:" + } + TextField { + Layout.fillWidth: true + Layout.columnSpan: 3 + text: new Date(Date(timeEntry.Started)).toLocaleDateString(Qt.locale(), Locale.ShortFormat) + } + + Text { + text: "Tags" + Layout.fillHeight: true + } + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.columnSpan: 3 + Frame { + anchors.fill: parent + background: Rectangle { + color: palette.base + border.color: palette.dark + border.width: 1 + } + } + } + + Text { + text: "Workspace:" + } + Label { + Layout.fillWidth: true + Layout.columnSpan: 3 + text: timeEntry.WorkspaceName + } + } + RowLayout { + Button { + text: "Done" + onClicked: toggl.viewTimeEntryList() + } + Button { + text: "Delete" + onClicked: toggl.deleteTimeEntry(timeEntry.GUID) + } + Button { + text: "Cancel" + onClicked: toggl.viewTimeEntryList() + } + } + Text { + text: timeEntry.lastUpdate() + } + } +} diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 6660a3aa06..348294ce29 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -39,6 +39,15 @@ Rectangle { anchors.fill: parent anchors.margins: 0.5 color: "white" + MouseArea { + anchors.fill: parent + onClicked: { + if (modelData.Group) + toggl.toggleEntriesGroup(modelData.GroupName) + else + toggl.editTimeEntry(modelData.GUID, "description") + } + } RowLayout { anchors.fill: parent Item { width: 1 } diff --git a/src/ui/linux/TogglDesktop/common.h b/src/ui/linux/TogglDesktop/common.h new file mode 100644 index 0000000000..603eae2970 --- /dev/null +++ b/src/ui/linux/TogglDesktop/common.h @@ -0,0 +1,13 @@ +// Copyright 2019 Toggl Desktop developers. + +#ifndef SRC_COMMON_H_ +#define SRC_COMMON_H_ + +#define PROPERTY(type, name) \ +private: \ + Q_PROPERTY(type name READ name ## Get CONSTANT) \ +public: \ + type name; \ + type name ## Get() const { return name; } + +#endif // SRC_COMMON_H_ diff --git a/src/ui/linux/TogglDesktop/timeentryview.h b/src/ui/linux/TogglDesktop/timeentryview.h index cbfb9bc33b..1bf2b64707 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.h +++ b/src/ui/linux/TogglDesktop/timeentryview.h @@ -19,7 +19,7 @@ class TimeEntryView : public QObject { static QVector importAll(TogglTimeEntryView *first); bool confirmlessDelete(); - const QString lastUpdate(); + Q_INVOKABLE const QString lastUpdate(); PROPERTY(int64_t, DurationInSeconds) PROPERTY(QString, Description) From 0e4a7d0d20ef90f72d919cebbc9b5aef005316c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 15 Oct 2019 13:53:40 +0200 Subject: [PATCH 22/83] Basic nonfunctional autocomplete implementation (linux) --- .../linux/TogglDesktop/AutocompleteView.qml | 79 +++++++++++++++++++ src/ui/linux/TogglDesktop/Resources.qrc | 1 + .../linux/TogglDesktop/TimeEntryListView.qml | 1 + src/ui/linux/TogglDesktop/TimerView.qml | 10 +++ .../TogglDesktop/autocompletelistmodel.h | 2 +- src/ui/linux/TogglDesktop/autocompleteview.h | 58 +++++++------- src/ui/linux/TogglDesktop/main.cpp | 1 + .../TogglDesktop/timeentryeditorwidget.cpp | 2 +- src/ui/linux/TogglDesktop/toggl.cpp | 51 +++++++++--- src/ui/linux/TogglDesktop/toggl.h | 25 +++--- 10 files changed, 179 insertions(+), 51 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/AutocompleteView.qml diff --git a/src/ui/linux/TogglDesktop/AutocompleteView.qml b/src/ui/linux/TogglDesktop/AutocompleteView.qml new file mode 100644 index 0000000000..2bddae57f1 --- /dev/null +++ b/src/ui/linux/TogglDesktop/AutocompleteView.qml @@ -0,0 +1,79 @@ +import QtQuick 2.12 +import QtQml.Models 2.12 + +Rectangle { + id: root + color: palette.base + height: 500 + + property string filter: "" + + ListView { + anchors.fill: parent + clip: true + model: toggl.minitimerAutocomplete + delegate: Loader { + // TODO use the enum instead of magic values + sourceComponent: autocompleteData.Type === 13 ? workspaceDelegate : + autocompleteData.Type === 12 ? clientDelegate : + autocompleteData.Type === 11 ? headerDelegate : + regularDelegate + property var autocompleteData: modelData + + visible: autocompleteData.Description.includes(root.filter) + } + } + + Component { + id: workspaceDelegate + + Text { + height: 24 + width: root.width + font.bold: true + horizontalAlignment: Text.AlignHCenter + color: "#9e9e9e" + + text: autocompleteData.Description + } + } + + Component { + id: clientDelegate + + Text { + height: 24 + x: 9 + color: "#9e9e9e" + + text: autocompleteData.Description + } + } + + Component { + id: headerDelegate + + Text { + height: 24 + text: autocompleteData.Description + color: "#9e9e9e" + } + } + + Component { + id: regularDelegate + + Text { + height: 24 + x: 9 + textFormat: Text.RichText + property string timeEntry: autocompleteData.Description + property string project: autocompleteData.ProjectLabel.length > 0 ? + " • " + autocompleteData.ProjectLabel + "" : + "" + property string task: autocompleteData.TaskLabel.length > 0 ? " " + autocompleteData.TaskLabel : "" + property string client: autocompleteData.ClientLabel.length > 0 ? " " + autocompleteData.ClientLabel : "" + text: timeEntry + project + task + client + } + } +} diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index c46d8e1e74..b307da25dd 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -27,5 +27,6 @@ TimeEntryListView.qml TimerView.qml TimeEntryEditView.qml + AutocompleteView.qml diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 348294ce29..f0e3dca0c5 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -11,6 +11,7 @@ Rectangle { TimerView { Layout.fillWidth: true height: 64 + z: 1 } ScrollView { diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index ccb60d45f1..5db791180b 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -59,6 +59,16 @@ Rectangle { placeholderText: "What are you doing?" placeholderTextColor: "light gray" color: "white" + + AutocompleteView { + id: autocomplete + anchors{ + top: parent.bottom + left: parent.left + right: parent.right + } + filter: description.text + } } RowLayout { visible: runningTimeEntry && runningTimeEntry.ProjectLabel.length > 0 diff --git a/src/ui/linux/TogglDesktop/autocompletelistmodel.h b/src/ui/linux/TogglDesktop/autocompletelistmodel.h index 9f289c3b0e..f460753d35 100644 --- a/src/ui/linux/TogglDesktop/autocompletelistmodel.h +++ b/src/ui/linux/TogglDesktop/autocompletelistmodel.h @@ -10,7 +10,7 @@ class AutocompleteListModel : public QAbstractListModel { Q_OBJECT public: - AutocompleteListModel(QObject *parent = nullptr, QVector list = {}, uint64_t displayItem = AC_TIME_ENTRY); + AutocompleteListModel(QObject *parent = nullptr, QVector list = {}, uint64_t displayItem = AutocompleteView::AC_TIME_ENTRY); void setList(QVector autocompletelist); Qt::ItemFlags flags(const QModelIndex &index) const override; diff --git a/src/ui/linux/TogglDesktop/autocompleteview.h b/src/ui/linux/TogglDesktop/autocompleteview.h index 38da90d03f..f25a71a589 100644 --- a/src/ui/linux/TogglDesktop/autocompleteview.h +++ b/src/ui/linux/TogglDesktop/autocompleteview.h @@ -8,15 +8,7 @@ #include #include "./toggl_api.h" - -enum AutocompleteType { - AC_TIME_ENTRY = 0, - AC_TASK = 1, - AC_PROJECT = 2, - AC_HEADER = 11, - AC_CLIENT = 12, - AC_WORKSPACE = 13 -}; +#include "./common.h" class AutocompleteView : public QObject { Q_OBJECT @@ -24,6 +16,16 @@ class AutocompleteView : public QObject { public: explicit AutocompleteView(QObject *parent = 0); + enum AutocompleteType { + AC_TIME_ENTRY = 0, + AC_TASK = 1, + AC_PROJECT = 2, + AC_HEADER = 11, + AC_CLIENT = 12, + AC_WORKSPACE = 13 + }; + Q_ENUMS(AutocompleteType) + static QVector importAll( TogglAutocompleteView *first) { QVector result; @@ -50,13 +52,13 @@ class AutocompleteView : public QObject { view->ProjectID = it->ProjectID; view->WorkspaceID = it->WorkspaceID; view->WorkspaceName = QString(it->WorkspaceName); - view->Type = it->Type; + view->Type = AutocompleteType(it->Type); view->Billable = it->Billable; view->Tags = QString(it->Tags); if (!currentWorkspace || currentWorkspace->Description != view->WorkspaceName) { currentWorkspace = new AutocompleteView(); - currentWorkspace->Type = 13; + currentWorkspace->Type = AC_WORKSPACE; currentWorkspace->Description = view->WorkspaceName; result.push_back(currentWorkspace); currentLevel1Header = nullptr; @@ -74,7 +76,7 @@ class AutocompleteView : public QObject { currentLevel3Header = nullptr; currentLevel2Header = nullptr; currentLevel1Header = new AutocompleteView(); - currentLevel1Header->Type = 11; + currentLevel1Header->Type = AC_HEADER; switch (view->Type) { case 0: currentLevel1Header->Description = "TIME ENTRIES"; @@ -96,7 +98,7 @@ class AutocompleteView : public QObject { if (view->Type == 2 && (!currentLevel2Header || currentLevel2Header->ClientLabel != view->ClientLabel)) { currentLevel3Header = nullptr; currentLevel2Header = new AutocompleteView(); - currentLevel2Header->Type = 12; + currentLevel2Header->Type = AC_CLIENT; currentLevel2Header->ClientLabel = view->ClientLabel; if (view->ClientLabel.isEmpty()) currentLevel2Header->Description = "No client"; @@ -128,21 +130,21 @@ class AutocompleteView : public QObject { return result; } - QString Text {}; - QString Description {}; - QString ProjectAndTaskLabel {}; - QString ProjectLabel {}; - QString ClientLabel {}; - QString TaskLabel {}; - QString WorkspaceName {}; - QString ProjectColor {}; - uint64_t TaskID { 0 }; - uint64_t ProjectID { 0 }; - uint64_t ClientID { 0 }; - uint64_t WorkspaceID { 0 }; - uint64_t Type { 0 }; - bool Billable { false }; - QString Tags {}; + PROPERTY(QString, Text) + PROPERTY(QString, Description) + PROPERTY(QString, ProjectAndTaskLabel) + PROPERTY(QString, ProjectLabel) + PROPERTY(QString, ClientLabel) + PROPERTY(QString, TaskLabel) + PROPERTY(QString, WorkspaceName) + PROPERTY(QString, ProjectColor) + PROPERTY(uint64_t, TaskID) + PROPERTY(uint64_t, ProjectID) + PROPERTY(uint64_t, ClientID) + PROPERTY(uint64_t, WorkspaceID) + PROPERTY(AutocompleteType, Type) + PROPERTY(bool, Billable) + PROPERTY(QString, Tags) QList _Children; }; diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index 14334e5483..b5d4a85940 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -106,6 +106,7 @@ int main(int argc, char *argv[]) try { QQmlApplicationEngine engine; qmlRegisterType("toggl", 1, 0, "Country"); qmlRegisterType("toggl", 1, 0, "TimeEntry"); + qmlRegisterType("toggl", 1, 0, "Autocomplete"); engine.rootContext()->setContextProperty("toggl", new TogglApi(nullptr)); engine.load(QUrl(QStringLiteral("qrc:/MainWindow.qml"))); if (!TogglApi::instance->startEvents()) { diff --git a/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp b/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp index 43d0e7151c..947dffa7a4 100644 --- a/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp +++ b/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp @@ -29,7 +29,7 @@ timer(new QTimer(this)), duration(0), previousTagList(""), descriptionModel(new AutocompleteListModel(this, QVector())), -projectModel(new AutocompleteListModel(this, QVector(), AC_PROJECT)) +projectModel(new AutocompleteListModel(this, QVector(), AutocompleteView::AC_PROJECT)) { ui->setupUi(this); diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index 6ea36301f0..cf198d9f59 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -210,16 +210,36 @@ void on_countries( CountryView::importAll(first)); } -void TogglApi::setCountries(QVector list) { - for (auto i : countries_) +template +void replaceList(const QVector &from, QList &to) { + for (auto i : to) i->deleteLater(); - countries_.clear(); - for (auto i : list) { - countries_.append(i); - } + to.clear(); + for (auto i : from) + to.append(i); +} + +void TogglApi::setCountries(QVector list) { + replaceList(list, countries_); emit countriesChanged(); } +void TogglApi::displayTimeEntryAutocomplete(QVector list) { + replaceList(list, timeEntryAutocomplete_); + emit timeEntryAutocompleteChanged(); +} + +void TogglApi::displayMinitimerAutocomplete(QVector list) { + replaceList(list, minitimerAutocomplete_); + emit minitimerAutocompleteChanged(); + +} + +void TogglApi::displayProjectAutocomplete(QVector list) { + replaceList(list, projectAutocomplete_); + emit projectAutocompleteChanged(); +} + TogglApi::TogglApi( QObject *parent, QString logPathOverride, @@ -337,17 +357,24 @@ QList TogglApi::countries() { return countries_; } +QList TogglApi::timeEntryAutocomplete() { + return timeEntryAutocomplete_; +} + +QList TogglApi::minitimerAutocomplete() { + return minitimerAutocomplete_; +} + +QList TogglApi::projectAutocomplete() { + return projectAutocomplete_; +} + QQmlListProperty TogglApi::timeEntries() { return QQmlListProperty(this, timeEntries_); } void TogglApi::importTimeEntries(QVector list) { - for (auto i : timeEntries_) - i->deleteLater(); - timeEntries_.clear(); - for (auto i : list) { - timeEntries_.append(i); - } + replaceList(list, timeEntries_); emit timeEntriesChanged(); } diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index c2ed508f6b..5c61948f98 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -21,6 +21,9 @@ class CountryView; class TogglApi : public QObject { Q_OBJECT Q_PROPERTY(QList countries READ countries NOTIFY countriesChanged) + Q_PROPERTY(QList timeEntryAutocomplete READ timeEntryAutocomplete NOTIFY timeEntryAutocompleteChanged) + Q_PROPERTY(QList minitimerAutocomplete READ minitimerAutocomplete NOTIFY minitimerAutocompleteChanged) + Q_PROPERTY(QList projectAutocomplete READ projectAutocomplete NOTIFY projectAutocompleteChanged) Q_PROPERTY(QQmlListProperty timeEntries READ timeEntries NOTIFY timeEntriesChanged) public: @@ -44,18 +47,31 @@ class TogglApi : public QObject { bool shutdown; QList countries(); + QList timeEntryAutocomplete(); + QList minitimerAutocomplete(); + QList projectAutocomplete(); QQmlListProperty timeEntries(); void importTimeEntries(QVector list); signals: void countriesChanged(); + void timeEntryAutocompleteChanged(); + void minitimerAutocompleteChanged(); + void projectAutocompleteChanged(); void timeEntriesChanged(); private: QList countries_; + QList timeEntryAutocomplete_; + QList minitimerAutocomplete_; + QList projectAutocomplete_; QList timeEntries_; public slots: void setCountries(QVector list); + void displayTimeEntryAutocomplete(QVector list); + void displayMinitimerAutocomplete(QVector list); + void displayProjectAutocomplete(QVector list); + /////////////////////////////////////////////////////// bool startEvents(); @@ -307,15 +323,6 @@ class TogglApi : public QObject { void displayTags( QVector list); - void displayTimeEntryAutocomplete( - QVector list); - - void displayMinitimerAutocomplete( - QVector list); - - void displayProjectAutocomplete( - QVector list); - void displayWorkspaceSelect( QVector list); From 47f7e46b60b686a5684fb75f98518e90d18031b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 15 Oct 2019 14:13:22 +0200 Subject: [PATCH 23/83] Reuse the old AutocompleteListModel class to implement filtering (linux) --- .../linux/TogglDesktop/AutocompleteView.qml | 5 +-- .../TogglDesktop/autocompletecombobox.cpp | 4 +++ .../linux/TogglDesktop/autocompletecombobox.h | 2 ++ .../TogglDesktop/autocompletelistmodel.cpp | 4 +++ .../TogglDesktop/autocompletelistmodel.h | 2 ++ src/ui/linux/TogglDesktop/toggl.cpp | 32 ++++++++++++------- src/ui/linux/TogglDesktop/toggl.h | 23 +++++++------ 7 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/ui/linux/TogglDesktop/AutocompleteView.qml b/src/ui/linux/TogglDesktop/AutocompleteView.qml index 2bddae57f1..c111212972 100644 --- a/src/ui/linux/TogglDesktop/AutocompleteView.qml +++ b/src/ui/linux/TogglDesktop/AutocompleteView.qml @@ -7,6 +7,9 @@ Rectangle { height: 500 property string filter: "" + onFilterChanged: { + toggl.minitimerAutocomplete.setFilter(filter) + } ListView { anchors.fill: parent @@ -19,8 +22,6 @@ Rectangle { autocompleteData.Type === 11 ? headerDelegate : regularDelegate property var autocompleteData: modelData - - visible: autocompleteData.Description.includes(root.filter) } } diff --git a/src/ui/linux/TogglDesktop/autocompletecombobox.cpp b/src/ui/linux/TogglDesktop/autocompletecombobox.cpp index 07994437ff..5054081094 100644 --- a/src/ui/linux/TogglDesktop/autocompletecombobox.cpp +++ b/src/ui/linux/TogglDesktop/autocompletecombobox.cpp @@ -159,6 +159,10 @@ AutocompleteProxyModel::AutocompleteProxyModel(QObject *parent) setFilterRole(Qt::UserRole); } +void AutocompleteProxyModel::setFilter(const QString &filter) { + setFilterRegExp(filter); +} + bool AutocompleteProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { QString input = filterRegExp().pattern(); QStringList words = input.split(" "); diff --git a/src/ui/linux/TogglDesktop/autocompletecombobox.h b/src/ui/linux/TogglDesktop/autocompletecombobox.h index 1cb60cea1a..708125301f 100644 --- a/src/ui/linux/TogglDesktop/autocompletecombobox.h +++ b/src/ui/linux/TogglDesktop/autocompletecombobox.h @@ -64,6 +64,8 @@ class AutocompleteProxyModel : public QSortFilterProxyModel { public: AutocompleteProxyModel(QObject *parent = nullptr); + Q_INVOKABLE void setFilter(const QString &filter); + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; }; diff --git a/src/ui/linux/TogglDesktop/autocompletelistmodel.cpp b/src/ui/linux/TogglDesktop/autocompletelistmodel.cpp index afe46014cd..f1856d72cf 100644 --- a/src/ui/linux/TogglDesktop/autocompletelistmodel.cpp +++ b/src/ui/linux/TogglDesktop/autocompletelistmodel.cpp @@ -33,6 +33,10 @@ Qt::ItemFlags AutocompleteListModel::flags(const QModelIndex &index) const { } } +QHash AutocompleteListModel::roleNames() const { + return { { Qt::UserRole, "modelData" } }; +} + int AutocompleteListModel::rowCount(const QModelIndex &parent) const { return list.count(); } diff --git a/src/ui/linux/TogglDesktop/autocompletelistmodel.h b/src/ui/linux/TogglDesktop/autocompletelistmodel.h index f460753d35..57a4cd6198 100644 --- a/src/ui/linux/TogglDesktop/autocompletelistmodel.h +++ b/src/ui/linux/TogglDesktop/autocompletelistmodel.h @@ -15,6 +15,8 @@ class AutocompleteListModel : public QAbstractListModel Qt::ItemFlags flags(const QModelIndex &index) const override; + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index cf198d9f59..16908c820e 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -225,28 +225,32 @@ void TogglApi::setCountries(QVector list) { } void TogglApi::displayTimeEntryAutocomplete(QVector list) { - replaceList(list, timeEntryAutocomplete_); + timeEntryModel_->setList(list); emit timeEntryAutocompleteChanged(); } void TogglApi::displayMinitimerAutocomplete(QVector list) { - replaceList(list, minitimerAutocomplete_); + minitimerModel_->setList(list); emit minitimerAutocompleteChanged(); } void TogglApi::displayProjectAutocomplete(QVector list) { - replaceList(list, projectAutocomplete_); + projectModel_->setList(list); emit projectAutocompleteChanged(); } -TogglApi::TogglApi( - QObject *parent, - QString logPathOverride, - QString dbPathOverride) +TogglApi::TogglApi(QObject *parent, QString logPathOverride, QString dbPathOverride) : QObject(parent) -, shutdown(false) -, ctx(nullptr) { + , shutdown(false) + , ctx(nullptr) + , timeEntryModel_(new AutocompleteListModel(this)) + , minitimerModel_(new AutocompleteListModel(this)) + , projectModel_(new AutocompleteListModel(this, {}, AutocompleteView::AC_PROJECT)) + , timeEntryAutocomplete_(new AutocompleteProxyModel(this)) + , minitimerAutocomplete_(new AutocompleteProxyModel(this)) + , projectAutocomplete_(new AutocompleteProxyModel(this)) +{ QString version = QApplication::applicationVersion(); ctx = toggl_context_init("linux_native_app", version.toStdString().c_str()); @@ -316,6 +320,10 @@ TogglApi::TogglApi( toggl_on_project_colors(ctx, on_project_colors); toggl_on_countries(ctx, on_countries); + timeEntryAutocomplete_->setSourceModel(timeEntryModel_); + minitimerAutocomplete_->setSourceModel(minitimerModel_); + projectAutocomplete_->setSourceModel(projectModel_); + char *env = toggl_environment(ctx); if (env) { Bugsnag::releaseStage = QString(env); @@ -357,15 +365,15 @@ QList TogglApi::countries() { return countries_; } -QList TogglApi::timeEntryAutocomplete() { +AutocompleteProxyModel *TogglApi::timeEntryAutocomplete() { return timeEntryAutocomplete_; } -QList TogglApi::minitimerAutocomplete() { +AutocompleteProxyModel *TogglApi::minitimerAutocomplete() { return minitimerAutocomplete_; } -QList TogglApi::projectAutocomplete() { +AutocompleteProxyModel *TogglApi::projectAutocomplete() { return projectAutocomplete_; } diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index 5c61948f98..ffacb5ae07 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -11,6 +11,8 @@ #include #include "./toggl_api.h" +#include "./autocompletelistmodel.h" +#include "./autocompletecombobox.h" class AutocompleteView; class GenericView; @@ -21,9 +23,9 @@ class CountryView; class TogglApi : public QObject { Q_OBJECT Q_PROPERTY(QList countries READ countries NOTIFY countriesChanged) - Q_PROPERTY(QList timeEntryAutocomplete READ timeEntryAutocomplete NOTIFY timeEntryAutocompleteChanged) - Q_PROPERTY(QList minitimerAutocomplete READ minitimerAutocomplete NOTIFY minitimerAutocompleteChanged) - Q_PROPERTY(QList projectAutocomplete READ projectAutocomplete NOTIFY projectAutocompleteChanged) + Q_PROPERTY(AutocompleteProxyModel* timeEntryAutocomplete READ timeEntryAutocomplete CONSTANT) + Q_PROPERTY(AutocompleteProxyModel* minitimerAutocomplete READ minitimerAutocomplete CONSTANT) + Q_PROPERTY(AutocompleteProxyModel* projectAutocomplete READ projectAutocomplete CONSTANT) Q_PROPERTY(QQmlListProperty timeEntries READ timeEntries NOTIFY timeEntriesChanged) public: @@ -47,9 +49,9 @@ class TogglApi : public QObject { bool shutdown; QList countries(); - QList timeEntryAutocomplete(); - QList minitimerAutocomplete(); - QList projectAutocomplete(); + AutocompleteProxyModel *timeEntryAutocomplete(); + AutocompleteProxyModel *minitimerAutocomplete(); + AutocompleteProxyModel *projectAutocomplete(); QQmlListProperty timeEntries(); void importTimeEntries(QVector list); signals: @@ -60,9 +62,12 @@ class TogglApi : public QObject { void timeEntriesChanged(); private: QList countries_; - QList timeEntryAutocomplete_; - QList minitimerAutocomplete_; - QList projectAutocomplete_; + AutocompleteListModel *timeEntryModel_; + AutocompleteListModel *minitimerModel_; + AutocompleteListModel *projectModel_; + AutocompleteProxyModel *timeEntryAutocomplete_; + AutocompleteProxyModel *minitimerAutocomplete_; + AutocompleteProxyModel *projectAutocomplete_; QList timeEntries_; public slots: From 7aace13f42d36e1ac65cd341b2461216451bea22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 15 Oct 2019 15:16:39 +0200 Subject: [PATCH 24/83] Meh autocomplete is broken this way, i'll have to rewrite it but i don't feel like doing that today (linux) --- .../linux/TogglDesktop/AutocompleteView.qml | 115 ++++++++++++++---- src/ui/linux/TogglDesktop/TimerView.qml | 24 +++- .../TogglDesktop/autocompletecombobox.cpp | 13 +- .../linux/TogglDesktop/autocompletecombobox.h | 1 + 4 files changed, 128 insertions(+), 25 deletions(-) diff --git a/src/ui/linux/TogglDesktop/AutocompleteView.qml b/src/ui/linux/TogglDesktop/AutocompleteView.qml index c111212972..2714ca96ee 100644 --- a/src/ui/linux/TogglDesktop/AutocompleteView.qml +++ b/src/ui/linux/TogglDesktop/AutocompleteView.qml @@ -1,27 +1,83 @@ import QtQuick 2.12 -import QtQml.Models 2.12 +import QtQuick.Layouts 1.12 -Rectangle { +Item { id: root - color: palette.base height: 500 + property real maximumHeight: 500 + + property var model: null + property var currentItem: null + property string filter: "" onFilterChanged: { toggl.minitimerAutocomplete.setFilter(filter) + list.currentIndex = -1 + } + onVisibleChanged: list.currentIndex = -1 + + function upPressed() { + for (var i = list.currentIndex - 1; i > 0 ; --i) { + if (model.get(i).Type < 10) { + list.currentIndex = i + list.positionViewAtIndex(i, ListView.Center) + return + } + } + } + function downPressed() { + for (var i = list.currentIndex + 1; i < list.count; i++) { + if (model.get(i).Type < 10) { + list.currentIndex = i + list.positionViewAtIndex(i, ListView.Center) + return + } + } + } + + Rectangle { + anchors.fill: list + anchors.margins: -1 + color: palette.dark } ListView { - anchors.fill: parent + id: list + width: parent.width + height: root.maximumHeight clip: true - model: toggl.minitimerAutocomplete - delegate: Loader { - // TODO use the enum instead of magic values - sourceComponent: autocompleteData.Type === 13 ? workspaceDelegate : - autocompleteData.Type === 12 ? clientDelegate : - autocompleteData.Type === 11 ? headerDelegate : - regularDelegate - property var autocompleteData: modelData + model: root.model + currentIndex: -1 + onCurrentIndexChanged: { + root.currentItem = model.get(currentIndex) + } + onCountChanged: { + var base = list.visibleChildren[0] + var listViewHeight = 0 + for (var i = 0; i < base.visibleChildren.length; i++) + listViewHeight += base.visibleChildren[i].height + list.height = Math.min(listViewHeight, root.maximumHeight) + } + highlight: Rectangle { + color: "red" + width: root.width + } + highlightMoveDuration: 100 + delegate: Rectangle { + width: root.width + height: 24 + color: palette.base + property bool selectable: modelData.Type < 10 + Loader { + // TODO use the enum instead of magic values + sourceComponent: autocompleteData.Type === 13 ? workspaceDelegate : + autocompleteData.Type === 12 ? clientDelegate : + autocompleteData.Type === 11 ? headerDelegate : + regularDelegate + property var autocompleteData: modelData + Component.onCompleted: console.log(index) + } } } @@ -64,17 +120,32 @@ Rectangle { Component { id: regularDelegate - Text { + Rectangle { height: 24 - x: 9 - textFormat: Text.RichText - property string timeEntry: autocompleteData.Description - property string project: autocompleteData.ProjectLabel.length > 0 ? - " • " + autocompleteData.ProjectLabel + "" : - "" - property string task: autocompleteData.TaskLabel.length > 0 ? " " + autocompleteData.TaskLabel : "" - property string client: autocompleteData.ClientLabel.length > 0 ? " " + autocompleteData.ClientLabel : "" - text: timeEntry + project + task + client + width: root.width + Text { + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + x: 9 + textFormat: Text.RichText + property string timeEntry: autocompleteData.Description + property string project: autocompleteData.ProjectLabel.length > 0 ? + " • " + autocompleteData.ProjectLabel + "" : + "" + property string task: autocompleteData.TaskLabel.length > 0 ? " " + autocompleteData.TaskLabel : "" + property string client: autocompleteData.ClientLabel.length > 0 ? " " + autocompleteData.ClientLabel : "" + text: timeEntry + project + task + client + } + MouseArea { + id: delegateMouse + anchors.fill: parent + hoverEnabled: true + onContainsMouseChanged: { + if (containsMouse) { + ListView.isCurrentItem = true + } + } + } } } } diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 5db791180b..66e6aadb43 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -53,21 +53,41 @@ Rectangle { focus: true visible: !running Layout.fillWidth: true - onAccepted: start() background: Item {} font.pixelSize: 12 placeholderText: "What are you doing?" placeholderTextColor: "light gray" color: "white" - + Keys.onUpPressed: autocomplete.upPressed() + Keys.onDownPressed: autocomplete.downPressed() + Keys.onEscapePressed: autocomplete.visible = false + Keys.onReturnPressed: { + if (autocomplete.visible && autocomplete.currentItem) { + console.log("something") + autocomplete.visible = false + } + else { + if (acceptableInput) { + start() + } + } + } + onTextEdited: { + if (focus) { + autocomplete.visible = true + } + } + onFocusChanged: if (!focus) autocomplete.visible = false AutocompleteView { id: autocomplete + visible: false anchors{ top: parent.bottom left: parent.left right: parent.right } filter: description.text + model: toggl.minitimerAutocomplete } } RowLayout { diff --git a/src/ui/linux/TogglDesktop/autocompletecombobox.cpp b/src/ui/linux/TogglDesktop/autocompletecombobox.cpp index 5054081094..748f3a11f9 100644 --- a/src/ui/linux/TogglDesktop/autocompletecombobox.cpp +++ b/src/ui/linux/TogglDesktop/autocompletecombobox.cpp @@ -163,11 +163,22 @@ void AutocompleteProxyModel::setFilter(const QString &filter) { setFilterRegExp(filter); } +AutocompleteView *AutocompleteProxyModel::get(int idx) { + if (idx >= 0 && idx < rowCount()) + return qvariant_cast(data(index(idx, 0), Qt::UserRole)); + return nullptr; +} + bool AutocompleteProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { QString input = filterRegExp().pattern(); QStringList words = input.split(" "); - auto view = qvariant_cast(sourceModel()->data(sourceModel()->index(source_row, 0), Qt::UserRole)); + auto variant = sourceModel()->data(sourceModel()->index(source_row, 0), Qt::UserRole); + if (!variant.isValid()) + return false; + auto view = qvariant_cast(variant); + if (!view) + return false; for (auto word : words) { if (word.isEmpty() && words.count() > 1) continue; diff --git a/src/ui/linux/TogglDesktop/autocompletecombobox.h b/src/ui/linux/TogglDesktop/autocompletecombobox.h index 708125301f..24a374654e 100644 --- a/src/ui/linux/TogglDesktop/autocompletecombobox.h +++ b/src/ui/linux/TogglDesktop/autocompletecombobox.h @@ -65,6 +65,7 @@ class AutocompleteProxyModel : public QSortFilterProxyModel { AutocompleteProxyModel(QObject *parent = nullptr); Q_INVOKABLE void setFilter(const QString &filter); + Q_INVOKABLE AutocompleteView *get(int idx); bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; }; From 97d15bf972c7ea8f6df2bf3446a27e1f08281560 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 15 Oct 2019 16:04:49 +0200 Subject: [PATCH 25/83] Some lame attempts at animating the time entry list (linux) --- src/ui/linux/TogglDesktop/CMakeLists.txt | 1 + .../linux/TogglDesktop/TimeEntryListView.qml | 14 +++ src/ui/linux/TogglDesktop/TimerView.qml | 4 +- src/ui/linux/TogglDesktop/qmlobjectlist.cc | 93 +++++++++++++++++++ src/ui/linux/TogglDesktop/qmlobjectlist.h | 73 +++++++++++++++ src/ui/linux/TogglDesktop/timeentryview.cpp | 39 ++++++++ src/ui/linux/TogglDesktop/timeentryview.h | 3 + src/ui/linux/TogglDesktop/toggl.cpp | 42 ++++++++- src/ui/linux/TogglDesktop/toggl.h | 8 +- 9 files changed, 267 insertions(+), 10 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/qmlobjectlist.cc create mode 100644 src/ui/linux/TogglDesktop/qmlobjectlist.h diff --git a/src/ui/linux/TogglDesktop/CMakeLists.txt b/src/ui/linux/TogglDesktop/CMakeLists.txt index 0615e09e09..811aad7e53 100644 --- a/src/ui/linux/TogglDesktop/CMakeLists.txt +++ b/src/ui/linux/TogglDesktop/CMakeLists.txt @@ -34,6 +34,7 @@ set(BINARY_SOURCE_FILES overlaywidget.cpp powermanagement.cpp preferencesdialog.cpp + qmlobjectlist.cc settingsview.cpp singleapplication.cpp systemtray.cpp diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index f0e3dca0c5..8af8f0827c 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -33,6 +33,20 @@ Rectangle { color: palette.windowText } + add: Transition { + NumberAnimation { + properties: "opacity" + from: 0 + to: 1 + } + } + + moveDisplaced: Transition { + NumberAnimation { + + } + } + delegate: Item { height: 74 width: timeEntryList.viewportWidth diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 66e6aadb43..88ec1139e0 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -74,7 +74,7 @@ Rectangle { } onTextEdited: { if (focus) { - autocomplete.visible = true + //autocomplete.visible = true } } onFocusChanged: if (!focus) autocomplete.visible = false @@ -87,7 +87,7 @@ Rectangle { right: parent.right } filter: description.text - model: toggl.minitimerAutocomplete + //model: toggl.minitimerAutocomplete } } RowLayout { diff --git a/src/ui/linux/TogglDesktop/qmlobjectlist.cc b/src/ui/linux/TogglDesktop/qmlobjectlist.cc new file mode 100644 index 0000000000..4d50770d89 --- /dev/null +++ b/src/ui/linux/TogglDesktop/qmlobjectlist.cc @@ -0,0 +1,93 @@ +#include "qmlobjectlist.h" +#include +#include +#include + +#define ValidateIndex(m_i) (m_i < 0 || m_i >= rowCount()) + +void QmlObjectList::append(const QVariantMap& properties) +{ + QObject* newObj = mMetaObject.newInstance(); + if(newObj == Q_NULLPTR) { + qWarning("invalid constructor"); + return; + } + for(const QString& key : properties.keys()) { + if(!newObj->setProperty(key.toUtf8().data(), properties.value(key))) + qWarning()<<"append object with invalid property"<metaObject() == &mMetaObject); + beginInsertRows(QModelIndex(), 0, 0); + mData.prepend(QObjectPointer(object)); + endInsertRows(); +} + +void QmlObjectList::append(QObject *object) +{ + insert(rowCount(), object); +} + +bool QmlObjectList::insert(const int& i, QObject* object) +{ + Q_ASSERT(object->metaObject() == &mMetaObject); + if(i < 0 || i > rowCount()) + return false; + beginInsertRows(QModelIndex(), i, i); + mData.insert(i, QObjectPointer(object)); + endInsertRows(); + return true; +} + +int QmlObjectList::count() { + return rowCount(); +} + +bool QmlObjectList::removeRow(int row, const QModelIndex &parent) +{ + const int first = row; + const int last = row; + if(ValidateIndex(first) || ValidateIndex(last)) + return false; + beginRemoveRows(parent, first, last); + mData.removeAt(row); + endRemoveRows(); + return true; +} + +QVariant QmlObjectList::data(const QModelIndex &index, int role) const +{ + if(ValidateIndex(index.row())) + return QVariant(); + const QObjectPointer& data = mData[index.row()]; + if(data.isNull()) { + qWarning()<<__FUNCTION__<<"data is null"; + return QVariant(); + } + return QVariant::fromValue(data.get()); +} + +QHash QmlObjectList::roleNames() const +{ + return { { Qt::UserRole, "modelData" } }; +} + +int QmlObjectList::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return mData.count(); +} + +QmlObjectList::QmlObjectList(const QMetaObject &m, QObject *parent) : + QAbstractListModel(parent), + mMetaObject(m) +{ } + +void QmlObjectList::clear() { + beginResetModel(); + mData.clear(); + endResetModel(); +} diff --git a/src/ui/linux/TogglDesktop/qmlobjectlist.h b/src/ui/linux/TogglDesktop/qmlobjectlist.h new file mode 100644 index 0000000000..bed64352e0 --- /dev/null +++ b/src/ui/linux/TogglDesktop/qmlobjectlist.h @@ -0,0 +1,73 @@ +#ifndef QMLOBJECTLIST_H +#define QMLOBJECTLIST_H + +#include +#include +#include + +typedef QSharedPointer QObjectPointer; + +class QmlObjectList : public QAbstractListModel +{ + Q_OBJECT +public: + Q_DISABLE_COPY(QmlObjectList) + virtual ~QmlObjectList() { + mData.clear(); + } + + template + inline static QmlObjectList* create(QObject *parent = Q_NULLPTR) { + return new QmlObjectList(T::staticMetaObject, parent); + } + + void prepend(QObject* object); + void append(QObject *object); + + bool insert(const int& i, QObject *object); + + int count(); + + void clear(); + + Q_INVOKABLE + /** + * @brief append + * append an object to list and assign given properties. + * @param properties + * @warning if append new Object from QML is needed + * have to assign the default value for the parameters, + * and adding Q_INVOKABLE to constructor. + */ + void append(const QVariantMap& properties); + + Q_INVOKABLE bool removeRow(int row, const QModelIndex &parent = QModelIndex()); + + Q_INVOKABLE inline void removeFirst() { + if(!mData.isEmpty()) + removeRow(0); + } + + Q_INVOKABLE inline void removeLast() { + if(!mData.isEmpty()) + removeRow(rowCount() - 1); + } + + Q_INVOKABLE inline QVariant at(const int& i) { + return QVariant::fromValue(mData.at(i)); + } + +protected: + QVariant data(const QModelIndex &index, int role) const; + + QHash roleNames() const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; +private: + QmlObjectList(const QMetaObject& m, QObject *parent = Q_NULLPTR); + + const QMetaObject& mMetaObject; + QList mData; +}; + +#endif // QMLOBJECTLIST_H diff --git a/src/ui/linux/TogglDesktop/timeentryview.cpp b/src/ui/linux/TogglDesktop/timeentryview.cpp index 2e46789be5..e722276093 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.cpp +++ b/src/ui/linux/TogglDesktop/timeentryview.cpp @@ -3,10 +3,49 @@ #include "./timeentryview.h" #include +#include TimeEntryView::TimeEntryView(QObject *parent) : QObject(parent) { } +bool TimeEntryView::operator==(const TimeEntryView &o) { + return this->DurationInSeconds == o.DurationInSeconds && + this->Description == o.Description && + this->ProjectAndTaskLabel == o.ProjectAndTaskLabel && + this->ProjectLabel == o.ProjectLabel && + this->TaskLabel == o.TaskLabel && + this->ClientLabel == o.ClientLabel && + this->WID == o.WID && + this->PID == o.PID && + this->TID == o.TID && + this->Duration == o.Duration && + this->Color == o.Color && + this->GUID == o.GUID && + this->Billable == o.Billable && + this->Tags == o.Tags && + this->Started == o.Started && + this->Ended == o.Ended && + this->StartTimeString == o.StartTimeString && + this->EndTimeString == o.EndTimeString && + this->DateHeader == o.DateHeader && + this->DateDuration == o.DateDuration && + this->CanAddProjects == o.CanAddProjects && + this->CanSeeBillable == o.CanSeeBillable && + this->DefaultWID == o.DefaultWID && + this->WorkspaceName == o.WorkspaceName && + this->Error == o.Error && + this->Unsynced == o.Unsynced && + this->Group == o.Group && + this->GroupOpen == o.GroupOpen && + this->GroupName == o.GroupName && + this->GroupDuration == o.GroupDuration && + this->GroupItemCount == o.GroupItemCount; +} + +bool TimeEntryView::operator!=(const TimeEntryView &o) { + return ! (*this == o); +} + TimeEntryView *TimeEntryView::importOne(TogglTimeEntryView *view) { TimeEntryView *result = new TimeEntryView(); result->DurationInSeconds = view->DurationInSeconds; diff --git a/src/ui/linux/TogglDesktop/timeentryview.h b/src/ui/linux/TogglDesktop/timeentryview.h index 1bf2b64707..d0ed057e18 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.h +++ b/src/ui/linux/TogglDesktop/timeentryview.h @@ -15,6 +15,9 @@ class TimeEntryView : public QObject { public: explicit TimeEntryView(QObject *parent = 0); + bool operator==(const TimeEntryView &o); + bool operator!=(const TimeEntryView &o); + static TimeEntryView *importOne(TogglTimeEntryView *view); static QVector importAll(TogglTimeEntryView *first); diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index 16908c820e..bca6160255 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -324,6 +324,8 @@ TogglApi::TogglApi(QObject *parent, QString logPathOverride, QString dbPathOverr minitimerAutocomplete_->setSourceModel(minitimerModel_); projectAutocomplete_->setSourceModel(projectModel_); + timeEntries_ = QmlObjectList::create(this); + char *env = toggl_environment(ctx); if (env) { Bugsnag::releaseStage = QString(env); @@ -377,13 +379,45 @@ AutocompleteProxyModel *TogglApi::projectAutocomplete() { return projectAutocomplete_; } -QQmlListProperty TogglApi::timeEntries() { - return QQmlListProperty(this, timeEntries_); +QmlObjectList *TogglApi::timeEntries() { + return timeEntries_; } void TogglApi::importTimeEntries(QVector list) { - replaceList(list, timeEntries_); - emit timeEntriesChanged(); + bool reset = false; + if (list.count() - 1 != timeEntries_->count() && list.count() != timeEntries_->count()) { + reset = true; + } + if (!reset) { + bool areSame = true; + for (int i = 0; i < list.count(); i++) { + auto obj = qvariant_cast(timeEntries_->at(i)).get(); + auto view = qobject_cast(obj); + if (!view || *list[i] != *view) { + areSame = false; + break; + } + } + if (areSame) + return; + for (int i = 1; i < list.count(); i++) { + auto obj = qvariant_cast(timeEntries_->at(i - 1)).get(); + auto view = qobject_cast(obj); + if (!view || *list[i] != *view) { + reset = true; + break; + } + } + } + if (reset) { + timeEntries_->clear(); + for (auto i : list) { + timeEntries()->append(i); + } + } + else { + timeEntries_->prepend(list.first()); + } } bool TogglApi::startEvents() { diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index ffacb5ae07..649cfcf93e 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -13,6 +13,7 @@ #include "./toggl_api.h" #include "./autocompletelistmodel.h" #include "./autocompletecombobox.h" +#include "./qmlobjectlist.h" class AutocompleteView; class GenericView; @@ -26,7 +27,7 @@ class TogglApi : public QObject { Q_PROPERTY(AutocompleteProxyModel* timeEntryAutocomplete READ timeEntryAutocomplete CONSTANT) Q_PROPERTY(AutocompleteProxyModel* minitimerAutocomplete READ minitimerAutocomplete CONSTANT) Q_PROPERTY(AutocompleteProxyModel* projectAutocomplete READ projectAutocomplete CONSTANT) - Q_PROPERTY(QQmlListProperty timeEntries READ timeEntries NOTIFY timeEntriesChanged) + Q_PROPERTY(QmlObjectList *timeEntries READ timeEntries CONSTANT) public: TogglApi( @@ -52,14 +53,13 @@ class TogglApi : public QObject { AutocompleteProxyModel *timeEntryAutocomplete(); AutocompleteProxyModel *minitimerAutocomplete(); AutocompleteProxyModel *projectAutocomplete(); - QQmlListProperty timeEntries(); + QmlObjectList *timeEntries(); void importTimeEntries(QVector list); signals: void countriesChanged(); void timeEntryAutocompleteChanged(); void minitimerAutocompleteChanged(); void projectAutocompleteChanged(); - void timeEntriesChanged(); private: QList countries_; AutocompleteListModel *timeEntryModel_; @@ -68,7 +68,7 @@ class TogglApi : public QObject { AutocompleteProxyModel *timeEntryAutocomplete_; AutocompleteProxyModel *minitimerAutocomplete_; AutocompleteProxyModel *projectAutocomplete_; - QList timeEntries_; + QmlObjectList *timeEntries_; public slots: void setCountries(QVector list); From 657cf51321161d99ffa3b2fbc1d8827cfa7b3813 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Wed, 16 Oct 2019 17:20:49 +0200 Subject: [PATCH 26/83] (Probably) implement a list model that handles time entry list changes (linux) This is the messiest thing I've written in ages --- .../linux/TogglDesktop/TimeEntryListView.qml | 1 + src/ui/linux/TogglDesktop/autocompleteview.h | 5 +- src/ui/linux/TogglDesktop/common.h | 28 ++- src/ui/linux/TogglDesktop/countryview.h | 2 + src/ui/linux/TogglDesktop/main.cpp | 1 + .../TogglDesktop/timeentryeditorwidget.cpp | 2 +- src/ui/linux/TogglDesktop/timeentryview.cpp | 235 ++++++++++++++++++ src/ui/linux/TogglDesktop/timeentryview.h | 30 ++- src/ui/linux/TogglDesktop/timerwidget.cpp | 2 +- src/ui/linux/TogglDesktop/toggl.cpp | 49 +--- src/ui/linux/TogglDesktop/toggl.h | 12 +- 11 files changed, 313 insertions(+), 54 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 8af8f0827c..5f405dfd8c 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -32,6 +32,7 @@ Rectangle { text: " " + section + "
" + width + "
" color: palette.windowText } + onCountChanged: console.log(count) add: Transition { NumberAnimation { diff --git a/src/ui/linux/TogglDesktop/autocompleteview.h b/src/ui/linux/TogglDesktop/autocompleteview.h index f25a71a589..394d63ffcb 100644 --- a/src/ui/linux/TogglDesktop/autocompleteview.h +++ b/src/ui/linux/TogglDesktop/autocompleteview.h @@ -145,8 +145,11 @@ class AutocompleteView : public QObject { PROPERTY(AutocompleteType, Type) PROPERTY(bool, Billable) PROPERTY(QString, Tags) - QList _Children; + +private: + mutable QMutex propertyMutex_; + }; #endif // SRC_UI_LINUX_TOGGLDESKTOP_AUTOCOMPLETEVIEW_H_ diff --git a/src/ui/linux/TogglDesktop/common.h b/src/ui/linux/TogglDesktop/common.h index 603eae2970..70591bc1f9 100644 --- a/src/ui/linux/TogglDesktop/common.h +++ b/src/ui/linux/TogglDesktop/common.h @@ -3,11 +3,27 @@ #ifndef SRC_COMMON_H_ #define SRC_COMMON_H_ -#define PROPERTY(type, name) \ -private: \ - Q_PROPERTY(type name READ name ## Get CONSTANT) \ -public: \ - type name; \ - type name ## Get() const { return name; } +#include + +#define PROPERTY(type, name, ...) \ +private: Q_PROPERTY(type name READ name##Get WRITE name##Set NOTIFY name##Changed) \ +public: type name { __VA_ARGS__ }; \ + type name##Get() const { \ + propertyMutex_.lock(); \ + auto value = name; \ + propertyMutex_.unlock(); \ + return value; \ + } \ + Q_SLOT Q_INVOKABLE void name##Set(const type &val) { \ + propertyMutex_.lock(); \ + if (name != val) { \ + name = val; \ + propertyMutex_.unlock(); \ + emit name##Changed(); \ + } \ + else \ + propertyMutex_.unlock(); \ + } \ + Q_SIGNAL void name##Changed(); #endif // SRC_COMMON_H_ diff --git a/src/ui/linux/TogglDesktop/countryview.h b/src/ui/linux/TogglDesktop/countryview.h index 92cd94bd6d..b05d407e8c 100644 --- a/src/ui/linux/TogglDesktop/countryview.h +++ b/src/ui/linux/TogglDesktop/countryview.h @@ -32,6 +32,8 @@ class CountryView : public QObject return result; } + mutable QMutex propertyMutex_; + PROPERTY(uint64_t, ID) PROPERTY(QString, Text) PROPERTY(QString, Name) diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index b5d4a85940..dea138800d 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -106,6 +106,7 @@ int main(int argc, char *argv[]) try { QQmlApplicationEngine engine; qmlRegisterType("toggl", 1, 0, "Country"); qmlRegisterType("toggl", 1, 0, "TimeEntry"); + qmlRegisterType("toggl", 1, 0, "TimeEntryList"); qmlRegisterType("toggl", 1, 0, "Autocomplete"); engine.rootContext()->setContextProperty("toggl", new TogglApi(nullptr)); engine.load(QUrl(QStringLiteral("qrc:/MainWindow.qml"))); diff --git a/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp b/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp index 947dffa7a4..95ea040fb8 100644 --- a/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp +++ b/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp @@ -252,7 +252,7 @@ void TimeEntryEditorWidget::displayTimeEntryEditor( } guid = view->GUID; - duration = view->DurationInSeconds; + duration = view->DurationInSecondsGet(); if (duration < 0) { timer->start(1000); diff --git a/src/ui/linux/TogglDesktop/timeentryview.cpp b/src/ui/linux/TogglDesktop/timeentryview.cpp index e722276093..ad0c8b0f52 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.cpp +++ b/src/ui/linux/TogglDesktop/timeentryview.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include TimeEntryView::TimeEntryView(QObject *parent) : QObject(parent) { } @@ -86,6 +88,7 @@ TimeEntryView *TimeEntryView::importOne(TogglTimeEntryView *view) { return result; } +/* QVector TimeEntryView::importAll( TogglTimeEntryView *first) { QVector result; @@ -96,6 +99,76 @@ QVector TimeEntryView::importAll( } return result; } +*/ + +void TimeEntryView::update(const TogglTimeEntryView *view) { + int64_t DurationInSeconds_ = view->DurationInSeconds; + QMetaObject::invokeMethod(this, "DurationInSecondsSet", Qt::QueuedConnection, Q_ARG( const int64_t &, DurationInSeconds_ )); + QString Description_ = view->Description; + QMetaObject::invokeMethod(this, "DescriptionSet", Qt::QueuedConnection, Q_ARG( const QString &, Description_ )); + QString ProjectAndTaskLabel_ = view->ProjectAndTaskLabel; + QMetaObject::invokeMethod(this, "ProjectAndTaskLabelSet", Qt::QueuedConnection, Q_ARG( const QString &, ProjectAndTaskLabel_ )); + QString ProjectLabel_ = view->ProjectLabel; + QMetaObject::invokeMethod(this, "ProjectLabelSet", Qt::QueuedConnection, Q_ARG( const QString &, ProjectLabel_ )); + QString TaskLabel_ = view->TaskLabel; + QMetaObject::invokeMethod(this, "TaskLabelSet", Qt::QueuedConnection, Q_ARG( const QString &, TaskLabel_ )); + QString ClientLabel_ = view->ClientLabel; + QMetaObject::invokeMethod(this, "ClientLabelSet", Qt::QueuedConnection, Q_ARG( const QString &, ClientLabel_ )); + uint64_t WID_ = view->WID; + QMetaObject::invokeMethod(this, "WIDSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, WID_ )); + uint64_t PID_ = view->PID; + QMetaObject::invokeMethod(this, "PIDSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, PID_ )); + uint64_t TID_ = view->TID; + QMetaObject::invokeMethod(this, "TIDSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, TID_ )); + QString Duration_ = view->Duration; + QMetaObject::invokeMethod(this, "DurationSet", Qt::QueuedConnection, Q_ARG( const QString &, Duration_ )); + QString Color_ = view->Color; + QMetaObject::invokeMethod(this, "ColorSet", Qt::QueuedConnection, Q_ARG( const QString &, Color_ )); + QString GUID_ = view->GUID; + QMetaObject::invokeMethod(this, "GUIDSet", Qt::QueuedConnection, Q_ARG( const QString &, GUID_ )); + bool Billable_ = view->Billable; + QMetaObject::invokeMethod(this, "BillableSet", Qt::QueuedConnection, Q_ARG( const bool &, Billable_ )); + QString Tags_ = view->Tags; + QMetaObject::invokeMethod(this, "TagsSet", Qt::QueuedConnection, Q_ARG( const QString &, Tags_ )); + uint64_t Started_ = view->Started; + QMetaObject::invokeMethod(this, "StartedSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, Started_ )); + uint64_t Ended_ = view->Ended; + QMetaObject::invokeMethod(this, "EndedSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, Ended_ )); + QString StartTimeString_ = view->StartTimeString; + QMetaObject::invokeMethod(this, "StartTimeStringSet", Qt::QueuedConnection, Q_ARG( const QString &, StartTimeString_ )); + QString EndTimeString_ = view->EndTimeString; + QMetaObject::invokeMethod(this, "EndTimeStringSet", Qt::QueuedConnection, Q_ARG( const QString &, EndTimeString_ )); + uint64_t UpdatedAt_ = view->UpdatedAt; + QMetaObject::invokeMethod(this, "UpdatedAtSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, UpdatedAt_ )); + QString DateHeader_ = view->DateHeader; + QMetaObject::invokeMethod(this, "DateHeaderSet", Qt::QueuedConnection, Q_ARG( const QString &, DateHeader_ )); + QString DateDuration_ = view->DateDuration; + QMetaObject::invokeMethod(this, "DateDurationSet", Qt::QueuedConnection, Q_ARG( const QString &, DateDuration_ )); + bool IsHeader_ = view->IsHeader; + QMetaObject::invokeMethod(this, "IsHeaderSet", Qt::QueuedConnection, Q_ARG( const bool &, IsHeader_ )); + bool CanAddProjects_ = view->CanAddProjects; + QMetaObject::invokeMethod(this, "CanAddProjectsSet", Qt::QueuedConnection, Q_ARG( const bool &, CanAddProjects_ )); + bool CanSeeBillable_ = view->CanSeeBillable; + QMetaObject::invokeMethod(this, "CanSeeBillableSet", Qt::QueuedConnection, Q_ARG( const bool &, CanSeeBillable_ )); + uint64_t DefaultWID_ = view->DefaultWID; + QMetaObject::invokeMethod(this, "DefaultWIDSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, DefaultWID_ )); + QString WorkspaceName_ = view->WorkspaceName; + QMetaObject::invokeMethod(this, "WorkspaceNameSet", Qt::QueuedConnection, Q_ARG( const QString &, WorkspaceName_ )); + QString Error_ = view->Error; + QMetaObject::invokeMethod(this, "ErrorSet", Qt::QueuedConnection, Q_ARG( const QString &, Error_ )); + bool Unsynced_ = view->Unsynced; + QMetaObject::invokeMethod(this, "UnsyncedSet", Qt::QueuedConnection, Q_ARG( const bool &, Unsynced_ )); + bool Group_ = view->Group; + QMetaObject::invokeMethod(this, "GroupSet", Qt::QueuedConnection, Q_ARG( const bool &, Group_ )); + bool GroupOpen_ = view->GroupOpen; + QMetaObject::invokeMethod(this, "GroupOpenSet", Qt::QueuedConnection, Q_ARG( const bool &, GroupOpen_ )); + QString GroupName_ = view->GroupName; + QMetaObject::invokeMethod(this, "GroupNameSet", Qt::QueuedConnection, Q_ARG( const QString &, GroupName_ )); + QString GroupDuration_ = view->GroupDuration; + QMetaObject::invokeMethod(this, "GroupDurationSet", Qt::QueuedConnection, Q_ARG( const QString &, GroupDuration_ )); + quint64 GroupItemCount_ = view->GroupItemCount; + QMetaObject::invokeMethod(this, "GroupItemCountSet", Qt::QueuedConnection, Q_ARG( const quint64 &, GroupItemCount_ )); +} const QString TimeEntryView::lastUpdate() { return QString("Last update ") + @@ -110,3 +183,165 @@ bool TimeEntryView::confirmlessDelete() { return DurationInSeconds < 15; } } + +TimeEntryViewStorage::TimeEntryViewStorage(QObject *parent) + : QAbstractListModel(parent) + , uiThread(QThread::currentThread()) +{ +} + +void TimeEntryViewStorage::importList(TogglTimeEntryView *first) { + // this function is a tough (and ugly one) + // first define the necessary data structures + QSet added; + QSet updated; + QSet removed; + QSet newGuids; + QSet oldGuids; + QList newOrder; + QList flattened; + QList reordered; + + // we take old GUIDs but make them a set so they're easier to work with (no need to know the order) + oldGuids = guidOrder_.toSet(); + + // now, let's pass through the new set of TEs + TogglTimeEntryView *libraryView = first; + while (libraryView) { + // spew an error when there's no GUID (which would make our lives much harder) + if (!libraryView->GUID || !*libraryView->GUID) { + qCritical() << libraryView->Description << "Does not have a GUID!"; + } + // build the order of the GUIDs in the new list + if (!newOrder.contains(libraryView->GUID)) + newOrder.append(libraryView->GUID); + // and also build the set of the GUIDs now for easier comparison + newGuids.insert(libraryView->GUID); + // and create an addressable list of the new items (library, not qt structures, though) + flattened.append(libraryView); + libraryView = static_cast(libraryView->Next); + } + + // now we compare the sets: + // this gives us the newly added guids + added = newGuids - oldGuids; + // this gives us guids that stayed in place but we need to update them + updated = newGuids; + updated = updated.intersect(oldGuids); + // and here we have guids that are gone + removed = oldGuids - newGuids; + + qCritical() << "==================== NEW ROUND ===================="; + qCritical() << "======== Storage"; + qCritical() << storage_; + qCritical() << "======== added"; + qCritical() << added; + qCritical() << "======== updated"; + qCritical() << updated; + qCritical() << "======== removed"; + qCritical() << removed; + qCritical() << "======== newGuids"; + qCritical() << newGuids; + qCritical() << "======== oldGuids"; + qCritical() << oldGuids; + qCritical() << "======== newOrder"; + qCritical() << newOrder; + qCritical() << "======== flattened"; + qCritical() << flattened; + + // first we remove all items with guids that are gone in the update + for (auto i : removed) { + qCritical() << "BAZBAZ"; + remove(i); + } + + // now pass through the list again to update the items we already have + libraryView = first; + while (libraryView) { + if (updated.contains(libraryView->GUID)) { + qCritical() << "BAZ"; + qCritical() << "Current thread" << QThread::currentThread(); + qCritical() << "UI thread" << uiThread; + storage_[libraryView->GUID]->update(libraryView); + // and also build the order of GUIDs we already had (before) + reordered.append(libraryView); + updated.remove(libraryView->GUID); + } + libraryView = static_cast(libraryView->Next); + } + + qCritical() << "======== reordered"; + qCritical() << reordered; + + // and then we take the new order of the current items and reorder them according to the new order + for (int i = 0; i < reordered.count(); i++) { + if (reordered[i]->GUID != guidOrder_[i]) { + qCritical() << "BAR"; + move(reordered[i]->GUID, i); + } + } + + // and finally, we insert newly added items into the reordered set of old items that stayed in the storage + for (int i = 0; i < newOrder.count(); i++) { + if (i >= guidOrder_.count() || guidOrder_[i] != newOrder[i]) { + qCritical() << "FOO"; + beginInsertRows(QModelIndex(), i, i); + QString guid = newOrder[i]; + guidOrder_.insert(i, guid); + auto v = TimeEntryView::importOne(flattened[i]); + qCritical() << "CREATED:" << v; + qCritical() << "Current thread" << QThread::currentThread(); + qCritical() << "UI thread" << uiThread; + v->moveToThread(uiThread); + storage_.insert(guid, v); + qCritical() << "Storage:" << storage_; + endInsertRows(); + qCritical() << guidOrder_.count(); + } + } +} + +QVariant TimeEntryViewStorage::data(const QModelIndex &index, int role) const { + if (index.isValid() && role == Qt::UserRole) { + qCritical() << "============== REQUEST ============="; + qCritical() << "Looking for index" << index.row(); + qCritical() << "Storage:" << storage_; + qCritical() << "Order:" << guidOrder_; + qCritical() << "Going to return" << storage_[guidOrder_[index.row()]]; + return QVariant::fromValue(storage_[guidOrder_[index.row()]]); + } + return QVariant(); +} + +QHash TimeEntryViewStorage::roleNames() const { + return { { Qt::UserRole, "modelData" } }; +} + +int TimeEntryViewStorage::rowCount(const QModelIndex &parent) const { + return guidOrder_.count(); +} + +void TimeEntryViewStorage::remove(const QString &guid) { + int pos = guidOrder_.indexOf(guid); + if (pos < 0) { + qCritical() << "Could not remove" << guid << "because it's not present"; + return; + } + beginRemoveRows(QModelIndex(), pos, pos); + guidOrder_.removeAt(pos); + auto old = storage_[guid]; + storage_.remove(guid); + old->deleteLater(); + endRemoveRows(); +} + +void TimeEntryViewStorage::move(const QString &guid, int to) { + int pos = guidOrder_.indexOf(guid); + if (pos < 0) { + qCritical() << "Could not move" << guid << "because it's not present"; + return; + } + beginMoveRows(QModelIndex(), pos, pos, QModelIndex(), to + 1 ); + guidOrder_.move(pos, to); + endMoveRows(); +} diff --git a/src/ui/linux/TogglDesktop/timeentryview.h b/src/ui/linux/TogglDesktop/timeentryview.h index d0ed057e18..08cd171349 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.h +++ b/src/ui/linux/TogglDesktop/timeentryview.h @@ -5,10 +5,35 @@ #include #include +#include #include "./toggl_api.h" #include "./common.h" +class TimeEntryView; + +class TimeEntryViewStorage : public QAbstractListModel { + Q_OBJECT +public: + TimeEntryViewStorage(QObject *parent = nullptr); + + void importList(TogglTimeEntryView *first); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + +private: + void remove(const QString &guid); + void move(const QString &guid, int to); + + QList guidOrder_; + QMap storage_; + + QThread *uiThread; +}; + class TimeEntryView : public QObject { Q_OBJECT @@ -19,11 +44,14 @@ class TimeEntryView : public QObject { bool operator!=(const TimeEntryView &o); static TimeEntryView *importOne(TogglTimeEntryView *view); - static QVector importAll(TogglTimeEntryView *first); + //static QVector importAll(TogglTimeEntryView *first); + void update(const TogglTimeEntryView *view); bool confirmlessDelete(); Q_INVOKABLE const QString lastUpdate(); + mutable QMutex propertyMutex_; + PROPERTY(int64_t, DurationInSeconds) PROPERTY(QString, Description) PROPERTY(QString, ProjectAndTaskLabel) diff --git a/src/ui/linux/TogglDesktop/timerwidget.cpp b/src/ui/linux/TogglDesktop/timerwidget.cpp index e3679a7ece..6b680204cd 100644 --- a/src/ui/linux/TogglDesktop/timerwidget.cpp +++ b/src/ui/linux/TogglDesktop/timerwidget.cpp @@ -223,7 +223,7 @@ void TimerWidget::displayRunningTimerState( ui->tags->setToolTip(QString("

" + te->Tags + "

")); - duration = te->DurationInSeconds; + duration = te->DurationInSecondsGet(); if (te->Description.length() > 0) { ui->description->setToolTip( diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index bca6160255..980e4833f5 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -101,7 +101,7 @@ void on_display_time_entry_list( if (open) { TogglApi::instance->aboutToDisplayTimeEntryList(); } - TogglApi::instance->importTimeEntries(TimeEntryView::importAll(first)); + TogglApi::instance->importTimeEntries(first); TogglApi::instance->displayTimeEntryList( open, show_load_more_button); @@ -168,8 +168,9 @@ void on_display_settings( void on_display_timer_state( TogglTimeEntryView *te) { if (te) { - TogglApi::instance->displayRunningTimerState( - TimeEntryView::importOne(te)); + auto v = TimeEntryView::importOne(te); + v->moveToThread(TogglApi::instance->uiThread_); + TogglApi::instance->displayRunningTimerState(v); return; } @@ -250,6 +251,7 @@ TogglApi::TogglApi(QObject *parent, QString logPathOverride, QString dbPathOverr , timeEntryAutocomplete_(new AutocompleteProxyModel(this)) , minitimerAutocomplete_(new AutocompleteProxyModel(this)) , projectAutocomplete_(new AutocompleteProxyModel(this)) + , uiThread_(QThread::currentThread()) { QString version = QApplication::applicationVersion(); ctx = toggl_context_init("linux_native_app", @@ -324,7 +326,7 @@ TogglApi::TogglApi(QObject *parent, QString logPathOverride, QString dbPathOverr minitimerAutocomplete_->setSourceModel(minitimerModel_); projectAutocomplete_->setSourceModel(projectModel_); - timeEntries_ = QmlObjectList::create(this); + timeEntries_ = new TimeEntryViewStorage(this); char *env = toggl_environment(ctx); if (env) { @@ -379,45 +381,12 @@ AutocompleteProxyModel *TogglApi::projectAutocomplete() { return projectAutocomplete_; } -QmlObjectList *TogglApi::timeEntries() { +TimeEntryViewStorage *TogglApi::timeEntries() { return timeEntries_; } -void TogglApi::importTimeEntries(QVector list) { - bool reset = false; - if (list.count() - 1 != timeEntries_->count() && list.count() != timeEntries_->count()) { - reset = true; - } - if (!reset) { - bool areSame = true; - for (int i = 0; i < list.count(); i++) { - auto obj = qvariant_cast(timeEntries_->at(i)).get(); - auto view = qobject_cast(obj); - if (!view || *list[i] != *view) { - areSame = false; - break; - } - } - if (areSame) - return; - for (int i = 1; i < list.count(); i++) { - auto obj = qvariant_cast(timeEntries_->at(i - 1)).get(); - auto view = qobject_cast(obj); - if (!view || *list[i] != *view) { - reset = true; - break; - } - } - } - if (reset) { - timeEntries_->clear(); - for (auto i : list) { - timeEntries()->append(i); - } - } - else { - timeEntries_->prepend(list.first()); - } +void TogglApi::importTimeEntries(TogglTimeEntryView *first) { + timeEntries_->importList(first); } bool TogglApi::startEvents() { diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index 649cfcf93e..68a76e069a 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -20,6 +21,7 @@ class GenericView; class SettingsView; class TimeEntryView; class CountryView; +class TimeEntryViewStorage; class TogglApi : public QObject { Q_OBJECT @@ -27,7 +29,7 @@ class TogglApi : public QObject { Q_PROPERTY(AutocompleteProxyModel* timeEntryAutocomplete READ timeEntryAutocomplete CONSTANT) Q_PROPERTY(AutocompleteProxyModel* minitimerAutocomplete READ minitimerAutocomplete CONSTANT) Q_PROPERTY(AutocompleteProxyModel* projectAutocomplete READ projectAutocomplete CONSTANT) - Q_PROPERTY(QmlObjectList *timeEntries READ timeEntries CONSTANT) + Q_PROPERTY(TimeEntryViewStorage *timeEntries READ timeEntries CONSTANT) public: TogglApi( @@ -36,6 +38,8 @@ class TogglApi : public QObject { QString dbPathOverride = ""); ~TogglApi(); + QThread *uiThread_; + static TogglApi *instance; static QString Project; @@ -53,8 +57,8 @@ class TogglApi : public QObject { AutocompleteProxyModel *timeEntryAutocomplete(); AutocompleteProxyModel *minitimerAutocomplete(); AutocompleteProxyModel *projectAutocomplete(); - QmlObjectList *timeEntries(); - void importTimeEntries(QVector list); + TimeEntryViewStorage *timeEntries(); + void importTimeEntries(TogglTimeEntryView *first); signals: void countriesChanged(); void timeEntryAutocompleteChanged(); @@ -68,7 +72,7 @@ class TogglApi : public QObject { AutocompleteProxyModel *timeEntryAutocomplete_; AutocompleteProxyModel *minitimerAutocomplete_; AutocompleteProxyModel *projectAutocomplete_; - QmlObjectList *timeEntries_; + TimeEntryViewStorage *timeEntries_; public slots: void setCountries(QVector list); From eaf47693cbc91ddc01eef6844f982a1395e946ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Thu, 17 Oct 2019 14:46:11 +0200 Subject: [PATCH 27/83] Because GUIDs are not really unique, handle special cases and create our own UID for groups (linux) --- .../linux/TogglDesktop/TimeEntryListView.qml | 16 +- src/ui/linux/TogglDesktop/common.h | 21 +++ src/ui/linux/TogglDesktop/timeentryview.cpp | 164 ++++++------------ src/ui/linux/TogglDesktop/timeentryview.h | 3 + 4 files changed, 92 insertions(+), 112 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 5f405dfd8c..2408ca87b4 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -22,7 +22,7 @@ Rectangle { ListView { clip: true model: toggl.timeEntries - section.property: "DateHeader" + section.property: "modelData.DateHeader" section.delegate: Text { y: 2 width: timeEntryList.viewportWidth @@ -44,7 +44,21 @@ Rectangle { moveDisplaced: Transition { NumberAnimation { + properties: "y" + } + } + + move: Transition { + NumberAnimation { + properties: "y" + } + } + remove: Transition { + NumberAnimation { + properties: "opacity" + from: 1 + to: 0 } } diff --git a/src/ui/linux/TogglDesktop/common.h b/src/ui/linux/TogglDesktop/common.h index 70591bc1f9..d9a17ac978 100644 --- a/src/ui/linux/TogglDesktop/common.h +++ b/src/ui/linux/TogglDesktop/common.h @@ -5,6 +5,25 @@ #include +#ifndef TOGGL_SAFE_PROPERTIES + +#define PROPERTY(type, name, ...) \ +private: Q_PROPERTY(type name READ name##Get WRITE name##Set NOTIFY name##Changed) \ +public: type name { __VA_ARGS__ }; \ + type name##Get() const { \ + auto value = name; \ + return value; \ + } \ + Q_SLOT Q_INVOKABLE void name##Set(const type &val) { \ + if (name != val) { \ + name = val; \ + emit name##Changed(); \ + } \ + } \ + Q_SIGNAL void name##Changed(); + +#else // TOGGL_SAFE_PROPERTIES + #define PROPERTY(type, name, ...) \ private: Q_PROPERTY(type name READ name##Get WRITE name##Set NOTIFY name##Changed) \ public: type name { __VA_ARGS__ }; \ @@ -26,4 +45,6 @@ public: type name { __VA_ARGS__ }; \ } \ Q_SIGNAL void name##Changed(); +#endif // TOGGL_SAFE_PROPERTIES + #endif // SRC_COMMON_H_ diff --git a/src/ui/linux/TogglDesktop/timeentryview.cpp b/src/ui/linux/TogglDesktop/timeentryview.cpp index ad0c8b0f52..df8c583292 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.cpp +++ b/src/ui/linux/TogglDesktop/timeentryview.cpp @@ -102,72 +102,39 @@ QVector TimeEntryView::importAll( */ void TimeEntryView::update(const TogglTimeEntryView *view) { - int64_t DurationInSeconds_ = view->DurationInSeconds; - QMetaObject::invokeMethod(this, "DurationInSecondsSet", Qt::QueuedConnection, Q_ARG( const int64_t &, DurationInSeconds_ )); - QString Description_ = view->Description; - QMetaObject::invokeMethod(this, "DescriptionSet", Qt::QueuedConnection, Q_ARG( const QString &, Description_ )); - QString ProjectAndTaskLabel_ = view->ProjectAndTaskLabel; - QMetaObject::invokeMethod(this, "ProjectAndTaskLabelSet", Qt::QueuedConnection, Q_ARG( const QString &, ProjectAndTaskLabel_ )); - QString ProjectLabel_ = view->ProjectLabel; - QMetaObject::invokeMethod(this, "ProjectLabelSet", Qt::QueuedConnection, Q_ARG( const QString &, ProjectLabel_ )); - QString TaskLabel_ = view->TaskLabel; - QMetaObject::invokeMethod(this, "TaskLabelSet", Qt::QueuedConnection, Q_ARG( const QString &, TaskLabel_ )); - QString ClientLabel_ = view->ClientLabel; - QMetaObject::invokeMethod(this, "ClientLabelSet", Qt::QueuedConnection, Q_ARG( const QString &, ClientLabel_ )); - uint64_t WID_ = view->WID; - QMetaObject::invokeMethod(this, "WIDSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, WID_ )); - uint64_t PID_ = view->PID; - QMetaObject::invokeMethod(this, "PIDSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, PID_ )); - uint64_t TID_ = view->TID; - QMetaObject::invokeMethod(this, "TIDSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, TID_ )); - QString Duration_ = view->Duration; - QMetaObject::invokeMethod(this, "DurationSet", Qt::QueuedConnection, Q_ARG( const QString &, Duration_ )); - QString Color_ = view->Color; - QMetaObject::invokeMethod(this, "ColorSet", Qt::QueuedConnection, Q_ARG( const QString &, Color_ )); - QString GUID_ = view->GUID; - QMetaObject::invokeMethod(this, "GUIDSet", Qt::QueuedConnection, Q_ARG( const QString &, GUID_ )); - bool Billable_ = view->Billable; - QMetaObject::invokeMethod(this, "BillableSet", Qt::QueuedConnection, Q_ARG( const bool &, Billable_ )); - QString Tags_ = view->Tags; - QMetaObject::invokeMethod(this, "TagsSet", Qt::QueuedConnection, Q_ARG( const QString &, Tags_ )); - uint64_t Started_ = view->Started; - QMetaObject::invokeMethod(this, "StartedSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, Started_ )); - uint64_t Ended_ = view->Ended; - QMetaObject::invokeMethod(this, "EndedSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, Ended_ )); - QString StartTimeString_ = view->StartTimeString; - QMetaObject::invokeMethod(this, "StartTimeStringSet", Qt::QueuedConnection, Q_ARG( const QString &, StartTimeString_ )); - QString EndTimeString_ = view->EndTimeString; - QMetaObject::invokeMethod(this, "EndTimeStringSet", Qt::QueuedConnection, Q_ARG( const QString &, EndTimeString_ )); - uint64_t UpdatedAt_ = view->UpdatedAt; - QMetaObject::invokeMethod(this, "UpdatedAtSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, UpdatedAt_ )); - QString DateHeader_ = view->DateHeader; - QMetaObject::invokeMethod(this, "DateHeaderSet", Qt::QueuedConnection, Q_ARG( const QString &, DateHeader_ )); - QString DateDuration_ = view->DateDuration; - QMetaObject::invokeMethod(this, "DateDurationSet", Qt::QueuedConnection, Q_ARG( const QString &, DateDuration_ )); - bool IsHeader_ = view->IsHeader; - QMetaObject::invokeMethod(this, "IsHeaderSet", Qt::QueuedConnection, Q_ARG( const bool &, IsHeader_ )); - bool CanAddProjects_ = view->CanAddProjects; - QMetaObject::invokeMethod(this, "CanAddProjectsSet", Qt::QueuedConnection, Q_ARG( const bool &, CanAddProjects_ )); - bool CanSeeBillable_ = view->CanSeeBillable; - QMetaObject::invokeMethod(this, "CanSeeBillableSet", Qt::QueuedConnection, Q_ARG( const bool &, CanSeeBillable_ )); - uint64_t DefaultWID_ = view->DefaultWID; - QMetaObject::invokeMethod(this, "DefaultWIDSet", Qt::QueuedConnection, Q_ARG( const uint64_t &, DefaultWID_ )); - QString WorkspaceName_ = view->WorkspaceName; - QMetaObject::invokeMethod(this, "WorkspaceNameSet", Qt::QueuedConnection, Q_ARG( const QString &, WorkspaceName_ )); - QString Error_ = view->Error; - QMetaObject::invokeMethod(this, "ErrorSet", Qt::QueuedConnection, Q_ARG( const QString &, Error_ )); - bool Unsynced_ = view->Unsynced; - QMetaObject::invokeMethod(this, "UnsyncedSet", Qt::QueuedConnection, Q_ARG( const bool &, Unsynced_ )); - bool Group_ = view->Group; - QMetaObject::invokeMethod(this, "GroupSet", Qt::QueuedConnection, Q_ARG( const bool &, Group_ )); - bool GroupOpen_ = view->GroupOpen; - QMetaObject::invokeMethod(this, "GroupOpenSet", Qt::QueuedConnection, Q_ARG( const bool &, GroupOpen_ )); - QString GroupName_ = view->GroupName; - QMetaObject::invokeMethod(this, "GroupNameSet", Qt::QueuedConnection, Q_ARG( const QString &, GroupName_ )); - QString GroupDuration_ = view->GroupDuration; - QMetaObject::invokeMethod(this, "GroupDurationSet", Qt::QueuedConnection, Q_ARG( const QString &, GroupDuration_ )); - quint64 GroupItemCount_ = view->GroupItemCount; - QMetaObject::invokeMethod(this, "GroupItemCountSet", Qt::QueuedConnection, Q_ARG( const quint64 &, GroupItemCount_ )); + DurationInSecondsSet(view->DurationInSeconds); + DescriptionSet(view->Description); + ProjectAndTaskLabelSet(view->ProjectAndTaskLabel); + ProjectLabelSet(view->ProjectLabel); + TaskLabelSet(view->TaskLabel); + ClientLabelSet(view->ClientLabel); + WIDSet(view->WID); + PIDSet(view->PID); + TIDSet(view->TID); + DurationSet(view->Duration); + ColorSet(view->Color); + GUIDSet(view->GUID); + BillableSet(view->Billable); + TagsSet(view->Tags); + StartedSet(view->Started); + EndedSet(view->Ended); + StartTimeStringSet(view->StartTimeString); + EndTimeStringSet(view->EndTimeString); + UpdatedAtSet(view->UpdatedAt); + DateHeaderSet(QString("%1%2").arg(view->DateHeader).arg(view->DateDuration)); + DateDurationSet(view->DateDuration); + IsHeaderSet(view->IsHeader); + CanAddProjectsSet(view->CanAddProjects); + CanSeeBillableSet(view->CanSeeBillable); + DefaultWIDSet(view->DefaultWID); + WorkspaceNameSet(view->WorkspaceName); + ErrorSet(view->Error); + UnsyncedSet(view->Unsynced); + GroupSet(view->Group); + GroupOpenSet(view->GroupOpen); + GroupNameSet(view->GroupName); + GroupDurationSet(view->GroupDuration); + GroupItemCountSet(view->GroupItemCount); } const QString TimeEntryView::lastUpdate() { @@ -213,10 +180,10 @@ void TimeEntryViewStorage::importList(TogglTimeEntryView *first) { qCritical() << libraryView->Description << "Does not have a GUID!"; } // build the order of the GUIDs in the new list - if (!newOrder.contains(libraryView->GUID)) - newOrder.append(libraryView->GUID); + if (!newOrder.contains(uid(libraryView))) + newOrder.append(uid(libraryView)); // and also build the set of the GUIDs now for easier comparison - newGuids.insert(libraryView->GUID); + newGuids.insert(uid(libraryView)); // and create an addressable list of the new items (library, not qt structures, though) flattened.append(libraryView); libraryView = static_cast(libraryView->Next); @@ -231,83 +198,46 @@ void TimeEntryViewStorage::importList(TogglTimeEntryView *first) { // and here we have guids that are gone removed = oldGuids - newGuids; - qCritical() << "==================== NEW ROUND ===================="; - qCritical() << "======== Storage"; - qCritical() << storage_; - qCritical() << "======== added"; - qCritical() << added; - qCritical() << "======== updated"; - qCritical() << updated; - qCritical() << "======== removed"; - qCritical() << removed; - qCritical() << "======== newGuids"; - qCritical() << newGuids; - qCritical() << "======== oldGuids"; - qCritical() << oldGuids; - qCritical() << "======== newOrder"; - qCritical() << newOrder; - qCritical() << "======== flattened"; - qCritical() << flattened; - // first we remove all items with guids that are gone in the update for (auto i : removed) { - qCritical() << "BAZBAZ"; remove(i); } // now pass through the list again to update the items we already have libraryView = first; while (libraryView) { - if (updated.contains(libraryView->GUID)) { - qCritical() << "BAZ"; - qCritical() << "Current thread" << QThread::currentThread(); - qCritical() << "UI thread" << uiThread; - storage_[libraryView->GUID]->update(libraryView); + if (updated.contains(uid(libraryView))) { + storage_[uid(libraryView)]->update(libraryView); // and also build the order of GUIDs we already had (before) reordered.append(libraryView); - updated.remove(libraryView->GUID); + updated.remove(uid(libraryView)); } libraryView = static_cast(libraryView->Next); } - qCritical() << "======== reordered"; - qCritical() << reordered; - // and then we take the new order of the current items and reorder them according to the new order for (int i = 0; i < reordered.count(); i++) { - if (reordered[i]->GUID != guidOrder_[i]) { - qCritical() << "BAR"; - move(reordered[i]->GUID, i); + if (uid(reordered[i]) != guidOrder_[i]) { + move(uid(reordered[i]), i); } } // and finally, we insert newly added items into the reordered set of old items that stayed in the storage for (int i = 0; i < newOrder.count(); i++) { if (i >= guidOrder_.count() || guidOrder_[i] != newOrder[i]) { - qCritical() << "FOO"; beginInsertRows(QModelIndex(), i, i); QString guid = newOrder[i]; guidOrder_.insert(i, guid); auto v = TimeEntryView::importOne(flattened[i]); - qCritical() << "CREATED:" << v; - qCritical() << "Current thread" << QThread::currentThread(); - qCritical() << "UI thread" << uiThread; v->moveToThread(uiThread); storage_.insert(guid, v); - qCritical() << "Storage:" << storage_; endInsertRows(); - qCritical() << guidOrder_.count(); } } } QVariant TimeEntryViewStorage::data(const QModelIndex &index, int role) const { if (index.isValid() && role == Qt::UserRole) { - qCritical() << "============== REQUEST ============="; - qCritical() << "Looking for index" << index.row(); - qCritical() << "Storage:" << storage_; - qCritical() << "Order:" << guidOrder_; - qCritical() << "Going to return" << storage_[guidOrder_[index.row()]]; return QVariant::fromValue(storage_[guidOrder_[index.row()]]); } return QVariant(); @@ -321,6 +251,18 @@ int TimeEntryViewStorage::rowCount(const QModelIndex &parent) const { return guidOrder_.count(); } +QString TimeEntryViewStorage::uid(TogglTimeEntryView *view) { + if (view->Group) + return QString("Group %1").arg(view->GUID); + return view->GUID; +} + +QString TimeEntryViewStorage::uid(TimeEntryView *view) { + if (view->Group) + return QString("Group %1").arg(view->GUID); + return view->GUID; +} + void TimeEntryViewStorage::remove(const QString &guid) { int pos = guidOrder_.indexOf(guid); if (pos < 0) { diff --git a/src/ui/linux/TogglDesktop/timeentryview.h b/src/ui/linux/TogglDesktop/timeentryview.h index 08cd171349..07f46bcde7 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.h +++ b/src/ui/linux/TogglDesktop/timeentryview.h @@ -25,6 +25,9 @@ class TimeEntryViewStorage : public QAbstractListModel { int rowCount(const QModelIndex &parent = QModelIndex()) const override; private: + QString uid(TogglTimeEntryView *view); + QString uid(TimeEntryView *view); + void remove(const QString &guid); void move(const QString &guid, int to); From 87c315c48da35b1cf3fd7775ec93fb72353e6113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Thu, 17 Oct 2019 14:56:50 +0200 Subject: [PATCH 28/83] Make working with the time entry list prettier (linux) --- .../linux/TogglDesktop/TimeEntryListView.qml | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 2408ca87b4..b4b13f705c 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -36,14 +36,21 @@ Rectangle { add: Transition { NumberAnimation { - properties: "opacity" - from: 0 - to: 1 + properties: "y" + from: -74 + 30 + duration: 120 + } + } + addDisplaced: Transition { + NumberAnimation { + properties: "y" + duration: 120 } } moveDisplaced: Transition { NumberAnimation { + from: -height properties: "y" } } @@ -55,10 +62,24 @@ Rectangle { } remove: Transition { + ParallelAnimation { + NumberAnimation { + duration: 120 + properties: "opacity" + from: 1.0 + to: 0.0 + } + NumberAnimation { + duration: 120 + properties: "x" + from: 0 + to: width + } + } + } + removeDisplaced: Transition { NumberAnimation { - properties: "opacity" - from: 1 - to: 0 + properties: "y" } } @@ -134,7 +155,7 @@ Rectangle { } Text { Layout.alignment: Qt.AlignVCenter - text: "00:00:00" + text: modelData.Duration } Item { width: 1 } } From 31a892b75394a1014907060d8c0d8e2a2ae10071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Thu, 17 Oct 2019 16:02:27 +0200 Subject: [PATCH 29/83] Make Autocomplete work mostly as expected --- .../linux/TogglDesktop/AutocompleteView.qml | 30 +++++++++++++------ .../linux/TogglDesktop/TimeEntryListView.qml | 1 - src/ui/linux/TogglDesktop/TimerView.qml | 4 +-- .../TogglDesktop/autocompletecombobox.cpp | 17 +++++++++-- .../linux/TogglDesktop/autocompletecombobox.h | 2 ++ 5 files changed, 40 insertions(+), 14 deletions(-) diff --git a/src/ui/linux/TogglDesktop/AutocompleteView.qml b/src/ui/linux/TogglDesktop/AutocompleteView.qml index 2714ca96ee..fb5d5b4496 100644 --- a/src/ui/linux/TogglDesktop/AutocompleteView.qml +++ b/src/ui/linux/TogglDesktop/AutocompleteView.qml @@ -18,22 +18,34 @@ Item { onVisibleChanged: list.currentIndex = -1 function upPressed() { + console.log("==== START UP ====") + console.log("Current index: " + list.currentIndex) + console.log("There is " + list.count + " rows") + console.log("Model reports " + model.count() + " rows") for (var i = list.currentIndex - 1; i > 0 ; --i) { if (model.get(i).Type < 10) { list.currentIndex = i list.positionViewAtIndex(i, ListView.Center) + console.log("Current index: " + list.currentIndex) return } } + console.log("Current index: " + list.currentIndex) } function downPressed() { + console.log("==== START DOWN ====") + console.log("Current index: " + list.currentIndex) + console.log("There is " + list.count + " rows") + console.log("Model reports " + model.count() + " rows") for (var i = list.currentIndex + 1; i < list.count; i++) { if (model.get(i).Type < 10) { list.currentIndex = i list.positionViewAtIndex(i, ListView.Center) + console.log("Current index: " + list.currentIndex) return } } + console.log("Current index: " + list.currentIndex) } Rectangle { @@ -50,8 +62,9 @@ Item { model: root.model currentIndex: -1 onCurrentIndexChanged: { - root.currentItem = model.get(currentIndex) + console.log(currentIndex) } + onCountChanged: { var base = list.visibleChildren[0] var listViewHeight = 0 @@ -59,15 +72,16 @@ Item { listViewHeight += base.visibleChildren[i].height list.height = Math.min(listViewHeight, root.maximumHeight) } + highlightFollowsCurrentItem: true highlight: Rectangle { color: "red" width: root.width + height: 24 } - highlightMoveDuration: 100 delegate: Rectangle { width: root.width height: 24 - color: palette.base + color: ListView.isCurrentItem ? "red" : palette.base property bool selectable: modelData.Type < 10 Loader { // TODO use the enum instead of magic values @@ -76,7 +90,7 @@ Item { autocompleteData.Type === 11 ? headerDelegate : regularDelegate property var autocompleteData: modelData - Component.onCompleted: console.log(index) + property int index: index } } } @@ -120,9 +134,10 @@ Item { Component { id: regularDelegate - Rectangle { + Item { height: 24 width: root.width + Text { anchors.fill: parent verticalAlignment: Text.AlignVCenter @@ -134,16 +149,13 @@ Item { "" property string task: autocompleteData.TaskLabel.length > 0 ? " " + autocompleteData.TaskLabel : "" property string client: autocompleteData.ClientLabel.length > 0 ? " " + autocompleteData.ClientLabel : "" - text: timeEntry + project + task + client + text: (ListView.isCurrentItem ? "HOVNO" : "") + timeEntry + project + task + client } MouseArea { id: delegateMouse anchors.fill: parent hoverEnabled: true onContainsMouseChanged: { - if (containsMouse) { - ListView.isCurrentItem = true - } } } } diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index b4b13f705c..eabec589b1 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -32,7 +32,6 @@ Rectangle { text: " " + section + "
" + width + "
" color: palette.windowText } - onCountChanged: console.log(count) add: Transition { NumberAnimation { diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 88ec1139e0..66e6aadb43 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -74,7 +74,7 @@ Rectangle { } onTextEdited: { if (focus) { - //autocomplete.visible = true + autocomplete.visible = true } } onFocusChanged: if (!focus) autocomplete.visible = false @@ -87,7 +87,7 @@ Rectangle { right: parent.right } filter: description.text - //model: toggl.minitimerAutocomplete + model: toggl.minitimerAutocomplete } } RowLayout { diff --git a/src/ui/linux/TogglDesktop/autocompletecombobox.cpp b/src/ui/linux/TogglDesktop/autocompletecombobox.cpp index 748f3a11f9..0b3795a3d9 100644 --- a/src/ui/linux/TogglDesktop/autocompletecombobox.cpp +++ b/src/ui/linux/TogglDesktop/autocompletecombobox.cpp @@ -3,6 +3,7 @@ #include "autocompletelistview.h" #include +#include AutocompleteComboBox::AutocompleteComboBox(QWidget *parent) : QComboBox(parent) @@ -159,13 +160,25 @@ AutocompleteProxyModel::AutocompleteProxyModel(QObject *parent) setFilterRole(Qt::UserRole); } +int AutocompleteProxyModel::count() { + return rowCount(QModelIndex()); +} + void AutocompleteProxyModel::setFilter(const QString &filter) { setFilterRegExp(filter); } AutocompleteView *AutocompleteProxyModel::get(int idx) { - if (idx >= 0 && idx < rowCount()) - return qvariant_cast(data(index(idx, 0), Qt::UserRole)); + if (idx >= 0 && idx < rowCount()) { + auto d = data(index(idx, 0), Qt::UserRole); + if (!d.isValid()) + return nullptr; + auto ptr = qvariant_cast(d); + if (!ptr) + return nullptr; + QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); + return ptr; + } return nullptr; } diff --git a/src/ui/linux/TogglDesktop/autocompletecombobox.h b/src/ui/linux/TogglDesktop/autocompletecombobox.h index 24a374654e..526b88650a 100644 --- a/src/ui/linux/TogglDesktop/autocompletecombobox.h +++ b/src/ui/linux/TogglDesktop/autocompletecombobox.h @@ -64,6 +64,8 @@ class AutocompleteProxyModel : public QSortFilterProxyModel { public: AutocompleteProxyModel(QObject *parent = nullptr); + Q_INVOKABLE int count(); + Q_INVOKABLE void setFilter(const QString &filter); Q_INVOKABLE AutocompleteView *get(int idx); From 4171481c23d55e7558279c87ba53d1af263db2d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 18 Oct 2019 11:17:19 +0200 Subject: [PATCH 30/83] Get "heavily inspired" by the macOS design (linux) --- .../linux/TogglDesktop/TimeEntryListView.qml | 349 ++++++++++++++---- src/ui/linux/TogglDesktop/TimerView.qml | 4 +- 2 files changed, 273 insertions(+), 80 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index eabec589b1..c716dbf20f 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -1,10 +1,18 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 +import QtGraphicalEffects 1.0 Rectangle { + id: root anchors.fill: parent - color: palette.window + color: palette.alternateBase + + property real shadowWidth: 9 + property real sectionMargin: 2 * shadowWidth + property real headerHeight: 32 + property real itemHeight: 74 + ColumnLayout { anchors.fill: parent spacing: 0 @@ -23,20 +31,104 @@ Rectangle { clip: true model: toggl.timeEntries section.property: "modelData.DateHeader" - section.delegate: Text { - y: 2 - width: timeEntryList.viewportWidth - height: 30 - textFormat: Text.RichText - // this is actually rendering the width underneath so we force a repaint on each width change - text: " " + section + "
" + width + "
" - color: palette.windowText + section.delegate: Item { + height: headerHeight + sectionMargin + width: parent.width + Behavior on y { + NumberAnimation { + duration: 120 + } + } + + Rectangle { + y: sectionMargin + x: 12 + width: timeEntryList.viewportWidth - 24 + height: headerHeight + + Rectangle { + anchors.right: parent.left + anchors.bottom: parent.top + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: 45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: palette.alternateBase } + GradientStop { position: 0.5; color: "light gray" } + } + } + Rectangle { + anchors.left: parent.right + anchors.bottom: parent.top + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: -45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.5; color: "light gray" } + GradientStop { position: 1.0; color: palette.alternateBase } + } + } + + Rectangle { + anchors.right: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + + width: shadowWidth + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: palette.alternateBase } + GradientStop { position: 1.0; color: "light gray" } + } + } + Rectangle { + anchors.left: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + width: shadowWidth + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: "light gray" } + GradientStop { position: 1.0; color: palette.alternateBase } + } + } + Rectangle { + anchors.bottom: parent.top + anchors.right: parent.right + anchors.left: parent.left + + height: shadowWidth + gradient: Gradient { + GradientStop { position: 0.0; color: palette.alternateBase } + GradientStop { position: 1.0; color: "light gray" } + } + } + Text { + anchors.fill: parent + anchors.leftMargin: 6 + anchors.rightMargin: 6 + textFormat: Text.RichText + // this is actually rendering the width underneath so we force a repaint on each width change + text: " " + section + "
" + width + "
" + color: palette.windowText + } + } } add: Transition { + id: addTransition NumberAnimation { properties: "y" - from: -74 + 30 + from: addTransition.ViewTransition.destination.y - itemHeight duration: 120 } } @@ -51,12 +143,14 @@ Rectangle { NumberAnimation { from: -height properties: "y" + duration: 120 } } move: Transition { NumberAnimation { properties: "y" + duration: 120 } } @@ -79,85 +173,184 @@ Rectangle { removeDisplaced: Transition { NumberAnimation { properties: "y" + duration: 120 } } - delegate: Item { - height: 74 - width: timeEntryList.viewportWidth - Rectangle { - anchors.fill: parent - anchors.margins: 0.5 - color: "white" - MouseArea { - anchors.fill: parent - onClicked: { - if (modelData.Group) - toggl.toggleEntriesGroup(modelData.GroupName) - else - toggl.editTimeEntry(modelData.GUID, "description") - } + delegate: timeEntryDelegate + } + } + } + + Component { + id: timeEntryDelegate + Item { + z: index + height: visible ? itemHeight : 0 + width: timeEntryList.viewportWidth + Rectangle { + anchors { + fill: parent + leftMargin: 12 + rightMargin: anchors.leftMargin + topMargin: 0 + } + color: palette.base + + Rectangle { + anchors.left: parent.right + anchors.top: parent.bottom + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: 45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.5; color: "light gray" } + GradientStop { position: 1.0; color: palette.alternateBase } + } + } + + Rectangle { + anchors.right: parent.left + anchors.top: parent.bottom + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: -45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.5; color: "light gray" } + GradientStop { position: 0.0; color: palette.alternateBase } + } + } + + Rectangle { + anchors.right: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + + width: shadowWidth + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: palette.alternateBase } + GradientStop { position: 1.0; color: "light gray" } + } + } + + Rectangle { + anchors.left: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + width: shadowWidth + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: "light gray" } + GradientStop { position: 1.0; color: palette.alternateBase } + } + } + Rectangle { + anchors.top: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + height: shadowWidth + gradient: Gradient { + GradientStop { position: 0.0; color: "light gray" } + GradientStop { position: 1.0; color: palette.alternateBase } + } + } + + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: "#d4d4d4" + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (modelData.Group) + toggl.toggleEntriesGroup(modelData.GroupName) + else + toggl.editTimeEntry(modelData.GUID, "description") + } + } + RowLayout { + anchors.fill: parent + anchors.margins: 12 + spacing: 9 + Button { + Layout.alignment: Qt.AlignVCenter + visible: modelData.Group + implicitWidth: implicitHeight + contentItem: Text { + anchors.centerIn: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: modelData.GroupOpen ? "▵" : modelData.GroupItemCount + } + checked: modelData.GroupOpen + onClicked: toggl.toggleEntriesGroup(modelData.GroupName) + } + ColumnLayout { + Layout.fillHeight: true + Layout.fillWidth: true + Text { + Layout.fillWidth: true + text: modelData.Description.length > 0 ? modelData.Description : "(no description)" + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.pixelSize: 12 + verticalAlignment: Text.AlignVCenter } RowLayout { - anchors.fill: parent - Item { width: 1 } - ColumnLayout { - Layout.fillHeight: true - Layout.fillWidth: true - Text { - Layout.fillWidth: true - text: modelData.Description.length > 0 ? modelData.Description : "(no description)" - font.pixelSize: 12 - verticalAlignment: Text.AlignVCenter - } - RowLayout { - Layout.fillWidth: true - Text { - text: modelData.ClientLabel - font.pixelSize: 8 - } - Text { - text: modelData.ProjectLabel - color: modelData.Color - font.pixelSize: 8 - } - Text { - text: modelData.TaskLabel - font.pixelSize: 8 - } - Item { - Layout.fillWidth: true - } - } + Layout.fillWidth: true + Rectangle { + height: 3 + width: height + radius: height / 2 + visible: modelData.ClientLabel.length > 0 + color: modelData.Color + Layout.alignment: Qt.AlignVCenter } - Button { - visible: modelData.Group - implicitWidth: implicitHeight - contentItem: Text { - anchors.centerIn: parent - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: modelData.GroupOpen ? "▵" : modelData.GroupItemCount - } - checked: modelData.GroupOpen - onClicked: toggl.toggleEntriesGroup(modelData.GroupName) + Text { + text: modelData.ClientLabel + color: modelData.Color + font.pixelSize: 8 } - Button { - implicitWidth: implicitHeight - contentItem: Text { - anchors.centerIn: parent - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: "▸" - } - onClicked: toggl.continueTimeEntry(modelData.GUID) + Text { + text: modelData.ProjectLabel + font.pixelSize: 8 } Text { - Layout.alignment: Qt.AlignVCenter - text: modelData.Duration + text: modelData.TaskLabel + font.pixelSize: 8 } - Item { width: 1 } + Item { + Layout.fillWidth: true + } + } + } + Button { + implicitWidth: implicitHeight + contentItem: Text { + anchors.centerIn: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: "▸" } + onClicked: toggl.continueTimeEntry(modelData.GUID) + } + Text { + Layout.alignment: Qt.AlignVCenter + text: modelData.Duration } } } diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 66e6aadb43..88ec1139e0 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -74,7 +74,7 @@ Rectangle { } onTextEdited: { if (focus) { - autocomplete.visible = true + //autocomplete.visible = true } } onFocusChanged: if (!focus) autocomplete.visible = false @@ -87,7 +87,7 @@ Rectangle { right: parent.right } filter: description.text - model: toggl.minitimerAutocomplete + //model: toggl.minitimerAutocomplete } } RowLayout { From 0464da4adeaa1e0a0fdf668314dd8163bff993e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 18 Oct 2019 11:34:46 +0200 Subject: [PATCH 31/83] UI tweaks, especially placing the time entry edit view next to the window --- src/ui/linux/TogglDesktop/MainWindow.qml | 36 ++++++++++++++++--- .../linux/TogglDesktop/TimeEntryEditView.qml | 1 - src/ui/linux/TogglDesktop/TimerView.qml | 15 +++++++- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index d3ccd87d92..043c41766e 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -49,22 +49,50 @@ ApplicationWindow { Connections { target: toggl onDisplayLogin: { - if (open) + if (open) { mainView.source = "LoginView.qml" + timeEntryEdit.visible = false + } } onDisplayTimeEntryList: { - if (open) + if (open) { mainView.source = "TimeEntryListView.qml" + timeEntryEdit.visible = false + } } onDisplayTimeEntryEditor: { if (open) { - mainView.setSource("qrc:/TimeEntryEditView.qml", { timeEntry: view } ) + timeEntryEdit.timeEntry = view + timeEntryEdit.visible = true + } + else { + timeEntryEdit.visible = false } } } Loader { id: mainView - anchors.fill: parent + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: timeEntryEdit.visible ? timeEntryEdit.left : parent.right } + + TimeEntryEditView { + id: timeEntryEdit + visible: false + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + width: 360 + onVisibleChanged: { + if (visible) + window.width += width + else + window.width -= width + } + } + } diff --git a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml index 609d105818..e198ed15eb 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml @@ -3,7 +3,6 @@ import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 Rectangle { - anchors.fill: parent color: palette.window property var timeEntry: null diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 88ec1139e0..a541d38f74 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -11,7 +11,7 @@ Rectangle { property var runningTimeEntry: null function start() { - if (!running && description.text.length > 0) { + if (!running) { var dur = duration.text === "00:00" ? "" : duration.text toggl.start(description.text, dur, 0, 0, "", false) description.text = "" @@ -30,6 +30,19 @@ Rectangle { } } + MouseArea { + anchors.fill: timerContainer + onClicked: { + if (running) { + toggl.editRunningTimeEntry("description") + mouse.accepted = true + } + else { + mouse.accepted = false + } + } + } + RowLayout { id: timerContainer height: parent.height From 246cbca07b4eb65d040d71fa81624a674523655c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 18 Oct 2019 12:09:54 +0200 Subject: [PATCH 32/83] Enable adding and removing tags in a nice fashion (linux) --- .../linux/TogglDesktop/TimeEntryEditView.qml | 158 ++++++++++++++++++ .../TogglDesktop/timeentrycellwidget.cpp | 2 +- .../TogglDesktop/timeentryeditorwidget.cpp | 2 +- src/ui/linux/TogglDesktop/timeentryview.cpp | 4 +- src/ui/linux/TogglDesktop/timeentryview.h | 2 +- src/ui/linux/TogglDesktop/timerwidget.cpp | 2 +- src/ui/linux/TogglDesktop/toggl.cpp | 12 ++ src/ui/linux/TogglDesktop/toggl.h | 9 +- 8 files changed, 182 insertions(+), 9 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml index e198ed15eb..ef67ca9bbf 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml @@ -77,6 +77,164 @@ Rectangle { border.color: palette.dark border.width: 1 } + ColumnLayout { + anchors.fill: parent + ListView { + clip: true + Layout.fillHeight: true + Layout.fillWidth: true + model: timeEntry.Tags + delegate: Item { + width: childrenRect.width + 4 + height: childrenRect.height + 4 + Rectangle { + x: 2 + y: 2 + width: selectedTagLayout.width + 4 + height: selectedTagLayout.height + 4 + color: "white" + border.width: 1 + border.color: "#d4d4d4" + radius: 2 + + RowLayout { + x: 2 + y: 2 + id: selectedTagLayout + Text { + id: selectedTagText + text: modelData + font.pixelSize: 10 + + Rectangle { + Behavior on opacity { NumberAnimation { } } + opacity: selectedTagMouse.containsMouse ? 1.0 : 0.0 + visible: opacity > 0.0 + width: 10 + height: 10 + radius: 3 + anchors.centerIn: parent + color: "red" + Text { + anchors.centerIn: parent + text: "x" + font.pointSize: 8 + } + } + } + } + MouseArea { + id: selectedTagMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + var list = timeEntry.Tags + var index = list.indexOf(modelData); + if (index > -1) { + list.splice(index, 1); + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + } + } + } + } + } + } + + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#c3c3c3" + } + + + ListView { + clip: true + Layout.fillHeight: true + Layout.fillWidth: true + model: toggl.tags.filter(n => !timeEntry.Tags.includes(n)) + delegate: Item { + width: childrenRect.width + 4 + height: childrenRect.height + 4 + Rectangle { + x: 2 + y: 2 + width: tagLayout.width + 4 + height: tagLayout.height + 4 + color: "white" + border.width: 1 + border.color: "#d4d4d4" + radius: 2 + + RowLayout { + id: tagLayout + x: 2 + y: 2 + Text { + id: tagText + text: modelData + font.pixelSize: 10 + Rectangle { + Behavior on opacity { NumberAnimation { } } + opacity: tagMouse.containsMouse ? 1.0 : 0.0 + visible: opacity > 0.0 + width: 10 + height: 10 + radius: 3 + anchors.centerIn: parent + color: "green" + Text { + anchors.centerIn: parent + text: "+" + font.pointSize: 8 + } + } + } + } + MouseArea { + id: tagMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + var list = timeEntry.Tags + list.push(modelData) + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + } + } + } + } + } + + RowLayout { + Layout.fillWidth: true + Text { + text: "New:" + } + TextField { + Layout.fillWidth: true + id: newTagField + onAccepted: { + if (text.length > 0) { + var list = timeEntry.Tags + list.push(newTagField.text) + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + newTagField.text = "" + } + } + } + Button { + Layout.preferredWidth: 64 + text: "Add" + onClicked: { + if (text.length > 0) { + var list = timeEntry.Tags + list.push(newTagField.text) + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + newTagField.text = "" + } + } + } + } + } } } diff --git a/src/ui/linux/TogglDesktop/timeentrycellwidget.cpp b/src/ui/linux/TogglDesktop/timeentrycellwidget.cpp index a51c9b5213..ed3fe5b8d8 100644 --- a/src/ui/linux/TogglDesktop/timeentrycellwidget.cpp +++ b/src/ui/linux/TogglDesktop/timeentrycellwidget.cpp @@ -68,7 +68,7 @@ void TimeEntryCellWidget::display(TimeEntryView *view) { ui->tags->setToolTip( QString("

" + - (view->Tags).replace(QString("\t"), QString(", ")) + "

")); + (view->Tags.join("\t")).replace(QString("\t"), QString(", ")) + "

")); if (view->Description.length() > 0) { ui->description->setToolTip( QString("

" + diff --git a/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp b/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp index 95ea040fb8..2cc99116da 100644 --- a/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp +++ b/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp @@ -281,7 +281,7 @@ void TimeEntryEditorWidget::displayTimeEntryEditor( ui->addNewProject->setVisible(false); } - QStringList tags = view->Tags.split("\t", QString::SkipEmptyParts); + QStringList tags = view->Tags; tags.sort(); previousTagList = tags.join("\t"); diff --git a/src/ui/linux/TogglDesktop/timeentryview.cpp b/src/ui/linux/TogglDesktop/timeentryview.cpp index df8c583292..1f037197a6 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.cpp +++ b/src/ui/linux/TogglDesktop/timeentryview.cpp @@ -63,7 +63,7 @@ TimeEntryView *TimeEntryView::importOne(TogglTimeEntryView *view) { result->Color = QString(view->Color); result->GUID = QString(view->GUID); result->Billable = view->Billable; - result->Tags = QString(view->Tags); + result->Tags = QString(view->Tags).split("\t", QString::SkipEmptyParts); result->Started = view->Started; result->Ended = view->Ended; result->StartTimeString = QString(view->StartTimeString); @@ -115,7 +115,7 @@ void TimeEntryView::update(const TogglTimeEntryView *view) { ColorSet(view->Color); GUIDSet(view->GUID); BillableSet(view->Billable); - TagsSet(view->Tags); + TagsSet(QString(view->Tags).split("\t", QString::SkipEmptyParts)); StartedSet(view->Started); EndedSet(view->Ended); StartTimeStringSet(view->StartTimeString); diff --git a/src/ui/linux/TogglDesktop/timeentryview.h b/src/ui/linux/TogglDesktop/timeentryview.h index 07f46bcde7..9b6fecd290 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.h +++ b/src/ui/linux/TogglDesktop/timeentryview.h @@ -68,7 +68,7 @@ class TimeEntryView : public QObject { PROPERTY(QString, Color) PROPERTY(QString, GUID) PROPERTY(bool, Billable) - PROPERTY(QString, Tags) + PROPERTY(QStringList, Tags) PROPERTY(uint64_t, Started) PROPERTY(uint64_t, Ended) PROPERTY(QString, StartTimeString) diff --git a/src/ui/linux/TogglDesktop/timerwidget.cpp b/src/ui/linux/TogglDesktop/timerwidget.cpp index 6b680204cd..2e21c18d0e 100644 --- a/src/ui/linux/TogglDesktop/timerwidget.cpp +++ b/src/ui/linux/TogglDesktop/timerwidget.cpp @@ -221,7 +221,7 @@ void TimerWidget::displayRunningTimerState( ui->billable->setVisible(te->Billable); ui->tags->setVisible(!te->Tags.isEmpty()); ui->tags->setToolTip(QString("

" + - te->Tags + "

")); + te->Tags.join(", ") + "

")); duration = te->DurationInSecondsGet(); diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index 980e4833f5..196fe0d002 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -241,6 +241,14 @@ void TogglApi::displayProjectAutocomplete(QVector list) { emit projectAutocompleteChanged(); } +void TogglApi::displayTags(QVector list) { + tags_.clear(); + for (auto i : list) { + tags_.append(i->Name); + } + emit tagsChanged(); +} + TogglApi::TogglApi(QObject *parent, QString logPathOverride, QString dbPathOverride) : QObject(parent) , shutdown(false) @@ -389,6 +397,10 @@ void TogglApi::importTimeEntries(TogglTimeEntryView *first) { timeEntries_->importList(first); } +QStringList TogglApi::tags() { + return tags_; +} + bool TogglApi::startEvents() { return toggl_ui_start(ctx); } diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index 68a76e069a..47923c18a2 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -30,6 +30,7 @@ class TogglApi : public QObject { Q_PROPERTY(AutocompleteProxyModel* minitimerAutocomplete READ minitimerAutocomplete CONSTANT) Q_PROPERTY(AutocompleteProxyModel* projectAutocomplete READ projectAutocomplete CONSTANT) Q_PROPERTY(TimeEntryViewStorage *timeEntries READ timeEntries CONSTANT) + Q_PROPERTY(QStringList tags READ tags NOTIFY tagsChanged) public: TogglApi( @@ -59,11 +60,13 @@ class TogglApi : public QObject { AutocompleteProxyModel *projectAutocomplete(); TimeEntryViewStorage *timeEntries(); void importTimeEntries(TogglTimeEntryView *first); + QStringList tags(); signals: void countriesChanged(); void timeEntryAutocompleteChanged(); void minitimerAutocompleteChanged(); void projectAutocompleteChanged(); + void tagsChanged(); private: QList countries_; AutocompleteListModel *timeEntryModel_; @@ -73,6 +76,7 @@ class TogglApi : public QObject { AutocompleteProxyModel *minitimerAutocomplete_; AutocompleteProxyModel *projectAutocomplete_; TimeEntryViewStorage *timeEntries_; + QStringList tags_; public slots: void setCountries(QVector list); @@ -81,6 +85,8 @@ class TogglApi : public QObject { void displayMinitimerAutocomplete(QVector list); void displayProjectAutocomplete(QVector list); + void displayTags(QVector list); + /////////////////////////////////////////////////////// bool startEvents(); @@ -329,9 +335,6 @@ class TogglApi : public QObject { void displayClientSelect( QVector list); - void displayTags( - QVector list); - void displayWorkspaceSelect( QVector list); From 0ea4420aef6f65dbdf22eb8a397e8c70f085e741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 18 Oct 2019 12:23:11 +0200 Subject: [PATCH 33/83] Lay out the tags on a line (linux) It changes the dimensions of the layout but it works pretty nice overall --- src/ui/linux/TogglDesktop/MainWindow.qml | 3 - .../linux/TogglDesktop/TimeEntryEditView.qml | 177 +++++++++--------- 2 files changed, 90 insertions(+), 90 deletions(-) diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index 043c41766e..adf311fa3e 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -65,9 +65,6 @@ ApplicationWindow { timeEntryEdit.timeEntry = view timeEntryEdit.visible = true } - else { - timeEntryEdit.visible = false - } } } diff --git a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml index ef67ca9bbf..71e485de9d 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml @@ -79,60 +79,61 @@ Rectangle { } ColumnLayout { anchors.fill: parent - ListView { - clip: true + Flow { Layout.fillHeight: true Layout.fillWidth: true - model: timeEntry.Tags - delegate: Item { - width: childrenRect.width + 4 - height: childrenRect.height + 4 - Rectangle { - x: 2 - y: 2 - width: selectedTagLayout.width + 4 - height: selectedTagLayout.height + 4 - color: "white" - border.width: 1 - border.color: "#d4d4d4" - radius: 2 - - RowLayout { + Repeater { + model: timeEntry.Tags + delegate: Item { + width: childrenRect.width + 4 + height: childrenRect.height + 4 + Rectangle { x: 2 y: 2 - id: selectedTagLayout - Text { - id: selectedTagText - text: modelData - font.pixelSize: 10 + width: selectedTagLayout.width + 4 + height: selectedTagLayout.height + 4 + color: "white" + border.width: 1 + border.color: "#d4d4d4" + radius: 2 + + RowLayout { + x: 2 + y: 2 + id: selectedTagLayout + Text { + id: selectedTagText + text: modelData + font.pixelSize: 10 - Rectangle { - Behavior on opacity { NumberAnimation { } } - opacity: selectedTagMouse.containsMouse ? 1.0 : 0.0 - visible: opacity > 0.0 - width: 10 - height: 10 - radius: 3 - anchors.centerIn: parent - color: "red" - Text { + Rectangle { + Behavior on opacity { NumberAnimation { } } + opacity: selectedTagMouse.containsMouse ? 1.0 : 0.0 + visible: opacity > 0.0 + width: 10 + height: 10 + radius: 3 anchors.centerIn: parent - text: "x" - font.pointSize: 8 + color: "red" + Text { + anchors.centerIn: parent + text: "x" + font.pointSize: 8 + } } } } - } - MouseArea { - id: selectedTagMouse - anchors.fill: parent - hoverEnabled: true - onClicked: { - var list = timeEntry.Tags - var index = list.indexOf(modelData); - if (index > -1) { - list.splice(index, 1); - toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + MouseArea { + id: selectedTagMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + var list = timeEntry.Tags + var index = list.indexOf(modelData); + if (index > -1) { + list.splice(index, 1); + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + } } } } @@ -147,57 +148,59 @@ Rectangle { } - ListView { + Flow { clip: true Layout.fillHeight: true Layout.fillWidth: true - model: toggl.tags.filter(n => !timeEntry.Tags.includes(n)) - delegate: Item { - width: childrenRect.width + 4 - height: childrenRect.height + 4 - Rectangle { - x: 2 - y: 2 - width: tagLayout.width + 4 - height: tagLayout.height + 4 - color: "white" - border.width: 1 - border.color: "#d4d4d4" - radius: 2 - - RowLayout { - id: tagLayout + Repeater { + model: toggl.tags.filter(n => !timeEntry.Tags.includes(n)) + delegate: Item { + width: childrenRect.width + 4 + height: childrenRect.height + 4 + Rectangle { x: 2 y: 2 - Text { - id: tagText - text: modelData - font.pixelSize: 10 - Rectangle { - Behavior on opacity { NumberAnimation { } } - opacity: tagMouse.containsMouse ? 1.0 : 0.0 - visible: opacity > 0.0 - width: 10 - height: 10 - radius: 3 - anchors.centerIn: parent - color: "green" - Text { + width: tagLayout.width + 4 + height: tagLayout.height + 4 + color: "white" + border.width: 1 + border.color: "#d4d4d4" + radius: 2 + + RowLayout { + id: tagLayout + x: 2 + y: 2 + Text { + id: tagText + text: modelData + font.pixelSize: 10 + Rectangle { + Behavior on opacity { NumberAnimation { } } + opacity: tagMouse.containsMouse ? 1.0 : 0.0 + visible: opacity > 0.0 + width: 10 + height: 10 + radius: 3 anchors.centerIn: parent - text: "+" - font.pointSize: 8 + color: "green" + Text { + anchors.centerIn: parent + text: "+" + font.pointSize: 8 + } } } } - } - MouseArea { - id: tagMouse - anchors.fill: parent - hoverEnabled: true - onClicked: { - var list = timeEntry.Tags - list.push(modelData) - toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + MouseArea { + id: tagMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + var list = timeEntry.Tags + list.push(modelData) + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + } } } } From 451fbca8582a8b341114e6232228a7bfd76ca888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 18 Oct 2019 16:48:31 +0200 Subject: [PATCH 34/83] Redesign the QML project UI (linux) --- src/ui/linux/TogglDesktop/LoginView.qml | 182 +++++++++++++++--- src/ui/linux/TogglDesktop/MainWindow.qml | 22 ++- src/ui/linux/TogglDesktop/Resources.qrc | 6 + .../linux/TogglDesktop/TimeEntryEditView.qml | 29 +-- .../linux/TogglDesktop/TimeEntryListView.qml | 4 +- src/ui/linux/TogglDesktop/TogglButton.qml | 17 ++ .../TogglDesktop/TogglButtonBackground.qml | 32 +++ src/ui/linux/TogglDesktop/TogglCheckBox.qml | 38 ++++ src/ui/linux/TogglDesktop/TogglComboBox.qml | 42 ++++ src/ui/linux/TogglDesktop/TogglSwitch.qml | 35 ++++ src/ui/linux/TogglDesktop/TogglTextField.qml | 13 ++ src/ui/linux/TogglDesktop/images/logo.png | Bin 2955 -> 18702 bytes src/ui/linux/TogglDesktop/toggl.cpp | 15 +- .../Images/Wide310x150Logo.scale-200.png | Bin 18702 -> 0 bytes 14 files changed, 388 insertions(+), 47 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/TogglButton.qml create mode 100644 src/ui/linux/TogglDesktop/TogglButtonBackground.qml create mode 100644 src/ui/linux/TogglDesktop/TogglCheckBox.qml create mode 100644 src/ui/linux/TogglDesktop/TogglComboBox.qml create mode 100644 src/ui/linux/TogglDesktop/TogglSwitch.qml create mode 100644 src/ui/linux/TogglDesktop/TogglTextField.qml delete mode 100644 src/ui/windows/TogglDesktop/TogglDesktop.Package/Images/Wide310x150Logo.scale-200.png diff --git a/src/ui/linux/TogglDesktop/LoginView.qml b/src/ui/linux/TogglDesktop/LoginView.qml index 6978909465..bd6ea158a2 100644 --- a/src/ui/linux/TogglDesktop/LoginView.qml +++ b/src/ui/linux/TogglDesktop/LoginView.qml @@ -27,8 +27,129 @@ Item { } } + property real shadowWidth: 9 + + Rectangle { + anchors.fill: loginColumn + anchors.margins: -24 + + + Rectangle { + anchors.left: parent.right + anchors.top: parent.bottom + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: 45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.5; color: "light gray" } + GradientStop { position: 1.0; color: palette.alternateBase } + } + } + + Rectangle { + anchors.right: parent.left + anchors.top: parent.bottom + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: -45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.5; color: "light gray" } + GradientStop { position: 0.0; color: palette.alternateBase } + } + } + + Rectangle { + anchors.left: parent.right + anchors.bottom: parent.top + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: -45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.5; color: "light gray" } + GradientStop { position: 1.0; color: palette.alternateBase } + } + } + + Rectangle { + anchors.right: parent.left + anchors.bottom: parent.top + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: 45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.5; color: "light gray" } + GradientStop { position: 0.0; color: palette.alternateBase } + } + } + + Rectangle { + anchors.right: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + + width: shadowWidth + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: palette.alternateBase } + GradientStop { position: 1.0; color: "light gray" } + } + } + + Rectangle { + anchors.left: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + width: shadowWidth + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: "light gray" } + GradientStop { position: 1.0; color: palette.alternateBase } + } + } + Rectangle { + anchors.top: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + height: shadowWidth + gradient: Gradient { + GradientStop { position: 0.0; color: "light gray" } + GradientStop { position: 1.0; color: palette.alternateBase } + } + } + Rectangle { + anchors.bottom: parent.top + anchors.right: parent.right + anchors.left: parent.left + + height: shadowWidth + gradient: Gradient { + GradientStop { position: 0.0; color: palette.alternateBase } + GradientStop { position: 1.0; color: "light gray" } + } + } + } + Column { - y: parent.height / 5 + id: loginColumn + y: parent.height / 6.7 anchors.horizontalCenter: parent.horizontalCenter width: termsAndConditionsMetrics.width + 30 spacing: 18 @@ -54,34 +175,42 @@ Item { Image { anchors.horizontalCenter: parent.horizontalCenter source: "qrc:/images/logo.png" + width: parent.width * 0.67 + fillMode: Image.PreserveAspectFit + antialiasing: true + mipmap: true } - Item { height: 3; width: 1 } - - RowLayout { + Item { width: parent.width + height: loginSwitch.height + Text { - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter + anchors.right: loginSwitch.left + anchors.rightMargin: 9 + anchors.verticalCenter: parent.verticalCenter text: "Login" - color: loggingIn ? "white" : "#9a9a9a" + color: loggingIn ? palette.text : disabledPalette.text } - Switch { + TogglSwitch { id: loginSwitch + anchors.horizontalCenter: parent.horizontalCenter + Layout.alignment: Qt.AlignCenter onCheckedChanged: username.forceActiveFocus() } Text { - Layout.fillWidth: true - horizontalAlignment: Text.AlignHCenter + anchors.left: loginSwitch.right + anchors.leftMargin: 9 + anchors.verticalCenter: parent.verticalCenter text: "Sign up" - color: signingUp ? "white" : "#9a9a9a" + color: signingUp ? palette.text : disabledPalette.text } } RowLayout { width: parent.width visible: signingUp - Button { + TogglButton { id: signupWithEmail Layout.fillWidth: true checkable: true @@ -89,7 +218,7 @@ Item { checked: true text: "With email" } - Button { + TogglButton { id: signupWithGoogle Layout.fillWidth: true checkable: true @@ -98,7 +227,7 @@ Item { } } - TextField { + TogglTextField { width: parent.width id: username focus: true @@ -108,17 +237,17 @@ Item { onAccepted: act() } - TextField { + TogglTextField { width: parent.width id: password anchors.horizontalCenter: parent.horizontalCenter placeholderText: "Password" echoMode: TextField.Password visible: loggingIn || !signupWithGoogle.checked - onAccepted: act() + onAccepted: act() } - ComboBox { + TogglComboBox { id: country width: parent.width anchors.horizontalCenter: parent.horizontalCenter @@ -126,27 +255,30 @@ Item { model: toggl.countries textRole: "Text" currentIndex: -1 - property int selectedID: toggl.countries[currentIndex].ID + property int selectedID: toggl.countries && toggl.countries[currentIndex] ? toggl.countries[currentIndex].ID : -1 displayText: currentIndex < 0 ? "Please select your country" : currentText } - Row { + RowLayout { id: termsAndConditionsRow width: parent.width visible: signingUp anchors.horizontalCenter: parent.horizontalCenter - CheckBox { + TogglCheckBox { id: termsAndConditions } Text { id: termsAndConditionsText anchors.verticalCenter: parent.verticalCenter - text: "Agree to conditions and terms TBD" - color: "#d2d2d2" + Layout.fillWidth: true + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.pointSize: 9 + text: "I agree to terms of service and privacy policy" + color: palette.text } } - Button { + TogglButton { width: parent.width anchors.horizontalCenter: parent.horizontalCenter text: signingUp ? "Sign up" : "Log in" @@ -154,10 +286,10 @@ Item { onClicked: act() } - Button { + TogglButton { width: parent.width anchors.horizontalCenter: parent.horizontalCenter - text: "Log in with Google (not implemented)" + text: "Log in with Google" visible: loggingIn enabled: false } diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index adf311fa3e..df18ebcfd1 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -7,14 +7,27 @@ import QtQuick.Controls 1.4 ApplicationWindow { id: window visible: true - minimumWidth: 360 - width: 360 + minimumWidth: 400 + width: 400 minimumHeight: 640 height: 640 + function mixColors(a, b, ratio) { + return Qt.rgba( + ratio * a.r + (1.0 - ratio) * b.r, + ratio * a.g + (1.0 - ratio) * b.g, + ratio * a.b + (1.0 - ratio) * b.b, + ratio * a.a + (1.0 - ratio) * b.a, + ) + } + SystemPalette { id: palette } + SystemPalette { + id: disabledPalette + colorGroup: SystemPalette.Disabled + } menuBar: MenuBar { Menu { @@ -32,7 +45,7 @@ ApplicationWindow { Rectangle { anchors.fill: parent - color: "#202020" + color: palette.base clip: true ErrorOverlay { @@ -43,7 +56,8 @@ ApplicationWindow { TextMetrics { id: termsAndConditionsMetrics - text: "Agree to conditions and terms TBD" + font.pointSize: 9 + text: "I agree to terms of service and privacy policy" } Connections { diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index b307da25dd..ca37163d94 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -28,5 +28,11 @@ TimerView.qml TimeEntryEditView.qml AutocompleteView.qml + TogglButton.qml + TogglTextField.qml + TogglComboBox.qml + TogglCheckBox.qml + TogglSwitch.qml + TogglButtonBackground.qml diff --git a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml index 71e485de9d..0fe422598b 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml @@ -3,25 +3,26 @@ import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 Rectangle { - color: palette.window + color: palette.alternateBase property var timeEntry: null ColumnLayout { anchors.fill: parent - anchors.margins: 3 + anchors.margins: 12 + spacing: 9 - TextField { + TogglTextField { Layout.fillWidth: true text: timeEntry.Description } - TextField { + TogglTextField { Layout.fillWidth: true text: timeEntry.ClientLabel + " . " + timeEntry.ProjectLabel } - Button { + TogglButton { Layout.alignment: Qt.AlignRight text: "Add project" } @@ -32,7 +33,7 @@ Rectangle { Text { text: "Duration:" } - TextField { + TogglTextField { Layout.fillWidth: true Layout.columnSpan: 3 text: timeEntry.Duration @@ -41,14 +42,14 @@ Rectangle { Text { text: "Start-end time:" } - TextField { + TogglTextField { Layout.fillWidth: true text: timeEntry.StartTimeString } Text { text: "-" } - TextField { + TogglTextField { Layout.fillWidth: true text: timeEntry.EndTimeString } @@ -56,7 +57,7 @@ Rectangle { Text { text: "Date:" } - TextField { + TogglTextField { Layout.fillWidth: true Layout.columnSpan: 3 text: new Date(Date(timeEntry.Started)).toLocaleDateString(Qt.locale(), Locale.ShortFormat) @@ -212,7 +213,7 @@ Rectangle { Text { text: "New:" } - TextField { + TogglTextField { Layout.fillWidth: true id: newTagField onAccepted: { @@ -224,7 +225,7 @@ Rectangle { } } } - Button { + TogglButton { Layout.preferredWidth: 64 text: "Add" onClicked: { @@ -251,15 +252,15 @@ Rectangle { } } RowLayout { - Button { + TogglButton { text: "Done" onClicked: toggl.viewTimeEntryList() } - Button { + TogglButton { text: "Delete" onClicked: toggl.deleteTimeEntry(timeEntry.GUID) } - Button { + TogglButton { text: "Cancel" onClicked: toggl.viewTimeEntryList() } diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index c716dbf20f..df259f6d98 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -287,7 +287,7 @@ Rectangle { anchors.fill: parent anchors.margins: 12 spacing: 9 - Button { + TogglButton { Layout.alignment: Qt.AlignVCenter visible: modelData.Group implicitWidth: implicitHeight @@ -338,7 +338,7 @@ Rectangle { } } } - Button { + TogglButton { implicitWidth: implicitHeight contentItem: Text { anchors.centerIn: parent diff --git a/src/ui/linux/TogglDesktop/TogglButton.qml b/src/ui/linux/TogglDesktop/TogglButton.qml new file mode 100644 index 0000000000..26d79089d7 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglButton.qml @@ -0,0 +1,17 @@ +import QtQuick 2.0 +import QtQuick.Controls 2.12 + +Button { + id: control + background: TogglButtonBackground { + control: control + } + contentItem: Text { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: control.enabled ? control.pressed | control.checked ? palette.button + : palette.buttonText + : disabledPalette.buttonText + text: control.text + } +} diff --git a/src/ui/linux/TogglDesktop/TogglButtonBackground.qml b/src/ui/linux/TogglDesktop/TogglButtonBackground.qml new file mode 100644 index 0000000000..c5f356d883 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglButtonBackground.qml @@ -0,0 +1,32 @@ +import QtQuick 2.0 + +Rectangle { + property Item control + clip: true + implicitWidth: 100 + implicitHeight: 40 + radius: 2 + + property color borderColor: mixColors(palette.base, palette.text, 0.33) + //property color shadowColor: control.enabled ? control.checked ? "#595959" : control.hovered ? "#e5e5e5" : "#d4d4d4" : baseColor + //property color baseColor: control.enabled ? control.checked ? "#7a7a7a" : control.hovered ? "#d4d4d4" : "#c3c3c3" : "#b2b2b2" + property color shadowColor: control.enabled ? control.pressed | control.checked ? mixColors(palette.button, palette.buttonText, 0.5) + : control.hovered ? mixColors(palette.button, palette.light, 0.5) + : mixColors(palette.button, palette.light, 0.5) + : baseColor + property color baseColor: control.enabled ? control.pressed | control.checked ? mixColors(palette.buttonText, palette.button, 0.66) + : control.hovered ? mixColors(palette.button, palette.light, 0.5) + : palette.button + : disabledPalette.button + + Behavior on shadowColor { ColorAnimation { duration: 120 } } + Behavior on baseColor { ColorAnimation { duration: 120 } } + + gradient: Gradient { + GradientStop { position: 0.0; color: shadowColor } + GradientStop { position: 0.12; color: baseColor } + } + + border.width: 1 + border.color: borderColor +} diff --git a/src/ui/linux/TogglDesktop/TogglCheckBox.qml b/src/ui/linux/TogglDesktop/TogglCheckBox.qml new file mode 100644 index 0000000000..55cef0ad61 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglCheckBox.qml @@ -0,0 +1,38 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +CheckBox { + id: control + indicator: Rectangle { + implicitWidth: 18 + implicitHeight: 18 + x: control.leftPadding + y: parent.height / 2 - height / 2 + radius: 3 + color: palette.base + border.color: mixColors(palette.base, palette.text, 0.33) + border.width: 1 + + Rectangle { + width: parent.width - 8 + height: parent.height - 8 + x: 4 + y: 4 + radius: 1 + antialiasing: true + color: palette.text + opacity: control.checked ? 1.0 : 0.0 + Behavior on opacity { NumberAnimation { duration: 120 } } + visible: opacity > 0.0 + } + } + + contentItem: Text { + text: control.text + font: control.font + opacity: enabled ? 1.0 : 0.3 + color: control.down ? "#17a81a" : "#21be2b" + verticalAlignment: Text.AlignVCenter + leftPadding: control.indicator.width + control.spacing + } +} diff --git a/src/ui/linux/TogglDesktop/TogglComboBox.qml b/src/ui/linux/TogglDesktop/TogglComboBox.qml new file mode 100644 index 0000000000..d982e1e2c8 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglComboBox.qml @@ -0,0 +1,42 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +ComboBox { + id: control + background: TogglButtonBackground { + control: control + } + indicator: Canvas { + id: canvas + x: control.width - width - control.rightPadding + y: control.topPadding + (control.availableHeight - height) / 2 + width: 10 + height: 5 + contextType: "2d" + + Connections { + target: control + onPressedChanged: canvas.requestPaint() + } + + onPaint: { + context.reset(); + context.moveTo(0, 0); + context.lineTo(width, 0); + context.lineTo(width / 2, height); + context.closePath(); + context.fillStyle = palette.buttonText; + context.fill(); + } + } + contentItem: Text { + leftPadding: 0 + rightPadding: control.indicator.width + control.spacing + + text: control.displayText + font: control.font + color: control.currentIndex < 0 ? disabledPalette.buttonText : palette.buttonText + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } +} diff --git a/src/ui/linux/TogglDesktop/TogglSwitch.qml b/src/ui/linux/TogglDesktop/TogglSwitch.qml new file mode 100644 index 0000000000..ae045280dc --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglSwitch.qml @@ -0,0 +1,35 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +Switch { + id: control + width: 40 + indicator: Rectangle { + implicitWidth: 40 + implicitHeight: 20 + x: control.leftPadding + y: parent.height / 2 - height / 2 + radius: 13 + color: palette.base + border.width: 1 + border.color: mixColors(palette.base, palette.text, 0.33) + + Rectangle { + x: control.checked ? parent.width - width - 1 : 1 + y: 1 + + width: parent.height - 2 + height: parent.height - 2 + radius: parent.height / 2 - 1 + + color: palette.button + + Behavior on x { + NumberAnimation { + duration: 120 + } + } + border.color: mixColors(palette.base, palette.text, 0.33) + } + } +} diff --git a/src/ui/linux/TogglDesktop/TogglTextField.qml b/src/ui/linux/TogglDesktop/TogglTextField.qml new file mode 100644 index 0000000000..0f296454cf --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglTextField.qml @@ -0,0 +1,13 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +TextField { + id: control + color: palette.text + background: Rectangle { + border.width: 1 + radius: 2 + border.color: mixColors(palette.base, palette.text, 0.33) + color: palette.base + } +} diff --git a/src/ui/linux/TogglDesktop/images/logo.png b/src/ui/linux/TogglDesktop/images/logo.png index a4e8be4ba237b38321e3ae626b5beb54ef59fb3b..2b1da4f669ea1d64d0be36796abfaacff2b745ea 100644 GIT binary patch literal 18702 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 literal 2955 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 #include +#include + #include // NOLINT #include "./../../../toggl_api.h" @@ -207,8 +209,11 @@ void on_project_colors( void on_countries( TogglCountryView *first) { - TogglApi::instance->setCountries( - CountryView::importAll(first)); + auto v = CountryView::importAll(first); + for (auto i : v) { + i->moveToThread(TogglApi::instance->uiThread_); + } + TogglApi::instance->setCountries(v); } template @@ -221,6 +226,9 @@ void replaceList(const QVector &from, QList &to) { } void TogglApi::setCountries(QVector list) { + for (auto i : list) { + QQmlEngine::setObjectOwnership(i, QQmlEngine::CppOwnership); + } replaceList(list, countries_); emit countriesChanged(); } @@ -374,6 +382,9 @@ bool TogglApi::notifyBugsnag( } QList TogglApi::countries() { + for (auto i : countries_) { + QQmlEngine::setObjectOwnership(i, QQmlEngine::CppOwnership); + } return countries_; } diff --git a/src/ui/windows/TogglDesktop/TogglDesktop.Package/Images/Wide310x150Logo.scale-200.png b/src/ui/windows/TogglDesktop/TogglDesktop.Package/Images/Wide310x150Logo.scale-200.png deleted file mode 100644 index 2b1da4f669ea1d64d0be36796abfaacff2b745ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18702 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 From b6e0aca2bb4333ab1dde4bf21524f2ad73ba2bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 29 Nov 2019 11:52:12 +0100 Subject: [PATCH 35/83] Make the Linux app compile on macOS (all) --- CMakeLists.txt | 11 ++++- src/CMakeLists.txt | 26 +++++++++++- src/platforminfo.h | 4 +- src/ui/linux/TogglDesktop/CMakeLists.txt | 6 ++- .../TogglDesktop/idlenotificationwidget.cpp | 12 +++++- .../TogglDesktop/idlenotificationwidget.h | 4 ++ src/ui/linux/TogglDesktop/qmlobjectlist.cc | 2 +- third_party/CMakeLists.txt | 41 ++++++++++++++----- .../gui/qxtwindowsystem_mac.cpp | 2 +- 9 files changed, 88 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b231d7a81a..53884f01f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,9 @@ option(TOGGL_PRODUCTION_BUILD "Use production servers in the app" OFF) option(TOGGL_ALLOW_UPDATE_CHECK "Allow the app to check for updates" OFF) option(USE_BUNDLED_LIBRARIES "Prefer bundled libraries to bundled ones" OFF) option(INSTALL_HIRES_ICONS "Do not install icons over 512x512" OFF) +option(TOGGL_BUILD_TESTS "Build the Toggl test suite" ON) + +set(ENV{OPENSSL_ROOT_DIR} ${OPENSSL_ROOT_DIR}) if ("${TOGGL_VERSION}" STREQUAL "7.0.0") find_package (Git) @@ -44,7 +47,9 @@ find_package(Qt5DBus CONFIG REQUIRED) find_package(Qt5Network CONFIG REQUIRED) find_package(Qt5WebEngine CONFIG REQUIRED) find_package(Qt5WebEngineWidgets CONFIG REQUIRED) -find_package(Qt5X11Extras CONFIG REQUIRED) +if(UNIX AND NOT APPLE) + find_package(Qt5X11Extras CONFIG REQUIRED) +endif() # We need to include private headers manually include_directories( ${Qt5Widgets_PRIVATE_INCLUDE_DIRS} ) @@ -87,5 +92,7 @@ set(CMAKE_INSTALL_RPATH "${TOGGL_LIB_DIR};${TOGGL_INTERNAL_LIB_DIR}" CACHE STRIN add_subdirectory(third_party) add_subdirectory(src) -add_subdirectory(src/test) +if (TOGGL_BUILD_TESTS) + add_subdirectory(src/test) +endif() add_subdirectory(src/ui/linux/TogglDesktop) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a7ba0ff101..80d24823f8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ include_directories( ${LUA_INCLUDE_DIRS} ${JSONCPP_INCLUDE_DIRS} ${POCO_INCLUDE_DIRS} + $ENV{OPENSSL_ROOT_DIR}/include ) # TogglDesktopLibrary sources @@ -46,7 +47,6 @@ set(LIBRARY_SOURCE_FILES database.cc feedback.cc formatter.cc - get_focused_window_linux.cc error.cc gui.cc netconf.cc @@ -73,11 +73,35 @@ set(LIBRARY_SOURCE_FILES workspace.cc ) +if(APPLE) + set(LIBRARY_SOURCE_FILES + ${LIBRARY_SOURCE_FILES} + get_focused_window_mac.cc + lib/osx/Kopsik/MacOSVersionChecker.mm + ) + set(ADDITIONAL_LIBRARIES + "-framework Foundation" + "-framework CFNetwork" + "-framework AppKit" + ) +elseif(WIN32) + set(LIBRARY_SOURCE_FILES + ${LIBRARY_SOURCE_FILES} + get_focused_window_windows.cc + ) +else() + set(LIBRARY_SOURCE_FILES + ${LIBRARY_SOURCE_FILES} + get_focused_window_linux.cc + ) +endif() + # Set up compilation targets add_library(TogglDesktopLibrary SHARED ${LIBRARY_SOURCE_FILES}) # And list the dependencies between them (and system libraries) target_link_libraries(TogglDesktopLibrary PRIVATE + ${ADDITIONAL_LIBRARIES} ${JSONCPP_LIBRARIES} ${LUA_LIBRARIES} PocoCrypto PocoDataSQLite PocoNetSSL diff --git a/src/platforminfo.h b/src/platforminfo.h index ee13bc6cdb..9dab45e58e 100644 --- a/src/platforminfo.h +++ b/src/platforminfo.h @@ -14,14 +14,14 @@ std::map RetrieveOsDetailsMap(); inline void RetrieveOsDetails(std::stringstream &ss) { } -std::map RetrieveOsDetailsMap() { +inline std::map RetrieveOsDetailsMap() { return {}; } #elif defined(__APPLE__) inline void RetrieveOsDetails(std::stringstream &ss) { } -std::map RetrieveOsDetailsMap() { +inline std::map RetrieveOsDetailsMap() { return {}; } #endif diff --git a/src/ui/linux/TogglDesktop/CMakeLists.txt b/src/ui/linux/TogglDesktop/CMakeLists.txt index 811aad7e53..002d94e554 100644 --- a/src/ui/linux/TogglDesktop/CMakeLists.txt +++ b/src/ui/linux/TogglDesktop/CMakeLists.txt @@ -74,8 +74,12 @@ target_link_libraries(TogglDesktop PRIVATE OAuth2 ${QXT_LIBRARIES} Bugsnag - -lX11 -lXss ) +if(UNIX AND NOT APPLE) + target_link_libraries(TogglDesktop PRIVATE + -lX11 -lXss + ) +endif() configure_file(${PROJECT_SOURCE_DIR}/src/ssl/cacert.pem cacert.pem COPYONLY) diff --git a/src/ui/linux/TogglDesktop/idlenotificationwidget.cpp b/src/ui/linux/TogglDesktop/idlenotificationwidget.cpp index 2b020f81b4..cb8b9d8f0d 100644 --- a/src/ui/linux/TogglDesktop/idlenotificationwidget.cpp +++ b/src/ui/linux/TogglDesktop/idlenotificationwidget.cpp @@ -6,7 +6,9 @@ #include "./toggl.h" #include "./settingsview.h" +#ifdef __linux #include // NOLINT +#endif IdleNotificationWidget::IdleNotificationWidget(QStackedWidget *parent) : QWidget(parent), @@ -18,8 +20,10 @@ IdleNotificationWidget::IdleNotificationWidget(QStackedWidget *parent) idleHintTimer(new QTimer(this)) { ui->setupUi(this); +#ifdef __linux screensaver = new QDBusInterface("org.freedesktop.ScreenSaver", "/org/freedesktop/ScreenSaver", "org.freedesktop.ScreenSaver", QDBusConnection::sessionBus(), this); - + connect(screensaver, SIGNAL(ActiveChanged(bool)), this, SLOT(onScreensaverActiveChanged(bool))); +#endif connect(TogglApi::instance, &TogglApi::displayIdleNotification, this, &IdleNotificationWidget::displayIdleNotification); connect(TogglApi::instance, SIGNAL(displaySettings(bool,SettingsView*)), // NOLINT @@ -31,7 +35,6 @@ IdleNotificationWidget::IdleNotificationWidget(QStackedWidget *parent) connect(TogglApi::instance, SIGNAL(displayLogin(bool,uint64_t)), // NOLINT this, SLOT(displayLogin(bool,uint64_t))); // NOLINT - connect(screensaver, SIGNAL(ActiveChanged(bool)), this, SLOT(onScreensaverActiveChanged(bool))); connect(idleHintTimer, &QTimer::timeout, this, &IdleNotificationWidget::requestIdleHint); idleHintTimer->setInterval(5000); @@ -47,6 +50,7 @@ void IdleNotificationWidget::displaySettings(const bool open, SettingsView *sett } void IdleNotificationWidget::requestIdleHint() { +#ifdef __linux if (dbusApiAvailable) { auto pendingCall = screensaver->asyncCall("GetSessionIdleTime"); auto watcher = new QDBusPendingCallWatcher(pendingCall, this); @@ -65,9 +69,12 @@ void IdleNotificationWidget::requestIdleHint() { XFree(info); XCloseDisplay(display); } +#endif } +#ifdef __linux void IdleNotificationWidget::idleHintReceived(QDBusPendingCallWatcher *watcher) { + QDBusPendingReply reply = *watcher; if (reply.isError()) { dbusApiAvailable = false; @@ -80,6 +87,7 @@ void IdleNotificationWidget::idleHintReceived(QDBusPendingCallWatcher *watcher) } watcher->deleteLater(); } +#endif void IdleNotificationWidget::onScreensaverActiveChanged(bool active) { screenLocked = active; diff --git a/src/ui/linux/TogglDesktop/idlenotificationwidget.h b/src/ui/linux/TogglDesktop/idlenotificationwidget.h index b0b7c6b702..0fd75f2563 100644 --- a/src/ui/linux/TogglDesktop/idlenotificationwidget.h +++ b/src/ui/linux/TogglDesktop/idlenotificationwidget.h @@ -33,7 +33,9 @@ class IdleNotificationWidget : public QWidget { private slots: // NOLINT void requestIdleHint(); +#ifdef __linux void idleHintReceived(QDBusPendingCallWatcher *watcher); +#endif void onScreensaverActiveChanged(bool active); @@ -65,7 +67,9 @@ class IdleNotificationWidget : public QWidget { QWidget *previousView; uint64_t idleStarted; +#ifdef __linux QDBusInterface *screensaver; +#endif bool dbusApiAvailable; bool screenLocked; uint64_t lastActiveTime; diff --git a/src/ui/linux/TogglDesktop/qmlobjectlist.cc b/src/ui/linux/TogglDesktop/qmlobjectlist.cc index 4d50770d89..10eb6ca07d 100644 --- a/src/ui/linux/TogglDesktop/qmlobjectlist.cc +++ b/src/ui/linux/TogglDesktop/qmlobjectlist.cc @@ -67,7 +67,7 @@ QVariant QmlObjectList::data(const QModelIndex &index, int role) const qWarning()<<__FUNCTION__<<"data is null"; return QVariant(); } - return QVariant::fromValue(data.get()); + return QVariant::fromValue(data.data()); } QHash QmlObjectList::roleNames() const diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index f74e74f56b..697f089469 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -135,17 +135,27 @@ endif() if(NOT QXT_CORE_FOUND) set(QXT_SOURCE_FILES - #qxtglobalshortcut5/gui/qxtglobalshortcut_win.cpp - #qxtglobalshortcut5/gui/qxtwindowsystem_win.cpp - #qxtglobalshortcut5/gui/qxtglobalshortcut_mac.cpp - #qxtglobalshortcut5/gui/qxtwindowsystem_mac.cpp qxtglobalshortcut5/gui/qxtwindowsystem.cpp - qxtglobalshortcut5/gui/qxtglobalshortcut_x11.cpp - qxtglobalshortcut5/gui/x11info.cpp - qxtglobalshortcut5/gui/qxtwindowsystem_x11.cpp qxtglobalshortcut5/gui/qxtglobalshortcut.cpp qxtglobalshortcut5/core/qxtglobal.cpp ) + if (UNIX AND NOT APPLE) + set(QXT_SOURCE_FILES ${QXT_SOURCE_FILES} + qxtglobalshortcut5/gui/qxtglobalshortcut_x11.cpp + qxtglobalshortcut5/gui/qxtwindowsystem_x11.cpp + qxtglobalshortcut5/gui/x11info.cpp + ) + elseif(WIN32) + set(QXT_SOURCE_FILES ${QXT_SOURCE_FILES} + qxtglobalshortcut5/gui/qxtglobalshortcut_win.cpp + qxtglobalshortcut5/gui/qxtwindowsystem_win.cpp + ) + else() + set(QXT_SOURCE_FILES ${QXT_SOURCE_FILES} + qxtglobalshortcut5/gui/qxtglobalshortcut_mac.cpp + qxtglobalshortcut5/gui/qxtwindowsystem_mac.cpp + ) + endif() set(QXT_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/qxtglobalshortcut5/gui ${CMAKE_CURRENT_SOURCE_DIR}/qxtglobalshortcut5/core @@ -154,8 +164,19 @@ if(NOT QXT_CORE_FOUND) add_library(Qxt SHARED ${QXT_SOURCE_FILES}) target_link_libraries(Qxt Qt5::Widgets - Qt5::X11Extras ) + if (UNIX AND NOT APPLE) + target_link_libraries(Qxt + Qt5::X11Extras + ) + else() + target_link_libraries(Qxt + "-framework Foundation" + "-framework AppKit" + "-framework Carbon" + ) + endif() + add_definitions(-DBUILD_QXT_GUI -DBUILD_QXT_CORE) target_include_directories(Qxt PRIVATE qxtglobalshortcut5/gui qxtglobalshortcut5/core @@ -164,10 +185,10 @@ if(NOT QXT_CORE_FOUND) Qxt PARENT_SCOPE ) - install(TARGETS Qxt DESTINATION ${TOGGL_INTERNAL_LIB_DIR}) + install(TARGETS Qxt DESTINATION lib) endif() -if(NOT GTEST_FOUND) +if(NOT GTEST_FOUND AND TOGGL_BUILD_TESTS) set(GTEST_SOURCE_FILES googletest-read-only/src/gtest-all.cc ) diff --git a/third_party/qxtglobalshortcut5/gui/qxtwindowsystem_mac.cpp b/third_party/qxtglobalshortcut5/gui/qxtwindowsystem_mac.cpp index a898e82754..0fc57144cb 100644 --- a/third_party/qxtglobalshortcut5/gui/qxtwindowsystem_mac.cpp +++ b/third_party/qxtglobalshortcut5/gui/qxtwindowsystem_mac.cpp @@ -95,7 +95,7 @@ QString QxtWindowSystem::windowTitle(WId window) if (err != noErr) return QString(); // this is UTF8 encoded - return QCFString::toQString((CFStringRef)windowTitle); + return QCFString((CFStringRef)windowTitle); } QRect QxtWindowSystem::windowGeometry(WId window) From ed3b107be8670b258f868075378342e8f32722d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 29 Nov 2019 13:05:00 +0100 Subject: [PATCH 36/83] Try building this in GitHub Actions (macos) --- .github/workflows/main.yml | 60 +++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 44c31f8ef7..9460a5ba1d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ on: types: [ created ] jobs: - macos: + macos-qt: runs-on: macOS-latest steps: - uses: actions/checkout@v1 @@ -27,7 +27,7 @@ jobs: # Application Certificate base64 -D <<< "${{ secrets.CERT_MACOS_APPLICATION }}" > Bundle_cert_macos_distribution.p12 security import ./Bundle_cert_macos_distribution.p12 -k ~/Library/Keychains/build.keychain -P ${{ secrets.CERT_MACOS_APPLICATION_PASSWORD }} -T /usr/bin/codesign - + # Installer Certificate base64 -D <<< "${{ secrets.CERT_MACOS_INSTALLER }}" > Bundle_cert_macos_installer.p12 security import ./Bundle_cert_macos_installer.p12 -k ~/Library/Keychains/build.keychain -P ${{ secrets.CERT_MACOS_INSTALLER_PASSWORD }} -T /usr/bin/codesign @@ -36,17 +36,37 @@ jobs: security unlock-keychain -p password ~/Library/Keychains/build.keychain security set-keychain-settings -lu security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k password ~/Library/Keychains/build.keychain - - name: Build the dependencies - run: | - bash ./dist/osx/build.sh dependencies - - name: Get cocoapods + - name: Install the dependencies run: | - bash ./dist/osx/build.sh cocoapods + brew install qt + brew install openssl + brew install cmake - name: Build the application run: | - bash ./dist/osx/build.sh app - - name: Insert version string - if: github.event_name == 'release' + mkdir build + cd build + export CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/ + cmake + + macos: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v1 + with: + fetch-depth: 1 + - name: Import signing key + run: | + # Create a new keychian and make it default + security create-keychain -p password build.keychain + security default-keychain -s ~/Library/Keychains/build.keychain + + # Mac Developer Certificate + base64 -D <<< "${{ secrets.CERT_MACOS_DEVELOPMENT }}" > Bundle_cert_macos_development.p12 + security import ./Bundle_cert_macos_development.p12 -k ~/Library/Keychains/build.keychain -P ${{ secrets.CERT_MACOS_DEVELOPMENT_PASSWORD }} -T /usr/bin/codesign + + # Application Certificate + base64 -D <<< "${{ secrets.CERT_MACOS_APPLICATION }}" > Bundle_cert_macos_distribution.p12 + security import ./Bundle_cert_macos_distribution.p12 -k ~/Library/Keychains/build.keychain -P ${{ secrets.CERT_MACOS_APPLICATION_PASSWORD }} -T /usr/bin/codesign run: | TAG_NAME=$(./dist/get-tag-name.sh) bash ./dist/osx/build.sh plist @@ -307,3 +327,23 @@ jobs: TAG_NAME=$(./dist/get-tag-name.sh) ./dist/upload-github-release-asset.sh github_api_token=${{ secrets.GITHUB_TOKEN }} tag="$TAG_NAME" filename="TogglDesktop.Package_${TAG_NAME/v/}.0_x86_x64_bundle_StoreRelease.appxupload" shell: bash +name: Build + +on: + push: + branches: + - '**' + release: + types: [ created ] + +jobs: + macos-qt: + runs-on: macOS-latest + steps: + - uses: actions/checkout@v1 + with: + fetch-depth: 1 + - name: Import signing key + run: | + # Create a new keychian and make it default + security create-keychain -p password build.keychain From c106b5fa6231f2dddcafe98a6d16794a9791678e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 29 Nov 2019 14:36:28 +0100 Subject: [PATCH 37/83] Fix colors in darkmode (linux) --- src/ui/linux/TogglDesktop/MainWindow.qml | 1 + .../linux/TogglDesktop/TimeEntryListView.qml | 56 +++++++++++-------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index df18ebcfd1..fcee75680c 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -23,6 +23,7 @@ ApplicationWindow { SystemPalette { id: palette + property bool isDark: (shadowColor.r + shadowColor.g + shadowColor.b) < 300 } SystemPalette { id: disabledPalette diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index df259f6d98..d2ab0ba50f 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -6,13 +6,16 @@ import QtGraphicalEffects 1.0 Rectangle { id: root anchors.fill: parent - color: palette.alternateBase + color: backgroundColor - property real shadowWidth: 9 - property real sectionMargin: 2 * shadowWidth + property real sectionMargin: Math.max(2 * shadowWidth, 16) property real headerHeight: 32 property real itemHeight: 74 + property real shadowWidth: palette.isDark ? 1 : 2 + property color shadowColor: palette.shadow + property color backgroundColor: mixColors(palette.base, palette.alternateBase, 0.8) + ColumnLayout { anchors.fill: parent spacing: 0 @@ -45,6 +48,7 @@ Rectangle { x: 12 width: timeEntryList.viewportWidth - 24 height: headerHeight + color: palette.base Rectangle { anchors.right: parent.left @@ -57,8 +61,8 @@ Rectangle { z: -1 gradient: Gradient { orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: palette.alternateBase } - GradientStop { position: 0.5; color: "light gray" } + GradientStop { position: 0.0; color: backgroundColor } + GradientStop { position: 0.5; color: shadowColor } } } Rectangle { @@ -72,8 +76,8 @@ Rectangle { z: -1 gradient: Gradient { orientation: Gradient.Horizontal - GradientStop { position: 0.5; color: "light gray" } - GradientStop { position: 1.0; color: palette.alternateBase } + GradientStop { position: 0.5; color: shadowColor } + GradientStop { position: 1.0; color: backgroundColor } } } @@ -85,8 +89,8 @@ Rectangle { width: shadowWidth gradient: Gradient { orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: palette.alternateBase } - GradientStop { position: 1.0; color: "light gray" } + GradientStop { position: 0.0; color: backgroundColor } + GradientStop { position: 1.0; color: shadowColor } } } Rectangle { @@ -97,8 +101,8 @@ Rectangle { width: shadowWidth gradient: Gradient { orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: "light gray" } - GradientStop { position: 1.0; color: palette.alternateBase } + GradientStop { position: 0.0; color: shadowColor } + GradientStop { position: 1.0; color: backgroundColor } } } Rectangle { @@ -108,8 +112,8 @@ Rectangle { height: shadowWidth gradient: Gradient { - GradientStop { position: 0.0; color: palette.alternateBase } - GradientStop { position: 1.0; color: "light gray" } + GradientStop { position: 0.0; color: backgroundColor } + GradientStop { position: 1.0; color: shadowColor } } } Text { @@ -208,8 +212,8 @@ Rectangle { z: -1 gradient: Gradient { orientation: Gradient.Horizontal - GradientStop { position: 0.5; color: "light gray" } - GradientStop { position: 1.0; color: palette.alternateBase } + GradientStop { position: 0.5; color: shadowColor } + GradientStop { position: 1.0; color: backgroundColor } } } @@ -224,8 +228,8 @@ Rectangle { z: -1 gradient: Gradient { orientation: Gradient.Horizontal - GradientStop { position: 0.5; color: "light gray" } - GradientStop { position: 0.0; color: palette.alternateBase } + GradientStop { position: 0.5; color: shadowColor } + GradientStop { position: 0.0; color: backgroundColor } } } @@ -237,8 +241,8 @@ Rectangle { width: shadowWidth gradient: Gradient { orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: palette.alternateBase } - GradientStop { position: 1.0; color: "light gray" } + GradientStop { position: 0.0; color: backgroundColor } + GradientStop { position: 1.0; color: shadowColor } } } @@ -250,8 +254,8 @@ Rectangle { width: shadowWidth gradient: Gradient { orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: "light gray" } - GradientStop { position: 1.0; color: palette.alternateBase } + GradientStop { position: 0.0; color: shadowColor } + GradientStop { position: 1.0; color: backgroundColor } } } Rectangle { @@ -261,8 +265,8 @@ Rectangle { height: shadowWidth gradient: Gradient { - GradientStop { position: 0.0; color: "light gray" } - GradientStop { position: 1.0; color: palette.alternateBase } + GradientStop { position: 0.0; color: shadowColor } + GradientStop { position: 1.0; color: backgroundColor } } } @@ -306,6 +310,7 @@ Rectangle { Text { Layout.fillWidth: true text: modelData.Description.length > 0 ? modelData.Description : "(no description)" + color: palette.text wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pixelSize: 12 verticalAlignment: Text.AlignVCenter @@ -322,15 +327,17 @@ Rectangle { } Text { text: modelData.ClientLabel - color: modelData.Color + color: modelData.Color.length > 0 ? modelData.Color : palette.text font.pixelSize: 8 } Text { text: modelData.ProjectLabel + color: palette.text font.pixelSize: 8 } Text { text: modelData.TaskLabel + color: palette.text font.pixelSize: 8 } Item { @@ -351,6 +358,7 @@ Rectangle { Text { Layout.alignment: Qt.AlignVCenter text: modelData.Duration + color: palette.text } } } From 5940cb3406a59b400f9b9e1f6908f3470eaf5a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 29 Nov 2019 15:13:59 +0100 Subject: [PATCH 38/83] Make the timer look more like the macOS app (linux) --- .../xcshareddata/IDEWorkspaceChecks.plist | 8 ++ src/ui/linux/TogglDesktop/MainWindow.qml | 1 + src/ui/linux/TogglDesktop/Resources.qrc | 1 + src/ui/linux/TogglDesktop/StartButton.qml | 86 +++++++++++++++++++ src/ui/linux/TogglDesktop/TimerView.qml | 78 ++++++----------- 5 files changed, 124 insertions(+), 50 deletions(-) create mode 100644 src/lib/osx/TogglDesktopLibrary.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 src/ui/linux/TogglDesktop/StartButton.qml diff --git a/src/lib/osx/TogglDesktopLibrary.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/src/lib/osx/TogglDesktopLibrary.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/src/lib/osx/TogglDesktopLibrary.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index fcee75680c..28032a1ed8 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -11,6 +11,7 @@ ApplicationWindow { width: 400 minimumHeight: 640 height: 640 + title: "Toggl" function mixColors(a, b, ratio) { return Qt.rgba( diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index ca37163d94..90bd474d2e 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -34,5 +34,6 @@ TogglCheckBox.qml TogglSwitch.qml TogglButtonBackground.qml + StartButton.qml diff --git a/src/ui/linux/TogglDesktop/StartButton.qml b/src/ui/linux/TogglDesktop/StartButton.qml new file mode 100644 index 0000000000..a5be3a335a --- /dev/null +++ b/src/ui/linux/TogglDesktop/StartButton.qml @@ -0,0 +1,86 @@ +import QtQuick 2.0 + +Rectangle { + id: startButton + property bool running + + signal clicked + + width: 32 + height: width + radius: width / 2 + + color: running ? "#e20000" : "#47bc00" + + Rectangle { + anchors.centerIn: parent + width: startButton.width / 3 + height: startButton.width / 3 + radius: 2 + visible: running + } + + + Item { + anchors.centerIn: parent + visible: !running + + Item { + x: -4.5 + y: -1 + /* Could be worth replacing with an actual picture eventually... */ + Rectangle { + y: -Math.sqrt(3)/6 * width + radius / 2 + width: startButton.width / 2.7 + height: radius + rotation: 30 + radius: 2 + } + Rectangle { + y: Math.sqrt(3)/6 * width - radius / 2 + width: startButton.width / 2.7 + height: radius + rotation: -30 + radius: 2 + } + Rectangle { + x: -Math.sqrt(3)/6 * width - radius / 2 + width: startButton.width / 2.7 + height: radius + rotation: 90 + radius: 2 + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 1.6 + width: startButton.width / 2.7 - 3 + height: 2 + rotation: 90 + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 3.6 + width: startButton.width / 2.7 - 5 + height: 2 + rotation: 90 + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 5.6 + width: startButton.width / 2.7 - 7 + height: 2 + rotation: 90 + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 7.6 + width: startButton.width / 2.7 - 9 + height: 2 + rotation: 90 + } + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + startButton.clicked() + } + } +} diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index a541d38f74..5c208187e6 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -4,7 +4,7 @@ import QtQuick.Controls 2.12 Rectangle { id: root - color: "black" + color: mixColors(palette.window, palette.base, 0.9) height: 64 property bool running: false @@ -12,8 +12,7 @@ Rectangle { function start() { if (!running) { - var dur = duration.text === "00:00" ? "" : duration.text - toggl.start(description.text, dur, 0, 0, "", false) + toggl.start(description.text, "", 0, 0, "", false) description.text = "" } } @@ -45,9 +44,9 @@ Rectangle { RowLayout { id: timerContainer - height: parent.height - width: parent.width - startButton.width - 6 x: 6 + height: parent.height + width: parent.width - 12 ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true @@ -102,6 +101,14 @@ Rectangle { filter: description.text //model: toggl.minitimerAutocomplete } + Rectangle { + anchors.fill: parent + anchors.topMargin: -1 + anchors.bottomMargin: -1 + radius: height / 2 + color: mixColors(palette.window, palette.alternateBase, 0.8) + z: -1 + } } RowLayout { visible: runningTimeEntry && runningTimeEntry.ProjectLabel.length > 0 @@ -135,56 +142,27 @@ Rectangle { } } } - ColumnLayout { + Text { + visible: running Layout.fillHeight: true - Text { - visible: running - Layout.fillHeight: true - verticalAlignment: Text.AlignVCenter - text: runningTimeEntry ? runningTimeEntry.Duration : "" - Timer { - running: root.running - interval: 100 - repeat: true - onTriggered: { - parent.text = toggl.formatDurationInSecondsHHMMSS(new Date().getTime() / 1000 - runningTimeEntry.Started) - } - } - - color: "white" - } - TextField { - id: duration - visible: !running - Layout.preferredWidth: 64 - onAccepted: start() - validator: RegExpValidator { - regExp: /[0-9][0-9]:[0-9][0-9]/ + verticalAlignment: Text.AlignVCenter + text: runningTimeEntry ? runningTimeEntry.Duration : "" + Timer { + running: root.running + interval: 100 + repeat: true + onTriggered: { + parent.text = toggl.formatDurationInSecondsHHMMSS(new Date().getTime() / 1000 - runningTimeEntry.Started) } - font.pixelSize: 12 - background: Item {} - placeholderText: "00:00" - placeholderTextColor: "light gray" - color: "white" } - } - } - Rectangle { - id: startButton - width: parent.height - height: parent.height - anchors.left: timerContainer.right - anchors.leftMargin: 3 - color: running ? "#e20000" : "#47bc00" - Text { - font.weight: Font.DemiBold - text: running ? "Stop" : "Start" - anchors.centerIn: parent - font.pixelSize: 12 + color: "white" } - MouseArea { - anchors.fill: parent + + StartButton { + id: startButton + running: root.running + Layout.alignment: Qt.AlignVCenter onClicked: { if (running) { toggl.stop() From 063ca361d396cbc992d7c8efd0b1adeba0a53112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 29 Nov 2019 15:49:57 +0100 Subject: [PATCH 39/83] Fix the Actions workflow (macos) --- .github/workflows/main.yml | 44 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9460a5ba1d..1d02236685 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -46,7 +46,7 @@ jobs: mkdir build cd build export CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/ - cmake + cmake macos: runs-on: macOS-latest @@ -67,6 +67,26 @@ jobs: # Application Certificate base64 -D <<< "${{ secrets.CERT_MACOS_APPLICATION }}" > Bundle_cert_macos_distribution.p12 security import ./Bundle_cert_macos_distribution.p12 -k ~/Library/Keychains/build.keychain -P ${{ secrets.CERT_MACOS_APPLICATION_PASSWORD }} -T /usr/bin/codesign + + # Installer Certificate + base64 -D <<< "${{ secrets.CERT_MACOS_INSTALLER }}" > Bundle_cert_macos_installer.p12 + security import ./Bundle_cert_macos_installer.p12 -k ~/Library/Keychains/build.keychain -P ${{ secrets.CERT_MACOS_INSTALLER_PASSWORD }} -T /usr/bin/codesign + + # Unlock + security unlock-keychain -p password ~/Library/Keychains/build.keychain + security set-keychain-settings -lu + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k password ~/Library/Keychains/build.keychain + - name: Build the dependencies + run: | + bash ./dist/osx/build.sh dependencies + - name: Get cocoapods + run: | + bash ./dist/osx/build.sh cocoapods + - name: Build the application + run: | + bash ./dist/osx/build.sh app + - name: Insert version string + if: github.event_name == 'release' run: | TAG_NAME=$(./dist/get-tag-name.sh) bash ./dist/osx/build.sh plist @@ -100,7 +120,7 @@ jobs: - name: Install dependencies run: | sudo apt update - sudo apt install libxss1 libgstreamer-plugins-base1.0-0 libgstreamer1.0-0 qt5-default libqt5gui5 libqt5webengine5 libqt5webenginecore5 libqt5webenginewidgets5 libqt5printsupport5 libqt5quickwidgets5 libqt5x11extras5 libxss1 + sudo apt install libxss1 libgstreamer-plugins-base1.0-0 libgstreamer1.0-0 qt5-default libqt5gui5 libqt5webengine5 libqt5webenginecore5 libqt5webenginewidgets5 libqt5printsupport5 libqt5quickwidgets5 libqt5x11extras5 libxss1 sudo apt install patchelf binutils cmake pkg-config qtbase5-dev qtwebengine5-dev libqt5x11extras5-dev qtbase5-private-dev libssl-dev libxss-dev libxmu-dev - name: Build the Linux binary run: | @@ -327,23 +347,3 @@ jobs: TAG_NAME=$(./dist/get-tag-name.sh) ./dist/upload-github-release-asset.sh github_api_token=${{ secrets.GITHUB_TOKEN }} tag="$TAG_NAME" filename="TogglDesktop.Package_${TAG_NAME/v/}.0_x86_x64_bundle_StoreRelease.appxupload" shell: bash -name: Build - -on: - push: - branches: - - '**' - release: - types: [ created ] - -jobs: - macos-qt: - runs-on: macOS-latest - steps: - - uses: actions/checkout@v1 - with: - fetch-depth: 1 - - name: Import signing key - run: | - # Create a new keychian and make it default - security create-keychain -p password build.keychain From 32ba9b8c1da68fbe6c5a1f31b333c03f187d90f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 29 Nov 2019 16:03:42 +0100 Subject: [PATCH 40/83] Make the time entry list look similar to macOS (linux) --- .../linux/TogglDesktop/TimeEntryListView.qml | 129 ++++++++++++++---- 1 file changed, 101 insertions(+), 28 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index d2ab0ba50f..e242a9f073 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -8,9 +8,9 @@ Rectangle { anchors.fill: parent color: backgroundColor - property real sectionMargin: Math.max(2 * shadowWidth, 16) + property real sectionMargin: Math.max(2 * shadowWidth, 12) property real headerHeight: 32 - property real itemHeight: 74 + property real itemHeight: 64 property real shadowWidth: palette.isDark ? 1 : 2 property color shadowColor: palette.shadow @@ -119,7 +119,7 @@ Rectangle { Text { anchors.fill: parent anchors.leftMargin: 6 - anchors.rightMargin: 6 + anchors.rightMargin: 32 textFormat: Text.RichText // this is actually rendering the width underneath so we force a repaint on each width change text: " " + section + "
" + width + "
" @@ -199,7 +199,7 @@ Rectangle { rightMargin: anchors.leftMargin topMargin: 0 } - color: palette.base + color: delegateMouse.containsMouse ? backgroundColor : modelData.GroupOpen ? backgroundColor : palette.base Rectangle { anchors.left: parent.right @@ -275,10 +275,12 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right height: 1 - color: "#d4d4d4" + color: backgroundColor } MouseArea { + id: delegateMouse + hoverEnabled: true anchors.fill: parent onClicked: { if (modelData.Group) @@ -291,26 +293,32 @@ Rectangle { anchors.fill: parent anchors.margins: 12 spacing: 9 - TogglButton { + Rectangle { Layout.alignment: Qt.AlignVCenter visible: modelData.Group - implicitWidth: implicitHeight - contentItem: Text { + width: 24 + height: 24 + radius: 4 + color: modelData.GroupOpen ? "dark green" : palette.base + border { + color: modelData.GroupOpen ? "transparent" : palette.alternateBase + width: 0.5 + } + Text { + color: modelData.GroupOpen ? "light green" : palette.alternateBase anchors.centerIn: parent verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter - text: modelData.GroupOpen ? "▵" : modelData.GroupItemCount + text: modelData.GroupItemCount } - checked: modelData.GroupOpen - onClicked: toggl.toggleEntriesGroup(modelData.GroupName) } ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true Text { Layout.fillWidth: true - text: modelData.Description.length > 0 ? modelData.Description : "(no description)" - color: palette.text + text: modelData.Description.length > 0 ? modelData.Description : "+ Add description" + color: modelData.Description.length > 0 ? palette.text : disabledPalette.text wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pixelSize: 12 verticalAlignment: Text.AlignVCenter @@ -326,40 +334,105 @@ Rectangle { Layout.alignment: Qt.AlignVCenter } Text { + visible: modelData.ClientLabel.length > 0 text: modelData.ClientLabel color: modelData.Color.length > 0 ? modelData.Color : palette.text - font.pixelSize: 8 + font.pixelSize: 12 } Text { - text: modelData.ProjectLabel - color: palette.text - font.pixelSize: 8 + text: modelData.ProjectLabel.length > 0 ? modelData.ProjectLabel : "+ Add project" + color: modelData.ProjectLabel.length > 0 ? palette.text : disabledPalette.text + font.pixelSize: 12 } Text { + visible: modelData.TaskLabel.length > 0 text: modelData.TaskLabel color: palette.text - font.pixelSize: 8 + font.pixelSize: 12 } Item { Layout.fillWidth: true } } } - TogglButton { - implicitWidth: implicitHeight - contentItem: Text { - anchors.centerIn: parent - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: "▸" - } - onClicked: toggl.continueTimeEntry(modelData.GUID) - } Text { Layout.alignment: Qt.AlignVCenter text: modelData.Duration color: palette.text } + Item { + id: startButton + opacity: delegateMouse.containsMouse ? 1.0 : 0.0 + width: 20 + height: 20 + MouseArea { + anchors.fill: parent + onClicked: toggl.continueTimeEntry(modelData.GUID) + } + + Item { + anchors.centerIn: parent + scale: 1.5 + + Item { + x: -4.5 + y: -1 + /* Could be worth replacing with an actual picture eventually... */ + Rectangle { + y: -Math.sqrt(3)/6 * width + radius / 2 + width: startButton.width / 2.7 + height: radius + rotation: 30 + radius: 2 + color: "#47bc00" + } + Rectangle { + y: Math.sqrt(3)/6 * width - radius / 2 + width: startButton.width / 2.7 + height: radius + rotation: -30 + radius: 2 + color: "#47bc00" + } + Rectangle { + x: -Math.sqrt(3)/6 * width - radius / 2 + width: startButton.width / 2.7 + height: radius + rotation: 90 + radius: 2 + color: "#47bc00" + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 1.6 + width: startButton.width / 2.7 - 3 + height: 2 + rotation: 90 + color: "#47bc00" + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 3.6 + width: startButton.width / 2.7 - 5 + height: 2 + rotation: 90 + color: "#47bc00" + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 5.6 + width: startButton.width / 2.7 - 7 + height: 2 + rotation: 90 + color: "#47bc00" + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 7.6 + width: startButton.width / 2.7 - 9 + height: 2 + rotation: 90 + color: "#47bc00" + } + } + } + } } } } From 95696c2235c1337be82924fc83cab2ae79b14473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 29 Nov 2019 16:06:41 +0100 Subject: [PATCH 41/83] ... --- .github/workflows/main.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1d02236685..0fddccc424 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -45,8 +45,10 @@ jobs: run: | mkdir build cd build - export CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/ - cmake + export CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake + export OPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1/ + cmake -DTOGGL_BUILD_TESTS=OFF -DOPENSSL_ROOT_DIR=$OPENSSL_ROOT_DIR .. + make -j2 macos: runs-on: macOS-latest From 1336035f306fae32e6ac8da0783a7d54b8bc8854 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 2 Dec 2019 18:17:15 +0100 Subject: [PATCH 42/83] Split off shadows and list items into separate items (linux) --- src/ui/linux/TogglDesktop/MainWindow.qml | 16 + src/ui/linux/TogglDesktop/Resources.qrc | 3 + .../TogglDesktop/TimeEntryListHeader.qml | 35 ++ .../linux/TogglDesktop/TimeEntryListItem.qml | 190 ++++++++++ .../linux/TogglDesktop/TimeEntryListView.qml | 348 +----------------- src/ui/linux/TogglDesktop/TogglShadowBox.qml | 144 ++++++++ 6 files changed, 391 insertions(+), 345 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/TimeEntryListHeader.qml create mode 100644 src/ui/linux/TogglDesktop/TimeEntryListItem.qml create mode 100644 src/ui/linux/TogglDesktop/TogglShadowBox.qml diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index 28032a1ed8..f29568e233 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -3,6 +3,7 @@ import QtQuick.Window 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 ApplicationWindow { id: window @@ -25,6 +26,9 @@ ApplicationWindow { SystemPalette { id: palette property bool isDark: (shadowColor.r + shadowColor.g + shadowColor.b) < 300 + + property color itemShadow: palette.shadow + property color listBackground: mixColors(palette.base, palette.alternateBase, 0.8) } SystemPalette { id: disabledPalette @@ -92,6 +96,18 @@ ApplicationWindow { anchors.right: timeEntryEdit.visible ? timeEntryEdit.left : parent.right } + Dialog { + //visible: true + //flags: Qt.WA_TranslucentBackground | Qt.FramelessWindowHint | Qt.ToolTip | Qt.WindowStaysOnTopHint + Rectangle { + anchors.centerIn: parent + width: parent.width + height: parent.height + radius: height / 2 + color: "red" + } + } + TimeEntryEditView { id: timeEntryEdit visible: false diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index 90bd474d2e..3da2f132ff 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -35,5 +35,8 @@ TogglSwitch.qml TogglButtonBackground.qml StartButton.qml + TimeEntryListItem.qml + TogglShadowBox.qml + TimeEntryListHeader.qml diff --git a/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml b/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml new file mode 100644 index 0000000000..becfa96154 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml @@ -0,0 +1,35 @@ +import QtQuick 2.12 + +Item { + Behavior on y { + NumberAnimation { + duration: 120 + } + } + + Rectangle { + y: sectionMargin + x: 12 + width: timeEntryList.viewportWidth - 24 + height: headerHeight + color: palette.base + + TogglShadowBox { + anchors.fill: parent + shadowWidth: 9 + shadowColor: palette.itemShadow + backgroundColor: palette.listBackground + sides: TogglShadowBox.Side.Left | TogglShadowBox.Side.Right | TogglShadowBox.Side.Top + } + + Text { + anchors.fill: parent + anchors.leftMargin: 6 + anchors.rightMargin: 32 + textFormat: Text.RichText + // this is actually rendering the width underneath so we force a repaint on each width change + text: " " + section + "
" + width + "
" + color: palette.windowText + } + } +} diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml new file mode 100644 index 0000000000..b61545678b --- /dev/null +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -0,0 +1,190 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 + +Item { + z: index + height: visible ? itemHeight : 0 + width: timeEntryList.viewportWidth + Rectangle { + anchors { + fill: parent + leftMargin: 12 + rightMargin: anchors.leftMargin + topMargin: 0 + } + color: delegateMouse.containsMouse ? backgroundColor : modelData.GroupOpen ? backgroundColor : palette.base + + TogglShadowBox { + anchors.fill: parent + shadowWidth: 9 + shadowColor: palette.itemShadow + backgroundColor: palette.listBackground + sides: TogglShadowBox.Side.Left | TogglShadowBox.Side.Right | TogglShadowBox.Side.Bottom + } + + Rectangle { + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + height: 1 + color: backgroundColor + } + + MouseArea { + id: delegateMouse + hoverEnabled: true + anchors.fill: parent + onClicked: { + if (modelData.Group) + toggl.toggleEntriesGroup(modelData.GroupName) + else + toggl.editTimeEntry(modelData.GUID, "description") + } + } + RowLayout { + anchors.fill: parent + anchors.margins: 12 + spacing: 9 + Rectangle { + Layout.alignment: Qt.AlignVCenter + visible: modelData.Group + width: 24 + height: 24 + radius: 4 + color: modelData.GroupOpen ? "dark green" : palette.base + border { + color: modelData.GroupOpen ? "transparent" : palette.alternateBase + width: 0.5 + } + Text { + color: modelData.GroupOpen ? "light green" : palette.alternateBase + anchors.centerIn: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: modelData.GroupItemCount + } + } + ColumnLayout { + Layout.fillHeight: true + Layout.fillWidth: true + Text { + Layout.fillWidth: true + text: modelData.Description.length > 0 ? modelData.Description : "+ Add description" + color: modelData.Description.length > 0 ? palette.text : disabledPalette.text + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.pixelSize: 12 + verticalAlignment: Text.AlignVCenter + } + RowLayout { + Layout.fillWidth: true + Rectangle { + height: 3 + width: height + radius: height / 2 + visible: modelData.ClientLabel.length > 0 + color: modelData.Color + Layout.alignment: Qt.AlignVCenter + } + Text { + visible: modelData.ClientLabel.length > 0 + text: modelData.ClientLabel + color: modelData.Color.length > 0 ? modelData.Color : palette.text + font.pixelSize: 12 + } + Text { + text: modelData.ProjectLabel.length > 0 ? modelData.ProjectLabel : "+ Add project" + color: modelData.ProjectLabel.length > 0 ? palette.text : disabledPalette.text + font.pixelSize: 12 + } + Text { + visible: modelData.TaskLabel.length > 0 + text: modelData.TaskLabel + color: palette.text + font.pixelSize: 12 + } + Item { + Layout.fillWidth: true + } + } + } + Text { + Layout.alignment: Qt.AlignVCenter + text: modelData.Duration + color: palette.text + } + Item { + id: startButton + opacity: delegateMouse.containsMouse ? 1.0 : 0.0 + width: 20 + height: 20 + MouseArea { + anchors.fill: parent + onClicked: toggl.continueTimeEntry(modelData.GUID) + } + + Item { + anchors.centerIn: parent + scale: 1.5 + + Item { + x: -4.5 + y: -1 + /* Could be worth replacing with an actual picture eventually... */ + Rectangle { + y: -Math.sqrt(3)/6 * width + radius / 2 + width: startButton.width / 2.7 + height: radius + rotation: 30 + radius: 2 + color: "#47bc00" + } + Rectangle { + y: Math.sqrt(3)/6 * width - radius / 2 + width: startButton.width / 2.7 + height: radius + rotation: -30 + radius: 2 + color: "#47bc00" + } + Rectangle { + x: -Math.sqrt(3)/6 * width - radius / 2 + width: startButton.width / 2.7 + height: radius + rotation: 90 + radius: 2 + color: "#47bc00" + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 1.6 + width: startButton.width / 2.7 - 3 + height: 2 + rotation: 90 + color: "#47bc00" + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 3.6 + width: startButton.width / 2.7 - 5 + height: 2 + rotation: 90 + color: "#47bc00" + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 5.6 + width: startButton.width / 2.7 - 7 + height: 2 + rotation: 90 + color: "#47bc00" + } + Rectangle { + x: -Math.sqrt(3)/6 * width + 7.6 + width: startButton.width / 2.7 - 9 + height: 2 + rotation: 90 + color: "#47bc00" + } + } + } + } + } + } +} diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index e242a9f073..895f454e5e 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -11,10 +11,7 @@ Rectangle { property real sectionMargin: Math.max(2 * shadowWidth, 12) property real headerHeight: 32 property real itemHeight: 64 - - property real shadowWidth: palette.isDark ? 1 : 2 - property color shadowColor: palette.shadow - property color backgroundColor: mixColors(palette.base, palette.alternateBase, 0.8) + property real shadowWidth: 9 ColumnLayout { anchors.fill: parent @@ -34,98 +31,9 @@ Rectangle { clip: true model: toggl.timeEntries section.property: "modelData.DateHeader" - section.delegate: Item { + section.delegate: TimeEntryListHeader { height: headerHeight + sectionMargin width: parent.width - Behavior on y { - NumberAnimation { - duration: 120 - } - } - - Rectangle { - y: sectionMargin - x: 12 - width: timeEntryList.viewportWidth - 24 - height: headerHeight - color: palette.base - - Rectangle { - anchors.right: parent.left - anchors.bottom: parent.top - anchors.margins: -shadowWidth - width: 2 * shadowWidth - height: 2 * shadowWidth - radius: shadowWidth - rotation: 45 - z: -1 - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: backgroundColor } - GradientStop { position: 0.5; color: shadowColor } - } - } - Rectangle { - anchors.left: parent.right - anchors.bottom: parent.top - anchors.margins: -shadowWidth - width: 2 * shadowWidth - height: 2 * shadowWidth - radius: shadowWidth - rotation: -45 - z: -1 - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.5; color: shadowColor } - GradientStop { position: 1.0; color: backgroundColor } - } - } - - Rectangle { - anchors.right: parent.left - anchors.top: parent.top - anchors.bottom: parent.bottom - - width: shadowWidth - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: backgroundColor } - GradientStop { position: 1.0; color: shadowColor } - } - } - Rectangle { - anchors.left: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - - width: shadowWidth - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: shadowColor } - GradientStop { position: 1.0; color: backgroundColor } - } - } - Rectangle { - anchors.bottom: parent.top - anchors.right: parent.right - anchors.left: parent.left - - height: shadowWidth - gradient: Gradient { - GradientStop { position: 0.0; color: backgroundColor } - GradientStop { position: 1.0; color: shadowColor } - } - } - Text { - anchors.fill: parent - anchors.leftMargin: 6 - anchors.rightMargin: 32 - textFormat: Text.RichText - // this is actually rendering the width underneath so we force a repaint on each width change - text: " " + section + "
" + width + "
" - color: palette.windowText - } - } } add: Transition { @@ -181,258 +89,8 @@ Rectangle { } } - delegate: timeEntryDelegate - } - } - } - - Component { - id: timeEntryDelegate - Item { - z: index - height: visible ? itemHeight : 0 - width: timeEntryList.viewportWidth - Rectangle { - anchors { - fill: parent - leftMargin: 12 - rightMargin: anchors.leftMargin - topMargin: 0 - } - color: delegateMouse.containsMouse ? backgroundColor : modelData.GroupOpen ? backgroundColor : palette.base - - Rectangle { - anchors.left: parent.right - anchors.top: parent.bottom - anchors.margins: -shadowWidth - width: 2 * shadowWidth - height: 2 * shadowWidth - radius: shadowWidth - rotation: 45 - z: -1 - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.5; color: shadowColor } - GradientStop { position: 1.0; color: backgroundColor } - } - } - - Rectangle { - anchors.right: parent.left - anchors.top: parent.bottom - anchors.margins: -shadowWidth - width: 2 * shadowWidth - height: 2 * shadowWidth - radius: shadowWidth - rotation: -45 - z: -1 - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.5; color: shadowColor } - GradientStop { position: 0.0; color: backgroundColor } - } - } - - Rectangle { - anchors.right: parent.left - anchors.top: parent.top - anchors.bottom: parent.bottom - - width: shadowWidth - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: backgroundColor } - GradientStop { position: 1.0; color: shadowColor } - } - } - - Rectangle { - anchors.left: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - - width: shadowWidth - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: shadowColor } - GradientStop { position: 1.0; color: backgroundColor } - } - } - Rectangle { - anchors.top: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - - height: shadowWidth - gradient: Gradient { - GradientStop { position: 0.0; color: shadowColor } - GradientStop { position: 1.0; color: backgroundColor } - } - } - - Rectangle { - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - height: 1 - color: backgroundColor - } - - MouseArea { - id: delegateMouse - hoverEnabled: true - anchors.fill: parent - onClicked: { - if (modelData.Group) - toggl.toggleEntriesGroup(modelData.GroupName) - else - toggl.editTimeEntry(modelData.GUID, "description") - } - } - RowLayout { - anchors.fill: parent - anchors.margins: 12 - spacing: 9 - Rectangle { - Layout.alignment: Qt.AlignVCenter - visible: modelData.Group - width: 24 - height: 24 - radius: 4 - color: modelData.GroupOpen ? "dark green" : palette.base - border { - color: modelData.GroupOpen ? "transparent" : palette.alternateBase - width: 0.5 - } - Text { - color: modelData.GroupOpen ? "light green" : palette.alternateBase - anchors.centerIn: parent - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: modelData.GroupItemCount - } - } - ColumnLayout { - Layout.fillHeight: true - Layout.fillWidth: true - Text { - Layout.fillWidth: true - text: modelData.Description.length > 0 ? modelData.Description : "+ Add description" - color: modelData.Description.length > 0 ? palette.text : disabledPalette.text - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - font.pixelSize: 12 - verticalAlignment: Text.AlignVCenter - } - RowLayout { - Layout.fillWidth: true - Rectangle { - height: 3 - width: height - radius: height / 2 - visible: modelData.ClientLabel.length > 0 - color: modelData.Color - Layout.alignment: Qt.AlignVCenter - } - Text { - visible: modelData.ClientLabel.length > 0 - text: modelData.ClientLabel - color: modelData.Color.length > 0 ? modelData.Color : palette.text - font.pixelSize: 12 - } - Text { - text: modelData.ProjectLabel.length > 0 ? modelData.ProjectLabel : "+ Add project" - color: modelData.ProjectLabel.length > 0 ? palette.text : disabledPalette.text - font.pixelSize: 12 - } - Text { - visible: modelData.TaskLabel.length > 0 - text: modelData.TaskLabel - color: palette.text - font.pixelSize: 12 - } - Item { - Layout.fillWidth: true - } - } - } - Text { - Layout.alignment: Qt.AlignVCenter - text: modelData.Duration - color: palette.text - } - Item { - id: startButton - opacity: delegateMouse.containsMouse ? 1.0 : 0.0 - width: 20 - height: 20 - MouseArea { - anchors.fill: parent - onClicked: toggl.continueTimeEntry(modelData.GUID) - } - - Item { - anchors.centerIn: parent - scale: 1.5 + delegate: TimeEntryListItem { - Item { - x: -4.5 - y: -1 - /* Could be worth replacing with an actual picture eventually... */ - Rectangle { - y: -Math.sqrt(3)/6 * width + radius / 2 - width: startButton.width / 2.7 - height: radius - rotation: 30 - radius: 2 - color: "#47bc00" - } - Rectangle { - y: Math.sqrt(3)/6 * width - radius / 2 - width: startButton.width / 2.7 - height: radius - rotation: -30 - radius: 2 - color: "#47bc00" - } - Rectangle { - x: -Math.sqrt(3)/6 * width - radius / 2 - width: startButton.width / 2.7 - height: radius - rotation: 90 - radius: 2 - color: "#47bc00" - } - Rectangle { - x: -Math.sqrt(3)/6 * width + 1.6 - width: startButton.width / 2.7 - 3 - height: 2 - rotation: 90 - color: "#47bc00" - } - Rectangle { - x: -Math.sqrt(3)/6 * width + 3.6 - width: startButton.width / 2.7 - 5 - height: 2 - rotation: 90 - color: "#47bc00" - } - Rectangle { - x: -Math.sqrt(3)/6 * width + 5.6 - width: startButton.width / 2.7 - 7 - height: 2 - rotation: 90 - color: "#47bc00" - } - Rectangle { - x: -Math.sqrt(3)/6 * width + 7.6 - width: startButton.width / 2.7 - 9 - height: 2 - rotation: 90 - color: "#47bc00" - } - } - } - } } } } diff --git a/src/ui/linux/TogglDesktop/TogglShadowBox.qml b/src/ui/linux/TogglDesktop/TogglShadowBox.qml new file mode 100644 index 0000000000..256ac46c29 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglShadowBox.qml @@ -0,0 +1,144 @@ +import QtQuick 2.12 + +Item { + id: root + z: -1 + + property real shadowWidth + property color shadowColor + property color backgroundColor + + enum Side { + None = 0, + Left = 1, + Right = 2, + Top = 4, + Bottom = 8 + } + + property int sides: TogglShadowBox.Side.None + + // corners + Rectangle { + visible: (sides & TogglShadowBox.Side.Left) && (sides & TogglShadowBox.Side.Top) + anchors.right: parent.left + anchors.bottom: parent.top + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: 45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: backgroundColor } + GradientStop { position: 0.5; color: shadowColor } + } + } + + Rectangle { + visible: (sides & TogglShadowBox.Side.Right) && (sides & TogglShadowBox.Side.Top) + anchors.left: parent.right + anchors.bottom: parent.top + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: -45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.5; color: shadowColor } + GradientStop { position: 1.0; color: backgroundColor } + } + } + + Rectangle { + visible: (sides & TogglShadowBox.Side.Right) && (sides & TogglShadowBox.Side.Bottom) + anchors.left: parent.right + anchors.top: parent.bottom + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: 45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.5; color: shadowColor } + GradientStop { position: 1.0; color: backgroundColor } + } + } + + Rectangle { + visible: (sides & TogglShadowBox.Side.Left) && (sides & TogglShadowBox.Side.Bottom) + anchors.right: parent.left + anchors.top: parent.bottom + anchors.margins: -shadowWidth + width: 2 * shadowWidth + height: 2 * shadowWidth + radius: shadowWidth + rotation: -45 + z: -1 + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.5; color: shadowColor } + GradientStop { position: 0.0; color: backgroundColor } + } + } + + // sides + Rectangle { + visible: sides & TogglShadowBox.Side.Left + anchors.right: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + + width: shadowWidth + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: backgroundColor } + GradientStop { position: 1.0; color: shadowColor } + } + } + + Rectangle { + visible: sides & TogglShadowBox.Side.Right + anchors.left: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + width: shadowWidth + gradient: Gradient { + orientation: Gradient.Horizontal + GradientStop { position: 0.0; color: shadowColor } + GradientStop { position: 1.0; color: backgroundColor } + } + } + + Rectangle { + visible: sides & TogglShadowBox.Side.Bottom + anchors.top: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + + height: shadowWidth + gradient: Gradient { + GradientStop { position: 0.0; color: shadowColor } + GradientStop { position: 1.0; color: backgroundColor } + } + } + + Rectangle { + visible: sides & TogglShadowBox.Side.Top + anchors.bottom: parent.top + anchors.right: parent.right + anchors.left: parent.left + + height: shadowWidth + gradient: Gradient { + GradientStop { position: 0.0; color: backgroundColor } + GradientStop { position: 1.0; color: shadowColor } + } + } +} From cbc4503dea3d8098d62eed3de93701dbce130027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 10:49:49 +0100 Subject: [PATCH 43/83] Prune undefined references (linux) --- src/ui/linux/TogglDesktop/TimeEntryListItem.qml | 4 ++-- src/ui/linux/TogglDesktop/TimeEntryListView.qml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index b61545678b..d47913d24c 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -12,7 +12,7 @@ Item { rightMargin: anchors.leftMargin topMargin: 0 } - color: delegateMouse.containsMouse ? backgroundColor : modelData.GroupOpen ? backgroundColor : palette.base + color: delegateMouse.containsMouse ? palette.listBackground : modelData.GroupOpen ? palette.listBackground : palette.base TogglShadowBox { anchors.fill: parent @@ -27,7 +27,7 @@ Item { anchors.left: parent.left anchors.right: parent.right height: 1 - color: backgroundColor + color: palette.listBackground } MouseArea { diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 895f454e5e..e780ffc29c 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -6,7 +6,7 @@ import QtGraphicalEffects 1.0 Rectangle { id: root anchors.fill: parent - color: backgroundColor + color: palette.listBackground property real sectionMargin: Math.max(2 * shadowWidth, 12) property real headerHeight: 32 From 9c3e0f99d4d41c216cc78e7cb83088ebe485d43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 12:21:24 +0100 Subject: [PATCH 44/83] Fix up the look of the Time Entry Editor view (linux) --- src/ui/linux/TogglDesktop/MainWindow.qml | 33 +- .../linux/TogglDesktop/TimeEntryEditView.qml | 443 ++++++++++-------- 2 files changed, 243 insertions(+), 233 deletions(-) diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index f29568e233..90c5d54a0c 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -3,7 +3,6 @@ import QtQuick.Window 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 1.4 -import QtQuick.Dialogs 1.2 ApplicationWindow { id: window @@ -25,7 +24,7 @@ ApplicationWindow { SystemPalette { id: palette - property bool isDark: (shadowColor.r + shadowColor.g + shadowColor.b) < 300 + property bool isDark: (itemShadow.r + itemShadow.g + itemShadow.b) < 300 property color itemShadow: palette.shadow property color listBackground: mixColors(palette.base, palette.alternateBase, 0.8) @@ -90,38 +89,12 @@ ApplicationWindow { Loader { id: mainView - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.right: timeEntryEdit.visible ? timeEntryEdit.left : parent.right - } - - Dialog { - //visible: true - //flags: Qt.WA_TranslucentBackground | Qt.FramelessWindowHint | Qt.ToolTip | Qt.WindowStaysOnTopHint - Rectangle { - anchors.centerIn: parent - width: parent.width - height: parent.height - radius: height / 2 - color: "red" - } + anchors.fill: parent } TimeEntryEditView { id: timeEntryEdit visible: false - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - - width: 360 - onVisibleChanged: { - if (visible) - window.width += width - else - window.width -= width - } + anchors.fill: parent } - } diff --git a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml index 0fe422598b..b9e193d4ad 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml @@ -2,271 +2,308 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 -Rectangle { - color: palette.alternateBase - +Item { property var timeEntry: null + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton + } - ColumnLayout { + Rectangle { anchors.fill: parent - anchors.margins: 12 - spacing: 9 + color: palette.base + opacity: 0.5 + } - TogglTextField { - Layout.fillWidth: true - text: timeEntry.Description - } - TogglTextField { - Layout.fillWidth: true - text: timeEntry.ClientLabel + " . " + timeEntry.ProjectLabel + Rectangle { + anchors.fill: parent + anchors.margins: 9 + radius: 6 + + color: palette.base + opacity: visible ? 1.0 : 0.0 + Behavior on opacity { + NumberAnimation { + duration: 120 + } } - TogglButton { - Layout.alignment: Qt.AlignRight - text: "Add project" + border { + width: 1 + color: palette.shadow } - GridLayout { - Layout.fillWidth: true - columns: 4 - Text { - text: "Duration:" - } - TogglTextField { - Layout.fillWidth: true - Layout.columnSpan: 3 - text: timeEntry.Duration - } + ColumnLayout { + anchors.fill: parent + anchors.margins: 12 + spacing: 9 - Text { - text: "Start-end time:" - } TogglTextField { Layout.fillWidth: true - text: timeEntry.StartTimeString - } - Text { - text: "-" + text: timeEntry ? timeEntry.Description : "" } TogglTextField { Layout.fillWidth: true - text: timeEntry.EndTimeString + text: timeEntry ? (timeEntry.ClientLabel + " . " + timeEntry.ProjectLabel) : "" } - Text { - text: "Date:" - } - TogglTextField { - Layout.fillWidth: true - Layout.columnSpan: 3 - text: new Date(Date(timeEntry.Started)).toLocaleDateString(Qt.locale(), Locale.ShortFormat) + TogglButton { + Layout.alignment: Qt.AlignRight + text: "Add project" } - Text { - text: "Tags" - Layout.fillHeight: true - } - ListView { + GridLayout { Layout.fillWidth: true - Layout.fillHeight: true - Layout.columnSpan: 3 - Frame { - anchors.fill: parent - background: Rectangle { - color: palette.base - border.color: palette.dark - border.width: 1 - } - ColumnLayout { - anchors.fill: parent - Flow { - Layout.fillHeight: true - Layout.fillWidth: true - Repeater { - model: timeEntry.Tags - delegate: Item { - width: childrenRect.width + 4 - height: childrenRect.height + 4 - Rectangle { - x: 2 - y: 2 - width: selectedTagLayout.width + 4 - height: selectedTagLayout.height + 4 - color: "white" - border.width: 1 - border.color: "#d4d4d4" - radius: 2 + columns: 4 + Text { + text: "Duration:" + color: palette.text + } + TogglTextField { + Layout.fillWidth: true + Layout.columnSpan: 3 + text: timeEntry ? timeEntry.Duration : "" + } - RowLayout { + Text { + text: "Start-end time:" + color: palette.text + } + TogglTextField { + Layout.fillWidth: true + text: timeEntry ? timeEntry.StartTimeString : "" + } + Text { + text: "-" + color: palette.text + } + TogglTextField { + Layout.fillWidth: true + text: timeEntry ? timeEntry.EndTimeString : "" + } + + Text { + text: "Date:" + color: palette.text + } + TogglTextField { + Layout.fillWidth: true + Layout.columnSpan: 3 + text: timeEntry ? (new Date(Date(timeEntry.Started)).toLocaleDateString(Qt.locale(), Locale.ShortFormat)) : "" + } + + Text { + text: "Tags" + Layout.fillHeight: true + color: palette.text + } + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.columnSpan: 3 + Frame { + anchors.fill: parent + background: Rectangle { + color: palette.base + border.color: palette.dark + border.width: 1 + } + ColumnLayout { + anchors.fill: parent + Flow { + Layout.fillHeight: true + Layout.fillWidth: true + Repeater { + model: timeEntry ? timeEntry.Tags : null + delegate: Item { + width: childrenRect.width + 4 + height: childrenRect.height + 4 + Rectangle { x: 2 y: 2 - id: selectedTagLayout - Text { - id: selectedTagText - text: modelData - font.pixelSize: 10 + width: selectedTagLayout.width + 4 + height: selectedTagLayout.height + 4 + color: "white" + border.width: 1 + border.color: "#d4d4d4" + radius: 2 + + RowLayout { + x: 2 + y: 2 + id: selectedTagLayout + Text { + id: selectedTagText + text: modelData + font.pixelSize: 10 - Rectangle { - Behavior on opacity { NumberAnimation { } } - opacity: selectedTagMouse.containsMouse ? 1.0 : 0.0 - visible: opacity > 0.0 - width: 10 - height: 10 - radius: 3 - anchors.centerIn: parent - color: "red" - Text { + Rectangle { + Behavior on opacity { NumberAnimation { } } + opacity: selectedTagMouse.containsMouse ? 1.0 : 0.0 + visible: opacity > 0.0 + width: 10 + height: 10 + radius: 3 anchors.centerIn: parent - text: "x" - font.pointSize: 8 + color: "red" + Text { + anchors.centerIn: parent + text: "x" + font.pointSize: 8 + } } } } - } - MouseArea { - id: selectedTagMouse - anchors.fill: parent - hoverEnabled: true - onClicked: { - var list = timeEntry.Tags - var index = list.indexOf(modelData); - if (index > -1) { - list.splice(index, 1); - toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + MouseArea { + id: selectedTagMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + var list = timeEntry.Tags + var index = list.indexOf(modelData); + if (index > -1) { + list.splice(index, 1); + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + } } } } } } } - } - - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#c3c3c3" - } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#c3c3c3" + } - Flow { - clip: true - Layout.fillHeight: true - Layout.fillWidth: true - Repeater { - model: toggl.tags.filter(n => !timeEntry.Tags.includes(n)) - delegate: Item { - width: childrenRect.width + 4 - height: childrenRect.height + 4 - Rectangle { - x: 2 - y: 2 - width: tagLayout.width + 4 - height: tagLayout.height + 4 - color: "white" - border.width: 1 - border.color: "#d4d4d4" - radius: 2 - RowLayout { - id: tagLayout + Flow { + clip: true + Layout.fillHeight: true + Layout.fillWidth: true + Repeater { + model: timeEntry ? (toggl.tags.filter(n => !timeEntry.Tags.includes(n))) : null + delegate: Item { + width: childrenRect.width + 4 + height: childrenRect.height + 4 + Rectangle { x: 2 y: 2 - Text { - id: tagText - text: modelData - font.pixelSize: 10 - Rectangle { - Behavior on opacity { NumberAnimation { } } - opacity: tagMouse.containsMouse ? 1.0 : 0.0 - visible: opacity > 0.0 - width: 10 - height: 10 - radius: 3 - anchors.centerIn: parent - color: "green" - Text { + width: tagLayout.width + 4 + height: tagLayout.height + 4 + color: "white" + border.width: 1 + border.color: "#d4d4d4" + radius: 2 + + RowLayout { + id: tagLayout + x: 2 + y: 2 + Text { + id: tagText + text: modelData + font.pixelSize: 10 + Rectangle { + Behavior on opacity { NumberAnimation { } } + opacity: tagMouse.containsMouse ? 1.0 : 0.0 + visible: opacity > 0.0 + width: 10 + height: 10 + radius: 3 anchors.centerIn: parent - text: "+" - font.pointSize: 8 + color: "green" + Text { + anchors.centerIn: parent + text: "+" + font.pointSize: 8 + } } } } - } - MouseArea { - id: tagMouse - anchors.fill: parent - hoverEnabled: true - onClicked: { - var list = timeEntry.Tags - list.push(modelData) - toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + MouseArea { + id: tagMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + var list = timeEntry.Tags + list.push(modelData) + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + } } } } } } - } - RowLayout { - Layout.fillWidth: true - Text { - text: "New:" - } - TogglTextField { + RowLayout { Layout.fillWidth: true - id: newTagField - onAccepted: { - if (text.length > 0) { - var list = timeEntry.Tags - list.push(newTagField.text) - toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) - newTagField.text = "" + Text { + text: "New:" + color: palette.text + } + TogglTextField { + Layout.fillWidth: true + id: newTagField + onAccepted: { + if (text.length > 0) { + var list = timeEntry.Tags + list.push(newTagField.text) + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + newTagField.text = "" + } } } - } - TogglButton { - Layout.preferredWidth: 64 - text: "Add" - onClicked: { - if (text.length > 0) { - var list = timeEntry.Tags - list.push(newTagField.text) - toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) - newTagField.text = "" + TogglButton { + Layout.preferredWidth: 64 + text: "Add" + onClicked: { + if (text.length > 0) { + var list = timeEntry.Tags + list.push(newTagField.text) + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + newTagField.text = "" + } } } } } } } - } - Text { - text: "Workspace:" - } - Label { - Layout.fillWidth: true - Layout.columnSpan: 3 - text: timeEntry.WorkspaceName - } - } - RowLayout { - TogglButton { - text: "Done" - onClicked: toggl.viewTimeEntryList() + Text { + text: "Workspace:" + color: palette.text + } + Text { + Layout.fillWidth: true + Layout.columnSpan: 3 + text: timeEntry ? timeEntry.WorkspaceName : "" + color: palette.text + } } - TogglButton { - text: "Delete" - onClicked: toggl.deleteTimeEntry(timeEntry.GUID) + RowLayout { + TogglButton { + text: "Done" + onClicked: toggl.viewTimeEntryList() + } + TogglButton { + text: "Delete" + onClicked: toggl.deleteTimeEntry(timeEntry.GUID) + } + TogglButton { + text: "Cancel" + onClicked: toggl.viewTimeEntryList() + } } - TogglButton { - text: "Cancel" - onClicked: toggl.viewTimeEntryList() + Text { + text: timeEntry ? timeEntry.lastUpdate() : "" + color: palette.text } } - Text { - text: timeEntry.lastUpdate() - } } } From 521fe2b97be3e31027e00f3c44994330e0314776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 12:40:18 +0100 Subject: [PATCH 45/83] Get rid of all unnecessary C++ UI code (linux) --- src/ui/linux/TogglDesktop/CMakeLists.txt | 27 - src/ui/linux/TogglDesktop/aboutdialog.cpp | 60 -- src/ui/linux/TogglDesktop/aboutdialog.h | 32 - src/ui/linux/TogglDesktop/aboutdialog.ui | 112 --- .../TogglDesktop/autocompletecombobox.cpp | 213 ----- .../linux/TogglDesktop/autocompletecombobox.h | 75 -- .../TogglDesktop/autocompletelistmodel.cpp | 60 ++ .../TogglDesktop/autocompletelistmodel.h | 15 + .../TogglDesktop/autocompletelistview.cpp | 367 --------- .../linux/TogglDesktop/autocompletelistview.h | 52 -- src/ui/linux/TogglDesktop/clickablelabel.cpp | 24 - src/ui/linux/TogglDesktop/clickablelabel.h | 21 - src/ui/linux/TogglDesktop/colorpicker.cpp | 67 -- src/ui/linux/TogglDesktop/colorpicker.h | 32 - src/ui/linux/TogglDesktop/colorpicker.ui | 98 --- .../TogglDesktop/errorviewcontroller.cpp | 63 -- .../linux/TogglDesktop/errorviewcontroller.h | 43 - .../linux/TogglDesktop/errorviewcontroller.ui | 143 ---- src/ui/linux/TogglDesktop/feedbackdialog.cpp | 61 -- src/ui/linux/TogglDesktop/feedbackdialog.h | 30 - src/ui/linux/TogglDesktop/feedbackdialog.ui | 135 --- src/ui/linux/TogglDesktop/loginwidget.cpp | 202 ----- src/ui/linux/TogglDesktop/loginwidget.h | 72 -- src/ui/linux/TogglDesktop/loginwidget.ui | 499 ----------- src/ui/linux/TogglDesktop/main.cpp | 4 +- .../TogglDesktop/mainwindowcontroller.cpp | 584 ------------- .../linux/TogglDesktop/mainwindowcontroller.h | 149 ---- .../TogglDesktop/mainwindowcontroller.ui | 221 ----- src/ui/linux/TogglDesktop/overlaywidget.cpp | 96 --- src/ui/linux/TogglDesktop/overlaywidget.h | 40 - src/ui/linux/TogglDesktop/overlaywidget.ui | 82 -- .../linux/TogglDesktop/preferencesdialog.cpp | 333 -------- src/ui/linux/TogglDesktop/preferencesdialog.h | 69 -- .../linux/TogglDesktop/preferencesdialog.ui | 476 ----------- src/ui/linux/TogglDesktop/systemtray.cpp | 10 +- src/ui/linux/TogglDesktop/systemtray.h | 5 +- .../TogglDesktop/timeentrycellwidget.cpp | 227 ----- .../linux/TogglDesktop/timeentrycellwidget.h | 63 -- .../linux/TogglDesktop/timeentrycellwidget.ui | 484 ----------- .../TogglDesktop/timeentryeditorwidget.cpp | 664 --------------- .../TogglDesktop/timeentryeditorwidget.h | 127 --- .../TogglDesktop/timeentryeditorwidget.ui | 779 ------------------ .../TogglDesktop/timeentrylistwidget.cpp | 140 ---- .../linux/TogglDesktop/timeentrylistwidget.h | 55 -- .../linux/TogglDesktop/timeentrylistwidget.ui | 98 --- src/ui/linux/TogglDesktop/timerwidget.cpp | 399 --------- src/ui/linux/TogglDesktop/timerwidget.h | 94 --- src/ui/linux/TogglDesktop/timerwidget.ui | 476 ----------- src/ui/linux/TogglDesktop/toggl.h | 1 - 49 files changed, 83 insertions(+), 8096 deletions(-) delete mode 100644 src/ui/linux/TogglDesktop/aboutdialog.cpp delete mode 100644 src/ui/linux/TogglDesktop/aboutdialog.h delete mode 100644 src/ui/linux/TogglDesktop/aboutdialog.ui delete mode 100644 src/ui/linux/TogglDesktop/autocompletecombobox.cpp delete mode 100644 src/ui/linux/TogglDesktop/autocompletecombobox.h delete mode 100644 src/ui/linux/TogglDesktop/autocompletelistview.cpp delete mode 100644 src/ui/linux/TogglDesktop/autocompletelistview.h delete mode 100644 src/ui/linux/TogglDesktop/clickablelabel.cpp delete mode 100644 src/ui/linux/TogglDesktop/clickablelabel.h delete mode 100644 src/ui/linux/TogglDesktop/colorpicker.cpp delete mode 100644 src/ui/linux/TogglDesktop/colorpicker.h delete mode 100644 src/ui/linux/TogglDesktop/colorpicker.ui delete mode 100644 src/ui/linux/TogglDesktop/errorviewcontroller.cpp delete mode 100644 src/ui/linux/TogglDesktop/errorviewcontroller.h delete mode 100644 src/ui/linux/TogglDesktop/errorviewcontroller.ui delete mode 100644 src/ui/linux/TogglDesktop/feedbackdialog.cpp delete mode 100644 src/ui/linux/TogglDesktop/feedbackdialog.h delete mode 100644 src/ui/linux/TogglDesktop/feedbackdialog.ui delete mode 100644 src/ui/linux/TogglDesktop/loginwidget.cpp delete mode 100644 src/ui/linux/TogglDesktop/loginwidget.h delete mode 100644 src/ui/linux/TogglDesktop/loginwidget.ui delete mode 100644 src/ui/linux/TogglDesktop/mainwindowcontroller.cpp delete mode 100644 src/ui/linux/TogglDesktop/mainwindowcontroller.h delete mode 100644 src/ui/linux/TogglDesktop/mainwindowcontroller.ui delete mode 100644 src/ui/linux/TogglDesktop/overlaywidget.cpp delete mode 100644 src/ui/linux/TogglDesktop/overlaywidget.h delete mode 100644 src/ui/linux/TogglDesktop/overlaywidget.ui delete mode 100644 src/ui/linux/TogglDesktop/preferencesdialog.cpp delete mode 100644 src/ui/linux/TogglDesktop/preferencesdialog.h delete mode 100644 src/ui/linux/TogglDesktop/preferencesdialog.ui delete mode 100644 src/ui/linux/TogglDesktop/timeentrycellwidget.cpp delete mode 100644 src/ui/linux/TogglDesktop/timeentrycellwidget.h delete mode 100644 src/ui/linux/TogglDesktop/timeentrycellwidget.ui delete mode 100644 src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp delete mode 100644 src/ui/linux/TogglDesktop/timeentryeditorwidget.h delete mode 100644 src/ui/linux/TogglDesktop/timeentryeditorwidget.ui delete mode 100644 src/ui/linux/TogglDesktop/timeentrylistwidget.cpp delete mode 100644 src/ui/linux/TogglDesktop/timeentrylistwidget.h delete mode 100644 src/ui/linux/TogglDesktop/timeentrylistwidget.ui delete mode 100644 src/ui/linux/TogglDesktop/timerwidget.cpp delete mode 100644 src/ui/linux/TogglDesktop/timerwidget.h delete mode 100644 src/ui/linux/TogglDesktop/timerwidget.ui diff --git a/src/ui/linux/TogglDesktop/CMakeLists.txt b/src/ui/linux/TogglDesktop/CMakeLists.txt index 002d94e554..ef4ebf6314 100644 --- a/src/ui/linux/TogglDesktop/CMakeLists.txt +++ b/src/ui/linux/TogglDesktop/CMakeLists.txt @@ -15,53 +15,26 @@ include_directories( # TogglDesktop sources set(BINARY_SOURCE_FILES - aboutdialog.cpp - autocompletecombobox.cpp - autocompletelistview.cpp autocompletelistmodel.cpp autocompleteview.cpp - clickablelabel.cpp - colorpicker.cpp countryview.cpp - errorviewcontroller.cpp - feedbackdialog.cpp genericview.cpp idlenotificationwidget.cpp - loginwidget.cpp main.cpp - mainwindowcontroller.cpp networkmanagement.cpp - overlaywidget.cpp powermanagement.cpp - preferencesdialog.cpp qmlobjectlist.cc settingsview.cpp singleapplication.cpp systemtray.cpp - timeentrycellwidget.cpp - timeentryeditorwidget.cpp - timeentrylistwidget.cpp timeentryview.cpp - timerwidget.cpp toggl.cpp # Resources have to be listed Resources.qrc # it's better to list UI files - aboutdialog.ui - colorpicker.ui - errorviewcontroller.ui - feedbackdialog.ui idlenotificationwidget.ui - loginwidget.ui - mainwindowcontroller.ui - overlaywidget.ui - preferencesdialog.ui - timeentrycellwidget.ui - timeentryeditorwidget.ui - timeentrylistwidget.ui - timerwidget.ui ) # Set up compilation targets diff --git a/src/ui/linux/TogglDesktop/aboutdialog.cpp b/src/ui/linux/TogglDesktop/aboutdialog.cpp deleted file mode 100644 index 7e8cb04003..0000000000 --- a/src/ui/linux/TogglDesktop/aboutdialog.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#include "./aboutdialog.h" -#include "./ui_aboutdialog.h" - -#include // NOLINT -#include // NOLINT -#include // NOLINT - -#include "./toggl.h" - -AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), -ui(new Ui::AboutDialog) { - ui->setupUi(this); - - ui->releaseChannel->addItem("stable"); - ui->releaseChannel->addItem("beta"); - ui->releaseChannel->addItem("dev"); - - ui->version->setText(QApplication::applicationVersion()); - - connect(TogglApi::instance, SIGNAL(displayUpdate(QString)), // NOLINT - this, SLOT(displayUpdate(QString))); // NOLINT -} - -AboutDialog::~AboutDialog() { - delete ui; -} - -void AboutDialog::displayUpdate(const QString update_url) { - qDebug() << "displayUpdate update_url=" << update_url; - - url = update_url; - - QString channel = TogglApi::instance->updateChannel(); - ui->releaseChannel->setCurrentText(channel); - ui->releaseChannel->setEnabled(true); - ui->updateButton->setEnabled(!url.isEmpty()); - - if (!url.isEmpty()) { - ui->updateButton->setText( - "Click here to download update!"); - } else { - ui->updateButton->setText("Toggl Desktop is up to date"); - } -} - -void AboutDialog::on_updateButton_clicked() { - qDebug() << "on_updateButton_clicked url=" << url; - QDesktopServices::openUrl(QUrl(url)); - TogglApi::instance->shutdown = true; - qApp->exit(0); -} - -void AboutDialog::on_releaseChannel_activated(const QString &arg1) { - qDebug() << "on_releaseChannel_activated channel=" << arg1; - ui->updateButton->setEnabled(false); - ui->updateButton->setText("Checking for update"); - TogglApi::instance->setUpdateChannel(arg1); -} diff --git a/src/ui/linux/TogglDesktop/aboutdialog.h b/src/ui/linux/TogglDesktop/aboutdialog.h deleted file mode 100644 index 255ea308db..0000000000 --- a/src/ui/linux/TogglDesktop/aboutdialog.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_ABOUTDIALOG_H_ -#define SRC_UI_LINUX_TOGGLDESKTOP_ABOUTDIALOG_H_ - -#include - -namespace Ui { -class AboutDialog; -} - -class AboutDialog : public QDialog { - Q_OBJECT - - public: - explicit AboutDialog(QWidget *parent = 0); - ~AboutDialog(); - - private slots: // NOLINT - void displayUpdate(const QString url); - - void on_updateButton_clicked(); - - void on_releaseChannel_activated(const QString &arg1); - - private: - Ui::AboutDialog *ui; - - QString url; -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_ABOUTDIALOG_H_ diff --git a/src/ui/linux/TogglDesktop/aboutdialog.ui b/src/ui/linux/TogglDesktop/aboutdialog.ui deleted file mode 100644 index 23656c0bcd..0000000000 --- a/src/ui/linux/TogglDesktop/aboutdialog.ui +++ /dev/null @@ -1,112 +0,0 @@ - - - AboutDialog - - - - 0 - 0 - 299 - 300 - - - - About - - - color:rgb(220, 220, 220);background-color: rgb(45, 45, 45);font-size:13px; - - - - - - false - - - - - - :/images/toggl-white-icon.png - - - Qt::AlignCenter - - - - - - - - 16777215 - 30 - - - - Toggl Desktop - - - Qt::AlignCenter - - - - - - - - 16777215 - 25 - - - - - -1 - - - - Version 1.0.0 - - - Qt::AlignCenter - - - - - - - false - - - color:rgb(220, 220, 220); - - - Toggl Desktop is up to date - - - - - - - - - - Release channel - - - - - - - color:rgb(220, 220, 220); - - - - - - - - - - - - - diff --git a/src/ui/linux/TogglDesktop/autocompletecombobox.cpp b/src/ui/linux/TogglDesktop/autocompletecombobox.cpp deleted file mode 100644 index 0b3795a3d9..0000000000 --- a/src/ui/linux/TogglDesktop/autocompletecombobox.cpp +++ /dev/null @@ -1,213 +0,0 @@ -#include "autocompletecombobox.h" -#include "autocompletelistmodel.h" -#include "autocompletelistview.h" - -#include -#include - -AutocompleteComboBox::AutocompleteComboBox(QWidget *parent) - : QComboBox(parent) - , completer(new AutocompleteCompleter(this)) - , proxyModel(new AutocompleteProxyModel(this)) - , listView(qobject_cast(completer->popup())) -{ - setEditable(true); - completer->installEventFilter(this); - listView->installEventFilter(this); - completer->setModel(proxyModel); - setCompleter(completer); - connect(listView, &AutocompleteListView::visibleChanged, this, &AutocompleteComboBox::onDropdownVisibleChanged); - connect(lineEdit(), &QLineEdit::textEdited, proxyModel, &AutocompleteProxyModel::setFilterFixedString); - connect(listView, &AutocompleteListView::selected, this, &AutocompleteComboBox::onDropdownSelected); -} - -void AutocompleteComboBox::setModel(QAbstractItemModel *model) { - proxyModel->setSourceModel(model); -} - -void AutocompleteComboBox::showPopup() { - completer->complete(); -} - -bool AutocompleteComboBox::eventFilter(QObject *o, QEvent *e) { - if (e->type() == QEvent::KeyPress) { - auto ke = reinterpret_cast(e); - switch (ke->key()) { - case Qt::Key_Tab: - case Qt::Key_Escape: - cancelSelection(); - return true; - case Qt::Key_Enter: - case Qt::Key_Return: { - if (ke->modifiers() & Qt::CTRL) { - cancelSelection(); - e->ignore(); - return true; - } - if (listView->currentIndex().isValid()) { - listView->keyPressEvent(ke); - } - else { - listView->hide(); - emit returnPressed(); - } - return true; - } - case Qt::Key_Home: - case Qt::Key_End: - case Qt::Key_PageDown: - case Qt::Key_PageUp: - case Qt::Key_Up: - case Qt::Key_Down: - if (!listView->isVisible()) - completer->complete(); - else - listView->keyPressEvent(ke); - return true; - default: - QComboBox::keyPressEvent(ke); - //lineEdit()->keyPressEvent(ke); - return true; - } - } - return QComboBox::eventFilter(o, e); -} - -AutocompleteView *AutocompleteComboBox::currentView() { - return qvariant_cast(listView->currentIndex().data(Qt::UserRole)); -} - -void AutocompleteComboBox::keyPressEvent(QKeyEvent *event) { - switch (event->key()) { - case Qt::Key_Enter: - case Qt::Key_Return: - if (event->modifiers() & Qt::CTRL) { - event->ignore(); - return; - } - emit returnPressed(); - break; - case Qt::Key_Home: - case Qt::Key_End: - case Qt::Key_PageDown: - case Qt::Key_PageUp: - case Qt::Key_Down: - case Qt::Key_Up: - if (!listView->isVisible()) - completer->complete(); - else - listView->keyPressEvent(event); - break; - default: - QComboBox::keyPressEvent(event); - } -} - -void AutocompleteComboBox::onDropdownVisibleChanged() { - if (listView->isVisible()) { - oldLabel = currentText(); - proxyModel->setFilterFixedString(currentText()); - } -} - -void AutocompleteComboBox::onDropdownSelected(AutocompleteView *item) { - if (item) { - switch (item->Type) { - case 0: - emit projectSelected(item->ProjectLabel, item->ProjectID, item->ProjectColor, item->TaskLabel, item->TaskID); - emit billableChanged(item->Billable); - emit tagsChanged(item->Tags); - emit timeEntrySelected(item->Text); - break; - case 1: - emit projectSelected(item->ProjectLabel, item->ProjectID, item->ProjectColor, item->TaskLabel, item->TaskID); - emit billableChanged(item->Billable); - break; - case 2: - emit projectSelected(item->Text, item->ProjectID, item->ProjectColor, QString(), 0); - break; - default: - break; - } - } - emit activated(listView->currentIndex().row()); -} - -void AutocompleteComboBox::cancelSelection() { - setCurrentText(oldLabel); - oldLabel = QString(); - listView->setVisible(false); -} - -AutocompleteCompleter::AutocompleteCompleter(QWidget *parent) - : QCompleter(parent) -{ - setMaxVisibleItems(20); - setPopup(new AutocompleteListView(parent)); - setCompletionMode(QCompleter::UnfilteredPopupCompletion); -} - -bool AutocompleteCompleter::eventFilter(QObject *o, QEvent *e) { - // completely ignore key events in the filter and let the combobox handle them - if (e->type() == QEvent::KeyPress) { - return false; - } - return QCompleter::eventFilter(o, e); -} - -AutocompleteProxyModel::AutocompleteProxyModel(QObject *parent) - : QSortFilterProxyModel(parent) { - setFilterRole(Qt::UserRole); -} - -int AutocompleteProxyModel::count() { - return rowCount(QModelIndex()); -} - -void AutocompleteProxyModel::setFilter(const QString &filter) { - setFilterRegExp(filter); -} - -AutocompleteView *AutocompleteProxyModel::get(int idx) { - if (idx >= 0 && idx < rowCount()) { - auto d = data(index(idx, 0), Qt::UserRole); - if (!d.isValid()) - return nullptr; - auto ptr = qvariant_cast(d); - if (!ptr) - return nullptr; - QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); - return ptr; - } - return nullptr; -} - -bool AutocompleteProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { - QString input = filterRegExp().pattern(); - QStringList words = input.split(" "); - - auto variant = sourceModel()->data(sourceModel()->index(source_row, 0), Qt::UserRole); - if (!variant.isValid()) - return false; - auto view = qvariant_cast(variant); - if (!view) - return false; - for (auto word : words) { - if (word.isEmpty() && words.count() > 1) - continue; - if (view->_Children.isEmpty()) { - if (view->Description.contains(word, Qt::CaseInsensitive)) - return true; - if (view->Text.contains(word, Qt::CaseInsensitive)) - return true; - } - for (auto v : view->_Children) { - if (v->Description.contains(word, Qt::CaseInsensitive)) - return true; - if (v->Text.contains(word, Qt::CaseInsensitive)) - return true; - } - } - - return false; -} diff --git a/src/ui/linux/TogglDesktop/autocompletecombobox.h b/src/ui/linux/TogglDesktop/autocompletecombobox.h deleted file mode 100644 index 526b88650a..0000000000 --- a/src/ui/linux/TogglDesktop/autocompletecombobox.h +++ /dev/null @@ -1,75 +0,0 @@ -#ifndef AUTOCOMPLETECOMBOBOX_H -#define AUTOCOMPLETECOMBOBOX_H - -#include -#include -#include -#include - -#include "autocompleteview.h" - -class AutocompleteCompleter; -class AutocompleteProxyModel; -class AutocompleteListView; - -class AutocompleteComboBox : public QComboBox { - Q_OBJECT -public: - AutocompleteComboBox(QWidget *parent = nullptr); - - void setModel(QAbstractItemModel *model); - - void showPopup() override; - - bool eventFilter(QObject *o, QEvent *e) override; - - AutocompleteView *currentView(); - -protected: - void keyPressEvent(QKeyEvent *event) override; - -private slots: - void onDropdownVisibleChanged(); - void onDropdownSelected(AutocompleteView *item); - - void cancelSelection(); - -signals: - void returnPressed(); - void timeEntrySelected(const QString &name); - void projectSelected(const QString &projectName, uint64_t projectId, const QString &color, const QString &taskName, uint64_t taskId); - void billableChanged(bool billable); - void tagsChanged(const QString &tags); - -private: - AutocompleteCompleter *completer; - AutocompleteProxyModel *proxyModel; - AutocompleteListView *listView; - - QString oldLabel {}; -}; - -class AutocompleteCompleter : public QCompleter { - Q_OBJECT - friend class AutocompleteComboBox; -public: - AutocompleteCompleter(QWidget *parent = nullptr); - - bool eventFilter(QObject *o, QEvent *e) override; -}; - -class AutocompleteProxyModel : public QSortFilterProxyModel { - Q_OBJECT - friend class AutocompleteComboBox; -public: - AutocompleteProxyModel(QObject *parent = nullptr); - - Q_INVOKABLE int count(); - - Q_INVOKABLE void setFilter(const QString &filter); - Q_INVOKABLE AutocompleteView *get(int idx); - - bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; -}; - -#endif // AUTOCOMPLETECOMBOBOX_H diff --git a/src/ui/linux/TogglDesktop/autocompletelistmodel.cpp b/src/ui/linux/TogglDesktop/autocompletelistmodel.cpp index f1856d72cf..d5c09de2ea 100644 --- a/src/ui/linux/TogglDesktop/autocompletelistmodel.cpp +++ b/src/ui/linux/TogglDesktop/autocompletelistmodel.cpp @@ -1,5 +1,7 @@ #include "autocompletelistmodel.h" + #include +#include AutocompleteListModel::AutocompleteListModel(QObject *parent, QVector list, uint64_t displayItem) : QAbstractListModel(parent), @@ -69,3 +71,61 @@ QVariant AutocompleteListModel::data(const QModelIndex &index, int role) const { return QVariant::fromValue(list.at(index.row())); return QVariant(); } + + +AutocompleteProxyModel::AutocompleteProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) { + setFilterRole(Qt::UserRole); +} + +int AutocompleteProxyModel::count() { + return rowCount(QModelIndex()); +} + +void AutocompleteProxyModel::setFilter(const QString &filter) { + setFilterRegExp(filter); +} + +AutocompleteView *AutocompleteProxyModel::get(int idx) { + if (idx >= 0 && idx < rowCount()) { + auto d = data(index(idx, 0), Qt::UserRole); + if (!d.isValid()) + return nullptr; + auto ptr = qvariant_cast(d); + if (!ptr) + return nullptr; + QQmlEngine::setObjectOwnership(ptr, QQmlEngine::CppOwnership); + return ptr; + } + return nullptr; +} + +bool AutocompleteProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { + QString input = filterRegExp().pattern(); + QStringList words = input.split(" "); + + auto variant = sourceModel()->data(sourceModel()->index(source_row, 0), Qt::UserRole); + if (!variant.isValid()) + return false; + auto view = qvariant_cast(variant); + if (!view) + return false; + for (auto word : words) { + if (word.isEmpty() && words.count() > 1) + continue; + if (view->_Children.isEmpty()) { + if (view->Description.contains(word, Qt::CaseInsensitive)) + return true; + if (view->Text.contains(word, Qt::CaseInsensitive)) + return true; + } + for (auto v : view->_Children) { + if (v->Description.contains(word, Qt::CaseInsensitive)) + return true; + if (v->Text.contains(word, Qt::CaseInsensitive)) + return true; + } + } + + return false; +} diff --git a/src/ui/linux/TogglDesktop/autocompletelistmodel.h b/src/ui/linux/TogglDesktop/autocompletelistmodel.h index 57a4cd6198..7bf679f059 100644 --- a/src/ui/linux/TogglDesktop/autocompletelistmodel.h +++ b/src/ui/linux/TogglDesktop/autocompletelistmodel.h @@ -25,4 +25,19 @@ class AutocompleteListModel : public QAbstractListModel uint64_t displayItem; }; +class AutocompleteProxyModel : public QSortFilterProxyModel { + Q_OBJECT + friend class AutocompleteComboBox; +public: + AutocompleteProxyModel(QObject *parent = nullptr); + + Q_INVOKABLE int count(); + + Q_INVOKABLE void setFilter(const QString &filter); + Q_INVOKABLE AutocompleteView *get(int idx); + + bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; +}; + + #endif // AUTOCOMPLETELISTMODEL_H diff --git a/src/ui/linux/TogglDesktop/autocompletelistview.cpp b/src/ui/linux/TogglDesktop/autocompletelistview.cpp deleted file mode 100644 index e08e431120..0000000000 --- a/src/ui/linux/TogglDesktop/autocompletelistview.cpp +++ /dev/null @@ -1,367 +0,0 @@ -#include "autocompletelistview.h" - -#include -#include -#include - -AutocompleteListView::AutocompleteListView(QWidget *parent) : - QListView(parent) -{ - setFixedWidth(320); - setViewMode(QListView::ListMode); - setUniformItemSizes(true); - setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - setItemDelegateForColumn(0, new AutoCompleteItemDelegate(this)); - - connect(this, &QListView::activated, [=](const QModelIndex &index){ - emit selected(qvariant_cast(index.data(Qt::UserRole))); - setVisible(false); - }); - connect(this, &QListView::clicked, [=](const QModelIndex &index){ - emit selected(qvariant_cast(index.data(Qt::UserRole))); - }); -} - -void AutocompleteListView::paintEvent(QPaintEvent *e) { - QListView::paintEvent(e); -} - -QModelIndex AutocompleteListView::moveCursor(QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers) { - Q_UNUSED(modifiers); - switch(cursorAction) { - case MoveDown: - case MoveRight: - case MoveNext: - for (int i = currentIndex().row() + 1; i < model()->rowCount(); i++) { - auto index = model()->index(i, 0); - if (index.isValid() && index.flags() & Qt::ItemIsEnabled) { - return model()->index(i, 0); - } - } - return QModelIndex(); - case MovePageDown: { - QModelIndex lastValid; - int remaining = 19; - for (int i = currentIndex().row() + 1; i < model()->rowCount() && remaining > 0; i++, remaining--) { - auto index = model()->index(i, 0); - if (index.isValid() && index.flags() & Qt::ItemIsEnabled) { - lastValid = model()->index(i, 0); - } - } - return lastValid; - } - case MoveEnd: { - for (int i = model()->rowCount() - 1; i >= 0; i--) { - auto index = model()->index(i, 0); - if (index.isValid() && index.flags() & Qt::ItemIsEnabled) { - return index; - } - } - return QModelIndex(); - } - case MoveUp: - case MoveLeft: - case MovePrevious: { - for (int i = currentIndex().row() - 1; i >= 0; i--) { - auto index = model()->index(i, 0); - if (index.isValid() && index.flags() & Qt::ItemIsEnabled) { - return model()->index(i, 0); - } - } - return QModelIndex(); - } - case MovePageUp: { - QModelIndex lastValid; - int remaining = 19; - for (int i = currentIndex().row() - 1; i >= 0 && remaining > 0; i--, remaining--) { - auto index = model()->index(i, 0); - if (index.isValid() && index.flags() & Qt::ItemIsEnabled) { - lastValid = model()->index(i, 0); - } - } - return lastValid; - } - case MoveHome: { - for (int i = 0; i < model()->rowCount(); i++) { - auto index = model()->index(i, 0); - if (index.isValid() && index.flags() & Qt::ItemIsEnabled) { - return index; - } - } - return QModelIndex(); - } - } - return QModelIndex(); -} - -void AutocompleteListView::showEvent(QShowEvent *event) { - emit visibleChanged(); - QListView::showEvent(event); -} - -void AutocompleteListView::hideEvent(QHideEvent *event) { - emit visibleChanged(); - QListView::hideEvent(event); -} - -void AutocompleteListView::keyPressEvent(QKeyEvent *e) -{ - bool modifiers = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier); - if (modifiers) { - //emit keyPress(e); - return; - } - - QListView::keyPressEvent(e); - //emit keyPress(e); -} - -AutoCompleteItemDelegate::AutoCompleteItemDelegate(QObject *parent) - : QItemDelegate(parent) -{ - -} - -void AutoCompleteItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - painter->save(); - - auto view = getCurrentView(index); - /* - * Because QTextDocument doesn't support very well, we need to paint time entries ourselves by hand - */ - switch (view->Type) { - case 0: - layoutTimeEntry(painter, option, view); - break; - case 1: - layoutTask(painter, option, view); - break; - case 2: - layoutProject(painter, option, view); - break; - default: { - QTextDocument doc; - doc.setTextWidth(option.rect.width()); - doc.setHtml(format(view)); - - option.widget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter); - - painter->translate(option.rect.left(), option.rect.top()); - QRect clip(0, 0, option.rect.width(), option.rect.height()); - doc.drawContents(painter, clip); - }} - - painter->restore(); -} - -void AutoCompleteItemDelegate::layoutProject(QPainter *painter, const QStyleOptionViewItem &option, AutocompleteView *view) const { - // set up the basic stuff, fonts, colors and font metrics to measure text sizes - int lineWidth = option.rect.width(); - QFont font; - font.setPixelSize(12); - QFontMetrics metrics(font); - QPen colorPen; - if (!view->ProjectColor.isEmpty()) - colorPen.setColor(QColor(view->ProjectColor)); - - // center the string vertically - int topMargin = (24 - metrics.height()) / 2; - // some margins to please the eye - int leftMargin = 18; - int rightMargin = 12; - - // then lay out the project - QTextLayout projectLayout; - int projectWidth = lineWidth - leftMargin - rightMargin - 1; - QString elidedProject = metrics.elidedText(view->ProjectLabel, option.textElideMode, projectWidth); - projectLayout.setText(" • " + elidedProject); - projectLayout.setFont(font); - projectWidth = metrics.width(" • " + elidedProject) + 1; - - projectLayout.beginLayout(); - auto projectLine = projectLayout.createLine(); - projectLine.setLineWidth(projectWidth + 1); - projectLine.setPosition({qreal(leftMargin), qreal(topMargin)}); - projectLayout.endLayout(); - - // and this is the actual painting part - // this asks Qt to paint the item background (for the highlighted items, especially) - option.widget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter); - // set the pen to match the project color and render project name - painter->setPen(colorPen); - projectLayout.draw(painter, QPointF(option.rect.left(), option.rect.top())); -} - -void AutoCompleteItemDelegate::layoutTask(QPainter *painter, const QStyleOptionViewItem &option, AutocompleteView *view) const { - // set up the basic stuff, fonts, colors and font metrics to measure text sizes - int lineWidth = option.rect.width(); - QFont font; - font.setPixelSize(12); - QFontMetrics metrics(font); - QPen grayPen; - grayPen.setColor(Qt::darkGray); - - // center the string vertically - int topMargin = (24 - metrics.height()) / 2; - // some margins to please the eye - int leftMargin = 30; - int rightMargin = 12; - - // do line trimming - QTextLayout taskLayout; - int taskWidth = lineWidth - leftMargin - rightMargin - 1; - QString elidedTask = metrics.elidedText(view->TaskLabel, option.textElideMode, taskWidth); - taskLayout.setText(elidedTask); - taskLayout.setFont(font); - taskWidth = metrics.width(elidedTask) + 1; - - // here we do the actual layout of the line - taskLayout.beginLayout(); - auto taskLine = taskLayout.createLine(); - taskLine.setLineWidth(taskWidth + 1); - taskLine.setPosition({qreal(leftMargin), qreal(topMargin)}); - taskLayout.endLayout(); - - // and this is the actual painting part - // this asks Qt to paint the item background (for the highlighted items, especially) - option.widget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter); - // set the pen to gray - painter->setPen(grayPen); - // render the task label - taskLayout.draw(painter, QPointF(option.rect.left(), option.rect.top())); -} - -void AutoCompleteItemDelegate::layoutTimeEntry(QPainter *painter, const QStyleOptionViewItem &option, AutocompleteView *view) const { - // set up the basic stuff, fonts, colors and font metrics to measure text sizes - int lineWidth = option.rect.width(); - QFont font; - font.setPixelSize(12); - QFontMetrics metrics(font); - QPen defaultPen(painter->pen()); - QPen colorPen(defaultPen); - if (!view->ProjectColor.isEmpty()) - colorPen.setColor(QColor(view->ProjectColor)); - QPen grayPen(defaultPen); - grayPen.setColor(Qt::darkGray); - - // center the string vertically - int topMargin = (24 - metrics.height()) / 2; - // some margins to please the eye - int leftMargin = 18; - int rightMargin = 6; - - // first lay out the client (rightmost string) - QTextLayout clientLayout; - int clientWidth = 0; - if (!view->ClientLabel.isEmpty()) { - QString elidedClient = metrics.elidedText(view->ClientLabel, option.textElideMode, lineWidth / 3); - clientLayout.setText(elidedClient); - clientLayout.setFont(font); - clientWidth = metrics.width(elidedClient) + 1; - } - - // then lay out the project - QTextLayout projectLayout; - int projectWidth = 0; - if (!view->ProjectLabel.isEmpty()) { - QString elidedProject = metrics.elidedText(view->ProjectLabel, option.textElideMode, lineWidth / 2); - projectLayout.setText(" • " + elidedProject); - projectLayout.setFont(font); - projectWidth = metrics.width(" • " + elidedProject) + 1; - } - - // and finally the description, since it can be cropped by the project and client - QTextLayout descriptionLayout; - // crop the potential length by the margins and client/project widths - int descriptionWidth = lineWidth - projectWidth - clientWidth - leftMargin - rightMargin + 1; - // metrics will do the ellipsis for us - QString elidedDescription = metrics.elidedText(view->Description, option.textElideMode, descriptionWidth); - // and now we measure the actual length of the ellided string so everything aligns nicely - descriptionWidth = metrics.width(elidedDescription); - descriptionLayout.setText(elidedDescription); - descriptionLayout.setFont(font); - - // here we do the actual layout of the line - descriptionLayout.beginLayout(); - auto descriptionLine = descriptionLayout.createLine(); - descriptionLine.setLineWidth(descriptionWidth + 1); - descriptionLine.setPosition({qreal(leftMargin), qreal(topMargin)}); - descriptionLayout.endLayout(); - - projectLayout.beginLayout(); - auto projectLine = projectLayout.createLine(); - projectLine.setLineWidth(projectWidth + 1); - projectLine.setPosition({qreal(leftMargin + descriptionWidth + 1), qreal(topMargin)}); - projectLayout.endLayout(); - - clientLayout.beginLayout(); - auto clientLine = clientLayout.createLine(); - clientLine.setLineWidth(clientWidth + 1); - clientLine.setPosition({qreal(leftMargin + descriptionWidth + projectWidth + 2), qreal(topMargin)}); - clientLayout.endLayout(); - - // and this is the actual painting part - // this asks Qt to paint the item background (for the highlighted items, especially) - option.widget->style()->drawControl(QStyle::CE_ItemViewItem, &option, painter); - // paint the description with the default pen first - descriptionLayout.draw(painter, QPointF(option.rect.left(), option.rect.top())); - // set the pen to match the project color and render project name - painter->setPen(colorPen); - projectLayout.draw(painter, QPointF(option.rect.left(), option.rect.top())); - // and finally, paint the client in gray - painter->setPen(grayPen); - clientLayout.draw(painter, QPointF(option.rect.left(), option.rect.top())); -} - -QSize AutoCompleteItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - return { 320, 24 }; -} - -QString AutoCompleteItemDelegate::format(const AutocompleteView *view) const { - QString label; - QString transparent = "background-color: transparent;"; - - // Format is: Description - TaskName · ProjectName - ClientName - - switch (view->Type) { - case 13: // Workspace row - return "
" + view->Description + "
"; - case 11: // Category row - return "
" + view->Description + "
"; - case 12: { // Client row / no project row - return "
" + view->Description + "
"; - } - case 2: { // Project items rows - label.append("
• " + view->ProjectLabel + "
"); - return label; - } - case 1: { // Task row - QString row = QString("
"); - row += view->TaskLabel; - row += "
"; - return row; - } - case 0: { // Item rows (projects/time entries) - // !!! UNUSED - QString table("
"); - if (!view->Description.isEmpty()) - table.append(view->Description + " "); - if (view->TaskID) - table.append(view->TaskLabel + " "); - //table.append("
"); - if (view->ProjectID) - table.append(" • " + view->ProjectLabel + " "); - if (view->ClientID) - table.append(" " + view->ClientLabel + ""); - table.append("
"); - return table; - } - default: - //ui->label->setStyleSheet(transparent + "padding-top:7px;padding-left:15px;font-size:9pt;"); - return "" + view->ProjectLabel + view->TaskLabel + view->ClientLabel + view->Description + ""; - } -} - -AutocompleteView *AutoCompleteItemDelegate::getCurrentView(const QModelIndex &index) const { - return qvariant_cast(index.data(Qt::UserRole)); -} diff --git a/src/ui/linux/TogglDesktop/autocompletelistview.h b/src/ui/linux/TogglDesktop/autocompletelistview.h deleted file mode 100644 index 91e98c6244..0000000000 --- a/src/ui/linux/TogglDesktop/autocompletelistview.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef AUTOCOMPLETELISTVIEW_H -#define AUTOCOMPLETELISTVIEW_H - -#include -#include -#include -#include -#include -#include - -#include "./autocompleteview.h" - -class AutocompleteListView : public QListView -{ - Q_OBJECT - friend class AutocompleteComboBox; -public: - explicit AutocompleteListView(QWidget *parent = 0); - - void paintEvent(QPaintEvent *e) override; - - QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override; - - void showEvent(QShowEvent *event) override; - void hideEvent(QHideEvent *event) override; - -signals: - void visibleChanged(); - void selected(AutocompleteView *item); - -protected: - void keyPressEvent(QKeyEvent *e); - -}; - -class AutoCompleteItemDelegate : public QItemDelegate -{ - Q_OBJECT -public: - AutoCompleteItemDelegate(QObject *parent = nullptr); - void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - void layoutProject(QPainter *painter, const QStyleOptionViewItem &option, AutocompleteView *view) const; - void layoutTask(QPainter *painter, const QStyleOptionViewItem &option, AutocompleteView *view) const; - void layoutTimeEntry(QPainter *painter, const QStyleOptionViewItem &option, AutocompleteView *view) const; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; - -private: - QString format(const AutocompleteView *view) const; - AutocompleteView *getCurrentView(const QModelIndex &index) const; -}; - -#endif // AUTOCOMPLETELISTVIEW_H diff --git a/src/ui/linux/TogglDesktop/clickablelabel.cpp b/src/ui/linux/TogglDesktop/clickablelabel.cpp deleted file mode 100644 index f65363e47c..0000000000 --- a/src/ui/linux/TogglDesktop/clickablelabel.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#include "./clickablelabel.h" -#include "./toggl.h" -#include "./timeentrycellwidget.h" - -ClickableLabel::ClickableLabel(QWidget * parent) : QLabel(parent) { -} - -ClickableLabel::~ClickableLabel() { -} - -void ClickableLabel::mousePressEvent(QMouseEvent * event) { - Q_UNUSED(event); - QWidget* parentObject = this->parentWidget(); - - while (parentObject->objectName().compare("TimeEntryCellWidget") != 0) { - parentObject = parentObject->parentWidget(); - } - - TimeEntryCellWidget *Cell = - qobject_cast(parentObject); - Cell->labelClicked(this->objectName()); -} diff --git a/src/ui/linux/TogglDesktop/clickablelabel.h b/src/ui/linux/TogglDesktop/clickablelabel.h deleted file mode 100644 index 0a7659a432..0000000000 --- a/src/ui/linux/TogglDesktop/clickablelabel.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_CLICKABLELABEL_H_ -#define SRC_UI_LINUX_TOGGLDESKTOP_CLICKABLELABEL_H_ - -#include -#include - -class ClickableLabel : public QLabel { - public: - explicit ClickableLabel(QWidget * parent = 0); - ~ClickableLabel(); - - signals: - void clicked(); - - protected: - void mousePressEvent(QMouseEvent * event); -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_CLICKABLELABEL_H_ diff --git a/src/ui/linux/TogglDesktop/colorpicker.cpp b/src/ui/linux/TogglDesktop/colorpicker.cpp deleted file mode 100644 index 2a89e51c60..0000000000 --- a/src/ui/linux/TogglDesktop/colorpicker.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2016 Toggl Desktop developers. - -#include -#include "colorpicker.h" -#include "ui_colorpicker.h" -#include "timeentryeditorwidget.h" - -ColorPicker::ColorPicker(QWidget *parent) : - QDialog(parent), - ui(new Ui::ColorPicker) -{ - ui->setupUi(this); - setModal(true); - setWindowTitle("Color Select"); - setWindowFlags( Qt::Popup ); - - // Add shadow - setAttribute(Qt::WA_TranslucentBackground); //enable MainWindow to be transparent - - QGraphicsDropShadowEffect* effect = new QGraphicsDropShadowEffect(); - effect->setOffset(0); - effect->setBlurRadius(5); - ui->verticalWidget->setGraphicsEffect(effect); -} - -ColorPicker::~ColorPicker() -{ - delete ui; -} - -void ColorPicker::setColors(QVector list) { - int row = 0; - int cell = 0; - - for (int i = 0; i < list.size(); i++) { - QPushButton* colorButton = new QPushButton(this); - colorButton->setText(QString("■")); - colorButton->setMinimumSize(QSize(20, 20)); - colorButton->setMaximumSize(QSize(20, 20)); - colorButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - QString style = "color:" + QString::fromUtf8(list.at(i)) + ";"; - colorButton->setStyleSheet(style); - connect(colorButton,SIGNAL(clicked()),this,SLOT(color_clicked())); - - ui->gridLayout->addWidget(colorButton,row,cell); - - cell++; - - // Keep 5 items in 1 row - if ((i+1) % 5 == 0) { - row++; - cell = 0; - } - } -} - -void ColorPicker::color_clicked() { - QString color = (qobject_cast(sender()))->styleSheet(); - TimeEntryEditorWidget *p = qobject_cast(parent()); - p->setSelectedColor(color); - close(); -} - -void ColorPicker::on_closeButton_clicked() -{ - close(); -} diff --git a/src/ui/linux/TogglDesktop/colorpicker.h b/src/ui/linux/TogglDesktop/colorpicker.h deleted file mode 100644 index 07be4a8bc4..0000000000 --- a/src/ui/linux/TogglDesktop/colorpicker.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2016 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_COLORPICKER_H -#define SRC_UI_LINUX_TOGGLDESKTOP_COLORPICKER_H - -#include -#include - -namespace Ui { -class ColorPicker; -} - -class ColorPicker : public QDialog -{ - Q_OBJECT - -public: - explicit ColorPicker(QWidget *parent = 0); - ~ColorPicker(); - -private: - Ui::ColorPicker *ui; - -public: - void setColors(QVector list); - -private slots: - void color_clicked(); - void on_closeButton_clicked(); -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_COLORPICKER_H diff --git a/src/ui/linux/TogglDesktop/colorpicker.ui b/src/ui/linux/TogglDesktop/colorpicker.ui deleted file mode 100644 index b31a5392f0..0000000000 --- a/src/ui/linux/TogglDesktop/colorpicker.ui +++ /dev/null @@ -1,98 +0,0 @@ - - - ColorPicker - - - - 0 - 0 - 170 - 175 - - - - - 170 - 175 - - - - Form - - - - - 10 - 10 - 150 - 155 - - - - - 150 - 155 - - - - - 150 - 155 - - - - true - - - - - 0 - 0 - 150 - 110 - - - - - 150 - 110 - - - - - 150 - 110 - - - - font-size:72px; - - - - - - - 90 - 120 - 50 - 25 - - - - close - - - false - - - false - - - false - - - - - - - diff --git a/src/ui/linux/TogglDesktop/errorviewcontroller.cpp b/src/ui/linux/TogglDesktop/errorviewcontroller.cpp deleted file mode 100644 index 1eed9628f1..0000000000 --- a/src/ui/linux/TogglDesktop/errorviewcontroller.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#include "./errorviewcontroller.h" -#include "./ui_errorviewcontroller.h" -#include "./toggl.h" - -ErrorViewController::ErrorViewController(QWidget *parent) - : QWidget(parent) -, ui(new Ui::ErrorViewController) -, loginError(false) -, uid(0) { - ui->setupUi(this); - setVisible(false); - - connect(TogglApi::instance, SIGNAL(displayError(QString,bool)), // NOLINT - this, SLOT(displayError(QString,bool))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayOnlineState(int64_t)), // NOLINT - this, SLOT(displayOnlineState(int64_t))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayLogin(bool,uint64_t)), // NOLINT - this, SLOT(displayLogin(bool,uint64_t))); // NOLINT -} - -ErrorViewController::~ErrorViewController() { - delete ui; -} - -void ErrorViewController::on_pushButton_clicked() { - setVisible(false); -} - -void ErrorViewController::displayError( - const QString errmsg, - const bool user_error) { - loginError = !uid; - ui->errorMessage->setText(errmsg); - ui->errorMessage->setToolTip( - QString("

" + - errmsg + "

")); - setVisible(true); - if (!user_error) { - TogglApi::notifyBugsnag("error in shared lib", errmsg, - "ErrorViewController"); - } -} - -void ErrorViewController::displayOnlineState( - int64_t state) { - Q_UNUSED(state); - // FIXME: need separate online state label -} - -void ErrorViewController::displayLogin( - const bool open, - const uint64_t user_id) { - Q_UNUSED(open); - uid = user_id; - if (user_id && isVisible() && loginError) { - loginError = false; - setVisible(false); - } -} diff --git a/src/ui/linux/TogglDesktop/errorviewcontroller.h b/src/ui/linux/TogglDesktop/errorviewcontroller.h deleted file mode 100644 index 420a6f922b..0000000000 --- a/src/ui/linux/TogglDesktop/errorviewcontroller.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_ERRORVIEWCONTROLLER_H_ -#define SRC_UI_LINUX_TOGGLDESKTOP_ERRORVIEWCONTROLLER_H_ - -#include - -#include - -namespace Ui { -class ErrorViewController; -} - -class ErrorViewController : public QWidget { - Q_OBJECT - - public: - explicit ErrorViewController(QWidget *parent = 0); - ~ErrorViewController(); - - private slots: // NOLINT - void on_pushButton_clicked(); - - void displayError( - const QString errmsg, - const bool user_error); - - void displayOnlineState( - int64_t state); - - void displayLogin( - const bool open, - const uint64_t user_id); - - private: - Ui::ErrorViewController *ui; - - bool networkError; - bool loginError; - uint64_t uid; -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_ERRORVIEWCONTROLLER_H_ diff --git a/src/ui/linux/TogglDesktop/errorviewcontroller.ui b/src/ui/linux/TogglDesktop/errorviewcontroller.ui deleted file mode 100644 index 13e8a26127..0000000000 --- a/src/ui/linux/TogglDesktop/errorviewcontroller.ui +++ /dev/null @@ -1,143 +0,0 @@ - - - ErrorViewController - - - - 0 - 0 - 258 - 40 - - - - - 0 - 0 - - - - - 0 - 40 - - - - - 16777215 - 40 - - - - Form - - - false - - - background-color: rgb(236, 223, 95);color:rgb(85, 85, 85);font-size:13px; - - - - 0 - - - QLayout::SetMaximumSize - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 40 - - - - - 16777215 - 40 - - - - Qt::LeftToRight - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 6 - - - 0 - - - 6 - - - 0 - - - - - - 0 - 0 - - - - An error has occured! - - - false - - - - - - - - 20 - 20 - - - - - 20 - 20 - - - - Qt::NoFocus - - - X - - - - - - - - - - - diff --git a/src/ui/linux/TogglDesktop/feedbackdialog.cpp b/src/ui/linux/TogglDesktop/feedbackdialog.cpp deleted file mode 100644 index a9115857c6..0000000000 --- a/src/ui/linux/TogglDesktop/feedbackdialog.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#include "./feedbackdialog.h" -#include "./ui_feedbackdialog.h" - -#include // NOLINT -#include // NOLINT -#include // NOLINT - -#include "./toggl.h" - -FeedbackDialog::FeedbackDialog(QWidget *parent) : QDialog(parent), -ui(new Ui::FeedbackDialog) { - ui->setupUi(this); - - ui->topic->addItem("Bug report"); - ui->topic->addItem("Feature request"); - ui->topic->addItem("Other"); - ui->topic->setCurrentIndex(0); -} - -FeedbackDialog::~FeedbackDialog() { - delete ui; -} - -void FeedbackDialog::on_uploadImageButton_clicked() { - path = QFileDialog::getOpenFileName(this, "Select an image to upload"); - QFileInfo info(path); - ui->selectedImageFilename->setText(info.fileName()); -} - -void FeedbackDialog::on_sendButton_clicked() { - QString content = ui->content->toPlainText(); - if (content.isEmpty()) { - QMessageBox(QMessageBox::Information, - "Feedback not sent!", - "Please type in your feedback before sending.", - QMessageBox::Ok).exec(); - ui->content->setFocus(); - return; - } - if (!TogglApi::instance->sendFeedback(ui->topic->currentText(), - ui->content->toPlainText(), - path)) { - QMessageBox(QMessageBox::Information, - "Feedback not sent!", - "Please check that file you are sending is not larger than 5MB.", - QMessageBox::Ok).exec(); - return; - } - ui->topic->setCurrentIndex(0); - ui->content->clear(); - ui->selectedImageFilename->setText(""); - path = ""; - hide(); - - QMessageBox(QMessageBox::Information, - "Thank you!", - "Your feedback was sent successfully.", - QMessageBox::Ok).exec(); -} diff --git a/src/ui/linux/TogglDesktop/feedbackdialog.h b/src/ui/linux/TogglDesktop/feedbackdialog.h deleted file mode 100644 index fef9565cb2..0000000000 --- a/src/ui/linux/TogglDesktop/feedbackdialog.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_FEEDBACKDIALOG_H_ -#define SRC_UI_LINUX_TOGGLDESKTOP_FEEDBACKDIALOG_H_ - -#include - -namespace Ui { -class FeedbackDialog; -} - -class FeedbackDialog : public QDialog { - Q_OBJECT - - public: - explicit FeedbackDialog(QWidget *parent = 0); - ~FeedbackDialog(); - - private slots: // NOLINT - void on_uploadImageButton_clicked(); - - void on_sendButton_clicked(); - - private: - Ui::FeedbackDialog *ui; - - QString path; -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_FEEDBACKDIALOG_H_ diff --git a/src/ui/linux/TogglDesktop/feedbackdialog.ui b/src/ui/linux/TogglDesktop/feedbackdialog.ui deleted file mode 100644 index 3bd6e1d3e0..0000000000 --- a/src/ui/linux/TogglDesktop/feedbackdialog.ui +++ /dev/null @@ -1,135 +0,0 @@ - - - FeedbackDialog - - - - 0 - 0 - 400 - 300 - - - - Send Feedback - - - font-size:13px; - - - - - - Send us your feedback and questions - - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 0 - - - 6 - - - 0 - - - 6 - - - - - (maximum image size 5MB) - - - - - - - Qt::Horizontal - - - - 152 - 20 - - - - - - - - - 100 - 0 - - - - - 100 - 16777215 - - - - Upload image - - - false - - - - - - - - - - - 0 - - - 0 - - - - - - 100 - 0 - - - - Send - - - true - - - - - - - - - - - diff --git a/src/ui/linux/TogglDesktop/loginwidget.cpp b/src/ui/linux/TogglDesktop/loginwidget.cpp deleted file mode 100644 index cc9bc1b897..0000000000 --- a/src/ui/linux/TogglDesktop/loginwidget.cpp +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#include -#include "./loginwidget.h" -#include "./ui_loginwidget.h" - -#include "./toggl.h" - -LoginWidget::LoginWidget(QStackedWidget *parent) : QWidget(parent), -ui(new Ui::LoginWidget), -oauth2(new OAuth2(this)) { - ui->setupUi(this); - - connect(TogglApi::instance, SIGNAL(displayLogin(bool,uint64_t)), // NOLINT - this, SLOT(displayLogin(bool,uint64_t))); // NOLINT - - connect(TogglApi::instance, SIGNAL(setCountries(QVector)), // NOLINT - this, SLOT(setCountries(QVector))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayError(QString,bool)), // NOLINT - this, SLOT(displayError(QString,bool))); // NOLINT - - oauth2->setScope("profile email"); - oauth2->setAppName("Toggl Desktop"); - oauth2->setClientID("426090949585.apps.googleusercontent.com"); - oauth2->setRedirectURI("http://www.google.com/robots.txt"); - - connect(oauth2, SIGNAL(loginDone()), this, SLOT(loginDone())); - - signupVisible = true; - countriesLoaded = false; - selectedCountryId = UINT64_MAX; - - on_viewchangelabel_linkActivated(""); -} - -LoginWidget::~LoginWidget() { - delete ui; -} - -void LoginWidget::displayError( - const QString errmsg, - const bool user_error) { - Q_UNUSED(errmsg); - Q_UNUSED(user_error); - enableAllControls(true); -} - -void LoginWidget::enableAllControls(const bool enable) { - ui->email->setEnabled(enable); - ui->password->setEnabled(enable); - ui->login->setEnabled(enable); - ui->signup->setEnabled(enable); - ui->googleLogin->setEnabled(enable); - ui->forgotPassword->setEnabled(enable); - ui->viewchangelabel->setEnabled(enable); -} - -void LoginWidget::display() { - signupVisible = true; - on_viewchangelabel_linkActivated(""); - qobject_cast(parent())->setCurrentWidget(this); -} - -void LoginWidget::keyPressEvent(QKeyEvent* event) { - if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { - if (signupVisible) { - on_signup_clicked(); - } else { - on_login_clicked(); - } - } -} - -void LoginWidget::mousePressEvent(QMouseEvent* event) { - Q_UNUSED(event); - setFocus(); -} - -void LoginWidget::displayLogin( - const bool open, - const uint64_t user_id) { - - if (open) { - display(); - ui->email->setFocus(); - } - if (user_id) { - ui->password->clear(); - } - - // Enable all - enableAllControls(true); -} - -void LoginWidget::on_login_clicked() { - if (!validateFields(false)) { - return; - } - enableAllControls(false); - TogglApi::instance->login(ui->email->text(), ui->password->text()); -} - -void LoginWidget::on_googleLogin_linkActivated(const QString &link) { - Q_UNUSED(link) - oauth2->startLogin(true); -} - -void LoginWidget::on_googleSignup_linkActivated(const QString &link) { - Q_UNUSED(link) - if (validateFields(true, true)) { - oauth2->startLogin(true); - } -} - -void LoginWidget::loginDone() { - if (signupVisible) { - TogglApi::instance->googleSignup(oauth2->accessToken(), selectedCountryId); - } - else { - TogglApi::instance->googleLogin(oauth2->accessToken()); - } -} - -bool LoginWidget::validateFields(bool signup, bool google) { - if (google) - signup = true; - if (!google) { - if (ui->email->text().isEmpty()) { - ui->email->setFocus(); - TogglApi::instance->displayError(QString("Please enter valid email address"), true); - return false; - } - if (ui->password->text().isEmpty()) { - ui->password->setFocus(); - TogglApi::instance->displayError(QString("A password is required"), true); - return false; - } - } - if (signup) { - if (selectedCountryId == UINT64_MAX) { - ui->countryComboBox->setFocus(); - TogglApi::instance->displayError(QString("Please select Country before signing up"), true); - return false; - } - if (ui->tosCheckBox->checkState() == Qt::Unchecked) { - ui->tosCheckBox->setFocus(); - TogglApi::instance->displayError(QString("You must agree to the terms of service and privacy policy to use Toggl"), true); - return false; - } - } - return true; -} - -void LoginWidget::on_signup_clicked() { - if (!validateFields(true)) { - return; - } - TogglApi::instance->signup(ui->email->text(), ui->password->text(), selectedCountryId); -} - -void LoginWidget::setCountries( - QVector list) { - ui->countryComboBox->clear(); - ui->countryComboBox->addItem(" -- Select country -- "); - foreach(CountryView *view, list) { - ui->countryComboBox->addItem(view->Text, QVariant::fromValue(view)); - } -} - -void LoginWidget::on_viewchangelabel_linkActivated(const QString &link) -{ - Q_UNUSED(link) - if (signupVisible) { - ui->signupWidget->hide(); - ui->loginWidget->show(); - ui->viewchangelabel->setText("Sign up for free"); - signupVisible = false; - } else { - ui->loginWidget->hide(); - ui->signupWidget->show(); - ui->viewchangelabel->setText("Back to login"); - signupVisible = true; - if (!countriesLoaded) { - TogglApi::instance->getCountries(); - countriesLoaded = true; - } - } -} - -void LoginWidget::on_countryComboBox_currentIndexChanged(int index) -{ - if (index == 0) { - selectedCountryId = UINT64_MAX; - return; - } - QVariant data = ui->countryComboBox->currentData(); - if (data.canConvert()) { - CountryView *view = data.value(); - selectedCountryId = view->ID; - } -} diff --git a/src/ui/linux/TogglDesktop/loginwidget.h b/src/ui/linux/TogglDesktop/loginwidget.h deleted file mode 100644 index f7c6a98f17..0000000000 --- a/src/ui/linux/TogglDesktop/loginwidget.h +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_LOGINWIDGET_H_ -#define SRC_UI_LINUX_TOGGLDESKTOP_LOGINWIDGET_H_ - -#include -#include -#include - -#include - -#include "./timeentryview.h" -#include "./countryview.h" -#include "./oauth2.h" - -namespace Ui { -class LoginWidget; -} - -class LoginWidget : public QWidget { - Q_OBJECT - - public: - explicit LoginWidget(QStackedWidget *parent = nullptr); - ~LoginWidget(); - - void display(); - - protected: - virtual void keyPressEvent(QKeyEvent *event); - void mousePressEvent(QMouseEvent *event); - - private slots: // NOLINT - void on_login_clicked(); - - void displayLogin( - const bool open, - const uint64_t user_id); - - void on_googleLogin_linkActivated(const QString &link); - void on_googleSignup_linkActivated(const QString &link); - - void loginDone(); - bool validateFields(bool signup, bool google = false); - - void on_signup_clicked(); - - void setCountries( - QVector list); - - void on_viewchangelabel_linkActivated(const QString &link); - - void on_countryComboBox_currentIndexChanged(int index); - - void displayError( - const QString errmsg, - const bool user_error); - - private: - Ui::LoginWidget *ui; - - OAuth2 *oauth2; - - bool signupVisible; - - bool countriesLoaded; - uint64_t selectedCountryId; - - void enableAllControls(const bool enable); -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_LOGINWIDGET_H_ diff --git a/src/ui/linux/TogglDesktop/loginwidget.ui b/src/ui/linux/TogglDesktop/loginwidget.ui deleted file mode 100644 index 1038286b4d..0000000000 --- a/src/ui/linux/TogglDesktop/loginwidget.ui +++ /dev/null @@ -1,499 +0,0 @@ - - - LoginWidget - - - - 0 - 0 - 427 - 556 - - - - - 0 - 0 - - - - Form - - - false - - - font-size:13px; - - - - 12 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - background-color: rgb(45, 45, 45); - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - padding:12px - - - - - - :/images/logo.png - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - - 320 - 35 - - - - false - - - background-color: rgb(255, 255, 255);color:rgb(85, 85, 85); - - - Your e-mail address - - - - - - - - 0 - 0 - - - - - 320 - 35 - - - - false - - - background-color: rgb(255, 255, 255);color:rgb(85, 85, 85); - - - - - - QLineEdit::Password - - - Password - - - - - - - - 320 - 0 - - - - - 1 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - -1 - - - - true - - - color:#fff;padding-bottom:24px; - - - <a href="https://www.toggl.com/forgot-password"><span style=" text-decoration: underline; color:#fff;">Forgot password?</span></a> - - - Qt::RichText - - - Qt::AlignHCenter|Qt::AlignTop - - - true - - - Qt::TextBrowserInteraction - - - - - - - - 0 - 0 - - - - - 320 - 35 - - - - false - - - background-color: rgb(255, 255, 255);color:rgb(85, 85, 85); - - - Log in - - - - - - - true - - - - -1 - - - - color:#fff;padding-top:6px - - - <html><head/><body><p><a href="#google_login"><span style=" text-decoration: underline; color:#fff;">Log in with Google Account</span></a></p></body></html> - - - Qt::RichText - - - Qt::AlignCenter - - - Qt::TextBrowserInteraction - - - - - - - color:#fff;padding-top:18px - - - or you can - - - Qt::AlignCenter - - - - - - - - - - - - - color:#fff;padding-top:18px - - - Select country - - - Qt::AlignCenter - - - - - - - - 320 - 35 - - - - color:#fff; - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - color:#fff; - - - - - - - - - - color:#fff; - - - <p style="color:#fff;">I agree to <a href="https://toggl.com/legal/terms/" style=" text-decoration: underline; color:#fff;">terms of service</a> and <a href="https://toggl.com/legal/privacy/" style=" text-decoration: underline; color:#fff;">privacy policy</a> - - - true - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - - - 0 - 0 - - - - - 320 - 35 - - - - false - - - background-color: rgb(255, 255, 255);color:rgb(85, 85, 85); - - - Sign up - - - false - - - false - - - - - - - - -1 - - - - true - - - color:#fff;padding-bottom:6px; - - - <html><head/><body><p><a href="#google_signup"><span style=" text-decoration: underline; color:#fff;">Sign up with Google Account</span></a></p></body></html> - - - Qt::RichText - - - Qt::AlignHCenter|Qt::AlignTop - - - false - - - Qt::TextBrowserInteraction - - - - - - - - - - - -1 - - - - - - - <html><head/><body><a href="#" style="text-decoration:none;color:#fff">Sign up for free</a></body></html> - - - Qt::RichText - - - Qt::AlignCenter - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - - diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index dea138800d..d90a0b6ad3 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -22,8 +23,9 @@ #include "./autocompleteview.h" #include "./bugsnag.h" #include "./genericview.h" -#include "./mainwindowcontroller.h" #include "./toggl.h" +#include "./timeentryview.h" +#include "./countryview.h" class TogglApplication : public SingleApplication { public: diff --git a/src/ui/linux/TogglDesktop/mainwindowcontroller.cpp b/src/ui/linux/TogglDesktop/mainwindowcontroller.cpp deleted file mode 100644 index 699ada86e0..0000000000 --- a/src/ui/linux/TogglDesktop/mainwindowcontroller.cpp +++ /dev/null @@ -1,584 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#include "./mainwindowcontroller.h" -#include "./ui_mainwindowcontroller.h" - -#include // NOLINT - -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT - -#include "./toggl.h" -#include "./timeentryeditorwidget.h" -#include "./timeentrylistwidget.h" -#include "./timerwidget.h" -#include "./errorviewcontroller.h" -#include "./timeentrycellwidget.h" - -MainWindowController::MainWindowController( - QWidget *parent, - QString logPathOverride, - QString dbPathOverride, - QString scriptPath) - : QMainWindow(parent), - ui(new Ui::MainWindowController), - togglApi(new TogglApi(nullptr, logPathOverride, dbPathOverride)), - tracking(false), - loggedIn(false), - preferencesDialog(new PreferencesDialog(this)), - aboutDialog(new AboutDialog(this)), - feedbackDialog(new FeedbackDialog(this)), - icon(":/icons/1024x1024/toggldesktop.png"), - iconDisabled(":/icons/1024x1024/toggldesktop_gray.png"), - trayIcon(nullptr), - pomodoro(false), - script(scriptPath), - powerManagement(new PowerManagement(this)), - networkManagement(new NetworkManagement(this)), - shortcutDelete(QKeySequence(Qt::CTRL + Qt::Key_Delete), this), - shortcutPause(QKeySequence(Qt::CTRL + Qt::Key_Space), this), - shortcutConfirm(QKeySequence(Qt::CTRL + Qt::Key_Return), this), - shortcutGroupOpen(QKeySequence(Qt::Key_Right), this), - shortcutGroupClose(QKeySequence(Qt::Key_Left), this), - ui_started(false) { - ui->setupUi(this); - - ui->menuBar->setVisible(true); - - readSettings(); - - connect(TogglApi::instance, SIGNAL(displayApp(bool)), // NOLINT - this, SLOT(displayApp(bool))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayStoppedTimerState()), // NOLINT - this, SLOT(displayStoppedTimerState())); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayRunningTimerState(TimeEntryView*)), // NOLINT - this, SLOT(displayRunningTimerState(TimeEntryView*))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayLogin(bool,uint64_t)), // NOLINT - this, SLOT(displayLogin(bool,uint64_t))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayPomodoro(QString,QString)), // NOLINT - this, SLOT(displayPomodoro(QString,QString))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayPomodoroBreak(QString,QString)), // NOLINT - this, SLOT(displayPomodoroBreak(QString,QString))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayUpdate(QString)), // NOLINT - this, SLOT(displayUpdate(QString))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayOnlineState(int64_t)), // NOLINT - this, SLOT(displayOnlineState(int64_t))); // NOLINT - - connect(TogglApi::instance, SIGNAL(updateShowHideShortcut()), // NOLINT - this, SLOT(updateShowHideShortcut())); // NOLINT - - connect(TogglApi::instance, SIGNAL(updateContinueStopShortcut()), // NOLINT - this, SLOT(updateContinueStopShortcut())); // NOLINT - - setWindowIcon(icon); - trayIcon = new SystemTray(this, icon); - - setShortcuts(); - connectMenuActions(); - enableMenuActions(); - - connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), - this, SLOT(toggleWindow(QSystemTrayIcon::ActivationReason))); - connect(networkManagement, &NetworkManagement::onlineStateChanged, - this, &MainWindowController::onOnlineStateChanged); - onOnlineStateChanged(); - restoreLastWindowsFrame(); -} - -MainWindowController::~MainWindowController() { - delete togglApi; - togglApi = nullptr; - - delete ui; -} - -void MainWindowController::toggleWindow(QSystemTrayIcon::ActivationReason r) { - if (r == QSystemTrayIcon::Trigger) { - if (!this->isVisible()) { - this->show(); - } else { - this->hide(); - } - } -} - -void MainWindowController::displayOnlineState( - int64_t state) { - - switch (state) { - case 0: // online - statusBar()->clearMessage(); - break; - case 1: // no network - statusBar()->showMessage("Status: Offline, no network"); - break; - case 2: // backend down - statusBar()->showMessage("Status: Offline, Toggl not responding"); - break; - default: - qDebug() << "Unknown online state " << state; - break; - } -} - -void MainWindowController::displayPomodoro( - const QString title, - const QString informative_text) { - - if (pomodoro) { - return; - } - pomodoro = true; - - QMessageBox msgBox; - msgBox.setWindowTitle("Pomodoro Timer"); - msgBox.setText(title); - msgBox.setInformativeText(informative_text); - QPushButton *continueButton = - msgBox.addButton(tr("Continue"), QMessageBox::YesRole); - QPushButton *closeButton = - msgBox.addButton(tr("Close"), QMessageBox::NoRole); - msgBox.setDefaultButton(closeButton); - msgBox.setEscapeButton(closeButton); - - msgBox.exec(); - - if (msgBox.clickedButton() == continueButton) { - TogglApi::instance->continueLatestTimeEntry(); - } - - pomodoro = false; -} - -void MainWindowController::displayPomodoroBreak( - const QString title, - const QString informative_text) { - - if (pomodoro) { - return; - } - pomodoro = true; - - QMessageBox msgBox; - msgBox.setWindowTitle("Pomodoro Break Timer"); - msgBox.setText(title); - msgBox.setInformativeText(informative_text); - QPushButton *continueButton = - msgBox.addButton(tr("Continue"), QMessageBox::YesRole); - QPushButton *closeButton = - msgBox.addButton(tr("Close"), QMessageBox::NoRole); - msgBox.setDefaultButton(closeButton); - msgBox.setEscapeButton(closeButton); - - msgBox.exec(); - - if (msgBox.clickedButton() == continueButton) { - TogglApi::instance->continueLatestTimeEntry(); - } - - pomodoro = false; -} - -void MainWindowController::displayLogin( - const bool open, - const uint64_t user_id) { - - loggedIn = !open && user_id; - enableMenuActions(); -} - -void MainWindowController::displayRunningTimerState( - TimeEntryView *te) { - Q_UNUSED(te); - tracking = true; - enableMenuActions(); -} - -void MainWindowController::displayStoppedTimerState() { - tracking = false; - enableMenuActions(); -} - -void MainWindowController::enableMenuActions() { - ui->actionNew->setEnabled(loggedIn); - ui->actionContinue->setEnabled(loggedIn && !tracking); - ui->actionStop->setEnabled(loggedIn && tracking); - ui->actionSync->setEnabled(loggedIn); - ui->actionLogout->setEnabled(loggedIn); - ui->actionClear_Cache->setEnabled(loggedIn); - ui->actionSend_Feedback->setEnabled(loggedIn); - ui->actionReports->setEnabled(loggedIn); - ui->actionEmail->setText(TogglApi::instance->userEmail()); - if (tracking) { - setWindowIcon(icon); - trayIcon->setIcon(icon); - } else { - setWindowIcon(iconDisabled); - trayIcon->setIcon(iconDisabled); - } -} - -void MainWindowController::showHideHotkeyPressed() { - if (this->isVisible()) { - if (this->isActiveWindow()) { - hide(); - } else { - activateWindow(); - } - } else { - onActionShow(); - activateWindow(); - } -} - -void MainWindowController::continueStopHotkeyPressed() { - if (tracking) { - onActionStop(); - } else { - onActionContinue(); - } -} - -void MainWindowController::updateShowHideShortcut() { - showHide->setShortcut( - QKeySequence(TogglApi::instance->getShowHideKey())); -} - -void MainWindowController::updateContinueStopShortcut() { - continueStop->setShortcut( - QKeySequence(TogglApi::instance->getContinueStopKey())); -} - -void MainWindowController::onOnlineStateChanged() { - if (networkManagement->isOnline()) { - TogglApi::instance->setOnline(); - } -} - -void MainWindowController::onShortcutDelete() { - if (ui->stackedWidget->currentWidget() == ui->timeEntryListWidget) { - if (ui->timeEntryListWidget->focusWidget() && - ui->timeEntryListWidget->focusWidget()->parentWidget() && - qobject_cast(ui->timeEntryListWidget->focusWidget()->parentWidget())) { - qobject_cast(ui->timeEntryListWidget->focusWidget()->parentWidget())->deleteTimeEntry(); - } - else { - ui->timeEntryListWidget->timer()->deleteTimeEntry(); - } - } - else if (ui->stackedWidget->currentWidget() == ui->timeEntryEditorWidget) { - ui->timeEntryEditorWidget->deleteTimeEntry(); - } -} - -void MainWindowController::onShortcutPause() { - auto w = focusWidget(); - while (w) { - auto timer = qobject_cast(w); - auto timeEntryList = qobject_cast(w); - auto timeEntryEdit = qobject_cast(w); - if (timer) { - continueStopHotkeyPressed(); - return; - } - else if (timeEntryList) { - bool thisItem = !timeEntryList->timer()->currentEntryGuid().isEmpty() && - timeEntryList->highlightedCell() && - timeEntryList->timer()->currentEntryGuid() == timeEntryList->highlightedCell()->entryGuid(); - QString selectedGuid = timeEntryList->highlightedCell() ? timeEntryList->highlightedCell()->entryGuid() : QString(); - if (tracking) { - onActionStop(); - } - if (!thisItem) { - TogglApi::instance->continueTimeEntry(selectedGuid); - } - return; - } - else if (timeEntryEdit) { - return; - } - w = w->parentWidget(); - } -} - -void MainWindowController::onShortcutConfirm() { - auto w = focusWidget(); - while (w) { - auto timer = qobject_cast(w); - auto timeEntryList = qobject_cast(w); - auto timeEntryEdit = qobject_cast(w); - if (timer) { - TogglApi::instance->editRunningTimeEntry(""); - return; - } - else if (timeEntryList) { - QString selectedGuid = timeEntryList->highlightedCell() ? timeEntryList->highlightedCell()->entryGuid() : QString(); - TogglApi::instance->editTimeEntry(selectedGuid, "description"); - return; - } - else if (timeEntryEdit) { - timeEntryEdit->clickDone(); - return; - } - w = w->parentWidget(); - } -} - -void MainWindowController::onShortcutGroupOpen() { - if (ui->stackedWidget->currentWidget() == ui->timeEntryListWidget) { - if (ui->timeEntryListWidget->focusWidget() && - ui->timeEntryListWidget->focusWidget()->parentWidget() && - qobject_cast(ui->timeEntryListWidget->focusWidget()->parentWidget())) { - qobject_cast(ui->timeEntryListWidget->focusWidget()->parentWidget())->toggleGroup(true); - } - } -} - -void MainWindowController::onShortcutGroupClose() { - if (ui->stackedWidget->currentWidget() == ui->timeEntryListWidget) { - if (ui->timeEntryListWidget->focusWidget() && - ui->timeEntryListWidget->focusWidget()->parentWidget() && - qobject_cast(ui->timeEntryListWidget->focusWidget()->parentWidget())) { - qobject_cast(ui->timeEntryListWidget->focusWidget()->parentWidget())->toggleGroup(false); - } - } -} - -void MainWindowController::setShortcuts() { - showHide = new QxtGlobalShortcut(this); - connect(showHide, SIGNAL(activated()), - this, SLOT(showHideHotkeyPressed())); - - updateShowHideShortcut(); - - continueStop = new QxtGlobalShortcut(this); - connect(continueStop, SIGNAL(activated()), - this, SLOT(continueStopHotkeyPressed())); - - updateContinueStopShortcut(); - - connect(&shortcutDelete, &QShortcut::activated, - this, &MainWindowController::onShortcutDelete); - connect(&shortcutPause, &QShortcut::activated, - this, &MainWindowController::onShortcutPause); - connect(&shortcutConfirm, &QShortcut::activated, - this, &MainWindowController::onShortcutConfirm); - connect(&shortcutGroupOpen, &QShortcut::activated, - this, &MainWindowController::onShortcutGroupOpen); - connect(&shortcutGroupClose, &QShortcut::activated, - this, &MainWindowController::onShortcutGroupClose); -} - -void MainWindowController::connectMenuActions() { - connect(ui->actionNew, &QAction::triggered, this, &MainWindowController::onActionNew); - connect(ui->actionContinue, &QAction::triggered, this, &MainWindowController::onActionContinue); - connect(ui->actionStop, &QAction::triggered, this, &MainWindowController::onActionStop); - connect(ui->actionSync, &QAction::triggered, this, &MainWindowController::onActionSync); - connect(ui->actionLogout, &QAction::triggered, this, &MainWindowController::onActionLogout); - connect(ui->actionClear_Cache, &QAction::triggered, this, &MainWindowController::onActionClear_Cache); - connect(ui->actionSend_Feedback, &QAction::triggered, this, &MainWindowController::onActionSend_Feedback); - connect(ui->actionReports, &QAction::triggered, this, &MainWindowController::onActionReports); - connect(ui->actionShow, &QAction::triggered, this, &MainWindowController::onActionShow); - connect(ui->actionPreferences, &QAction::triggered, this, &MainWindowController::onActionPreferences); - connect(ui->actionAbout, &QAction::triggered, this, &MainWindowController::onActionAbout); - connect(ui->actionQuit, &QAction::triggered, this, &MainWindowController::onActionQuit); - connect(ui->actionHelp, &QAction::triggered, this, &MainWindowController::onActionHelp); - - QMenu *trayMenu = new QMenu(this); -for (auto act : ui->menuToggl_Desktop->actions()) { - trayMenu->addAction(act); - } - - trayIcon->setContextMenu(trayMenu); -} - -void MainWindowController::onActionNew() { - TogglApi::instance->start("", "", 0, 0, nullptr, false); -} - -void MainWindowController::onActionContinue() { - TogglApi::instance->continueLatestTimeEntry(); -} - -void MainWindowController::onActionStop() { - TogglApi::instance->stop(); -} - -void MainWindowController::onActionShow() { - displayApp(true); -} - -void MainWindowController::onActionSync() { - TogglApi::instance->sync(); -} - -void MainWindowController::onActionReports() { - TogglApi::instance->openInBrowser(); -} - -void MainWindowController::onActionPreferences() { - TogglApi::instance->editPreferences(); -} - -void MainWindowController::onActionAbout() { - aboutDialog->show(); -} - -void MainWindowController::onActionSend_Feedback() { - feedbackDialog->show(); -} - -void MainWindowController::onActionLogout() { - TogglApi::instance->logout(); -} - -void MainWindowController::onActionQuit() { - quitApp(); -} - -void MainWindowController::quitApp() { - TogglApi::instance->shutdown = true; - TogglApi::instance->clear(); - qApp->exit(0); -} - -void MainWindowController::onActionClear_Cache() { - if (QMessageBox::Ok == QMessageBox( - QMessageBox::Question, - "Clear Cache?", - "Clearing cache will delete any unsaved time entries and log you out.", - QMessageBox::Ok|QMessageBox::Cancel).exec()) { - TogglApi::instance->clearCache(); - } -} - -void MainWindowController::onActionHelp() { - TogglApi::instance->getSupport(); -} - -void MainWindowController::displayApp(const bool open) { - if (open) { - show(); - raise(); - } -} - -void MainWindowController::readSettings() { - QSettings settings("Toggl", "TogglDesktop"); - restoreGeometry(settings.value("geometry").toByteArray()); - restoreState(settings.value("windowState").toByteArray()); -} - -void MainWindowController::writeSettings() { - QSettings settings("Toggl", "TogglDesktop"); - settings.setValue("geometry", saveGeometry()); - settings.setValue("windowState", saveState()); -} - -void MainWindowController::closeEvent(QCloseEvent *event) { - - // Window manager has requested the app to quit so just quit - if (powerManagement->aboutToShutdown()) { - QMainWindow::closeEvent(event); - return; - } - - // Save current windows frame - TogglApi::instance->setWindowsFrameSetting(QRect(pos().x(), - pos().y(), - size().width(), - size().height())); - - if (trayIcon->isVisible()) { - event->ignore(); - hide(); - return; - } - - QMessageBox::StandardButton dialog; - dialog = QMessageBox::question(this, - "Quit Toggl Desktop", - "Really quit the app?", - QMessageBox::Ok | QMessageBox::Cancel); - if (QMessageBox::Ok == dialog) { - writeSettings(); - close(); - } else { - event->ignore(); - return; - } - - QMainWindow::closeEvent(event); -} - -void MainWindowController::showEvent(QShowEvent *event) { - QMainWindow::showEvent(event); - - // Avoid 'user already logged in' error from double UI start - if (ui_started) { - return; - } - ui_started = true; - - if (!TogglApi::instance->startEvents()) { - QMessageBox( - QMessageBox::Warning, - "Error", - "The application could not start. Please inspect the log file.", - QMessageBox::Ok|QMessageBox::Cancel).exec(); - return; - } - if (script.isEmpty()) { - // qDebug() << "no script to run"; - return; - } - qDebug() << "will run script: " << script; - - QtConcurrent::run(this, &MainWindowController::runScript); -} - -void MainWindowController::displayUpdate(const QString url) { - if (aboutDialog->isVisible() - || url.isEmpty()) { - return; - } - if (QMessageBox::Yes == QMessageBox( - QMessageBox::Question, - "Download new version?", - "A new version of Toggl Desktop is available. Continue with download?", - QMessageBox::No|QMessageBox::Yes).exec()) { - QDesktopServices::openUrl(QUrl(url)); - quitApp(); - } -} - -void MainWindowController::runScript() { - if (TogglApi::instance->runScriptFile(script)) { - quitApp(); - } -} - -void MainWindowController::restoreLastWindowsFrame() { - const QRect frame = TogglApi::instance->getWindowsFrameSetting(); - move(frame.x(), frame.y()); - resize(frame.width(), frame.height()); -} diff --git a/src/ui/linux/TogglDesktop/mainwindowcontroller.h b/src/ui/linux/TogglDesktop/mainwindowcontroller.h deleted file mode 100644 index c03efe1a99..0000000000 --- a/src/ui/linux/TogglDesktop/mainwindowcontroller.h +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_MAINWINDOWCONTROLLER_H_ -#define SRC_UI_LINUX_TOGGLDESKTOP_MAINWINDOWCONTROLLER_H_ - -#include -#include -#include // NOLINT -#include - -#include -#include - -#include "./toggl.h" -#include "./preferencesdialog.h" -#include "./aboutdialog.h" -#include "./feedbackdialog.h" -#include "./qxtglobalshortcut.h" -#include "./systemtray.h" -#include "./powermanagement.h" -#include "./networkmanagement.h" -#include "./overlaywidget.h" -#include "./loginwidget.h" -#include "./timeentrylistwidget.h" -#include "./timeentryeditorwidget.h" -#include "./idlenotificationwidget.h" - -namespace Ui { -class MainWindowController; -} - -class MainWindowController : public QMainWindow { - Q_OBJECT - - public: - MainWindowController( - QWidget *parent = 0, - QString logPathOverride = "", - QString dbPathOverride = "", - QString scriptPath = ""); - ~MainWindowController(); - - static MainWindowController *Instance; - - void quitApp(); - void setShortcuts(); - - protected: - void closeEvent(QCloseEvent *event); - void showEvent(QShowEvent *event); - void runScript(); - - private slots: // NOLINT - void toggleWindow(QSystemTrayIcon::ActivationReason r); - - void displayApp(const bool open); - - void displayRunningTimerState(TimeEntryView *te); - - void displayStoppedTimerState(); - - void displayLogin( - const bool open, - const uint64_t user_id); - - void displayPomodoro( - const QString title, - const QString informative_text); - - void displayPomodoroBreak( - const QString title, - const QString informative_text); - - void displayUpdate(const QString url); - - void displayOnlineState(int64_t); - void showHideHotkeyPressed(); - void continueStopHotkeyPressed(); - - void onActionNew(); - void onActionContinue(); - void onActionStop(); - void onActionShow(); - void onActionSync(); - void onActionReports(); - void onActionPreferences(); - void onActionAbout(); - void onActionSend_Feedback(); - void onActionLogout(); - void onActionQuit(); - void onActionClear_Cache(); - void onActionHelp(); - - void updateShowHideShortcut(); - void updateContinueStopShortcut(); - - void onOnlineStateChanged(); - - void onShortcutDelete(); - void onShortcutPause(); - void onShortcutConfirm(); - void onShortcutGroupOpen(); - void onShortcutGroupClose(); - - private: - Ui::MainWindowController *ui; - - TogglApi *togglApi; - - bool tracking; - bool loggedIn; - bool startInBackground; - - QxtGlobalShortcut* showHide; - QxtGlobalShortcut* continueStop; - - PreferencesDialog *preferencesDialog; - AboutDialog *aboutDialog; - FeedbackDialog *feedbackDialog; - - QIcon icon; - QIcon iconDisabled; - SystemTray *trayIcon; - - bool pomodoro; - - QString script; - - PowerManagement *powerManagement; - NetworkManagement *networkManagement; - - QShortcut shortcutDelete; - QShortcut shortcutPause; - QShortcut shortcutConfirm; - QShortcut shortcutGroupOpen; - QShortcut shortcutGroupClose; - - void readSettings(); - void writeSettings(); - - void connectMenuActions(); - void enableMenuActions(); - - bool ui_started; - - void restoreLastWindowsFrame(); -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_MAINWINDOWCONTROLLER_H_ diff --git a/src/ui/linux/TogglDesktop/mainwindowcontroller.ui b/src/ui/linux/TogglDesktop/mainwindowcontroller.ui deleted file mode 100644 index d88d1ed89b..0000000000 --- a/src/ui/linux/TogglDesktop/mainwindowcontroller.ui +++ /dev/null @@ -1,221 +0,0 @@ - - - MainWindowController - - - - 0 - 0 - 350 - 420 - - - - - 0 - 0 - - - - Toggl Desktop - - - false - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - - - 0 - 0 - 350 - 27 - - - - - - - false - - - - To&ggl Desktop - - - - - - - - - - - - - - - - - - - - - - - - - &New - - - Ctrl+N - - - - - &Continue - - - Ctrl+O - - - - - &Stop - - - Ctrl+S - - - - - S&ync - - - Ctrl+R - - - - - &Reports - - - - - &Preferences - - - - - &About - - - - - Send &Feedback - - - - - &Logout - - - - - &Quit - - - - - Clear Cache - - - - - &Help - - - - - false - - - &email - - - - - Sh&ow - - - - - - - ErrorViewController - QWidget -
errorviewcontroller.h
- 1 -
- - OverlayWidget - QWidget -
overlaywidget.h
-
- - LoginWidget - QWidget -
loginwidget.h
-
- - TimeEntryEditorWidget - QWidget -
timeentryeditorwidget.h
-
- - TimeEntryListWidget - QWidget -
timeentrylistwidget.h
-
- - IdleNotificationWidget - QWidget -
idlenotificationwidget.h
-
-
- - -
diff --git a/src/ui/linux/TogglDesktop/overlaywidget.cpp b/src/ui/linux/TogglDesktop/overlaywidget.cpp deleted file mode 100644 index 88ce1b252f..0000000000 --- a/src/ui/linux/TogglDesktop/overlaywidget.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "overlaywidget.h" -#include "ui_overlaywidget.h" -#include "./toggl.h" - -OverlayWidget::OverlayWidget(QStackedWidget *parent) : - QWidget(parent), - ui(new Ui::OverlayWidget) -{ - ui->setupUi(this); - current_type = -1; - - ui->bottomText->setCursor(Qt::PointingHandCursor); - - connect(TogglApi::instance, SIGNAL(displayLogin(bool,uint64_t)), // NOLINT - this, SLOT(displayLogin(bool,uint64_t))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayOverlay(int64_t)), // NOLINT - this, SLOT(displayOverlay(int64_t))); // NOLINT -} - -OverlayWidget::~OverlayWidget() -{ - delete ui; -} - -void OverlayWidget::display() { - qobject_cast(parent())->setCurrentWidget(this); -} - -void OverlayWidget::displayOverlay(const int64_t type) { - current_type = type; - QString top(""); - QString button(""); - QString bottom(""); - // setup overlay content - if (type == 0) { - // missing ws - top.append("You no longer have access to your last Workspace. Create a new workspace on Toggl.com to continue tracking"); - - bottom.append("

"); - bottom.append("Created your new workspace?
"); - bottom.append(""); - bottom.append("Force sync

"); - - button.append("Log in to Toggl.com"); - } else if (type == 1) { - // tos accept - top.append("

Our Terms of Service and Privacy Policy have changed

"); - top.append("Please read and accept our updated "); - top.append(""); - top.append(""); - top.append("Terms of Service and "); - top.append(""); - top.append(""); - top.append("Privacy policy to continuue using Toggl"); - bottom.append("If you have any questions, contact us at support@toggl.com"); - button.append("I accept"); - } - - ui->topText->setText(top); - ui->actionButton->setText(button); - ui->bottomText->setText(bottom); - display(); -} - -void OverlayWidget::displayLogin( - const bool open, - const uint64_t user_id) { - - if (open || user_id) { - } -} - -void OverlayWidget::on_actionButton_clicked() -{ - if (current_type == 0) { - TogglApi::instance->openInBrowser(); - } else if (current_type == 1) { - TogglApi::instance->tosAccept(); - } -} - -void OverlayWidget::on_bottomText_linkActivated(const QString &link) -{ - Q_UNUSED(link); - if (current_type == 0) { - TogglApi::instance->fullSync(); - } -} - -void OverlayWidget::on_topText_linkActivated(const QString &link) -{ - if (current_type == 1) { - TogglApi::instance->openLegal(link); - } -} diff --git a/src/ui/linux/TogglDesktop/overlaywidget.h b/src/ui/linux/TogglDesktop/overlaywidget.h deleted file mode 100644 index a7ac6998fc..0000000000 --- a/src/ui/linux/TogglDesktop/overlaywidget.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef OVERLAYWIDGET_H -#define OVERLAYWIDGET_H - -#include -#include -#include -#include "./timeentryview.h" - -namespace Ui { -class OverlayWidget; -} - -class OverlayWidget : public QWidget -{ - Q_OBJECT - -public: - explicit OverlayWidget(QStackedWidget *parent = nullptr); - ~OverlayWidget(); - void display(); - -private: - Ui::OverlayWidget *ui; - int64_t current_type; - -private slots: // NOLINT - - void displayOverlay(const int64_t type); - - void displayLogin( - const bool open, - const uint64_t user_id); - - void on_actionButton_clicked(); - void on_bottomText_linkActivated(const QString &link); - void on_topText_linkActivated(const QString &link); -}; - - -#endif // OVERLAYWIDGET_H diff --git a/src/ui/linux/TogglDesktop/overlaywidget.ui b/src/ui/linux/TogglDesktop/overlaywidget.ui deleted file mode 100644 index 77e7e8b1b1..0000000000 --- a/src/ui/linux/TogglDesktop/overlaywidget.ui +++ /dev/null @@ -1,82 +0,0 @@ - - - OverlayWidget - - - - 0 - 0 - 400 - 300 - - - - Form - - - - - - <html><head/><body><p align="center">Created your new workspace?<br/><a href="#force-sync"><span style=" text-decoration: underline; color:#0000ff;">Force sync</span></a></p></body></html> - - - Qt::RichText - - - - - - - QLayout::SetMinAndMaxSize - - - - - - 160 - 30 - - - - Log in to Toggl.com - - - true - - - true - - - false - - - - - - - - - - 16777215 - 16777215 - - - - You no longer have access to your last Workspace. Create a new workspace on Toggl.com to continue tracking - - - Qt::AlignCenter - - - true - - - 20 - - - - - - - - diff --git a/src/ui/linux/TogglDesktop/preferencesdialog.cpp b/src/ui/linux/TogglDesktop/preferencesdialog.cpp deleted file mode 100644 index 9998a234f7..0000000000 --- a/src/ui/linux/TogglDesktop/preferencesdialog.cpp +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. -#include // NOLINT - -#include "./preferencesdialog.h" -#include "./ui_preferencesdialog.h" - -#include "./toggl.h" -#include "./settingsview.h" - -PreferencesDialog::PreferencesDialog(QWidget *parent) : QDialog(parent), -ui(new Ui::PreferencesDialog) { - ui->setupUi(this); - - connect(TogglApi::instance, SIGNAL(displaySettings(bool,SettingsView*)), // NOLINT - this, SLOT(displaySettings(bool,SettingsView*))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayLogin(bool,uint64_t)), // NOLINT - this, SLOT(displayLogin(bool,uint64_t))); // NOLINT - - connect(TogglApi::instance, SIGNAL(updateShowHideShortcut()), // NOLINT - this, SLOT(updateShowHideShortcut())); // NOLINT - - connect(TogglApi::instance, SIGNAL(updateContinueStopShortcut()), // NOLINT - this, SLOT(updateContinueStopShortcut())); // NOLINT - - connect(ui->dayCheckbox_1, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); - connect(ui->dayCheckbox_2, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); - connect(ui->dayCheckbox_3, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); - connect(ui->dayCheckbox_4, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); - connect(ui->dayCheckbox_5, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); - connect(ui->dayCheckbox_6, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); - connect(ui->dayCheckbox_7, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); - connect(ui->stopEntry, &QCheckBox::clicked, this, &PreferencesDialog::onStopEntryCheckboxClicked); - keyId = 0; -} - -PreferencesDialog::~PreferencesDialog() { - delete ui; -} - -void PreferencesDialog::displaySettings(const bool open, - SettingsView *settings) { - if (open) { - show(); - } - - ui->useSystemProxySettings->setChecked(settings->AutodetectProxy); - - ui->useProxy->setChecked(settings->UseProxy); - ui->proxyHost->setText(settings->ProxyHost); - ui->proxyPort->setText(QString::number(settings->ProxyPort)); - ui->proxyUsername->setText(settings->ProxyUsername); - ui->proxyPassword->setText(settings->ProxyPassword); - - ui->recordTimeline->setChecked(settings->RecordTimeline); // user based! - - ui->idleDetection->setChecked(settings->UseIdleDetection); - ui->idleMinutes->setText(QString::number(settings->IdleMinutes)); - ui->idleMinutes->setEnabled(ui->idleDetection->isChecked()); - - ui->remindToTrackTime->setChecked(settings->Reminder); - ui->reminderMinutes->setText(QString::number(settings->ReminderMinutes)); - ui->reminderMinutes->setEnabled(ui->remindToTrackTime->isChecked()); - ui->dayCheckbox_1->setEnabled(ui->remindToTrackTime->isChecked()); - ui->dayCheckbox_2->setEnabled(ui->remindToTrackTime->isChecked()); - ui->dayCheckbox_3->setEnabled(ui->remindToTrackTime->isChecked()); - ui->dayCheckbox_4->setEnabled(ui->remindToTrackTime->isChecked()); - ui->dayCheckbox_5->setEnabled(ui->remindToTrackTime->isChecked()); - ui->dayCheckbox_6->setEnabled(ui->remindToTrackTime->isChecked()); - ui->dayCheckbox_7->setEnabled(ui->remindToTrackTime->isChecked()); - ui->reminderStartTimeEdit->setEnabled(ui->remindToTrackTime->isChecked()); - ui->reminderEndTimeEdit->setEnabled(ui->remindToTrackTime->isChecked()); - - ui->pomodoroTimer->setChecked(settings->Pomodoro); - ui->pomodoroMinutes->setText(QString::number(settings->PomodoroMinutes)); - ui->pomodoroMinutes->setEnabled(ui->pomodoroTimer->isChecked()); - - ui->pomodoroBreakTimer->setChecked(settings->PomodoroBreak); - ui->pomodoroBreakTimer->setEnabled(settings->Pomodoro); - ui->pomodoroBreakMinutes->setText( - QString::number(settings->PomodoroBreakMinutes)); - ui->pomodoroBreakMinutes->setEnabled( - ui->pomodoroTimer->isChecked() - && ui->pomodoroBreakTimer->isChecked()); - - ui->focusAppOnShortcut->setChecked((settings->FocusOnShortcut)); - - ui->dayCheckbox_1->setChecked(settings->RemindOnMonday); - ui->dayCheckbox_2->setChecked(settings->RemindOnTuesday); - ui->dayCheckbox_3->setChecked(settings->RemindOnWednesday); - ui->dayCheckbox_4->setChecked(settings->RemindOnThursday); - ui->dayCheckbox_5->setChecked(settings->RemindOnFriday); - ui->dayCheckbox_6->setChecked(settings->RemindOnSaturday); - ui->dayCheckbox_7->setChecked(settings->RemindOnSunday); - - ui->reminderStartTimeEdit->setTime(settings->RemindStartTime); - ui->reminderEndTimeEdit->setTime(settings->RemindEndTime); - - ui->stopEntry->setChecked(settings->StopEntryOnShutdownSleep); - - QString sh(TogglApi::instance->getShowHideKey()); - if (sh.length() == 0) { - sh = "Record shortcut"; - } - ui->showHideButton->setText(sh); - - QString cs(TogglApi::instance->getContinueStopKey()); - if (cs.length() == 0) { - cs = "Record shortcut"; - } - ui->continueStopButton->setText(cs); -} - -void PreferencesDialog::displayLogin(const bool open, - const uint64_t user_id) { - ui->recordTimeline->setEnabled(!open && user_id); -} - -void PreferencesDialog::onDayCheckboxClicked(bool checked) { - Q_UNUSED(checked); - TogglApi::instance->setSettingsRemindDays( - ui->dayCheckbox_1->isChecked(), - ui->dayCheckbox_2->isChecked(), - ui->dayCheckbox_3->isChecked(), - ui->dayCheckbox_4->isChecked(), - ui->dayCheckbox_5->isChecked(), - ui->dayCheckbox_6->isChecked(), - ui->dayCheckbox_7->isChecked() - ); -} - -void PreferencesDialog::on_proxyHost_editingFinished() { - setProxySettings(); -} - -void PreferencesDialog::on_proxyPort_editingFinished() { - setProxySettings(); -} - -void PreferencesDialog::on_proxyUsername_editingFinished() { - setProxySettings(); -} - -void PreferencesDialog::on_proxyPassword_editingFinished() { - setProxySettings(); -} - -void PreferencesDialog::on_idleDetection_clicked(bool checked) { - TogglApi::instance->setSettingsUseIdleDetection(checked); -} - -void PreferencesDialog::on_recordTimeline_clicked(bool checked) { - TogglApi::instance->toggleTimelineRecording(checked); -} - -void PreferencesDialog::on_remindToTrackTime_clicked(bool checked) { - TogglApi::instance->setSettingsReminder(checked); -} - -void PreferencesDialog::on_pomodoroTimer_clicked(bool checked) { - TogglApi::instance->setSettingsPomodoro(checked); -} - -void PreferencesDialog::on_pomodoroBreakTimer_clicked(bool checked) { - TogglApi::instance->setSettingsPomodoroBreak(checked); -} - -void PreferencesDialog::updateShowHideShortcut() { - QString text(TogglApi::instance->getShowHideKey()); - if (text.length() == 0) { - text = "Record shortcut"; - } - ui->showHideButton->setText(text); -} - -void PreferencesDialog::updateContinueStopShortcut() { - QString text(TogglApi::instance->getContinueStopKey()); - if (text.length() == 0) { - text = "Record shortcut"; - } - ui->continueStopButton->setText(text); -} - -void PreferencesDialog::on_showHideClear_clicked() { - keySequence = ""; - keyId = 1; - saveCurrentShortcut(); -} - -void PreferencesDialog::on_continueStopClear_clicked() { - keySequence = ""; - keyId = 2; - saveCurrentShortcut(); -} - -void PreferencesDialog::on_showHideButton_clicked() { - ui->showHideButton->setText("Type shortcut"); - keyId = 1; -} - -void PreferencesDialog::on_reminderStartTimeEdit_editingFinished() { - TogglApi::instance->setSettingsRemindTimes( - ui->reminderStartTimeEdit->time(), - ui->reminderEndTimeEdit->time() - ); -} - -void PreferencesDialog::on_reminderEndTimeEdit_editingFinished() { - TogglApi::instance->setSettingsRemindTimes( - ui->reminderStartTimeEdit->time(), - ui->reminderEndTimeEdit->time() - ); -} - -void PreferencesDialog::on_continueStopButton_clicked() { - ui->continueStopButton->setText("Type shortcut"); - keyId = 2; -} - -void PreferencesDialog::keyPressEvent(QKeyEvent *event) { - if (keyId) { - keySequence = ""; - QKeyEvent *keyEvent = static_cast(event); - - int keyInt = keyEvent->key(); - Qt::Key key = static_cast(keyInt); - if (key == Qt::Key_unknown) { - return; - } - - // Reset to previous values - if (key == Qt::Key_Escape) { - if (keyId == 1) { - TogglApi::instance->updateShowHideShortcut(); - } - if (keyId == 2) { - TogglApi::instance->updateContinueStopShortcut(); - } - keyId = 0; - return; - } - - // the user have clicked just and - // only the special keys Ctrl, Shift, Alt, Meta. - if (key == Qt::Key_Control || - key == Qt::Key_Shift || - key == Qt::Key_Alt || - key == Qt::Key_Meta) { - return; - } - - // check for a combination of user clicks - Qt::KeyboardModifiers modifiers = keyEvent->modifiers(); - - if (modifiers & Qt::ShiftModifier) - keyInt += Qt::SHIFT; - if (modifiers & Qt::ControlModifier) - keyInt += Qt::CTRL; - if (modifiers & Qt::AltModifier) - keyInt += Qt::ALT; - if (modifiers & Qt::MetaModifier) - keyInt += Qt::META; - - keySequence = QKeySequence(keyInt).toString(QKeySequence::NativeText); - - if (keyId == 1) { - ui->showHideButton->setText(keySequence); - } else if (keyId == 2) { - ui->continueStopButton->setText(keySequence); - } - } -} - -void PreferencesDialog::keyReleaseEvent(QKeyEvent *event) { - Q_UNUSED(event); - saveCurrentShortcut(); -} - -void PreferencesDialog::saveCurrentShortcut() { - if (keyId == 1) { - TogglApi::instance->setShowHideKey(keySequence); - } else if (keyId == 2) { - TogglApi::instance->setContinueStopKey(keySequence); - } - - keyId = 0; - keySequence = ""; -} - -bool PreferencesDialog::setProxySettings() { - return TogglApi::instance->setProxySettings(ui->useProxy->isChecked(), - ui->proxyHost->text(), - ui->proxyPort->text().toULongLong(), - ui->proxyUsername->text(), - ui->proxyPassword->text()); -} - -void PreferencesDialog::on_useProxy_clicked(bool checked) { - Q_UNUSED(checked); - setProxySettings(); -} - -void PreferencesDialog::on_idleMinutes_editingFinished() { - TogglApi::instance->setSettingsIdleMinutes( - ui->idleMinutes->text().toULongLong()); -} - -void PreferencesDialog::on_reminderMinutes_editingFinished() { - TogglApi::instance->setSettingsReminderMinutes( - ui->reminderMinutes->text().toULongLong()); -} - -void PreferencesDialog::on_pomodoroMinutes_editingFinished() { - TogglApi::instance->setSettingsPomodoroMinutes( - ui->pomodoroMinutes->text().toULongLong()); -} - -void PreferencesDialog::on_pomodoroBreakMinutes_editingFinished() { - TogglApi::instance->setSettingsPomodoroBreakMinutes( - ui->pomodoroBreakMinutes->text().toULongLong()); -} - -void PreferencesDialog::on_useSystemProxySettings_clicked(bool checked) { - TogglApi::instance->setSettingsAutodetectProxy(checked); -} - -void PreferencesDialog::on_focusAppOnShortcut_clicked(bool checked) { - TogglApi::instance->setSettingsFocusOnShortcut(checked); -} - -void PreferencesDialog::onStopEntryCheckboxClicked(bool checked) { - TogglApi::instance->setSettingsStopEntryOnShutdown(checked); -} diff --git a/src/ui/linux/TogglDesktop/preferencesdialog.h b/src/ui/linux/TogglDesktop/preferencesdialog.h deleted file mode 100644 index d1b97d1e4a..0000000000 --- a/src/ui/linux/TogglDesktop/preferencesdialog.h +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_PREFERENCESDIALOG_H_ -#define SRC_UI_LINUX_TOGGLDESKTOP_PREFERENCESDIALOG_H_ - -#include -#include -#include "./settingsview.h" - -namespace Ui { -class PreferencesDialog; -} - -class PreferencesDialog : public QDialog { - Q_OBJECT - - public: - explicit PreferencesDialog(QWidget *parent = 0); - ~PreferencesDialog(); - - private: - Ui::PreferencesDialog *ui; - int keyId; - QString keySequence; - - bool setSettings(); - bool setProxySettings(); - - private slots: // NOLINT - void displaySettings(const bool open, - SettingsView *settings); - - void displayLogin(const bool open, - const uint64_t user_id); - - void onDayCheckboxClicked(bool checked); - - void on_proxyHost_editingFinished(); - void on_proxyPort_editingFinished(); - void on_proxyUsername_editingFinished(); - void on_proxyPassword_editingFinished(); - void on_idleDetection_clicked(bool checked); - void on_recordTimeline_clicked(bool checked); - void on_remindToTrackTime_clicked(bool checked); - void on_pomodoroTimer_clicked(bool checked); - void on_pomodoroBreakTimer_clicked(bool checked); - void on_useProxy_clicked(bool checked); - void on_idleMinutes_editingFinished(); - void on_reminderMinutes_editingFinished(); - void on_pomodoroMinutes_editingFinished(); - void on_pomodoroBreakMinutes_editingFinished(); - void on_useSystemProxySettings_clicked(bool checked); - void on_focusAppOnShortcut_clicked(bool checked); - void on_continueStopClear_clicked(); - void on_showHideClear_clicked(); - void on_continueStopButton_clicked(); - void on_showHideButton_clicked(); - void on_reminderStartTimeEdit_editingFinished(); - void on_reminderEndTimeEdit_editingFinished(); - void keyPressEvent(QKeyEvent *event); - void keyReleaseEvent(QKeyEvent *event); - void saveCurrentShortcut(); - void onStopEntryCheckboxClicked(bool checked); - - void updateShowHideShortcut(); - void updateContinueStopShortcut(); -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_PREFERENCESDIALOG_H_ diff --git a/src/ui/linux/TogglDesktop/preferencesdialog.ui b/src/ui/linux/TogglDesktop/preferencesdialog.ui deleted file mode 100644 index a8f5c9cb1f..0000000000 --- a/src/ui/linux/TogglDesktop/preferencesdialog.ui +++ /dev/null @@ -1,476 +0,0 @@ - - - PreferencesDialog - - - - 0 - 0 - 451 - 447 - - - - Preferences - - - - - - - - - 2 - - - - General - - - - - - Pomodoro - - - - - - Pomodoro timer - - - - - - - - 60 - 16777215 - - - - 25 - - - 3 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - minutes - - - - - - - Pomodoro break timer - - - - - - - - 60 - 16777215 - - - - 25 - - - 3 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - minutes - - - - - - - - - - - 60 - 16777215 - - - - 5 - - - 3 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - minutes - - - - - - - Idle detection - - - - - - - Record timeline - - - - - - - Shortcuts - - - - - - Continue/Stop timer - - - - - - - - - - Record Shortcut - - - - - - - - 25 - 25 - - - - Clear shortcut - - - x - - - - - - - Show/Hide Toggl - - - - - - - - - - Record Shortcut - - - - - - - - 25 - 25 - - - - Clear Shortcut - - - x - - - - - - - Focus app on shortcut - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Stop running entry on computer sleep/shutdown - - - - - - - - Proxy - - - - - - Use system proxy settings - - - - - - - Use prox&y to connect to Toggl - - - false - - - true - - - false - - - - - - &Host - - - - - - - - - - Port - - - - - - - - - - Username - - - - - - - - - - Password - - - - - - - QLineEdit::Password - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - true - - - Reminder - - - - - - - - - Remind to track time - - - - - - - Sunday - - - - - - - Reminder end time - - - - - - - Thursday - - - - - - - Reminder start time - - - - - - - Wednesday - - - - - - - Reminder days - - - - - - - Friday - - - - - - - Saturday - - - - - - - Monday - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 60 - 16777215 - - - - 5 - - - 3 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Tuesday - - - - - - - - - - minutes - - - - - - - - - - - - diff --git a/src/ui/linux/TogglDesktop/systemtray.cpp b/src/ui/linux/TogglDesktop/systemtray.cpp index 5858c9da1d..8ab018c3fb 100644 --- a/src/ui/linux/TogglDesktop/systemtray.cpp +++ b/src/ui/linux/TogglDesktop/systemtray.cpp @@ -2,12 +2,12 @@ #include #include +#include #include "./toggl.h" #include "./systemtray.h" -#include "./mainwindowcontroller.h" -SystemTray::SystemTray(MainWindowController *parent, QIcon defaultIcon) : +SystemTray::SystemTray(QObject *parent, QIcon defaultIcon) : QSystemTrayIcon(parent), notificationsPresent(true) { @@ -31,10 +31,6 @@ SystemTray::SystemTray(MainWindowController *parent, QIcon defaultIcon) : connect(watcher, &QDBusPendingCallWatcher::finished, this, &SystemTray::notificationCapabilitiesReceived); } -MainWindowController *SystemTray::mainWindow() { - return qobject_cast(parent()); -} - bool SystemTray::isVisible() const { #if (QT_VERSION >= QT_VERSION_CHECK(5, 5, 0)) return true; @@ -125,9 +121,11 @@ void SystemTray::notificationClosed(uint id, uint reason) { void SystemTray::notificationActionInvoked(uint id, const QString &action) { Q_UNUSED(id); + /* TODO if (action == "default") { mainWindow()->setWindowState(Qt::WindowActive); } + */ } void SystemTray::displayIdleNotification( diff --git a/src/ui/linux/TogglDesktop/systemtray.h b/src/ui/linux/TogglDesktop/systemtray.h index cbd3aeefca..4e1a649172 100644 --- a/src/ui/linux/TogglDesktop/systemtray.h +++ b/src/ui/linux/TogglDesktop/systemtray.h @@ -6,16 +6,13 @@ #include #include -class MainWindowController; class SettingsView; class SystemTray : public QSystemTrayIcon { Q_OBJECT public: - SystemTray(MainWindowController *parent = nullptr, QIcon defaultIcon = QIcon()); - - MainWindowController *mainWindow(); + SystemTray(QObject *parent = nullptr, QIcon defaultIcon = QIcon()); bool isVisible() const; bool notificationsAvailable(); diff --git a/src/ui/linux/TogglDesktop/timeentrycellwidget.cpp b/src/ui/linux/TogglDesktop/timeentrycellwidget.cpp deleted file mode 100644 index ed3fe5b8d8..0000000000 --- a/src/ui/linux/TogglDesktop/timeentrycellwidget.cpp +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#include "./timeentrycellwidget.h" -#include "./ui_timeentrycellwidget.h" - -#include -#include -#include - -#include "./toggl.h" - -TimeEntryCellWidget::TimeEntryCellWidget(QListWidgetItem *item) : QWidget(nullptr), -ui(new Ui::TimeEntryCellWidget), -item(item), -description(""), -project(""), -guid(""), -group(false), -groupOpen(false), -groupName(""), -timeEntry(nullptr) { - ui->setupUi(this); - ui->dataFrame->installEventFilter(this); - setStyleSheet( - "* { font-size: 13px }" - "QFrame { background-color:transparent; border:none; margin:0 }" - "QPushButton#dataFrame { background-color:#fefefe; border: none; border-bottom:1px solid #cacaca; margin: 0 }" - "QPushButton#dataFrame:flat { background-color:transparent; }" - ); - ui->groupButton->setStyleSheet( - "QPushButton { outline:none; border:none; font-size:11px; color:#a4a4a4 }" - "QPushButton:!checked { background:url(:/images/group_icon_closed.svg) no-repeat; }" - "QPushButton:checked { background:url(:/images/group_icon_open.svg) no-repeat; }" - ); -} - -void TimeEntryCellWidget::display(TimeEntryView *view) { - setLoadMore(false); - guid = view->GUID; - groupName = view->GroupName; - timeEntry = view; - description = - (view->Description.length() > 0) ? - view->Description : "(no description)"; - project = view->ProjectAndTaskLabel; - - setEllipsisTextToLabel(ui->description, description); - setEllipsisTextToLabel(ui->project, project); - ui->project->setStyleSheet("color: '" + getProjectColor(view->Color) + "'"); - ui->duration->setText(view->Duration); - - ui->billable->setVisible(view->Billable); - ui->tags->setVisible(!view->Tags.isEmpty()); - - ui->headerFrame->setVisible(view->IsHeader); - ui->date->setText(view->DateHeader); - ui->dateDuration->setText(view->DateDuration); - - ui->unsyncedicon->setVisible(view->Unsynced); - - if (view->StartTimeString.length() > 0 && - view->EndTimeString.length() > 0) { - ui->duration->setToolTip( - QString("

" + - view->StartTimeString + " - " + - view->EndTimeString+"

")); - } - - ui->tags->setToolTip( - QString("

" + - (view->Tags.join("\t")).replace(QString("\t"), QString(", ")) + "

")); - if (view->Description.length() > 0) { - ui->description->setToolTip( - QString("

" + - view->Description + "

")); - } - if (view->ProjectAndTaskLabel.length() > 0) { - ui->project->setToolTip( - QString("

" + - view->ProjectAndTaskLabel + "

")); - } - setupGroupedMode(view); -} - -void TimeEntryCellWidget::setLoadMore(bool load_more) { - ui->headerFrame->setVisible(false); - ui->loadMoreButton->setEnabled(load_more); - ui->loadMoreButton->setVisible(load_more); - ui->descProjFrame->setVisible(!load_more); - ui->groupFrame->setVisible(!load_more); - ui->frame->setVisible(!load_more); - ui->dataFrame->setFlat(load_more); - ui->dataFrame->setStyleSheet(load_more ? "QPushButton#dataFrame { border:none }" : ""); - ui->dataFrame->setFocusPolicy(load_more ? Qt::NoFocus : Qt::StrongFocus); - if (load_more) { - ui->unsyncedicon->setVisible(false); - } -} - -QString TimeEntryCellWidget::entryGuid() { - return guid; -} - -void TimeEntryCellWidget::deleteTimeEntry() { - if (guid.isEmpty()) - return; - - if (timeEntry->confirmlessDelete() || QMessageBox::Ok == QMessageBox( - QMessageBox::Question, - "Delete this time entry?", - "Deleted time entries cannot be restored.", - QMessageBox::Ok|QMessageBox::Cancel).exec()) { - TogglApi::instance->deleteTimeEntry(guid); - } -} - -bool TimeEntryCellWidget::eventFilter(QObject *watched, QEvent *event) { - if (event->type() == QEvent::FocusIn) { - auto fe = reinterpret_cast(event); - if (fe->reason() == Qt::TabFocusReason || fe->reason() == Qt::BacktabFocusReason) - focusInEvent(fe); - } - return QWidget::eventFilter(watched, event); -} - -void TimeEntryCellWidget::focusInEvent(QFocusEvent *event) { - item->listWidget()->scrollToItem(item, QAbstractItemView::PositionAtCenter); -} - -void TimeEntryCellWidget::setupGroupedMode(TimeEntryView *view) { - // Grouped Mode Setup - group = view->Group; - QString style = "#dataFrame{border-right:2px solid palette(alternate-base);border-bottom:2px solid palette(alternate-base);background-color: palette(base);}"; - QString count = ""; - QString continueIcon = ":/images/continue_light.svg"; - QString descriptionStyle = "border:none;"; - int left = 0; - if (view->GroupItemCount && view->GroupOpen && !view->Group) { - left = 10; - descriptionStyle = "border:none;color:palette(mid)"; - } - ui->description->setStyleSheet(descriptionStyle); - ui->descProjFrame->layout()->setContentsMargins(left, 9, 9, 9); - ui->dataFrame->setStyleSheet(style); - - if (view->Group) { - ui->groupButton->setChecked(view->GroupOpen); - if (!view->GroupOpen) - count = QString::number(view->GroupItemCount); - continueIcon = ":/images/continue_regular.svg"; - } - ui->groupButton->setText(count); - ui->continueButton->setIcon(QIcon(continueIcon)); - ui->groupFrame->setVisible(view->Group); -} - -void TimeEntryCellWidget::setEllipsisTextToLabel(ClickableLabel *label, QString text) -{ - QFontMetrics metrix(label->font()); - int width = label->width() - 2; - QString clippedText = metrix.elidedText(text, Qt::ElideRight, width); - label->setText(clippedText); -} - -void TimeEntryCellWidget::labelClicked(QString field_name) { - if (group) { - on_groupButton_clicked(); - return; - } - TogglApi::instance->editTimeEntry(guid, field_name); -} - -TimeEntryCellWidget::~TimeEntryCellWidget() { - delete ui; -} - -QSize TimeEntryCellWidget::getSizeHint(bool is_header) { - if (is_header) { - return QSize(minimumWidth(), sizeHint().height()); - } - return QSize(minimumWidth(), ui->dataFrame->height()); -} - -void TimeEntryCellWidget::on_continueButton_clicked() { - TogglApi::instance->continueTimeEntry(guid); -} - -QString TimeEntryCellWidget::getProjectColor(QString color) { - if (color.length() == 0) { - return QString("#9d9d9d"); - } - return color; -} - -void TimeEntryCellWidget::toggleGroup(bool open) -{ - if (group && groupOpen != open) { - groupOpen = open; - TogglApi::instance->toggleEntriesGroup(groupName); - } -} - -void TimeEntryCellWidget::on_groupButton_clicked() -{ - TogglApi::instance->toggleEntriesGroup(groupName); -} - -void TimeEntryCellWidget::resizeEvent(QResizeEvent* event) -{ - setEllipsisTextToLabel(ui->description, description); - setEllipsisTextToLabel(ui->project, project); - QWidget::resizeEvent(event); -} - -void TimeEntryCellWidget::on_loadMoreButton_clicked() -{ - TogglApi::instance->loadMore(); - ui->loadMoreButton->setEnabled(false); - ui->loadMoreButton->setText("Loading ..."); -} - -void TimeEntryCellWidget::on_dataFrame_clicked() { - if (group) - on_groupButton_clicked(); - else - TogglApi::instance->editTimeEntry(guid, "description"); -} diff --git a/src/ui/linux/TogglDesktop/timeentrycellwidget.h b/src/ui/linux/TogglDesktop/timeentrycellwidget.h deleted file mode 100644 index c348f5eeae..0000000000 --- a/src/ui/linux/TogglDesktop/timeentrycellwidget.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYCELLWIDGET_H_ -#define SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYCELLWIDGET_H_ - -#include - -#include "./timeentryview.h" -#include "./clickablelabel.h" - -namespace Ui { -class TimeEntryCellWidget; -} - -class QListWidgetItem; - -class TimeEntryCellWidget : public QWidget { - Q_OBJECT - - public: - TimeEntryCellWidget(QListWidgetItem *item); - ~TimeEntryCellWidget(); - - void display(TimeEntryView *view); - QSize getSizeHint(bool is_header); - void labelClicked(QString field_name); - void setLoadMore(bool load_more); - - QString entryGuid(); - void toggleGroup(bool open); - - public slots: - void deleteTimeEntry(); - - protected: - virtual bool eventFilter(QObject *watched, QEvent *event) override; - virtual void focusInEvent(QFocusEvent *event) override; - virtual void resizeEvent(QResizeEvent *) override; - - private slots: // NOLINT - void on_continueButton_clicked(); - void on_groupButton_clicked(); - void on_loadMoreButton_clicked(); - void on_dataFrame_clicked(); - - private: - Ui::TimeEntryCellWidget *ui; - QListWidgetItem *item; - - QString description; - QString project; - QString guid; - bool group; - bool groupOpen; - QString groupName; - TimeEntryView *timeEntry; - QString getProjectColor(QString color); - - void setupGroupedMode(TimeEntryView *view); - void setEllipsisTextToLabel(ClickableLabel *label, QString text); -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYCELLWIDGET_H_ diff --git a/src/ui/linux/TogglDesktop/timeentrycellwidget.ui b/src/ui/linux/TogglDesktop/timeentrycellwidget.ui deleted file mode 100644 index cdaead0451..0000000000 --- a/src/ui/linux/TogglDesktop/timeentrycellwidget.ui +++ /dev/null @@ -1,484 +0,0 @@ - - - TimeEntryCellWidget - - - - 0 - 0 - 372 - 138 - - - - - 0 - 77 - - - - - 16777215 - 138 - - - - Form - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - - - - Thu 22. Jan - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 40 - 20 - - - - - - - - 2 h 12 min - - - - - - - - - - - 0 - 77 - - - - - 16777215 - 77 - - - - Qt::TabFocus - - - - - - false - - - - 0 - - - QLayout::SetNoConstraint - - - 11 - - - 0 - - - 11 - - - 0 - - - - - - 12 - 12 - - - - - 12 - 12 - - - - Time Entry has not been synced to the server - - - - - - - - - :/windows/TogglDesktop/TogglDesktop/Resources/warning-icon.png - - - true - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - 0 - 30 - - - - - 90 - 30 - - - - Load more - - - false - - - false - - - false - - - - - - - - 0 - 0 - - - - - - - - QLayout::SetMinimumSize - - - 0 - - - - - - - - Blogpost about this - - - true - - - - - - - - - - NEW - TOGGL - - - true - - - - - - - - - - - 46 - 42 - - - - - 46 - 42 - - - - - - - - 5 - - - 7 - - - 5 - - - 7 - - - 5 - - - - - - 0 - 0 - - - - - 32 - 32 - - - - - 32 - 32 - - - - Qt::NoFocus - - - - - - - - - - 32 - 32 - - - - true - - - true - - - - - - - - - - - 0 - 77 - - - - - 137 - 77 - - - - - - - - 0 - - - QLayout::SetFixedSize - - - 0 - - - 0 - - - - - - 23 - 31 - - - - - 23 - 31 - - - - - - - - - - :/images/icon-tags.png - - - - - - - - 17 - 31 - - - - - 17 - 31 - - - - - - - - - - :/images/icon-billable.png - - - - - - - - 32 - 32 - - - - - 32 - 32 - - - - Qt::NoFocus - - - - - - - - - - :/images/continue_regular.svg:/images/continue_regular.svg - - - - 32 - 32 - - - - true - - - - - - - - 65 - 0 - - - - - 90 - 16777215 - - - - - - - 00:00:00 - - - true - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - - ClickableLabel - QLabel -
clickablelabel.h
-
-
- - - - -
diff --git a/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp b/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp deleted file mode 100644 index 2cc99116da..0000000000 --- a/src/ui/linux/TogglDesktop/timeentryeditorwidget.cpp +++ /dev/null @@ -1,664 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#include "./timeentryeditorwidget.h" -#include "./ui_timeentryeditorwidget.h" - -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT - -#include "./autocompletelistview.h" -#include "./autocompletelistmodel.h" -#include "./autocompleteview.h" -#include "./genericview.h" -#include "./timeentryview.h" -#include "./toggl.h" - -TimeEntryEditorWidget::TimeEntryEditorWidget(QStackedWidget *parent) : QWidget(parent), -ui(new Ui::TimeEntryEditorWidget), -guid(""), -timeEntryAutocompleteNeedsUpdate(false), -projectAutocompleteNeedsUpdate(false), -workspaceSelectNeedsUpdate(false), -clientSelectNeedsUpdate(false), -colorPicker(new ColorPicker(this)), -timer(new QTimer(this)), -duration(0), -previousTagList(""), -descriptionModel(new AutocompleteListModel(this, QVector())), -projectModel(new AutocompleteListModel(this, QVector(), AutocompleteView::AC_PROJECT)) -{ - ui->setupUi(this); - - ui->description->setModel(descriptionModel); - ui->project->setModel(projectModel); - - ui->description->installEventFilter(this); - ui->project->installEventFilter(this); - - connect(ui->description, &AutocompleteComboBox::returnPressed, [this]() { - focusNextChild(); - }); - connect(ui->project, &AutocompleteComboBox::returnPressed, [this]() { - focusNextChild(); - }); - - connect(ui->newProjectWorkspace, QOverload::of(&QComboBox::activated), [this](int) { - focusNextChild(); - }); - connect(ui->newProjectClient, QOverload::of(&QComboBox::activated), [this](int) { - focusNextChild(); - }); - - toggleNewClientMode(false); - - connect(TogglApi::instance, SIGNAL(displayLogin(bool,uint64_t)), // NOLINT - this, SLOT(displayLogin(bool,uint64_t))); // NOLINT - - connect(TogglApi::instance, SIGNAL(aboutToDisplayTimeEntryList()), // NOLINT - this, SLOT(aboutToDisplayTimeEntryList())); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayTimeEntryEditor(bool,TimeEntryView*,QString)), // NOLINT - this, SLOT(displayTimeEntryEditor(bool,TimeEntryView*,QString))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayTags(QVector)), // NOLINT - this, SLOT(displayTags(QVector))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayWorkspaceSelect(QVector)), // NOLINT - this, SLOT(displayWorkspaceSelect(QVector))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayProjectAutocomplete(QVector)), // NOLINT - this, SLOT(displayProjectAutocomplete(QVector))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayTimeEntryAutocomplete(QVector)), // NOLINT - this, SLOT(displayTimeEntryAutocomplete(QVector))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayClientSelect(QVector)), // NOLINT - this, SLOT(displayClientSelect(QVector))); // NOLINT - - connect(TogglApi::instance, SIGNAL(setProjectColors(QVector)), // NOLINT - this, SLOT(setProjectColors(QVector))); // NOLINT - - connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); - - TogglApi::instance->getProjectColors(); -} - -TimeEntryEditorWidget::~TimeEntryEditorWidget() { - delete ui; -} - -void TimeEntryEditorWidget::setSelectedColor(QString color) { - QString style = "font-size:72px;" + color; - ui->colorButton->setStyleSheet(style); -} - -void TimeEntryEditorWidget::display() { - qobject_cast(parent())->setCurrentWidget(this); -} - -void TimeEntryEditorWidget::deleteTimeEntry() { - if (timeEntry->confirmlessDelete() || QMessageBox::Ok == QMessageBox( - QMessageBox::Question, - "Delete this time entry?", - "Deleted time entries cannot be restored.", - QMessageBox::Ok|QMessageBox::Cancel).exec()) { - TogglApi::instance->deleteTimeEntry(guid); - } -} - -void TimeEntryEditorWidget::clickDone() -{ - on_doneButton_clicked(); -} - -void TimeEntryEditorWidget::displayClientSelect( - QVector list) { - clientSelectUpdate = list; - clientSelectNeedsUpdate = true; - if (ui->newProjectClient->hasFocus()) { - return; - } - - bool selectingNewClient = !recentlyAddedClient.isEmpty(); - QString lastSelectedItem = ui->newProjectClient->currentText(); - uint64_t workspaceID = 0; - QVariant data = ui->newProjectWorkspace->currentData(); - if (data.canConvert()) { - GenericView *view = data.value(); - workspaceID = view->ID; - } - ui->newProjectClient->clear(); - ui->newProjectClient->addItem(""); - int index = 0; - int selectedIndex = 0; - foreach(GenericView *view, clientSelectUpdate) { - if (workspaceID && workspaceID != view->WID) { - continue; - } - ui->newProjectClient->addItem(view->Name, QVariant::fromValue(view)); - index++; - if (selectingNewClient && view->Name == recentlyAddedClient) { - selectedIndex = index; - recentlyAddedClient = QString(); - } - if (!selectingNewClient && view->Name == lastSelectedItem) { - selectedIndex = index; - } - } - ui->newProjectClient->setCurrentIndex(selectedIndex); - clientSelectNeedsUpdate = false; -} - -void TimeEntryEditorWidget::displayTimeEntryAutocomplete( - QVector list) { - timeEntryAutocompleteUpdate = list; - timeEntryAutocompleteNeedsUpdate = true; - if (ui->description->hasFocus()) { - return; - } - QString currentText = ui->description->currentText(); - ui->description->clear(); - descriptionModel->setList(list); - timeEntryAutocompleteNeedsUpdate = false; - ui->description->setEditText(currentText); -} - -void TimeEntryEditorWidget::displayProjectAutocomplete( - QVector list) { - projectAutocompleteUpdate = list; - projectAutocompleteNeedsUpdate = true; - if (ui->project->hasFocus()) { - return; - } - ui->project->clear(); - ui->project->addItem(""); - - projectModel->setList(list); - projectAutocompleteNeedsUpdate = false; -} - -void TimeEntryEditorWidget::displayWorkspaceSelect( - QVector list) { - workspaceSelectUpdate = list; - workspaceSelectNeedsUpdate = true; - if (ui->newProjectWorkspace->hasFocus()) { - return; - } - ui->newProjectWorkspace->clear(); - foreach(GenericView *view, workspaceSelectUpdate) { - ui->newProjectWorkspace->addItem(view->Name, QVariant::fromValue(view)); - } - workspaceSelectNeedsUpdate = false; -} - -void TimeEntryEditorWidget::displayLogin( - const bool open, - const uint64_t user_id) { - if (open || !user_id) { - timer->stop(); - } -} - -void TimeEntryEditorWidget::aboutToDisplayTimeEntryList() { - timer->stop(); -} - -void TimeEntryEditorWidget::displayTimeEntryEditor( - const bool open, - TimeEntryView *view, - const QString focused_field_name) { - - timeEntry = view; - - if (!ui->description->hasFocus()) { - ui->description->setEditText(view->Description); - } - if (!ui->project->hasFocus()) { - ui->project->setEditText(view->ProjectAndTaskLabel); - } - if (!ui->duration->hasFocus()) { - ui->duration->setText(view->Duration); - } - - if (open) { - // Show the dialog first, hide items later (to preserve size) - display(); - - // Reset adding new project - ui->newProject->setVisible(false); - ui->project->setVisible(true); - ui->addNewProject->setVisible(true); - ui->newProjectName->setText(""); - ui->publicProject->setChecked(false); - ui->newProjectWorkspace->setCurrentIndex(-1); - ui->newProjectClient->setCurrentIndex(-1); - ui->newProjectClient->clear(); - - // Reset adding new client - toggleNewClientMode(false); - toggleNewTagMode(false); - - if (focused_field_name == TogglApi::Duration) { - ui->duration->setFocus(); - } else if (focused_field_name == TogglApi::Description) { - ui->description->setFocus(); - } else if (focused_field_name == TogglApi::Project) { - ui->project->setFocus(); - } - } - - guid = view->GUID; - duration = view->DurationInSecondsGet(); - - if (duration < 0) { - timer->start(1000); - } - - if (!ui->start->hasFocus()) { - ui->start->setText(view->StartTimeString); - } - if (!ui->stop->hasFocus()) { - ui->stop->setText(view->EndTimeString); - } - ui->stop->setVisible(duration >= 0); - ui->timeSeparator->setVisible(duration >= 0); - if (!ui->dateEdit->hasFocus()) { - ui->dateEdit->setDateTime(QDateTime::fromTime_t(view->Started)); - } - - ui->billable->setChecked(view->Billable); - - ui->lastUpdate->setVisible(view->UpdatedAt); - ui->lastUpdate->setText(view->lastUpdate()); - - ui->billable->setVisible(view->CanSeeBillable); - if (!view->CanAddProjects) { - ui->newProject->setVisible(false); - ui->addNewProject->setVisible(false); - } - - QStringList tags = view->Tags; - tags.sort(); - previousTagList = tags.join("\t"); - - recentlyAddedClient = QString(); - - for (int i = 0; i < ui->tags->count(); i++) { - QListWidgetItem *item = ui->tags->item(i); - if (tags.contains(item->text())) { - item->setCheckState(Qt::Checked); - } else { - item->setCheckState(Qt::Unchecked); - } - } - - ui->workspaceNameLabel->setText(view->WorkspaceName); -} - -void TimeEntryEditorWidget::on_doneButton_clicked() { - if (applyNewProject()) { - TogglApi::instance->viewTimeEntryList(); - } -} - -bool TimeEntryEditorWidget::applyNewProject() { - if (!ui->newProject->isVisible()) { - return true; - } - - if (ui->newProjectName->text().isEmpty()) { - return true; - } - - QVariant workspace = ui->newProjectWorkspace->currentData(); - if (!workspace.canConvert()) { - return false; - } - uint64_t workspaceID = workspace.value()->ID; - - uint64_t clientID = 0; - QString clientGUID = ""; - QVariant client = ui->newProjectClient->currentData(); - if (client.canConvert()) { - clientID = client.value()->ID; - if (clientID == 0) - clientGUID = client.value()->GUID; - } - // Get the selected project color from stylesheet - QString colorCode = ui->colorButton->styleSheet() - .replace("font-size:72px;color:", "") - .replace(";", ""); - - QString projectGUID = TogglApi::instance->addProject(guid, - workspaceID, - clientID, - clientGUID, - ui->newProjectName->text(), - !ui->publicProject->isChecked(), - colorCode); - - ui->project->setVisible(true); - - return !projectGUID.isEmpty(); -} - -bool TimeEntryEditorWidget::eventFilter(QObject *object, QEvent *event) { - if (event->type() == QEvent::FocusOut) { - if (object == ui->description && !guid.isEmpty()) { - TogglApi::instance->setTimeEntryDescription(guid, - ui->description->currentText()); - } - - if (object == ui->project && ui->project->currentText().length() == 0) { - TogglApi::instance->setTimeEntryProject(guid, - 0, - 0, - ""); - } - } - if (event->type() == QEvent::KeyPress) { - if (object == ui->description) { - ui->deleteButton->setFocusPolicy(Qt::StrongFocus); - auto ke = static_cast(event); - if (ke && ke->key() == Qt::Key_Backtab) { - // this is an ugly hack for the focus chain - backtabbing selected the date picker for some reason - ui->deleteButton->setFocus(Qt::FocusReason::MouseFocusReason); - return true; - } - } - } - - return false; -} - -void TimeEntryEditorWidget::on_deleteButton_clicked() { - deleteTimeEntry(); -} - -void TimeEntryEditorWidget::on_addNewProject_clicked() { - bool hasMultipleWorkspaces = ui->newProjectWorkspace->count() > 1; - ui->newProjectWorkspace->setVisible(hasMultipleWorkspaces); - ui->newProjectWorkspaceLabel->setVisible(hasMultipleWorkspaces); - - ui->newProject->setVisible(true); - ui->addNewProject->setVisible(false); - ui->project->setVisible(false); - - if (!hasMultipleWorkspaces) { - ui->newProjectWorkspace->setCurrentIndex(0); - } else { - int i = 0; - foreach(GenericView *view, workspaceSelectUpdate) { - if(view->ID == timeEntry->WID) { - ui->newProjectWorkspace->setCurrentIndex(i); - break; - } - i++; - } - } -} - -void TimeEntryEditorWidget::on_newProjectWorkspace_currentIndexChanged( - int index) { - Q_UNUSED(index); - displayClientSelect(clientSelectUpdate); -} - -void TimeEntryEditorWidget::on_description_activated(int index) { - AutocompleteView *view = ui->description->currentView(); - if (view) { - ui->description->setEditText(view->Description); - ui->project->setFocus(); - ui->description->setFocus(); - TogglApi::instance->setTimeEntryProject(guid, - view->TaskID, - view->ProjectID, - ""); - if (view->Billable) { - TogglApi::instance->setTimeEntryBillable(guid, view->Billable); - } - - if (!view->Tags.isEmpty() && ui->tags->count() > 0) { - bool tagsSet = false; - for (int i = 0; i < ui->tags->count(); i++) { - QListWidgetItem *widgetItem = ui->tags->item(i); - if (widgetItem->checkState() == Qt::Checked) { - tagsSet = true; - break; - } - } - if (!tagsSet) { - TogglApi::instance->setTimeEntryTags(guid, view->Tags); - } - } - } -} - -void TimeEntryEditorWidget::on_project_activated(int index) { - Q_UNUSED(index); - AutocompleteView *view = ui->project->currentView(); - if (view) { - TogglApi::instance->setTimeEntryProject(guid, - view->TaskID, - view->ProjectID, - ""); - } -} - -void TimeEntryEditorWidget::on_duration_editingFinished() { - TogglApi::instance->setTimeEntryDuration(guid, - ui->duration->text()); -} - -void TimeEntryEditorWidget::on_start_editingFinished() { - TogglApi::instance->setTimeEntryStart(guid, - ui->start->text()); -} - -void TimeEntryEditorWidget::on_stop_editingFinished() { - TogglApi::instance->setTimeEntryStop(guid, - ui->stop->text()); -} - -void TimeEntryEditorWidget::on_dateEdit_editingFinished() { - TogglApi::instance->setTimeEntryDate(guid, - ui->dateEdit->dateTime().toTime_t()); -} - -void TimeEntryEditorWidget::displayTags( - QVector tags) { - ui->tags->clear(); - QStringList tagList; - foreach(GenericView *view, tags) { - tagList << view->Name; - } - - QSet actuallyAddedTags; -for (auto recentlyAddedTag : recentlyAddedTags) { - if (!recentlyAddedTag.isEmpty() && !tagList.contains(recentlyAddedTag)) { - tagList << recentlyAddedTag; - } - if (!recentlyAddedTag.isEmpty() && tagList.contains(recentlyAddedTag)) { - actuallyAddedTags.insert(recentlyAddedTag); - } - } - tagList.sort(); - recentlyAddedTags = recentlyAddedTags - actuallyAddedTags; - -for(auto tag : tagList) { - QListWidgetItem *item = new QListWidgetItem(tag, ui->tags); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - item->setCheckState(Qt::Unchecked); - } -} - -void TimeEntryEditorWidget::on_billable_clicked(bool checked) { - TogglApi::instance->setTimeEntryBillable(guid, checked); -} - -void TimeEntryEditorWidget::timeout() { - if (duration < 0 && - ui->duration->isVisible() && - !ui->duration->hasFocus()) { - ui->duration->setText( - TogglApi::formatDurationInSecondsHHMMSS(duration)); - } -} - -void TimeEntryEditorWidget::toggleNewTagMode(bool visible) { - ui->addNewTagButton->setVisible(!visible); - ui->newTagLabel->setVisible(visible); - ui->newTagButton->setVisible(visible); - ui->newTag->setVisible(visible); - if (visible) - ui->newTag->setFocus(); - else - ui->addNewTagButton->setFocus(); -} - -void TimeEntryEditorWidget::on_tags_itemClicked(QListWidgetItem *item) { - Q_UNUSED(item); - QStringList tags; - for (int i = 0; i < ui->tags->count(); i++) { - QListWidgetItem *widgetItem = ui->tags->item(i); - if (widgetItem->checkState() == Qt::Checked) { - tags.push_back(widgetItem->text()); - } - } - - tags.sort(); - QString list = tags.join("\t"); - - if (previousTagList != list) { - TogglApi::instance->setTimeEntryTags(guid, list); - } -} - -void TimeEntryEditorWidget::toggleNewClientMode(const bool visible) { - // First hide stuff, to avoid expanding - ui->cancelNewClient->setVisible(false); - ui->addNewClient->setVisible(false); - ui->newProjectClient->setVisible(false); - ui->newClientName->setVisible(false); - ui->addClientButton->setVisible(false); - - // No display whats needed - ui->cancelNewClient->setVisible(visible); - ui->addNewClient->setVisible(!visible); - ui->newProjectClient->setVisible(!visible); - ui->newClientName->setVisible(visible); - ui->addClientButton->setVisible(visible); - - ui->newProjectClient->setFocus(); - ui->newClientName->setFocus(); - - ui->newClientName->setText(""); -} - -void TimeEntryEditorWidget::on_addNewClient_clicked() { - toggleNewClientMode(true); -} - -void TimeEntryEditorWidget::on_addClientButton_clicked() { - QString name = ui->newClientName->text(); - if (name.isEmpty()) { - ui->newClientName->setFocus(); - return; - } - QVariant workspace = ui->newProjectWorkspace->currentData(); - if (!workspace.canConvert()) { - ui->newProjectWorkspace->setFocus(); - return; - } - uint64_t wid = workspace.value()->ID; - if (!wid) { - ui->newProjectWorkspace->setFocus(); - return; - } - recentlyAddedClient = name; - QString clientGUID = TogglApi::instance->createClient(wid, name); - if (clientGUID.isEmpty()) { - return; - } - toggleNewClientMode(false); -} - -void TimeEntryEditorWidget::on_cancelNewClient_clicked() { - toggleNewClientMode(false); -} - -void TimeEntryEditorWidget::on_colorButton_clicked() -{ - int newX = window()->pos().x() + window()->width() - colorPicker->width() + 5; - int newY = window()->pos().y() + ui->newProject->pos().y()+80; - - colorPicker->move(newX, newY); - colorPicker->show(); -} - -void TimeEntryEditorWidget::on_newTagButton_clicked() { - QStringList tags; - QStringList allTags; - QString newTag = ui->newTag->text(); - - if (!newTag.isEmpty()) { - ui->newTag->clear(); - for (int i = 0; i < ui->tags->count(); i++) { - QListWidgetItem *widgetItem = ui->tags->item(i); - if (widgetItem->text() == newTag) { - if (widgetItem->checkState() != Qt::Checked) { - widgetItem->setCheckState(Qt::Checked); - on_tags_itemClicked(widgetItem); - } - return; - } - allTags << widgetItem->text(); - if (widgetItem->checkState() == Qt::Checked) { - tags.push_back(widgetItem->text()); - } - } - tags.push_back(newTag); - tags.sort(); - allTags.push_back(newTag); - allTags.sort(); - - QString list = tags.join("\t"); - if (previousTagList != list) { - TogglApi::instance->setTimeEntryTags(guid, list); - recentlyAddedTags.insert(newTag); - } - previousTagList = list; - - ui->tags->clear(); - for (int i = 0; i < allTags.count(); i++) { - auto item = new QListWidgetItem(allTags[i], ui->tags); - item->setFlags(item->flags() | Qt::ItemIsUserCheckable); - if (tags.contains(allTags[i])) - item->setCheckState(Qt::Checked); - else - item->setCheckState(Qt::Unchecked); - ui->tags->addItem(item); - } - } - - QTimer::singleShot(0, [this]() { - toggleNewTagMode(false); - }); -} - -void TimeEntryEditorWidget::on_newTag_returnPressed() { - on_newTagButton_clicked(); -} - -void TimeEntryEditorWidget::on_addNewTagButton_clicked() { - toggleNewTagMode(true); -} - -void TimeEntryEditorWidget::setProjectColors(QVector list) { - colorPicker->setColors(list); -} - diff --git a/src/ui/linux/TogglDesktop/timeentryeditorwidget.h b/src/ui/linux/TogglDesktop/timeentryeditorwidget.h deleted file mode 100644 index 3259d0f771..0000000000 --- a/src/ui/linux/TogglDesktop/timeentryeditorwidget.h +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYEDITORWIDGET_H_ -#define SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYEDITORWIDGET_H_ - -#include -#include -#include -#include -#include - -#include -#include "./colorpicker.h" - -namespace Ui { -class TimeEntryEditorWidget; -} - -class AutocompleteView; -class AutocompleteListModel; -class GenericView; -class TimeEntryView; - -class TimeEntryEditorWidget : public QWidget { - Q_OBJECT - - public: - explicit TimeEntryEditorWidget(QStackedWidget *parent = nullptr); - ~TimeEntryEditorWidget(); - void setSelectedColor(QString color); - - void display(); - - public slots: - void deleteTimeEntry(); - void clickDone(); - - private: - Ui::TimeEntryEditorWidget *ui; - - QString guid; - - QVector timeEntryAutocompleteUpdate; - bool timeEntryAutocompleteNeedsUpdate; - - QVector projectAutocompleteUpdate; - bool projectAutocompleteNeedsUpdate; - - QVector workspaceSelectUpdate; - bool workspaceSelectNeedsUpdate; - - QVector clientSelectUpdate; - bool clientSelectNeedsUpdate; - QString recentlyAddedClient; - - ColorPicker *colorPicker; - - QTimer *timer; - - int64_t duration; - - QString previousTagList; - QSet recentlyAddedTags; - - TimeEntryView *timeEntry; - - AutocompleteListModel *descriptionModel; - AutocompleteListModel *projectModel; - - bool applyNewProject(); - bool eventFilter(QObject *object, QEvent *event); - void toggleNewClientMode(const bool visible); - void toggleNewTagMode(bool visible); - - private slots: // NOLINT - void displayLogin( - const bool open, - const uint64_t user_id); - - void aboutToDisplayTimeEntryList(); - - void displayTimeEntryEditor( - const bool open, - TimeEntryView *view, - const QString focused_field_name); - - void displayTags( - QVector list); - - void displayWorkspaceSelect( - QVector list); - - void displayClientSelect( - QVector list); - - void displayTimeEntryAutocomplete( - QVector list); - - void displayProjectAutocomplete( - QVector list); - - void setProjectColors(QVector list); - - void timeout(); - - void on_doneButton_clicked(); - void on_deleteButton_clicked(); - void on_addNewProject_clicked(); - void on_newProjectWorkspace_currentIndexChanged(int index); - void on_description_activated(int index); - void on_project_activated(int index); - void on_duration_editingFinished(); - void on_start_editingFinished(); - void on_stop_editingFinished(); - void on_dateEdit_editingFinished(); - void on_billable_clicked(bool checked); - void on_tags_itemClicked(QListWidgetItem *item); - void on_addNewClient_clicked(); - void on_addClientButton_clicked(); - void on_cancelNewClient_clicked(); - void on_colorButton_clicked(); - void on_newTagButton_clicked(); - void on_newTag_returnPressed(); - void on_addNewTagButton_clicked(); -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYEDITORWIDGET_H_ diff --git a/src/ui/linux/TogglDesktop/timeentryeditorwidget.ui b/src/ui/linux/TogglDesktop/timeentryeditorwidget.ui deleted file mode 100644 index b567a8b2c1..0000000000 --- a/src/ui/linux/TogglDesktop/timeentryeditorwidget.ui +++ /dev/null @@ -1,779 +0,0 @@ - - - TimeEntryEditorWidget - - - - 0 - 0 - 387 - 674 - - - - Form - - - font-size:13px; - - - - 10 - - - 10 - - - 5 - - - - - - 0 - 0 - - - - combobox-popup: 0; - - - true - - - - - - - - - - - 0 - 0 - - - - combobox-popup: 0; - - - true - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - padding:1;text-align:right;color:palette(link);text-decoration:underline - - - Add new project - - - true - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 30 - - - - - 5 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Select project color - - - Project name - - - - - - - - 23 - 23 - - - - - -1 - - - - false - - - font-size:72px;color:#06aaf5; - - - - - - - - - - - - - Public (Visible to the whole team) - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - - - - - 99 - 0 - - - - Workspace: - - - - - - - Client: - - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - - - - - - - Add - - - - - - - - - - font-size:12px; - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - padding:1px;text-align:right;color:palette(link);text-decoration:underline - - - Add new client - - - true - - - - - - - padding:1px;text-align:right;color:palette(link);text-decoration:underline - - - Cancel - - - true - - - - - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 6 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Duration: - - - - - - - 00:03:05 - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Start-end time: - - - - - - - - - - 0 - 0 - - - - 08:57 PM - - - - - - - - 18 - 0 - - - - - - - - Qt::AlignCenter - - - - - - - - 0 - 0 - - - - 09:01 PM - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Date: - - - - - - - true - - - - - - - - 0 - 0 - - - - Tags: - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - - - New Tag: - - - - - - - - 0 - 0 - - - - - - - - - 0 - 0 - - - - Add - - - - - - - padding:1px;text-align:right;color:palette(link);text-decoration:underline - - - Add new tag - - - true - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - QSizePolicy::Fixed - - - - 105 - 20 - - - - - - - - - - - Billable - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 99 - 0 - - - - - 99 - 16777215 - - - - false - - - - - - Workspace: - - - - - - - - - - - - - - - - - - - - - 10 - - - 0 - - - 0 - - - 0 - - - - - Done - - - - - - - Delete - - - - - - - Qt::Horizontal - - - - 1 - 20 - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - -1 - - - - Last update: 2014.06.19 at 21:12:34 - - - - - - - - - - - AutocompleteComboBox - QComboBox -
autocompletecombobox.h
-
-
- - description - project - addNewProject - newProjectName - colorButton - publicProject - newProjectWorkspace - newProjectClient - newClientName - addClientButton - addNewClient - cancelNewClient - duration - start - stop - dateEdit - tags - newTag - newTagButton - addNewTagButton - billable - doneButton - deleteButton - - - -
diff --git a/src/ui/linux/TogglDesktop/timeentrylistwidget.cpp b/src/ui/linux/TogglDesktop/timeentrylistwidget.cpp deleted file mode 100644 index 66fe1f4b6b..0000000000 --- a/src/ui/linux/TogglDesktop/timeentrylistwidget.cpp +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#include "./timeentrylistwidget.h" -#include "./ui_timeentrylistwidget.h" - -#include "./toggl.h" -#include "./timerwidget.h" -#include "./timeentrycellwidget.h" - -TimeEntryListWidget::TimeEntryListWidget(QStackedWidget *parent) : QWidget(parent), -ui(new Ui::TimeEntryListWidget) { - ui->setupUi(this); - - connect(ui->list, &QListWidget::currentRowChanged, [=](int row) { - qCritical() << row; - }); - - connect(TogglApi::instance, SIGNAL(displayLogin(bool,uint64_t)), // NOLINT - this, SLOT(displayLogin(bool,uint64_t))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayTimeEntryList(bool,QVector,bool)), // NOLINT - this, SLOT(displayTimeEntryList(bool,QVector,bool))); // NOLINT - - ui->blankView->setVisible(false); -} - -TimeEntryListWidget::~TimeEntryListWidget() { - delete ui; -} - -void TimeEntryListWidget::display() { - qobject_cast(parent())->setCurrentWidget(this); -} - -TimeEntryCellWidget *TimeEntryListWidget::highlightedCell() { - auto w = focusWidget(); - while (w) { - auto cell = qobject_cast(w); - if (cell) - return cell; - w = w->parentWidget(); - } - return nullptr; -} - -TimerWidget *TimeEntryListWidget::timer() { - return ui->timer; -} - -void TimeEntryListWidget::displayLogin( - const bool open, - const uint64_t user_id) { - - if (open || !user_id) { - ui->list->clear(); - } -} - -void TimeEntryListWidget::displayTimeEntryList( - const bool open, - QVector list, - const bool show_load_more_button) { - - int size = list.size(); - if (open) { - display(); - } - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - render_m_.lock(); - - for (int i = 0; i < size; i++) { - TimeEntryView *te = list.at(i); - - QListWidgetItem *item = nullptr; - TimeEntryCellWidget *cell = nullptr; - - if (ui->list->count() > i) { - item = ui->list->item(i); - cell = static_cast( - ui->list->itemWidget(item)); - } - - if (!item) { - item = new QListWidgetItem(); - cell = new TimeEntryCellWidget(item); - - ui->list->addItem(item); - ui->list->setItemWidget(item, cell); - } - - cell->display(te); - - QSize sizeHint = cell->getSizeHint(te->IsHeader); - item->setSizeHint(sizeHint); - } - - if (show_load_more_button) { - showLoadMoreButton(size); - size++; - } - - while (ui->list->count() > size) { - ui->list->model()->removeRow(size); - } - - ui->list->setVisible(!list.isEmpty()); - ui->blankView->setVisible(list.isEmpty()); - - render_m_.unlock(); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); -} - -void TimeEntryListWidget::showLoadMoreButton(int size) { - QListWidgetItem *item = nullptr; - TimeEntryCellWidget *cell = nullptr; - - if (ui->list->count() > size) { - item = ui->list->item(size); - cell = static_cast( - ui->list->itemWidget(item)); - } - - if (!item) { - item = new QListWidgetItem(); - cell = new TimeEntryCellWidget(item); - - ui->list->addItem(item); - ui->list->setItemWidget(item, cell); - } - - cell->setLoadMore(true); - - QSize sizeHint = cell->getSizeHint(false); - item->setSizeHint(sizeHint); -} - -void TimeEntryListWidget::on_blankView_linkActivated(const QString &link) { - Q_UNUSED(link); - TogglApi::instance->openInBrowser(); -} diff --git a/src/ui/linux/TogglDesktop/timeentrylistwidget.h b/src/ui/linux/TogglDesktop/timeentrylistwidget.h deleted file mode 100644 index e7ca4ae9c6..0000000000 --- a/src/ui/linux/TogglDesktop/timeentrylistwidget.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYLISTWIDGET_H_ -#define SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYLISTWIDGET_H_ - -#include -#include -#include -#include - -#include - -#include "./timeentryview.h" - -namespace Ui { -class TimeEntryListWidget; -} - -class TimeEntryCellWidget; -class TimerWidget; - -class TimeEntryListWidget : public QWidget { - Q_OBJECT - - public: - explicit TimeEntryListWidget(QStackedWidget *parent = nullptr); - ~TimeEntryListWidget(); - - void display(); - - TimeEntryCellWidget *highlightedCell(); - TimerWidget *timer(); - - private slots: // NOLINT - - void displayLogin( - const bool open, - const uint64_t user_id); - - void displayTimeEntryList( - const bool open, - QVector list, - const bool show_load_more_button); - - void showLoadMoreButton(int size); - - void on_blankView_linkActivated(const QString &link); - - private: - Ui::TimeEntryListWidget *ui; - - QMutex render_m_; -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYLISTWIDGET_H_ diff --git a/src/ui/linux/TogglDesktop/timeentrylistwidget.ui b/src/ui/linux/TogglDesktop/timeentrylistwidget.ui deleted file mode 100644 index ae0f16575a..0000000000 --- a/src/ui/linux/TogglDesktop/timeentrylistwidget.ui +++ /dev/null @@ -1,98 +0,0 @@ - - - TimeEntryListWidget - - - - 0 - 0 - 400 - 300 - - - - Form - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 56 - - - - - 16777215 - 56 - - - - - - - - Welcome back! Your previous entries are available in the web under <a href="#reports">Reports</a> - - - Qt::RichText - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - 10 - - - - - - - Qt::NoFocus - - - false - - - QListWidget { background-color:palette(window); } - - - Qt::ScrollBarAsNeeded - - - QAbstractScrollArea::AdjustToContents - - - - - - - - TimerWidget - QWidget -
timerwidget.h
- 1 -
-
- - -
diff --git a/src/ui/linux/TogglDesktop/timerwidget.cpp b/src/ui/linux/TogglDesktop/timerwidget.cpp deleted file mode 100644 index 2e21c18d0e..0000000000 --- a/src/ui/linux/TogglDesktop/timerwidget.cpp +++ /dev/null @@ -1,399 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#include "./timerwidget.h" -#include "./ui_timerwidget.h" - -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT -#include // NOLINT - -#include "./autocompletelistmodel.h" -#include "./autocompleteview.h" -#include "./timeentryview.h" -#include "./toggl.h" - -TimerWidget::TimerWidget(QWidget *parent) : QFrame(parent), -ui(new Ui::TimerWidget), -timer(new QTimer(this)), -duration(0), -projectName(""), -taskName(""), -tagsHolder(""), -timeEntryAutocompleteNeedsUpdate(false), -descriptionModel(new AutocompleteListModel(this)), -timeEntry(nullptr), -selectedTaskId(0), -selectedProjectId(0) { - ui->setupUi(this); - - ui->start->installEventFilter(this); - - connect(TogglApi::instance, SIGNAL(displayStoppedTimerState()), - this, SLOT(displayStoppedTimerState())); - - connect(TogglApi::instance, SIGNAL(displayRunningTimerState(TimeEntryView*)), // NOLINT - this, SLOT(displayRunningTimerState(TimeEntryView*))); // NOLINT - - connect(TogglApi::instance, SIGNAL(displayMinitimerAutocomplete(QVector)), // NOLINT - this, SLOT(displayMinitimerAutocomplete(QVector))); // NOLINT - - connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), - this, SLOT(focusChanged(QWidget*, QWidget*))); - - connect(timer, SIGNAL(timeout()), this, SLOT(timeout())); - - connect(ui->description, SIGNAL(returnPressed()), - this, SLOT(descriptionReturnPressed())); - connect(ui->description, SIGNAL(projectSelected(QString,uint64_t,QString,QString,uint64_t)), - this, SLOT(descriptionProjectSelected(QString,uint64_t,QString,QString,uint64_t))); - connect(ui->description, SIGNAL(billableChanged(bool)), - this, SLOT(descriptionBillableChanged(bool))); - connect(ui->description, SIGNAL(tagsChanged(QString)), - this, SLOT(descriptionTagsChanged(QString))); - connect(ui->description, &QComboBox::editTextChanged, - this, &TimerWidget::updateCoverLabel); - - connect(ui->deleteProject, &QPushButton::clicked, this, &TimerWidget::clearProject); - connect(ui->deleteTask, &QPushButton::clicked, this, &TimerWidget::clearTask); - - ui->description->setModel(descriptionModel); - ui->taskFrame->setVisible(false); - ui->projectFrame->setVisible(false); - - ui->billable->setVisible(false); - ui->tags->setVisible(false); - - descriptionPlaceholder = "What are you doing?"; - tagsHolder = ""; -} - -TimerWidget::~TimerWidget() { - timer->stop(); - - delete ui; -} - -QString TimerWidget::currentEntryGuid() { - return guid; -} - -void TimerWidget::deleteTimeEntry() { - if (guid.isEmpty()) - return; - - if (timeEntry->confirmlessDelete() || QMessageBox::Ok == QMessageBox( - QMessageBox::Question, - "Delete this time entry?", - "Deleted time entries cannot be restored.", - QMessageBox::Ok|QMessageBox::Cancel).exec()) { - TogglApi::instance->deleteTimeEntry(guid); - } -} - -void TimerWidget::descriptionReturnPressed() { - start(); -} - -void TimerWidget::descriptionProjectSelected(const QString &projectName, uint64_t projectId, const QString &color, const QString &taskName, uint64_t taskId) { - selectedProjectId = projectId; - selectedTaskId = taskId; - if (projectId && !projectName.isEmpty()) { - ui->projectFrame->setVisible(true); - ui->project->setText(QString("%2").arg(color).arg(projectName)); - if (!taskId) - clearTask(); - if (taskId && !taskName.isEmpty()) { - ui->taskFrame->setVisible(true); - ui->task->setText(QString("%1").arg(taskName)); - } - } -} - -void TimerWidget::descriptionBillableChanged(bool billable) { - ui->billable->setVisible(billable); -} - -void TimerWidget::descriptionTagsChanged(const QString &tags) { - ui->tags->setVisible(!tags.isEmpty()); - ui->tags->setToolTip("

" + tags + "

"); - tagsHolder = tags; -} - -void TimerWidget::clearProject() { - selectedProjectId = 0; - if (guid.isEmpty()) { - ui->project->clear(); - ui->projectFrame->setVisible(false); - } - // else branch API call happens in clearTask - clearTask(); -} - -void TimerWidget::clearTask() { - selectedTaskId = 0; - if (guid.isEmpty()) { - ui->task->clear(); - ui->taskFrame->setVisible(false); - } - else { - TogglApi::instance->setTimeEntryProject(guid, - selectedTaskId, - selectedProjectId, - ""); - } -} - -void TimerWidget::updateCoverLabel(const QString &text) { - QFont font; - font.setPixelSize(14); - QFontMetrics metrics(font); - - ui->descriptionCover->setText(metrics.elidedText(text, Qt::ElideRight, ui->descriptionCover->width() - 2)); -} - -void TimerWidget::focusChanged(QWidget *old, QWidget *now) { - if (old == ui->description) { - if (ui->description->currentText().length() == 0) { - ui->description->setEditText(descriptionPlaceholder); - } - if (timeEntryAutocompleteNeedsUpdate) { - displayMinitimerAutocomplete(timeEntryAutocompleteUpdate); - } - } - if (now == ui->description && - ui->description->currentText() == descriptionPlaceholder) { - ui->description->setEditText(""); - } -} - -void TimerWidget::displayRunningTimerState( - TimeEntryView *te) { - guid = te->GUID; - timeEntry = te; - selectedTaskId = te->TID; - selectedProjectId = te->PID; - - ui->description->setVisible(false); - ui->descriptionCover->setVisible(true); - - ui->start->setText("Stop"); - ui->start->setStyleSheet( - "background-color: #e20000; color:'white'; font-weight: bold; border: none;"); - - QString description = (te->Description.length() > 0) ? - te->Description : "(no description)"; - - updateCoverLabel(description); - ui->description->setEditText(description); - ui->description->setEnabled(false); - - ui->duration->setText(te->Duration); - ui->duration->setEnabled(false); - ui->duration->setToolTip( - QString("

Started: " + - te->StartTimeString+"

")); - - if (!te->ProjectLabel.isEmpty()) { - ui->projectFrame->setVisible(true); - ui->deleteProject->setVisible(true); - projectName = te->ProjectLabel; - if (!te->ClientLabel.isEmpty()) - projectName += ". " + te->ClientLabel; - setEllipsisTextToLabel(ui->project, projectName); - } - else { - ui->deleteProject->setVisible(true); - ui->projectFrame->setVisible(false); - } - if (!te->TaskLabel.isEmpty()) { - ui->taskFrame->setVisible(true); - ui->deleteTask->setVisible(true); - taskName = te->TaskLabel; - setEllipsisTextToLabel(ui->task, te->TaskLabel); - } - else { - ui->deleteTask->setVisible(true); - ui->taskFrame->setVisible(false); - } - - ui->billable->setVisible(te->Billable); - ui->tags->setVisible(!te->Tags.isEmpty()); - ui->tags->setToolTip(QString("

" + - te->Tags.join(", ") + "

")); - - duration = te->DurationInSecondsGet(); - - if (te->Description.length() > 0) { - ui->description->setToolTip( - QString("

" + - te->Description + "

")); - } - if (te->ProjectAndTaskLabel.length() > 0) { - ui->project->setToolTip( - QString("

" + - te->ProjectAndTaskLabel+"

")); - } else { - ui->project->setToolTip(QString("")); - } - - disconnect(this, SLOT(start())); - disconnect(this, SLOT(stop())); - - connect(this, SIGNAL(buttonClicked()), this, SLOT(stop())); - - timer->start(1000); -} - -void TimerWidget::displayStoppedTimerState() { - if (ui->description->hasFocus()) - return; - - guid = QString(); - selectedTaskId = 0; - selectedProjectId = 0; - - ui->descriptionCover->setVisible(false); - ui->description->setVisible(true); - - ui->start->setText("Start"); - ui->start->setStyleSheet( - "background-color: #47bc00; color:'white'; font-weight: bold; border: none;"); - - if (!ui->description->hasFocus()) { - ui->description->setEditText(descriptionPlaceholder); - - ui->project->setText(""); - ui->project->setToolTip(QString("")); - ui->deleteProject->setVisible(true); - ui->deleteTask->setVisible(true); - ui->projectFrame->setVisible(false); - ui->taskFrame->setVisible(false); - - ui->billable->setVisible(false); - ui->tags->setVisible(false); - - tagsHolder = ""; - } - - ui->description->setEnabled(true); - ui->description->setToolTip(QString("")); - - if (!ui->duration->hasFocus()) { - ui->duration->setText(""); - } - ui->duration->setEnabled(true); - ui->duration->setToolTip(QString("")); - - duration = 0; - - disconnect(this, SLOT(start())); - disconnect(this, SLOT(stop())); - - connect(this, SIGNAL(buttonClicked()), this, SLOT(start())); - - timer->stop(); -} - -void TimerWidget::on_start_clicked() { - emit buttonClicked(); -} - -void TimerWidget::start() { - /* - QVariant data = ui->description->currentData(); - if (data.canConvert()) { - AutocompleteView *view = data.value(); - task_id = view->TaskID; - project_id = view->ProjectID; - } - */ - - QString description = ui->description->currentText(); - if (description == descriptionPlaceholder) { - description = ""; - } - - TogglApi::instance->start(description, - ui->duration->text(), - selectedTaskId, - selectedProjectId, - tagsHolder.toStdString().c_str(), - ui->billable->isVisible()); - tagsHolder = ""; - - ui->start->setFocus(); -} - -void TimerWidget::stop() { - TogglApi::instance->stop(); -} - -void TimerWidget::displayMinitimerAutocomplete( - QVector list) { - timeEntryAutocompleteUpdate = list; - timeEntryAutocompleteNeedsUpdate = true; - if (ui->description->hasFocus()) { - return; - } - QString currentText = ui->description->currentText(); - ui->description->clear(); - descriptionModel->setList(list); - timeEntryAutocompleteNeedsUpdate = false; - ui->description->setEditText(currentText); -} - -void TimerWidget::timeout() { - if (!isVisible()) { - return; - } - if (duration >= 0) { - return; - } - ui->duration->setText(TogglApi::formatDurationInSecondsHHMMSS(duration)); -} - -void TimerWidget::mousePressEvent(QMouseEvent *event) { - TogglApi::instance->editRunningTimeEntry(""); - QWidget::mousePressEvent(event); -} - -void TimerWidget::on_duration_returnPressed() { - start(); -} - -void TimerWidget::resizeEvent(QResizeEvent* event) -{ - setEllipsisTextToLabel(ui->project, projectName); - setEllipsisTextToLabel(ui->task, taskName); - QWidget::resizeEvent(event); -} - -bool TimerWidget::eventFilter(QObject *obj, QEvent *event) { - if (obj == ui->start && event->type() == QEvent::KeyPress) { - auto keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Return: - case Qt::Key_Enter: - case Qt::Key_Space: - if (keyEvent->modifiers() & (Qt::CTRL)) { - TogglApi::instance->editRunningTimeEntry(""); - return true; - } - return false; - default: - return QFrame::eventFilter(obj, event); - } - } - return QFrame::eventFilter(obj, event); -} - -void TimerWidget::setEllipsisTextToLabel(QLabel *label, QString text) -{ - updateCoverLabel(ui->description->currentText()); - QFontMetrics metrix(label->font()); - int width = label->width() - 4; - QString clippedText = metrix.elidedText(text, Qt::ElideRight, width); - label->setText(clippedText); -} diff --git a/src/ui/linux/TogglDesktop/timerwidget.h b/src/ui/linux/TogglDesktop/timerwidget.h deleted file mode 100644 index 7f76869da1..0000000000 --- a/src/ui/linux/TogglDesktop/timerwidget.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2014 Toggl Desktop developers. - -#ifndef SRC_UI_LINUX_TOGGLDESKTOP_TIMERWIDGET_H_ -#define SRC_UI_LINUX_TOGGLDESKTOP_TIMERWIDGET_H_ - -#include -#include -#include -#include -#include - -namespace Ui { -class TimerWidget; -} - -class AutocompleteListModel; -class AutocompleteView; -class TimeEntryView; - -class TimerWidget : public QFrame { - Q_OBJECT - - public: - explicit TimerWidget(QWidget *parent = 0); - ~TimerWidget(); - - QString currentEntryGuid(); - - public slots: - void deleteTimeEntry(); - - signals: - void buttonClicked(); - - protected: - void mousePressEvent(QMouseEvent *event) override; - void resizeEvent(QResizeEvent *) override; - bool eventFilter(QObject *obj, QEvent *event); - - private slots: // NOLINT - void displayStoppedTimerState(); - - void displayRunningTimerState( - TimeEntryView *te); - - void displayMinitimerAutocomplete( - QVector list); - - void start(); - void stop(); - - void timeout(); - - void focusChanged(QWidget *old, QWidget *now); - void on_start_clicked(); - - void on_duration_returnPressed(); - - void descriptionReturnPressed(); - void descriptionProjectSelected(const QString &projectName, uint64_t projectId, const QString &color, const QString &taskName, uint64_t taskId); - void descriptionBillableChanged(bool billable); - void descriptionTagsChanged(const QString &tags); - - void clearProject(); - void clearTask(); - - void updateCoverLabel(const QString &text); - - private: - Ui::TimerWidget *ui; - - QTimer *timer; - - int64_t duration; - - QString projectName; - QString taskName; - QString descriptionPlaceholder; - QString tagsHolder; - - bool timeEntryAutocompleteNeedsUpdate; - QVector timeEntryAutocompleteUpdate; - AutocompleteListModel *descriptionModel; - - TimeEntryView *timeEntry; - uint64_t selectedTaskId; - uint64_t selectedProjectId; - - QString guid; - - void setEllipsisTextToLabel(QLabel *label, QString text); -}; - -#endif // SRC_UI_LINUX_TOGGLDESKTOP_TIMERWIDGET_H_ diff --git a/src/ui/linux/TogglDesktop/timerwidget.ui b/src/ui/linux/TogglDesktop/timerwidget.ui deleted file mode 100644 index 166c8dcf00..0000000000 --- a/src/ui/linux/TogglDesktop/timerwidget.ui +++ /dev/null @@ -1,476 +0,0 @@ - - - TimerWidget - - - - 0 - 0 - 282 - 60 - - - - - 0 - 0 - - - - - 0 - 60 - - - - - 282 - 104 - - - - Form - - - font-size:13px;background-color: rgb(22, 22, 22);color:palette(light); - - - - 3 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 1 - - - 3 - - - 3 - - - - - - 0 - 0 - - - - margin: 0 1px 0 0; border: 0; color: rgb(200, 200, 200); combobox-popup: 0; font-size: 14px; - - - TextLabel - - - - - - - - 0 - 0 - - - - - 25 - 25 - - - - - -1 - - - - AutocompleteComboBox { margin: 1px; font-size: 14px; color: rgb(200, 200, 200); border: 0; } AutocompleteComboBox::down-arrow { margin: -3px; background-color: rgb(22, 22, 22); image: url(:/images/down_arrow.png); } - - - true - - - - - - true - - - - - - - - 0 - 0 - - - - - 0 - 16 - - - - border: 0; - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 16 - 16 - - - - - 16 - 16 - - - - color: rgb(190, 190, 190);line-height:40px;position:absolute;bottom:5px;padding:0; - - - × - - - - - - - - 0 - 0 - - - - - 0 - 16 - - - - - 16777215 - 25 - - - - color: rgb(190, 190, 190);line-height:40px;position:absolute;bottom:5px;font-size:8pt; - - - NEW TOGGL - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - 2 - - - - - - - - - - - 0 - 0 - - - - - 0 - 16 - - - - border: 0; - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 16 - 16 - - - - - 16 - 16 - - - - color: rgb(190, 190, 190);line-height:40px;position:absolute;bottom:5px;padding:0; - - - × - - - - - - - - 0 - 0 - - - - - 0 - 16 - - - - - 16777215 - 25 - - - - color: rgb(190, 190, 190);line-height:40px;position:absolute;bottom:5px;font-size:8pt; - - - NEW TOGGL - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - 2 - - - - - - - - - - - - - - 13 - 31 - - - - - 13 - 31 - - - - border:none; - - - - - - :/images/icon-tags.png - - - - - - - - 0 - 0 - - - - - 13 - 31 - - - - - 13 - 31 - - - - border:none; - - - - - - :/images/icon-billable.png - - - - - - - - 0 - 0 - - - - - 50 - 0 - - - - - 16777215 - 16777215 - - - - - -1 - - - - color: rgb(200, 200, 200); - - - false - - - Qt::AlignCenter - - - 00:00:00 - - - - - - - - 0 - 0 - - - - - 60 - 0 - - - - - 60 - 16777215 - - - - background-color: rgb(69, 185, 0); color: rgb(255, 255, 255);border:none; - - - Start - - - true - - - - - - - - AutocompleteComboBox - QComboBox -
autocompletecombobox.h
-
-
- - - - -
diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index 47923c18a2..de88dcacd5 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -13,7 +13,6 @@ #include "./toggl_api.h" #include "./autocompletelistmodel.h" -#include "./autocompletecombobox.h" #include "./qmlobjectlist.h" class AutocompleteView; From 35119b1cdf58abc28278805ebbbf9b66a7918ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 12:45:58 +0100 Subject: [PATCH 46/83] Fix QML C++ bindings (linux) --- src/ui/linux/TogglDesktop/main.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index d90a0b6ad3..b2767bcbef 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -26,6 +26,7 @@ #include "./toggl.h" #include "./timeentryview.h" #include "./countryview.h" +#include "./autocompletelistmodel.h" class TogglApplication : public SingleApplication { public: @@ -106,10 +107,13 @@ int main(int argc, char *argv[]) try { qputenv("QML_DISABLE_DISTANCEFIELD", "1"); QQmlApplicationEngine engine; - qmlRegisterType("toggl", 1, 0, "Country"); - qmlRegisterType("toggl", 1, 0, "TimeEntry"); - qmlRegisterType("toggl", 1, 0, "TimeEntryList"); - qmlRegisterType("toggl", 1, 0, "Autocomplete"); + qmlRegisterUncreatableType("toggl", 1, 0, "Country", "Created by backend code"); + qmlRegisterUncreatableType("toggl", 1, 0, "TimeEntry", "Created by backend code"); + qmlRegisterUncreatableType("toggl", 1, 0, "TimeEntryList", "Created by backend code"); + qmlRegisterUncreatableType("toggl", 1, 0, "Autocomplete", "Created by backend code"); + qmlRegisterUncreatableType("toggl", 1, 0, "AutocompleteListModel", "Created by backend code"); + qmlRegisterUncreatableType("toggl", 1, 0, "AutocompleteProxyModel", "Created by backend code"); + engine.rootContext()->setContextProperty("toggl", new TogglApi(nullptr)); engine.load(QUrl(QStringLiteral("qrc:/MainWindow.qml"))); if (!TogglApi::instance->startEvents()) { From 81970e32b09b903eb9ab26bc62a8515a6bd19b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 12:53:48 +0100 Subject: [PATCH 47/83] Format main.cpp (linux) --- src/ui/linux/TogglDesktop/main.cpp | 104 +++++++++++++---------------- 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index b2767bcbef..260bf3ee62 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -30,8 +30,9 @@ class TogglApplication : public SingleApplication { public: - TogglApplication(int &argc, char **argv) // NOLINT - : SingleApplication(argc, argv) {} + TogglApplication(int &argc, char **argv) + : SingleApplication(argc, argv) + {} virtual bool notify(QObject *receiver, QEvent *event); }; @@ -40,96 +41,87 @@ bool TogglApplication::notify(QObject *receiver, QEvent *event) { try { return SingleApplication::notify(receiver, event); } catch(std::exception &e) { - TogglApi::notifyBugsnag("std::exception", e.what(), - receiver->objectName()); + TogglApi::notifyBugsnag("std::exception", e.what(), receiver->objectName()); } catch(...) { - TogglApi::notifyBugsnag("unspecified", "exception", - receiver->objectName()); + TogglApi::notifyBugsnag("unspecified", "exception", receiver->objectName()); } return true; } -int main(int argc, char *argv[]) try { - Bugsnag::apiKey = "aa13053a88d5133b688db0f25ec103b7"; - - TogglApplication::setQuitOnLastWindowClosed(false); - - qRegisterMetaType("uint64_t"); - qRegisterMetaType("int64_t"); - qRegisterMetaType("bool_t"); - qRegisterMetaType >("QVector"); - qRegisterMetaType >("QVector"); - qRegisterMetaType >("QVector"); - - QApplication::setApplicationName("Toggl Desktop"); - QApplication::setOrganizationName("Toggl"); - - TogglApplication a(argc, argv); - if (a.isRunning()) { - qDebug() << "An instance of the app is already running"; - return 0; - } - - QtWebEngine::initialize(); - a.setApplicationVersion(APP_VERSION); - Bugsnag::app.version = APP_VERSION; - +void setOptions(const TogglApplication &a) { QCommandLineParser parser; parser.setApplicationDescription("Toggl Desktop"); parser.addHelpOption(); parser.addVersionOption(); - QCommandLineOption logPathOption( - QStringList() << "log-path", - " of the app log file", - "path"); + QCommandLineOption logPathOption("log-path", " of the app log file", "path"); parser.addOption(logPathOption); - QCommandLineOption dbPathOption( - QStringList() << "db-path", - " of the app DB file", - "path"); + QCommandLineOption dbPathOption("db-path", " of the app DB file", "path"); parser.addOption(dbPathOption); - QCommandLineOption scriptPathOption( - QStringList() << "script-path", - " of a Lua script to run", - "path"); - parser.addOption(scriptPathOption); - // A boolean option with multiple names (-b, --background) - QCommandLineOption forceOption(QStringList() << "b" << "background", - QCoreApplication::translate("main", "Start app in background.")); + QCommandLineOption forceOption({ "b", "background" }, "Start app in background."); parser.addOption(forceOption); parser.process(a); +} - qputenv("QML_DISABLE_DISTANCEFIELD", "1"); +void registerTypes() { + qRegisterMetaType("uint64_t"); + qRegisterMetaType("int64_t"); + qRegisterMetaType("bool_t"); + qRegisterMetaType >("QVector"); + qRegisterMetaType >("QVector"); + qRegisterMetaType >("QVector"); - QQmlApplicationEngine engine; qmlRegisterUncreatableType("toggl", 1, 0, "Country", "Created by backend code"); qmlRegisterUncreatableType("toggl", 1, 0, "TimeEntry", "Created by backend code"); qmlRegisterUncreatableType("toggl", 1, 0, "TimeEntryList", "Created by backend code"); qmlRegisterUncreatableType("toggl", 1, 0, "Autocomplete", "Created by backend code"); qmlRegisterUncreatableType("toggl", 1, 0, "AutocompleteListModel", "Created by backend code"); qmlRegisterUncreatableType("toggl", 1, 0, "AutocompleteProxyModel", "Created by backend code"); +} + + +int main(int argc, char *argv[]) try { + Bugsnag::apiKey = "aa13053a88d5133b688db0f25ec103b7"; + Bugsnag::app.version = APP_VERSION; + + TogglApplication::setQuitOnLastWindowClosed(false); + QApplication::setApplicationName("Toggl Desktop"); + QApplication::setOrganizationName("Toggl"); + + TogglApplication a(argc, argv); + if (a.isRunning()) { + qDebug() << "An instance of the app is already running"; + return 0; + } + a.setApplicationVersion(APP_VERSION); + setOptions(a); + + QtWebEngine::initialize(); + qputenv("QML_DISABLE_DISTANCEFIELD", "1"); + QQmlApplicationEngine engine; + registerTypes(); engine.rootContext()->setContextProperty("toggl", new TogglApi(nullptr)); engine.load(QUrl(QStringLiteral("qrc:/MainWindow.qml"))); + if (!TogglApi::instance->startEvents()) { - QMessageBox( - QMessageBox::Warning, - "Error", - "The application could not start. Please inspect the log file.", - QMessageBox::Ok|QMessageBox::Cancel).exec(); + QMessageBox(QMessageBox::Warning, + "Error", + "The application could not start. Please inspect the log file.", + QMessageBox::Ok|QMessageBox::Cancel) + .exec(); return 1; } return a.exec(); -} catch (std::exception &e) { // NOLINT +} catch (std::exception &e) { TogglApi::notifyBugsnag("std::exception", e.what(), "main"); return 1; -} catch (...) { // NOLINT +} catch (...) { TogglApi::notifyBugsnag("unspecified", "exception", "main"); return 1; } From 166c8a13ca518eec3b0a16e321d6bd0d87e6f8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 14:33:58 +0100 Subject: [PATCH 48/83] Work on improving the design and clean up the QML code (linux) --- CMakeLists.txt | 1 + src/ui/linux/TogglDesktop/CMakeLists.txt | 12 ++- src/ui/linux/TogglDesktop/MainWindow.qml | 24 +---- src/ui/linux/TogglDesktop/Resources.qrc | 1 + src/ui/linux/TogglDesktop/TimeEntryLabel.qml | 89 +++++++++++++++++++ .../TogglDesktop/TimeEntryListHeader.qml | 2 +- .../linux/TogglDesktop/TimeEntryListItem.qml | 47 ++-------- src/ui/linux/TogglDesktop/TimerView.qml | 71 +++++++-------- src/ui/linux/TogglDesktop/macosSpecific.h | 8 ++ src/ui/linux/TogglDesktop/macosSpecific.mm | 11 +++ src/ui/linux/TogglDesktop/main.cpp | 12 ++- src/ui/linux/TogglDesktop/mainwindow.cpp | 30 +++++++ src/ui/linux/TogglDesktop/mainwindow.h | 22 +++++ src/ui/linux/TogglDesktop/mainwindow.ui | 53 +++++++++++ 14 files changed, 277 insertions(+), 106 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/TimeEntryLabel.qml create mode 100644 src/ui/linux/TogglDesktop/macosSpecific.h create mode 100644 src/ui/linux/TogglDesktop/macosSpecific.mm create mode 100644 src/ui/linux/TogglDesktop/mainwindow.cpp create mode 100644 src/ui/linux/TogglDesktop/mainwindow.h create mode 100644 src/ui/linux/TogglDesktop/mainwindow.ui diff --git a/CMakeLists.txt b/CMakeLists.txt index 53884f01f9..d4a9b705c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ include(FindPkgConfig) # Look for Qt find_package(Qt5Widgets CONFIG REQUIRED) +find_package(Qt5QuickWidgets CONFIG REQUIRED) find_package(Qt5DBus CONFIG REQUIRED) find_package(Qt5Network CONFIG REQUIRED) find_package(Qt5WebEngine CONFIG REQUIRED) diff --git a/src/ui/linux/TogglDesktop/CMakeLists.txt b/src/ui/linux/TogglDesktop/CMakeLists.txt index ef4ebf6314..4aa766713e 100644 --- a/src/ui/linux/TogglDesktop/CMakeLists.txt +++ b/src/ui/linux/TogglDesktop/CMakeLists.txt @@ -21,6 +21,7 @@ set(BINARY_SOURCE_FILES genericview.cpp idlenotificationwidget.cpp main.cpp + mainwindow.cpp networkmanagement.cpp powermanagement.cpp qmlobjectlist.cc @@ -35,15 +36,22 @@ set(BINARY_SOURCE_FILES # it's better to list UI files idlenotificationwidget.ui + mainwindow.ui ) +if (UNIX AND NOT LINUX) + set(PLATFORM_SPECIFIC_FILES + macosSpecific.mm + ) +endif() + # Set up compilation targets -add_executable(TogglDesktop ${BINARY_SOURCE_FILES}) +add_executable(TogglDesktop ${BINARY_SOURCE_FILES} ${PLATFORM_SPECIFIC_FILES}) # And list the dependencies between them (and system libraries) target_link_libraries(TogglDesktop PRIVATE TogglDesktopLibrary - Qt5::Widgets Qt5::DBus Qt5::WebEngine Qt5::Qml Qt5::Quick + Qt5::Widgets Qt5::DBus Qt5::WebEngine Qt5::Qml Qt5::Quick Qt5::QuickWidgets OAuth2 ${QXT_LIBRARIES} Bugsnag diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index 90c5d54a0c..a0bde3cca4 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -4,14 +4,11 @@ import QtQuick.Layouts 1.12 import QtQuick.Controls 1.4 -ApplicationWindow { +Rectangle { id: window visible: true - minimumWidth: 400 width: 400 - minimumHeight: 640 height: 640 - title: "Toggl" function mixColors(a, b, ratio) { return Qt.rgba( @@ -24,9 +21,10 @@ ApplicationWindow { SystemPalette { id: palette - property bool isDark: (itemShadow.r + itemShadow.g + itemShadow.b) < 300 + property bool isDark: (itemShadow.r + itemShadow.g + itemShadow.b) < 1 - property color itemShadow: palette.shadow + property int itemShadowSize: palette.isDark ? 1 : 9 + property color itemShadow: mixColors(palette.shadow, palette.listBackground, 0.2) property color listBackground: mixColors(palette.base, palette.alternateBase, 0.8) } SystemPalette { @@ -34,20 +32,6 @@ ApplicationWindow { colorGroup: SystemPalette.Disabled } - menuBar: MenuBar { - Menu { - title: "Toggl Desktop" - MenuItem { - text: "Log out" - onTriggered: toggl.logout() - } - MenuItem { - text: "Exit" - onTriggered: Qt.quit() - } - } - } - Rectangle { anchors.fill: parent color: palette.base diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index 3da2f132ff..25259b6caa 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -38,5 +38,6 @@ TimeEntryListItem.qml TogglShadowBox.qml TimeEntryListHeader.qml + TimeEntryLabel.qml diff --git a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml new file mode 100644 index 0000000000..ec83678891 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml @@ -0,0 +1,89 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 + +ColumnLayout { + Layout.fillHeight: true + Layout.fillWidth: true + + property QtObject timeEntry: null + + Text { + Layout.fillWidth: true + text: timeEntry.Description.length > 0 ? timeEntry.Description : "+ Add description" + color: timeEntry.Description.length > 0 ? palette.text : disabledPalette.text + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + font.pixelSize: 12 + verticalAlignment: Text.AlignVCenter + } + RowLayout { + Layout.fillWidth: true + Rectangle { + height: 3 + width: height + radius: height / 2 + visible: timeEntry.ClientLabel.length > 0 + color: timeEntry.Color + Layout.alignment: Qt.AlignVCenter + } + Text { + visible: timeEntry.ClientLabel.length > 0 + text: timeEntry.ClientLabel + color: timeEntry.Color.length > 0 ? timeEntry.Color : palette.text + font.pixelSize: 12 + } + Text { + text: timeEntry.ProjectLabel.length > 0 ? timeEntry.ProjectLabel : "+ Add project" + color: timeEntry.ProjectLabel.length > 0 ? palette.text : disabledPalette.text + font.pixelSize: 12 + } + Text { + visible: timeEntry.TaskLabel.length > 0 + text: timeEntry.TaskLabel + color: palette.text + font.pixelSize: 12 + } + Item { + Layout.fillWidth: true + } + } +} + +/* +Text { + visible: running + Layout.fillWidth: true + verticalAlignment: Text.AlignVCenter + text: runningTimeEntry && runningTimeEntry.Description.length > 0 ? " " + runningTimeEntry.Description : " (no description)" + color: palette.text + font.pixelSize: 12 +} +RowLayout { + visible: runningTimeEntry + Rectangle { + antialiasing: true + visible: runningTimeEntry && runningTimeEntry.ProjectLabel.length > 0 + height: 8 + width: height + radius: height / 2 + color: runningTimeEntry && runningTimeEntry.Color.length > 0 ? runningTimeEntry.Color : palette.text + } + Text { + visible: runningTimeEntry && runningTimeEntry.ProjectLabel.length > 0 + text: runningTimeEntry ? runningTimeEntry.ProjectLabel : "" + color: runningTimeEntry && runningTimeEntry.Color.length > 0 ? runningTimeEntry.Color : palette.text + font.pixelSize: 11 + } + Text { + visible: runningTimeEntry && runningTimeEntry.TaskLabel.length > 0 + text: runningTimeEntry && runningTimeEntry.TaskLabel.length ? "- " + runningTimeEntry.TaskLabel : "" + color: palette.text + font.pixelSize: 11 + } + Text { + //visible: runningTimeEntry && runningTimeEntry.ClientLabel.length > 0 + text: runningTimeEntry ? "• " + runningTimeEntry.ClientLabel : "" + color: palette.text + font.pixelSize: 11 + } +} +*/ diff --git a/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml b/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml index becfa96154..30e9f2dc02 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml @@ -16,7 +16,7 @@ Item { TogglShadowBox { anchors.fill: parent - shadowWidth: 9 + shadowWidth: palette.itemShadowSize shadowColor: palette.itemShadow backgroundColor: palette.listBackground sides: TogglShadowBox.Side.Left | TogglShadowBox.Side.Right | TogglShadowBox.Side.Top diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index d47913d24c..3351c67f2a 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -16,7 +16,7 @@ Item { TogglShadowBox { anchors.fill: parent - shadowWidth: 9 + shadowWidth: palette.itemShadowSize shadowColor: palette.itemShadow backgroundColor: palette.listBackground sides: TogglShadowBox.Side.Left | TogglShadowBox.Side.Right | TogglShadowBox.Side.Bottom @@ -64,49 +64,14 @@ Item { text: modelData.GroupItemCount } } - ColumnLayout { + + TimeEntryLabel { Layout.fillHeight: true Layout.fillWidth: true - Text { - Layout.fillWidth: true - text: modelData.Description.length > 0 ? modelData.Description : "+ Add description" - color: modelData.Description.length > 0 ? palette.text : disabledPalette.text - wrapMode: Text.WrapAtWordBoundaryOrAnywhere - font.pixelSize: 12 - verticalAlignment: Text.AlignVCenter - } - RowLayout { - Layout.fillWidth: true - Rectangle { - height: 3 - width: height - radius: height / 2 - visible: modelData.ClientLabel.length > 0 - color: modelData.Color - Layout.alignment: Qt.AlignVCenter - } - Text { - visible: modelData.ClientLabel.length > 0 - text: modelData.ClientLabel - color: modelData.Color.length > 0 ? modelData.Color : palette.text - font.pixelSize: 12 - } - Text { - text: modelData.ProjectLabel.length > 0 ? modelData.ProjectLabel : "+ Add project" - color: modelData.ProjectLabel.length > 0 ? palette.text : disabledPalette.text - font.pixelSize: 12 - } - Text { - visible: modelData.TaskLabel.length > 0 - text: modelData.TaskLabel - color: palette.text - font.pixelSize: 12 - } - Item { - Layout.fillWidth: true - } - } + + timeEntry: modelData } + Text { Layout.alignment: Qt.AlignVCenter text: modelData.Duration diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 5c208187e6..3fde33d61d 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -4,7 +4,7 @@ import QtQuick.Controls 2.12 Rectangle { id: root - color: mixColors(palette.window, palette.base, 0.9) + color: palette.base height: 64 property bool running: false @@ -44,21 +44,18 @@ Rectangle { RowLayout { id: timerContainer - x: 6 + x: 12 + clip: true height: parent.height - width: parent.width - 12 + width: parent.width - 24 + ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true - spacing: 0 - Text { - visible: running - Layout.fillWidth: true + spacing: 6 + + Item { Layout.fillHeight: true - verticalAlignment: Text.AlignVCenter - text: runningTimeEntry && runningTimeEntry.Description.length > 0 ? " " + runningTimeEntry.Description : " (no description)" - color: "white" - font.pixelSize: 12 } TextField { id: description @@ -110,36 +107,15 @@ Rectangle { z: -1 } } - RowLayout { - visible: runningTimeEntry && runningTimeEntry.ProjectLabel.length > 0 - Button { - implicitWidth: 12 - implicitHeight: 12 - text: "x" - } - Text { - text: runningTimeEntry ? runningTimeEntry.ProjectLabel : "" - color: runningTimeEntry && runningTimeEntry.Color.length > 0 ? runningTimeEntry.Color : "white" - font.pixelSize: 8 - } - Text { - visible: runningTimeEntry && runningTimeEntry.TaskLabel.length > 0 - text: runningTimeEntry && runningTimeEntry.TaskLabel.length ? "• " + runningTimeEntry.TaskLabel : "" - color: "white" - } + TimeEntryLabel { + Layout.fillHeight: true + Layout.fillWidth: true + timeEntry: runningTimeEntry + visible: runningTimeEntry } - RowLayout { - visible: runningTimeEntry && runningTimeEntry.ClientLabel.length > 0 - Button { - implicitWidth: 12 - implicitHeight: 12 - text: "x" - } - Text { - text: runningTimeEntry ? runningTimeEntry.ClientLabel : "" - color: "white" - font.pixelSize: 8 - } + + Item { + Layout.fillHeight: true } } Text { @@ -173,4 +149,19 @@ Rectangle { } } } + + Rectangle { + anchors { + left: parent.left + right: parent.right + top: parent.bottom + } + height: 12 + + gradient: Gradient { + orientation: Gradient.Vertical + GradientStop { position: 0.0; color: "#33888888" } + GradientStop { position: 1.0; color: "#00888888" } + } + } } diff --git a/src/ui/linux/TogglDesktop/macosSpecific.h b/src/ui/linux/TogglDesktop/macosSpecific.h new file mode 100644 index 0000000000..104e9fb4a7 --- /dev/null +++ b/src/ui/linux/TogglDesktop/macosSpecific.h @@ -0,0 +1,8 @@ +#ifndef SRC_MACOS_SPECIFIC_H_ +#define SRC_MACOS_SPECIFIC_H_ + + +void changeTitleBarColor(WId view, double r, double g, double b); + + +#endif SRC_MACOS_SPECIFIC_H diff --git a/src/ui/linux/TogglDesktop/macosSpecific.mm b/src/ui/linux/TogglDesktop/macosSpecific.mm new file mode 100644 index 0000000000..37fdedefe5 --- /dev/null +++ b/src/ui/linux/TogglDesktop/macosSpecific.mm @@ -0,0 +1,11 @@ +#include +#include + +#include + +void changeTitleBarColor(WId id, double r, double g, double b) { + NSView *view = (NSView*) id; + NSWindow* window = [view window]; + window.titlebarAppearsTransparent = YES; + window.backgroundColor = [NSColor colorWithRed:r green:g blue:b alpha:1.]; +} diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index 260bf3ee62..d5cd2282b9 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -28,6 +28,8 @@ #include "./countryview.h" #include "./autocompletelistmodel.h" +#include "mainwindow.h" + class TogglApplication : public SingleApplication { public: TogglApplication(int &argc, char **argv) @@ -88,6 +90,8 @@ int main(int argc, char *argv[]) try { Bugsnag::apiKey = "aa13053a88d5133b688db0f25ec103b7"; Bugsnag::app.version = APP_VERSION; + qputenv("QML_DISABLE_DISTANCEFIELD", "1"); + TogglApplication::setQuitOnLastWindowClosed(false); QApplication::setApplicationName("Toggl Desktop"); QApplication::setOrganizationName("Toggl"); @@ -101,12 +105,16 @@ int main(int argc, char *argv[]) try { setOptions(a); QtWebEngine::initialize(); + registerTypes(); - qputenv("QML_DISABLE_DISTANCEFIELD", "1"); + /* QQmlApplicationEngine engine; - registerTypes(); engine.rootContext()->setContextProperty("toggl", new TogglApi(nullptr)); engine.load(QUrl(QStringLiteral("qrc:/MainWindow.qml"))); + */ + + auto w = new MainWindow(); + w->show(); if (!TogglApi::instance->startEvents()) { QMessageBox(QMessageBox::Warning, diff --git a/src/ui/linux/TogglDesktop/mainwindow.cpp b/src/ui/linux/TogglDesktop/mainwindow.cpp new file mode 100644 index 0000000000..bd8ade9229 --- /dev/null +++ b/src/ui/linux/TogglDesktop/mainwindow.cpp @@ -0,0 +1,30 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include + +#include + +#ifdef __APPLE__ +#include "macosSpecific.h" +#endif + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + ui->centralwidget->engine()->rootContext()->setContextProperty("toggl", new TogglApi(nullptr)); + ui->centralwidget->setSource(QUrl("qrc:/MainWindow.qml")); +#ifdef __APPLE__ + auto color = QPalette().color(QPalette::Base); + changeTitleBarColor(effectiveWinId(), color.redF(), color.greenF(), color.blueF()); +#endif +} + +MainWindow::~MainWindow() +{ + delete ui; +} diff --git a/src/ui/linux/TogglDesktop/mainwindow.h b/src/ui/linux/TogglDesktop/mainwindow.h new file mode 100644 index 0000000000..935344132e --- /dev/null +++ b/src/ui/linux/TogglDesktop/mainwindow.h @@ -0,0 +1,22 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; +}; + +#endif // MAINWINDOW_H diff --git a/src/ui/linux/TogglDesktop/mainwindow.ui b/src/ui/linux/TogglDesktop/mainwindow.ui new file mode 100644 index 0000000000..41d3e791ff --- /dev/null +++ b/src/ui/linux/TogglDesktop/mainwindow.ui @@ -0,0 +1,53 @@ + + + MainWindow + + + + 0 + 0 + 360 + 480 + + + + + 0 + 0 + + + + + 240 + 64 + + + + Toggl + + + + QQuickWidget::SizeRootObjectToView + + + + + + 0 + 0 + 360 + 22 + + + + + + + QQuickWidget + QWidget +
QtQuickWidgets/QQuickWidget
+
+
+ + +
From 6c27fa58f336242c2d47d305566ab4b61966b396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 14:50:52 +0100 Subject: [PATCH 49/83] Fix type errors (linux) --- src/ui/linux/TogglDesktop/TimeEntryLabel.qml | 24 +++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml index ec83678891..1652d6e631 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml @@ -8,9 +8,10 @@ ColumnLayout { property QtObject timeEntry: null Text { + visible: timeEntry Layout.fillWidth: true - text: timeEntry.Description.length > 0 ? timeEntry.Description : "+ Add description" - color: timeEntry.Description.length > 0 ? palette.text : disabledPalette.text + text: timeEntry && timeEntry.Description.length > 0 ? timeEntry.Description : "+ Add description" + color: timeEntry && timeEntry.Description.length > 0 ? palette.text : disabledPalette.text wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pixelSize: 12 verticalAlignment: Text.AlignVCenter @@ -18,27 +19,28 @@ ColumnLayout { RowLayout { Layout.fillWidth: true Rectangle { + visible: timeEntry && timeEntry.ClientLabel.length > 0 height: 3 width: height radius: height / 2 - visible: timeEntry.ClientLabel.length > 0 - color: timeEntry.Color + color: timeEntry ? timeEntry.Color : palette.text Layout.alignment: Qt.AlignVCenter } Text { - visible: timeEntry.ClientLabel.length > 0 - text: timeEntry.ClientLabel - color: timeEntry.Color.length > 0 ? timeEntry.Color : palette.text + visible: timeEntry && timeEntry.ClientLabel.length > 0 + text: timeEntry ? timeEntry.ClientLabel : "" + color: timeEntry && timeEntry.Color.length > 0 ? timeEntry.Color : palette.text font.pixelSize: 12 } Text { - text: timeEntry.ProjectLabel.length > 0 ? timeEntry.ProjectLabel : "+ Add project" - color: timeEntry.ProjectLabel.length > 0 ? palette.text : disabledPalette.text + visible: timeEntry + text: timeEntry && timeEntry.ProjectLabel.length > 0 ? timeEntry.ProjectLabel : "+ Add project" + color: timeEntry && timeEntry.ProjectLabel.length > 0 ? palette.text : disabledPalette.text font.pixelSize: 12 } Text { - visible: timeEntry.TaskLabel.length > 0 - text: timeEntry.TaskLabel + visible: timeEntry && timeEntry.TaskLabel.length > 0 + text: timeEntry ? timeEntry.TaskLabel : "" color: palette.text font.pixelSize: 12 } From 840cf073d230d9158e4fc4c7f6c59d4f3dcd7bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 15:30:12 +0100 Subject: [PATCH 50/83] Make the time entry labels look more like on macOS (linux) --- src/ui/linux/TogglDesktop/TimeEntryLabel.qml | 60 ++++---------------- 1 file changed, 10 insertions(+), 50 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml index 1652d6e631..57aabd8d79 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml @@ -19,28 +19,28 @@ ColumnLayout { RowLayout { Layout.fillWidth: true Rectangle { - visible: timeEntry && timeEntry.ClientLabel.length > 0 - height: 3 + visible: timeEntry && timeEntry.ProjectLabel.length > 0 + height: 8 width: height radius: height / 2 color: timeEntry ? timeEntry.Color : palette.text Layout.alignment: Qt.AlignVCenter } - Text { - visible: timeEntry && timeEntry.ClientLabel.length > 0 - text: timeEntry ? timeEntry.ClientLabel : "" - color: timeEntry && timeEntry.Color.length > 0 ? timeEntry.Color : palette.text - font.pixelSize: 12 - } Text { visible: timeEntry text: timeEntry && timeEntry.ProjectLabel.length > 0 ? timeEntry.ProjectLabel : "+ Add project" - color: timeEntry && timeEntry.ProjectLabel.length > 0 ? palette.text : disabledPalette.text + color: timeEntry && timeEntry.ProjectLabel.length > 0 ? (timeEntry.Color.length ? timeEntry.Color : palette.text) : disabledPalette.text font.pixelSize: 12 } Text { visible: timeEntry && timeEntry.TaskLabel.length > 0 - text: timeEntry ? timeEntry.TaskLabel : "" + text: timeEntry && timeEntry.TaskLabel.length ? "- " + timeEntry.TaskLabel : "" + color: palette.text + font.pixelSize: 12 + } + Text { + visible: timeEntry && timeEntry.ClientLabel.length > 0 + text: timeEntry && timeEntry.ClientLabel ? "• " + timeEntry.ClientLabel : "" color: palette.text font.pixelSize: 12 } @@ -49,43 +49,3 @@ ColumnLayout { } } } - -/* -Text { - visible: running - Layout.fillWidth: true - verticalAlignment: Text.AlignVCenter - text: runningTimeEntry && runningTimeEntry.Description.length > 0 ? " " + runningTimeEntry.Description : " (no description)" - color: palette.text - font.pixelSize: 12 -} -RowLayout { - visible: runningTimeEntry - Rectangle { - antialiasing: true - visible: runningTimeEntry && runningTimeEntry.ProjectLabel.length > 0 - height: 8 - width: height - radius: height / 2 - color: runningTimeEntry && runningTimeEntry.Color.length > 0 ? runningTimeEntry.Color : palette.text - } - Text { - visible: runningTimeEntry && runningTimeEntry.ProjectLabel.length > 0 - text: runningTimeEntry ? runningTimeEntry.ProjectLabel : "" - color: runningTimeEntry && runningTimeEntry.Color.length > 0 ? runningTimeEntry.Color : palette.text - font.pixelSize: 11 - } - Text { - visible: runningTimeEntry && runningTimeEntry.TaskLabel.length > 0 - text: runningTimeEntry && runningTimeEntry.TaskLabel.length ? "- " + runningTimeEntry.TaskLabel : "" - color: palette.text - font.pixelSize: 11 - } - Text { - //visible: runningTimeEntry && runningTimeEntry.ClientLabel.length > 0 - text: runningTimeEntry ? "• " + runningTimeEntry.ClientLabel : "" - color: palette.text - font.pixelSize: 11 - } -} -*/ From 3fa2c2919c474131ee3567b8f834022d40a5a25e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 17:05:32 +0100 Subject: [PATCH 51/83] Style autocomplete to look more like on macOS (linux) --- .../linux/TogglDesktop/AutocompleteView.qml | 127 +++++++++++------- src/ui/linux/TogglDesktop/MainWindow.qml | 6 +- src/ui/linux/TogglDesktop/TimerView.qml | 27 ++-- 3 files changed, 98 insertions(+), 62 deletions(-) diff --git a/src/ui/linux/TogglDesktop/AutocompleteView.qml b/src/ui/linux/TogglDesktop/AutocompleteView.qml index fb5d5b4496..a797d19126 100644 --- a/src/ui/linux/TogglDesktop/AutocompleteView.qml +++ b/src/ui/linux/TogglDesktop/AutocompleteView.qml @@ -1,5 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 +import QtQuick.Controls 1.4 Item { id: root @@ -49,41 +50,40 @@ Item { } Rectangle { - anchors.fill: list - anchors.margins: -1 - color: palette.dark + z: -1 + anchors.fill: parent + color: setAlpha(palette.shadow, 0.5) } - ListView { - id: list + ScrollView { + id: scrollArea width: parent.width height: root.maximumHeight clip: true - model: root.model - currentIndex: -1 - onCurrentIndexChanged: { - console.log(currentIndex) - } + ListView { + id: list + model: root.model + currentIndex: -1 + onCurrentIndexChanged: { + console.log(currentIndex) + } - onCountChanged: { - var base = list.visibleChildren[0] - var listViewHeight = 0 - for (var i = 0; i < base.visibleChildren.length; i++) - listViewHeight += base.visibleChildren[i].height - list.height = Math.min(listViewHeight, root.maximumHeight) - } - highlightFollowsCurrentItem: true - highlight: Rectangle { - color: "red" - width: root.width - height: 24 - } - delegate: Rectangle { - width: root.width - height: 24 - color: ListView.isCurrentItem ? "red" : palette.base - property bool selectable: modelData.Type < 10 - Loader { + onCountChanged: { + var base = list.visibleChildren[0] + var listViewHeight = 0 + if (!base) + return + for (var i = 0; i < base.visibleChildren.length; i++) + listViewHeight += base.visibleChildren[i].height + list.height = Math.min(listViewHeight, root.maximumHeight) + } + highlightFollowsCurrentItem: true + highlight: Rectangle { + color: "red" + width: root.width + height: 24 + } + delegate: Loader { // TODO use the enum instead of magic values sourceComponent: autocompleteData.Type === 13 ? workspaceDelegate : autocompleteData.Type === 12 ? clientDelegate : @@ -98,48 +98,79 @@ Item { Component { id: workspaceDelegate - Text { - height: 24 + Rectangle { + height: 40 width: root.width - font.bold: true - horizontalAlignment: Text.AlignHCenter - color: "#9e9e9e" + color: palette.base + Text { + anchors.centerIn: parent + color: "#9e9e9e" - text: autocompleteData.Description + text: autocompleteData.Description + font.pointSize: 14 + } + Rectangle { + anchors { + left: parent.left + bottom: parent.bottom + right: parent.right + } + height: 1 + color: palette.alternateBase + } } } Component { id: clientDelegate - Text { - height: 24 - x: 9 - color: "#9e9e9e" - text: autocompleteData.Description + Rectangle { + height: 30 + width: root.width + color: palette.base + Text { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.verticalCenter: parent.verticalCenter + height: 24 + x: 9 + color: "#9e9e9e" + + text: autocompleteData.Description + } } } Component { id: headerDelegate - Text { - height: 24 - text: autocompleteData.Description - color: "#9e9e9e" + Rectangle { + height: 30 + width: root.width + color: palette.base + Text { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: autocompleteData.Description + color: "#9e9e9e" + font.pointSize: 11 + } } } Component { id: regularDelegate - Item { - height: 24 + Rectangle { + height: 30 width: root.width Text { anchors.fill: parent + anchors.left: parent.left + anchors.leftMargin: 18 verticalAlignment: Text.AlignVCenter x: 9 textFormat: Text.RichText @@ -148,8 +179,8 @@ Item { " • " + autocompleteData.ProjectLabel + "" : "" property string task: autocompleteData.TaskLabel.length > 0 ? " " + autocompleteData.TaskLabel : "" - property string client: autocompleteData.ClientLabel.length > 0 ? " " + autocompleteData.ClientLabel : "" - text: (ListView.isCurrentItem ? "HOVNO" : "") + timeEntry + project + task + client + text: (ListView.isCurrentItem ? "Iscurrent" : "") + timeEntry + project + task + font.pointSize: 12 } MouseArea { id: delegateMouse diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index a0bde3cca4..8083213b59 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -5,7 +5,7 @@ import QtQuick.Layouts 1.12 import QtQuick.Controls 1.4 Rectangle { - id: window + id: mainWindow visible: true width: 400 height: 640 @@ -19,6 +19,10 @@ Rectangle { ) } + function setAlpha(color, alpha) { + return Qt.rgba(color.r, color.g, color.b, alpha) + } + SystemPalette { id: palette property bool isDark: (itemShadow.r + itemShadow.g + itemShadow.b) < 1 diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 3fde33d61d..820d886580 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -45,7 +45,6 @@ Rectangle { RowLayout { id: timerContainer x: 12 - clip: true height: parent.height width: parent.width - 24 @@ -83,21 +82,10 @@ Rectangle { } onTextEdited: { if (focus) { - //autocomplete.visible = true + autocomplete.visible = true } } onFocusChanged: if (!focus) autocomplete.visible = false - AutocompleteView { - id: autocomplete - visible: false - anchors{ - top: parent.bottom - left: parent.left - right: parent.right - } - filter: description.text - //model: toggl.minitimerAutocomplete - } Rectangle { anchors.fill: parent anchors.topMargin: -1 @@ -164,4 +152,17 @@ Rectangle { GradientStop { position: 1.0; color: "#00888888" } } } + + AutocompleteView { + id: autocomplete + visible: false + anchors{ + top: parent.bottom + left: parent.left + right: parent.right + } + maximumHeight: mainWindow.height - parent.height + filter: description.text + model: toggl.minitimerAutocomplete + } } From 599fac4c75a131f48bfa95136a6892dc959e064f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 17:09:22 +0100 Subject: [PATCH 52/83] Keep left margin in open group items in the list (linux) --- src/ui/linux/TogglDesktop/TimeEntryListItem.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index 3351c67f2a..71efb8a7de 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -45,9 +45,11 @@ Item { anchors.fill: parent anchors.margins: 12 spacing: 9 + Rectangle { Layout.alignment: Qt.AlignVCenter - visible: modelData.Group + visible: modelData.Group | modelData.GroupOpen + opacity: modelData.GroupOpen ? 0.0 : 1.0 width: 24 height: 24 radius: 4 From b42af7af2e58274a8555932db278672d12185129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 17:11:46 +0100 Subject: [PATCH 53/83] Fix linux build (linux) --- src/ui/linux/TogglDesktop/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/linux/TogglDesktop/CMakeLists.txt b/src/ui/linux/TogglDesktop/CMakeLists.txt index 4aa766713e..1b946b18f2 100644 --- a/src/ui/linux/TogglDesktop/CMakeLists.txt +++ b/src/ui/linux/TogglDesktop/CMakeLists.txt @@ -39,7 +39,7 @@ set(BINARY_SOURCE_FILES mainwindow.ui ) -if (UNIX AND NOT LINUX) +if (APPLE) set(PLATFORM_SPECIFIC_FILES macosSpecific.mm ) From cb8025c8afafd0fbd0574d04e829d5dc20ab6dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 3 Dec 2019 23:11:58 +0100 Subject: [PATCH 54/83] Make the Linux app compile on Windows (win) --- src/CMakeLists.txt | 2 +- src/database.cc | 2 +- src/ui/linux/TogglDesktop/autocompleteview.h | 18 +- src/ui/linux/TogglDesktop/common.h | 19 ++ src/ui/linux/TogglDesktop/countryview.h | 10 +- src/ui/linux/TogglDesktop/genericview.h | 5 +- src/ui/linux/TogglDesktop/main.cpp | 2 + src/ui/linux/TogglDesktop/settingsview.h | 11 +- src/ui/linux/TogglDesktop/timeentryview.cpp | 66 +++--- src/ui/linux/TogglDesktop/toggl.cpp | 216 +++++++++---------- src/ui/linux/TogglDesktop/toggl.h | 33 ++- third_party/CMakeLists.txt | 21 +- third_party/bugsnag-qt/bugsnag.h | 7 +- third_party/luafar/ustring.h | 4 +- third_party/qt-oauth-lib/logindialog.h | 4 +- third_party/qt-oauth-lib/oauth2.h | 12 +- 16 files changed, 242 insertions(+), 190 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 80d24823f8..0b400e5f37 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,7 +14,7 @@ if(TOGGL_ALLOW_UPDATE_CHECK) add_definitions(-DTOGGL_ALLOW_UPDATE_CHECK=1) endif() -add_definitions(-DTOGGL_BUILD_TYPE="${TOGGL_BUILD_TYPE}") +add_definitions(-DTOGGL_BUILD_TYPE="${TOGGL_BUILD_TYPE}" -DTOGGLDESKTOP_DLL_BUILD) set(TOGGLDESKTOPLIBRARY_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/src/database.cc b/src/database.cc index 4278f42869..0712c4720f 100644 --- a/src/database.cc +++ b/src/database.cc @@ -51,7 +51,7 @@ Database::Database(const std::string &db_path) session_ = new Poco::Data::Session("SQLite", db_path); { - int is_sqlite_threadsafe = sqlite3_threadsafe(); + int is_sqlite_threadsafe = Poco::Data::SQLite::Utility::isThreadSafe(); std::stringstream ss; ss << "sqlite3_threadsafe()=" << is_sqlite_threadsafe; diff --git a/src/ui/linux/TogglDesktop/autocompleteview.h b/src/ui/linux/TogglDesktop/autocompleteview.h index 394d63ffcb..7e7c457830 100644 --- a/src/ui/linux/TogglDesktop/autocompleteview.h +++ b/src/ui/linux/TogglDesktop/autocompleteview.h @@ -40,21 +40,21 @@ class AutocompleteView : public QObject { while (it) { AutocompleteView *view = new AutocompleteView(); - view->Text = QString(it->Text); - view->Description = QString(it->Description); - view->ProjectAndTaskLabel = QString(it->ProjectAndTaskLabel); - view->ProjectLabel = QString(it->ProjectLabel); - view->ClientLabel = QString(it->ClientLabel); - view->ProjectColor = QString(it->ProjectColor); + view->Text = toQString(it->Text); + view->Description = toQString(it->Description); + view->ProjectAndTaskLabel = toQString(it->ProjectAndTaskLabel); + view->ProjectLabel = toQString(it->ProjectLabel); + view->ClientLabel = toQString(it->ClientLabel); + view->ProjectColor = toQString(it->ProjectColor); view->ClientID = it->ClientID; view->TaskID = it->TaskID; - view->TaskLabel = it->TaskLabel; + view->TaskLabel = toQString(it->TaskLabel); view->ProjectID = it->ProjectID; view->WorkspaceID = it->WorkspaceID; - view->WorkspaceName = QString(it->WorkspaceName); + view->WorkspaceName = toQString(it->WorkspaceName); view->Type = AutocompleteType(it->Type); view->Billable = it->Billable; - view->Tags = QString(it->Tags); + view->Tags = toQString(it->Tags); if (!currentWorkspace || currentWorkspace->Description != view->WorkspaceName) { currentWorkspace = new AutocompleteView(); diff --git a/src/ui/linux/TogglDesktop/common.h b/src/ui/linux/TogglDesktop/common.h index d9a17ac978..5b1f4b636b 100644 --- a/src/ui/linux/TogglDesktop/common.h +++ b/src/ui/linux/TogglDesktop/common.h @@ -4,6 +4,25 @@ #define SRC_COMMON_H_ #include +#include + +#ifdef _WIN32 +inline QString toQString(const wchar_t *s) { + return QString::fromWCharArray(s); +} +inline const wchar_t *toLocalString(const QString &s) { + return s.toStdWString().c_str(); +} +#define strLiteral(x) L"" x +#else +inline QString toQString(const char_t *s) { + return QString::fromLocal8Bit(s); +} +inline const char_t *toLocalString(const QString &s) { + return s.toStdString().c_str(); +} +#define strLiteral(x) x +#endif #ifndef TOGGL_SAFE_PROPERTIES diff --git a/src/ui/linux/TogglDesktop/countryview.h b/src/ui/linux/TogglDesktop/countryview.h index b05d407e8c..b67e427008 100644 --- a/src/ui/linux/TogglDesktop/countryview.h +++ b/src/ui/linux/TogglDesktop/countryview.h @@ -21,12 +21,12 @@ class CountryView : public QObject CountryView *view = new CountryView(); view->ID = it->ID; view->VatApplicable = it->VatApplicable; - view->Text = QString(it->Name); - view->Name = QString(it->Name); + view->Text = toQString(it->Name); + view->Name = toQString(it->Name); result.push_back(view); - view->Name = QString(it->VatPercentage); - view->Name = QString(it->VatRegex); - view->Name = QString(it->Code); + view->Name = toQString(it->VatPercentage); + view->Name = toQString(it->VatRegex); + view->Name = toQString(it->Code); it = static_cast(it->Next); } return result; diff --git a/src/ui/linux/TogglDesktop/genericview.h b/src/ui/linux/TogglDesktop/genericview.h index 44b203ac38..2f36001a38 100644 --- a/src/ui/linux/TogglDesktop/genericview.h +++ b/src/ui/linux/TogglDesktop/genericview.h @@ -7,6 +7,7 @@ #include #include "./toggl_api.h" +#include "./common.h" class GenericView : public QObject { Q_OBJECT @@ -21,8 +22,8 @@ class GenericView : public QObject { GenericView *view = new GenericView(); view->ID = it->ID; view->WID = it->WID; - view->GUID = QString(it->GUID); - view->Name = QString(it->Name); + view->GUID = toQString(it->GUID); + view->Name = toQString(it->Name); result.push_back(view); it = static_cast(it->Next); } diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index d5cd2282b9..a51278da11 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -16,7 +16,9 @@ #include #include #include +#ifndef _WIN32 #include +#endif #include "singleapplication.h" // NOLINT diff --git a/src/ui/linux/TogglDesktop/settingsview.h b/src/ui/linux/TogglDesktop/settingsview.h index ee628fc25f..06a297805b 100644 --- a/src/ui/linux/TogglDesktop/settingsview.h +++ b/src/ui/linux/TogglDesktop/settingsview.h @@ -7,6 +7,7 @@ #include #include "./toggl_api.h" +#include "./common.h" class SettingsView : public QObject { Q_OBJECT @@ -18,10 +19,10 @@ class SettingsView : public QObject { SettingsView *result = new SettingsView(); result->AutodetectProxy = view->AutodetectProxy; result->UseProxy = view->UseProxy; - result->ProxyHost = QString(view->ProxyHost); + result->ProxyHost = toQString(view->ProxyHost); result->ProxyPort = view->ProxyPort; - result->ProxyUsername = QString(view->ProxyUsername); - result->ProxyPassword = QString(view->ProxyPassword); + result->ProxyUsername = toQString(view->ProxyUsername); + result->ProxyPassword = toQString(view->ProxyPassword); result->UseIdleDetection = view->UseIdleDetection; result->MenubarTimer = view->MenubarTimer; result->DockIcon = view->DockIcon; @@ -43,8 +44,8 @@ class SettingsView : public QObject { result->RemindOnFriday = view->RemindFri; result->RemindOnSaturday = view->RemindSat; result->RemindOnSunday = view->RemindSun; - result->RemindStartTime = QTime::fromString(view->RemindStarts, "HH:mm"); - result->RemindEndTime = QTime::fromString(view->RemindEnds, "HH:mm"); + result->RemindStartTime = QTime::fromString(toQString(view->RemindStarts), "HH:mm"); + result->RemindEndTime = QTime::fromString(toQString(view->RemindEnds), "HH:mm"); result->StopEntryOnShutdownSleep = view->StopEntryOnShutdownSleep; return result; } diff --git a/src/ui/linux/TogglDesktop/timeentryview.cpp b/src/ui/linux/TogglDesktop/timeentryview.cpp index 1f037197a6..27b4cbd9df 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.cpp +++ b/src/ui/linux/TogglDesktop/timeentryview.cpp @@ -51,38 +51,38 @@ bool TimeEntryView::operator!=(const TimeEntryView &o) { TimeEntryView *TimeEntryView::importOne(TogglTimeEntryView *view) { TimeEntryView *result = new TimeEntryView(); result->DurationInSeconds = view->DurationInSeconds; - result->ProjectAndTaskLabel = view->ProjectAndTaskLabel; - result->Description = QString(view->Description); - result->ProjectLabel = QString(view->ProjectLabel); - result->TaskLabel = QString(view->TaskLabel); - result->ClientLabel = QString(view->ClientLabel); + result->ProjectAndTaskLabel = toQString(view->ProjectAndTaskLabel); + result->Description = toQString(view->Description); + result->ProjectLabel = toQString(view->ProjectLabel); + result->TaskLabel = toQString(view->TaskLabel); + result->ClientLabel = toQString(view->ClientLabel); result->WID = view->WID; result->PID = view->PID; result->TID = view->TID; - result->Duration = QString(view->Duration); - result->Color = QString(view->Color); - result->GUID = QString(view->GUID); + result->Duration = toQString(view->Duration); + result->Color = toQString(view->Color); + result->GUID = toQString(view->GUID); result->Billable = view->Billable; - result->Tags = QString(view->Tags).split("\t", QString::SkipEmptyParts); + result->Tags = toQString(view->Tags).split("\t", QString::SkipEmptyParts); result->Started = view->Started; result->Ended = view->Ended; - result->StartTimeString = QString(view->StartTimeString); - result->EndTimeString = QString(view->EndTimeString); + result->StartTimeString = toQString(view->StartTimeString); + result->EndTimeString = toQString(view->EndTimeString); result->UpdatedAt = view->UpdatedAt; result->DateHeader = QString("
").arg(view->DateHeader).arg(view->DateDuration); - result->DateDuration = QString(view->DateDuration); + result->DateDuration = toQString(view->DateDuration); result->IsHeader = view->IsHeader; result->CanSeeBillable = view->CanSeeBillable; result->CanAddProjects = view->CanAddProjects; result->DefaultWID = view->DefaultWID; - result->WorkspaceName = QString(view->WorkspaceName); - result->Error = QString(view->Error); + result->WorkspaceName = toQString(view->WorkspaceName); + result->Error = toQString(view->Error); result->Unsynced = view->Unsynced; // Grouped entries mode result->Group = view->Group; result->GroupOpen = view->GroupOpen; - result->GroupName = QString(view->GroupName); - result->GroupDuration = QString(view->GroupDuration); + result->GroupName = toQString(view->GroupName); + result->GroupDuration = toQString(view->GroupDuration); result->GroupItemCount = view->GroupItemCount; return result; @@ -103,37 +103,37 @@ QVector TimeEntryView::importAll( void TimeEntryView::update(const TogglTimeEntryView *view) { DurationInSecondsSet(view->DurationInSeconds); - DescriptionSet(view->Description); - ProjectAndTaskLabelSet(view->ProjectAndTaskLabel); - ProjectLabelSet(view->ProjectLabel); - TaskLabelSet(view->TaskLabel); - ClientLabelSet(view->ClientLabel); + DescriptionSet(toQString(view->Description)); + ProjectAndTaskLabelSet(toQString(view->ProjectAndTaskLabel)); + ProjectLabelSet(toQString(view->ProjectLabel)); + TaskLabelSet(toQString(view->TaskLabel)); + ClientLabelSet(toQString(view->ClientLabel)); WIDSet(view->WID); PIDSet(view->PID); TIDSet(view->TID); - DurationSet(view->Duration); - ColorSet(view->Color); - GUIDSet(view->GUID); + DurationSet(toQString(view->Duration)); + ColorSet(toQString(view->Color)); + GUIDSet(toQString(view->GUID)); BillableSet(view->Billable); - TagsSet(QString(view->Tags).split("\t", QString::SkipEmptyParts)); + TagsSet(toQString(view->Tags).split("\t", QString::SkipEmptyParts)); StartedSet(view->Started); EndedSet(view->Ended); - StartTimeStringSet(view->StartTimeString); - EndTimeStringSet(view->EndTimeString); + StartTimeStringSet(toQString(view->StartTimeString)); + EndTimeStringSet(toQString(view->EndTimeString)); UpdatedAtSet(view->UpdatedAt); DateHeaderSet(QString("").arg(view->DateHeader).arg(view->DateDuration)); - DateDurationSet(view->DateDuration); + DateDurationSet(toQString(view->DateDuration)); IsHeaderSet(view->IsHeader); CanAddProjectsSet(view->CanAddProjects); CanSeeBillableSet(view->CanSeeBillable); DefaultWIDSet(view->DefaultWID); - WorkspaceNameSet(view->WorkspaceName); - ErrorSet(view->Error); + WorkspaceNameSet(toQString(view->WorkspaceName)); + ErrorSet(toQString(view->Error)); UnsyncedSet(view->Unsynced); GroupSet(view->Group); GroupOpenSet(view->GroupOpen); - GroupNameSet(view->GroupName); - GroupDurationSet(view->GroupDuration); + GroupNameSet(toQString(view->GroupName)); + GroupDurationSet(toQString(view->GroupDuration)); GroupItemCountSet(view->GroupItemCount); } @@ -254,7 +254,7 @@ int TimeEntryViewStorage::rowCount(const QModelIndex &parent) const { QString TimeEntryViewStorage::uid(TogglTimeEntryView *view) { if (view->Group) return QString("Group %1").arg(view->GUID); - return view->GUID; + return toQString(view->GUID); } QString TimeEntryViewStorage::uid(TimeEntryView *view) { diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index 9406813e95..07294de5b4 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -24,6 +24,7 @@ #include "./autocompleteview.h" #include "./settingsview.h" #include "./bugsnag.h" +#include "./common.h" TogglApi *TogglApi::instance = nullptr; @@ -35,15 +36,15 @@ void on_display_app(const bool_t open) { TogglApi::instance->displayApp(open); } -void on_display_update(const char *url) { - TogglApi::instance->displayUpdate(QString(url)); +void on_display_update(const char_t *url) { + TogglApi::instance->displayUpdate(toQString(url)); } void on_display_error( - const char *errmsg, + const char_t *errmsg, const bool_t user_error) { TogglApi::instance->aboutToDisplayError(); - TogglApi::instance->displayError(QString(errmsg), user_error); + TogglApi::instance->displayError(toQString(errmsg), user_error); } void on_overlay(const int64_t type) { @@ -57,8 +58,8 @@ void on_display_online_state( } void on_display_url( - const char *url) { - QDesktopServices::openUrl(QUrl(url)); + const char_t*url) { + QDesktopServices::openUrl(QUrl(toQString(url))); } void on_display_login( @@ -73,27 +74,27 @@ void on_display_login( } void on_display_pomodoro( - const char *title, - const char *informative_text) { + const char_t*title, + const char_t*informative_text) { TogglApi::instance->displayPomodoro( - QString(title), - QString(informative_text)); + toQString(title), + toQString(informative_text)); } void on_display_pomodoro_break( - const char *title, - const char *informative_text) { + const char_t*title, + const char_t*informative_text) { TogglApi::instance->displayPomodoroBreak( - QString(title), - QString(informative_text)); + toQString(title), + toQString(informative_text)); } void on_display_reminder( - const char *title, - const char *informative_text) { + const char_t*title, + const char_t*informative_text) { TogglApi::instance->displayReminder( - QString(title), - QString(informative_text)); + toQString(title), + toQString(informative_text)); } void on_display_time_entry_list( @@ -149,14 +150,14 @@ void on_display_tags( void on_display_time_entry_editor( const bool_t open, TogglTimeEntryView *te, - const char *focused_field_name) { + const char_t*focused_field_name) { if (open) { TogglApi::instance->aboutToDisplayTimeEntryEditor(); } TogglApi::instance->displayTimeEntryEditor( open, TimeEntryView::importOne(te), - QString(focused_field_name)); + toQString(focused_field_name)); } void on_display_settings( @@ -180,28 +181,28 @@ void on_display_timer_state( } void on_display_idle_notification( - const char *guid, - const char *since, - const char *duration, + const char_t*guid, + const char_t*since, + const char_t*duration, const int64_t started, - const char *description) { + const char_t*description) { TogglApi::instance->displayIdleNotification( - QString(guid), - QString(since), - QString(duration), + toQString(guid), + toQString(since), + toQString(duration), started, - QString(description)); + toQString(description)); } void on_project_colors( - char *list[], + char_t *list[], const uint64_t count) { - QVector result; + QVector result; for (uint i = 0; i < count; i++) { - char *c = list[i]; + auto c = list[i]; result.push_back(c); } TogglApi::instance->setProjectColors(result); @@ -270,8 +271,7 @@ TogglApi::TogglApi(QObject *parent, QString logPathOverride, QString dbPathOverr , uiThread_(QThread::currentThread()) { QString version = QApplication::applicationVersion(); - ctx = toggl_context_init("linux_native_app", - version.toStdString().c_str()); + ctx = toggl_context_init(strLiteral("linux_native_app"), toLocalString(version)); QString appDirPath = QStandardPaths::writableLocation( @@ -287,10 +287,10 @@ TogglApi::TogglApi(QObject *parent, QString logPathOverride, QString dbPathOverr } else { logPath = logPathOverride; } - toggl_set_log_path(logPath.toUtf8().constData()); + toggl_set_log_path(toLocalString(logPath)); qDebug() << "Log path " << logPath; - toggl_set_log_level("debug"); + toggl_set_log_level(strLiteral("debug")); QString dbPath(""); if (dbPathOverride.isEmpty()) { @@ -298,7 +298,7 @@ TogglApi::TogglApi(QObject *parent, QString logPathOverride, QString dbPathOverr } else { dbPath = dbPathOverride; } - toggl_set_db_path(ctx, dbPath.toUtf8().constData()); + toggl_set_db_path(ctx, toLocalString(dbPath)); qDebug() << "DB path " << dbPath; QString executablePath = QCoreApplication::applicationDirPath(); @@ -312,7 +312,7 @@ TogglApi::TogglApi(QObject *parent, QString logPathOverride, QString dbPathOverr cacertPath = QString("%1/cacert.pem").arg(TOGGL_DATA_DIR); } #endif // TOGGL_DATA_DIR - toggl_set_cacert_path(ctx, cacertPath.toUtf8().constData()); + toggl_set_cacert_path(ctx, toLocalString(cacertPath)); toggl_on_show_app(ctx, on_display_app); toggl_on_update(ctx, on_display_update); @@ -344,9 +344,9 @@ TogglApi::TogglApi(QObject *parent, QString logPathOverride, QString dbPathOverr timeEntries_ = new TimeEntryViewStorage(this); - char *env = toggl_environment(ctx); + auto env = toggl_environment(ctx); if (env) { - Bugsnag::releaseStage = QString(env); + Bugsnag::releaseStage = toQString(env); free(env); } @@ -425,20 +425,20 @@ void TogglApi::clear() { void TogglApi::login(const QString email, const QString password) { toggl_login_async(ctx, - email.toStdString().c_str(), - password.toStdString().c_str()); + toLocalString(email), + toLocalString(password)); } void TogglApi::signup(const QString email, const QString password, const uint64_t countryID) { toggl_signup_async(ctx, - email.toStdString().c_str(), - password.toStdString().c_str(), + toLocalString(email), + toLocalString(password), countryID); } void TogglApi::setEnvironment(const QString environment) { - toggl_set_environment(ctx, environment.toStdString().c_str()); + toggl_set_environment(ctx, toLocalString(environment)); Bugsnag::releaseStage = environment; } @@ -446,7 +446,7 @@ bool TogglApi::setTimeEntryDate( const QString guid, const int64_t unix_timestamp) { return toggl_set_time_entry_date(ctx, - guid.toStdString().c_str(), + toLocalString(guid), unix_timestamp); } @@ -454,24 +454,24 @@ bool TogglApi::setTimeEntryStart( const QString guid, const QString value) { return toggl_set_time_entry_start(ctx, - guid.toStdString().c_str(), - value.toStdString().c_str()); + toLocalString(guid), + toLocalString(value)); } bool TogglApi::setTimeEntryStop( const QString guid, const QString value) { return toggl_set_time_entry_end(ctx, - guid.toStdString().c_str(), - value.toStdString().c_str()); + toLocalString(guid), + toLocalString(value)); } void TogglApi::googleLogin(const QString accessToken) { - toggl_google_login_async(ctx, accessToken.toStdString().c_str()); + toggl_google_login_async(ctx, toLocalString(accessToken)); } void TogglApi::googleSignup(const QString &accessToken, uint64_t countryID) { - toggl_google_signup_async(ctx, accessToken.toStdString().c_str(), countryID); + toggl_google_signup_async(ctx, toLocalString(accessToken), countryID); } bool TogglApi::setProxySettings( @@ -482,24 +482,24 @@ bool TogglApi::setProxySettings( const QString proxyPassword) { return toggl_set_proxy_settings(ctx, useProxy, - proxyHost.toStdString().c_str(), + toLocalString(proxyHost), proxyPort, - proxyUsername.toStdString().c_str(), - proxyPassword.toStdString().c_str()); + toLocalString(proxyUsername), + toLocalString(proxyPassword)); } bool TogglApi::discardTimeAt(const QString guid, const uint64_t at, const bool split_into_new_time_entry) { return toggl_discard_time_at(ctx, - guid.toStdString().c_str(), + toLocalString(guid), at, split_into_new_time_entry); } bool TogglApi::discardTimeAndContinue(const QString guid, const uint64_t at) { - return toggl_discard_time_and_continue(ctx, guid.toStdString().c_str(), at); + return toggl_discard_time_and_continue(ctx, toLocalString(guid), at); } // Returns true if script file was successfully @@ -528,11 +528,11 @@ bool TogglApi::runScriptFile(const QString filename) { int64_t err(0); QString textOutput(""); - char_t *result = toggl_run_script( + auto result = toggl_run_script( ctx, - code.toUtf8().constData(), + toLocalString(code), &err); - textOutput = QString(result); + textOutput = toQString(result); free(result); if (err) { @@ -544,7 +544,7 @@ bool TogglApi::runScriptFile(const QString filename) { return !err; } -void TogglApi::setIdleSeconds(u_int64_t idleSeconds) { +void TogglApi::setIdleSeconds(uint64_t idleSeconds) { toggl_set_idle_seconds(ctx, idleSeconds); } @@ -617,7 +617,7 @@ bool TogglApi::setSettingsRemindDays( } bool TogglApi::setSettingsRemindTimes(const QTime &remind_starts, const QTime &remind_ends) { - return toggl_set_settings_remind_times(ctx, qPrintable(remind_starts.toString("HH:mm")), qPrintable(remind_ends.toString("HH:mm"))); + return toggl_set_settings_remind_times(ctx, toLocalString(remind_starts.toString("HH:mm")), toLocalString(remind_ends.toString("HH:mm"))); } void TogglApi::toggleTimelineRecording(const bool recordTimeline) { @@ -625,7 +625,7 @@ void TogglApi::toggleTimelineRecording(const bool recordTimeline) { } bool TogglApi::setUpdateChannel(const QString channel) { - return toggl_set_update_channel(ctx, channel.toStdString().c_str()); + return toggl_set_update_channel(ctx, toLocalString(channel)); } bool TogglApi::setSettingsStopEntryOnShutdown(const bool stop_entry) { @@ -637,20 +637,20 @@ void TogglApi::stopEntryOnShutdown() { } QString TogglApi::updateChannel() { - char *channel = toggl_get_update_channel(ctx); + auto channel = toggl_get_update_channel(ctx); QString res; if (channel) { - res = QString(channel); + res = toQString(channel); free(channel); } return res; } QString TogglApi::userEmail() { - char *email = toggl_get_user_email(ctx); + auto email = toggl_get_user_email(ctx); QString res; if (email) { - res = QString(email); + res = toQString(email); free(email); } return res; @@ -662,21 +662,21 @@ QString TogglApi::start(const QString description, const uint64_t project_id, const QString tags, const bool_t billable) { - char *guid = toggl_start(ctx, - description.toStdString().c_str(), - duration.toStdString().c_str(), + auto guid = toggl_start(ctx, + toLocalString(description), + toLocalString(duration), task_id, project_id, nullptr /* project guid */, - tags.toStdString().c_str() /* tags */, + toLocalString(tags) /* tags */, false); QString res(""); if (guid) { - res = QString(guid); + res = toQString(guid); free(guid); if (billable) { toggl_set_time_entry_billable(ctx, - res.toStdString().c_str(), + toLocalString(res), billable); } } @@ -689,14 +689,14 @@ bool TogglApi::stop() { const QString TogglApi::formatDurationInSecondsHHMMSS( const int64_t duration) { - char *buf = toggl_format_tracking_time_duration(duration); - QString res = QString(buf); + auto buf = toggl_format_tracking_time_duration(duration); + QString res = toQString(buf); free(buf); return res; } bool TogglApi::continueTimeEntry(const QString guid) { - return toggl_continue(ctx, guid.toStdString().c_str()); + return toggl_continue(ctx, toLocalString(guid)); } bool TogglApi::continueLatestTimeEntry() { @@ -736,15 +736,15 @@ void TogglApi::setOnline() { } void TogglApi::toggleEntriesGroup(const QString groupName) { - toggl_toggle_entries_group(ctx, groupName.toStdString().c_str()); + toggl_toggle_entries_group(ctx, toLocalString(groupName)); } void TogglApi::editTimeEntry(const QString guid, const QString focusedFieldName) { toggl_edit(ctx, - guid.toStdString().c_str(), + toLocalString(guid), false, - focusedFieldName.toStdString().c_str()); + toLocalString(focusedFieldName)); } bool TogglApi::setTimeEntryProject( @@ -753,41 +753,41 @@ bool TogglApi::setTimeEntryProject( const uint64_t project_id, const QString project_guid) { return toggl_set_time_entry_project(ctx, - guid.toStdString().c_str(), + toLocalString(guid), task_id, project_id, - project_guid.toStdString().c_str()); + toLocalString(project_guid)); } bool TogglApi::setTimeEntryDescription( const QString guid, const QString value) { return toggl_set_time_entry_description(ctx, - guid.toStdString().c_str(), - value.toStdString().c_str()); + toLocalString(guid), + toLocalString(value)); } bool TogglApi::setTimeEntryTags( const QString guid, const QString tags) { return toggl_set_time_entry_tags(ctx, - guid.toStdString().c_str(), - tags.toStdString().c_str()); + toLocalString(guid), + toLocalString(tags)); } void TogglApi::editRunningTimeEntry( const QString focusedFieldName) { toggl_edit(ctx, - "", + strLiteral(""), true, - focusedFieldName.toStdString().c_str()); + toLocalString(focusedFieldName)); } bool TogglApi::setTimeEntryBillable( const QString guid, const bool billable) { return toggl_set_time_entry_billable(ctx, - guid.toStdString().c_str(), + toLocalString(guid), billable); } @@ -800,17 +800,17 @@ QString TogglApi::addProject( const bool is_private, const QString project_color) { - char *guid = toggl_add_project(ctx, - time_entry_guid.toStdString().c_str(), + auto guid = toggl_add_project(ctx, + toLocalString(time_entry_guid), workspace_id, client_id, - client_guid.toStdString().c_str(), - project_name.toStdString().c_str(), + toLocalString(client_guid), + toLocalString(project_name), is_private, - project_color.toStdString().c_str()); + toLocalString(project_color)); QString res(""); if (guid) { - res = QString(guid); + res = toQString(guid); free(guid); } return res; @@ -819,12 +819,12 @@ QString TogglApi::addProject( QString TogglApi::createClient( const uint64_t wid, const QString name) { - char *guid = toggl_create_client(ctx, + auto guid = toggl_create_client(ctx, wid, - name.toStdString().c_str()); + toLocalString(name)); QString res(""); if (guid) { - res = QString(guid); + res = toQString(guid); free(guid); } return res; @@ -835,46 +835,46 @@ void TogglApi::viewTimeEntryList() { } bool TogglApi::deleteTimeEntry(const QString guid) { - return toggl_delete_time_entry(ctx, guid.toStdString().c_str()); + return toggl_delete_time_entry(ctx, toLocalString(guid)); } bool TogglApi::setTimeEntryDuration( const QString guid, const QString value) { return toggl_set_time_entry_duration(ctx, - guid.toStdString().c_str(), - value.toStdString().c_str()); + toLocalString(guid), + toLocalString(value)); } bool TogglApi::sendFeedback(const QString topic, const QString details, const QString filename) { return toggl_feedback_send(ctx, - topic.toStdString().c_str(), - details.toStdString().c_str(), - filename.toStdString().c_str()); + toLocalString(topic), + toLocalString(details), + toLocalString(filename)); } void TogglApi::setShowHideKey(const QString keys) { - toggl_set_key_show(ctx, keys.toStdString().c_str()); + toggl_set_key_show(ctx, toLocalString(keys)); TogglApi::instance->updateShowHideShortcut(); } void TogglApi::setContinueStopKey(const QString keys) { - toggl_set_key_start(ctx, keys.toStdString().c_str()); + toggl_set_key_start(ctx, toLocalString(keys)); TogglApi::instance->updateContinueStopShortcut(); } QString TogglApi::getShowHideKey() { - char *buf = toggl_get_key_show(ctx); - QString res = QString(buf); + auto buf = toggl_get_key_show(ctx); + QString res = toQString(buf); free(buf); return res; } QString TogglApi::getContinueStopKey() { - char *buf = toggl_get_key_start(ctx); - QString res = QString(buf); + auto buf = toggl_get_key_start(ctx); + QString res = toQString(buf); free(buf); return res; } diff --git a/src/ui/linux/TogglDesktop/toggl.h b/src/ui/linux/TogglDesktop/toggl.h index de88dcacd5..b1fcdec405 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -144,7 +144,7 @@ class TogglApi : public QObject { void viewTimeEntryList(); - void setIdleSeconds(u_int64_t idleSeconds); + void setIdleSeconds(uint64_t idleSeconds); bool setTimeEntryProject( const QString guid, @@ -342,7 +342,7 @@ class TogglApi : public QObject { void updateContinueStopShortcut(); void setProjectColors( - QVector list); + QVector list); private: void *ctx; @@ -357,28 +357,27 @@ class TogglApi : public QObject { // callbacks used internally by the app instance void on_display_app(const bool_t open); void on_display_error( - const char *errmsg, + const char_t *errmsg, const bool_t user_error); void on_overlay(const int64_t type); -void on_display_update( - const char *url); +void on_display_update(const char_t *url); void on_display_online_state( const bool is_online, - const char *reason); + const char_t *reason); void on_display_url( - const char *url); + const char_t *url); void on_display_login( const bool_t open, const uint64_t user_id); void on_display_pomodoro( - const char *title, - const char *informative_text); + const char_t *title, + const char_t *informative_text); void on_display_pomodoro_break( - const char *title, - const char *informative_text); + const char_t *title, + const char_t *informative_text); void on_display_reminder( - const char *title, - const char *informative_text); + const char_t *title, + const char_t *informative_text); void on_display_time_entry_list( const bool_t open, TogglTimeEntryView *first); @@ -397,16 +396,16 @@ void on_display_tags( void on_display_time_entry_editor( const bool_t open, TogglTimeEntryView *te, - const char *focused_field_name); + const char_t *focused_field_name); void on_display_settings( const bool_t open, TogglSettingsView *settings); void on_display_timer_state( TogglTimeEntryView *te); void on_display_idle_notification( - const char *guid, - const char *since, - const char *duration, + const char_t *guid, + const char_t *since, + const char_t *duration, const int64_t started); void on_project_colors( const char_t *list[], diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index 697f089469..a15ccbe619 100644 --- a/third_party/CMakeLists.txt +++ b/third_party/CMakeLists.txt @@ -19,6 +19,7 @@ set(OAUTH2_SOURCE_FILES qt-oauth-lib/logindialog.cpp qt-oauth-lib/oauth2.cpp ) +add_definitions(-DOAUTH2_BUILD_DLL) add_library(OAuth2 SHARED ${OAUTH2_SOURCE_FILES}) target_link_libraries(OAuth2 PRIVATE Qt5::Widgets Qt5::WebEngine Qt5::WebEngineWidgets @@ -26,6 +27,7 @@ target_link_libraries(OAuth2 PRIVATE install(TARGETS OAuth2 DESTINATION ${TOGGL_INTERNAL_LIB_DIR}) # Bugsnag has to be bundled +add_definitions(-DBUGSNAGQT_LIBRARY) set(BUGSNAG_SOURCE_FILES bugsnag-qt/bugsnag.cpp ) @@ -99,9 +101,20 @@ if(NOT LUA_FOUND) lua/src/lvm.c lua/src/lzio.c ) + if (WIN32) + set(LUA_SOURCE_FILES ${LUA_SOURCE_FILES} + luafar/ustring.c + ) + add_definitions(-DLUA_BUILD_AS_DLL) + endif() add_library(Lua SHARED ${LUA_SOURCE_FILES}) + target_include_directories(Lua PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/lua/src + ${CMAKE_CURRENT_SOURCE_DIR}/luafar + ) set(LUA_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/lua/src + ${CMAKE_CURRENT_SOURCE_DIR}/luafar PARENT_SCOPE ) set(LUA_LIBRARIES @@ -117,6 +130,7 @@ if(NOT jsoncpp_FOUND) jsoncpp/src/lib_json/json_value.cpp jsoncpp/src/lib_json/json_writer.cpp ) + add_definitions(-DJSON_DLL_BUILD) add_library(jsoncpp SHARED ${JSON_SOURCE_FILES}) target_include_directories(jsoncpp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/include @@ -169,7 +183,7 @@ if(NOT QXT_CORE_FOUND) target_link_libraries(Qxt Qt5::X11Extras ) - else() + elseif(APPLE) target_link_libraries(Qxt "-framework Foundation" "-framework AppKit" @@ -197,8 +211,9 @@ if(NOT GTEST_FOUND AND TOGGL_BUILD_TESTS) googletest-read-only googletest-read-only/include ) - target_link_libraries(gtest pthread) + target_link_libraries(gtest) set(GTEST_MAIN_SOURCE_FILES + googletest-read-only/src/gtest-all.cc googletest-read-only/src/gtest_main.cc ) add_library(gtest_main SHARED ${GTEST_MAIN_SOURCE_FILES}) @@ -206,7 +221,7 @@ if(NOT GTEST_FOUND AND TOGGL_BUILD_TESTS) googletest-read-only googletest-read-only/include ) - target_link_libraries(gtest_main pthread) + target_link_libraries(gtest_main PRIVATE) set(GTEST_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/googletest-read-only/include PARENT_SCOPE diff --git a/third_party/bugsnag-qt/bugsnag.h b/third_party/bugsnag-qt/bugsnag.h index b5ad32f22d..1d206de02e 100644 --- a/third_party/bugsnag-qt/bugsnag.h +++ b/third_party/bugsnag-qt/bugsnag.h @@ -18,10 +18,12 @@ #include #include +#ifndef _WIN32 #include +#include +#endif #include #include -#include class Notifier { public: @@ -277,7 +279,7 @@ class BUGSNAGQTSHARED_EXPORT Bugsnag : public QObject { void *buffer[64]; char **strings; int size; - +#ifdef __linux size = backtrace(buffer, 64); strings = backtrace_symbols(buffer, size); if (strings) { @@ -288,6 +290,7 @@ class BUGSNAGQTSHARED_EXPORT Bugsnag : public QObject { } free(strings); } +#endif Event event; event.context = context; diff --git a/third_party/luafar/ustring.h b/third_party/luafar/ustring.h index f47759e5b6..c3b2ce07c1 100644 --- a/third_party/luafar/ustring.h +++ b/third_party/luafar/ustring.h @@ -35,11 +35,11 @@ void PutStrToTable(lua_State *L, const char* key, const char* str); void PutWStrToArray(lua_State *L, int key, const wchar_t* str, intptr_t numchars); void PutWStrToTable(lua_State *L, const char* key, const wchar_t* str, intptr_t numchars); -wchar_t* check_utf8_string(lua_State *L, int pos, size_t* pTrgSize); +LUAMOD_API wchar_t* check_utf8_string(lua_State *L, int pos, size_t* pTrgSize); wchar_t* utf8_to_utf16(lua_State *L, int pos, size_t* pTrgSize); const wchar_t* opt_utf8_string(lua_State *L, int pos, const wchar_t* dflt); wchar_t* oem_to_utf16(lua_State *L, int pos, size_t* pTrgSize); -char* push_utf8_string(lua_State* L, const wchar_t* str, intptr_t numchars); +LUAMOD_API char* push_utf8_string(lua_State* L, const wchar_t* str, intptr_t numchars); char* push_oem_string(lua_State* L, const wchar_t* str, intptr_t numchars); void push_utf16_string(lua_State* L, const wchar_t* str, intptr_t numchars); const wchar_t* check_utf16_string(lua_State *L, int pos, size_t *len); diff --git a/third_party/qt-oauth-lib/logindialog.h b/third_party/qt-oauth-lib/logindialog.h index 93720d223b..ce1702bff4 100644 --- a/third_party/qt-oauth-lib/logindialog.h +++ b/third_party/qt-oauth-lib/logindialog.h @@ -5,11 +5,13 @@ #include #include +#include "oauth2.h" + namespace Ui { class LoginDialog; } -class LoginDialog : public QDialog +class OAUTH2_INTERNAL_EXPORT LoginDialog : public QDialog { Q_OBJECT diff --git a/third_party/qt-oauth-lib/oauth2.h b/third_party/qt-oauth-lib/oauth2.h index ca663899f0..515411b306 100644 --- a/third_party/qt-oauth-lib/oauth2.h +++ b/third_party/qt-oauth-lib/oauth2.h @@ -4,9 +4,19 @@ #include #include +#if defined(_WIN32) || defined(WIN32) +# ifdef OAUTH2_BUILD_DLL +# define OAUTH2_INTERNAL_EXPORT __declspec(dllexport) +# else +# define OAUTH2_INTERNAL_EXPORT __declspec(dllimport) +# endif +#else +# define OAUTH2_INTERNAL_EXPORT +#endif // _WIN32 || WIN32 + class LoginDialog; -class OAuth2 : public QObject +class OAUTH2_INTERNAL_EXPORT OAuth2 : public QObject { Q_OBJECT From 2f8c64661697aca9c446a44acc7d06a4284b0778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Wed, 4 Dec 2019 13:04:29 +0100 Subject: [PATCH 55/83] Use QQC1 ScrollView because it doesn't glitch on macOS (linux) --- .../linux/TogglDesktop/TimeEntryListItem.qml | 27 ++++++++++--------- .../linux/TogglDesktop/TimeEntryListView.qml | 14 +++++----- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index 71efb8a7de..8835d586cd 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -5,6 +5,9 @@ Item { z: index height: visible ? itemHeight : 0 width: timeEntryList.viewportWidth + + property QtObject timeEntry: null + Rectangle { anchors { fill: parent @@ -12,7 +15,7 @@ Item { rightMargin: anchors.leftMargin topMargin: 0 } - color: delegateMouse.containsMouse ? palette.listBackground : modelData.GroupOpen ? palette.listBackground : palette.base + color: delegateMouse.containsMouse ? palette.listBackground : timeEntry.GroupOpen ? palette.listBackground : palette.base TogglShadowBox { anchors.fill: parent @@ -35,10 +38,10 @@ Item { hoverEnabled: true anchors.fill: parent onClicked: { - if (modelData.Group) - toggl.toggleEntriesGroup(modelData.GroupName) + if (timeEntry.Group) + toggl.toggleEntriesGroup(timeEntry.GroupName) else - toggl.editTimeEntry(modelData.GUID, "description") + toggl.editTimeEntry(timeEntry.GUID, "description") } } RowLayout { @@ -48,22 +51,22 @@ Item { Rectangle { Layout.alignment: Qt.AlignVCenter - visible: modelData.Group | modelData.GroupOpen - opacity: modelData.GroupOpen ? 0.0 : 1.0 + visible: timeEntry.Group | timeEntry.GroupOpen + opacity: timeEntry.GroupOpen ? 0.0 : 1.0 width: 24 height: 24 radius: 4 - color: modelData.GroupOpen ? "dark green" : palette.base + color: timeEntry.GroupOpen ? "dark green" : palette.base border { - color: modelData.GroupOpen ? "transparent" : palette.alternateBase + color: timeEntry.GroupOpen ? "transparent" : palette.alternateBase width: 0.5 } Text { - color: modelData.GroupOpen ? "light green" : palette.alternateBase + color: timeEntry.GroupOpen ? "light green" : palette.alternateBase anchors.centerIn: parent verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter - text: modelData.GroupItemCount + text: timeEntry.GroupItemCount } } @@ -76,7 +79,7 @@ Item { Text { Layout.alignment: Qt.AlignVCenter - text: modelData.Duration + text: timeEntry.Duration color: palette.text } Item { @@ -86,7 +89,7 @@ Item { height: 20 MouseArea { anchors.fill: parent - onClicked: toggl.continueTimeEntry(modelData.GUID) + onClicked: toggl.continueTimeEntry(timeEntry.GUID) } Item { diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index e780ffc29c..90ca823d94 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -1,6 +1,6 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 -import QtQuick.Controls 2.12 +import QtQuick.Controls 1.4 import QtGraphicalEffects 1.0 Rectangle { @@ -24,11 +24,11 @@ Rectangle { ScrollView { id: timeEntryList + clip: true Layout.fillWidth: true Layout.fillHeight: true - property real viewportWidth: width - ScrollBar.vertical.width + property real viewportWidth: viewport.width ListView { - clip: true model: toggl.timeEntries section.property: "modelData.DateHeader" section.delegate: TimeEntryListHeader { @@ -36,6 +36,10 @@ Rectangle { width: parent.width } + delegate: TimeEntryListItem { + timeEntry: modelData + } + add: Transition { id: addTransition NumberAnimation { @@ -88,10 +92,6 @@ Rectangle { duration: 120 } } - - delegate: TimeEntryListItem { - - } } } } From 968e131acbe06bbf04d86226b019773061314f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Wed, 4 Dec 2019 13:22:16 +0100 Subject: [PATCH 56/83] Fix string issues (linux) --- src/ui/linux/TogglDesktop/common.h | 16 ++++------------ src/ui/linux/TogglDesktop/main.cpp | 1 + 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/ui/linux/TogglDesktop/common.h b/src/ui/linux/TogglDesktop/common.h index 5b1f4b636b..2feac321f8 100644 --- a/src/ui/linux/TogglDesktop/common.h +++ b/src/ui/linux/TogglDesktop/common.h @@ -7,20 +7,12 @@ #include #ifdef _WIN32 -inline QString toQString(const wchar_t *s) { - return QString::fromWCharArray(s); -} -inline const wchar_t *toLocalString(const QString &s) { - return s.toStdWString().c_str(); -} +#define toQString(x) QString::fromWCharArray(x) +#define toLocalString(x) x.toStdWString().c_str() #define strLiteral(x) L"" x #else -inline QString toQString(const char_t *s) { - return QString::fromLocal8Bit(s); -} -inline const char_t *toLocalString(const QString &s) { - return s.toStdString().c_str(); -} +#define toQString(x) QString(x) +#define toLocalString(x) (x).toStdString().c_str() #define strLiteral(x) x #endif diff --git a/src/ui/linux/TogglDesktop/main.cpp b/src/ui/linux/TogglDesktop/main.cpp index a51278da11..8525de400c 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -75,6 +75,7 @@ void registerTypes() { qRegisterMetaType("uint64_t"); qRegisterMetaType("int64_t"); qRegisterMetaType("bool_t"); + qRegisterMetaType("char_t"); qRegisterMetaType >("QVector"); qRegisterMetaType >("QVector"); qRegisterMetaType >("QVector"); From f4dea6735dab0056c37a99239e9c1fa5d7233af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Wed, 4 Dec 2019 13:49:07 +0100 Subject: [PATCH 57/83] Rename palette to mainPalette to avoid conflicts (linux) --- .../linux/TogglDesktop/AutocompleteView.qml | 18 ++++++------ src/ui/linux/TogglDesktop/LoginView.qml | 22 +++++++-------- src/ui/linux/TogglDesktop/MainWindow.qml | 10 +++---- .../linux/TogglDesktop/TimeEntryEditView.qml | 28 +++++++++---------- src/ui/linux/TogglDesktop/TimeEntryLabel.qml | 10 +++---- .../TogglDesktop/TimeEntryListHeader.qml | 10 +++---- .../linux/TogglDesktop/TimeEntryListItem.qml | 18 ++++++------ .../linux/TogglDesktop/TimeEntryListView.qml | 2 +- src/ui/linux/TogglDesktop/TimerView.qml | 4 +-- src/ui/linux/TogglDesktop/TogglButton.qml | 4 +-- .../TogglDesktop/TogglButtonBackground.qml | 14 +++++----- src/ui/linux/TogglDesktop/TogglCheckBox.qml | 6 ++-- src/ui/linux/TogglDesktop/TogglComboBox.qml | 4 +-- src/ui/linux/TogglDesktop/TogglSwitch.qml | 8 +++--- src/ui/linux/TogglDesktop/TogglTextField.qml | 6 ++-- 15 files changed, 83 insertions(+), 81 deletions(-) diff --git a/src/ui/linux/TogglDesktop/AutocompleteView.qml b/src/ui/linux/TogglDesktop/AutocompleteView.qml index a797d19126..745a2966b9 100644 --- a/src/ui/linux/TogglDesktop/AutocompleteView.qml +++ b/src/ui/linux/TogglDesktop/AutocompleteView.qml @@ -52,7 +52,7 @@ Item { Rectangle { z: -1 anchors.fill: parent - color: setAlpha(palette.shadow, 0.5) + color: setAlpha(mainPalette.shadow, 0.5) } ScrollView { @@ -101,10 +101,10 @@ Item { Rectangle { height: 40 width: root.width - color: palette.base + color: mainPalette.base Text { anchors.centerIn: parent - color: "#9e9e9e" + color: mainPalette.text text: autocompleteData.Description font.pointSize: 14 @@ -116,7 +116,7 @@ Item { right: parent.right } height: 1 - color: palette.alternateBase + color: mainPalette.alternateBase } } } @@ -128,14 +128,14 @@ Item { Rectangle { height: 30 width: root.width - color: palette.base + color: mainPalette.base Text { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter height: 24 x: 9 - color: "#9e9e9e" + color: mainPalette.text text: autocompleteData.Description } @@ -148,13 +148,13 @@ Item { Rectangle { height: 30 width: root.width - color: palette.base + color: mainPalette.base Text { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter text: autocompleteData.Description - color: "#9e9e9e" + color: mainPalette.text font.pointSize: 11 } } @@ -166,6 +166,7 @@ Item { Rectangle { height: 30 width: root.width + color: mainPalette.base Text { anchors.fill: parent @@ -181,6 +182,7 @@ Item { property string task: autocompleteData.TaskLabel.length > 0 ? " " + autocompleteData.TaskLabel : "" text: (ListView.isCurrentItem ? "Iscurrent" : "") + timeEntry + project + task font.pointSize: 12 + color: mainPalette.text } MouseArea { id: delegateMouse diff --git a/src/ui/linux/TogglDesktop/LoginView.qml b/src/ui/linux/TogglDesktop/LoginView.qml index bd6ea158a2..cfda7ecdd1 100644 --- a/src/ui/linux/TogglDesktop/LoginView.qml +++ b/src/ui/linux/TogglDesktop/LoginView.qml @@ -46,7 +46,7 @@ Item { gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0.5; color: "light gray" } - GradientStop { position: 1.0; color: palette.alternateBase } + GradientStop { position: 1.0; color: mainPalette.alternateBase } } } @@ -62,7 +62,7 @@ Item { gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0.5; color: "light gray" } - GradientStop { position: 0.0; color: palette.alternateBase } + GradientStop { position: 0.0; color: mainPalette.alternateBase } } } @@ -78,7 +78,7 @@ Item { gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0.5; color: "light gray" } - GradientStop { position: 1.0; color: palette.alternateBase } + GradientStop { position: 1.0; color: mainPalette.alternateBase } } } @@ -94,7 +94,7 @@ Item { gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0.5; color: "light gray" } - GradientStop { position: 0.0; color: palette.alternateBase } + GradientStop { position: 0.0; color: mainPalette.alternateBase } } } @@ -106,7 +106,7 @@ Item { width: shadowWidth gradient: Gradient { orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: palette.alternateBase } + GradientStop { position: 0.0; color: mainPalette.alternateBase } GradientStop { position: 1.0; color: "light gray" } } } @@ -120,7 +120,7 @@ Item { gradient: Gradient { orientation: Gradient.Horizontal GradientStop { position: 0.0; color: "light gray" } - GradientStop { position: 1.0; color: palette.alternateBase } + GradientStop { position: 1.0; color: mainPalette.alternateBase } } } Rectangle { @@ -131,7 +131,7 @@ Item { height: shadowWidth gradient: Gradient { GradientStop { position: 0.0; color: "light gray" } - GradientStop { position: 1.0; color: palette.alternateBase } + GradientStop { position: 1.0; color: mainPalette.alternateBase } } } Rectangle { @@ -141,7 +141,7 @@ Item { height: shadowWidth gradient: Gradient { - GradientStop { position: 0.0; color: palette.alternateBase } + GradientStop { position: 0.0; color: mainPalette.alternateBase } GradientStop { position: 1.0; color: "light gray" } } } @@ -190,7 +190,7 @@ Item { anchors.rightMargin: 9 anchors.verticalCenter: parent.verticalCenter text: "Login" - color: loggingIn ? palette.text : disabledPalette.text + color: loggingIn ? mainPalette.text : disabledPalette.text } TogglSwitch { id: loginSwitch @@ -203,7 +203,7 @@ Item { anchors.leftMargin: 9 anchors.verticalCenter: parent.verticalCenter text: "Sign up" - color: signingUp ? palette.text : disabledPalette.text + color: signingUp ? mainPalette.text : disabledPalette.text } } @@ -274,7 +274,7 @@ Item { wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pointSize: 9 text: "I agree to terms of service and privacy policy" - color: palette.text + color: mainPalette.text } } diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index 8083213b59..f8dd06de3d 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -24,12 +24,12 @@ Rectangle { } SystemPalette { - id: palette + id: mainPalette property bool isDark: (itemShadow.r + itemShadow.g + itemShadow.b) < 1 - property int itemShadowSize: palette.isDark ? 1 : 9 - property color itemShadow: mixColors(palette.shadow, palette.listBackground, 0.2) - property color listBackground: mixColors(palette.base, palette.alternateBase, 0.8) + property int itemShadowSize: mainPalette.isDark ? 1 : 9 + property color itemShadow: mixColors(mainPalette.shadow, mainPalette.listBackground, 0.2) + property color listBackground: mixColors(mainPalette.base, mainPalette.alternateBase, 0.8) } SystemPalette { id: disabledPalette @@ -38,7 +38,7 @@ Rectangle { Rectangle { anchors.fill: parent - color: palette.base + color: mainPalette.base clip: true ErrorOverlay { diff --git a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml index b9e193d4ad..9e30832e59 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml @@ -13,7 +13,7 @@ Item { Rectangle { anchors.fill: parent - color: palette.base + color: mainPalette.base opacity: 0.5 } @@ -22,7 +22,7 @@ Item { anchors.margins: 9 radius: 6 - color: palette.base + color: mainPalette.base opacity: visible ? 1.0 : 0.0 Behavior on opacity { NumberAnimation { @@ -32,7 +32,7 @@ Item { border { width: 1 - color: palette.shadow + color: mainPalette.shadow } ColumnLayout { @@ -59,7 +59,7 @@ Item { columns: 4 Text { text: "Duration:" - color: palette.text + color: mainPalette.text } TogglTextField { Layout.fillWidth: true @@ -69,7 +69,7 @@ Item { Text { text: "Start-end time:" - color: palette.text + color: mainPalette.text } TogglTextField { Layout.fillWidth: true @@ -77,7 +77,7 @@ Item { } Text { text: "-" - color: palette.text + color: mainPalette.text } TogglTextField { Layout.fillWidth: true @@ -86,7 +86,7 @@ Item { Text { text: "Date:" - color: palette.text + color: mainPalette.text } TogglTextField { Layout.fillWidth: true @@ -97,7 +97,7 @@ Item { Text { text: "Tags" Layout.fillHeight: true - color: palette.text + color: mainPalette.text } ListView { Layout.fillWidth: true @@ -106,8 +106,8 @@ Item { Frame { anchors.fill: parent background: Rectangle { - color: palette.base - border.color: palette.dark + color: mainPalette.base + border.color: mainPalette.dark border.width: 1 } ColumnLayout { @@ -244,7 +244,7 @@ Item { Layout.fillWidth: true Text { text: "New:" - color: palette.text + color: mainPalette.text } TogglTextField { Layout.fillWidth: true @@ -277,13 +277,13 @@ Item { Text { text: "Workspace:" - color: palette.text + color: mainPalette.text } Text { Layout.fillWidth: true Layout.columnSpan: 3 text: timeEntry ? timeEntry.WorkspaceName : "" - color: palette.text + color: mainPalette.text } } RowLayout { @@ -302,7 +302,7 @@ Item { } Text { text: timeEntry ? timeEntry.lastUpdate() : "" - color: palette.text + color: mainPalette.text } } } diff --git a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml index 57aabd8d79..c7b65bb913 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml @@ -11,7 +11,7 @@ ColumnLayout { visible: timeEntry Layout.fillWidth: true text: timeEntry && timeEntry.Description.length > 0 ? timeEntry.Description : "+ Add description" - color: timeEntry && timeEntry.Description.length > 0 ? palette.text : disabledPalette.text + color: timeEntry && timeEntry.Description.length > 0 ? mainPalette.text : disabledPalette.text wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pixelSize: 12 verticalAlignment: Text.AlignVCenter @@ -23,25 +23,25 @@ ColumnLayout { height: 8 width: height radius: height / 2 - color: timeEntry ? timeEntry.Color : palette.text + color: timeEntry ? timeEntry.Color : mainPalette.text Layout.alignment: Qt.AlignVCenter } Text { visible: timeEntry text: timeEntry && timeEntry.ProjectLabel.length > 0 ? timeEntry.ProjectLabel : "+ Add project" - color: timeEntry && timeEntry.ProjectLabel.length > 0 ? (timeEntry.Color.length ? timeEntry.Color : palette.text) : disabledPalette.text + color: timeEntry && timeEntry.ProjectLabel.length > 0 ? (timeEntry.Color.length ? timeEntry.Color : mainPalette.text) : disabledPalette.text font.pixelSize: 12 } Text { visible: timeEntry && timeEntry.TaskLabel.length > 0 text: timeEntry && timeEntry.TaskLabel.length ? "- " + timeEntry.TaskLabel : "" - color: palette.text + color: mainPalette.text font.pixelSize: 12 } Text { visible: timeEntry && timeEntry.ClientLabel.length > 0 text: timeEntry && timeEntry.ClientLabel ? "• " + timeEntry.ClientLabel : "" - color: palette.text + color: mainPalette.text font.pixelSize: 12 } Item { diff --git a/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml b/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml index 30e9f2dc02..d95c603e3b 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml @@ -12,13 +12,13 @@ Item { x: 12 width: timeEntryList.viewportWidth - 24 height: headerHeight - color: palette.base + color: mainPalette.base TogglShadowBox { anchors.fill: parent - shadowWidth: palette.itemShadowSize - shadowColor: palette.itemShadow - backgroundColor: palette.listBackground + shadowWidth: mainPalette.itemShadowSize + shadowColor: mainPalette.itemShadow + backgroundColor: mainPalette.listBackground sides: TogglShadowBox.Side.Left | TogglShadowBox.Side.Right | TogglShadowBox.Side.Top } @@ -29,7 +29,7 @@ Item { textFormat: Text.RichText // this is actually rendering the width underneath so we force a repaint on each width change text: "
%1%2
%1%2
" + section + "
" + width + "
" - color: palette.windowText + color: mainPalette.windowText } } } diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index 8835d586cd..58fed216f2 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -15,13 +15,13 @@ Item { rightMargin: anchors.leftMargin topMargin: 0 } - color: delegateMouse.containsMouse ? palette.listBackground : timeEntry.GroupOpen ? palette.listBackground : palette.base + color: delegateMouse.containsMouse ? mainPalette.listBackground : timeEntry.GroupOpen ? mainPalette.listBackground : mainPalette.base TogglShadowBox { anchors.fill: parent - shadowWidth: palette.itemShadowSize - shadowColor: palette.itemShadow - backgroundColor: palette.listBackground + shadowWidth: mainPalette.itemShadowSize + shadowColor: mainPalette.itemShadow + backgroundColor: mainPalette.listBackground sides: TogglShadowBox.Side.Left | TogglShadowBox.Side.Right | TogglShadowBox.Side.Bottom } @@ -30,7 +30,7 @@ Item { anchors.left: parent.left anchors.right: parent.right height: 1 - color: palette.listBackground + color: mainPalette.listBackground } MouseArea { @@ -56,13 +56,13 @@ Item { width: 24 height: 24 radius: 4 - color: timeEntry.GroupOpen ? "dark green" : palette.base + color: timeEntry.GroupOpen ? "dark green" : mainPalette.base border { - color: timeEntry.GroupOpen ? "transparent" : palette.alternateBase + color: timeEntry.GroupOpen ? "transparent" : mainPalette.alternateBase width: 0.5 } Text { - color: timeEntry.GroupOpen ? "light green" : palette.alternateBase + color: timeEntry.GroupOpen ? "light green" : mainPalette.alternateBase anchors.centerIn: parent verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter @@ -80,7 +80,7 @@ Item { Text { Layout.alignment: Qt.AlignVCenter text: timeEntry.Duration - color: palette.text + color: mainPalette.text } Item { id: startButton diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 90ca823d94..c63eb20a45 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -6,7 +6,7 @@ import QtGraphicalEffects 1.0 Rectangle { id: root anchors.fill: parent - color: palette.listBackground + color: mainPalette.listBackground property real sectionMargin: Math.max(2 * shadowWidth, 12) property real headerHeight: 32 diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 820d886580..6f08874766 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -4,7 +4,7 @@ import QtQuick.Controls 2.12 Rectangle { id: root - color: palette.base + color: mainPalette.base height: 64 property bool running: false @@ -91,7 +91,7 @@ Rectangle { anchors.topMargin: -1 anchors.bottomMargin: -1 radius: height / 2 - color: mixColors(palette.window, palette.alternateBase, 0.8) + color: mixColors(mainPalette.window, mainPalette.alternateBase, 0.8) z: -1 } } diff --git a/src/ui/linux/TogglDesktop/TogglButton.qml b/src/ui/linux/TogglDesktop/TogglButton.qml index 26d79089d7..8d85c7e914 100644 --- a/src/ui/linux/TogglDesktop/TogglButton.qml +++ b/src/ui/linux/TogglDesktop/TogglButton.qml @@ -9,8 +9,8 @@ Button { contentItem: Text { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - color: control.enabled ? control.pressed | control.checked ? palette.button - : palette.buttonText + color: control.enabled ? control.pressed | control.checked ? mainPalette.button + : mainPalette.buttonText : disabledPalette.buttonText text: control.text } diff --git a/src/ui/linux/TogglDesktop/TogglButtonBackground.qml b/src/ui/linux/TogglDesktop/TogglButtonBackground.qml index c5f356d883..92d82df4ad 100644 --- a/src/ui/linux/TogglDesktop/TogglButtonBackground.qml +++ b/src/ui/linux/TogglDesktop/TogglButtonBackground.qml @@ -7,16 +7,16 @@ Rectangle { implicitHeight: 40 radius: 2 - property color borderColor: mixColors(palette.base, palette.text, 0.33) + property color borderColor: mixColors(palette.base, mainPalette.text, 0.33) //property color shadowColor: control.enabled ? control.checked ? "#595959" : control.hovered ? "#e5e5e5" : "#d4d4d4" : baseColor //property color baseColor: control.enabled ? control.checked ? "#7a7a7a" : control.hovered ? "#d4d4d4" : "#c3c3c3" : "#b2b2b2" - property color shadowColor: control.enabled ? control.pressed | control.checked ? mixColors(palette.button, palette.buttonText, 0.5) - : control.hovered ? mixColors(palette.button, palette.light, 0.5) - : mixColors(palette.button, palette.light, 0.5) + property color shadowColor: control.enabled ? control.pressed | control.checked ? mixColors(palette.button, mainPalette.buttonText, 0.5) + : control.hovered ? mixColors(palette.button, mainPalette.light, 0.5) + : mixColors(palette.button, mainPalette.light, 0.5) : baseColor - property color baseColor: control.enabled ? control.pressed | control.checked ? mixColors(palette.buttonText, palette.button, 0.66) - : control.hovered ? mixColors(palette.button, palette.light, 0.5) - : palette.button + property color baseColor: control.enabled ? control.pressed | control.checked ? mixColors(palette.buttonText, mainPalette.button, 0.66) + : control.hovered ? mixColors(palette.button, mainPalette.light, 0.5) + : mainPalette.button : disabledPalette.button Behavior on shadowColor { ColorAnimation { duration: 120 } } diff --git a/src/ui/linux/TogglDesktop/TogglCheckBox.qml b/src/ui/linux/TogglDesktop/TogglCheckBox.qml index 55cef0ad61..79c89919cd 100644 --- a/src/ui/linux/TogglDesktop/TogglCheckBox.qml +++ b/src/ui/linux/TogglDesktop/TogglCheckBox.qml @@ -9,8 +9,8 @@ CheckBox { x: control.leftPadding y: parent.height / 2 - height / 2 radius: 3 - color: palette.base - border.color: mixColors(palette.base, palette.text, 0.33) + color: mainPalette.base + border.color: mixColors(palette.base, mainPalette.text, 0.33) border.width: 1 Rectangle { @@ -20,7 +20,7 @@ CheckBox { y: 4 radius: 1 antialiasing: true - color: palette.text + color: mainPalette.text opacity: control.checked ? 1.0 : 0.0 Behavior on opacity { NumberAnimation { duration: 120 } } visible: opacity > 0.0 diff --git a/src/ui/linux/TogglDesktop/TogglComboBox.qml b/src/ui/linux/TogglDesktop/TogglComboBox.qml index d982e1e2c8..6784be8857 100644 --- a/src/ui/linux/TogglDesktop/TogglComboBox.qml +++ b/src/ui/linux/TogglDesktop/TogglComboBox.qml @@ -25,7 +25,7 @@ ComboBox { context.lineTo(width, 0); context.lineTo(width / 2, height); context.closePath(); - context.fillStyle = palette.buttonText; + context.fillStyle = mainPalette.buttonText; context.fill(); } } @@ -35,7 +35,7 @@ ComboBox { text: control.displayText font: control.font - color: control.currentIndex < 0 ? disabledPalette.buttonText : palette.buttonText + color: control.currentIndex < 0 ? disabledPalette.buttonText : mainPalette.buttonText verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } diff --git a/src/ui/linux/TogglDesktop/TogglSwitch.qml b/src/ui/linux/TogglDesktop/TogglSwitch.qml index ae045280dc..9f9fd2ccc3 100644 --- a/src/ui/linux/TogglDesktop/TogglSwitch.qml +++ b/src/ui/linux/TogglDesktop/TogglSwitch.qml @@ -10,9 +10,9 @@ Switch { x: control.leftPadding y: parent.height / 2 - height / 2 radius: 13 - color: palette.base + color: mainPalette.base border.width: 1 - border.color: mixColors(palette.base, palette.text, 0.33) + border.color: mixColors(palette.base, mainPalette.text, 0.33) Rectangle { x: control.checked ? parent.width - width - 1 : 1 @@ -22,14 +22,14 @@ Switch { height: parent.height - 2 radius: parent.height / 2 - 1 - color: palette.button + color: mainPalette.button Behavior on x { NumberAnimation { duration: 120 } } - border.color: mixColors(palette.base, palette.text, 0.33) + border.color: mixColors(palette.base, mainPalette.text, 0.33) } } } diff --git a/src/ui/linux/TogglDesktop/TogglTextField.qml b/src/ui/linux/TogglDesktop/TogglTextField.qml index 0f296454cf..37865c9abc 100644 --- a/src/ui/linux/TogglDesktop/TogglTextField.qml +++ b/src/ui/linux/TogglDesktop/TogglTextField.qml @@ -3,11 +3,11 @@ import QtQuick.Controls 2.12 TextField { id: control - color: palette.text + color: mainPalette.text background: Rectangle { border.width: 1 radius: 2 - border.color: mixColors(palette.base, palette.text, 0.33) - color: palette.base + border.color: mixColors(palette.base, mainPalette.text, 0.33) + color: "transparent" } } From 27cfdaea29af18bb22f8ee201cf38fd346f22e3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Wed, 4 Dec 2019 14:15:23 +0100 Subject: [PATCH 58/83] Make the TE Editor a bit prettier (linux) --- src/ui/linux/TogglDesktop/MainWindow.qml | 2 + .../linux/TogglDesktop/TimeEntryEditView.qml | 416 +++++++++--------- src/ui/linux/TogglDesktop/TogglButton.qml | 6 +- .../TogglDesktop/TogglButtonBackground.qml | 7 +- src/ui/linux/TogglDesktop/TogglTextField.qml | 4 +- 5 files changed, 227 insertions(+), 208 deletions(-) diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index f8dd06de3d..11c887df1e 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -30,6 +30,8 @@ Rectangle { property int itemShadowSize: mainPalette.isDark ? 1 : 9 property color itemShadow: mixColors(mainPalette.shadow, mainPalette.listBackground, 0.2) property color listBackground: mixColors(mainPalette.base, mainPalette.alternateBase, 0.8) + + property color borderColor: mixColors(mainPalette.text, mainPalette.base, 0.33) } SystemPalette { id: disabledPalette diff --git a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml index 9e30832e59..24a9bc5f75 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml @@ -40,269 +40,289 @@ Item { anchors.margins: 12 spacing: 9 + Text { + text: qsTr("Details") + font.capitalization: Font.AllUppercase + color: mainPalette.windowText + } + TogglTextField { Layout.fillWidth: true text: timeEntry ? timeEntry.Description : "" } TogglTextField { Layout.fillWidth: true - text: timeEntry ? (timeEntry.ClientLabel + " . " + timeEntry.ProjectLabel) : "" - } + placeholderText: qsTr("Select project") + text: timeEntry && (timeEntry.ClientLabel.length > 0 || timeEntry.ProjectLabel.length > 0) ? (timeEntry.ClientLabel + " . " + timeEntry.ProjectLabel) : "" - TogglButton { - Layout.alignment: Qt.AlignRight - text: "Add project" - } - - GridLayout { - Layout.fillWidth: true - columns: 4 - Text { - text: "Duration:" - color: mainPalette.text + Rectangle { + smooth: true + opacity: 0.5 + width: 4 + height: 1 + rotation: 45 + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 14.5 } - TogglTextField { - Layout.fillWidth: true - Layout.columnSpan: 3 - text: timeEntry ? timeEntry.Duration : "" - } - - Text { - text: "Start-end time:" - color: mainPalette.text - } - TogglTextField { - Layout.fillWidth: true - text: timeEntry ? timeEntry.StartTimeString : "" - } - Text { - text: "-" - color: mainPalette.text - } - TogglTextField { - Layout.fillWidth: true - text: timeEntry ? timeEntry.EndTimeString : "" - } - - Text { - text: "Date:" - color: mainPalette.text - } - TogglTextField { - Layout.fillWidth: true - Layout.columnSpan: 3 - text: timeEntry ? (new Date(Date(timeEntry.Started)).toLocaleDateString(Qt.locale(), Locale.ShortFormat)) : "" + Rectangle { + smooth: true + opacity: 0.5 + width: 4 + height: 1 + rotation: -45 + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 11.5 } + } + TogglTextField { + Layout.fillWidth: true + placeholderText: qsTr("Add tags") + } - Text { - text: "Tags" - Layout.fillHeight: true - color: mainPalette.text - } - ListView { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.columnSpan: 3 - Frame { + /* + ListView { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.columnSpan: 3 + Frame { + anchors.fill: parent + background: Rectangle { + color: mainPalette.base + border.color: mainPalette.dark + border.width: 1 + } + ColumnLayout { anchors.fill: parent - background: Rectangle { - color: mainPalette.base - border.color: mainPalette.dark - border.width: 1 - } - ColumnLayout { - anchors.fill: parent - Flow { - Layout.fillHeight: true - Layout.fillWidth: true - Repeater { - model: timeEntry ? timeEntry.Tags : null - delegate: Item { - width: childrenRect.width + 4 - height: childrenRect.height + 4 - Rectangle { + Flow { + Layout.fillHeight: true + Layout.fillWidth: true + Repeater { + model: timeEntry ? timeEntry.Tags : null + delegate: Item { + width: childrenRect.width + 4 + height: childrenRect.height + 4 + Rectangle { + x: 2 + y: 2 + width: selectedTagLayout.width + 4 + height: selectedTagLayout.height + 4 + color: "white" + border.width: 1 + border.color: "#d4d4d4" + radius: 2 + + RowLayout { x: 2 y: 2 - width: selectedTagLayout.width + 4 - height: selectedTagLayout.height + 4 - color: "white" - border.width: 1 - border.color: "#d4d4d4" - radius: 2 - - RowLayout { - x: 2 - y: 2 - id: selectedTagLayout - Text { - id: selectedTagText - text: modelData - font.pixelSize: 10 + id: selectedTagLayout + Text { + id: selectedTagText + text: modelData + font.pixelSize: 10 - Rectangle { - Behavior on opacity { NumberAnimation { } } - opacity: selectedTagMouse.containsMouse ? 1.0 : 0.0 - visible: opacity > 0.0 - width: 10 - height: 10 - radius: 3 + Rectangle { + Behavior on opacity { NumberAnimation { } } + opacity: selectedTagMouse.containsMouse ? 1.0 : 0.0 + visible: opacity > 0.0 + width: 10 + height: 10 + radius: 3 + anchors.centerIn: parent + color: "red" + Text { anchors.centerIn: parent - color: "red" - Text { - anchors.centerIn: parent - text: "x" - font.pointSize: 8 - } + text: "x" + font.pointSize: 8 } } } - MouseArea { - id: selectedTagMouse - anchors.fill: parent - hoverEnabled: true - onClicked: { - var list = timeEntry.Tags - var index = list.indexOf(modelData); - if (index > -1) { - list.splice(index, 1); - toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) - } + } + MouseArea { + id: selectedTagMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + var list = timeEntry.Tags + var index = list.indexOf(modelData); + if (index > -1) { + list.splice(index, 1); + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) } } } } } } + } - Rectangle { - Layout.fillWidth: true - height: 1 - color: "#c3c3c3" - } + Rectangle { + Layout.fillWidth: true + height: 1 + color: "#c3c3c3" + } - Flow { - clip: true - Layout.fillHeight: true - Layout.fillWidth: true - Repeater { - model: timeEntry ? (toggl.tags.filter(n => !timeEntry.Tags.includes(n))) : null - delegate: Item { - width: childrenRect.width + 4 - height: childrenRect.height + 4 - Rectangle { + Flow { + clip: true + Layout.fillHeight: true + Layout.fillWidth: true + Repeater { + model: timeEntry ? (toggl.tags.filter(n => !timeEntry.Tags.includes(n))) : null + delegate: Item { + width: childrenRect.width + 4 + height: childrenRect.height + 4 + Rectangle { + x: 2 + y: 2 + width: tagLayout.width + 4 + height: tagLayout.height + 4 + color: "white" + border.width: 1 + border.color: "#d4d4d4" + radius: 2 + + RowLayout { + id: tagLayout x: 2 y: 2 - width: tagLayout.width + 4 - height: tagLayout.height + 4 - color: "white" - border.width: 1 - border.color: "#d4d4d4" - radius: 2 - - RowLayout { - id: tagLayout - x: 2 - y: 2 - Text { - id: tagText - text: modelData - font.pixelSize: 10 - Rectangle { - Behavior on opacity { NumberAnimation { } } - opacity: tagMouse.containsMouse ? 1.0 : 0.0 - visible: opacity > 0.0 - width: 10 - height: 10 - radius: 3 + Text { + id: tagText + text: modelData + font.pixelSize: 10 + Rectangle { + Behavior on opacity { NumberAnimation { } } + opacity: tagMouse.containsMouse ? 1.0 : 0.0 + visible: opacity > 0.0 + width: 10 + height: 10 + radius: 3 + anchors.centerIn: parent + color: "green" + Text { anchors.centerIn: parent - color: "green" - Text { - anchors.centerIn: parent - text: "+" - font.pointSize: 8 - } + text: "+" + font.pointSize: 8 } } } - MouseArea { - id: tagMouse - anchors.fill: parent - hoverEnabled: true - onClicked: { - var list = timeEntry.Tags - list.push(modelData) - toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) - } + } + MouseArea { + id: tagMouse + anchors.fill: parent + hoverEnabled: true + onClicked: { + var list = timeEntry.Tags + list.push(modelData) + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) } } } } } + } - RowLayout { + RowLayout { + Layout.fillWidth: true + Text { + text: qsTr("New:", "tag") + color: mainPalette.text + } + TogglTextField { Layout.fillWidth: true - Text { - text: "New:" - color: mainPalette.text - } - TogglTextField { - Layout.fillWidth: true - id: newTagField - onAccepted: { - if (text.length > 0) { - var list = timeEntry.Tags - list.push(newTagField.text) - toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) - newTagField.text = "" - } + id: newTagField + onAccepted: { + if (text.length > 0) { + var list = timeEntry.Tags + list.push(newTagField.text) + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + newTagField.text = "" } } - TogglButton { - Layout.preferredWidth: 64 - text: "Add" - onClicked: { - if (text.length > 0) { - var list = timeEntry.Tags - list.push(newTagField.text) - toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) - newTagField.text = "" - } + } + TogglButton { + Layout.preferredWidth: 64 + text: qsTr("Add") + onClicked: { + if (text.length > 0) { + var list = timeEntry.Tags + list.push(newTagField.text) + toggl.setTimeEntryTags(timeEntry.GUID, list.sort().join("\t")) + newTagField.text = "" } } } } } } + }*/ - Text { - text: "Workspace:" - color: mainPalette.text + Text { + text: qsTr("Duration") + color: mainPalette.windowText + font.capitalization: Font.AllUppercase + } + RowLayout { + Layout.fillWidth: true + TextMetrics { + id: timeMetrics + text: "00:00:00 AM" } - Text { + + TogglTextField { + Layout.minimumWidth: timeMetrics.width + text: timeEntry ? timeEntry.Duration : "" + } + Item { + height: 1 Layout.fillWidth: true - Layout.columnSpan: 3 - text: timeEntry ? timeEntry.WorkspaceName : "" + } + + TogglTextField { + Layout.minimumWidth: timeMetrics.width + text: timeEntry ? timeEntry.StartTimeString : "" + } + Text { + text: "→" color: mainPalette.text } + TogglTextField { + Layout.minimumWidth: timeMetrics.width + text: timeEntry ? timeEntry.EndTimeString : "" + } + } + + TogglTextField { + Layout.fillWidth: true + Layout.columnSpan: 3 + text: timeEntry ? (new Date(Date(timeEntry.Started)).toLocaleDateString(Qt.locale(), Locale.ShortFormat)) : "" + } + + + Text { + Layout.fillWidth: true + Layout.columnSpan: 3 + text: timeEntry ? timeEntry.WorkspaceName : "" + color: mainPalette.windowText } RowLayout { TogglButton { - text: "Done" + text: qsTr("Done") onClicked: toggl.viewTimeEntryList() } TogglButton { - text: "Delete" + text: qsTr("Delete") onClicked: toggl.deleteTimeEntry(timeEntry.GUID) } TogglButton { - text: "Cancel" + text: qsTr("Cancel") onClicked: toggl.viewTimeEntryList() } } - Text { - text: timeEntry ? timeEntry.lastUpdate() : "" - color: mainPalette.text + Item { + Layout.fillHeight: true + Layout.fillWidth: true } } } diff --git a/src/ui/linux/TogglDesktop/TogglButton.qml b/src/ui/linux/TogglDesktop/TogglButton.qml index 8d85c7e914..bd154e18e5 100644 --- a/src/ui/linux/TogglDesktop/TogglButton.qml +++ b/src/ui/linux/TogglDesktop/TogglButton.qml @@ -9,9 +9,9 @@ Button { contentItem: Text { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter - color: control.enabled ? control.pressed | control.checked ? mainPalette.button - : mainPalette.buttonText - : disabledPalette.buttonText + color: control.enabled ? control.pressed | control.checked ? mainPalette.text + : mainPalette.text + : disabledPalette.text text: control.text } } diff --git a/src/ui/linux/TogglDesktop/TogglButtonBackground.qml b/src/ui/linux/TogglDesktop/TogglButtonBackground.qml index 92d82df4ad..13ace7d392 100644 --- a/src/ui/linux/TogglDesktop/TogglButtonBackground.qml +++ b/src/ui/linux/TogglDesktop/TogglButtonBackground.qml @@ -7,7 +7,7 @@ Rectangle { implicitHeight: 40 radius: 2 - property color borderColor: mixColors(palette.base, mainPalette.text, 0.33) + property color borderColor: mainPalette.borderColor //property color shadowColor: control.enabled ? control.checked ? "#595959" : control.hovered ? "#e5e5e5" : "#d4d4d4" : baseColor //property color baseColor: control.enabled ? control.checked ? "#7a7a7a" : control.hovered ? "#d4d4d4" : "#c3c3c3" : "#b2b2b2" property color shadowColor: control.enabled ? control.pressed | control.checked ? mixColors(palette.button, mainPalette.buttonText, 0.5) @@ -22,10 +22,7 @@ Rectangle { Behavior on shadowColor { ColorAnimation { duration: 120 } } Behavior on baseColor { ColorAnimation { duration: 120 } } - gradient: Gradient { - GradientStop { position: 0.0; color: shadowColor } - GradientStop { position: 0.12; color: baseColor } - } + color: mainPalette.base border.width: 1 border.color: borderColor diff --git a/src/ui/linux/TogglDesktop/TogglTextField.qml b/src/ui/linux/TogglDesktop/TogglTextField.qml index 37865c9abc..132c18e17e 100644 --- a/src/ui/linux/TogglDesktop/TogglTextField.qml +++ b/src/ui/linux/TogglDesktop/TogglTextField.qml @@ -6,8 +6,8 @@ TextField { color: mainPalette.text background: Rectangle { border.width: 1 - radius: 2 - border.color: mixColors(palette.base, mainPalette.text, 0.33) + radius: 6 + border.color: mainPalette.borderColor color: "transparent" } } From ea1edd66128380eb2e0d76af360a41eff9c86c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Wed, 4 Dec 2019 14:42:44 +0100 Subject: [PATCH 59/83] Reintroduce main window menu, connect some actions (linux) --- src/ui/linux/TogglDesktop/mainwindow.cpp | 55 +++++++++++- src/ui/linux/TogglDesktop/mainwindow.h | 16 ++++ src/ui/linux/TogglDesktop/mainwindow.ui | 101 +++++++++++++++++++++++ 3 files changed, 170 insertions(+), 2 deletions(-) diff --git a/src/ui/linux/TogglDesktop/mainwindow.cpp b/src/ui/linux/TogglDesktop/mainwindow.cpp index bd8ade9229..d211b47d97 100644 --- a/src/ui/linux/TogglDesktop/mainwindow.cpp +++ b/src/ui/linux/TogglDesktop/mainwindow.cpp @@ -24,7 +24,58 @@ MainWindow::MainWindow(QWidget *parent) : #endif } -MainWindow::~MainWindow() -{ +MainWindow::~MainWindow() { delete ui; } + +void MainWindow::on_actionAbout_trigggered() { + // TODO +} + +void MainWindow::on_actionHelp_triggered() { + TogglApi::instance->getSupport(); +} + +void MainWindow::on_actionUpdateCheck_triggered() { + // TODO +} + +void MainWindow::on_actionPreferences_triggered() { + // TODO +} + +void MainWindow::on_actionQuit_triggered() { + qApp->quit(); +} + +void MainWindow::on_actionNew_triggered() { + TogglApi::instance->start("", "", 0, 0, nullptr, false); +} + +void MainWindow::on_actionContinue_triggered() { + TogglApi::instance->continueLatestTimeEntry(); +} + +void MainWindow::on_actionStop_triggered() { + TogglApi::instance->stop(); +} + +void MainWindow::on_actionShow_triggered() { + // TODO +} + +void MainWindow::on_actionEdit_triggered() { + TogglApi::instance->editRunningTimeEntry("description"); +} + +void MainWindow::on_actionSync_triggered() { + TogglApi::instance->sync(); +} + +void MainWindow::on_actionClearCache_triggered() { + // TODO +} + +void MainWindow::on_actionLogout_triggered() { + TogglApi::instance->logout(); +} diff --git a/src/ui/linux/TogglDesktop/mainwindow.h b/src/ui/linux/TogglDesktop/mainwindow.h index 935344132e..f7b151c4b8 100644 --- a/src/ui/linux/TogglDesktop/mainwindow.h +++ b/src/ui/linux/TogglDesktop/mainwindow.h @@ -15,6 +15,22 @@ class MainWindow : public QMainWindow explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); +private slots: + void on_actionAbout_trigggered(); + void on_actionHelp_triggered(); + void on_actionUpdateCheck_triggered(); + void on_actionPreferences_triggered(); + void on_actionQuit_triggered(); + + void on_actionNew_triggered(); + void on_actionContinue_triggered(); + void on_actionStop_triggered(); + void on_actionShow_triggered(); + void on_actionEdit_triggered(); + + void on_actionSync_triggered(); + void on_actionClearCache_triggered(); + void on_actionLogout_triggered(); private: Ui::MainWindow *ui; }; diff --git a/src/ui/linux/TogglDesktop/mainwindow.ui b/src/ui/linux/TogglDesktop/mainwindow.ui index 41d3e791ff..3c5008152b 100644 --- a/src/ui/linux/TogglDesktop/mainwindow.ui +++ b/src/ui/linux/TogglDesktop/mainwindow.ui @@ -39,7 +39,108 @@ 22 + + + Toggl Desktop + + + + + + + + + + + + Account + + + + + + + + + + Timer + + + + + + + + + + + + + + About Toggl Desktop + + + + + Check for updates + + + + + Preferences + + + + + Quit + + + + + Sync + + + + + Clear cache + + + + + Logout + + + + + New + + + + + Continue + + + + + Stop + + + + + Show + + + + + Edit + + + + + Help + + From 186df52c2f21e92c27e7faaba2dc099e3ae4977a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Wed, 4 Dec 2019 15:30:07 +0100 Subject: [PATCH 60/83] Touch up the look of the login view (linux) It probably doesn't work in this state though --- src/ui/linux/TogglDesktop/LoginView.qml | 203 ++++++------------ src/ui/linux/TogglDesktop/TogglButton.qml | 27 ++- .../TogglDesktop/TogglButtonBackground.qml | 6 +- src/ui/linux/TogglDesktop/TogglComboBox.qml | 4 +- 4 files changed, 96 insertions(+), 144 deletions(-) diff --git a/src/ui/linux/TogglDesktop/LoginView.qml b/src/ui/linux/TogglDesktop/LoginView.qml index cfda7ecdd1..74b5308312 100644 --- a/src/ui/linux/TogglDesktop/LoginView.qml +++ b/src/ui/linux/TogglDesktop/LoginView.qml @@ -6,12 +6,10 @@ import QtQuick.Controls 2.12 Item { anchors.fill: parent - property bool loggingIn: !loginSwitch.checked + property bool loggingIn: true property bool signingUp: !loggingIn - property bool everythingFilled: signingUp ? (!signupWithEmail.checked || (username.text.length > 0 && password.text.length > 0)) && - country.currentIndex > -1 && termsAndConditions.checked - : username.text.length > 0 && password.text.length > 0 + property bool everythingFilled: false function act() { if (!everythingFilled) @@ -30,129 +28,23 @@ Item { property real shadowWidth: 9 Rectangle { - anchors.fill: loginColumn - anchors.margins: -24 - - - Rectangle { - anchors.left: parent.right - anchors.top: parent.bottom - anchors.margins: -shadowWidth - width: 2 * shadowWidth - height: 2 * shadowWidth - radius: shadowWidth - rotation: 45 - z: -1 - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.5; color: "light gray" } - GradientStop { position: 1.0; color: mainPalette.alternateBase } - } - } - - Rectangle { - anchors.right: parent.left - anchors.top: parent.bottom - anchors.margins: -shadowWidth - width: 2 * shadowWidth - height: 2 * shadowWidth - radius: shadowWidth - rotation: -45 - z: -1 - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.5; color: "light gray" } - GradientStop { position: 0.0; color: mainPalette.alternateBase } - } - } - - Rectangle { - anchors.left: parent.right - anchors.bottom: parent.top - anchors.margins: -shadowWidth - width: 2 * shadowWidth - height: 2 * shadowWidth - radius: shadowWidth - rotation: -45 - z: -1 - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.5; color: "light gray" } - GradientStop { position: 1.0; color: mainPalette.alternateBase } - } - } - - Rectangle { - anchors.right: parent.left - anchors.bottom: parent.top - anchors.margins: -shadowWidth - width: 2 * shadowWidth - height: 2 * shadowWidth - radius: shadowWidth - rotation: 45 - z: -1 - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.5; color: "light gray" } - GradientStop { position: 0.0; color: mainPalette.alternateBase } - } - } - - Rectangle { - anchors.right: parent.left - anchors.top: parent.top - anchors.bottom: parent.bottom - - width: shadowWidth - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: mainPalette.alternateBase } - GradientStop { position: 1.0; color: "light gray" } - } - } - - Rectangle { - anchors.left: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - - width: shadowWidth - gradient: Gradient { - orientation: Gradient.Horizontal - GradientStop { position: 0.0; color: "light gray" } - GradientStop { position: 1.0; color: mainPalette.alternateBase } - } - } - Rectangle { - anchors.top: parent.bottom - anchors.right: parent.right - anchors.left: parent.left - - height: shadowWidth - gradient: Gradient { - GradientStop { position: 0.0; color: "light gray" } - GradientStop { position: 1.0; color: mainPalette.alternateBase } - } - } - Rectangle { - anchors.bottom: parent.top - anchors.right: parent.right - anchors.left: parent.left + anchors.fill: parent + color: "white" // TODO + } - height: shadowWidth - gradient: Gradient { - GradientStop { position: 0.0; color: mainPalette.alternateBase } - GradientStop { position: 1.0; color: "light gray" } - } - } + Rectangle { + anchors.fill: loginColumn + anchors.margins: -18 + color: mainPalette.base + radius: 8 } Column { id: loginColumn y: parent.height / 6.7 anchors.horizontalCenter: parent.horizontalCenter - width: termsAndConditionsMetrics.width + 30 - spacing: 18 + width: 196 + spacing: 12 add: Transition { NumberAnimation { properties: "opacity" @@ -175,12 +67,13 @@ Item { Image { anchors.horizontalCenter: parent.horizontalCenter source: "qrc:/images/logo.png" - width: parent.width * 0.67 + width: parent.width * 0.33 fillMode: Image.PreserveAspectFit antialiasing: true mipmap: true } + /* Item { width: parent.width height: loginSwitch.height @@ -226,25 +119,42 @@ Item { text: "With Google" } } + */ TogglTextField { width: parent.width id: username focus: true anchors.horizontalCenter: parent.horizontalCenter - placeholderText: "Username" - visible: loggingIn || !signupWithGoogle.checked + placeholderText: qsTr("Email address") onAccepted: act() } - TogglTextField { + Column { width: parent.width - id: password - anchors.horizontalCenter: parent.horizontalCenter - placeholderText: "Password" - echoMode: TextField.Password - visible: loggingIn || !signupWithGoogle.checked - onAccepted: act() + spacing: 6 + TogglTextField { + width: parent.width + id: password + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Password" + echoMode: TextField.Password + onAccepted: act() + } + + Text { + visible: loggingIn + width: parent.width + text: qsTr("Forgot password?") + font.underline: true + color: mainPalette.borderColor + MouseArea { + anchors.fill: parent + anchors.margins: -2 + cursorShape: Qt.PointingHandCursor + hoverEnabled: true + } + } } TogglComboBox { @@ -269,7 +179,7 @@ Item { } Text { id: termsAndConditionsText - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pointSize: 9 @@ -282,6 +192,8 @@ Item { width: parent.width anchors.horizontalCenter: parent.horizontalCenter text: signingUp ? "Sign up" : "Log in" + color: Qt.rgba(229/255.0, 85/255.0, 98/255.0, 1.0) + textColor: "white" enabled: everythingFilled onClicked: act() } @@ -289,9 +201,36 @@ Item { TogglButton { width: parent.width anchors.horizontalCenter: parent.horizontalCenter - text: "Log in with Google" + text: signingUp ? "Sign up with Google" : "Log in with Google" visible: loggingIn enabled: false } + + Column { + spacing: 0 + width: parent.width + Text { + width: parent.width + horizontalAlignment: Text.AlignHCenter + visible: loggingIn + text: "Don't have an account?" + color: mainPalette.borderColor + } + + Text { + width: parent.width + horizontalAlignment: Text.AlignHCenter + font.underline: true + text: loggingIn ? "Sign up for free" : "Back to Log in" + color: mainPalette.borderColor + MouseArea { + anchors.fill: parent + anchors.margins: -2 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: loggingIn = !loggingIn + } + } + } } } diff --git a/src/ui/linux/TogglDesktop/TogglButton.qml b/src/ui/linux/TogglDesktop/TogglButton.qml index bd154e18e5..759ef2e978 100644 --- a/src/ui/linux/TogglDesktop/TogglButton.qml +++ b/src/ui/linux/TogglDesktop/TogglButton.qml @@ -1,17 +1,30 @@ import QtQuick 2.0 import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 Button { id: control + property alias color: background.color + property alias textColor: text.color + property alias iconSource: icon.source background: TogglButtonBackground { + id: background control: control } - contentItem: Text { - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - color: control.enabled ? control.pressed | control.checked ? mainPalette.text - : mainPalette.text - : disabledPalette.text - text: control.text + contentItem: RowLayout { + anchors.fill: parent + spacing: icon.status == Image.Ready ? 3 : 0 + Image { + id: icon + Layout.alignment: Qt.AlignCenter + } + Text { + id: text + Layout.alignment: Qt.AlignCenter + color: control.enabled ? control.pressed | control.checked ? mainPalette.text + : mainPalette.text + : disabledPalette.text + text: control.text + } } } diff --git a/src/ui/linux/TogglDesktop/TogglButtonBackground.qml b/src/ui/linux/TogglDesktop/TogglButtonBackground.qml index 13ace7d392..a04cbe9460 100644 --- a/src/ui/linux/TogglDesktop/TogglButtonBackground.qml +++ b/src/ui/linux/TogglDesktop/TogglButtonBackground.qml @@ -4,10 +4,10 @@ Rectangle { property Item control clip: true implicitWidth: 100 - implicitHeight: 40 - radius: 2 + implicitHeight: 32 + radius: 6 - property color borderColor: mainPalette.borderColor + property color borderColor: control.enabled ? mainPalette.borderColor : disabledPalette.button //property color shadowColor: control.enabled ? control.checked ? "#595959" : control.hovered ? "#e5e5e5" : "#d4d4d4" : baseColor //property color baseColor: control.enabled ? control.checked ? "#7a7a7a" : control.hovered ? "#d4d4d4" : "#c3c3c3" : "#b2b2b2" property color shadowColor: control.enabled ? control.pressed | control.checked ? mixColors(palette.button, mainPalette.buttonText, 0.5) diff --git a/src/ui/linux/TogglDesktop/TogglComboBox.qml b/src/ui/linux/TogglDesktop/TogglComboBox.qml index 6784be8857..c97ec4ed6e 100644 --- a/src/ui/linux/TogglDesktop/TogglComboBox.qml +++ b/src/ui/linux/TogglDesktop/TogglComboBox.qml @@ -30,12 +30,12 @@ ComboBox { } } contentItem: Text { - leftPadding: 0 + leftPadding: 9 rightPadding: control.indicator.width + control.spacing text: control.displayText font: control.font - color: control.currentIndex < 0 ? disabledPalette.buttonText : mainPalette.buttonText + color: control.currentIndex < 0 ? mainPalette.borderColor : mainPalette.text verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } From c654f7d768e1f4b804faf35aa70e828e7dbb35e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Thu, 5 Dec 2019 12:10:49 +0100 Subject: [PATCH 61/83] Add an inline Time Entry Editor to the List (linux) Doesn't really work yet --- src/ui/linux/TogglDesktop/TimeEntryLabel.qml | 66 +++++++++- .../linux/TogglDesktop/TimeEntryListItem.qml | 115 +++++++++++++++++- .../linux/TogglDesktop/TimeEntryListView.qml | 29 +++++ src/ui/linux/TogglDesktop/TogglTextField.qml | 3 +- 4 files changed, 202 insertions(+), 11 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml index c7b65bb913..69b5e4993c 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml @@ -1,23 +1,45 @@ import QtQuick 2.12 import QtQuick.Layouts 1.12 -ColumnLayout { - Layout.fillHeight: true - Layout.fillWidth: true - +Item { property QtObject timeEntry: null + property bool editable: false + Layout.preferredHeight: editable ? timeEntryField.height * 2 + 12 : timeEntryText.height * 2 + 12 Text { + id: timeEntryText + anchors { + bottom: parent.verticalCenter + bottomMargin: editable ? 10 : 1.5 + left: parent.left + leftMargin: 6 + right: parent.right + } + Behavior on y { NumberAnimation { duration: 120 } } + visible: timeEntry - Layout.fillWidth: true + opacity: editable ? 0.0 : 1.0 + Behavior on opacity { NumberAnimation { duration: 120 } } text: timeEntry && timeEntry.Description.length > 0 ? timeEntry.Description : "+ Add description" color: timeEntry && timeEntry.Description.length > 0 ? mainPalette.text : disabledPalette.text + wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pixelSize: 12 verticalAlignment: Text.AlignVCenter } + RowLayout { - Layout.fillWidth: true + id: projectLayout + anchors { + top: parent.verticalCenter + topMargin: editable ? 9 : 1.5 + left: parent.left + leftMargin: 6 + right: parent.right + } + Behavior on y { NumberAnimation { duration: 120 } } + + opacity: editable ? 0.0 : 1.0 Rectangle { visible: timeEntry && timeEntry.ProjectLabel.length > 0 height: 8 @@ -48,4 +70,36 @@ ColumnLayout { Layout.fillWidth: true } } + + TogglTextField { + id: timeEntryField + anchors { + verticalCenter: timeEntryText.verticalCenter + left: parent.left + right: parent.right + } + + visible: opacity > 0.0 + opacity: editable ? 1.0 : 0.0 + Behavior on opacity { NumberAnimation { duration: 120 } } + text: timeEntry && timeEntry.Description.length > 0 ? timeEntry.Description : "" + placeholderText: qsTr("Add description") + } + + + TogglTextField { + id: projectField + anchors { + verticalCenter: projectLayout.verticalCenter + left: parent.left + right: parent.right + } + + visible: opacity > 0.0 + opacity: editable ? 1.0 : 0.0 + Behavior on opacity { NumberAnimation { duration: 120 } } + text: timeEntry && timeEntry.ProjectLabel.length > 0 ? timeEntry.ProjectLabel : "" + placeholderText: qsTr("Select project") + } + } diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index 58fed216f2..c1eddeef0a 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -3,11 +3,25 @@ import QtQuick.Layouts 1.12 Item { z: index - height: visible ? itemHeight : 0 + height: visible ? expanded ? itemHeight * 4 : itemHeight : 0 + Behavior on height { NumberAnimation { duration: 120 } } width: timeEntryList.viewportWidth + property bool expanded: false + onExpandedChanged: listView.itemExpanded = expanded + property var listView property QtObject timeEntry: null + Rectangle { + z: 99999999 + anchors.fill: parent + visible: opacity > 0.0 + opacity: listView.itemExpanded && !expanded ? 0.5 : 0.0 + Behavior on opacity { NumberAnimation { duration: 120 } } + color: "dark gray" + MouseArea { anchors.fill: parent } + } + Rectangle { anchors { fill: parent @@ -15,7 +29,7 @@ Item { rightMargin: anchors.leftMargin topMargin: 0 } - color: delegateMouse.containsMouse ? mainPalette.listBackground : timeEntry.GroupOpen ? mainPalette.listBackground : mainPalette.base + color: delegateMouse.containsMouse ? mainPalette.listBackground : timeEntry.GroupOpen ? mainPalette.listBackground : mainPalette.base TogglShadowBox { anchors.fill: parent @@ -38,10 +52,16 @@ Item { hoverEnabled: true anchors.fill: parent onClicked: { + console.log("B") + console.log("AAA " + index) + expanded = !expanded + listView.gotoIndex(index) + /* if (timeEntry.Group) toggl.toggleEntriesGroup(timeEntry.GroupName) else toggl.editTimeEntry(timeEntry.GUID, "description") + */ } } RowLayout { @@ -70,19 +90,106 @@ Item { } } - TimeEntryLabel { + ColumnLayout { Layout.fillHeight: true Layout.fillWidth: true + spacing: 3 + Text { + text: "Details" + visible: expanded + color: mainPalette.windowText + font.capitalization: Font.AllUppercase + } + TimeEntryLabel { + Layout.fillWidth: true + + Behavior on width { NumberAnimation { duration: 120 } } + + timeEntry: modelData + editable: expanded + } - timeEntry: modelData + TogglTextField { + Layout.fillWidth: true + opacity: expanded ? 1.0 : 0.0 + Behavior on opacity { NumberAnimation { duration: 120 } } + visible: opacity > 0.0 + placeholderText: qsTr("Add tags") + } + + Text { + visible: expanded + text: qsTr("Duration") + color: mainPalette.windowText + font.capitalization: Font.AllUppercase + } + RowLayout { + visible: expanded + Layout.fillWidth: true + TextMetrics { + id: timeMetrics + text: "00:00:00 AM" + } + + TogglTextField { + implicitWidth: timeMetrics.width + //Layout.minimumWidth: timeMetrics.width + text: timeEntry ? timeEntry.Duration : "" + } + Item { + height: 1 + Layout.fillWidth: true + } + + TogglTextField { + width: timeMetrics.width + implicitWidth: timeMetrics.width + //Layout.minimumWidth: timeMetrics.width + text: timeEntry ? timeEntry.StartTimeString : "" + } + Text { + text: "→" + color: mainPalette.text + } + TogglTextField { + width: timeMetrics.width + implicitWidth: timeMetrics.width + //Layout.minimumWidth: timeMetrics.width + text: timeEntry ? timeEntry.EndTimeString : "" + } + } + + TogglTextField { + visible: expanded + Layout.fillWidth: true + Layout.columnSpan: 3 + text: timeEntry ? (new Date(Date(timeEntry.Started)).toLocaleDateString(Qt.locale(), Locale.ShortFormat)) : "" + } + + + Text { + visible: expanded + Layout.fillWidth: true + Layout.columnSpan: 3 + text: timeEntry ? timeEntry.WorkspaceName : "" + color: mainPalette.windowText + } + + Item { + visible: expanded + Layout.fillHeight: true + Layout.fillWidth: true + } } Text { + visible: !expanded Layout.alignment: Qt.AlignVCenter text: timeEntry.Duration color: mainPalette.text } Item { + visible: !expanded id: startButton opacity: delegateMouse.containsMouse ? 1.0 : 0.0 width: 20 diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index c63eb20a45..51335709c4 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -29,15 +29,44 @@ Rectangle { Layout.fillHeight: true property real viewportWidth: viewport.width ListView { + id: timeEntryListView model: toggl.timeEntries section.property: "modelData.DateHeader" section.delegate: TimeEntryListHeader { height: headerHeight + sectionMargin width: parent.width + Rectangle { + z: 1 + anchors.fill: parent + visible: opacity > 0.0 + opacity: timeEntryListView.itemExpanded ? 0.5 : 0.0 + Behavior on opacity { NumberAnimation { duration: 120 } } + color: "dark gray" + MouseArea { anchors.fill: parent } + } } + function gotoIndex(idx) { + anim.running = false; + + var pos = timeEntryListView.contentY; + var destPos; + + timeEntryListView.positionViewAtIndex(idx, ListView.Beginning); + destPos = timeEntryListView.contentY; + + anim.from = pos; + anim.to = destPos; + anim.running = true; + } + NumberAnimation { id: anim; target: timeEntryListView; property: "contentY"; duration: 120 } + + property bool itemExpanded: false + delegate: TimeEntryListItem { timeEntry: modelData + listView: timeEntryListView + //index: index } add: Transition { diff --git a/src/ui/linux/TogglDesktop/TogglTextField.qml b/src/ui/linux/TogglDesktop/TogglTextField.qml index 132c18e17e..f15cefd49b 100644 --- a/src/ui/linux/TogglDesktop/TogglTextField.qml +++ b/src/ui/linux/TogglDesktop/TogglTextField.qml @@ -4,8 +4,9 @@ import QtQuick.Controls 2.12 TextField { id: control color: mainPalette.text + property bool flat: false background: Rectangle { - border.width: 1 + border.width: flat ? 0 : 1 radius: 6 border.color: mainPalette.borderColor color: "transparent" From 3c1ac5bf0720b0746aba70062880a975bc71da0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Thu, 5 Dec 2019 12:15:45 +0100 Subject: [PATCH 62/83] Disable the annoying flickering in the delegate (linux) --- src/ui/linux/TogglDesktop/TimeEntryListItem.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index c1eddeef0a..8f057e5097 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -49,7 +49,7 @@ Item { MouseArea { id: delegateMouse - hoverEnabled: true + hoverEnabled: !expanded anchors.fill: parent onClicked: { console.log("B") From 2c34afc0de73a9dc6dab7c98b808702e5f0eb477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Thu, 5 Dec 2019 12:28:52 +0100 Subject: [PATCH 63/83] Fix crash with autocomplete (linux) --- src/ui/linux/TogglDesktop/toggl.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ui/linux/TogglDesktop/toggl.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index 07294de5b4..c3460cea54 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -113,20 +113,26 @@ void on_display_time_entry_list( void on_display_time_entry_autocomplete( TogglAutocompleteView *first) { - TogglApi::instance->displayTimeEntryAutocomplete( - AutocompleteView::importAll(first)); + auto v = AutocompleteView::importAll(first); + for (auto i : v) + i->moveToThread(TogglApi::instance->uiThread_); + TogglApi::instance->displayTimeEntryAutocomplete(v); } void on_display_mini_timer_autocomplete( TogglAutocompleteView *first) { - TogglApi::instance->displayMinitimerAutocomplete( - AutocompleteView::importAll(first)); + auto v = AutocompleteView::importAll(first); + for (auto i : v) + i->moveToThread(TogglApi::instance->uiThread_); + TogglApi::instance->displayMinitimerAutocomplete(v); } void on_display_project_autocomplete( TogglAutocompleteView *first) { - TogglApi::instance->displayProjectAutocomplete( - AutocompleteView::importAll(first)); + auto v = AutocompleteView::importAll(first); + for (auto i : v) + i->moveToThread(TogglApi::instance->uiThread_); + TogglApi::instance->displayProjectAutocomplete(v); } void on_display_workspace_select( From 16ab462c716a3204bc97656b046e0a4d84b9a942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Thu, 5 Dec 2019 12:41:07 +0100 Subject: [PATCH 64/83] Make autocomplete actually do something for Time Entries (linux) --- src/ui/linux/TogglDesktop/AutocompleteView.qml | 13 +++++++++---- src/ui/linux/TogglDesktop/TimerView.qml | 9 +++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/ui/linux/TogglDesktop/AutocompleteView.qml b/src/ui/linux/TogglDesktop/AutocompleteView.qml index 745a2966b9..b8e46242be 100644 --- a/src/ui/linux/TogglDesktop/AutocompleteView.qml +++ b/src/ui/linux/TogglDesktop/AutocompleteView.qml @@ -9,7 +9,7 @@ Item { property real maximumHeight: 500 property var model: null - property var currentItem: null + property QtObject currentItem: list.currentItem ? list.currentItem.autocompleteData : null property string filter: "" onFilterChanged: { @@ -19,7 +19,6 @@ Item { onVisibleChanged: list.currentIndex = -1 function upPressed() { - console.log("==== START UP ====") console.log("Current index: " + list.currentIndex) console.log("There is " + list.count + " rows") console.log("Model reports " + model.count() + " rows") @@ -34,7 +33,6 @@ Item { console.log("Current index: " + list.currentIndex) } function downPressed() { - console.log("==== START DOWN ====") console.log("Current index: " + list.currentIndex) console.log("There is " + list.count + " rows") console.log("Model reports " + model.count() + " rows") @@ -49,6 +47,11 @@ Item { console.log("Current index: " + list.currentIndex) } + Text { + z: 20 + text: list.currentItem ? list.currentItem.autocompleteData.Description : "" + } + Rectangle { z: -1 anchors.fill: parent @@ -79,7 +82,9 @@ Item { } highlightFollowsCurrentItem: true highlight: Rectangle { - color: "red" + z: 30 + color: "light gray" + opacity: 0.5 width: root.width height: 24 } diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 6f08874766..777079b693 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -56,22 +56,22 @@ Rectangle { Item { Layout.fillHeight: true } - TextField { + TogglTextField { id: description focus: true + flat: true visible: !running Layout.fillWidth: true background: Item {} font.pixelSize: 12 placeholderText: "What are you doing?" - placeholderTextColor: "light gray" - color: "white" Keys.onUpPressed: autocomplete.upPressed() Keys.onDownPressed: autocomplete.downPressed() Keys.onEscapePressed: autocomplete.visible = false Keys.onReturnPressed: { if (autocomplete.visible && autocomplete.currentItem) { - console.log("something") + if (autocomplete.currentItem.Description) + toggl.start(autocomplete.currentItem.Description, "", autocomplete.currentItem.TaskID, autocomplete.currentItem.ProjectID, autocomplete.currentItem.Tags, false) autocomplete.visible = false } else { @@ -90,6 +90,7 @@ Rectangle { anchors.fill: parent anchors.topMargin: -1 anchors.bottomMargin: -1 + anchors.leftMargin: -3 radius: height / 2 color: mixColors(mainPalette.window, mainPalette.alternateBase, 0.8) z: -1 From 973b7ee2e01f30657f1795c2c60bc554a2ebba6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Thu, 5 Dec 2019 12:42:12 +0100 Subject: [PATCH 65/83] Hide group count when an entry is expanded (linux) --- src/ui/linux/TogglDesktop/TimeEntryListItem.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index 8f057e5097..b1d700bace 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -71,7 +71,7 @@ Item { Rectangle { Layout.alignment: Qt.AlignVCenter - visible: timeEntry.Group | timeEntry.GroupOpen + visible: (timeEntry.Group | timeEntry.GroupOpen) && ! expanded opacity: timeEntry.GroupOpen ? 0.0 : 1.0 width: 24 height: 24 From 482b89d6994250ab45faefa258c94008505ddc0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Thu, 5 Dec 2019 12:49:29 +0100 Subject: [PATCH 66/83] Fix typo --- src/ui/linux/TogglDesktop/mainwindow.cpp | 2 +- src/ui/linux/TogglDesktop/mainwindow.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/linux/TogglDesktop/mainwindow.cpp b/src/ui/linux/TogglDesktop/mainwindow.cpp index d211b47d97..ad7d87b8f8 100644 --- a/src/ui/linux/TogglDesktop/mainwindow.cpp +++ b/src/ui/linux/TogglDesktop/mainwindow.cpp @@ -28,7 +28,7 @@ MainWindow::~MainWindow() { delete ui; } -void MainWindow::on_actionAbout_trigggered() { +void MainWindow::on_actionAbout_triggered() { // TODO } diff --git a/src/ui/linux/TogglDesktop/mainwindow.h b/src/ui/linux/TogglDesktop/mainwindow.h index f7b151c4b8..abbd8bcaaa 100644 --- a/src/ui/linux/TogglDesktop/mainwindow.h +++ b/src/ui/linux/TogglDesktop/mainwindow.h @@ -16,7 +16,7 @@ class MainWindow : public QMainWindow ~MainWindow(); private slots: - void on_actionAbout_trigggered(); + void on_actionAbout_triggered(); void on_actionHelp_triggered(); void on_actionUpdateCheck_triggered(); void on_actionPreferences_triggered(); From bc2cf81800324250b0bd696ec7500f3dd1286c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Thu, 5 Dec 2019 13:17:01 +0100 Subject: [PATCH 67/83] Make the duration layout in the editor responsive (linux) --- .../linux/TogglDesktop/TimeEntryListItem.qml | 66 ++++++++++++------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index b1d700bace..1f461db0da 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -3,7 +3,7 @@ import QtQuick.Layouts 1.12 Item { z: index - height: visible ? expanded ? itemHeight * 4 : itemHeight : 0 + height: visible ? expanded ? contentLayout.height + 16 : itemHeight : 0 Behavior on height { NumberAnimation { duration: 120 } } width: timeEntryList.viewportWidth @@ -91,6 +91,7 @@ Item { } ColumnLayout { + id: contentLayout Layout.fillHeight: true Layout.fillWidth: true spacing: 3 @@ -123,39 +124,56 @@ Item { color: mainPalette.windowText font.capitalization: Font.AllUppercase } - RowLayout { + Item { + id: timeContainer visible: expanded Layout.fillWidth: true + property bool separate: timeContainer.width < (durationField.implicitWidth + startTimeField.implicitWidth + timeArrow.implicitWidth + endTimeField.implicitWidth + 12) + Layout.preferredHeight: durationField.height + (timeContainer.separate ? startTimeField.height + 3 : 0) TextMetrics { id: timeMetrics - text: "00:00:00 AM" + text: "+00:00 AM " } TogglTextField { - implicitWidth: timeMetrics.width - //Layout.minimumWidth: timeMetrics.width + id: durationField + width: timeContainer.separate ? parent.width : timeMetrics.width + 3 + height: implicitHeight + implicitWidth: timeMetrics.width + 3 text: timeEntry ? timeEntry.Duration : "" } - Item { - height: 1 - Layout.fillWidth: true - } - TogglTextField { - width: timeMetrics.width - implicitWidth: timeMetrics.width - //Layout.minimumWidth: timeMetrics.width - text: timeEntry ? timeEntry.StartTimeString : "" - } - Text { - text: "→" - color: mainPalette.text - } - TogglTextField { - width: timeMetrics.width - implicitWidth: timeMetrics.width - //Layout.minimumWidth: timeMetrics.width - text: timeEntry ? timeEntry.EndTimeString : "" + RowLayout { + id: startEndTimeLayout + anchors { + top: timeContainer.separate ? durationField.bottom : parent.top + topMargin: timeContainer.separate ? 3 : 0 + left: timeContainer.separate ? parent.left : durationField.right + right: parent.right + } + Item { + visible: !timeContainer.separate + Layout.fillWidth: true + } + TogglTextField { + id: startTimeField + implicitWidth: timeMetrics.width + 3 + //Layout.minimumWidth: timeMetrics.width + Layout.fillWidth: timeContainer.separate + text: timeEntry ? timeEntry.StartTimeString : "" + } + Text { + id: timeArrow + text: "→" + color: mainPalette.text + } + TogglTextField { + id: endTimeField + implicitWidth: timeMetrics.width + 3 + Layout.fillWidth: timeContainer.separate + //Layout.minimumWidth: timeMetrics.width + text: timeEntry ? timeEntry.EndTimeString : "" + } } } From e510a9edd9393f718c5a6305b5dbb5bfd50dbc8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 6 Dec 2019 10:08:48 +0100 Subject: [PATCH 68/83] Allow actually logging in (linux) --- src/ui/linux/TogglDesktop/LoginView.qml | 21 ++++++++++------ src/ui/linux/TogglDesktop/TogglButton.qml | 30 +++++++++++++---------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/ui/linux/TogglDesktop/LoginView.qml b/src/ui/linux/TogglDesktop/LoginView.qml index 74b5308312..aa887b048e 100644 --- a/src/ui/linux/TogglDesktop/LoginView.qml +++ b/src/ui/linux/TogglDesktop/LoginView.qml @@ -9,7 +9,14 @@ Item { property bool loggingIn: true property bool signingUp: !loggingIn - property bool everythingFilled: false + property bool everythingFilled: { + if (loggingIn) { + return username.filled && password.filled + } + else { + return username.filled && password.filled && country.filled && termsAndConditions.filled + } + } function act() { if (!everythingFilled) @@ -17,11 +24,7 @@ Item { if (loggingIn) toggl.login(username.text, password.text) else if (signingUp) { - if (signupWithEmail.checked) - toggl.signup(username.text, password.text, country.selectedID) - else if (signupWithGoogle.checked) { - // don't do anything in this case - } + toggl.signup(username.text, password.text, country.selectedID) } } @@ -127,6 +130,7 @@ Item { focus: true anchors.horizontalCenter: parent.horizontalCenter placeholderText: qsTr("Email address") + property bool filled: text.length > 0 // change this to a regexp eventually onAccepted: act() } @@ -139,6 +143,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter placeholderText: "Password" echoMode: TextField.Password + property bool filled: text.length > 0 onAccepted: act() } @@ -166,6 +171,7 @@ Item { textRole: "Text" currentIndex: -1 property int selectedID: toggl.countries && toggl.countries[currentIndex] ? toggl.countries[currentIndex].ID : -1 + property bool filled: selectedID >= 0 displayText: currentIndex < 0 ? "Please select your country" : currentText } @@ -176,13 +182,14 @@ Item { anchors.horizontalCenter: parent.horizontalCenter TogglCheckBox { id: termsAndConditions + property bool acceptable: checked } Text { id: termsAndConditionsText Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true wrapMode: Text.WrapAtWordBoundaryOrAnywhere - font.pointSize: 9 + font.pixelSize: 9 text: "I agree to terms of service and privacy policy" color: mainPalette.text } diff --git a/src/ui/linux/TogglDesktop/TogglButton.qml b/src/ui/linux/TogglDesktop/TogglButton.qml index 759ef2e978..072359339f 100644 --- a/src/ui/linux/TogglDesktop/TogglButton.qml +++ b/src/ui/linux/TogglDesktop/TogglButton.qml @@ -11,20 +11,24 @@ Button { id: background control: control } - contentItem: RowLayout { + contentItem: Rectangle { anchors.fill: parent - spacing: icon.status == Image.Ready ? 3 : 0 - Image { - id: icon - Layout.alignment: Qt.AlignCenter - } - Text { - id: text - Layout.alignment: Qt.AlignCenter - color: control.enabled ? control.pressed | control.checked ? mainPalette.text - : mainPalette.text - : disabledPalette.text - text: control.text + color: "blue" + RowLayout { + anchors.fill: parent + spacing: icon.status == Image.Ready ? 3 : 0 + Image { + id: icon + Layout.alignment: Qt.AlignCenter + } + Text { + id: text + Layout.alignment: Qt.AlignCenter + color: control.enabled ? control.pressed | control.checked ? mainPalette.text + : mainPalette.text + : disabledPalette.text + text: control.text + } } } } From 624a7a43ccc57dd2f2c286036fc772db728aeae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 6 Dec 2019 10:45:09 +0100 Subject: [PATCH 69/83] Start implementing the date picker (linux) --- .../linux/TogglDesktop/TimeEntryListItem.qml | 59 +++++++++++++++++-- .../TogglDesktop/TogglButtonBackground.qml | 12 ++-- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index 1f461db0da..33a001fdeb 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -137,9 +137,9 @@ Item { TogglTextField { id: durationField - width: timeContainer.separate ? parent.width : timeMetrics.width + 3 + width: timeContainer.separate ? parent.width : timeMetrics.width + 6 height: implicitHeight - implicitWidth: timeMetrics.width + 3 + implicitWidth: timeMetrics.width + 6 text: timeEntry ? timeEntry.Duration : "" } @@ -157,7 +157,7 @@ Item { } TogglTextField { id: startTimeField - implicitWidth: timeMetrics.width + 3 + implicitWidth: timeMetrics.width + 6 //Layout.minimumWidth: timeMetrics.width Layout.fillWidth: timeContainer.separate text: timeEntry ? timeEntry.StartTimeString : "" @@ -169,7 +169,7 @@ Item { } TogglTextField { id: endTimeField - implicitWidth: timeMetrics.width + 3 + implicitWidth: timeMetrics.width + 6 Layout.fillWidth: timeContainer.separate //Layout.minimumWidth: timeMetrics.width text: timeEntry ? timeEntry.EndTimeString : "" @@ -177,11 +177,58 @@ Item { } } - TogglTextField { + TogglButtonBackground { visible: expanded Layout.fillWidth: true Layout.columnSpan: 3 - text: timeEntry ? (new Date(Date(timeEntry.Started)).toLocaleDateString(Qt.locale(), Locale.ShortFormat)) : "" + + MouseArea { + anchors.fill: parent + onClicked: console.log("This would open a calendar") + } + MouseArea { + anchors { + right: leftSeparator.left + top: parent.top + bottom: parent.bottom + left: parent.left + } + onClicked: console.log("Date--") + } + MouseArea { + anchors { + left: rightSeparator.right + top: parent.top + bottom: parent.bottom + right: parent.right + } + onClicked: console.log("Date++") + } + + Rectangle { + id: leftSeparator + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.leftMargin: 32 + width: 1 + color: parent.borderColor + } + Rectangle { + id: rightSeparator + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.rightMargin: 32 + width: 1 + color: parent.borderColor + } + + Text { + color: mainPalette.text + anchors.centerIn: parent + text: timeEntry ? (new Date(Date(timeEntry.Started)).toLocaleDateString(Qt.locale(), Locale.ShortFormat)) : "" + } } diff --git a/src/ui/linux/TogglDesktop/TogglButtonBackground.qml b/src/ui/linux/TogglDesktop/TogglButtonBackground.qml index a04cbe9460..3bd7d88ad8 100644 --- a/src/ui/linux/TogglDesktop/TogglButtonBackground.qml +++ b/src/ui/linux/TogglDesktop/TogglButtonBackground.qml @@ -7,15 +7,15 @@ Rectangle { implicitHeight: 32 radius: 6 - property color borderColor: control.enabled ? mainPalette.borderColor : disabledPalette.button + property color borderColor: !control || control.enabled ? mainPalette.borderColor : disabledPalette.button //property color shadowColor: control.enabled ? control.checked ? "#595959" : control.hovered ? "#e5e5e5" : "#d4d4d4" : baseColor //property color baseColor: control.enabled ? control.checked ? "#7a7a7a" : control.hovered ? "#d4d4d4" : "#c3c3c3" : "#b2b2b2" - property color shadowColor: control.enabled ? control.pressed | control.checked ? mixColors(palette.button, mainPalette.buttonText, 0.5) - : control.hovered ? mixColors(palette.button, mainPalette.light, 0.5) - : mixColors(palette.button, mainPalette.light, 0.5) + property color shadowColor: !control || control.enabled ? !control || control.pressed || control.checked ? mixColors(mainPalette.button, mainPalette.buttonText, 0.5) + : control.hovered ? mixColors(mainPalette.button, mainPalette.light, 0.5) + : mixColors(mainPalette.button, mainPalette.light, 0.5) : baseColor - property color baseColor: control.enabled ? control.pressed | control.checked ? mixColors(palette.buttonText, mainPalette.button, 0.66) - : control.hovered ? mixColors(palette.button, mainPalette.light, 0.5) + property color baseColor: !control || control.enabled ? !control || control.pressed || control.checked ? mixColors(mainPalette.buttonText, mainPalette.button, 0.66) + : control.hovered ? mixColors(mainPalette.button, mainPalette.light, 0.5) : mainPalette.button : disabledPalette.button From 0d9204a38147844ae3b53352b2721b8f2a3a862b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 9 Dec 2019 13:24:36 +0100 Subject: [PATCH 70/83] Make the Arrow element separate (linux) --- src/ui/linux/TogglDesktop/Arrow.qml | 23 +++++++++++++++++++ src/ui/linux/TogglDesktop/Resources.qrc | 1 + .../linux/TogglDesktop/TimeEntryEditView.qml | 19 ++------------- .../linux/TogglDesktop/TimeEntryListItem.qml | 8 +++++++ 4 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/Arrow.qml diff --git a/src/ui/linux/TogglDesktop/Arrow.qml b/src/ui/linux/TogglDesktop/Arrow.qml new file mode 100644 index 0000000000..9a6e901676 --- /dev/null +++ b/src/ui/linux/TogglDesktop/Arrow.qml @@ -0,0 +1,23 @@ +import QtQuick 2.0 + +Item { + width: 7 + height: 3 + Rectangle { + smooth: true + anchors.verticalCenter: parent.verticalCenter + width: 4 + height: 1 + rotation: 45 + opacity: 0.5 + } + Rectangle { + smooth: true + x: 3 + anchors.verticalCenter: parent.verticalCenter + width: 4 + height: 1 + rotation: -45 + opacity: 0.5 + } +} diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index 25259b6caa..24c0aef30f 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -39,5 +39,6 @@ TogglShadowBox.qml TimeEntryListHeader.qml TimeEntryLabel.qml + Arrow.qml diff --git a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml index 24a9bc5f75..602bd7446a 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryEditView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml @@ -55,25 +55,10 @@ Item { placeholderText: qsTr("Select project") text: timeEntry && (timeEntry.ClientLabel.length > 0 || timeEntry.ProjectLabel.length > 0) ? (timeEntry.ClientLabel + " . " + timeEntry.ProjectLabel) : "" - Rectangle { - smooth: true - opacity: 0.5 - width: 4 - height: 1 - rotation: 45 - anchors.verticalCenter: parent.verticalCenter - anchors.right: parent.right - anchors.rightMargin: 14.5 - } - Rectangle { - smooth: true - opacity: 0.5 - width: 4 - height: 1 - rotation: -45 + Arrow { anchors.verticalCenter: parent.verticalCenter anchors.right: parent.right - anchors.rightMargin: 11.5 + anchors.rightMargin: 12 } } TogglTextField { diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index 33a001fdeb..8e468adbaa 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -194,6 +194,10 @@ Item { left: parent.left } onClicked: console.log("Date--") + Arrow { + anchors.centerIn: parent + rotation: 90 + } } MouseArea { anchors { @@ -203,6 +207,10 @@ Item { right: parent.right } onClicked: console.log("Date++") + Arrow { + anchors.centerIn: parent + rotation: -90 + } } Rectangle { From e4e3bcc6a0e0fb83ba789fa4ce32eec73da347a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 9 Dec 2019 13:34:55 +0100 Subject: [PATCH 71/83] Fix the terminal spam from Autocomplete (linux) --- .../linux/TogglDesktop/AutocompleteView.qml | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/ui/linux/TogglDesktop/AutocompleteView.qml b/src/ui/linux/TogglDesktop/AutocompleteView.qml index b8e46242be..c28f04f44d 100644 --- a/src/ui/linux/TogglDesktop/AutocompleteView.qml +++ b/src/ui/linux/TogglDesktop/AutocompleteView.qml @@ -19,32 +19,32 @@ Item { onVisibleChanged: list.currentIndex = -1 function upPressed() { - console.log("Current index: " + list.currentIndex) - console.log("There is " + list.count + " rows") - console.log("Model reports " + model.count() + " rows") + //console.log("Current index: " + list.currentIndex) + //console.log("There is " + list.count + " rows") + //console.log("Model reports " + model.count() + " rows") for (var i = list.currentIndex - 1; i > 0 ; --i) { if (model.get(i).Type < 10) { list.currentIndex = i list.positionViewAtIndex(i, ListView.Center) - console.log("Current index: " + list.currentIndex) + //console.log("Current index: " + list.currentIndex) return } } - console.log("Current index: " + list.currentIndex) + //console.log("Current index: " + list.currentIndex) } function downPressed() { - console.log("Current index: " + list.currentIndex) - console.log("There is " + list.count + " rows") - console.log("Model reports " + model.count() + " rows") + //console.log("Current index: " + list.currentIndex) + //console.log("There is " + list.count + " rows") + //console.log("Model reports " + model.count() + " rows") for (var i = list.currentIndex + 1; i < list.count; i++) { if (model.get(i).Type < 10) { list.currentIndex = i list.positionViewAtIndex(i, ListView.Center) - console.log("Current index: " + list.currentIndex) + //console.log("Current index: " + list.currentIndex) return } } - console.log("Current index: " + list.currentIndex) + //console.log("Current index: " + list.currentIndex) } Text { @@ -90,9 +90,9 @@ Item { } delegate: Loader { // TODO use the enum instead of magic values - sourceComponent: autocompleteData.Type === 13 ? workspaceDelegate : - autocompleteData.Type === 12 ? clientDelegate : - autocompleteData.Type === 11 ? headerDelegate : + sourceComponent: autocompleteData && autocompleteData.Type === 13 ? workspaceDelegate : + autocompleteData && autocompleteData.Type === 12 ? clientDelegate : + autocompleteData && autocompleteData.Type === 11 ? headerDelegate : regularDelegate property var autocompleteData: modelData property int index: index @@ -111,7 +111,7 @@ Item { anchors.centerIn: parent color: mainPalette.text - text: autocompleteData.Description + text: autocompleteData && autocompleteData.Description font.pointSize: 14 } Rectangle { @@ -142,7 +142,7 @@ Item { x: 9 color: mainPalette.text - text: autocompleteData.Description + text: autocompleteData && autocompleteData.Description } } } @@ -158,7 +158,7 @@ Item { anchors.left: parent.left anchors.leftMargin: 12 anchors.verticalCenter: parent.verticalCenter - text: autocompleteData.Description + text: autocompleteData && autocompleteData.Description color: mainPalette.text font.pointSize: 11 } @@ -180,11 +180,11 @@ Item { verticalAlignment: Text.AlignVCenter x: 9 textFormat: Text.RichText - property string timeEntry: autocompleteData.Description - property string project: autocompleteData.ProjectLabel.length > 0 ? + property string timeEntry: autocompleteData ? autocompleteData.Description : "" + property string project: autocompleteData && autocompleteData.ProjectLabel.length > 0 ? " • " + autocompleteData.ProjectLabel + "" : "" - property string task: autocompleteData.TaskLabel.length > 0 ? " " + autocompleteData.TaskLabel : "" + property string task: autocompleteData && autocompleteData.TaskLabel.length > 0 ? " " + autocompleteData.TaskLabel : "" text: (ListView.isCurrentItem ? "Iscurrent" : "") + timeEntry + project + task font.pointSize: 12 color: mainPalette.text From 0f9fbc19e29fdd6812ca7e01cb9d8fe64546bc21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 9 Dec 2019 17:35:23 +0100 Subject: [PATCH 72/83] Implement an embedded calendar widget (linux) --- src/ui/linux/TogglDesktop/Resources.qrc | 2 + src/ui/linux/TogglDesktop/TimeEntryLabel.qml | 3 +- .../linux/TogglDesktop/TimeEntryListItem.qml | 127 +++++-------- .../linux/TogglDesktop/TogglCalendarEdit.qml | 168 ++++++++++++++++++ .../TogglDesktop/TogglCalendarEditMonth.qml | 57 ++++++ 5 files changed, 271 insertions(+), 86 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/TogglCalendarEdit.qml create mode 100644 src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index 24c0aef30f..ccb3a693a4 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -40,5 +40,7 @@ TimeEntryListHeader.qml TimeEntryLabel.qml Arrow.qml + TogglCalendarEdit.qml + TogglCalendarEditMonth.qml diff --git a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml index 69b5e4993c..ba6b725777 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml @@ -4,7 +4,8 @@ import QtQuick.Layouts 1.12 Item { property QtObject timeEntry: null property bool editable: false - Layout.preferredHeight: editable ? timeEntryField.height * 2 + 12 : timeEntryText.height * 2 + 12 + Layout.preferredHeight: height + height: editable ? timeEntryField.height * 2 + 6 : timeEntryText.height * 2 + 6 Text { id: timeEntryText diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index 8e468adbaa..738d7b9606 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -19,7 +19,7 @@ Item { opacity: listView.itemExpanded && !expanded ? 0.5 : 0.0 Behavior on opacity { NumberAnimation { duration: 120 } } color: "dark gray" - MouseArea { anchors.fill: parent } + //MouseArea { anchors.fill: parent } } Rectangle { @@ -52,27 +52,29 @@ Item { hoverEnabled: !expanded anchors.fill: parent onClicked: { - console.log("B") - console.log("AAA " + index) - expanded = !expanded - listView.gotoIndex(index) - /* - if (timeEntry.Group) + if (timeEntry.Group) { toggl.toggleEntriesGroup(timeEntry.GroupName) - else - toggl.editTimeEntry(timeEntry.GUID, "description") - */ + } + else { + expanded = !expanded + listView.gotoIndex(index) + } } } - RowLayout { - anchors.fill: parent - anchors.margins: 12 + Row { + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: 12 + } spacing: 9 Rectangle { - Layout.alignment: Qt.AlignVCenter + id: groupContainer visible: (timeEntry.Group | timeEntry.GroupOpen) && ! expanded opacity: timeEntry.GroupOpen ? 0.0 : 1.0 + anchors.verticalCenter: parent.verticalCenter width: 24 height: 24 radius: 4 @@ -90,11 +92,19 @@ Item { } } - ColumnLayout { + Column { id: contentLayout - Layout.fillHeight: true - Layout.fillWidth: true - spacing: 3 + width: { + if (expanded) + return parent.width + else { + if (groupContainer.visible) + return parent.width - groupContainer.width - durationText.width - startButton.width - 27 + else + return parent.width - durationText.width - startButton.width - 18 + } + } + spacing: 6 Text { text: "Details" visible: expanded @@ -102,7 +112,7 @@ Item { font.capitalization: Font.AllUppercase } TimeEntryLabel { - Layout.fillWidth: true + width: parent.width Behavior on width { NumberAnimation { duration: 120 } } @@ -111,7 +121,7 @@ Item { } TogglTextField { - Layout.fillWidth: true + width: parent.width opacity: expanded ? 1.0 : 0.0 Behavior on opacity { NumberAnimation { duration: 120 } } visible: opacity > 0.0 @@ -127,9 +137,9 @@ Item { Item { id: timeContainer visible: expanded - Layout.fillWidth: true + width: parent.width property bool separate: timeContainer.width < (durationField.implicitWidth + startTimeField.implicitWidth + timeArrow.implicitWidth + endTimeField.implicitWidth + 12) - Layout.preferredHeight: durationField.height + (timeContainer.separate ? startTimeField.height + 3 : 0) + height: durationField.height + (timeContainer.separate ? startTimeField.height + 3 : 0) TextMetrics { id: timeMetrics text: "+00:00 AM " @@ -177,72 +187,17 @@ Item { } } - TogglButtonBackground { + TogglCalendarEdit { + id: calendarComponent visible: expanded - Layout.fillWidth: true - Layout.columnSpan: 3 - - MouseArea { - anchors.fill: parent - onClicked: console.log("This would open a calendar") - } - MouseArea { - anchors { - right: leftSeparator.left - top: parent.top - bottom: parent.bottom - left: parent.left - } - onClicked: console.log("Date--") - Arrow { - anchors.centerIn: parent - rotation: 90 - } - } - MouseArea { - anchors { - left: rightSeparator.right - top: parent.top - bottom: parent.bottom - right: parent.right - } - onClicked: console.log("Date++") - Arrow { - anchors.centerIn: parent - rotation: -90 - } - } - - Rectangle { - id: leftSeparator - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.left: parent.left - anchors.leftMargin: 32 - width: 1 - color: parent.borderColor - } - Rectangle { - id: rightSeparator - anchors.top: parent.top - anchors.bottom: parent.bottom - anchors.right: parent.right - anchors.rightMargin: 32 - width: 1 - color: parent.borderColor - } - - Text { - color: mainPalette.text - anchors.centerIn: parent - text: timeEntry ? (new Date(Date(timeEntry.Started)).toLocaleDateString(Qt.locale(), Locale.ShortFormat)) : "" - } + width: parent.width + clip: true } Text { visible: expanded - Layout.fillWidth: true + width: parent.width Layout.columnSpan: 3 text: timeEntry ? timeEntry.WorkspaceName : "" color: mainPalette.windowText @@ -250,14 +205,15 @@ Item { Item { visible: expanded - Layout.fillHeight: true - Layout.fillWidth: true + width: parent.width + height: 3 } } Text { + id: durationText visible: !expanded - Layout.alignment: Qt.AlignVCenter + anchors.verticalCenter: parent.verticalCenter text: timeEntry.Duration color: mainPalette.text } @@ -265,6 +221,7 @@ Item { visible: !expanded id: startButton opacity: delegateMouse.containsMouse ? 1.0 : 0.0 + anchors.verticalCenter: parent.verticalCenter width: 20 height: 20 MouseArea { diff --git a/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml b/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml new file mode 100644 index 0000000000..e284bad7ed --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml @@ -0,0 +1,168 @@ +import QtQuick 2.6 +import Qt.labs.calendar 1.0 +import QtQuick.Controls 1.4 + +TogglButtonBackground { + id: calendarComponent + visible: expanded + width: parent.width + clip: true + Behavior on height { NumberAnimation { duration: 120 } } + + height: calendar.visible ? implicitHeight + calendar.height + 12: implicitHeight + + property var date: timeEntry ? new Date(Date(timeEntry.Started)) : null + property string dayOfWeek: date ? date.toLocaleDateString(Qt.locale(), "dddd") : 0 + property int day: date ? date.toLocaleDateString(Qt.locale(), "dd") : 0 + property int month: date ? date.toLocaleDateString(Qt.locale(), "MM") : 0 + property int year: date ? date.toLocaleDateString(Qt.locale(), "yyyy") : 0 + + Item { + id: calendar + visible: false + y: parent.implicitHeight + 6 + width: parent.width + height: childrenRect.height + DayOfWeekRow { + id: dayOfWeekRow + width: monthScrollView.viewport.width + delegate: Text { + color: mainPalette.text + text: model.shortName + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + ScrollView { + id: monthScrollView + anchors.top: dayOfWeekRow.bottom + anchors.margins: 6 + width: parent.width + height: thisMonth.height + Component.onCompleted: { + flickableItem.contentY = thisMonth.y + } + Item { + x: 3 + y: 3 + width: monthScrollView.viewport.width - 6 + height: nextNextMonth.y + nextNextMonth.height - nextNextMonth.endOffset + 6 + TogglCalendarEditMonth { + id: previousPreviousMonth + width: parent.width + month: (calendarComponent.month - 3) % 12 + year: calendarComponent.year + Math.floor((calendarComponent.month - 3) / 12) + } + TogglCalendarEditMonth { + id: previousMonth + y: previousPreviousMonth.y + previousPreviousMonth.height - previousPreviousMonth.offset + width: parent.width + month: (calendarComponent.month - 2) % 12 + year: calendarComponent.year + Math.floor((calendarComponent.month - 2) / 12) + } + TogglCalendarEditMonth { + id: thisMonth + y: previousMonth.y + previousMonth.height - previousMonth.offset + width: parent.width + month: (calendarComponent.month - 1) % 12 + year: calendarComponent.year + Math.floor((calendarComponent.month - 1) / 12) + } + TogglCalendarEditMonth { + id: nextMonth + y: thisMonth.y + thisMonth.height - thisMonth.offset + width: parent.width + month: (calendarComponent.month - 0) % 12 + year: calendarComponent.year + Math.floor((calendarComponent.month - 0) / 12) + } + TogglCalendarEditMonth { + id: nextNextMonth + y: nextMonth.y + nextMonth.height - nextMonth.offset + width: parent.width + month: (calendarComponent.month + 1) % 12 + year: calendarComponent.year + Math.floor((calendarComponent.month + 1) / 12) + } + } + } + } + + Rectangle { + anchors { + left: parent.left + right: parent.right + bottom: calendar.top + bottomMargin: 6 + } + visible: calendar.visible + height: 1 + color: parent.borderColor + } + + MouseArea { + anchors { + top: parent.top + left: leftSeparator.right + right: rightSeparator.left + } + height: parent.implicitHeight + + onClicked: { + console.log("This would open a calendar") + calendar.visible = !calendar.visible + } + } + + Rectangle { + id: leftSeparator + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 32 + height: parent.implicitHeight + width: 1 + color: parent.borderColor + } + + MouseArea { + anchors { + right: leftSeparator.left + top: parent.top + left: parent.left + } + height: parent.implicitHeight + onClicked: console.log("Date--") + Arrow { + anchors.centerIn: parent + rotation: 90 + } + } + Rectangle { + id: rightSeparator + anchors.top: parent.top + anchors.right: parent.right + anchors.rightMargin: 32 + width: 1 + height: parent.implicitHeight + color: parent.borderColor + } + MouseArea { + anchors { + left: rightSeparator.right + top: parent.top + right: parent.right + } + height: parent.implicitHeight + onClicked: console.log("Date++") + Arrow { + anchors.centerIn: parent + rotation: -90 + } + } + + Text { + color: mainPalette.text + anchors.horizontalCenter: parent.horizontalCenter + y: parent.implicitHeight / 2 - height / 2 + //text: timeEntry ? (new Date(Date(timeEntry.Started)).toLocaleDateString(Qt.locale(), Locale.ShortFormat)) : "" + text: calendarComponent.dayOfWeek + ", " + calendarComponent.day + "/" + calendarComponent.month + "/" + calendarComponent.year + } +} diff --git a/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml b/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml new file mode 100644 index 0000000000..e66181de53 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml @@ -0,0 +1,57 @@ +import QtQuick 2.6 +import Qt.labs.calendar 1.0 +import QtQuick.Controls 1.4 + +MonthGrid { + id: monthGrid + property Item firstNextMonth: null + property real offset: firstNextMonth ? (height - firstNextMonth.y) : 0 + property Item firstWeekDayNextMonth: null + property real endOffset: firstWeekDayNextMonth ? (height - firstWeekDayNextMonth.y) : 0 + + delegate: Item { + implicitWidth: Math.max(dateDelegateMetrics.width, dateDelegateMetrics.height) * 2 + implicitHeight: width + opacity: monthGrid.month === model.month ? 1.0 : 0.0 + onYChanged: { + if (monthGrid.month < model.month || (monthGrid.month === Calendar.December && model.month === Calendar.January)) { + if (model.day === 1) { + monthGrid.firstNextMonth = this + } + if (index % 7 && model.day >= 1 && model.day <= 7) { + monthGrid.firstWeekDayNextMonth = this + } + } + } + TextMetrics { + id: dateDelegateMetrics + text: "00" + font.pointSize: 12 + } + Column { + anchors.centerIn: parent + Text { + color: (calendarComponent.month - 1) === model.month ? mainPalette.text : mainPalette.borderColor + anchors.horizontalCenter: parent.horizontalCenter + font.pointSize: 12 + font.bold: model.today + text: model.day + Rectangle { + z: -1 + anchors.centerIn: parent + height: parent.height + 10 + width: height + color: Qt.rgba(50/255.0, 215/255.0, 75/255.0, 1.0) + visible: calendarComponent.day === model.day && (calendarComponent.month - 1) === model.month + radius: 4 + } + } + Text { + visible: model.day === 1 + color: "red" + text: monthGrid.title.slice(0, 3).toUpperCase() + font.pointSize: 12 + } + } + } +} From 7f1fb92075fd5bbcb32500bc22060a8f76d03f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 9 Dec 2019 17:38:45 +0100 Subject: [PATCH 73/83] Improve Calendar performance by loading it on demand (linux) --- src/ui/linux/TogglDesktop/TimeEntryListItem.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index 738d7b9606..1f1225b817 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -187,14 +187,14 @@ Item { } } - TogglCalendarEdit { + Loader { id: calendarComponent - visible: expanded + source: expanded ? "TogglCalendarEdit.qml" : "" width: parent.width clip: true + visible: expanded } - Text { visible: expanded width: parent.width From f9dcf2ec4ce19215669d2142891bf3f1af2e677f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 10 Dec 2019 10:57:11 +0100 Subject: [PATCH 74/83] Fix some more QML errors in the terminal (linux) --- .../linux/TogglDesktop/TimeEntryListItem.qml | 16 ++++---- .../TogglDesktop/TogglCalendarEditMonth.qml | 38 ++++++++++++++----- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml index 1f1225b817..2c1bcf9eef 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -29,7 +29,7 @@ Item { rightMargin: anchors.leftMargin topMargin: 0 } - color: delegateMouse.containsMouse ? mainPalette.listBackground : timeEntry.GroupOpen ? mainPalette.listBackground : mainPalette.base + color: delegateMouse.containsMouse ? mainPalette.listBackground : timeEntry && timeEntry.GroupOpen ? mainPalette.listBackground : mainPalette.base TogglShadowBox { anchors.fill: parent @@ -72,23 +72,23 @@ Item { Rectangle { id: groupContainer - visible: (timeEntry.Group | timeEntry.GroupOpen) && ! expanded - opacity: timeEntry.GroupOpen ? 0.0 : 1.0 + visible: timeEntry && (timeEntry.Group | timeEntry.GroupOpen) && ! expanded + opacity: timeEntry && timeEntry.GroupOpen ? 0.0 : 1.0 anchors.verticalCenter: parent.verticalCenter width: 24 height: 24 radius: 4 - color: timeEntry.GroupOpen ? "dark green" : mainPalette.base + color: timeEntry && timeEntry.GroupOpen ? "dark green" : mainPalette.base border { - color: timeEntry.GroupOpen ? "transparent" : mainPalette.alternateBase + color: timeEntry && timeEntry.GroupOpen ? "transparent" : mainPalette.alternateBase width: 0.5 } Text { - color: timeEntry.GroupOpen ? "light green" : mainPalette.alternateBase + color: timeEntry && timeEntry.GroupOpen ? "light green" : mainPalette.alternateBase anchors.centerIn: parent verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter - text: timeEntry.GroupItemCount + text: timeEntry ? timeEntry.GroupItemCount : "" } } @@ -214,7 +214,7 @@ Item { id: durationText visible: !expanded anchors.verticalCenter: parent.verticalCenter - text: timeEntry.Duration + text: timeEntry && timeEntry.Duration color: mainPalette.text } Item { diff --git a/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml b/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml index e66181de53..98d2748e96 100644 --- a/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml +++ b/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml @@ -10,9 +10,9 @@ MonthGrid { property real endOffset: firstWeekDayNextMonth ? (height - firstWeekDayNextMonth.y) : 0 delegate: Item { + id: dayRoot implicitWidth: Math.max(dateDelegateMetrics.width, dateDelegateMetrics.height) * 2 implicitHeight: width - opacity: monthGrid.month === model.month ? 1.0 : 0.0 onYChanged: { if (monthGrid.month < model.month || (monthGrid.month === Calendar.December && model.month === Calendar.January)) { if (model.day === 1) { @@ -29,6 +29,8 @@ MonthGrid { font.pointSize: 12 } Column { + id: dateColumn + opacity: monthGrid.month === model.month ? 1.0 : 0.0 anchors.centerIn: parent Text { color: (calendarComponent.month - 1) === model.month ? mainPalette.text : mainPalette.borderColor @@ -36,15 +38,6 @@ MonthGrid { font.pointSize: 12 font.bold: model.today text: model.day - Rectangle { - z: -1 - anchors.centerIn: parent - height: parent.height + 10 - width: height - color: Qt.rgba(50/255.0, 215/255.0, 75/255.0, 1.0) - visible: calendarComponent.day === model.day && (calendarComponent.month - 1) === model.month - radius: 4 - } } Text { visible: model.day === 1 @@ -53,5 +46,30 @@ MonthGrid { font.pointSize: 12 } } + Rectangle { + z: -1 + anchors.centerIn: dateColumn + height: parent.height + 3 + width: height + color: Qt.rgba(50/255.0, 215/255.0, 75/255.0, 1.0) + opacity: calendarComponent.day === model.day && (calendarComponent.month - 1) === model.month ? 1.0 : + dayMouse.containsMouse ? 0.5 : 0.0 + Behavior on opacity { + NumberAnimation { + duration: 120 + } + } + + radius: 4 + MouseArea { + id: dayMouse + anchors.fill: parent + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + console.log("Day " + model.day + " clicked") + } + } + } } } From 209009ef83359ba07c18bf6cbf7668aafff4af1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Tue, 10 Dec 2019 14:54:18 +0100 Subject: [PATCH 75/83] Make space for the calendar by hiding some UI elements (linux) --- .../linux/TogglDesktop/TimeEntryListView.qml | 184 ++++++++++-------- .../linux/TogglDesktop/TogglCalendarEdit.qml | 7 +- .../TogglDesktop/TogglCalendarEditMonth.qml | 6 +- 3 files changed, 105 insertions(+), 92 deletions(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryListView.qml b/src/ui/linux/TogglDesktop/TimeEntryListView.qml index 51335709c4..defa32119f 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryListView.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -13,115 +13,127 @@ Rectangle { property real itemHeight: 64 property real shadowWidth: 9 - ColumnLayout { - anchors.fill: parent - spacing: 0 - TimerView { - Layout.fillWidth: true - height: 64 - z: 1 + TimerView { + id: timer + anchors { + left: parent.left + right: parent.right + top: parent.top } + height: timeEntryListView.itemExpanded ? 0 : 64 + z: timeEntryListView.itemExpanded ? -1 : 1 + clip: timeEntryListView.itemExpanded + Behavior on height { NumberAnimation { duration: 120 } } + } - ScrollView { - id: timeEntryList - clip: true - Layout.fillWidth: true - Layout.fillHeight: true - property real viewportWidth: viewport.width - ListView { - id: timeEntryListView - model: toggl.timeEntries - section.property: "modelData.DateHeader" - section.delegate: TimeEntryListHeader { - height: headerHeight + sectionMargin - width: parent.width - Rectangle { - z: 1 - anchors.fill: parent - visible: opacity > 0.0 - opacity: timeEntryListView.itemExpanded ? 0.5 : 0.0 - Behavior on opacity { NumberAnimation { duration: 120 } } - color: "dark gray" - MouseArea { anchors.fill: parent } - } - } + ScrollView { + id: timeEntryList + clip: true + anchors { + top: timer.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } - function gotoIndex(idx) { - anim.running = false; + property real viewportWidth: viewport.width + verticalScrollBarPolicy: timeEntryListView.itemExpanded ? Qt.ScrollBarAlwaysOff : Qt.ScrollBarAsNeeded - var pos = timeEntryListView.contentY; - var destPos; + ListView { + id: timeEntryListView + model: toggl.timeEntries - timeEntryListView.positionViewAtIndex(idx, ListView.Beginning); - destPos = timeEntryListView.contentY; + property bool itemExpanded: false - anim.from = pos; - anim.to = destPos; - anim.running = true; + section.property: "modelData.DateHeader" + section.criteria: ViewSection.FullString + section.delegate: TimeEntryListHeader { + height: headerHeight + sectionMargin + width: parent.width + Rectangle { + z: 1 + anchors.fill: parent + visible: opacity > 0.0 + opacity: timeEntryListView.itemExpanded ? 0.5 : 0.0 + Behavior on opacity { NumberAnimation { duration: 120 } } + color: "dark gray" + MouseArea { anchors.fill: parent } } - NumberAnimation { id: anim; target: timeEntryListView; property: "contentY"; duration: 120 } + } - property bool itemExpanded: false + function gotoIndex(idx) { + anim.running = false; - delegate: TimeEntryListItem { - timeEntry: modelData - listView: timeEntryListView - //index: index - } + var pos = timeEntryListView.contentY; + var destPos; - add: Transition { - id: addTransition - NumberAnimation { - properties: "y" - from: addTransition.ViewTransition.destination.y - itemHeight - duration: 120 - } + timeEntryListView.positionViewAtIndex(idx, ListView.Beginning); + destPos = timeEntryListView.contentY; + + anim.from = pos; + anim.to = destPos; + anim.running = true; + } + NumberAnimation { id: anim; target: timeEntryListView; property: "contentY"; duration: 120 } + + delegate: TimeEntryListItem { + timeEntry: modelData + listView: timeEntryListView + //index: index + } + + add: Transition { + id: addTransition + NumberAnimation { + properties: "y" + from: addTransition.ViewTransition.destination.y - itemHeight + duration: 120 } - addDisplaced: Transition { - NumberAnimation { - properties: "y" - duration: 120 - } + } + addDisplaced: Transition { + NumberAnimation { + properties: "y" + duration: 120 } + } - moveDisplaced: Transition { - NumberAnimation { - from: -height - properties: "y" - duration: 120 - } + moveDisplaced: Transition { + NumberAnimation { + from: -height + properties: "y" + duration: 120 } + } - move: Transition { - NumberAnimation { - properties: "y" - duration: 120 - } + move: Transition { + NumberAnimation { + properties: "y" + duration: 120 } + } - remove: Transition { - ParallelAnimation { - NumberAnimation { - duration: 120 - properties: "opacity" - from: 1.0 - to: 0.0 - } - NumberAnimation { - duration: 120 - properties: "x" - from: 0 - to: width - } + remove: Transition { + ParallelAnimation { + NumberAnimation { + duration: 120 + properties: "opacity" + from: 1.0 + to: 0.0 } - } - removeDisplaced: Transition { NumberAnimation { - properties: "y" duration: 120 + properties: "x" + from: 0 + to: width } } } + removeDisplaced: Transition { + NumberAnimation { + properties: "y" + duration: 120 + } + } } } } diff --git a/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml b/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml index e284bad7ed..b594da0f05 100644 --- a/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml +++ b/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml @@ -9,6 +9,7 @@ TogglButtonBackground { clip: true Behavior on height { NumberAnimation { duration: 120 } } + property bool calendarOpen: false height: calendar.visible ? implicitHeight + calendar.height + 12: implicitHeight property var date: timeEntry ? new Date(Date(timeEntry.Started)) : null @@ -19,7 +20,7 @@ TogglButtonBackground { Item { id: calendar - visible: false + visible: calendarOpen y: parent.implicitHeight + 6 width: parent.width height: childrenRect.height @@ -93,7 +94,7 @@ TogglButtonBackground { bottom: calendar.top bottomMargin: 6 } - visible: calendar.visible + visible: calendarOpen height: 1 color: parent.borderColor } @@ -108,7 +109,7 @@ TogglButtonBackground { onClicked: { console.log("This would open a calendar") - calendar.visible = !calendar.visible + calendarOpen = !calendarOpen } } diff --git a/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml b/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml index 98d2748e96..308257ed99 100644 --- a/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml +++ b/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml @@ -26,7 +26,7 @@ MonthGrid { TextMetrics { id: dateDelegateMetrics text: "00" - font.pointSize: 12 + font.pixelSize: 12 } Column { id: dateColumn @@ -35,7 +35,7 @@ MonthGrid { Text { color: (calendarComponent.month - 1) === model.month ? mainPalette.text : mainPalette.borderColor anchors.horizontalCenter: parent.horizontalCenter - font.pointSize: 12 + font.pixelSize: 12 font.bold: model.today text: model.day } @@ -43,7 +43,7 @@ MonthGrid { visible: model.day === 1 color: "red" text: monthGrid.title.slice(0, 3).toUpperCase() - font.pointSize: 12 + font.pixelSize: 12 } } Rectangle { From 3f8371cf1858e0cf9725c1eec48ed134bd53ea97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Wed, 11 Dec 2019 15:41:48 +0100 Subject: [PATCH 76/83] Set dates right (linux) --- src/ui/linux/TogglDesktop/TimeEntryLabel.qml | 3 +++ src/ui/linux/TogglDesktop/TogglCalendarEdit.qml | 2 +- src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml index ba6b725777..cc24f30608 100644 --- a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml +++ b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml @@ -85,6 +85,9 @@ Item { Behavior on opacity { NumberAnimation { duration: 120 } } text: timeEntry && timeEntry.Description.length > 0 ? timeEntry.Description : "" placeholderText: qsTr("Add description") + onAccepted: { + toggl.setTimeEntryDescription(timeEntry.GUID, timeEntryField.text) + } } diff --git a/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml b/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml index b594da0f05..c6058b0278 100644 --- a/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml +++ b/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml @@ -12,7 +12,7 @@ TogglButtonBackground { property bool calendarOpen: false height: calendar.visible ? implicitHeight + calendar.height + 12: implicitHeight - property var date: timeEntry ? new Date(Date(timeEntry.Started)) : null + property var date: timeEntry ? new Date(timeEntry.Started * 1000) : null property string dayOfWeek: date ? date.toLocaleDateString(Qt.locale(), "dddd") : 0 property int day: date ? date.toLocaleDateString(Qt.locale(), "dd") : 0 property int month: date ? date.toLocaleDateString(Qt.locale(), "MM") : 0 diff --git a/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml b/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml index 308257ed99..ab0edd069e 100644 --- a/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml +++ b/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml @@ -68,6 +68,10 @@ MonthGrid { cursorShape: Qt.PointingHandCursor onClicked: { console.log("Day " + model.day + " clicked") + var date = String(model.year)+"."+String(model.month+1).padStart(2, '0')+"."+String(model.day).padStart(2, '0') + console.warn((new Date(date).getTime() / 1000).toFixed(0)) + var timestamp = (new Date(date).getTime() / 1000).toFixed(0) + toggl.setTimeEntryDate(modelData.GUID, timestamp) } } } From 8bf3589acc01486bb26a581ce55e9ee80a4d38dc Mon Sep 17 00:00:00 2001 From: IndrekV Date: Fri, 13 Dec 2019 14:45:57 +0200 Subject: [PATCH 77/83] Added app build command for qt in mac --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index b6f8223ac8..7ad7608073 100644 --- a/Makefile +++ b/Makefile @@ -144,6 +144,11 @@ qa: lint fmt cppclean test fmt: fmt_lib fmt_ui + +app_qt: + mkdir -p build && cd build && cmake -DOPENSSL_ROOT_DIR="/usr/local/opt/openssl@1.1" -DTOGGL_BUILD_TESTS=OFF .. && make + ./build/src/ui/linux/TogglDesktop/TogglDesktop + ifeq ($(uname), Linux) app: mkdir -p build && cd build && cmake .. && make From 0c7e42490453b484ccff6dcaf48000c6e7060e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 13 Dec 2019 19:22:49 +0100 Subject: [PATCH 78/83] Improve login view look (especially the horrible blue) (linux) --- src/ui/linux/TogglDesktop/LoginView.qml | 3 ++- src/ui/linux/TogglDesktop/TogglButton.qml | 3 +-- src/ui/linux/TogglDesktop/TogglTextField.qml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/linux/TogglDesktop/LoginView.qml b/src/ui/linux/TogglDesktop/LoginView.qml index aa887b048e..18b3f93c33 100644 --- a/src/ui/linux/TogglDesktop/LoginView.qml +++ b/src/ui/linux/TogglDesktop/LoginView.qml @@ -74,6 +74,7 @@ Item { fillMode: Image.PreserveAspectFit antialiasing: true mipmap: true + smooth: true } /* @@ -199,7 +200,7 @@ Item { width: parent.width anchors.horizontalCenter: parent.horizontalCenter text: signingUp ? "Sign up" : "Log in" - color: Qt.rgba(229/255.0, 85/255.0, 98/255.0, 1.0) + color: Qt.rgba(255/255.0, 45/255.0, 85/255.0, 1.0) textColor: "white" enabled: everythingFilled onClicked: act() diff --git a/src/ui/linux/TogglDesktop/TogglButton.qml b/src/ui/linux/TogglDesktop/TogglButton.qml index 072359339f..52f2021ec9 100644 --- a/src/ui/linux/TogglDesktop/TogglButton.qml +++ b/src/ui/linux/TogglDesktop/TogglButton.qml @@ -11,9 +11,8 @@ Button { id: background control: control } - contentItem: Rectangle { + contentItem: Item { anchors.fill: parent - color: "blue" RowLayout { anchors.fill: parent spacing: icon.status == Image.Ready ? 3 : 0 diff --git a/src/ui/linux/TogglDesktop/TogglTextField.qml b/src/ui/linux/TogglDesktop/TogglTextField.qml index f15cefd49b..13b978281f 100644 --- a/src/ui/linux/TogglDesktop/TogglTextField.qml +++ b/src/ui/linux/TogglDesktop/TogglTextField.qml @@ -7,7 +7,7 @@ TextField { property bool flat: false background: Rectangle { border.width: flat ? 0 : 1 - radius: 6 + radius: 4 border.color: mainPalette.borderColor color: "transparent" } From bc6dae5b6a8ca8ac3aeee442603e31cf03070d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 13 Dec 2019 19:26:16 +0100 Subject: [PATCH 79/83] Return the preferences dialog (linux) --- src/ui/linux/TogglDesktop/CMakeLists.txt | 2 + src/ui/linux/TogglDesktop/mainwindow.cpp | 9 +- src/ui/linux/TogglDesktop/mainwindow.h | 4 + .../linux/TogglDesktop/preferencesdialog.cpp | 333 ++++++++++++ src/ui/linux/TogglDesktop/preferencesdialog.h | 69 +++ .../linux/TogglDesktop/preferencesdialog.ui | 476 ++++++++++++++++++ 6 files changed, 889 insertions(+), 4 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/preferencesdialog.cpp create mode 100644 src/ui/linux/TogglDesktop/preferencesdialog.h create mode 100644 src/ui/linux/TogglDesktop/preferencesdialog.ui diff --git a/src/ui/linux/TogglDesktop/CMakeLists.txt b/src/ui/linux/TogglDesktop/CMakeLists.txt index 1b946b18f2..70dcfed928 100644 --- a/src/ui/linux/TogglDesktop/CMakeLists.txt +++ b/src/ui/linux/TogglDesktop/CMakeLists.txt @@ -24,6 +24,7 @@ set(BINARY_SOURCE_FILES mainwindow.cpp networkmanagement.cpp powermanagement.cpp + preferencesdialog.cpp qmlobjectlist.cc settingsview.cpp singleapplication.cpp @@ -37,6 +38,7 @@ set(BINARY_SOURCE_FILES # it's better to list UI files idlenotificationwidget.ui mainwindow.ui + preferencesdialog.ui ) if (APPLE) diff --git a/src/ui/linux/TogglDesktop/mainwindow.cpp b/src/ui/linux/TogglDesktop/mainwindow.cpp index ad7d87b8f8..8216639c56 100644 --- a/src/ui/linux/TogglDesktop/mainwindow.cpp +++ b/src/ui/linux/TogglDesktop/mainwindow.cpp @@ -11,9 +11,10 @@ #include "macosSpecific.h" #endif -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , preferences_(new PreferencesDialog(this)) { ui->setupUi(this); ui->centralwidget->engine()->rootContext()->setContextProperty("toggl", new TogglApi(nullptr)); @@ -41,7 +42,7 @@ void MainWindow::on_actionUpdateCheck_triggered() { } void MainWindow::on_actionPreferences_triggered() { - // TODO + preferences_->show(); } void MainWindow::on_actionQuit_triggered() { diff --git a/src/ui/linux/TogglDesktop/mainwindow.h b/src/ui/linux/TogglDesktop/mainwindow.h index abbd8bcaaa..c990eacc11 100644 --- a/src/ui/linux/TogglDesktop/mainwindow.h +++ b/src/ui/linux/TogglDesktop/mainwindow.h @@ -3,6 +3,8 @@ #include +#include "preferencesdialog.h" + namespace Ui { class MainWindow; } @@ -33,6 +35,8 @@ private slots: void on_actionLogout_triggered(); private: Ui::MainWindow *ui; + + PreferencesDialog *preferences_; }; #endif // MAINWINDOW_H diff --git a/src/ui/linux/TogglDesktop/preferencesdialog.cpp b/src/ui/linux/TogglDesktop/preferencesdialog.cpp new file mode 100644 index 0000000000..9998a234f7 --- /dev/null +++ b/src/ui/linux/TogglDesktop/preferencesdialog.cpp @@ -0,0 +1,333 @@ +// Copyright 2014 Toggl Desktop developers. +#include // NOLINT + +#include "./preferencesdialog.h" +#include "./ui_preferencesdialog.h" + +#include "./toggl.h" +#include "./settingsview.h" + +PreferencesDialog::PreferencesDialog(QWidget *parent) : QDialog(parent), +ui(new Ui::PreferencesDialog) { + ui->setupUi(this); + + connect(TogglApi::instance, SIGNAL(displaySettings(bool,SettingsView*)), // NOLINT + this, SLOT(displaySettings(bool,SettingsView*))); // NOLINT + + connect(TogglApi::instance, SIGNAL(displayLogin(bool,uint64_t)), // NOLINT + this, SLOT(displayLogin(bool,uint64_t))); // NOLINT + + connect(TogglApi::instance, SIGNAL(updateShowHideShortcut()), // NOLINT + this, SLOT(updateShowHideShortcut())); // NOLINT + + connect(TogglApi::instance, SIGNAL(updateContinueStopShortcut()), // NOLINT + this, SLOT(updateContinueStopShortcut())); // NOLINT + + connect(ui->dayCheckbox_1, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); + connect(ui->dayCheckbox_2, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); + connect(ui->dayCheckbox_3, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); + connect(ui->dayCheckbox_4, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); + connect(ui->dayCheckbox_5, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); + connect(ui->dayCheckbox_6, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); + connect(ui->dayCheckbox_7, &QCheckBox::clicked, this, &PreferencesDialog::onDayCheckboxClicked); + connect(ui->stopEntry, &QCheckBox::clicked, this, &PreferencesDialog::onStopEntryCheckboxClicked); + keyId = 0; +} + +PreferencesDialog::~PreferencesDialog() { + delete ui; +} + +void PreferencesDialog::displaySettings(const bool open, + SettingsView *settings) { + if (open) { + show(); + } + + ui->useSystemProxySettings->setChecked(settings->AutodetectProxy); + + ui->useProxy->setChecked(settings->UseProxy); + ui->proxyHost->setText(settings->ProxyHost); + ui->proxyPort->setText(QString::number(settings->ProxyPort)); + ui->proxyUsername->setText(settings->ProxyUsername); + ui->proxyPassword->setText(settings->ProxyPassword); + + ui->recordTimeline->setChecked(settings->RecordTimeline); // user based! + + ui->idleDetection->setChecked(settings->UseIdleDetection); + ui->idleMinutes->setText(QString::number(settings->IdleMinutes)); + ui->idleMinutes->setEnabled(ui->idleDetection->isChecked()); + + ui->remindToTrackTime->setChecked(settings->Reminder); + ui->reminderMinutes->setText(QString::number(settings->ReminderMinutes)); + ui->reminderMinutes->setEnabled(ui->remindToTrackTime->isChecked()); + ui->dayCheckbox_1->setEnabled(ui->remindToTrackTime->isChecked()); + ui->dayCheckbox_2->setEnabled(ui->remindToTrackTime->isChecked()); + ui->dayCheckbox_3->setEnabled(ui->remindToTrackTime->isChecked()); + ui->dayCheckbox_4->setEnabled(ui->remindToTrackTime->isChecked()); + ui->dayCheckbox_5->setEnabled(ui->remindToTrackTime->isChecked()); + ui->dayCheckbox_6->setEnabled(ui->remindToTrackTime->isChecked()); + ui->dayCheckbox_7->setEnabled(ui->remindToTrackTime->isChecked()); + ui->reminderStartTimeEdit->setEnabled(ui->remindToTrackTime->isChecked()); + ui->reminderEndTimeEdit->setEnabled(ui->remindToTrackTime->isChecked()); + + ui->pomodoroTimer->setChecked(settings->Pomodoro); + ui->pomodoroMinutes->setText(QString::number(settings->PomodoroMinutes)); + ui->pomodoroMinutes->setEnabled(ui->pomodoroTimer->isChecked()); + + ui->pomodoroBreakTimer->setChecked(settings->PomodoroBreak); + ui->pomodoroBreakTimer->setEnabled(settings->Pomodoro); + ui->pomodoroBreakMinutes->setText( + QString::number(settings->PomodoroBreakMinutes)); + ui->pomodoroBreakMinutes->setEnabled( + ui->pomodoroTimer->isChecked() + && ui->pomodoroBreakTimer->isChecked()); + + ui->focusAppOnShortcut->setChecked((settings->FocusOnShortcut)); + + ui->dayCheckbox_1->setChecked(settings->RemindOnMonday); + ui->dayCheckbox_2->setChecked(settings->RemindOnTuesday); + ui->dayCheckbox_3->setChecked(settings->RemindOnWednesday); + ui->dayCheckbox_4->setChecked(settings->RemindOnThursday); + ui->dayCheckbox_5->setChecked(settings->RemindOnFriday); + ui->dayCheckbox_6->setChecked(settings->RemindOnSaturday); + ui->dayCheckbox_7->setChecked(settings->RemindOnSunday); + + ui->reminderStartTimeEdit->setTime(settings->RemindStartTime); + ui->reminderEndTimeEdit->setTime(settings->RemindEndTime); + + ui->stopEntry->setChecked(settings->StopEntryOnShutdownSleep); + + QString sh(TogglApi::instance->getShowHideKey()); + if (sh.length() == 0) { + sh = "Record shortcut"; + } + ui->showHideButton->setText(sh); + + QString cs(TogglApi::instance->getContinueStopKey()); + if (cs.length() == 0) { + cs = "Record shortcut"; + } + ui->continueStopButton->setText(cs); +} + +void PreferencesDialog::displayLogin(const bool open, + const uint64_t user_id) { + ui->recordTimeline->setEnabled(!open && user_id); +} + +void PreferencesDialog::onDayCheckboxClicked(bool checked) { + Q_UNUSED(checked); + TogglApi::instance->setSettingsRemindDays( + ui->dayCheckbox_1->isChecked(), + ui->dayCheckbox_2->isChecked(), + ui->dayCheckbox_3->isChecked(), + ui->dayCheckbox_4->isChecked(), + ui->dayCheckbox_5->isChecked(), + ui->dayCheckbox_6->isChecked(), + ui->dayCheckbox_7->isChecked() + ); +} + +void PreferencesDialog::on_proxyHost_editingFinished() { + setProxySettings(); +} + +void PreferencesDialog::on_proxyPort_editingFinished() { + setProxySettings(); +} + +void PreferencesDialog::on_proxyUsername_editingFinished() { + setProxySettings(); +} + +void PreferencesDialog::on_proxyPassword_editingFinished() { + setProxySettings(); +} + +void PreferencesDialog::on_idleDetection_clicked(bool checked) { + TogglApi::instance->setSettingsUseIdleDetection(checked); +} + +void PreferencesDialog::on_recordTimeline_clicked(bool checked) { + TogglApi::instance->toggleTimelineRecording(checked); +} + +void PreferencesDialog::on_remindToTrackTime_clicked(bool checked) { + TogglApi::instance->setSettingsReminder(checked); +} + +void PreferencesDialog::on_pomodoroTimer_clicked(bool checked) { + TogglApi::instance->setSettingsPomodoro(checked); +} + +void PreferencesDialog::on_pomodoroBreakTimer_clicked(bool checked) { + TogglApi::instance->setSettingsPomodoroBreak(checked); +} + +void PreferencesDialog::updateShowHideShortcut() { + QString text(TogglApi::instance->getShowHideKey()); + if (text.length() == 0) { + text = "Record shortcut"; + } + ui->showHideButton->setText(text); +} + +void PreferencesDialog::updateContinueStopShortcut() { + QString text(TogglApi::instance->getContinueStopKey()); + if (text.length() == 0) { + text = "Record shortcut"; + } + ui->continueStopButton->setText(text); +} + +void PreferencesDialog::on_showHideClear_clicked() { + keySequence = ""; + keyId = 1; + saveCurrentShortcut(); +} + +void PreferencesDialog::on_continueStopClear_clicked() { + keySequence = ""; + keyId = 2; + saveCurrentShortcut(); +} + +void PreferencesDialog::on_showHideButton_clicked() { + ui->showHideButton->setText("Type shortcut"); + keyId = 1; +} + +void PreferencesDialog::on_reminderStartTimeEdit_editingFinished() { + TogglApi::instance->setSettingsRemindTimes( + ui->reminderStartTimeEdit->time(), + ui->reminderEndTimeEdit->time() + ); +} + +void PreferencesDialog::on_reminderEndTimeEdit_editingFinished() { + TogglApi::instance->setSettingsRemindTimes( + ui->reminderStartTimeEdit->time(), + ui->reminderEndTimeEdit->time() + ); +} + +void PreferencesDialog::on_continueStopButton_clicked() { + ui->continueStopButton->setText("Type shortcut"); + keyId = 2; +} + +void PreferencesDialog::keyPressEvent(QKeyEvent *event) { + if (keyId) { + keySequence = ""; + QKeyEvent *keyEvent = static_cast(event); + + int keyInt = keyEvent->key(); + Qt::Key key = static_cast(keyInt); + if (key == Qt::Key_unknown) { + return; + } + + // Reset to previous values + if (key == Qt::Key_Escape) { + if (keyId == 1) { + TogglApi::instance->updateShowHideShortcut(); + } + if (keyId == 2) { + TogglApi::instance->updateContinueStopShortcut(); + } + keyId = 0; + return; + } + + // the user have clicked just and + // only the special keys Ctrl, Shift, Alt, Meta. + if (key == Qt::Key_Control || + key == Qt::Key_Shift || + key == Qt::Key_Alt || + key == Qt::Key_Meta) { + return; + } + + // check for a combination of user clicks + Qt::KeyboardModifiers modifiers = keyEvent->modifiers(); + + if (modifiers & Qt::ShiftModifier) + keyInt += Qt::SHIFT; + if (modifiers & Qt::ControlModifier) + keyInt += Qt::CTRL; + if (modifiers & Qt::AltModifier) + keyInt += Qt::ALT; + if (modifiers & Qt::MetaModifier) + keyInt += Qt::META; + + keySequence = QKeySequence(keyInt).toString(QKeySequence::NativeText); + + if (keyId == 1) { + ui->showHideButton->setText(keySequence); + } else if (keyId == 2) { + ui->continueStopButton->setText(keySequence); + } + } +} + +void PreferencesDialog::keyReleaseEvent(QKeyEvent *event) { + Q_UNUSED(event); + saveCurrentShortcut(); +} + +void PreferencesDialog::saveCurrentShortcut() { + if (keyId == 1) { + TogglApi::instance->setShowHideKey(keySequence); + } else if (keyId == 2) { + TogglApi::instance->setContinueStopKey(keySequence); + } + + keyId = 0; + keySequence = ""; +} + +bool PreferencesDialog::setProxySettings() { + return TogglApi::instance->setProxySettings(ui->useProxy->isChecked(), + ui->proxyHost->text(), + ui->proxyPort->text().toULongLong(), + ui->proxyUsername->text(), + ui->proxyPassword->text()); +} + +void PreferencesDialog::on_useProxy_clicked(bool checked) { + Q_UNUSED(checked); + setProxySettings(); +} + +void PreferencesDialog::on_idleMinutes_editingFinished() { + TogglApi::instance->setSettingsIdleMinutes( + ui->idleMinutes->text().toULongLong()); +} + +void PreferencesDialog::on_reminderMinutes_editingFinished() { + TogglApi::instance->setSettingsReminderMinutes( + ui->reminderMinutes->text().toULongLong()); +} + +void PreferencesDialog::on_pomodoroMinutes_editingFinished() { + TogglApi::instance->setSettingsPomodoroMinutes( + ui->pomodoroMinutes->text().toULongLong()); +} + +void PreferencesDialog::on_pomodoroBreakMinutes_editingFinished() { + TogglApi::instance->setSettingsPomodoroBreakMinutes( + ui->pomodoroBreakMinutes->text().toULongLong()); +} + +void PreferencesDialog::on_useSystemProxySettings_clicked(bool checked) { + TogglApi::instance->setSettingsAutodetectProxy(checked); +} + +void PreferencesDialog::on_focusAppOnShortcut_clicked(bool checked) { + TogglApi::instance->setSettingsFocusOnShortcut(checked); +} + +void PreferencesDialog::onStopEntryCheckboxClicked(bool checked) { + TogglApi::instance->setSettingsStopEntryOnShutdown(checked); +} diff --git a/src/ui/linux/TogglDesktop/preferencesdialog.h b/src/ui/linux/TogglDesktop/preferencesdialog.h new file mode 100644 index 0000000000..d1b97d1e4a --- /dev/null +++ b/src/ui/linux/TogglDesktop/preferencesdialog.h @@ -0,0 +1,69 @@ +// Copyright 2014 Toggl Desktop developers. + +#ifndef SRC_UI_LINUX_TOGGLDESKTOP_PREFERENCESDIALOG_H_ +#define SRC_UI_LINUX_TOGGLDESKTOP_PREFERENCESDIALOG_H_ + +#include +#include +#include "./settingsview.h" + +namespace Ui { +class PreferencesDialog; +} + +class PreferencesDialog : public QDialog { + Q_OBJECT + + public: + explicit PreferencesDialog(QWidget *parent = 0); + ~PreferencesDialog(); + + private: + Ui::PreferencesDialog *ui; + int keyId; + QString keySequence; + + bool setSettings(); + bool setProxySettings(); + + private slots: // NOLINT + void displaySettings(const bool open, + SettingsView *settings); + + void displayLogin(const bool open, + const uint64_t user_id); + + void onDayCheckboxClicked(bool checked); + + void on_proxyHost_editingFinished(); + void on_proxyPort_editingFinished(); + void on_proxyUsername_editingFinished(); + void on_proxyPassword_editingFinished(); + void on_idleDetection_clicked(bool checked); + void on_recordTimeline_clicked(bool checked); + void on_remindToTrackTime_clicked(bool checked); + void on_pomodoroTimer_clicked(bool checked); + void on_pomodoroBreakTimer_clicked(bool checked); + void on_useProxy_clicked(bool checked); + void on_idleMinutes_editingFinished(); + void on_reminderMinutes_editingFinished(); + void on_pomodoroMinutes_editingFinished(); + void on_pomodoroBreakMinutes_editingFinished(); + void on_useSystemProxySettings_clicked(bool checked); + void on_focusAppOnShortcut_clicked(bool checked); + void on_continueStopClear_clicked(); + void on_showHideClear_clicked(); + void on_continueStopButton_clicked(); + void on_showHideButton_clicked(); + void on_reminderStartTimeEdit_editingFinished(); + void on_reminderEndTimeEdit_editingFinished(); + void keyPressEvent(QKeyEvent *event); + void keyReleaseEvent(QKeyEvent *event); + void saveCurrentShortcut(); + void onStopEntryCheckboxClicked(bool checked); + + void updateShowHideShortcut(); + void updateContinueStopShortcut(); +}; + +#endif // SRC_UI_LINUX_TOGGLDESKTOP_PREFERENCESDIALOG_H_ diff --git a/src/ui/linux/TogglDesktop/preferencesdialog.ui b/src/ui/linux/TogglDesktop/preferencesdialog.ui new file mode 100644 index 0000000000..2bfe0f9a9d --- /dev/null +++ b/src/ui/linux/TogglDesktop/preferencesdialog.ui @@ -0,0 +1,476 @@ + + + PreferencesDialog + + + + 0 + 0 + 451 + 447 + + + + Preferences + + + + + + + + + 0 + + + + General + + + + + + Pomodoro + + + + + + Pomodoro timer + + + + + + + + 60 + 16777215 + + + + 25 + + + 3 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + minutes + + + + + + + Pomodoro break timer + + + + + + + + 60 + 16777215 + + + + 25 + + + 3 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + minutes + + + + + + + + + + + 60 + 16777215 + + + + 5 + + + 3 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + minutes + + + + + + + Idle detection + + + + + + + Record timeline + + + + + + + Shortcuts + + + + + + Continue/Stop timer + + + + + + + + + + Record Shortcut + + + + + + + + 25 + 25 + + + + Clear shortcut + + + x + + + + + + + Show/Hide Toggl + + + + + + + + + + Record Shortcut + + + + + + + + 25 + 25 + + + + Clear Shortcut + + + x + + + + + + + Focus app on shortcut + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Stop running entry on computer sleep/shutdown + + + + + + + + Proxy + + + + + + Use system proxy settings + + + + + + + Use prox&y to connect to Toggl + + + false + + + true + + + false + + + + + + &Host + + + + + + + + + + Port + + + + + + + + + + Username + + + + + + + + + + Password + + + + + + + QLineEdit::Password + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + true + + + Reminder + + + + + + + + + Remind to track time + + + + + + + Sunday + + + + + + + Reminder end time + + + + + + + Thursday + + + + + + + Reminder start time + + + + + + + Wednesday + + + + + + + Reminder days + + + + + + + Friday + + + + + + + Saturday + + + + + + + Monday + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 60 + 16777215 + + + + 5 + + + 3 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Tuesday + + + + + + + + + + minutes + + + + + + + + + + + + From 169b6cf13cc47ada1cd2af87eb70a250140ccabf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Fri, 13 Dec 2019 22:29:44 +0100 Subject: [PATCH 80/83] Actually display errors (linux) --- src/ui/linux/TogglDesktop/Cross.qml | 23 ++++++ src/ui/linux/TogglDesktop/ErrorBubble.qml | 86 ++++++++++++++++++++++ src/ui/linux/TogglDesktop/ErrorOverlay.qml | 33 --------- src/ui/linux/TogglDesktop/MainWindow.qml | 18 ++--- src/ui/linux/TogglDesktop/Resources.qrc | 3 +- 5 files changed, 118 insertions(+), 45 deletions(-) create mode 100644 src/ui/linux/TogglDesktop/Cross.qml create mode 100644 src/ui/linux/TogglDesktop/ErrorBubble.qml delete mode 100644 src/ui/linux/TogglDesktop/ErrorOverlay.qml diff --git a/src/ui/linux/TogglDesktop/Cross.qml b/src/ui/linux/TogglDesktop/Cross.qml new file mode 100644 index 0000000000..ec123fbbad --- /dev/null +++ b/src/ui/linux/TogglDesktop/Cross.qml @@ -0,0 +1,23 @@ +import QtQuick 2.0 + +Item { + id: root + property real size: 7 + property color color: "black" + width: size + height: size + Rectangle { + color: root.color + width: Math.sqrt(2 * Math.pow(size, 2)) + anchors.centerIn: parent + height: 1 + rotation: 45 + } + Rectangle { + color: root.color + width: Math.sqrt(2 * Math.pow(size, 2)) + anchors.centerIn: parent + height: 1 + rotation: -45 + } +} diff --git a/src/ui/linux/TogglDesktop/ErrorBubble.qml b/src/ui/linux/TogglDesktop/ErrorBubble.qml new file mode 100644 index 0000000000..a5d0bedde3 --- /dev/null +++ b/src/ui/linux/TogglDesktop/ErrorBubble.qml @@ -0,0 +1,86 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 + +import QtQuick.Controls 2.12 + + +Column { + id: messageLayout + + property real maximumWidth: Infinity + width: maximumWidth + + function show(text) { + errorComponent.createObject(messageLayout, { "text": text } ) + } + + move: Transition { + NumberAnimation { + properties: "y" + } + } + add: Transition { + NumberAnimation { + properties: "x" + from: messageLayout.width + to: messageLayout.width - width + } + } + + Connections { + target: toggl + onDisplayError: { + show(errmsg) + } + } + + Component { + id: errorComponent + Item { + id: errorRoot + Layout.preferredHeight: height + Layout.preferredWidth: width + Layout.alignment: Qt.AlignRight + property alias text: errorText.text + height: errorContainer.height + 18 + width: errorContainer.width + 18 + Rectangle { + id: errorContainer + color: mainPalette.base + x: 9 + y: 9 + height: errorLayout.height + 12 + width: errorLayout.width + 12 + + RowLayout { + id: errorLayout + x: 6 + y: 6 + Text { + id: errorText + verticalAlignment: Text.AlignVCenter + color: "red" + } + + TogglButton { + id: closeButton + flat: true + implicitHeight: 15 + implicitWidth: implicitHeight + Cross { + anchors.centerIn: parent + } + onClicked: errorRoot.destroy() + } + } + TogglShadowBox { + anchors.fill: parent + shadowWidth: mainPalette.itemShadowSize + shadowColor: mainPalette.itemShadow + backgroundColor: mainPalette.listBackground + sides: TogglShadowBox.Side.Left | TogglShadowBox.Side.Right | TogglShadowBox.Side.Bottom | TogglShadowBox.Side.Top + } + } + } + } +} diff --git a/src/ui/linux/TogglDesktop/ErrorOverlay.qml b/src/ui/linux/TogglDesktop/ErrorOverlay.qml deleted file mode 100644 index 5aed37113b..0000000000 --- a/src/ui/linux/TogglDesktop/ErrorOverlay.qml +++ /dev/null @@ -1,33 +0,0 @@ -import QtQuick 2.12 -import QtQuick.Layouts 1.12 - -import QtQuick.Controls 2.12 - -Rectangle { - id: errorOverlay - color: "red" - y: -height - Behavior on y { NumberAnimation { duration: 120 } } - Connections { - target: toggl - onDisplayError: { - errorOverlay.y = 0 - errorText.text = errmsg - } - } - RowLayout { - anchors.fill: parent - anchors.margins: 6 - Text { - id: errorText - Layout.fillHeight: true - Layout.fillWidth: true - verticalAlignment: Text.AlignVCenter - } - Button { - text: "x" - onClicked: errorOverlay.y = -errorOverlay.height - implicitWidth: implicitHeight - } - } -} diff --git a/src/ui/linux/TogglDesktop/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml index 11c887df1e..73f34d25b2 100644 --- a/src/ui/linux/TogglDesktop/MainWindow.qml +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -38,17 +38,6 @@ Rectangle { colorGroup: SystemPalette.Disabled } - Rectangle { - anchors.fill: parent - color: mainPalette.base - clip: true - - ErrorOverlay { - width: parent.width - height: 48 - } - } - TextMetrics { id: termsAndConditionsMetrics font.pointSize: 9 @@ -87,4 +76,11 @@ Rectangle { visible: false anchors.fill: parent } + + ErrorBubble { + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.margins: 16 + maximumWidth: parent.width - 32 + } } diff --git a/src/ui/linux/TogglDesktop/Resources.qrc b/src/ui/linux/TogglDesktop/Resources.qrc index ccb3a693a4..f83967d06d 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -23,7 +23,7 @@ ../../windows/TogglDesktop/TogglDesktop/Resources/warning-icon.png MainWindow.qml LoginView.qml - ErrorOverlay.qml + ErrorBubble.qml TimeEntryListView.qml TimerView.qml TimeEntryEditView.qml @@ -42,5 +42,6 @@ Arrow.qml TogglCalendarEdit.qml TogglCalendarEditMonth.qml + Cross.qml From 4e4f0f07ea8b64a560d044f8f9dcfcf9a8463372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 6 Apr 2020 09:36:31 +0200 Subject: [PATCH 81/83] Probably fix build on macOS (lib) --- src/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d63172b982..943e0a91cb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,6 +81,7 @@ if(APPLE) ${LIBRARY_SOURCE_FILES} get_focused_window_mac.cc lib/osx/Kopsik/MacOSVersionChecker.mm + src/lib/osx/Kopsik/MacOSApplicationInfo.mm ) set(ADDITIONAL_LIBRARIES "-framework Foundation" From 431cd297de402e539814d4da5f8cb39973a126a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20B=C5=99=C3=ADza?= Date: Mon, 6 Apr 2020 09:40:05 +0200 Subject: [PATCH 82/83] Now actually probably fix it (lib) --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 943e0a91cb..2239ebc20b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -81,7 +81,7 @@ if(APPLE) ${LIBRARY_SOURCE_FILES} get_focused_window_mac.cc lib/osx/Kopsik/MacOSVersionChecker.mm - src/lib/osx/Kopsik/MacOSApplicationInfo.mm + lib/osx/Kopsik/MacOSApplicationInfo.mm ) set(ADDITIONAL_LIBRARIES "-framework Foundation" From 0e6b285e6182cafc666f73830f94f3322d707bc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0ebele?= Date: Mon, 6 Apr 2020 23:23:58 +0200 Subject: [PATCH 83/83] Hide autocomplete on timer start --- src/ui/linux/TogglDesktop/TimerView.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml index 777079b693..edf2aec317 100644 --- a/src/ui/linux/TogglDesktop/TimerView.qml +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -77,6 +77,7 @@ Rectangle { else { if (acceptableInput) { start() + autocomplete.visible = false } } }