diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6c0d787f97..7b02f1aa78 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,6 +8,48 @@ on: 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 + 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 + + # 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: Install the dependencies + run: | + brew install qt + brew install openssl + brew install cmake + - name: Build the application + run: | + mkdir build + cd build + 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 steps: @@ -108,7 +150,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: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 12642ca396..a2306708fc 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) @@ -40,11 +43,14 @@ 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) 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} ) @@ -83,5 +89,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/Makefile b/Makefile index 0d53a2dbe0..88a1ee72a8 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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a7eb293e98..2239ebc20b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,7 +15,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} @@ -29,6 +29,7 @@ include_directories( ${LUA_INCLUDE_DIRS} ${JSONCPP_INCLUDE_DIRS} ${POCO_INCLUDE_DIRS} + $ENV{OPENSSL_ROOT_DIR}/include ) # TogglDesktopLibrary sources @@ -49,7 +50,6 @@ set(LIBRARY_SOURCE_FILES database.cc feedback.cc formatter.cc - get_focused_window_linux.cc error.cc gui.cc netconf.cc @@ -76,11 +76,36 @@ 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 + lib/osx/Kopsik/MacOSApplicationInfo.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/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/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/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/AutocompleteView.qml b/src/ui/linux/TogglDesktop/AutocompleteView.qml new file mode 100644 index 0000000000..c28f04f44d --- /dev/null +++ b/src/ui/linux/TogglDesktop/AutocompleteView.qml @@ -0,0 +1,201 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 1.4 + +Item { + id: root + height: 500 + + property real maximumHeight: 500 + + property var model: null + property QtObject currentItem: list.currentItem ? list.currentItem.autocompleteData : null + + property string filter: "" + onFilterChanged: { + toggl.minitimerAutocomplete.setFilter(filter) + list.currentIndex = -1 + } + 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") + 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("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) + } + + Text { + z: 20 + text: list.currentItem ? list.currentItem.autocompleteData.Description : "" + } + + Rectangle { + z: -1 + anchors.fill: parent + color: setAlpha(mainPalette.shadow, 0.5) + } + + ScrollView { + id: scrollArea + width: parent.width + height: root.maximumHeight + clip: true + ListView { + id: list + model: root.model + currentIndex: -1 + onCurrentIndexChanged: { + console.log(currentIndex) + } + + 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 { + z: 30 + color: "light gray" + opacity: 0.5 + width: root.width + height: 24 + } + delegate: Loader { + // TODO use the enum instead of magic values + 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 + } + } + } + + Component { + id: workspaceDelegate + + Rectangle { + height: 40 + width: root.width + color: mainPalette.base + Text { + anchors.centerIn: parent + color: mainPalette.text + + text: autocompleteData && autocompleteData.Description + font.pointSize: 14 + } + Rectangle { + anchors { + left: parent.left + bottom: parent.bottom + right: parent.right + } + height: 1 + color: mainPalette.alternateBase + } + } + } + + Component { + id: clientDelegate + + + Rectangle { + height: 30 + width: root.width + color: mainPalette.base + Text { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.verticalCenter: parent.verticalCenter + height: 24 + x: 9 + color: mainPalette.text + + text: autocompleteData && autocompleteData.Description + } + } + } + + Component { + id: headerDelegate + + Rectangle { + height: 30 + width: root.width + color: mainPalette.base + Text { + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.verticalCenter: parent.verticalCenter + text: autocompleteData && autocompleteData.Description + color: mainPalette.text + font.pointSize: 11 + } + } + } + + Component { + id: regularDelegate + + Rectangle { + height: 30 + width: root.width + color: mainPalette.base + + Text { + anchors.fill: parent + anchors.left: parent.left + anchors.leftMargin: 18 + verticalAlignment: Text.AlignVCenter + x: 9 + textFormat: Text.RichText + property string timeEntry: autocompleteData ? autocompleteData.Description : "" + property string project: autocompleteData && autocompleteData.ProjectLabel.length > 0 ? + " • " + autocompleteData.ProjectLabel + "" : + "" + property string task: autocompleteData && autocompleteData.TaskLabel.length > 0 ? " " + autocompleteData.TaskLabel : "" + text: (ListView.isCurrentItem ? "Iscurrent" : "") + timeEntry + project + task + font.pointSize: 12 + color: mainPalette.text + } + MouseArea { + id: delegateMouse + anchors.fill: parent + hoverEnabled: true + onContainsMouseChanged: { + } + } + } + } +} diff --git a/src/ui/linux/TogglDesktop/CMakeLists.txt b/src/ui/linux/TogglDesktop/CMakeLists.txt index 48477f1c24..70dcfed928 100644 --- a/src/ui/linux/TogglDesktop/CMakeLists.txt +++ b/src/ui/linux/TogglDesktop/CMakeLists.txt @@ -15,67 +15,54 @@ 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 + mainwindow.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 + mainwindow.ui preferencesdialog.ui - timeentrycellwidget.ui - timeentryeditorwidget.ui - timeentrylistwidget.ui - timerwidget.ui - ) +if (APPLE) + 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::Widgets Qt5::DBus Qt5::WebEngine Qt5::Qml Qt5::Quick Qt5::QuickWidgets 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/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/LoginView.qml b/src/ui/linux/TogglDesktop/LoginView.qml new file mode 100644 index 0000000000..18b3f93c33 --- /dev/null +++ b/src/ui/linux/TogglDesktop/LoginView.qml @@ -0,0 +1,244 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 + +import QtQuick.Controls 2.12 + +Item { + anchors.fill: parent + + property bool loggingIn: true + property bool signingUp: !loggingIn + + property bool everythingFilled: { + if (loggingIn) { + return username.filled && password.filled + } + else { + return username.filled && password.filled && country.filled && termsAndConditions.filled + } + } + + function act() { + if (!everythingFilled) + return + if (loggingIn) + toggl.login(username.text, password.text) + else if (signingUp) { + toggl.signup(username.text, password.text, country.selectedID) + } + } + + property real shadowWidth: 9 + + Rectangle { + anchors.fill: parent + color: "white" // TODO + } + + 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: 196 + spacing: 12 + 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" + width: parent.width * 0.33 + fillMode: Image.PreserveAspectFit + antialiasing: true + mipmap: true + smooth: true + } + + /* + Item { + width: parent.width + height: loginSwitch.height + + Text { + anchors.right: loginSwitch.left + anchors.rightMargin: 9 + anchors.verticalCenter: parent.verticalCenter + text: "Login" + color: loggingIn ? mainPalette.text : disabledPalette.text + } + TogglSwitch { + id: loginSwitch + anchors.horizontalCenter: parent.horizontalCenter + Layout.alignment: Qt.AlignCenter + onCheckedChanged: username.forceActiveFocus() + } + Text { + anchors.left: loginSwitch.right + anchors.leftMargin: 9 + anchors.verticalCenter: parent.verticalCenter + text: "Sign up" + color: signingUp ? mainPalette.text : disabledPalette.text + } + } + + RowLayout { + width: parent.width + visible: signingUp + TogglButton { + id: signupWithEmail + Layout.fillWidth: true + checkable: true + autoExclusive: true + checked: true + text: "With email" + } + TogglButton { + id: signupWithGoogle + Layout.fillWidth: true + checkable: true + autoExclusive: true + text: "With Google" + } + } + */ + + TogglTextField { + width: parent.width + id: username + focus: true + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: qsTr("Email address") + property bool filled: text.length > 0 // change this to a regexp eventually + onAccepted: act() + } + + Column { + width: parent.width + spacing: 6 + TogglTextField { + width: parent.width + id: password + anchors.horizontalCenter: parent.horizontalCenter + placeholderText: "Password" + echoMode: TextField.Password + property bool filled: text.length > 0 + 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 { + id: country + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + visible: signingUp + model: toggl.countries + 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 + } + + RowLayout { + id: termsAndConditionsRow + width: parent.width + visible: signingUp + 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.pixelSize: 9 + text: "I agree to terms of service and privacy policy" + color: mainPalette.text + } + } + + TogglButton { + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + text: signingUp ? "Sign up" : "Log in" + color: Qt.rgba(255/255.0, 45/255.0, 85/255.0, 1.0) + textColor: "white" + enabled: everythingFilled + onClicked: act() + } + + TogglButton { + width: parent.width + anchors.horizontalCenter: parent.horizontalCenter + 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/MainWindow.qml b/src/ui/linux/TogglDesktop/MainWindow.qml new file mode 100644 index 0000000000..73f34d25b2 --- /dev/null +++ b/src/ui/linux/TogglDesktop/MainWindow.qml @@ -0,0 +1,86 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Layouts 1.12 + +import QtQuick.Controls 1.4 + +Rectangle { + id: mainWindow + visible: true + width: 400 + 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, + ) + } + + function setAlpha(color, alpha) { + return Qt.rgba(color.r, color.g, color.b, alpha) + } + + SystemPalette { + id: mainPalette + property bool isDark: (itemShadow.r + itemShadow.g + itemShadow.b) < 1 + + 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 + colorGroup: SystemPalette.Disabled + } + + TextMetrics { + id: termsAndConditionsMetrics + font.pointSize: 9 + text: "I agree to terms of service and privacy policy" + } + + Connections { + target: toggl + onDisplayLogin: { + if (open) { + mainView.source = "LoginView.qml" + timeEntryEdit.visible = false + } + } + onDisplayTimeEntryList: { + if (open) { + mainView.source = "TimeEntryListView.qml" + timeEntryEdit.visible = false + } + } + onDisplayTimeEntryEditor: { + if (open) { + timeEntryEdit.timeEntry = view + timeEntryEdit.visible = true + } + } + } + + Loader { + id: mainView + anchors.fill: parent + } + + TimeEntryEditView { + id: timeEntryEdit + 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 0222db948f..f83967d06d 100644 --- a/src/ui/linux/TogglDesktop/Resources.qrc +++ b/src/ui/linux/TogglDesktop/Resources.qrc @@ -21,5 +21,27 @@ images/group_icon_open.svg images/down_arrow.png ../../windows/TogglDesktop/TogglDesktop/Resources/warning-icon.png + MainWindow.qml + LoginView.qml + ErrorBubble.qml + TimeEntryListView.qml + TimerView.qml + TimeEntryEditView.qml + AutocompleteView.qml + TogglButton.qml + TogglTextField.qml + TogglComboBox.qml + TogglCheckBox.qml + TogglSwitch.qml + TogglButtonBackground.qml + StartButton.qml + TimeEntryListItem.qml + TogglShadowBox.qml + TimeEntryListHeader.qml + TimeEntryLabel.qml + Arrow.qml + TogglCalendarEdit.qml + TogglCalendarEditMonth.qml + Cross.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/TimeEntryEditView.qml b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml new file mode 100644 index 0000000000..602bd7446a --- /dev/null +++ b/src/ui/linux/TogglDesktop/TimeEntryEditView.qml @@ -0,0 +1,314 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Item { + property var timeEntry: null + + MouseArea { + anchors.fill: parent + hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton + } + + Rectangle { + anchors.fill: parent + color: mainPalette.base + opacity: 0.5 + } + + Rectangle { + anchors.fill: parent + anchors.margins: 9 + radius: 6 + + color: mainPalette.base + opacity: visible ? 1.0 : 0.0 + Behavior on opacity { + NumberAnimation { + duration: 120 + } + } + + border { + width: 1 + color: mainPalette.shadow + } + + ColumnLayout { + anchors.fill: parent + 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 + placeholderText: qsTr("Select project") + text: timeEntry && (timeEntry.ClientLabel.length > 0 || timeEntry.ProjectLabel.length > 0) ? (timeEntry.ClientLabel + " . " + timeEntry.ProjectLabel) : "" + + Arrow { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + anchors.rightMargin: 12 + } + } + TogglTextField { + Layout.fillWidth: true + placeholderText: qsTr("Add tags") + } + + /* + 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 + 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 + 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" + } + + + 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 + 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: qsTr("New:", "tag") + 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 = "" + } + } + } + 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: qsTr("Duration") + color: mainPalette.windowText + font.capitalization: Font.AllUppercase + } + RowLayout { + Layout.fillWidth: true + TextMetrics { + id: timeMetrics + text: "00:00:00 AM" + } + + TogglTextField { + Layout.minimumWidth: timeMetrics.width + text: timeEntry ? timeEntry.Duration : "" + } + Item { + height: 1 + Layout.fillWidth: true + } + + 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: qsTr("Done") + onClicked: toggl.viewTimeEntryList() + } + TogglButton { + text: qsTr("Delete") + onClicked: toggl.deleteTimeEntry(timeEntry.GUID) + } + TogglButton { + text: qsTr("Cancel") + onClicked: toggl.viewTimeEntryList() + } + } + Item { + Layout.fillHeight: true + Layout.fillWidth: true + } + } + } +} diff --git a/src/ui/linux/TogglDesktop/TimeEntryLabel.qml b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml new file mode 100644 index 0000000000..cc24f30608 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TimeEntryLabel.qml @@ -0,0 +1,109 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 + +Item { + property QtObject timeEntry: null + property bool editable: false + Layout.preferredHeight: height + height: editable ? timeEntryField.height * 2 + 6 : timeEntryText.height * 2 + 6 + + 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 + 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 { + 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 + width: height + radius: height / 2 + 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 : mainPalette.text) : disabledPalette.text + font.pixelSize: 12 + } + Text { + visible: timeEntry && timeEntry.TaskLabel.length > 0 + text: timeEntry && timeEntry.TaskLabel.length ? "- " + timeEntry.TaskLabel : "" + color: mainPalette.text + font.pixelSize: 12 + } + Text { + visible: timeEntry && timeEntry.ClientLabel.length > 0 + text: timeEntry && timeEntry.ClientLabel ? "• " + timeEntry.ClientLabel : "" + color: mainPalette.text + font.pixelSize: 12 + } + Item { + 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") + onAccepted: { + toggl.setTimeEntryDescription(timeEntry.GUID, timeEntryField.text) + } + } + + + 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/TimeEntryListHeader.qml b/src/ui/linux/TogglDesktop/TimeEntryListHeader.qml new file mode 100644 index 0000000000..d95c603e3b --- /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: mainPalette.base + + TogglShadowBox { + anchors.fill: parent + shadowWidth: mainPalette.itemShadowSize + shadowColor: mainPalette.itemShadow + backgroundColor: mainPalette.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: mainPalette.windowText + } + } +} diff --git a/src/ui/linux/TogglDesktop/TimeEntryListItem.qml b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml new file mode 100644 index 0000000000..2c1bcf9eef --- /dev/null +++ b/src/ui/linux/TogglDesktop/TimeEntryListItem.qml @@ -0,0 +1,297 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 + +Item { + z: index + height: visible ? expanded ? contentLayout.height + 16 : 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 + leftMargin: 12 + rightMargin: anchors.leftMargin + topMargin: 0 + } + color: delegateMouse.containsMouse ? mainPalette.listBackground : timeEntry && timeEntry.GroupOpen ? mainPalette.listBackground : mainPalette.base + + TogglShadowBox { + anchors.fill: parent + shadowWidth: mainPalette.itemShadowSize + shadowColor: mainPalette.itemShadow + backgroundColor: mainPalette.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: mainPalette.listBackground + } + + MouseArea { + id: delegateMouse + hoverEnabled: !expanded + anchors.fill: parent + onClicked: { + if (timeEntry.Group) { + toggl.toggleEntriesGroup(timeEntry.GroupName) + } + else { + expanded = !expanded + listView.gotoIndex(index) + } + } + } + Row { + anchors { + top: parent.top + left: parent.left + right: parent.right + margins: 12 + } + spacing: 9 + + Rectangle { + id: groupContainer + 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 && timeEntry.GroupOpen ? "dark green" : mainPalette.base + border { + color: timeEntry && timeEntry.GroupOpen ? "transparent" : mainPalette.alternateBase + width: 0.5 + } + Text { + color: timeEntry && timeEntry.GroupOpen ? "light green" : mainPalette.alternateBase + anchors.centerIn: parent + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + text: timeEntry ? timeEntry.GroupItemCount : "" + } + } + + Column { + id: contentLayout + 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 + color: mainPalette.windowText + font.capitalization: Font.AllUppercase + } + TimeEntryLabel { + width: parent.width + + Behavior on width { NumberAnimation { duration: 120 } } + + timeEntry: modelData + editable: expanded + } + + TogglTextField { + width: parent.width + 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 + } + Item { + id: timeContainer + visible: expanded + width: parent.width + property bool separate: timeContainer.width < (durationField.implicitWidth + startTimeField.implicitWidth + timeArrow.implicitWidth + endTimeField.implicitWidth + 12) + height: durationField.height + (timeContainer.separate ? startTimeField.height + 3 : 0) + TextMetrics { + id: timeMetrics + text: "+00:00 AM " + } + + TogglTextField { + id: durationField + width: timeContainer.separate ? parent.width : timeMetrics.width + 6 + height: implicitHeight + implicitWidth: timeMetrics.width + 6 + text: timeEntry ? timeEntry.Duration : "" + } + + 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 + 6 + //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 + 6 + Layout.fillWidth: timeContainer.separate + //Layout.minimumWidth: timeMetrics.width + text: timeEntry ? timeEntry.EndTimeString : "" + } + } + } + + Loader { + id: calendarComponent + source: expanded ? "TogglCalendarEdit.qml" : "" + width: parent.width + clip: true + visible: expanded + } + + Text { + visible: expanded + width: parent.width + Layout.columnSpan: 3 + text: timeEntry ? timeEntry.WorkspaceName : "" + color: mainPalette.windowText + } + + Item { + visible: expanded + width: parent.width + height: 3 + } + } + + Text { + id: durationText + visible: !expanded + anchors.verticalCenter: parent.verticalCenter + text: timeEntry && timeEntry.Duration + color: mainPalette.text + } + Item { + visible: !expanded + id: startButton + opacity: delegateMouse.containsMouse ? 1.0 : 0.0 + anchors.verticalCenter: parent.verticalCenter + width: 20 + height: 20 + MouseArea { + anchors.fill: parent + onClicked: toggl.continueTimeEntry(timeEntry.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 new file mode 100644 index 0000000000..defa32119f --- /dev/null +++ b/src/ui/linux/TogglDesktop/TimeEntryListView.qml @@ -0,0 +1,139 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 1.4 +import QtGraphicalEffects 1.0 + +Rectangle { + id: root + anchors.fill: parent + color: mainPalette.listBackground + + property real sectionMargin: Math.max(2 * shadowWidth, 12) + property real headerHeight: 32 + property real itemHeight: 64 + property real shadowWidth: 9 + + 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 + anchors { + top: timer.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } + + property real viewportWidth: viewport.width + verticalScrollBarPolicy: timeEntryListView.itemExpanded ? Qt.ScrollBarAlwaysOff : Qt.ScrollBarAsNeeded + + ListView { + id: timeEntryListView + model: toggl.timeEntries + + property bool itemExpanded: false + + 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 } + } + } + + 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 } + + 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 + } + } + + moveDisplaced: Transition { + NumberAnimation { + from: -height + 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 + } + } + } + removeDisplaced: Transition { + NumberAnimation { + properties: "y" + duration: 120 + } + } + } + } +} diff --git a/src/ui/linux/TogglDesktop/TimerView.qml b/src/ui/linux/TogglDesktop/TimerView.qml new file mode 100644 index 0000000000..edf2aec317 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TimerView.qml @@ -0,0 +1,170 @@ +import QtQuick 2.12 +import QtQuick.Layouts 1.12 +import QtQuick.Controls 2.12 + +Rectangle { + id: root + color: mainPalette.base + height: 64 + + property bool running: false + property var runningTimeEntry: null + + function start() { + if (!running) { + toggl.start(description.text, "", 0, 0, "", false) + description.text = "" + } + } + + Connections { + target: toggl + onDisplayStoppedTimerState: { + running = false + runningTimeEntry = null + } + onDisplayRunningTimerState: { + running = true + runningTimeEntry = view + } + } + + MouseArea { + anchors.fill: timerContainer + onClicked: { + if (running) { + toggl.editRunningTimeEntry("description") + mouse.accepted = true + } + else { + mouse.accepted = false + } + } + } + + RowLayout { + id: timerContainer + x: 12 + height: parent.height + width: parent.width - 24 + + ColumnLayout { + Layout.fillHeight: true + Layout.fillWidth: true + spacing: 6 + + Item { + Layout.fillHeight: true + } + TogglTextField { + id: description + focus: true + flat: true + visible: !running + Layout.fillWidth: true + background: Item {} + font.pixelSize: 12 + placeholderText: "What are you doing?" + Keys.onUpPressed: autocomplete.upPressed() + Keys.onDownPressed: autocomplete.downPressed() + Keys.onEscapePressed: autocomplete.visible = false + Keys.onReturnPressed: { + if (autocomplete.visible && autocomplete.currentItem) { + if (autocomplete.currentItem.Description) + toggl.start(autocomplete.currentItem.Description, "", autocomplete.currentItem.TaskID, autocomplete.currentItem.ProjectID, autocomplete.currentItem.Tags, false) + autocomplete.visible = false + } + else { + if (acceptableInput) { + start() + autocomplete.visible = false + } + } + } + onTextEdited: { + if (focus) { + autocomplete.visible = true + } + } + onFocusChanged: if (!focus) autocomplete.visible = false + 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 + } + } + TimeEntryLabel { + Layout.fillHeight: true + Layout.fillWidth: true + timeEntry: runningTimeEntry + visible: runningTimeEntry + } + + Item { + 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" + } + + StartButton { + id: startButton + running: root.running + Layout.alignment: Qt.AlignVCenter + onClicked: { + if (running) { + toggl.stop() + } + else { + start() + } + } + } + } + + 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" } + } + } + + 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 + } +} diff --git a/src/ui/linux/TogglDesktop/TogglButton.qml b/src/ui/linux/TogglDesktop/TogglButton.qml new file mode 100644 index 0000000000..52f2021ec9 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglButton.qml @@ -0,0 +1,33 @@ +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: Item { + anchors.fill: parent + 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 new file mode 100644 index 0000000000..3bd7d88ad8 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglButtonBackground.qml @@ -0,0 +1,29 @@ +import QtQuick 2.0 + +Rectangle { + property Item control + clip: true + implicitWidth: 100 + implicitHeight: 32 + radius: 6 + + 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 || 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 || 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 + + Behavior on shadowColor { ColorAnimation { duration: 120 } } + Behavior on baseColor { ColorAnimation { duration: 120 } } + + color: mainPalette.base + + border.width: 1 + border.color: borderColor +} diff --git a/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml b/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml new file mode 100644 index 0000000000..c6058b0278 --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglCalendarEdit.qml @@ -0,0 +1,169 @@ +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 } } + + property bool calendarOpen: false + height: calendar.visible ? implicitHeight + calendar.height + 12: implicitHeight + + 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 + property int year: date ? date.toLocaleDateString(Qt.locale(), "yyyy") : 0 + + Item { + id: calendar + visible: calendarOpen + 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: calendarOpen + 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") + calendarOpen = !calendarOpen + } + } + + 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..ab0edd069e --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglCalendarEditMonth.qml @@ -0,0 +1,79 @@ +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 { + id: dayRoot + implicitWidth: Math.max(dateDelegateMetrics.width, dateDelegateMetrics.height) * 2 + implicitHeight: width + 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.pixelSize: 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 + anchors.horizontalCenter: parent.horizontalCenter + font.pixelSize: 12 + font.bold: model.today + text: model.day + } + Text { + visible: model.day === 1 + color: "red" + text: monthGrid.title.slice(0, 3).toUpperCase() + font.pixelSize: 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") + 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) + } + } + } + } +} diff --git a/src/ui/linux/TogglDesktop/TogglCheckBox.qml b/src/ui/linux/TogglDesktop/TogglCheckBox.qml new file mode 100644 index 0000000000..79c89919cd --- /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: mainPalette.base + border.color: mixColors(palette.base, mainPalette.text, 0.33) + border.width: 1 + + Rectangle { + width: parent.width - 8 + height: parent.height - 8 + x: 4 + y: 4 + radius: 1 + antialiasing: true + color: mainPalette.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..c97ec4ed6e --- /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 = mainPalette.buttonText; + context.fill(); + } + } + contentItem: Text { + leftPadding: 9 + rightPadding: control.indicator.width + control.spacing + + text: control.displayText + font: control.font + color: control.currentIndex < 0 ? mainPalette.borderColor : mainPalette.text + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + } +} 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 } + } + } +} diff --git a/src/ui/linux/TogglDesktop/TogglSwitch.qml b/src/ui/linux/TogglDesktop/TogglSwitch.qml new file mode 100644 index 0000000000..9f9fd2ccc3 --- /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: mainPalette.base + border.width: 1 + border.color: mixColors(palette.base, mainPalette.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: mainPalette.button + + Behavior on x { + NumberAnimation { + duration: 120 + } + } + 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 new file mode 100644 index 0000000000..13b978281f --- /dev/null +++ b/src/ui/linux/TogglDesktop/TogglTextField.qml @@ -0,0 +1,14 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +TextField { + id: control + color: mainPalette.text + property bool flat: false + background: Rectangle { + border.width: flat ? 0 : 1 + radius: 4 + border.color: mainPalette.borderColor + color: "transparent" + } +} 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 2cfdcf8322..0000000000 --- a/src/ui/linux/TogglDesktop/autocompletecombobox.cpp +++ /dev/null @@ -1,196 +0,0 @@ -#include "autocompletecombobox.h" -#include "autocompletelistmodel.h" -#include "autocompletelistview.h" - -#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); - lineEdit()->installEventFilter(this); - lineEdit()->setFrame(false); - completer->setModel(proxyModel); - setCompleter(completer); - disconnect(completer, SIGNAL(highlighted(QString)), lineEdit(), nullptr); - 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) { - // this is an ugly hack, this SHOULD happen in the FocusIn event but that actually never occurs - if (o == lineEdit()) { - auto retval = QComboBox::eventFilter(o, e); - disconnect(completer, SIGNAL(highlighted(QString)), lineEdit(), nullptr); - // there were text rendering glitches, this fixes the issue by forcing a repaint on every keypress - if (e->type() == QEvent::KeyPress) { - lineEdit()->repaint(); - } - return retval; - } - else 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_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; - } - } - else { - 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_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()) { - 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); - setCurrentText(item->Description); - break; - case 1: - emit projectSelected(item->ProjectLabel, item->ProjectID, item->ProjectColor, item->TaskLabel, item->TaskID); - emit billableChanged(item->Billable); - setCurrentText(QString()); - break; - case 2: - emit projectSelected(item->Text, item->ProjectID, item->ProjectColor, QString(), 0); - setCurrentText(QString()); - break; - default: - break; - } - } - emit activated(listView->currentIndex().row()); -} - -void AutocompleteComboBox::cancelSelection() { - 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); -} - -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)); - 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 23c4be8303..0000000000 --- a/src/ui/linux/TogglDesktop/autocompletecombobox.h +++ /dev/null @@ -1,68 +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; -}; - -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); - - 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 afe46014cd..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), @@ -33,6 +35,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(); } @@ -65,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 9f289c3b0e..7bf679f059 100644 --- a/src/ui/linux/TogglDesktop/autocompletelistmodel.h +++ b/src/ui/linux/TogglDesktop/autocompletelistmodel.h @@ -10,11 +10,13 @@ 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; + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; @@ -23,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/autocompleteview.h b/src/ui/linux/TogglDesktop/autocompleteview.h index b106f2f109..171c9fd9bc 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; @@ -38,25 +40,25 @@ 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).toUpper(); - view->Type = it->Type; + 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(); - 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,23 +130,26 @@ 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; + +private: + mutable QMutex propertyMutex_; + }; #endif // SRC_UI_LINUX_TOGGLDESKTOP_AUTOCOMPLETEVIEW_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/common.h b/src/ui/linux/TogglDesktop/common.h new file mode 100644 index 0000000000..2feac321f8 --- /dev/null +++ b/src/ui/linux/TogglDesktop/common.h @@ -0,0 +1,61 @@ +// Copyright 2019 Toggl Desktop developers. + +#ifndef SRC_COMMON_H_ +#define SRC_COMMON_H_ + +#include +#include + +#ifdef _WIN32 +#define toQString(x) QString::fromWCharArray(x) +#define toLocalString(x) x.toStdWString().c_str() +#define strLiteral(x) L"" x +#else +#define toQString(x) QString(x) +#define toLocalString(x) (x).toStdString().c_str() +#define strLiteral(x) x +#endif + +#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__ }; \ + 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 // TOGGL_SAFE_PROPERTIES + +#endif // SRC_COMMON_H_ diff --git a/src/ui/linux/TogglDesktop/countryview.h b/src/ui/linux/TogglDesktop/countryview.h index dbf904523e..b67e427008 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 { @@ -20,24 +21,26 @@ 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; } - uint64_t ID; - QString Text; - QString Name; - bool VatApplicable; - QString VatPercentage; - QString VatRegex; - QString Code; + mutable QMutex propertyMutex_; + + 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/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/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/idlenotificationwidget.cpp b/src/ui/linux/TogglDesktop/idlenotificationwidget.cpp index cc96df1815..fb7838c822 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/images/logo.png b/src/ui/linux/TogglDesktop/images/logo.png index a4e8be4ba2..2b1da4f669 100644 Binary files a/src/ui/linux/TogglDesktop/images/logo.png and b/src/ui/linux/TogglDesktop/images/logo.png differ 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/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 a23b4cf380..729c2fea32 100644 --- a/src/ui/linux/TogglDesktop/main.cpp +++ b/src/ui/linux/TogglDesktop/main.cpp @@ -7,27 +7,36 @@ #include #include #include +#include + +#include +#include #include #include #include #include +#ifndef _WIN32 #include +#endif #include "singleapplication.h" // NOLINT #include "./autocompleteview.h" #include "./bugsnag.h" #include "./genericview.h" -#include "./mainwindowcontroller.h" #include "./toggl.h" +#include "./timeentryview.h" +#include "./countryview.h" +#include "./autocompletelistmodel.h" -MainWindowController *w = nullptr; +#include "mainwindow.h" 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); }; @@ -36,27 +45,57 @@ 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"; +void setOptions(const TogglApplication &a) { + QCommandLineParser parser; + parser.setApplicationDescription("Toggl Desktop"); + parser.addHelpOption(); + parser.addVersionOption(); - TogglApplication::setQuitOnLastWindowClosed(false); + QCommandLineOption logPathOption("log-path", " of the app log file", "path"); + parser.addOption(logPathOption); + QCommandLineOption dbPathOption("db-path", " of the app DB file", "path"); + parser.addOption(dbPathOption); + + // A boolean option with multiple names (-b, --background) + QCommandLineOption forceOption({ "b", "background" }, "Start app in background."); + parser.addOption(forceOption); + + parser.process(a); +} + +void registerTypes() { qRegisterMetaType("uint64_t"); qRegisterMetaType("int64_t"); qRegisterMetaType("bool_t"); + qRegisterMetaType("char_t"); qRegisterMetaType >("QVector"); qRegisterMetaType >("QVector"); qRegisterMetaType >("QVector"); + 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; + + qputenv("QML_DISABLE_DISTANCEFIELD", "1"); + + TogglApplication::setQuitOnLastWindowClosed(false); QApplication::setApplicationName("Toggl Desktop"); QApplication::setOrganizationName("Toggl"); @@ -65,22 +104,29 @@ int main(int argc, char *argv[]) try { qDebug() << "An instance of the app is already running"; return 0; } + a.setApplicationVersion(APP_VERSION); + setOptions(a); QtWebEngine::initialize(); - a.setApplicationVersion(APP_VERSION); - Bugsnag::app.version = APP_VERSION; + registerTypes(); - QCommandLineParser parser; - parser.setApplicationDescription("Toggl Desktop"); - parser.addHelpOption(); - parser.addVersionOption(); + /* + QQmlApplicationEngine engine; + engine.rootContext()->setContextProperty("toggl", new TogglApi(nullptr)); + engine.load(QUrl(QStringLiteral("qrc:/MainWindow.qml"))); + */ - QCommandLineOption logPathOption( - QStringList() << "log-path", - " of the app log file", - "path"); - parser.addOption(logPathOption); + auto w = new MainWindow(); + w->show(); + 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; + /* TODO readd QCommandLineOption dbPathOption( QStringList() << "db-path", " of the app DB file", @@ -110,12 +156,14 @@ int main(int argc, char *argv[]) try { w->show(); if (parser.isSet(forceOption)) { QTimer::singleShot(1, w, &MainWindowController::hide); + */ } + 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; } diff --git a/src/ui/linux/TogglDesktop/mainwindow.cpp b/src/ui/linux/TogglDesktop/mainwindow.cpp new file mode 100644 index 0000000000..8216639c56 --- /dev/null +++ b/src/ui/linux/TogglDesktop/mainwindow.cpp @@ -0,0 +1,82 @@ +#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) + , preferences_(new PreferencesDialog(this)) +{ + 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; +} + +void MainWindow::on_actionAbout_triggered() { + // TODO +} + +void MainWindow::on_actionHelp_triggered() { + TogglApi::instance->getSupport(); +} + +void MainWindow::on_actionUpdateCheck_triggered() { + // TODO +} + +void MainWindow::on_actionPreferences_triggered() { + preferences_->show(); +} + +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 new file mode 100644 index 0000000000..c990eacc11 --- /dev/null +++ b/src/ui/linux/TogglDesktop/mainwindow.h @@ -0,0 +1,42 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include "preferencesdialog.h" + +namespace Ui { +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private slots: + void on_actionAbout_triggered(); + 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; + + PreferencesDialog *preferences_; +}; + +#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..3c5008152b --- /dev/null +++ b/src/ui/linux/TogglDesktop/mainwindow.ui @@ -0,0 +1,154 @@ + + + MainWindow + + + + 0 + 0 + 360 + 480 + + + + + 0 + 0 + + + + + 240 + 64 + + + + Toggl + + + + QQuickWidget::SizeRootObjectToView + + + + + + 0 + 0 + 360 + 22 + + + + + Toggl Desktop + + + + + + + + + + + + Account + + + + + + + + + + Timer + + + + + + + + + + + + + + + About Toggl Desktop + + + + + Check for updates + + + + + Preferences + + + + + Quit + + + + + Sync + + + + + Clear cache + + + + + Logout + + + + + New + + + + + Continue + + + + + Stop + + + + + Show + + + + + Edit + + + + + Help + + + + + + QQuickWidget + QWidget +
QtQuickWidgets/QQuickWidget
+
+
+ + +
diff --git a/src/ui/linux/TogglDesktop/mainwindowcontroller.cpp b/src/ui/linux/TogglDesktop/mainwindowcontroller.cpp deleted file mode 100644 index b0e44adb79..0000000000 --- a/src/ui/linux/TogglDesktop/mainwindowcontroller.cpp +++ /dev/null @@ -1,576 +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::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; -} - -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::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::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 e9dcc92cd5..0000000000 --- a/src/ui/linux/TogglDesktop/mainwindowcontroller.h +++ /dev/null @@ -1,148 +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 showEvent(QShowEvent *event); - void closeEvent(QCloseEvent *event); - - 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.ui b/src/ui/linux/TogglDesktop/preferencesdialog.ui index a8f5c9cb1f..2bfe0f9a9d 100644 --- a/src/ui/linux/TogglDesktop/preferencesdialog.ui +++ b/src/ui/linux/TogglDesktop/preferencesdialog.ui @@ -20,7 +20,7 @@ - 2 + 0 diff --git a/src/ui/linux/TogglDesktop/qmlobjectlist.cc b/src/ui/linux/TogglDesktop/qmlobjectlist.cc new file mode 100644 index 0000000000..10eb6ca07d --- /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.data()); +} + +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/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/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 a51c9b5213..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).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 43d0e7151c..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(), 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->DurationInSeconds; - - 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.split("\t", QString::SkipEmptyParts); - 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/timeentryview.cpp b/src/ui/linux/TogglDesktop/timeentryview.cpp index c94b4f75fa..27b4cbd9df 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.cpp +++ b/src/ui/linux/TogglDesktop/timeentryview.cpp @@ -3,50 +3,92 @@ #include "./timeentryview.h" #include +#include +#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; - 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); + 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(view->DateHeader); - result->DateDuration = QString(view->DateDuration); + result->DateHeader = QString("
").arg(view->DateHeader).arg(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; } +/* QVector TimeEntryView::importAll( TogglTimeEntryView *first) { QVector result; @@ -57,6 +99,43 @@ QVector TimeEntryView::importAll( } return result; } +*/ + +void TimeEntryView::update(const TogglTimeEntryView *view) { + DurationInSecondsSet(view->DurationInSeconds); + 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(toQString(view->Duration)); + ColorSet(toQString(view->Color)); + GUIDSet(toQString(view->GUID)); + BillableSet(view->Billable); + TagsSet(toQString(view->Tags).split("\t", QString::SkipEmptyParts)); + StartedSet(view->Started); + EndedSet(view->Ended); + StartTimeStringSet(toQString(view->StartTimeString)); + EndTimeStringSet(toQString(view->EndTimeString)); + UpdatedAtSet(view->UpdatedAt); + DateHeaderSet(QString("").arg(view->DateHeader).arg(view->DateDuration)); + DateDurationSet(toQString(view->DateDuration)); + IsHeaderSet(view->IsHeader); + CanAddProjectsSet(view->CanAddProjects); + CanSeeBillableSet(view->CanSeeBillable); + DefaultWIDSet(view->DefaultWID); + WorkspaceNameSet(toQString(view->WorkspaceName)); + ErrorSet(toQString(view->Error)); + UnsyncedSet(view->Unsynced); + GroupSet(view->Group); + GroupOpenSet(view->GroupOpen); + GroupNameSet(toQString(view->GroupName)); + GroupDurationSet(toQString(view->GroupDuration)); + GroupItemCountSet(view->GroupItemCount); +} const QString TimeEntryView::lastUpdate() { return QString("Last update ") + @@ -71,3 +150,140 @@ 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(uid(libraryView))) + newOrder.append(uid(libraryView)); + // and also build the set of the GUIDs now for easier comparison + 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); + } + + // 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; + + // first we remove all items with guids that are gone in the update + for (auto i : removed) { + remove(i); + } + + // now pass through the list again to update the items we already have + libraryView = first; + while (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(uid(libraryView)); + } + libraryView = static_cast(libraryView->Next); + } + + // 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 (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]) { + beginInsertRows(QModelIndex(), i, i); + QString guid = newOrder[i]; + guidOrder_.insert(i, guid); + auto v = TimeEntryView::importOne(flattened[i]); + v->moveToThread(uiThread); + storage_.insert(guid, v); + endInsertRows(); + } + } +} + +QVariant TimeEntryViewStorage::data(const QModelIndex &index, int role) const { + if (index.isValid() && role == Qt::UserRole) { + 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(); +} + +QString TimeEntryViewStorage::uid(TogglTimeEntryView *view) { + if (view->Group) + return QString("Group %1").arg(view->GUID); + return toQString(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) { + 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 12b9343e2d..9b6fecd290 100644 --- a/src/ui/linux/TogglDesktop/timeentryview.h +++ b/src/ui/linux/TogglDesktop/timeentryview.h @@ -5,8 +5,37 @@ #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: + QString uid(TogglTimeEntryView *view); + QString uid(TimeEntryView *view); + + void remove(const QString &guid); + void move(const QString &guid, int to); + + QList guidOrder_; + QMap storage_; + + QThread *uiThread; +}; class TimeEntryView : public QObject { Q_OBJECT @@ -14,46 +43,52 @@ 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); + //static QVector importAll(TogglTimeEntryView *first); + void update(const TogglTimeEntryView *view); 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; + Q_INVOKABLE const QString lastUpdate(); + + mutable QMutex propertyMutex_; + + 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(QStringList, 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(quint64, GroupItemCount) }; #endif // SRC_UI_LINUX_TOGGLDESKTOP_TIMEENTRYVIEW_H_ diff --git a/src/ui/linux/TogglDesktop/timerwidget.cpp b/src/ui/linux/TogglDesktop/timerwidget.cpp deleted file mode 100644 index e3679a7ece..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 + "

")); - - duration = te->DurationInSeconds; - - 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 8553c7f022..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 - - - - - - false - - - - - - - - 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.cpp b/src/ui/linux/TogglDesktop/toggl.cpp index 84bb9b5f6d..6c712804cb 100644 --- a/src/ui/linux/TogglDesktop/toggl.cpp +++ b/src/ui/linux/TogglDesktop/toggl.cpp @@ -11,6 +11,8 @@ #include #include +#include + #include // NOLINT #include "./../../../toggl_api.h" @@ -22,6 +24,7 @@ #include "./autocompleteview.h" #include "./settingsview.h" #include "./bugsnag.h" +#include "./common.h" TogglApi *TogglApi::instance = nullptr; @@ -33,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) { @@ -55,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( @@ -65,32 +68,33 @@ 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); } 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( @@ -100,28 +104,35 @@ void on_display_time_entry_list( if (open) { TogglApi::instance->aboutToDisplayTimeEntryList(); } + TogglApi::instance->importTimeEntries(first); TogglApi::instance->displayTimeEntryList( open, - TimeEntryView::importAll(first), show_load_more_button); + } 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( @@ -145,14 +156,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( @@ -166,8 +177,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; } @@ -175,28 +187,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); @@ -204,20 +216,68 @@ 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 +void replaceList(const QVector &from, QList &to) { + for (auto i : to) + i->deleteLater(); + to.clear(); + for (auto i : from) + to.append(i); +} + +void TogglApi::setCountries(QVector list) { + for (auto i : list) { + QQmlEngine::setObjectOwnership(i, QQmlEngine::CppOwnership); + } + replaceList(list, countries_); + emit countriesChanged(); +} + +void TogglApi::displayTimeEntryAutocomplete(QVector list) { + timeEntryModel_->setList(list); + emit timeEntryAutocompleteChanged(); +} + +void TogglApi::displayMinitimerAutocomplete(QVector list) { + minitimerModel_->setList(list); + emit minitimerAutocompleteChanged(); + +} + +void TogglApi::displayProjectAutocomplete(QVector list) { + projectModel_->setList(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) +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)) + , 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( @@ -233,10 +293,10 @@ TogglApi::TogglApi( } 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()) { @@ -244,7 +304,7 @@ TogglApi::TogglApi( } 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(); @@ -258,7 +318,7 @@ TogglApi::TogglApi( 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); @@ -284,9 +344,15 @@ TogglApi::TogglApi( toggl_on_project_colors(ctx, on_project_colors); toggl_on_countries(ctx, on_countries); - char *env = toggl_environment(ctx); + timeEntryAutocomplete_->setSourceModel(timeEntryModel_); + minitimerAutocomplete_->setSourceModel(minitimerModel_); + projectAutocomplete_->setSourceModel(projectModel_); + + timeEntries_ = new TimeEntryViewStorage(this); + + auto env = toggl_environment(ctx); if (env) { - Bugsnag::releaseStage = QString(env); + Bugsnag::releaseStage = toQString(env); free(env); } @@ -321,6 +387,37 @@ bool TogglApi::notifyBugsnag( return Bugsnag::notify(errorClass, message, context, &metadata); } +QList TogglApi::countries() { + for (auto i : countries_) { + QQmlEngine::setObjectOwnership(i, QQmlEngine::CppOwnership); + } + return countries_; +} + +AutocompleteProxyModel *TogglApi::timeEntryAutocomplete() { + return timeEntryAutocomplete_; +} + +AutocompleteProxyModel *TogglApi::minitimerAutocomplete() { + return minitimerAutocomplete_; +} + +AutocompleteProxyModel *TogglApi::projectAutocomplete() { + return projectAutocomplete_; +} + +TimeEntryViewStorage *TogglApi::timeEntries() { + return timeEntries_; +} + +void TogglApi::importTimeEntries(TogglTimeEntryView *first) { + timeEntries_->importList(first); +} + +QStringList TogglApi::tags() { + return tags_; +} + bool TogglApi::startEvents() { return toggl_ui_start(ctx); } @@ -334,20 +431,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; } @@ -355,7 +452,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); } @@ -363,24 +460,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( @@ -391,27 +488,27 @@ 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); } -void TogglApi::setIdleSeconds(u_int64_t idleSeconds) { +void TogglApi::setIdleSeconds(uint64_t idleSeconds) { toggl_set_idle_seconds(ctx, idleSeconds); } @@ -484,7 +581,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) { @@ -492,7 +589,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) { @@ -504,49 +601,48 @@ 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; } -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(), - duration.toStdString().c_str(), + auto guid = toggl_start(ctx, + toLocalString(description), + toLocalString(duration), task_id, project_id, nullptr /* project guid */, - tags /* tags */, + toLocalString(tags) /* tags */, false, 0, 0); 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); } } @@ -559,14 +655,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() { @@ -606,15 +702,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( @@ -623,41 +719,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); } @@ -670,17 +766,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; @@ -689,12 +785,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; @@ -705,46 +801,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 d00fe2c945..849744378f 100644 --- a/src/ui/linux/TogglDesktop/toggl.h +++ b/src/ui/linux/TogglDesktop/toggl.h @@ -6,20 +6,31 @@ #include #include #include +#include +#include #include #include #include "./toggl_api.h" +#include "./autocompletelistmodel.h" +#include "./qmlobjectlist.h" class AutocompleteView; class GenericView; class SettingsView; class TimeEntryView; class CountryView; +class TimeEntryViewStorage; class TogglApi : public QObject { Q_OBJECT + Q_PROPERTY(QList countries READ countries NOTIFY countriesChanged) + Q_PROPERTY(AutocompleteProxyModel* timeEntryAutocomplete READ timeEntryAutocomplete CONSTANT) + 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( @@ -28,6 +39,8 @@ class TogglApi : public QObject { QString dbPathOverride = ""); ~TogglApi(); + QThread *uiThread_; + static TogglApi *instance; static QString Project; @@ -41,6 +54,40 @@ class TogglApi : public QObject { bool shutdown; + QList countries(); + AutocompleteProxyModel *timeEntryAutocomplete(); + AutocompleteProxyModel *minitimerAutocomplete(); + 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_; + AutocompleteListModel *minitimerModel_; + AutocompleteListModel *projectModel_; + AutocompleteProxyModel *timeEntryAutocomplete_; + AutocompleteProxyModel *minitimerAutocomplete_; + AutocompleteProxyModel *projectAutocomplete_; + TimeEntryViewStorage *timeEntries_; + QStringList tags_; + + public slots: + void setCountries(QVector list); + + void displayTimeEntryAutocomplete(QVector list); + void displayMinitimerAutocomplete(QVector list); + void displayProjectAutocomplete(QVector list); + + void displayTags(QVector list); + +/////////////////////////////////////////////////////// bool startEvents(); void clear(); @@ -61,7 +108,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(); @@ -98,7 +145,7 @@ class TogglApi : public QObject { void viewTimeEntryList(); - void setIdleSeconds(u_int64_t idleSeconds); + void setIdleSeconds(uint64_t idleSeconds); bool setTimeEntryProject( const QString guid, @@ -214,9 +261,7 @@ class TogglApi : public QObject { bool discardTimeAndContinue(const QString guid, const uint64_t at); - bool runScriptFile(const QString filename); - - static const QString formatDurationInSecondsHHMMSS( + Q_INVOKABLE static const QString formatDurationInSecondsHHMMSS( const int64_t duration); QRect const getWindowsFrameSetting(); @@ -261,7 +306,6 @@ class TogglApi : public QObject { void aboutToDisplayTimeEntryList(); void displayTimeEntryList( const bool open, - QVector list, const bool show_load_more_button); void aboutToDisplayTimeEntryEditor(); @@ -289,18 +333,6 @@ class TogglApi : public QObject { void displayClientSelect( QVector list); - void displayTags( - QVector list); - - void displayTimeEntryAutocomplete( - QVector list); - - void displayMinitimerAutocomplete( - QVector list); - - void displayProjectAutocomplete( - QVector list); - void displayWorkspaceSelect( QVector list); @@ -309,10 +341,7 @@ class TogglApi : public QObject { void updateContinueStopShortcut(); void setProjectColors( - QVector list); - - void setCountries( - QVector list); + QVector list); private: void *ctx; @@ -327,28 +356,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); @@ -367,16 +395,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/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 2b1da4f669..0000000000 Binary files a/src/ui/windows/TogglDesktop/TogglDesktop.Package/Images/Wide310x150Logo.scale-200.png and /dev/null differ diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt index 923d494567..cc314c4f12 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 ) @@ -68,6 +70,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 @@ -86,17 +89,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 @@ -105,8 +118,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 + ) + elseif(APPLE) + 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 @@ -115,10 +139,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 ) @@ -127,8 +151,9 @@ if(NOT GTEST_FOUND) 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}) @@ -136,7 +161,7 @@ if(NOT GTEST_FOUND) 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/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 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)
%1%2
%1%2