├── .flatpak-manifest.json ├── .flatpak-manifest.json.license ├── .gitignore ├── .gitlab-ci.yml ├── .kde-ci.yml ├── CMakeLists.txt ├── LICENSES ├── CC-BY-4.0.txt ├── CC0-1.0.txt ├── GPL-2.0-or-later.txt ├── GPL-3.0-or-later.txt ├── LGPL-2.0-or-later.txt └── LGPL-2.1-or-later.txt ├── README.md ├── kclock-runner.desktop ├── kclockd.notifyrc ├── logo.png ├── logo.png.license ├── org.kde.kclock.appdata.xml ├── org.kde.kclock.desktop ├── org.kde.kclock.svg ├── org.kde.kclock.svg.license ├── org.kde.kclockd-autostart.desktop ├── org.kde.kclockd.service.in ├── org.kde.kclockd.service.in.license ├── po ├── ar │ ├── kclock.po │ └── kclockd.po ├── ast │ ├── kclock.po │ └── kclockd.po ├── bg │ ├── kclock.po │ └── kclockd.po ├── ca │ ├── kclock.po │ └── kclockd.po ├── ca@valencia │ ├── kclock.po │ └── kclockd.po ├── cs │ ├── kclock.po │ └── kclockd.po ├── de │ ├── kclock.po │ └── kclockd.po ├── el │ └── kclock.po ├── en_GB │ ├── kclock.po │ └── kclockd.po ├── eo │ ├── kclock.po │ └── kclockd.po ├── es │ ├── kclock.po │ └── kclockd.po ├── eu │ ├── kclock.po │ └── kclockd.po ├── fi │ ├── kclock.po │ └── kclockd.po ├── fr │ ├── kclock.po │ └── kclockd.po ├── gl │ ├── kclock.po │ └── kclockd.po ├── he │ ├── kclock.po │ └── kclockd.po ├── hi │ ├── kclock.po │ └── kclockd.po ├── hu │ ├── kclock.po │ └── kclockd.po ├── ia │ ├── kclock.po │ └── kclockd.po ├── it │ ├── kclock.po │ └── kclockd.po ├── ja │ ├── kclock.po │ └── kclockd.po ├── ka │ ├── kclock.po │ └── kclockd.po ├── ko │ ├── kclock.po │ └── kclockd.po ├── lt │ ├── kclock.po │ └── kclockd.po ├── lv │ ├── kclock.po │ └── kclockd.po ├── nl │ ├── kclock.po │ └── kclockd.po ├── nn │ ├── kclock.po │ └── kclockd.po ├── pa │ ├── kclock.po │ └── kclockd.po ├── pl │ ├── kclock.po │ └── kclockd.po ├── pt │ ├── kclock.po │ └── kclockd.po ├── pt_BR │ ├── kclock.po │ └── kclockd.po ├── ro │ └── kclock.po ├── ru │ ├── kclock.po │ └── kclockd.po ├── sa │ ├── kclock.po │ └── kclockd.po ├── sk │ ├── kclock.po │ └── kclockd.po ├── sl │ ├── kclock.po │ └── kclockd.po ├── sv │ ├── kclock.po │ └── kclockd.po ├── th │ └── kclockd.po ├── tr │ ├── kclock.po │ └── kclockd.po ├── uk │ ├── kclock.po │ └── kclockd.po ├── zh_CN │ ├── kclock.po │ └── kclockd.po └── zh_TW │ ├── kclock.po │ └── kclockd.po ├── screenshots ├── kclock-desktop-timer.png ├── kclock-desktop-timer.png.license ├── kclock-desktop-timezones.png ├── kclock-desktop-timezones.png.license ├── kclock-mobile-alarms.png ├── kclock-mobile-alarms.png.license ├── kclock-mobile-stopwatch.png ├── kclock-mobile-stopwatch.png.license ├── kclock-mobile-timers.png ├── kclock-mobile-timers.png.license ├── kclock-mobile-timezones.png └── kclock-mobile-timezones.png.license ├── snapcraft.yaml └── src ├── CMakeLists.txt ├── kclock ├── CMakeLists.txt ├── Messages.sh ├── addlocationmodel.cpp ├── addlocationmodel.h ├── alarm.cpp ├── alarm.h ├── alarmmodel.cpp ├── alarmmodel.h ├── kclock_algorithm.hpp ├── kclockformat.cpp ├── kclockformat.h ├── main.cpp ├── qml │ ├── Main.qml │ ├── alarm │ │ ├── AlarmForm.qml │ │ ├── AlarmFormPage.qml │ │ ├── AlarmListDelegate.qml │ │ ├── AlarmListPage.qml │ │ └── AlarmRingingPopup.qml │ ├── components │ │ ├── AboutPage.qml │ │ ├── BottomToolbar.qml │ │ ├── DialogComboBox.qml │ │ ├── FloatingActionButton.qml │ │ ├── FooterToolBarButton.qml │ │ ├── Sidebar.qml │ │ ├── SoundPickerPage.qml │ │ ├── TimePicker.qml │ │ ├── TimePickerSpinBox.qml │ │ └── TimePickerSpinBoxButton.qml │ ├── settings │ │ └── SettingsPage.qml │ ├── stopwatch │ │ └── StopwatchPage.qml │ ├── time │ │ ├── AddLocationListView.qml │ │ ├── AddLocationPage.qml │ │ ├── AddLocationWrapper.qml │ │ ├── AnalogClock.qml │ │ ├── AnalogClockHand.qml │ │ ├── TimePage.qml │ │ └── TimePageDelegate.qml │ └── timer │ │ ├── PresetDurationButton.qml │ │ ├── TimerComponent.qml │ │ ├── TimerForm.qml │ │ ├── TimerFormDialog.qml │ │ ├── TimerListDelegate.qml │ │ ├── TimerListPage.qml │ │ ├── TimerPage.qml │ │ └── TimerRingingPopup.qml ├── savedlocationsmodel.cpp ├── savedlocationsmodel.h ├── settingsmodel.cpp ├── settingsmodel.h ├── stopwatchmodel.cpp ├── stopwatchmodel.h ├── stopwatchtimer.cpp ├── stopwatchtimer.h ├── timer.cpp ├── timer.h ├── timermodel.cpp ├── timermodel.h ├── timerpresetmodel.cpp ├── timerpresetmodel.h ├── utilmodel.cpp ├── utilmodel.h ├── windowexposure.cpp └── windowexposure.h ├── kclockd ├── CMakeLists.txt ├── Messages.sh ├── abstractwakeupprovider.h ├── alarm.cpp ├── alarm.h ├── alarmmodel.cpp ├── alarmmodel.h ├── alarmplayer.cpp ├── alarmplayer.h ├── alarmwaitworker.cpp ├── alarmwaitworker.h ├── dbusutils_p.h ├── kclockdsettings.kcfg ├── kclockdsettings.kcfgc ├── kclockdsettings.kcfgc.license ├── kclockrunner.cpp ├── kclockrunner.h ├── main.cpp ├── powerdevilwakeupprovider.cpp ├── powerdevilwakeupprovider.h ├── systeminterfaces │ ├── org.kde.krunner1.xml │ └── org.mpris.MediaPlayer2.Player.xml ├── timer.cpp ├── timer.h ├── timermodel.cpp ├── timermodel.h ├── unitylauncher.cpp ├── unitylauncher.h ├── utilities.cpp ├── utilities.h ├── waittimerwakeupprovider.cpp ├── waittimerwakeupprovider.h ├── xdgportal.cpp └── xdgportal.h └── plasmoid ├── CMakeLists.txt ├── KClock_1x2 ├── CMakeLists.txt ├── kclock_1x2.cpp ├── kclock_1x2.h ├── kclock_plasmoid_1x2.svg ├── kclock_plasmoid_1x2.svg.license └── package │ ├── contents │ ├── config │ │ ├── config.qml │ │ └── main.xml │ └── ui │ │ ├── configGeneral.qml │ │ └── main.qml │ ├── metadata.json │ └── metadata.json.license └── KClock_KWeather_3x3 ├── CMakeLists.txt ├── kclock_kweather_3x3.cpp ├── kclock_kweather_3x3.h └── package ├── contents └── ui │ └── main.qml ├── metadata.json └── metadata.json.license /.flatpak-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "org.kde.kclock", 3 | "runtime": "org.kde.Platform", 4 | "runtime-version": "6.8", 5 | "sdk": "org.kde.Sdk", 6 | "command": "kclock", 7 | "tags": ["nightly"], 8 | "desktop-file-name-suffix": " (Nightly)", 9 | "finish-args": [ 10 | "--share=ipc", 11 | "--share=network", 12 | "--socket=fallback-x11", 13 | "--socket=wayland", 14 | "--device=dri", 15 | "--socket=pulseaudio", 16 | "--talk-name=org.freedesktop.Notifications", 17 | "--talk-name=org.kde.Solid.PowerManagement", 18 | "--own-name=org.kde.kclockd" 19 | ], 20 | "separate-locales": false, 21 | 22 | "modules": [ 23 | { 24 | "name": "intltool", 25 | "cleanup": [ "*" ], 26 | "sources": [ 27 | { 28 | "type": "archive", 29 | "url": "https://launchpad.net/intltool/trunk/0.51.0/+download/intltool-0.51.0.tar.gz", 30 | "sha256": "67c74d94196b153b774ab9f89b2fa6c6ba79352407037c8c14d5aeb334e959cd" 31 | } 32 | ] 33 | }, 34 | { 35 | "name": "sound-theme-freedesktop", 36 | "sources": [ 37 | { 38 | "type": "archive", 39 | "url": "https://people.freedesktop.org/~mccann/dist/sound-theme-freedesktop-0.8.tar.bz2", 40 | "sha256": "cb518b20eef05ec2e82dda1fa89a292c1760dc023aba91b8aa69bafac85e8a14" 41 | } 42 | ] 43 | }, 44 | { 45 | "name": "plasma-mobile-sounds", 46 | "buildsystem": "cmake-ninja", 47 | "builddir": true, 48 | "sources": [ 49 | { 50 | "type": "archive", 51 | "url": "https://download.kde.org/stable/plasma-mobile-sounds/0.1/plasma-mobile-sounds-0.1.tar.xz", 52 | "sha256": "f1aed3ddd1de209e0d60df54e968b141b4c868ff0c4706dedb85e4cce29f26af" 53 | } 54 | ] 55 | }, 56 | { 57 | "name": "kirigamiaddons", 58 | "config-opts": [ "-DBUILD_TESTING=OFF" ], 59 | "buildsystem": "cmake-ninja", 60 | "sources": [ { "type": "git", "url": "https://invent.kde.org/libraries/kirigami-addons.git", "branch": "master" } ] 61 | }, 62 | { 63 | "name": "kclock", 64 | "buildsystem": "cmake-ninja", 65 | "builddir": true, 66 | "config-opts": [ "-DBUILD_PLASMOID=OFF" ], 67 | "sources": [ { "type": "dir", "path": ".", "skip": [".git"] } ] 68 | } 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /.flatpak-manifest.json.license: -------------------------------------------------------------------------------- 1 | Copyright 2020-2021 Devin Lin 2 | SPDX-License-Identifier: GPL-2.0-or-later 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Han Young 3 | # Copyright 2020 Devin Lin 4 | # 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | # 7 | 8 | *.qmlc 9 | build*/ 10 | .kdev4/ 11 | kclock.kdev4 12 | CMakeLists.txt.user 13 | 14 | /cmake-build-debug 15 | /flatpak-build-desktop 16 | /.flatpak-builder 17 | 18 | kclock.cflags 19 | kclock.config 20 | kclock.creator 21 | kclock.creator.user 22 | kclock.cxxflags 23 | kclock.files 24 | kclock.includes 25 | .clang-format 26 | 27 | # LSP & IDE 28 | /.clang-format 29 | /compile_commands.json 30 | .clangd 31 | .cache 32 | .idea 33 | /cmake-build* 34 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021-2022 Devin Lin 2 | # SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | include: 5 | - project: sysadmin/ci-utilities 6 | file: 7 | - /gitlab-templates/reuse-lint.yml 8 | - /gitlab-templates/linux-qt6.yml 9 | - /gitlab-templates/linux-qt6-next.yml 10 | - /gitlab-templates/flatpak.yml 11 | - /gitlab-templates/snap-snapcraft-lxd.yml 12 | - /gitlab-templates/clang-format.yml 13 | - /gitlab-templates/xml-lint.yml 14 | - /gitlab-templates/yaml-lint.yml 15 | -------------------------------------------------------------------------------- /.kde-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: None 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | Dependencies: 5 | - 'on': ['Linux', 'Android', 'FreeBSD', 'Windows'] 6 | 'require': 7 | 'frameworks/extra-cmake-modules': '@latest-kf6' 8 | 'frameworks/kcoreaddons': '@latest-kf6' 9 | 'frameworks/kconfig': '@latest-kf6' 10 | 'frameworks/ki18n': '@latest-kf6' 11 | 'frameworks/kirigami': '@latest-kf6' 12 | 'frameworks/kdbusaddons': '@latest-kf6' 13 | 'frameworks/knotifications': '@latest-kf6' 14 | 'frameworks/kstatusnotifieritem': '@latest-kf6' 15 | 'frameworks/kcrash': '@latest-kf6' 16 | 'plasma/libplasma': '@latest-kf6' 17 | 'libraries/kirigami-addons': '@latest-kf6' 18 | 19 | Options: 20 | require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows'] 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Han Young 3 | # Copyright 2020-2021 Devin Lin 4 | # 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | # 7 | 8 | cmake_minimum_required(VERSION 3.16) 9 | set(CMAKE_CXX_STANDARD 17) 10 | 11 | # KDE Applications version, managed by release script. 12 | set(RELEASE_SERVICE_VERSION_MAJOR "25") 13 | set(RELEASE_SERVICE_VERSION_MINOR "07") 14 | set(RELEASE_SERVICE_VERSION_MICRO "70") 15 | set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") 16 | 17 | project(kclock VERSION ${RELEASE_SERVICE_VERSION}) 18 | 19 | option(BUILD_PLASMOID "Build Plasmoid" ON) 20 | 21 | set(KF_MIN_VERSION "6.8.0") 22 | set(QT_MIN_VERSION "6.5.0") 23 | 24 | find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE) 25 | 26 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) 27 | 28 | # only enable QML debugging on debug builds 29 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DQT_QML_DEBUG ") 30 | 31 | include(FeatureSummary) 32 | include(ECMSetupVersion) 33 | include(ECMFindQmlModule) 34 | include(ECMConfiguredInstall) 35 | include(KDEInstallDirs) 36 | include(KDECMakeSettings) 37 | include(KDECompilerSettings NO_POLICY_SCOPE) 38 | include(KDEClangFormat) 39 | include(KDEGitCommitHooks) 40 | include(ECMDeprecationSettings) 41 | include(ECMQtDeclareLoggingCategory) 42 | include(ECMQmlModule) 43 | 44 | ecm_setup_version(${PROJECT_VERSION} 45 | VARIABLE_PREFIX KCLOCK 46 | VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/version.h 47 | ) 48 | 49 | find_package(Qt6 ${QT_MIN_VERSION} REQUIRED COMPONENTS 50 | Core 51 | Gui 52 | Quick 53 | Test 54 | Svg 55 | QuickControls2 56 | Multimedia 57 | DBus 58 | Widgets 59 | ) 60 | 61 | find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS 62 | Config 63 | I18n 64 | CoreAddons 65 | Kirigami 66 | Notifications 67 | DBusAddons 68 | Crash 69 | StatusNotifierItem 70 | Svg 71 | ) 72 | if (BUILD_PLASMOID) 73 | find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS KIO JobWidgets) 74 | find_package(Plasma REQUIRED) 75 | endif() 76 | 77 | find_package(KF6KirigamiAddons 0.6 REQUIRED) 78 | 79 | ki18n_install(po) 80 | ecm_set_disabled_deprecation_versions(QT 6.8.0 81 | KF 6.12.0 82 | ) 83 | 84 | add_subdirectory(src) 85 | 86 | install(PROGRAMS org.kde.kclock.desktop DESTINATION ${KDE_INSTALL_APPDIR}) 87 | install(FILES org.kde.kclockd-autostart.desktop DESTINATION ${KDE_INSTALL_AUTOSTARTDIR}) 88 | install(FILES org.kde.kclock.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) 89 | install(FILES org.kde.kclock.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps/) 90 | install(FILES kclockd.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR}) 91 | install(FILES kclock-runner.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins) 92 | 93 | ecm_install_configured_files(INPUT org.kde.kclockd.service.in DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR} @ONLY) 94 | 95 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 96 | 97 | # add clang-format target for all our real source files 98 | file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.hpp) 99 | kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) 100 | kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | # KClock 8 | A convergent clock application for Plasma. 9 | 10 | Download on Flathub 11 | 12 | ## Features 13 | * Alarms 14 | * Stopwatch 15 | * World Clocks 16 | * Timers 17 | 18 | ## Links 19 | * Project page: https://invent.kde.org/plasma-mobile/kclock 20 | * File issues: https://bugs.kde.org/describecomponents.cgi?product=KClock 21 | * Development channel: https://matrix.to/#/#plasmamobile:matrix.org 22 | 23 | ## Installing 24 | This will compile and install kclock, kclockd, and the plasmoids onto the system. When running kclock, make sure that kclockd is running first (it is configured to autostart in sessions). 25 | 26 | ``` 27 | mkdir build 28 | cd build 29 | cmake .. # add -DCMAKE_BUILD_TYPE=Release to compile for release 30 | make 31 | sudo make install 32 | ``` 33 | 34 | ## Components 35 | KClock is split into three components: **kclock** (front-end), **kclockd** (backend-daemon), and **plasmoids**. 36 | 37 | ### `kclockd` 38 | The background daemon, which is configured to autostart. It has the following responsibilities: 39 | * Schedule wakeups with [PowerDevil](https://invent.kde.org/plasma/powerdevil), which handles waking from suspend for alarms and timers. 40 | * Exposing alarms, timers, and settings models via a D-Bus interface. 41 | * Alarm/Timer notifications and ringing audio. 42 | * Indicator on system tray for pending alarms. 43 | 44 | `kclockd` exposes its API in D-Bus under the service name `org.kde.kclockd`. Front-end applications like `kclock` and its plasmoids completely depend on it for clock functionality, which allows alarms and timers to function in the background when the front-end applications are not running. 45 | 46 | If PowerDevil is detected, it will use PowerDevil's scheduleWakeup feature to wakeup for alarm notifications. 47 | 48 | If you want to disable this behaviour, pass the `--no-powerdevil` option when launching kclockd. Note that while running in `--no-powerdevil` mode, kclockd will fail to keep track of time if the system sleeps, which is quite common for mobile devices. 49 | 50 | `kclockd` will automatically start up in `--no-powerdevil` mode and not have suspend waking functionality if: 51 | * PowerDevil is not running (ex. on GNOME, Phosh, etc.) 52 | * On BSD system 53 | 54 | #### `kclockd` D-Bus interface 55 | After installing, five D-Bus interface XML files are copied to the KDE D-Bus interface directory. 56 | 57 | These are: 58 | * org.kde.kclockd.KClockSettings.xml 59 | * org.kde.kclockd.Alarm.xml 60 | * org.kde.kclockd.AlarmModel.xml 61 | * org.kde.kclockd.TimerModel.xml 62 | * org.kde.kclockd.Timer.xml 63 | 64 | These files can be used to generate the D-Bus adaptor. Alarm and Timer will be registered under path "/Alarms/" or "/Timers/" + its uuid's Id128 string representation (ex. *8c7d59b3befa49a48853959fe7e025d7*). However, the "remove" slots in AlarmModel or TimerModel only accept its normal representation, (ex. *{8c7d59b3-befa-49a4-8853-959fe7e025d7}*). One can get the normal string by calling the getUUID() method in each alarm/timer. Timer is in seconds precision, but you may want to call elapsed() method every 500ms or so to avoid going out of sync with kclockd. 65 | 66 | ### `kclock` 67 | A front end for kclockd written in Kirigami for Plasma Mobile and the desktop. 68 | 69 | Run with these environment variables to have mobile controls: 70 | ``` 71 | QT_QUICK_CONTROLS_MOBILE=true kclock 72 | ``` 73 | 74 | ### Plasmoids 75 | Various plasmoids for kclockd exist, and some also communicate with [KWeather](https://invent.kde.org/plasma-mobile/kweather). 76 | 77 | Currently we have two plasmoids, expect more in the future. 78 | 79 | Intended to provide `widgets` similar to those you can find on mainstream mobile platforms. 80 | 81 | ## Gallery 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kclock/d73f1fffd6421bec7af45f74f81337a0ace4dacf/logo.png -------------------------------------------------------------------------------- /logo.png.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Devin Lin 2 | SPDX-License-Identifier: CC-BY-4.0 3 | -------------------------------------------------------------------------------- /org.kde.kclock.svg.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Devin Lin 2 | SPDX-License-Identifier: CC-BY-4.0 3 | -------------------------------------------------------------------------------- /org.kde.kclockd-autostart.desktop: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Han Young 3 | # 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | # 6 | 7 | [Desktop Entry] 8 | Name=Clock 9 | Name[ar]=الساعة 10 | Name[ast]=Reló 11 | Name[bg]=Часовник 12 | Name[ca]=Rellotge 13 | Name[ca@valencia]=Rellotge 14 | Name[cs]=Hodiny 15 | Name[de]=Clock 16 | Name[el]=Ρολόι 17 | Name[en_GB]=Clock 18 | Name[eo]=Horloĝo 19 | Name[es]=Reloj 20 | Name[et]=Kell 21 | Name[eu]=Erlojua 22 | Name[fi]=Kello 23 | Name[fr]=Horloge 24 | Name[gl]=Reloxo 25 | Name[he]=שעון 26 | Name[hi]=घड़ी 27 | Name[hu]=Óra 28 | Name[ia]=Clock (Horologio) 29 | Name[it]=Orologio 30 | Name[ka]=საათი 31 | Name[ko]=시계 32 | Name[lt]=Laikrodis 33 | Name[lv]=Pulkstenis 34 | Name[nl]=Klok 35 | Name[nn]=Klokke 36 | Name[pa]=ਘੜੀ 37 | Name[pl]=Zegar 38 | Name[pt]=Relógio 39 | Name[pt_BR]=Relógio 40 | Name[ro]=Ceas 41 | Name[ru]=Часы 42 | Name[sa]=घटिका 43 | Name[sk]=Hodiny 44 | Name[sl]=Clock 45 | Name[sv]=Klocka 46 | Name[th]=นาฬิกา 47 | Name[tr]=Saat 48 | Name[uk]=Годинник 49 | Name[x-test]=xxClockxx 50 | Name[zh_CN]=时钟 51 | Name[zh_TW]=時鐘 52 | Comment=A simple clock application in Kirigami. 53 | Comment[ar]=تطبيق ساعة بسيط مكتوب كراجامي 54 | Comment[bg]=Просто приложение за часовник в Kirigami. 55 | Comment[ca]=Una aplicació senzilla de rellotge creada amb el Kirigami. 56 | Comment[ca@valencia]=Una aplicació senzilla de rellotge creada amb Kirigami. 57 | Comment[cs]=Jednoduchá aplikace hodin v Kirigami. 58 | Comment[de]=Eine einfache Kirigami-Uhr. 59 | Comment[el]=Μια απλή εφαρμογή ρολογιού στο Kirigami. 60 | Comment[en_GB]=A simple clock application in Kirigami. 61 | Comment[eo]=Simpla horloĝ-apliko en Kirigami. 62 | Comment[es]=Sencilla aplicación de reloj en Kirigami. 63 | Comment[et]=Lihtne kellarakendus Kirigamis. 64 | Comment[eu]=Kirigami-rekin egindako erloju erraz bat. 65 | Comment[fi]=Yksinkertainen Kirigami-kellosovellus. 66 | Comment[fr]=Une application simple d'horloge dans Kirigami. 67 | Comment[gl]=Unha aplicación simple de reloxo feita con Kirigami. 68 | Comment[he]=יישום שעון פשוט ב־Kirigami. 69 | Comment[hi]=किरिगामी में एक सरल घड़ी अनुप्रयोग । 70 | Comment[hu]=Egyszerű óraalkalmazás Kirigamiban. 71 | Comment[ia]=Un simple application de horologio in Kirigami. 72 | Comment[it]=Una semplice applicazione per l'orologio in Kirigami. 73 | Comment[ka]=საათის უბრალო აპლიკაცია Kirigami-ში. 74 | Comment[ko]=Kirigami를 사용하는 간단한 시계 앱입니다. 75 | Comment[lt]=Paprasta laikrodžio programa, sukurta naudojant Kirigami. 76 | Comment[lv]=Vienkārša pulksteņa programma „Kirigami“ 77 | Comment[nl]=Een eenvoudige kloktoepassing in Kirigami. 78 | Comment[nn]=Ei enkel klokke i Kirigami. 79 | Comment[pa]=ਕੀਰੀਗਾਮੀ ਵਿੱਚ ਸਰਲ ਘੜੀ ਐਪਲੀਕੇਸ਼ਨ ਹੈ। 80 | Comment[pl]=Prosta aplikacja zegara w Kirigami. 81 | Comment[pt]=Uma aplicação simples de relógio do Kirigami. 82 | Comment[pt_BR]=Um simples aplicativo de relógio feito com o Kirigami. 83 | Comment[ro]=Aplicație simplă de ceas în Kirigami. 84 | Comment[ru]=Простое приложение-часы, написанное на Kirigami. 85 | Comment[sa]=किरिगामी मध्ये एकः सरलः घण्टायाः अनुप्रयोगः। 86 | Comment[sk]=Jednoduchá hodinová aplikácia v Kirigami. 87 | Comment[sl]=Enostavni program za uro v Kirigamiju. 88 | Comment[sv]=Ett enkelt klockprogram i Kirigami. 89 | Comment[th]=แอปพลิเคชันนาฬิกาแบบเรียบง่ายใน Kirigami 90 | Comment[tr]=Kirigami ile yazılmış yalın bir saat uygulaması 91 | Comment[uk]=Проста програма-годинник на Kirigami. 92 | Comment[x-test]=xxA simple clock application in Kirigami.xx 93 | Comment[zh_CN]=基于 Kirigami 的简易时钟应用程序。 94 | Comment[zh_TW]=使用 Kirigami 的簡單時鐘應用程式。 95 | Exec=kclockd 96 | Icon=kclock 97 | Type=Application 98 | Terminal=false 99 | Categories=Qt;KDE;Utility; 100 | -------------------------------------------------------------------------------- /org.kde.kclockd.service.in: -------------------------------------------------------------------------------- 1 | [D-BUS Service] 2 | Name=org.kde.kclockd 3 | Exec=@CMAKE_INSTALL_PREFIX@/bin/kclockd 4 | -------------------------------------------------------------------------------- /org.kde.kclockd.service.in.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Han Young 2 | SPDX-License-Identifier: GPL-2.0-or-later 3 | -------------------------------------------------------------------------------- /po/ja/kclockd.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: kclock\n" 4 | "Report-Msgid-Bugs-To: https://bugs.kde.org\n" 5 | "POT-Creation-Date: 2025-05-25 00:46+0000\n" 6 | "PO-Revision-Date: 2020-06-13 17:20-0700\n" 7 | "Last-Translator: Japanese KDE translation team \n" 8 | "Language-Team: Japanese \n" 9 | "Language: ja\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=1; plural=0;\n" 14 | "X-Accelerator-Marker: &\n" 15 | "X-Text-Markup: kde4\n" 16 | 17 | #, kde-format 18 | msgctxt "NAME OF TRANSLATORS" 19 | msgid "Your names" 20 | msgstr "" 21 | 22 | #, kde-format 23 | msgctxt "EMAIL OF TRANSLATORS" 24 | msgid "Your emails" 25 | msgstr "" 26 | 27 | #: alarm.cpp:72 alarm.h:51 28 | #, kde-format 29 | msgid "Alarm" 30 | msgstr "" 31 | 32 | #: alarm.cpp:76 timer.cpp:69 33 | #, kde-format 34 | msgctxt "@action:button" 35 | msgid "Dismiss" 36 | msgstr "" 37 | 38 | #: alarm.cpp:79 39 | #, kde-format 40 | msgid "Dismiss" 41 | msgstr "" 42 | 43 | #: alarm.cpp:82 44 | #, kde-format 45 | msgid "Snooze" 46 | msgstr "" 47 | 48 | #: alarmmodel.cpp:255 49 | #, kde-kuit-format 50 | msgctxt "@info" 51 | msgid "Alarm: %1 %2" 52 | msgstr "" 53 | 54 | #: kclockrunner.cpp:69 55 | #, kde-format 56 | msgctxt "@action:button Add 1 minute to timer" 57 | msgid "+1 Minute" 58 | msgstr "" 59 | 60 | #: kclockrunner.cpp:70 61 | #, kde-format 62 | msgctxt "@action:button" 63 | msgid "Reset Timer" 64 | msgstr "" 65 | 66 | #: kclockrunner.cpp:77 67 | #, kde-format 68 | msgctxt "Note this is a KRunner keyword" 69 | msgid "timer" 70 | msgstr "" 71 | 72 | #: kclockrunner.cpp:94 73 | #, kde-format 74 | msgctxt "Timer running or paused (remaining time)" 75 | msgid "%1 (%2 Remaining)" 76 | msgstr "" 77 | 78 | #: kclockrunner.cpp:96 79 | #, kde-format 80 | msgctxt "Timer not running (timer length)" 81 | msgid "%1 (%2)" 82 | msgstr "" 83 | 84 | #: kclockrunner.cpp:107 85 | #, kde-format 86 | msgctxt "KRunner category" 87 | msgid "Timer" 88 | msgstr "" 89 | 90 | #: kclockrunner.cpp:111 91 | #, kde-format 92 | msgctxt "@action" 93 | msgid "Pause" 94 | msgstr "" 95 | 96 | #: kclockrunner.cpp:113 97 | #, kde-format 98 | msgctxt "@action" 99 | msgid "Resume" 100 | msgstr "" 101 | 102 | #: kclockrunner.cpp:115 103 | #, kde-format 104 | msgctxt "@action" 105 | msgid "Start Timer" 106 | msgstr "" 107 | 108 | #: kclockrunner.cpp:130 109 | #, kde-format 110 | msgctxt "Note this is a KRunner keyword" 111 | msgid "alarm" 112 | msgstr "" 113 | 114 | #: kclockrunner.cpp:152 115 | #, kde-format 116 | msgctxt "Alarm name (time)" 117 | msgid "%1 (%2)" 118 | msgstr "" 119 | 120 | #: kclockrunner.cpp:162 121 | #, kde-format 122 | msgctxt "KRunner category" 123 | msgid "Alarm" 124 | msgstr "" 125 | 126 | #: kclockrunner.cpp:166 127 | #, kde-format 128 | msgctxt "@action Dismiss Alarm" 129 | msgid "Dismiss" 130 | msgstr "" 131 | 132 | #: kclockrunner.cpp:169 133 | #, kde-format 134 | msgctxt "Disable timer (rings in time)" 135 | msgid "Disable (Rings in %1)" 136 | msgstr "" 137 | 138 | #: kclockrunner.cpp:171 139 | #, kde-format 140 | msgctxt "@action Enable Alarm" 141 | msgid "Enable" 142 | msgstr "" 143 | 144 | #: main.cpp:32 145 | #, kde-format 146 | msgid "Don't use PowerDevil for alarms if it is available" 147 | msgstr "" 148 | 149 | #: main.cpp:48 150 | #, kde-format 151 | msgid "© 2020-2022 KDE Community" 152 | msgstr "" 153 | 154 | #: main.cpp:49 155 | #, kde-format 156 | msgid "Devin Lin" 157 | msgstr "" 158 | 159 | #: main.cpp:50 160 | #, kde-format 161 | msgid "Han Young" 162 | msgstr "" 163 | 164 | #: main.cpp:51 165 | #, kde-format 166 | msgid "Kai Uwe Broulik" 167 | msgstr "" 168 | 169 | #: main.cpp:51 170 | #, kde-format 171 | msgctxt "Author" 172 | msgid "Desktop Integration" 173 | msgstr "" 174 | 175 | #: timer.cpp:64 176 | #, kde-format 177 | msgid "Timer complete" 178 | msgstr "" 179 | 180 | #: timer.cpp:65 181 | #, kde-format 182 | msgid "Your timer %1 has finished!" 183 | msgstr "" 184 | 185 | #: timermodel.cpp:269 186 | #, kde-format 187 | msgctxt "@action:button Open kclock app" 188 | msgid "Open Clock" 189 | msgstr "" 190 | 191 | #: timermodel.cpp:274 192 | #, kde-format 193 | msgctxt "@action:button Add minute to timer" 194 | msgid "+1 Minute" 195 | msgstr "" 196 | 197 | #: timermodel.cpp:277 198 | #, kde-format 199 | msgctxt "@action:button Reset timer" 200 | msgid "Reset" 201 | msgstr "" 202 | 203 | #: timermodel.cpp:280 timermodel.cpp:305 204 | #, kde-format 205 | msgctxt "@action:button Pause timer" 206 | msgid "Pause" 207 | msgstr "" 208 | 209 | #: timermodel.cpp:298 210 | #, kde-format 211 | msgctxt "@title:notification Timer name (paused)" 212 | msgid "%1 (Paused)" 213 | msgstr "" 214 | 215 | #: timermodel.cpp:307 216 | #, kde-format 217 | msgctxt "@action:button Resume timer" 218 | msgid "Resume" 219 | msgstr "" 220 | 221 | #: xdgportal.cpp:34 222 | #, kde-format 223 | msgid "" 224 | "Allow the clock process to be in the background and launched on startup." 225 | msgstr "" 226 | -------------------------------------------------------------------------------- /po/th/kclockd.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: kclock\n" 4 | "Report-Msgid-Bugs-To: https://bugs.kde.org\n" 5 | "POT-Creation-Date: 2025-05-25 00:46+0000\n" 6 | "PO-Revision-Date: 2025-03-17 14:53+0700\n" 7 | "Last-Translator: Thai Translator \n" 8 | "Language-Team: Thai \n" 9 | "Language: th\n" 10 | "MIME-Version: 1.0\n" 11 | "Content-Type: text/plain; charset=UTF-8\n" 12 | "Content-Transfer-Encoding: 8bit\n" 13 | "Plural-Forms: nplurals=1; plural=0;\n" 14 | 15 | #, kde-format 16 | msgctxt "NAME OF TRANSLATORS" 17 | msgid "Your names" 18 | msgstr "" 19 | 20 | #, kde-format 21 | msgctxt "EMAIL OF TRANSLATORS" 22 | msgid "Your emails" 23 | msgstr "" 24 | 25 | #: alarm.cpp:72 alarm.h:51 26 | #, kde-format 27 | msgid "Alarm" 28 | msgstr "" 29 | 30 | #: alarm.cpp:76 timer.cpp:69 31 | #, kde-format 32 | msgctxt "@action:button" 33 | msgid "Dismiss" 34 | msgstr "" 35 | 36 | #: alarm.cpp:79 37 | #, kde-format 38 | msgid "Dismiss" 39 | msgstr "" 40 | 41 | #: alarm.cpp:82 42 | #, kde-format 43 | msgid "Snooze" 44 | msgstr "" 45 | 46 | #: alarmmodel.cpp:255 47 | #, kde-kuit-format 48 | msgctxt "@info" 49 | msgid "Alarm: %1 %2" 50 | msgstr "" 51 | 52 | #: kclockrunner.cpp:69 53 | #, kde-format 54 | msgctxt "@action:button Add 1 minute to timer" 55 | msgid "+1 Minute" 56 | msgstr "" 57 | 58 | #: kclockrunner.cpp:70 59 | #, kde-format 60 | msgctxt "@action:button" 61 | msgid "Reset Timer" 62 | msgstr "" 63 | 64 | #: kclockrunner.cpp:77 65 | #, kde-format 66 | msgctxt "Note this is a KRunner keyword" 67 | msgid "timer" 68 | msgstr "" 69 | 70 | #: kclockrunner.cpp:94 71 | #, kde-format 72 | msgctxt "Timer running or paused (remaining time)" 73 | msgid "%1 (%2 Remaining)" 74 | msgstr "" 75 | 76 | #: kclockrunner.cpp:96 77 | #, kde-format 78 | msgctxt "Timer not running (timer length)" 79 | msgid "%1 (%2)" 80 | msgstr "" 81 | 82 | #: kclockrunner.cpp:107 83 | #, kde-format 84 | msgctxt "KRunner category" 85 | msgid "Timer" 86 | msgstr "" 87 | 88 | #: kclockrunner.cpp:111 89 | #, kde-format 90 | msgctxt "@action" 91 | msgid "Pause" 92 | msgstr "" 93 | 94 | #: kclockrunner.cpp:113 95 | #, kde-format 96 | msgctxt "@action" 97 | msgid "Resume" 98 | msgstr "" 99 | 100 | #: kclockrunner.cpp:115 101 | #, kde-format 102 | msgctxt "@action" 103 | msgid "Start Timer" 104 | msgstr "" 105 | 106 | #: kclockrunner.cpp:130 107 | #, kde-format 108 | msgctxt "Note this is a KRunner keyword" 109 | msgid "alarm" 110 | msgstr "" 111 | 112 | #: kclockrunner.cpp:152 113 | #, kde-format 114 | msgctxt "Alarm name (time)" 115 | msgid "%1 (%2)" 116 | msgstr "" 117 | 118 | #: kclockrunner.cpp:162 119 | #, kde-format 120 | msgctxt "KRunner category" 121 | msgid "Alarm" 122 | msgstr "" 123 | 124 | #: kclockrunner.cpp:166 125 | #, kde-format 126 | msgctxt "@action Dismiss Alarm" 127 | msgid "Dismiss" 128 | msgstr "" 129 | 130 | #: kclockrunner.cpp:169 131 | #, kde-format 132 | msgctxt "Disable timer (rings in time)" 133 | msgid "Disable (Rings in %1)" 134 | msgstr "" 135 | 136 | #: kclockrunner.cpp:171 137 | #, kde-format 138 | msgctxt "@action Enable Alarm" 139 | msgid "Enable" 140 | msgstr "" 141 | 142 | #: main.cpp:32 143 | #, kde-format 144 | msgid "Don't use PowerDevil for alarms if it is available" 145 | msgstr "" 146 | 147 | #: main.cpp:48 148 | #, kde-format 149 | msgid "© 2020-2022 KDE Community" 150 | msgstr "" 151 | 152 | #: main.cpp:49 153 | #, kde-format 154 | msgid "Devin Lin" 155 | msgstr "" 156 | 157 | #: main.cpp:50 158 | #, kde-format 159 | msgid "Han Young" 160 | msgstr "" 161 | 162 | #: main.cpp:51 163 | #, kde-format 164 | msgid "Kai Uwe Broulik" 165 | msgstr "" 166 | 167 | #: main.cpp:51 168 | #, kde-format 169 | msgctxt "Author" 170 | msgid "Desktop Integration" 171 | msgstr "" 172 | 173 | #: timer.cpp:64 174 | #, kde-format 175 | msgid "Timer complete" 176 | msgstr "" 177 | 178 | #: timer.cpp:65 179 | #, kde-format 180 | msgid "Your timer %1 has finished!" 181 | msgstr "" 182 | 183 | #: timermodel.cpp:269 184 | #, kde-format 185 | msgctxt "@action:button Open kclock app" 186 | msgid "Open Clock" 187 | msgstr "" 188 | 189 | #: timermodel.cpp:274 190 | #, kde-format 191 | msgctxt "@action:button Add minute to timer" 192 | msgid "+1 Minute" 193 | msgstr "" 194 | 195 | #: timermodel.cpp:277 196 | #, kde-format 197 | msgctxt "@action:button Reset timer" 198 | msgid "Reset" 199 | msgstr "" 200 | 201 | #: timermodel.cpp:280 timermodel.cpp:305 202 | #, kde-format 203 | msgctxt "@action:button Pause timer" 204 | msgid "Pause" 205 | msgstr "" 206 | 207 | #: timermodel.cpp:298 208 | #, kde-format 209 | msgctxt "@title:notification Timer name (paused)" 210 | msgid "%1 (Paused)" 211 | msgstr "" 212 | 213 | #: timermodel.cpp:307 214 | #, kde-format 215 | msgctxt "@action:button Resume timer" 216 | msgid "Resume" 217 | msgstr "" 218 | 219 | #: xdgportal.cpp:34 220 | #, kde-format 221 | msgid "" 222 | "Allow the clock process to be in the background and launched on startup." 223 | msgstr "" 224 | -------------------------------------------------------------------------------- /screenshots/kclock-desktop-timer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kclock/d73f1fffd6421bec7af45f74f81337a0ace4dacf/screenshots/kclock-desktop-timer.png -------------------------------------------------------------------------------- /screenshots/kclock-desktop-timer.png.license: -------------------------------------------------------------------------------- 1 | Copyright 2021 Devin Lin 2 | SPDX-License-Identifier: CC-BY-4.0 3 | -------------------------------------------------------------------------------- /screenshots/kclock-desktop-timezones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kclock/d73f1fffd6421bec7af45f74f81337a0ace4dacf/screenshots/kclock-desktop-timezones.png -------------------------------------------------------------------------------- /screenshots/kclock-desktop-timezones.png.license: -------------------------------------------------------------------------------- 1 | Copyright 2021 Devin Lin 2 | SPDX-License-Identifier: CC-BY-4.0 3 | -------------------------------------------------------------------------------- /screenshots/kclock-mobile-alarms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kclock/d73f1fffd6421bec7af45f74f81337a0ace4dacf/screenshots/kclock-mobile-alarms.png -------------------------------------------------------------------------------- /screenshots/kclock-mobile-alarms.png.license: -------------------------------------------------------------------------------- 1 | Copyright 2021 Devin Lin 2 | SPDX-License-Identifier: CC-BY-4.0 3 | -------------------------------------------------------------------------------- /screenshots/kclock-mobile-stopwatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kclock/d73f1fffd6421bec7af45f74f81337a0ace4dacf/screenshots/kclock-mobile-stopwatch.png -------------------------------------------------------------------------------- /screenshots/kclock-mobile-stopwatch.png.license: -------------------------------------------------------------------------------- 1 | Copyright 2021 Devin Lin 2 | SPDX-License-Identifier: CC-BY-4.0 3 | -------------------------------------------------------------------------------- /screenshots/kclock-mobile-timers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kclock/d73f1fffd6421bec7af45f74f81337a0ace4dacf/screenshots/kclock-mobile-timers.png -------------------------------------------------------------------------------- /screenshots/kclock-mobile-timers.png.license: -------------------------------------------------------------------------------- 1 | Copyright 2021 Devin Lin 2 | SPDX-License-Identifier: CC-BY-4.0 3 | -------------------------------------------------------------------------------- /screenshots/kclock-mobile-timezones.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kclock/d73f1fffd6421bec7af45f74f81337a0ace4dacf/screenshots/kclock-mobile-timezones.png -------------------------------------------------------------------------------- /screenshots/kclock-mobile-timezones.png.license: -------------------------------------------------------------------------------- 1 | Copyright 2021 Devin Lin 2 | SPDX-License-Identifier: CC-BY-4.0 3 | -------------------------------------------------------------------------------- /snapcraft.yaml: -------------------------------------------------------------------------------- 1 | # * Copyright 2024 Soumyadeep Ghosh (সৌম্যদীপ ঘোষ) 2 | # * Copyright 2023-2025 Scarlett Moore 3 | # * SPDX-License-Identifier: GPL-2.0-or-later 4 | name: kclock 5 | confinement: strict 6 | grade: stable 7 | base: core24 8 | adopt-info: kclock 9 | 10 | apps: 11 | kclockd: 12 | command: usr/bin/kclockd 13 | command-chain: 14 | - usr/bin/kclockd-command-chain.sh 15 | daemon: simple 16 | plugs: 17 | - network 18 | - network-bind 19 | 20 | kclock: 21 | extensions: 22 | - kde-neon-6 23 | common-id: org.kde.kclock 24 | desktop: usr/share/applications/org.kde.kclock.desktop 25 | command: usr/bin/kclock 26 | command-chain: 27 | - usr/bin/kclockd-command-chain.sh 28 | plugs: 29 | - home 30 | slots: 31 | session-dbus-interface: 32 | interface: dbus 33 | name: org.kde.kclockd 34 | bus: session 35 | 36 | parts: 37 | libplasma: 38 | plugin: cmake 39 | source: https://invent.kde.org/plasma/libplasma/-/archive/v6.0.3/libplasma-v6.0.3.tar.gz 40 | cmake-parameters: 41 | - -DCMAKE_INSTALL_PREFIX=/usr 42 | - -DCMAKE_BUILD_TYPE=Release 43 | - -DBUILD_DESKTOPTHEMES=OFF 44 | - -DBUILD_TESTING=OFF 45 | build-environment: 46 | - CMAKE_PREFIX_PATH: $CRAFT_STAGE/usr:/snap/kde-qt6--sdk/current/usr:/snap/kf6-core24-sdk/current/usr${CMAKE_PREFIX_PATH:+:$CMAKE_PREFIX_PATH} 47 | - LD_LIBRARY_PATH: > 48 | "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH" 49 | build-packages: 50 | - libxcb-composite0-dev 51 | - libxcb-damage0-dev 52 | - libxcb-shape0-dev 53 | - libxcb-glx0-dev 54 | - libvulkan-dev 55 | - libxkbcommon-dev 56 | - libwayland-dev 57 | - doxygen 58 | - gettext 59 | - graphviz 60 | - pkg-config 61 | - libgl-dev 62 | - libgl1-mesa-dev 63 | - libgles-dev 64 | - libglvnd-dev 65 | - libglx-dev 66 | - libegl-dev 67 | - libsm-dev 68 | - docbook 69 | - docbook-xml 70 | - docbook-xsl 71 | prime: 72 | - -usr/lib/*/cmake/* 73 | - -usr/include/* 74 | - -usr/share/ECM/* 75 | - -usr/share/doc/* 76 | - -usr/share/man/* 77 | - -usr/share/icons/breeze-dark* 78 | - -usr/bin/X11 79 | - -usr/lib/gcc/$CRAFT_ARCH_TRIPLET_BUILD_FOR 80 | - -usr/lib/aspell/* 81 | - -usr/share/docs 82 | - -usr/share/lintain 83 | 84 | plasma-mobile-sounds: 85 | source: https://download.kde.org/stable/plasma-mobile-sounds/0.1/plasma-mobile-sounds-0.1.tar.xz 86 | plugin: cmake 87 | cmake-parameters: 88 | - -DCMAKE_INSTALL_PREFIX=/usr 89 | - -DCMAKE_BUILD_TYPE=Release 90 | build-environment: 91 | - CMAKE_PREFIX_PATH: $CRAFT_STAGE/usr:/snap/kde-qt6--sdk/current/usr:/snap/kf6-core24-sdk/current/usr${CMAKE_PREFIX_PATH:+:$CMAKE_PREFIX_PATH} 92 | - LD_LIBRARY_PATH: > 93 | "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH" 94 | 95 | kclock: 96 | after: [plasma-mobile-sounds, libplasma] 97 | parse-info: 98 | - usr/share/metainfo/org.kde.kclock.appdata.xml 99 | plugin: cmake 100 | source: . 101 | source-type: local 102 | build-environment: 103 | - CMAKE_PREFIX_PATH: $CRAFT_STAGE/usr:/snap/kde-qt6--sdk/current/usr:/snap/kf6-core24-sdk/current/usr${CMAKE_PREFIX_PATH:+:$CMAKE_PREFIX_PATH} 104 | - LD_LIBRARY_PATH: > 105 | "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH" 106 | cmake-parameters: 107 | - -DCMAKE_INSTALL_PREFIX=/usr 108 | - -DCMAKE_BUILD_TYPE=Release 109 | - -DENABLE_TESTING=OFF 110 | - -DBUILD_TESTING=OFF 111 | build-packages: 112 | - libpulse-dev 113 | override-build: | 114 | craftctl default 115 | cat < $CRAFT_PART_INSTALL/usr/bin/kclockd-command-chain.sh 116 | #!/bin/sh 117 | exec \$SNAP/usr/bin/kclockd & exec "\$@" 118 | EOF 119 | chmod +x $CRAFT_PART_INSTALL/usr/bin/kclockd-command-chain.sh 120 | prime: 121 | - -usr/lib/*/cmake/* 122 | - -usr/include/* 123 | - -usr/share/ECM/* 124 | - -usr/share/man/* 125 | - -usr/bin/X11 126 | - -usr/lib/gcc/$CRAFT_ARCH_TRIPLET/6.0.0 127 | - -usr/lib/aspell/* 128 | - -usr/share/lintian 129 | gpu-2404: 130 | after: [kclock] 131 | source: https://github.com/canonical/gpu-snap.git 132 | plugin: dump 133 | override-prime: | 134 | craftctl default 135 | ${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404 136 | prime: 137 | - bin/gpu-2404-wrapper 138 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Han Young 3 | # Copyright 2020 Devin Lin 4 | # 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | # 7 | 8 | add_subdirectory(kclockd) 9 | add_subdirectory(kclock) 10 | if (BUILD_PLASMOID) 11 | add_subdirectory(plasmoid) 12 | endif() 13 | -------------------------------------------------------------------------------- /src/kclock/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Han Young 3 | # Copyright 2020-2021 Devin Lin 4 | # 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | # 7 | 8 | set(kclock_SRCS 9 | alarm.cpp 10 | alarm.h 11 | alarmmodel.cpp 12 | alarmmodel.h 13 | timer.cpp 14 | timer.h 15 | timermodel.cpp 16 | timermodel.h 17 | utilmodel.cpp 18 | utilmodel.h 19 | stopwatchtimer.cpp 20 | stopwatchtimer.h 21 | kclockformat.cpp 22 | kclockformat.h 23 | settingsmodel.cpp 24 | settingsmodel.h 25 | stopwatchmodel.cpp 26 | stopwatchmodel.h 27 | addlocationmodel.cpp 28 | addlocationmodel.h 29 | timerpresetmodel.cpp 30 | timerpresetmodel.h 31 | savedlocationsmodel.cpp 32 | savedlocationsmodel.h 33 | windowexposure.cpp 34 | windowexposure.h 35 | ) 36 | 37 | # So we can share the enums. 38 | kconfig_add_kcfg_files(kclock_SRCS ../kclockd/kclockdsettings.kcfgc GENERATE_MOC) 39 | set(SettingsXML ${CMAKE_CURRENT_BINARY_DIR}/../kclockd/org.kde.kclockd.KClockSettings.xml) 40 | 41 | set(AlarmXML ${CMAKE_CURRENT_BINARY_DIR}/../kclockd/org.kde.kclockd.Alarm.xml) 42 | set(AlarmModelXML ${CMAKE_CURRENT_BINARY_DIR}/../kclockd/org.kde.kclockd.AlarmModel.xml) 43 | set(TimermModelXML ${CMAKE_CURRENT_BINARY_DIR}/../kclockd/org.kde.kclockd.TimerModel.xml) 44 | set(TimerXML ${CMAKE_CURRENT_BINARY_DIR}/../kclockd/org.kde.kclockd.Timer.xml) 45 | set(UtilityXML ${CMAKE_CURRENT_BINARY_DIR}/../kclockd/org.kde.kclockd.Utility.xml) 46 | 47 | qt_add_dbus_interface(kclock_SRCS ${SettingsXML} kclocksettingsinterface ) 48 | qt_add_dbus_interface(kclock_SRCS ${AlarmXML} alarminterface ) 49 | qt_add_dbus_interface(kclock_SRCS ${AlarmModelXML} alarmmodelinterface ) 50 | qt_add_dbus_interface(kclock_SRCS ${TimermModelXML} timermodelinterface ) 51 | qt_add_dbus_interface(kclock_SRCS ${TimerXML} timerinterface ) 52 | qt_add_dbus_interface(kclock_SRCS ${UtilityXML} utilityinterface ) 53 | 54 | add_executable(kclock main.cpp ${kclock_SRCS} ${RESOURCES}) 55 | add_dependencies(kclock kclockd) 56 | 57 | ecm_add_qml_module(kclock URI org.kde.kclock QML_FILES 58 | qml/Main.qml 59 | 60 | qml/alarm/AlarmForm.qml 61 | qml/alarm/AlarmFormPage.qml 62 | qml/alarm/AlarmListPage.qml 63 | qml/alarm/AlarmListDelegate.qml 64 | qml/alarm/AlarmRingingPopup.qml 65 | 66 | qml/components/SoundPickerPage.qml 67 | qml/components/AboutPage.qml 68 | qml/components/BottomToolbar.qml 69 | qml/components/DialogComboBox.qml 70 | qml/components/FooterToolBarButton.qml 71 | qml/components/FloatingActionButton.qml 72 | qml/components/Sidebar.qml 73 | qml/components/TimePicker.qml 74 | qml/components/TimePickerSpinBox.qml 75 | qml/components/TimePickerSpinBoxButton.qml 76 | 77 | qml/timer/PresetDurationButton.qml 78 | qml/timer/TimerComponent.qml 79 | qml/timer/TimerForm.qml 80 | qml/timer/TimerFormDialog.qml 81 | qml/timer/TimerListDelegate.qml 82 | qml/timer/TimerListPage.qml 83 | qml/timer/TimerPage.qml 84 | qml/timer/TimerRingingPopup.qml 85 | 86 | qml/settings/SettingsPage.qml 87 | 88 | qml/stopwatch/StopwatchPage.qml 89 | 90 | qml/time/AddLocationListView.qml 91 | qml/time/AddLocationPage.qml 92 | qml/time/AddLocationWrapper.qml 93 | qml/time/AnalogClock.qml 94 | qml/time/AnalogClockHand.qml 95 | qml/time/TimePage.qml 96 | qml/time/TimePageDelegate.qml 97 | ) 98 | 99 | target_link_libraries(kclock PRIVATE 100 | Qt6::Qml 101 | Qt6::Gui 102 | Qt6::QuickControls2 103 | Qt6::Widgets 104 | Qt6::Multimedia 105 | KF6::I18n 106 | KF6::ConfigCore 107 | KF6::ConfigGui 108 | KF6::CoreAddons 109 | KF6::DBusAddons 110 | KF6::Crash 111 | KF6::I18nQml 112 | KF6::Svg 113 | ) 114 | 115 | target_include_directories(kclock PRIVATE ${CMAKE_BINARY_DIR}) 116 | install(TARGETS kclock ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) 117 | -------------------------------------------------------------------------------- /src/kclock/Messages.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # Copyright 2020 Yuri Chornoivan 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | $XGETTEXT `find . -name \*.cpp -o -name \*.h -o -name \*.qml -o -name \*.js` -o $podir/kclock.pot 7 | -------------------------------------------------------------------------------- /src/kclock/addlocationmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * Copyright 2019 Nick Reitemeyer 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "kclockformat.h" 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | class QJSEngine; 22 | class QQmlEngine; 23 | 24 | class AddLocationModel : public QAbstractListModel 25 | { 26 | Q_OBJECT 27 | 28 | public: 29 | static AddLocationModel *instance(); 30 | 31 | explicit AddLocationModel(QObject *parent = nullptr); 32 | 33 | enum Roles { 34 | CityRole = Qt::DisplayRole, 35 | CountryRole = Qt::DisplayRole + 1, 36 | TimeZoneRole = Qt::UserRole + 0, 37 | CurrentTimeRole = Qt::UserRole + 1, 38 | AddedRole = Qt::UserRole + 2, 39 | IdRole 40 | }; 41 | 42 | int rowCount(const QModelIndex &parent) const override; 43 | QVariant data(const QModelIndex &index, int role) const override; 44 | Qt::ItemFlags flags(const QModelIndex &index) const override; 45 | QHash roleNames() const override; 46 | 47 | public Q_SLOTS: 48 | void load(); 49 | void updateAddedLocations(); 50 | 51 | private: 52 | QList m_list; 53 | QSet m_addedLocations; 54 | }; 55 | 56 | class AddLocationSearchModel : public QSortFilterProxyModel 57 | { 58 | Q_OBJECT 59 | QML_ELEMENT 60 | QML_SINGLETON 61 | 62 | Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged) 63 | 64 | public: 65 | static AddLocationSearchModel *instance(); 66 | static AddLocationSearchModel *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); 67 | 68 | QString query() const; 69 | void setQuery(const QString &query); 70 | Q_SIGNAL void queryChanged(const QString &query); 71 | 72 | Q_INVOKABLE void addLocation(int row); 73 | 74 | protected: 75 | bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; 76 | bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; 77 | 78 | private: 79 | explicit AddLocationSearchModel(QObject *parent = nullptr); 80 | 81 | QString m_query; 82 | }; 83 | -------------------------------------------------------------------------------- /src/kclock/alarm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "alarminterface.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | class Alarm : public QObject 22 | { 23 | Q_OBJECT 24 | QML_ELEMENT 25 | QML_UNCREATABLE("Managed by AlarmModel") 26 | Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) 27 | Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) 28 | Q_PROPERTY(QString formattedTime READ formattedTime NOTIFY formattedTimeChanged) 29 | Q_PROPERTY(int hours READ hours WRITE setHours NOTIFY hoursChanged) 30 | Q_PROPERTY(int minutes READ minutes WRITE setMinutes NOTIFY minutesChanged) 31 | Q_PROPERTY(int daysOfWeek READ daysOfWeek WRITE setDaysOfWeek NOTIFY daysOfWeekChanged) 32 | Q_PROPERTY(QString audioPath READ audioPath WRITE setAudioPath NOTIFY audioPathChanged) 33 | Q_PROPERTY(int ringDuration READ ringDuration WRITE setRingDuration NOTIFY ringDurationChanged) 34 | Q_PROPERTY(int snoozeDuration READ snoozeDuration WRITE setSnoozeDuration NOTIFY snoozeDurationChanged) 35 | Q_PROPERTY(int snoozedLength READ snoozedLength NOTIFY snoozedLengthChanged) 36 | Q_PROPERTY(bool ringing READ ringing NOTIFY ringingChanged) 37 | Q_PROPERTY(quint64 nextRingTime READ nextRingTime NOTIFY nextRingTimeChanged) 38 | 39 | public: 40 | explicit Alarm(); 41 | explicit Alarm(const QString &uuid); 42 | 43 | QUuid uuid() const; 44 | 45 | QString name() const; 46 | void setName(const QString &name); 47 | 48 | bool enabled() const; 49 | void setEnabled(bool enabled); 50 | 51 | QString formattedTime() const; 52 | 53 | int hours() const; 54 | void setHours(int hours); 55 | 56 | int minutes() const; 57 | void setMinutes(int minutes); 58 | 59 | int daysOfWeek() const; 60 | void setDaysOfWeek(int daysOfWeek); 61 | 62 | QString audioPath() const; 63 | void setAudioPath(const QString &path); 64 | 65 | int ringDuration() const; 66 | void setRingDuration(int ringDuration); 67 | 68 | int snoozeDuration() const; 69 | void setSnoozeDuration(int snoozeDuration); 70 | 71 | int snoozedLength() const; 72 | 73 | bool ringing() const; 74 | 75 | quint64 nextRingTime() const; 76 | 77 | bool isValid() const; 78 | 79 | Q_INVOKABLE void dismiss(); 80 | Q_INVOKABLE void snooze(); 81 | 82 | Q_INVOKABLE QString timeToRingFormatted(); 83 | 84 | Q_INVOKABLE void save(); 85 | 86 | Q_SIGNALS: 87 | void nameChanged(); 88 | void enabledChanged(); 89 | void formattedTimeChanged(); 90 | void hoursChanged(); 91 | void minutesChanged(); 92 | void daysOfWeekChanged(); 93 | void audioPathChanged(); 94 | void ringDurationChanged(); 95 | void snoozeDurationChanged(); 96 | void snoozedLengthChanged(); 97 | void ringingChanged(); 98 | void nextRingTimeChanged(); 99 | 100 | private Q_SLOTS: 101 | void updateName(); 102 | void updateEnabled(); 103 | void updateHours(); 104 | void updateMinutes(); 105 | void updateDaysOfWeek(); 106 | void updateAudioPath(); 107 | void updateRingDuration(); 108 | void updateSnoozeDuration(); 109 | void updateSnoozedLength(); 110 | void updateRinging(); 111 | void updateNextRingTime(); 112 | 113 | private: 114 | org::kde::kclock::Alarm *m_interface; 115 | 116 | QUuid m_uuid; 117 | 118 | QString m_name; 119 | bool m_enabled; 120 | int m_hours; 121 | int m_minutes; 122 | int m_daysOfWeek; 123 | QString m_audioPath; 124 | int m_ringDuration; 125 | int m_snoozeDuration; 126 | int m_snoozedLength; 127 | bool m_ringing; 128 | qint64 m_nextRingTime; 129 | 130 | bool m_isValid = true; 131 | }; 132 | -------------------------------------------------------------------------------- /src/kclock/alarmmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "alarm.h" 11 | #include "alarmmodelinterface.h" 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | class QJSEngine; 19 | class QQmlEngine; 20 | 21 | class AlarmModel : public QAbstractListModel 22 | { 23 | Q_OBJECT 24 | QML_ELEMENT 25 | QML_SINGLETON 26 | 27 | Q_PROPERTY(bool connectedToDaemon READ connectedToDaemon NOTIFY connectedToDaemonChanged) 28 | Q_PROPERTY(bool busy READ busy NOTIFY busyChanged) 29 | 30 | public: 31 | static AlarmModel *instance(); 32 | static AlarmModel *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); 33 | 34 | enum { 35 | AlarmRole, 36 | }; 37 | 38 | void load(); 39 | 40 | bool busy() const; 41 | Q_SIGNAL void busyChanged(bool busy); 42 | 43 | int rowCount(const QModelIndex &parent) const override; 44 | QVariant data(const QModelIndex &index, int role) const override; 45 | Qt::ItemFlags flags(const QModelIndex &index) const override; 46 | QHash roleNames() const override; 47 | 48 | Q_INVOKABLE void remove(int index); 49 | 50 | Q_INVOKABLE void addAlarm(const QString &name, 51 | int hours, 52 | int minutes, 53 | int daysOfWeek, 54 | const QString &audioPath, 55 | int ringDuration, 56 | int snoozeDuration); // in 24 hours units, ringTone could be chosen from a list 57 | 58 | Q_INVOKABLE QString timeToRingFormatted(int hours, int minutes, int daysOfWeek); // for new alarm use 59 | 60 | bool connectedToDaemon(); 61 | void setConnectedToDaemon(bool connectedToDaemon); 62 | 63 | Q_SIGNALS: 64 | void connectedToDaemonChanged(); 65 | 66 | private Q_SLOTS: 67 | void addAlarmInternal(QString uuid); 68 | void removeAlarm(const QString &uuid); 69 | 70 | private: 71 | org::kde::kclock::AlarmModel *m_interface; 72 | QDBusServiceWatcher *m_watcher; 73 | bool m_connectedToDaemon = false; 74 | bool m_busy = false; 75 | 76 | explicit AlarmModel(QObject *parent = nullptr); 77 | QList alarmsList; 78 | }; 79 | -------------------------------------------------------------------------------- /src/kclock/kclock_algorithm.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | namespace KClock 11 | { 12 | template 13 | int insert_index(const typename C::value_type &obj, 14 | const C &container, 15 | bool (*less_than)(const typename C::value_type &left, const typename C::value_type &right)) 16 | { 17 | int index = 0; 18 | for (auto element : container) { 19 | if (less_than(obj, element)) { 20 | return index; 21 | } 22 | ++index; 23 | } 24 | return index; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/kclock/kclockformat.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #include "kclockformat.h" 9 | 10 | #include "settingsmodel.h" 11 | #include "utilmodel.h" 12 | 13 | #include 14 | #include 15 | 16 | KclockFormat::KclockFormat(QObject *parent) 17 | : QObject(parent) 18 | , m_timer(new QTimer(this)) 19 | { 20 | connect(SettingsModel::instance(), &SettingsModel::timeFormatChanged, this, &KclockFormat::updateCurrentTime); 21 | connect(m_timer, &QTimer::timeout, this, &KclockFormat::updateCurrentTime); 22 | 23 | this->startTimer(); 24 | } 25 | 26 | QString KclockFormat::currentTime() const 27 | { 28 | return m_currentTime; 29 | } 30 | 31 | void KclockFormat::updateCurrentTime() 32 | { 33 | const QString newTime = QLocale::system().toString(QTime::currentTime(), UtilModel::instance()->timeFormat()); 34 | if (m_currentTime != newTime) { 35 | m_currentTime = newTime; 36 | Q_EMIT timeChanged(); 37 | } 38 | } 39 | 40 | QString KclockFormat::formatTimeString(int hours, int minutes) 41 | { 42 | return QLocale::system().toString(QTime(hours, minutes), UtilModel::instance()->timeFormat()); 43 | } 44 | 45 | bool KclockFormat::isChecked(int dayIndex, int daysOfWeek) 46 | { 47 | // convert start day to standard start day of week, Monday 48 | dayIndex += QLocale::system().firstDayOfWeek() - 1; 49 | while (dayIndex > 6) 50 | dayIndex -= 7; 51 | 52 | int day = 1; 53 | day <<= dayIndex; 54 | return daysOfWeek & day; 55 | } 56 | 57 | void KclockFormat::startTimer() 58 | { 59 | updateCurrentTime(); 60 | m_timer->start(1000); 61 | } 62 | 63 | WeekModel::WeekModel(QObject *parent) 64 | : QAbstractListModel(parent) 65 | { 66 | // init week and flag value 67 | int dayFlag[] = {1, 2, 4, 8, 16, 32, 64}, i = 0; 68 | for (int j = QLocale::system().firstDayOfWeek(); j <= 7; j++) { 69 | m_listItem[i++] = std::tuple(QLocale::system().dayName(j, QLocale::LongFormat), dayFlag[j - 1]); 70 | } 71 | 72 | for (int j = 1; j < QLocale::system().firstDayOfWeek(); j++) { 73 | m_listItem[i++] = std::tuple(QLocale::system().dayName(j, QLocale::LongFormat), dayFlag[j - 1]); 74 | } 75 | } 76 | 77 | int WeekModel::rowCount(const QModelIndex &parent) const 78 | { 79 | if (parent.isValid()) { 80 | return 0; 81 | } 82 | return 7; 83 | } 84 | 85 | QVariant WeekModel::data(const QModelIndex &index, int role) const 86 | { 87 | if (!index.isValid() || index.row() >= 7) { 88 | return QVariant(); 89 | } 90 | if (role == NameRole) 91 | return std::get<0>(m_listItem[index.row()]); 92 | else if (role == FlagRole) 93 | return std::get<1>(m_listItem[index.row()]); 94 | else 95 | return QVariant(); 96 | } 97 | 98 | QHash WeekModel::roleNames() const 99 | { 100 | return {{NameRole, "name"}, {FlagRole, "flag"}}; 101 | } 102 | 103 | #include "moc_kclockformat.cpp" 104 | -------------------------------------------------------------------------------- /src/kclock/kclockformat.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | class QJSEngine; 21 | 22 | class WeekModel; 23 | class KclockFormat : public QObject 24 | { 25 | Q_OBJECT 26 | QML_NAMED_ELEMENT(KClockFormat) 27 | QML_SINGLETON 28 | Q_PROPERTY(QString currentTime READ currentTime NOTIFY timeChanged) 29 | 30 | public: 31 | static KclockFormat *instance() 32 | { 33 | static KclockFormat *singleton = new KclockFormat(); 34 | return singleton; 35 | }; 36 | 37 | static KclockFormat *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) 38 | { 39 | Q_UNUSED(qmlEngine); 40 | Q_UNUSED(jsEngine); 41 | auto *format = instance(); 42 | QQmlEngine::setObjectOwnership(format, QQmlEngine::CppOwnership); 43 | return format; 44 | } 45 | 46 | QString currentTime() const; 47 | QString formatTimeString(int hours, int minutes); 48 | Q_INVOKABLE bool isChecked(int dayIndex, int daysOfWeek); 49 | 50 | private Q_SLOTS: 51 | void updateCurrentTime(); 52 | 53 | Q_SIGNALS: 54 | void timeChanged(); 55 | 56 | private: 57 | explicit KclockFormat(QObject *parent = nullptr); 58 | 59 | void startTimer(); // basic settings for updating time display 60 | 61 | QTimer *m_timer; 62 | QString m_currentTime; 63 | }; 64 | 65 | using weekListItem = std::array, 7>; 66 | 67 | class WeekModel : public QAbstractListModel 68 | { 69 | Q_OBJECT 70 | QML_ELEMENT 71 | QML_SINGLETON 72 | 73 | public: 74 | enum { 75 | NameRole = Qt::DisplayRole, 76 | FlagRole = Qt::UserRole + 1 77 | }; 78 | 79 | explicit WeekModel(QObject *parent = nullptr); 80 | 81 | int rowCount(const QModelIndex &parent) const override; 82 | QVariant data(const QModelIndex &index, int role) const override; 83 | QHash roleNames() const override; 84 | 85 | private: 86 | weekListItem m_listItem; 87 | }; 88 | -------------------------------------------------------------------------------- /src/kclock/qml/alarm/AlarmFormPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * Copyright 2019 Nick Reitemeyer 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | import QtQuick 10 | import QtQuick.Controls 11 | import QtQuick.Window 12 | import QtQuick.Layouts 13 | import QtQuick.Dialogs 14 | 15 | import org.kde.kirigami as Kirigami 16 | 17 | import org.kde.kclock 18 | 19 | Kirigami.ScrollablePage { 20 | id: root 21 | 22 | // null if this is a new alarm page, and the alarm to edit otherwise 23 | property Alarm selectedAlarm: null 24 | 25 | title: selectedAlarm ? i18nc("Edit alarm page title", "Editing %1", selectedAlarm.name === "" ? i18n("Alarm") : selectedAlarm.name) 26 | : i18n("New Alarm"); 27 | background: null 28 | 29 | function accept() { 30 | form.submitForm(); 31 | pageStack.currentIndex--; 32 | } 33 | 34 | actions: Kirigami.Action { 35 | icon.name: "dialog-ok" 36 | text: i18n("Done") 37 | onTriggered: root.accept() 38 | } 39 | 40 | // mobile footer actions 41 | footer: ToolBar { 42 | id: toolbar 43 | visible: Kirigami.Settings.isMobile 44 | height: Kirigami.Settings.isMobile ? implicitHeight : 0 45 | topPadding: 0; bottomPadding: 0 46 | rightPadding: 0; leftPadding: 0 47 | 48 | Kirigami.Theme.colorSet: Kirigami.Theme.Window 49 | Kirigami.Theme.inherit: false 50 | 51 | property bool opened: false 52 | RowLayout { 53 | anchors.fill: parent 54 | spacing: 0 55 | 56 | Item { Layout.fillWidth: true } 57 | FooterToolBarButton { 58 | display: toolbar.opened ? AbstractButton.TextUnderIcon : AbstractButton.TextOnly 59 | text: i18n("Done") 60 | icon.name: "dialog-ok" 61 | onClicked: root.accept() 62 | } 63 | FooterToolBarButton { 64 | display: toolbar.opened ? AbstractButton.TextUnderIcon : AbstractButton.TextOnly 65 | text: i18n("Cancel") 66 | icon.name: "dialog-cancel" 67 | onClicked: applicationWindow().pageStack.currentIndex-- 68 | } 69 | FooterToolBarButton { 70 | display: toolbar.opened ? AbstractButton.TextUnderIcon : AbstractButton.TextOnly 71 | icon.name: "view-more-symbolic" 72 | onClicked: toolbar.opened = !toolbar.opened 73 | iconSize: Kirigami.Units.iconSizes.small 74 | implicitWidth: Kirigami.Units.gridUnit * 2.5 75 | } 76 | } 77 | } 78 | 79 | ColumnLayout { 80 | AlarmForm { 81 | id: form 82 | Layout.fillWidth: true 83 | Layout.maximumWidth: root.width - root.leftPadding - root.rightPadding 84 | selectedAlarm: root.selectedAlarm 85 | } 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /src/kclock/qml/alarm/AlarmListDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Nick Reitemeyer 3 | * Copyright 2020 Han Young 4 | * Copyright 2020-2021 Devin Lin 5 | * Copyright 2023 Nate Graham 6 | * 7 | * SPDX-License-Identifier: GPL-2.0-or-later 8 | */ 9 | 10 | import QtQuick 11 | import QtQuick.Controls 12 | import QtQuick.Layouts 13 | 14 | import org.kde.kirigami as Kirigami 15 | import org.kde.kirigamiaddons.delegates as Delegates 16 | 17 | import org.kde.kclock 18 | 19 | Delegates.RoundedItemDelegate { 20 | id: root 21 | 22 | property Alarm alarm 23 | 24 | // avoid null errors when deleting delegate 25 | readonly property bool enabled: alarm ? alarm.enabled : false 26 | readonly property string name: alarm ? alarm.name : "" 27 | readonly property string formattedTime: alarm ? alarm.formattedTime : "" 28 | readonly property int daysOfWeek: alarm ? alarm.daysOfWeek : 0 29 | readonly property int snoozedLength: alarm ? alarm.snoozedLength : 0 30 | 31 | property bool editMode: false 32 | 33 | topPadding: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit : Kirigami.Units.largeSpacing 34 | bottomPadding: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit : Kirigami.Units.largeSpacing 35 | leftPadding: Kirigami.Units.gridUnit 36 | rightPadding: Kirigami.Units.gridUnit 37 | 38 | signal editClicked() 39 | signal deleteClicked() 40 | 41 | onClicked: editClicked() 42 | 43 | // alarm ringing popup 44 | Loader { 45 | id: popupLoader 46 | active: false 47 | sourceComponent: AlarmRingingPopup { 48 | alarm: root.alarm 49 | onVisibleChanged: { 50 | if (!visible) { 51 | popupLoader.active = false; 52 | } 53 | } 54 | } 55 | 56 | Component.onCompleted: determineState() 57 | function determineState() { 58 | if (root.alarm.ringing) { 59 | popupLoader.active = true; 60 | popupLoader.item.open(); 61 | } else if (popupLoader.item) { 62 | popupLoader.item.close(); 63 | } 64 | } 65 | 66 | Connections { 67 | target: root.alarm 68 | ignoreUnknownSignals: true 69 | 70 | function onRingingChanged() { 71 | popupLoader.determineState(); 72 | } 73 | } 74 | } 75 | 76 | contentItem: RowLayout { 77 | spacing: Kirigami.Units.smallSpacing 78 | 79 | ColumnLayout { 80 | Layout.fillWidth: true 81 | spacing: Kirigami.Units.smallSpacing 82 | opacity: root.enabled ? 1.0 : 0.7 83 | 84 | Label { 85 | id: label 86 | Layout.fillWidth: true 87 | text: root.formattedTime 88 | font.bold: true 89 | } 90 | 91 | Label { 92 | id: subtitle 93 | Layout.fillWidth: true 94 | text: { 95 | let subtitleString = ""; 96 | if (root.name.length > 0) { 97 | subtitleString = subtitleString + root.name + " - "; 98 | } 99 | 100 | subtitleString += UtilModel.repeatFormat(root.daysOfWeek); 101 | 102 | if (root.snoozedLength > 0) { 103 | subtitleString += "\n" 104 | subtitleString += i18np("Snoozed %1 minute", "Snoozed %1 minutes", root.snoozedLength); 105 | } 106 | return subtitleString; 107 | } 108 | textFormat: Text.PlainText 109 | } 110 | } 111 | 112 | Switch { 113 | id: toggleSwitch 114 | 115 | // can't use Connections since it conflicts with enabled property 116 | property bool alarmEnabled: root.enabled 117 | onAlarmEnabledChanged: checked = root.enabled 118 | 119 | checked: root.enabled 120 | onCheckedChanged: root.alarm.enabled = checked; 121 | } 122 | 123 | ToolButton { 124 | icon.name: "delete" 125 | text: i18nc("@action:button", "Delete") 126 | onClicked: root.deleteClicked() 127 | visible: root.editMode 128 | display: AbstractButton.IconOnly 129 | 130 | ToolTip.delay: Kirigami.Units.toolTipDelay 131 | ToolTip.visible: Kirigami.Settings.tabletMode ? pressed : hovered 132 | ToolTip.text: text 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/kclock/qml/alarm/AlarmListPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * Copyright 2019 Nick Reitemeyer 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | import QtQuick 10 | 11 | import org.kde.kirigami as Kirigami 12 | 13 | import org.kde.kclock 14 | 15 | Kirigami.ScrollablePage { 16 | id: root 17 | 18 | property real yTranslate: 0 19 | property bool editMode: false 20 | 21 | objectName: "Alarms" 22 | title: i18n("Alarms") 23 | icon.name: "notifications" 24 | background: null 25 | 26 | actions: [ 27 | Kirigami.Action { 28 | icon.name: "list-add" 29 | text: i18n("New Alarm") 30 | onTriggered: root.addAlarm() 31 | visible: !Kirigami.Settings.isMobile 32 | }, 33 | Kirigami.Action { 34 | icon.name: "edit-entry" 35 | text: i18n("Edit") 36 | onTriggered: root.editMode = !root.editMode 37 | checkable: true 38 | visible: alarmsList.count > 0 39 | }, 40 | Kirigami.Action { 41 | displayHint: Kirigami.DisplayHint.IconOnly 42 | visible: !applicationWindow().isWidescreen 43 | icon.name: "settings-configure" 44 | text: i18n("Settings") 45 | onTriggered: applicationWindow().pageStack.push(applicationWindow().getPage("Settings")) 46 | } 47 | ] 48 | 49 | function addAlarm() : void { 50 | applicationWindow().pageStack.push(Qt.resolvedUrl("AlarmFormPage.qml")); 51 | } 52 | 53 | header: Kirigami.InlineMessage { 54 | type: Kirigami.MessageType.Error 55 | text: i18n("The clock daemon was not found. Please start kclockd in order to have alarm functionality.") 56 | visible: !AlarmModel.connectedToDaemon // by default, it's false so we need this 57 | position: Kirigami.InlineMessage.Position.Header 58 | } 59 | 60 | ListView { 61 | id: alarmsList 62 | model: AlarmModel 63 | currentIndex: -1 // no default selection 64 | 65 | transform: Translate { y: root.yTranslate } 66 | 67 | topMargin: Kirigami.Units.smallSpacing 68 | reuseItems: true 69 | 70 | // no alarms placeholder 71 | Kirigami.PlaceholderMessage { 72 | anchors.centerIn: parent 73 | anchors.left: parent.left 74 | anchors.right: parent.right 75 | anchors.margins: Kirigami.Units.largeSpacing 76 | visible: alarmsList.count === 0 && !AlarmModel.busy 77 | text: i18n("No alarms configured") 78 | icon.name: "notifications" 79 | 80 | helpfulAction: Kirigami.Action { 81 | icon.name: "list-add" 82 | text: i18n("Add alarm") 83 | onTriggered: root.addAlarm() 84 | } 85 | } 86 | 87 | Kirigami.PlaceholderMessage { 88 | anchors.centerIn: parent 89 | anchors.left: parent.left 90 | anchors.right: parent.right 91 | anchors.margins: Kirigami.Units.largeSpacing 92 | visible: alarmsList.count === 0 && AlarmModel.busy 93 | text: i18n("Loading…") 94 | } 95 | 96 | add: Transition { 97 | NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: Kirigami.Units.shortDuration } 98 | } 99 | remove: Transition { 100 | NumberAnimation { property: "opacity"; from: 1; to: 0; duration: Kirigami.Units.shortDuration } 101 | } 102 | displaced: Transition { 103 | NumberAnimation { properties: "x,y"; duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad} 104 | } 105 | 106 | // mobile action 107 | FloatingActionButton { 108 | text: i18nc("@action:button", "New Alarm") 109 | icon.name: "list-add" 110 | onClicked: root.addAlarm() 111 | visible: Kirigami.Settings.isMobile 112 | } 113 | 114 | // each alarm 115 | delegate: AlarmListDelegate { 116 | alarm: modelData 117 | editMode: root.editMode 118 | width: alarmsList.width 119 | 120 | onEditClicked: { 121 | applicationWindow().pageStack.push(Qt.resolvedUrl("AlarmFormPage.qml"), { selectedAlarm: alarm }) 122 | alarmsList.currentIndex = -1 123 | } 124 | onDeleteClicked: { 125 | showPassiveNotification(i18n("Deleted %1", alarm.name == "" ? i18n("alarm") : alarm.name)); 126 | AlarmModel.remove(index); 127 | } 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/kclock/qml/alarm/AlarmRingingPopup.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Devin Lin 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | 11 | import org.kde.kirigami as Kirigami 12 | 13 | import org.kde.kclock 14 | 15 | Kirigami.Dialog { 16 | id: root 17 | 18 | property Alarm alarm 19 | 20 | readonly property string formattedTime: alarm ? alarm.formattedTime : "" 21 | readonly property string name: alarm ? alarm.name : "" 22 | 23 | padding: Kirigami.Units.gridUnit * 3 24 | preferredWidth: Kirigami.Units.gridUnit * 20 25 | 26 | title: i18n("Alarm is ringing") 27 | showCloseButton: false 28 | closePolicy: Popup.NoAutoClose 29 | 30 | ColumnLayout { 31 | width: implicitWidth 32 | Label { 33 | Layout.alignment: Qt.AlignHCenter 34 | font.weight: Font.Light 35 | font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 2) 36 | text: root.formattedTime 37 | } 38 | 39 | Label { 40 | Layout.alignment: Qt.AlignHCenter 41 | font.weight: Font.Bold 42 | font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.25) 43 | text: root.name ? root.name : i18n("Alarm") 44 | wrapMode: Text.Wrap 45 | } 46 | } 47 | 48 | standardButtons: Kirigami.Dialog.NoButton 49 | flatFooterButtons: true 50 | 51 | customFooterActions: [ 52 | Kirigami.Action { 53 | text: i18n("Snooze") 54 | onTriggered: root.alarm.snooze() 55 | }, 56 | Kirigami.Action { 57 | text: i18n("Dismiss") 58 | onTriggered: { 59 | root.alarm.dismiss(); 60 | } 61 | } 62 | ] 63 | } 64 | -------------------------------------------------------------------------------- /src/kclock/qml/components/AboutPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Devin Lin 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import org.kde.kirigamiaddons.formcard as FormCard 8 | import org.kde.coreaddons as KCA 9 | 10 | FormCard.AboutPage { 11 | id: aboutPage 12 | aboutData: KCA.AboutData 13 | } 14 | -------------------------------------------------------------------------------- /src/kclock/qml/components/BottomToolbar.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import org.kde.kirigami as Kirigami 11 | import org.kde.kclock as KClock 12 | 13 | Kirigami.NavigationTabBar { 14 | id: root 15 | position: ToolBar.Footer 16 | 17 | // set binding only after component has loaded, so we don't have an animation for the navigation bar coming in 18 | Component.onCompleted: shouldShow = Qt.binding(() => pageStack.layers.depth <= 1 && pageStack.depth <= 1); 19 | 20 | property bool shouldShow: true 21 | onShouldShowChanged: { 22 | if (shouldShow) { 23 | hideAnim.stop(); 24 | showAnim.restart(); 25 | } else { 26 | showAnim.stop(); 27 | hideAnim.restart(); 28 | } 29 | } 30 | 31 | visible: height !== 0 32 | 33 | // animate showing and hiding of navbar 34 | ParallelAnimation { 35 | id: showAnim 36 | NumberAnimation { 37 | target: root 38 | property: "height" 39 | to: root.implicitHeight 40 | duration: Kirigami.Units.longDuration 41 | easing.type: Easing.InOutQuad 42 | } 43 | NumberAnimation { 44 | target: root 45 | property: "opacity" 46 | to: 1 47 | duration: Kirigami.Units.longDuration 48 | easing.type: Easing.InOutQuad 49 | } 50 | } 51 | 52 | SequentialAnimation { 53 | id: hideAnim 54 | NumberAnimation { 55 | target: root 56 | property: "opacity" 57 | to: 0 58 | duration: Kirigami.Units.longDuration 59 | easing.type: Easing.InOutQuad 60 | } 61 | NumberAnimation { 62 | target: root 63 | property: "height" 64 | to: 0 65 | duration: Kirigami.Units.longDuration 66 | easing.type: Easing.InOutQuad 67 | } 68 | } 69 | 70 | property var pageStack: applicationWindow().pageStack 71 | 72 | actions: [ 73 | Kirigami.Action { 74 | icon.name: "clock" 75 | text: i18n("Time") 76 | checked: root.pageStack.currentItem?.objectName === "Time" 77 | onTriggered: { 78 | if (root.pageStack.currentItem?.objectName !== "Time") { 79 | const page = applicationWindow().getPage("Time"); 80 | applicationWindow().switchToPage(page, 0); 81 | } 82 | } 83 | }, 84 | Kirigami.Action { 85 | icon.name: "player-time" 86 | text: i18n("Timers") 87 | checked: root.pageStack.currentItem?.objectName === "Timers" 88 | onTriggered: { 89 | if (root.pageStack.currentItem?.objectName !== "Timers") { 90 | const page = applicationWindow().getPage("Timers"); 91 | applicationWindow().switchToPage(page, 0); 92 | } 93 | } 94 | }, 95 | Kirigami.Action { 96 | readonly property bool showTime: !checked && !KClock.StopwatchTimer.stopped && !KClock.StopwatchTimer.paused 97 | 98 | icon.name: "chronometer" 99 | text: showTime ? KClock.StopwatchTimer.display : i18n("Stopwatch") 100 | tooltip: showTime ? i18n("Stopwatch") : "" 101 | checked: root.pageStack.currentItem?.objectName === "Stopwatch" 102 | onTriggered: { 103 | if (root.pageStack.currentItem?.objectName !== "Stopwatch") { 104 | const page = applicationWindow().getPage("Stopwatch"); 105 | applicationWindow().switchToPage(page, 0); 106 | } 107 | } 108 | }, 109 | Kirigami.Action { 110 | icon.name: "notifications" 111 | text: i18n("Alarms") 112 | checked: root.pageStack.currentItem?.objectName === "Alarms" 113 | onTriggered: { 114 | if (root.pageStack.currentItem?.objectName !== "Alarms") { 115 | const page = applicationWindow().getPage("Alarms"); 116 | applicationWindow().switchToPage(page, 0); 117 | } 118 | } 119 | } 120 | ] 121 | } 122 | -------------------------------------------------------------------------------- /src/kclock/qml/components/DialogComboBox.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Devin Lin 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | 11 | import org.kde.kirigami as Kirigami 12 | 13 | Button { 14 | id: control 15 | 16 | property alias title: dialog.title 17 | property alias dialogDelegate: repeater.delegate 18 | property alias model: repeater.model 19 | 20 | onClicked: dialog.open() 21 | 22 | Kirigami.Dialog { 23 | id: dialog 24 | 25 | showCloseButton: false 26 | 27 | ColumnLayout { 28 | spacing: 0 29 | 30 | Kirigami.Theme.inherit: false 31 | Kirigami.Theme.colorSet: Kirigami.Theme.View 32 | 33 | Repeater { 34 | id: repeater 35 | } 36 | } 37 | } 38 | 39 | Kirigami.Icon { 40 | source: "go-down-symbolic" 41 | 42 | implicitWidth: Kirigami.Units.iconSizes.small 43 | implicitHeight: Kirigami.Units.iconSizes.small 44 | 45 | anchors { 46 | right: parent.right 47 | verticalCenter: parent.verticalCenter 48 | rightMargin: Kirigami.Units.smallSpacing 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/kclock/qml/components/FloatingActionButton.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021-2022 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls as Controls 6 | import QtQuick.Templates 2.15 as T 7 | 8 | import org.kde.kirigami as Kirigami 9 | 10 | /** 11 | * @brief Floating button that can be used on pages to trigger actions. 12 | * 13 | * It is automatically anchored to be horizontally centered in the parent component, and towards the bottom. 14 | * 15 | * These anchors can be overridden. 16 | * 17 | * Example usage: 18 | * @code{.qml} 19 | * import QtQuick 20 | * import org.kde.kirigami as Kirigami 21 | * 22 | * Kirigami.ScrollablePage { 23 | * title: i18n("Page with ListView") 24 | * 25 | * ListView { 26 | * model: 50 27 | * delegate: Kirigami.BasicListItem { 28 | * text: modelData 29 | * } 30 | * 31 | * Kirigami.FloatingActionButton { 32 | * icon.name: "list-add" 33 | * onClicked: { 34 | * // behaviour when clicked 35 | * } 36 | * } 37 | * } 38 | * } 39 | * @endcode 40 | */ 41 | T.RoundButton { 42 | id: root 43 | width: implicitWidth 44 | height: implicitHeight 45 | implicitWidth: Kirigami.Units.gridUnit * 3 46 | implicitHeight: Kirigami.Units.gridUnit * 3 47 | 48 | anchors.horizontalCenter: parent.horizontalCenter 49 | anchors.bottom: parent.bottom 50 | anchors.bottomMargin: Kirigami.Units.largeSpacing 51 | 52 | background: Kirigami.ShadowedRectangle { 53 | anchors.fill: parent 54 | radius: root.radius 55 | color: root.pressed ? Qt.darker(Kirigami.Theme.highlightColor, 1.2) : Kirigami.Theme.highlightColor 56 | renderType: Kirigami.ShadowedRectangle.HighQuality 57 | 58 | property color shadowColor: Qt.darker(Kirigami.Theme.backgroundColor, 1.5) 59 | shadow.color: Qt.rgba(shadowColor.r, shadowColor.g, shadowColor.b, 0.5) 60 | shadow.size: 7 61 | shadow.yOffset: 2 62 | } 63 | 64 | contentItem: Item { 65 | Kirigami.Icon { 66 | anchors.centerIn: parent 67 | source: root.icon.name 68 | Kirigami.Theme.colorSet: Kirigami.Theme.Complementary 69 | Kirigami.Theme.inherit: false 70 | isMask: true 71 | implicitWidth: Kirigami.Units.iconSizes.smallMedium 72 | implicitHeight: Kirigami.Units.iconSizes.smallMedium 73 | } 74 | } 75 | 76 | Controls.ToolTip.delay: Kirigami.Units.toolTipDelay 77 | Controls.ToolTip.visible: Kirigami.Settings.tabletMode ? pressed : hovered 78 | Controls.ToolTip.text: text 79 | } 80 | -------------------------------------------------------------------------------- /src/kclock/qml/components/FooterToolBarButton.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021 Devin Lin 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Controls as Controls 9 | import org.kde.kirigami as Kirigami 10 | 11 | Controls.AbstractButton { 12 | id: button 13 | 14 | property real iconSize: 24 15 | 16 | readonly property color activeColor: Qt.rgba(Kirigami.Theme.disabledTextColor.r, Kirigami.Theme.disabledTextColor.g, Kirigami.Theme.disabledTextColor.b, 0.3) 17 | readonly property color hoverColor: Qt.rgba(Kirigami.Theme.disabledTextColor.r, Kirigami.Theme.disabledTextColor.g, Kirigami.Theme.disabledTextColor.b, 0.2) 18 | 19 | background: Rectangle { 20 | Kirigami.Theme.colorSet: Kirigami.Theme.Button 21 | Kirigami.Theme.inherit: false 22 | color: (button.checked || button.pressed) ? button.activeColor : (hoverHandler.hovered ? button.hoverColor : "transparent") 23 | } 24 | 25 | contentItem: Item { 26 | implicitHeight: Kirigami.Units.gridUnit * (button.display !== Controls.AbstractButton.TextOnly ? 3 : 2.75) 27 | implicitWidth: Kirigami.Units.gridUnit * 3.5 28 | 29 | Behavior on implicitHeight { 30 | NumberAnimation { 31 | duration: Kirigami.Units.longDuration 32 | easing.type: Easing.InOutQuad 33 | } 34 | } 35 | 36 | Kirigami.Icon { 37 | isMask: true // no coloured icons (should all be symbolic actions) 38 | anchors.centerIn: parent 39 | transform: Translate { 40 | y: label.opacity !== 0 ? -Kirigami.Units.smallSpacing * 2 : 0 41 | Behavior on y { 42 | NumberAnimation { 43 | duration: Kirigami.Units.longDuration 44 | easing.type: Easing.InOutQuad 45 | } 46 | } 47 | } 48 | implicitHeight: button.iconSize 49 | implicitWidth: height 50 | visible: button.icon.name 51 | source: button.icon.name 52 | } 53 | 54 | Controls.Label { 55 | id: label 56 | anchors.bottom: parent.bottom 57 | anchors.bottomMargin: Kirigami.Units.smallSpacing 58 | anchors.horizontalCenter: parent.horizontalCenter 59 | text: button.text 60 | font: Kirigami.Theme.smallFont 61 | horizontalAlignment: Text.AlignHCenter 62 | opacity: button.display !== Controls.AbstractButton.TextOnly ? 1 : 0 63 | 64 | Behavior on opacity { 65 | NumberAnimation { 66 | duration: Kirigami.Units.longDuration 67 | easing.type: Easing.InOutQuad 68 | } 69 | } 70 | } 71 | 72 | HoverHandler { 73 | id: hoverHandler 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/kclock/qml/components/Sidebar.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Layouts 10 | import QtQuick.Controls as QQC2 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kclock as KClock 13 | 14 | Kirigami.OverlayDrawer { 15 | id: drawer 16 | modal: false 17 | width: 100 18 | height: applicationWindow().height 19 | 20 | edge: Qt.application.layoutDirection === Qt.RightToLeft ? Qt.RightEdge : Qt.LeftEdge 21 | parent: QQC2.Overlay.overlay 22 | x: 0 23 | 24 | Kirigami.Theme.colorSet: Kirigami.Theme.Window 25 | Kirigami.Theme.inherit: false 26 | 27 | leftPadding: 0 28 | rightPadding: 0 29 | topPadding: 0 30 | bottomPadding: 0 31 | 32 | contentItem: ColumnLayout { 33 | spacing: 0 34 | 35 | Kirigami.AbstractApplicationHeader { 36 | Layout.fillWidth: true 37 | } 38 | 39 | QQC2.ScrollView { 40 | id: scrollView 41 | Layout.fillWidth: true 42 | Layout.fillHeight: true 43 | 44 | QQC2.ScrollBar.vertical.policy: QQC2.ScrollBar.AlwaysOff 45 | QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff 46 | contentWidth: -1 // disable horizontal scroll 47 | 48 | ColumnLayout { 49 | id: column 50 | width: scrollView.width 51 | spacing: 0 52 | 53 | Kirigami.NavigationTabButton { 54 | Layout.fillWidth: true 55 | text: i18n("Time") 56 | icon.name: "clock" 57 | checked: pageStack.currentItem?.objectName === "Time" 58 | onClicked: { 59 | if (pageStack.currentItem?.objectName !== "Time") { 60 | const page = applicationWindow().getPage("Time"); 61 | applicationWindow().switchToPage(page, 0); 62 | } 63 | } 64 | } 65 | 66 | Kirigami.NavigationTabButton { 67 | Layout.fillWidth: true 68 | text: i18n("Timers") 69 | icon.name: "player-time" 70 | checked: pageStack.currentItem?.objectName === "Timers" 71 | onClicked: { 72 | if (pageStack.currentItem?.objectName !== "Timers") { 73 | const page = applicationWindow().getPage("Timers"); 74 | applicationWindow().switchToPage(page, 0); 75 | } 76 | } 77 | } 78 | 79 | Kirigami.NavigationTabButton { 80 | readonly property bool showTime: !checked && !KClock.StopwatchTimer.stopped && !KClock.StopwatchTimer.paused 81 | 82 | Layout.fillWidth: true 83 | text: showTime ? KClock.StopwatchTimer.display : Accessible.name 84 | Accessible.name: i18n("Stopwatch") 85 | icon.name: "chronometer" 86 | checked: pageStack.currentItem?.objectName === "Stopwatch" 87 | onClicked: { 88 | if (pageStack.currentItem?.objectName !== "Stopwatch") { 89 | const page = applicationWindow().getPage("Stopwatch"); 90 | applicationWindow().switchToPage(page, 0); 91 | } 92 | } 93 | 94 | QQC2.ToolTip.text: showTime ? Accessible.name : "" 95 | QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay 96 | QQC2.ToolTip.visible: (Kirigami.Settings.tabletMode ? pressed : hovered) && QQC2.ToolTip.text !== "" 97 | } 98 | 99 | Kirigami.NavigationTabButton { 100 | Layout.fillWidth: true 101 | text: i18n("Alarms") 102 | icon.name: "notifications" 103 | checked: pageStack.currentItem?.objectName === "Alarms" 104 | onClicked: { 105 | if (pageStack.currentItem?.objectName !== "Alarms") { 106 | const page = applicationWindow().getPage("Alarms"); 107 | applicationWindow().switchToPage(page, 0); 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | Kirigami.Separator { 115 | Layout.fillWidth: true 116 | Layout.rightMargin: Kirigami.Units.smallSpacing 117 | Layout.leftMargin: Kirigami.Units.smallSpacing 118 | } 119 | 120 | Kirigami.NavigationTabButton { 121 | Layout.fillWidth: true 122 | action: applicationWindow().settingsAction 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/kclock/qml/components/TimePicker.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | import QtQuick.Controls 6 | import QtQuick.Layouts 7 | 8 | import org.kde.kirigami as Kirigami 9 | 10 | import org.kde.kclock 11 | 12 | RowLayout { 13 | id: root 14 | 15 | property int hours: 0 16 | property int minutes: 0 17 | readonly property bool twelveHourTime: !UtilModel.use24HourTime // am/pm 18 | 19 | onHoursChanged: updateHours() 20 | onMinutesChanged: minutesSpinbox.value = minutes 21 | onTwelveHourTimeChanged: updateHours() 22 | 23 | Component.onCompleted: { 24 | // needs to manually be triggered because onHoursChanged doesn't emit when set to 0 25 | updateHours(); 26 | } 27 | 28 | function updateHours() : void { 29 | // manually do this instead of a binding so we can set the value without worrying about binding eval order 30 | hoursSpinbox.from = root.twelveHourTime ? 1 : 0; 31 | hoursSpinbox.to = root.twelveHourTime ? 12 : 23; 32 | 33 | if (twelveHourTime) { 34 | hoursSpinbox.value = ((root.hours % 12) === 0) ? 12 : root.hours % 12; 35 | } else { 36 | hoursSpinbox.value = root.hours; 37 | } 38 | } 39 | 40 | RowLayout { 41 | spacing: Kirigami.Units.largeSpacing 42 | Layout.alignment: Qt.AlignHCenter 43 | 44 | // note: for 12-hour time, we have hours from 1-12 (0'o clock displays as 12) 45 | // for 24-hour time, we have hours from 0-23 46 | TimePickerSpinBox { 47 | id: hoursSpinbox 48 | 49 | onValueModified: { 50 | if (root.twelveHourTime) { 51 | if (root.hours >= 12) { 52 | root.hours = value % 12 + 12; 53 | } else { 54 | root.hours = value % 12; 55 | } 56 | } else { 57 | root.hours = value; 58 | } 59 | } 60 | } 61 | 62 | Kirigami.Heading { 63 | level: 1 64 | text: ":" 65 | } 66 | 67 | TimePickerSpinBox { 68 | id: minutesSpinbox 69 | from: 0 70 | to: 59 71 | 72 | onValueModified: { 73 | root.minutes = value; 74 | } 75 | } 76 | 77 | Button { 78 | id: amPmToggle 79 | visible: root.twelveHourTime 80 | leftPadding: Kirigami.Units.largeSpacing 81 | rightPadding: Kirigami.Units.largeSpacing 82 | topPadding: Kirigami.Units.largeSpacing 83 | bottomPadding: Kirigami.Units.largeSpacing 84 | Layout.alignment: Qt.AlignVCenter 85 | 86 | contentItem: Item { 87 | implicitWidth: label.implicitWidth 88 | implicitHeight: label.implicitHeight 89 | Label { 90 | id: label 91 | anchors.centerIn: parent 92 | font.weight: Font.Light 93 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.3 94 | text: i18n(root.hours < 12 ? i18n("AM") : i18n("PM")) 95 | } 96 | } 97 | 98 | background: Rectangle { 99 | radius: Kirigami.Units.smallSpacing 100 | border.color: { 101 | if (amPmToggle.enabled && (amPmToggle.visualFocus || amPmToggle.hovered || amPmToggle.down)) { 102 | return Kirigami.Theme.focusColor 103 | } else { 104 | return Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.15) 105 | } 106 | } 107 | border.width: 1 108 | color: amPmToggle.down ? Kirigami.Theme.alternateBackgroundColor : Kirigami.Theme.backgroundColor 109 | } 110 | 111 | onClicked: { 112 | if (root.hours >= 12) { 113 | root.hours -= 12; 114 | } else { 115 | root.hours += 12; 116 | } 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/kclock/qml/components/TimePickerSpinBoxButton.qml: -------------------------------------------------------------------------------- 1 | // Copyright 2023 Carl Schwan 2 | // SPDX-License-Identifier: LGPL-2.0-or-later 3 | 4 | import QtQuick 2.15 5 | import QtQuick.Controls 2.15 as QQC2 6 | import org.kde.kirigami 2.19 as Kirigami 7 | 8 | QQC2.Button { 9 | id: root 10 | autoRepeat: true 11 | 12 | required property bool isEnd 13 | required property bool isStart 14 | 15 | readonly property color borderColor: if (enabled && (visualFocus || hovered || down)) { 16 | return Kirigami.Theme.focusColor 17 | } else { 18 | return Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, 0.15) 19 | } 20 | 21 | topPadding: 0 22 | bottomPadding: 0 23 | 24 | contentItem: Item { 25 | implicitHeight: Kirigami.Units.gridUnit * 2 26 | Kirigami.Icon { 27 | source: root.icon.name 28 | anchors.centerIn: parent 29 | implicitHeight: Kirigami.Units.iconSizes.small 30 | implicitWidth: Kirigami.Units.iconSizes.small 31 | } 32 | } 33 | 34 | background: Kirigami.ShadowedRectangle { 35 | Kirigami.Theme.colorSet: Kirigami.Theme.Button 36 | color: root.down ? Kirigami.Theme.alternateBackgroundColor : Kirigami.Theme.backgroundColor 37 | 38 | corners { 39 | topLeftRadius: root.isStart ? 4 : 0 40 | topRightRadius: root.isStart ? 4 : 0 41 | bottomLeftRadius: root.isEnd ? 4 : 0 42 | bottomRightRadius: root.isEnd ? 4 : 0 43 | } 44 | 45 | border { 46 | width: 1 47 | color: root.borderColor 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/kclock/qml/settings/SettingsPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Layouts 10 | import org.kde.kirigami as Kirigami 11 | import org.kde.kirigamiaddons.formcard as FormCard 12 | 13 | import org.kde.kclock 14 | 15 | FormCard.FormCardPage { 16 | id: root 17 | 18 | property real yTranslate: 0 19 | property string formAudioPath: TimerModel.defaultAudioLocation 20 | 21 | Binding { 22 | target: TimerModel 23 | property: "defaultAudioLocation" 24 | value: formAudioPath 25 | } 26 | 27 | objectName: "Settings" 28 | title: i18n("Settings") 29 | icon.name: "settings-configure" 30 | background: null 31 | 32 | Kirigami.ColumnView.fillWidth: false 33 | 34 | contentItem.transform: Translate { y: root.yTranslate } 35 | 36 | FormCard.FormCard { 37 | Layout.topMargin: Kirigami.Units.gridUnit 38 | 39 | FormCard.FormComboBoxDelegate { 40 | id: timeFormatDelegate 41 | text: i18n("Time Format") 42 | model: [ 43 | {name: i18n("Use System Default"), value: "SystemDefault"}, 44 | {name: i18n("12 Hour Time"), value: "12Hour"}, 45 | {name: i18n("24 Hour Time"), value: "24Hour"}, 46 | ] 47 | currentIndex: model.findIndex(item => item.value === SettingsModel.timeFormat) 48 | 49 | textRole: "name" 50 | valueRole: "value" 51 | 52 | onActivated: SettingsModel.timeFormat = currentValue 53 | } 54 | 55 | FormCard.FormDelegateSeparator { above: timeFormatDelegate; below: timerNotificationDelegate } 56 | 57 | FormCard.FormComboBoxDelegate { 58 | id: timerNotificationDelegate 59 | text: i18n("Notifications for running timers") 60 | model: [{ 61 | name: i18n("Always"), 62 | value: Settings.Always 63 | }, { 64 | name: i18nc("When the app is not running", "When %1 is not running", Application.displayName), 65 | value: Settings.WhenKClockNotRunning 66 | }, { 67 | name: i18n("Never"), 68 | value: Settings.Never 69 | }] 70 | description: currentValue !== Settings.Never ? i18n("Lets you pause and resume timers from the notification area.") : "" 71 | currentIndex: model.findIndex(item => item.value === SettingsModel.timerNotification) 72 | 73 | textRole: "name" 74 | valueRole: "value" 75 | 76 | onActivated: SettingsModel.timerNotification = currentValue 77 | } 78 | 79 | FormCard.FormDelegateSeparator { above: timerNotificationDelegate; below: defaultTimerAudioDelegate } 80 | 81 | FormCard.FormButtonDelegate { 82 | id: defaultTimerAudioDelegate 83 | description: { 84 | let split = root.formAudioPath.split('/'); 85 | if (split.length < 1) { 86 | return i18n("Default"); 87 | } 88 | return split[split.length - 1].split('.')[0]; 89 | } 90 | 91 | onClicked: applicationWindow().pageStack.push(Qt.resolvedUrl("../components/SoundPickerPage.qml"), { alarmForm: root, titleText: i18n("Select Timer Sound") }); 92 | 93 | text: i18n("Default timer ring sound") 94 | } 95 | } 96 | 97 | FormCard.FormCard { 98 | Layout.topMargin: Kirigami.Units.gridUnit 99 | 100 | FormCard.FormButtonDelegate { 101 | id: aboutClock 102 | text: i18n("About Clock") 103 | icon.name: "help-about-symbolic" 104 | onClicked: applicationWindow().pageStack.push(applicationWindow().getPage("About")) 105 | } 106 | 107 | FormCard.FormDelegateSeparator { above: aboutClock; below: aboutKde } 108 | 109 | FormCard.FormButtonDelegate { 110 | id: aboutKde 111 | text: i18n("About KDE") 112 | icon.name: "kde-symbolic" 113 | onClicked: applicationWindow().pageStack.push(kdePage) 114 | 115 | Component { 116 | id: kdePage 117 | FormCard.AboutKDEPage {} 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/kclock/qml/time/AddLocationListView.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Devin Lin 3 | * Copyright 2023 Nate Graham 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | 12 | import org.kde.kirigami as Kirigami 13 | import org.kde.kirigamiaddons.delegates as Delegates 14 | 15 | import org.kde.kclock 16 | 17 | ListView { 18 | id: root 19 | 20 | signal closeRequested() 21 | 22 | currentIndex: -1 23 | reuseItems: true 24 | 25 | headerPositioning: ListView.OverlayHeader 26 | header: ToolBar { 27 | width: root.width 28 | z: 2 29 | 30 | contentItem: Kirigami.SearchField { 31 | id: searchField 32 | 33 | Component.onCompleted: forceActiveFocus(Qt.PopupFocusReason) 34 | 35 | onTextChanged: { 36 | forceActiveFocus(); 37 | focus = true; 38 | } 39 | 40 | Binding { 41 | target: AddLocationSearchModel 42 | property: "query" 43 | value: searchField.text 44 | } 45 | } 46 | } 47 | 48 | Kirigami.PlaceholderMessage { 49 | anchors.centerIn: parent 50 | visible: root.count == 0 51 | text: i18n("No locations found") 52 | icon.name: "globe" 53 | } 54 | 55 | model: AddLocationSearchModel 56 | 57 | delegate: Delegates.RoundedItemDelegate { 58 | id: delegate 59 | 60 | required property int index 61 | required property string city 62 | required property string country 63 | required property string currentTime 64 | required property bool added 65 | 66 | text: city 67 | enabled: !added 68 | // TODO Fix RoundedItemDelegate to not highlight disabled entries. 69 | highlighted: enabled && delegate.ListView.isCurrentItem 70 | hoverEnabled: enabled && !Kirigami.Settings.isMobile 71 | 72 | onClicked: { 73 | AddLocationSearchModel.addLocation(index); 74 | ListView.view.closeRequested(); 75 | } 76 | 77 | contentItem: RowLayout { 78 | spacing: Kirigami.Units.smallSpacing 79 | 80 | Delegates.SubtitleContentItem { 81 | itemDelegate: delegate 82 | subtitle: delegate.country 83 | Layout.fillWidth: true 84 | } 85 | 86 | Kirigami.Icon { 87 | Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium 88 | Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium 89 | source: delegate.added ? "dialog-ok-symbolic" : "" 90 | visible: valid 91 | } 92 | 93 | Label { 94 | text: delegate.currentTime 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/kclock/qml/time/AddLocationPage.qml: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | import QtQuick 5 | 6 | import org.kde.kirigami as Kirigami 7 | 8 | import org.kde.kclock 9 | 10 | Kirigami.ScrollablePage { 11 | id: root 12 | 13 | title: i18n("Add Location") 14 | 15 | AddLocationListView { 16 | onCloseRequested: applicationWindow().pageStack.currentIndex = 0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/kclock/qml/time/AddLocationWrapper.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Devin Lin 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | import org.kde.kirigami as Kirigami 11 | 12 | import org.kde.kclock 13 | 14 | Loader { 15 | id: loader 16 | active: false 17 | 18 | function open() : void { 19 | loader.active = false; 20 | AddLocationSearchModel.setFilterFixedString(""); 21 | if (Kirigami.Settings.isMobile) { 22 | applicationWindow().pageStack.push(Qt.resolvedUrl("AddLocationPage.qml")); 23 | } else { 24 | loader.active = true; 25 | loader.item.open(); 26 | } 27 | } 28 | 29 | sourceComponent: Kirigami.Dialog { 30 | id: dialog 31 | 32 | standardButtons: Kirigami.Dialog.NoButton 33 | parent: applicationWindow().overlay 34 | title: i18n("Add Location") 35 | preferredHeight: Kirigami.Units.gridUnit * 20 36 | preferredWidth: Kirigami.Units.gridUnit * 20 37 | padding: 0 38 | topPadding: 0 39 | bottomPadding: 0 40 | 41 | AddLocationListView { 42 | clip: true 43 | onCloseRequested: dialog.close() 44 | } 45 | 46 | footer: null 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/kclock/qml/time/AnalogClockHand.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012 Viranch Mehta 3 | * Copyright 2012 Marco Martin 4 | * Copyright 2013 David Edmundson 5 | * Copyright 2021 Devin Lin 6 | * 7 | * SPDX-License-Identifier: GPL-2.0-or-later 8 | */ 9 | 10 | import QtQuick 11 | 12 | import org.kde.ksvg as KSvg 13 | 14 | KSvg.SvgItem { 15 | id: handRoot 16 | 17 | property alias animateRotation: angleBehavior.enabled 18 | 19 | property alias rotation: rotation.angle 20 | property double svgScale 21 | property double horizontalRotationOffset: 0 22 | property double verticalRotationOffset: 0 23 | property string rotationCenterHintId 24 | readonly property double horizontalRotationCenter: { 25 | if (svg.hasElement(rotationCenterHintId)) { 26 | var hintedCenterRect = svg.elementRect(rotationCenterHintId), 27 | handRect = svg.elementRect(elementId), 28 | hintedX = hintedCenterRect.x - handRect.x + hintedCenterRect.width/2; 29 | return Math.round(hintedX * svgScale) + Math.round(hintedX * svgScale) % 2; 30 | } 31 | return width/2; 32 | } 33 | readonly property double verticalRotationCenter: { 34 | if (svg.hasElement(rotationCenterHintId)) { 35 | var hintedCenterRect = svg.elementRect(rotationCenterHintId), 36 | handRect = svg.elementRect(elementId), 37 | hintedY = hintedCenterRect.y - handRect.y + hintedCenterRect.height/2; 38 | return Math.round(hintedY * svgScale) + width % 2; 39 | } 40 | return width/2; 41 | } 42 | 43 | width: Math.round(naturalSize.width * svgScale) + Math.round(naturalSize.width * svgScale) % 2 44 | height: Math.round(naturalSize.height * svgScale) + width % 2 45 | anchors { 46 | top: clock.verticalCenter 47 | topMargin: -verticalRotationCenter + verticalRotationOffset 48 | left: clock.horizontalCenter 49 | leftMargin: -horizontalRotationCenter + horizontalRotationOffset 50 | } 51 | 52 | transform: Rotation { 53 | id: rotation 54 | angle: 0 55 | origin { 56 | x: handRoot.horizontalRotationCenter 57 | y: handRoot.verticalRotationCenter 58 | } 59 | Behavior on angle { 60 | id: angleBehavior 61 | RotationAnimation { 62 | duration: 400 63 | direction: RotationAnimation.Clockwise 64 | easing.type: Easing.OutElastic 65 | easing.overshoot: 0.5 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/kclock/qml/time/TimePageDelegate.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2021 Devin Lin 3 | * Copyright 2023 Nate Graham 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | 12 | import org.kde.kirigami as Kirigami 13 | 14 | Control { 15 | id: root 16 | 17 | property bool editMode 18 | required property string city 19 | required property string relativeTime 20 | required property int relativeDays 21 | required property string timeString 22 | 23 | signal deleteRequested() 24 | 25 | topPadding: Kirigami.Units.largeSpacing 26 | bottomPadding: Kirigami.Units.largeSpacing 27 | leftPadding: Kirigami.Units.gridUnit 28 | rightPadding: Kirigami.Units.gridUnit 29 | 30 | contentItem: RowLayout { 31 | spacing: Kirigami.Units.largeSpacing 32 | 33 | ColumnLayout { 34 | Layout.fillWidth: true 35 | spacing: Kirigami.Units.smallSpacing 36 | 37 | Label { 38 | Layout.fillWidth: true 39 | text: root.city 40 | font.bold: true 41 | } 42 | Label { 43 | Layout.fillWidth: true 44 | text: { 45 | let parts = []; 46 | if (root.relativeDays === -1) { 47 | parts.push(i18n("Yesterday")); 48 | } else if (root.relativeDays === +1) { 49 | parts.push(i18n("Tomorrow")); 50 | } 51 | parts.push(root.relativeTime); 52 | return parts.join(" · "); 53 | } 54 | font: Kirigami.Theme.smallFont 55 | } 56 | } 57 | 58 | Label { 59 | text: root.timeString 60 | font.weight: Font.Bold 61 | font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.2 62 | opacity: 0.7 63 | color: Kirigami.Theme.textColor 64 | } 65 | 66 | ToolButton { 67 | icon.name: "delete" 68 | text: i18nc("@action:button", "Delete") 69 | onClicked: root.deleteRequested() 70 | visible: root.editMode 71 | display: AbstractButton.IconOnly 72 | 73 | ToolTip.delay: Kirigami.Units.toolTipDelay 74 | ToolTip.timeout: 5000 75 | ToolTip.visible: Kirigami.Settings.tabletMode ? pressed : hovered 76 | ToolTip.text: text 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/kclock/qml/timer/PresetDurationButton.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Devin Lin 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Controls 9 | 10 | import org.kde.kirigami as Kirigami 11 | 12 | Button { 13 | id: root 14 | required property var hoursSpinBox 15 | required property var minutesSpinBox 16 | required property var secondsSpinBox 17 | 18 | property int minutes: 0 19 | property int hours: 0 20 | 21 | implicitWidth: Kirigami.Units.gridUnit * 4 22 | text: i18n("1 m") 23 | 24 | checked: shouldBeChecked() 25 | 26 | function shouldBeChecked() { 27 | return hoursSpinBox.value === hours && minutesSpinBox.value === minutes && secondsSpinBox.value === 0; 28 | } 29 | 30 | Connections { 31 | target: root.hoursSpinBox 32 | function onValueChanged() { 33 | root.checked = root.shouldBeChecked(); 34 | } 35 | } 36 | Connections { 37 | target: root.minutesSpinBox 38 | function onValueChanged() { 39 | root.checked = root.shouldBeChecked(); 40 | } 41 | } 42 | Connections { 43 | target: root.secondsSpinBox 44 | function onValueChanged() { 45 | root.checked = root.shouldBeChecked(); 46 | } 47 | } 48 | 49 | onClicked: { 50 | root.hoursSpinBox.value = hours; 51 | root.minutesSpinBox.value = minutes; 52 | root.secondsSpinBox.value = 0; 53 | focus = false; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/kclock/qml/timer/TimerForm.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Devin Lin 3 | * Copyright 2021 Boris Petrov 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | 12 | import org.kde.kirigami as Kirigami 13 | 14 | import org.kde.kclock 15 | 16 | Kirigami.FormLayout { 17 | id: form 18 | 19 | property alias name: label.text 20 | property alias looping: loopingCheckBox.checked 21 | property alias commandTimeout: commandTimeoutField.text 22 | 23 | function setDuration(duration) { 24 | spinBoxHours.value = duration / 60 / 60; 25 | spinBoxMinutes.value = duration % (60 * 60) / 60; 26 | spinBoxSeconds.value = duration % 60; 27 | } 28 | function getDuration() { 29 | return spinBoxHours.value * 60 * 60 + spinBoxMinutes.value * 60 + spinBoxSeconds.value; 30 | } 31 | 32 | GridLayout { 33 | anchors.horizontalCenter: parent.horizontalCenter 34 | rowSpacing: Kirigami.Units.smallSpacing 35 | columnSpacing: Kirigami.Units.smallSpacing 36 | columns: 3 37 | 38 | Repeater { 39 | model: [ 40 | {label: i18n("1 min"), h: 0, m: 1}, 41 | {label: i18n("5 min"), h: 0, m: 5}, 42 | {label: i18n("10 min"), h: 0, m: 10}, 43 | {label: i18n("15 min"), h: 0, m: 15}, 44 | {label: i18n("30 min"), h: 0, m: 30}, 45 | {label: i18n("1 h"), h: 1, m: 0}, 46 | ] 47 | 48 | PresetDurationButton { 49 | required property string label 50 | required property int h 51 | required property int m 52 | text: label 53 | hours: h 54 | minutes: m 55 | hoursSpinBox: spinBoxHours 56 | minutesSpinBox: spinBoxMinutes 57 | secondsSpinBox: spinBoxSeconds 58 | } 59 | } 60 | } 61 | Kirigami.Separator {} 62 | 63 | ColumnLayout { 64 | Kirigami.FormData.label: i18n("Duration:") 65 | Kirigami.FormData.buddyFor: spinBoxHours 66 | RowLayout { 67 | SpinBox { 68 | id: spinBoxHours 69 | Layout.preferredWidth: Kirigami.Units.gridUnit * 6 70 | onActiveFocusChanged: { 71 | if (activeFocus) { 72 | (contentItem as TextInput)?.selectAll(); 73 | } 74 | } 75 | } 76 | Label { 77 | text: i18n("hours") 78 | } 79 | } 80 | RowLayout { 81 | SpinBox { 82 | id: spinBoxMinutes 83 | to: 59 84 | Layout.preferredWidth: Kirigami.Units.gridUnit * 6 85 | onActiveFocusChanged: { 86 | if (activeFocus) { 87 | (contentItem as TextInput)?.selectAll(); 88 | } 89 | } 90 | } 91 | Label { 92 | text: i18n("minutes") 93 | } 94 | } 95 | RowLayout { 96 | SpinBox { 97 | id: spinBoxSeconds 98 | to: 59 99 | Layout.preferredWidth: Kirigami.Units.gridUnit * 6 100 | onActiveFocusChanged: { 101 | if (activeFocus) { 102 | (contentItem as TextInput)?.selectAll(); 103 | } 104 | } 105 | } 106 | Label { 107 | text: i18n("seconds") 108 | } 109 | } 110 | } 111 | 112 | CheckBox { 113 | id: loopingCheckBox 114 | text: i18n("Loop Timer") 115 | icon.name: "media-repeat-all" 116 | } 117 | 118 | TextField { 119 | id: label 120 | Kirigami.FormData.label: i18n("Label:") 121 | focus: true 122 | } 123 | TextField { 124 | id: commandTimeoutField 125 | Kirigami.FormData.label: i18n("Command at timeout:") 126 | font.family: "Monospace" 127 | focus: true 128 | placeholderText: i18n("optional") 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/kclock/qml/timer/TimerFormDialog.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Devin Lin 3 | * Copyright 2021 Boris Petrov 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | import QtQuick 9 | import QtQuick.Controls 10 | import QtQuick.Layouts 11 | 12 | import org.kde.kirigami as Kirigami 13 | 14 | import org.kde.kclock 15 | 16 | Kirigami.Dialog { 17 | id: root 18 | 19 | property Timer timer: null 20 | 21 | function saveTimer(duration, label, looping, commandTimeout) { 22 | if (timer) { 23 | timer.length = duration; 24 | timer.label = label; 25 | timer.commandTimeout = commandTimeout; 26 | timer.looping = looping; 27 | timer.reset(); 28 | } else { 29 | TimerModel.addNew(duration, label, looping, commandTimeout); 30 | } 31 | } 32 | 33 | property bool showPresets: false 34 | property bool showDelete: false 35 | 36 | title: timer ? i18nc("@title:window Edit timer", "Edit %1", timer.label) : i18nc("@title:window", "Create Timer") 37 | standardButtons: Dialog.NoButton 38 | 39 | topPadding: 0 40 | bottomPadding: Kirigami.Units.largeSpacing 41 | leftPadding: Kirigami.Units.gridUnit 42 | rightPadding: Kirigami.Units.gridUnit 43 | preferredWidth: Kirigami.Units.gridUnit * 20 44 | 45 | onAboutToShow: { 46 | if (root.timer) { 47 | timerForm.setDuration(root.timer.length); 48 | timerForm.name = timer.label; 49 | timerForm.looping = timer.looping; 50 | timerForm.commandTimeout = timer.commandTimeout; 51 | } else { 52 | timerForm.setDuration(5 * 60); // 5 minutes default. 53 | timerForm.name = i18n("Timer"); 54 | timerForm.looping = false; 55 | timerForm.commandTimeout = ""; 56 | } 57 | } 58 | 59 | ColumnLayout { 60 | TimerForm { 61 | id: timerForm 62 | Layout.fillWidth: true 63 | } 64 | RowLayout { 65 | Layout.alignment: Qt.AlignHCenter 66 | spacing: Kirigami.Units.smallSpacing 67 | visible: repeater.count > 0 68 | 69 | Button { 70 | id: presetButton 71 | text: root.showPresets ? i18n("Hide Presets") : i18n("Show Presets") 72 | onClicked: root.showPresets = !root.showPresets 73 | } 74 | ToolButton { 75 | icon.name: "delete" 76 | text: i18n("Toggle Delete") 77 | onClicked: root.showDelete = !root.showDelete 78 | visible: root.showPresets 79 | checkable: true 80 | checked: false 81 | } 82 | } 83 | Flow { 84 | spacing: Kirigami.Units.smallSpacing 85 | visible: root.showPresets 86 | Layout.fillWidth: true 87 | 88 | Repeater { 89 | id: repeater 90 | model: TimerPresetModel 91 | 92 | Button { 93 | text: root.showDelete ? "Delete" : preset.presetName 94 | onClicked: root.showDelete ? TimerPresetModel.deletePreset(index) : root.saveTimer(preset.presetDuration, preset.presetName, false, "") & close(); 95 | } 96 | } 97 | } 98 | } 99 | 100 | customFooterActions: [ 101 | Kirigami.Action { 102 | icon.name: "list-add" 103 | text: i18n("Save As Preset") 104 | onTriggered: { 105 | TimerPresetModel.insertPreset(timerForm.name, timerForm.getDuration()); 106 | root.showPresets = true; 107 | } 108 | }, 109 | Kirigami.Action { 110 | icon.name: "dialog-cancel" 111 | text: i18n("Cancel") 112 | onTriggered: root.close() 113 | }, 114 | Kirigami.Action { 115 | icon.name: root.timer ? "document-save" : "dialog-ok" 116 | text: root.timer ? i18nc("@action:button", "Save") : i18nc("@action:button", "Done") 117 | onTriggered: { 118 | root.saveTimer(timerForm.getDuration(), timerForm.name, timerForm.looping, timerForm.commandTimeout); 119 | root.close(); 120 | } 121 | } 122 | ] 123 | } 124 | -------------------------------------------------------------------------------- /src/kclock/qml/timer/TimerListPage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2021 Devin Lin 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | 9 | import org.kde.kirigami as Kirigami 10 | 11 | import org.kde.kclock 12 | 13 | Kirigami.ScrollablePage { 14 | id: root 15 | 16 | property real yTranslate: 0 17 | 18 | objectName: "Timers" 19 | title: i18n("Timers") 20 | readonly property string hiddenTitle: TimerModel.runningTimer?.remainingPretty ?? "" 21 | icon.name: "player-time" 22 | background: null 23 | 24 | // desktop action 25 | actions: [ 26 | Kirigami.Action { 27 | icon.name: "list-add" 28 | text: i18n("New Timer") 29 | onTriggered: root.addTimer() 30 | visible: !Kirigami.Settings.isMobile 31 | }, 32 | Kirigami.Action { 33 | displayHint: Kirigami.DisplayHint.IconOnly 34 | visible: !applicationWindow().isWidescreen 35 | icon.name: "settings-configure" 36 | text: i18n("Settings") 37 | onTriggered: applicationWindow().pageStack.push(applicationWindow().getPage("Settings")) 38 | } 39 | ] 40 | 41 | function addTimer() : void { 42 | timerFormDialog.timer = null; 43 | timerFormDialog.open(); 44 | } 45 | 46 | function editTimer(timer : Timer) : void { 47 | timerFormDialog.timer = timer; 48 | timerFormDialog.open(); 49 | } 50 | 51 | header: Kirigami.InlineMessage { 52 | type: Kirigami.MessageType.Error 53 | text: i18n("The clock daemon was not found. Please start kclockd in order to have timer functionality.") 54 | visible: !TimerModel.connectedToDaemon // by default, it's false so we need this 55 | position: Kirigami.InlineMessage.Position.Header 56 | } 57 | 58 | ListView { 59 | id: timersList 60 | spacing: Kirigami.Units.gridUnit 61 | topMargin: Kirigami.Units.gridUnit 62 | bottomMargin: Kirigami.Units.gridUnit 63 | leftMargin: Kirigami.Units.gridUnit 64 | rightMargin: Kirigami.Units.gridUnit 65 | 66 | model: TimerModel 67 | 68 | transform: Translate { y: root.yTranslate } 69 | 70 | // TODO: these animations seem to cause the cards to overlap when a new timer is added, possible Qt bug? 71 | // add: Transition { 72 | // NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: Kirigami.Units.shortDuration } 73 | // } 74 | // remove: Transition { 75 | // NumberAnimation { property: "opacity"; from: 0; to: 1.0; duration: Kirigami.Units.shortDuration } 76 | // } 77 | // displaced: Transition { 78 | // NumberAnimation { properties: "x,y"; duration: Kirigami.Units.longDuration; easing.type: Easing.InOutQuad} 79 | // } 80 | 81 | // mobile action 82 | FloatingActionButton { 83 | text: i18nc("@action:button", "New Timer") 84 | icon.name: "list-add" 85 | onClicked: root.addTimer() 86 | visible: Kirigami.Settings.isMobile 87 | } 88 | 89 | // no timer placeholder 90 | Kirigami.PlaceholderMessage { 91 | anchors.centerIn: parent 92 | visible: timersList.count === 0 93 | text: i18n("No timers configured") 94 | icon.name: "player-time" 95 | 96 | helpfulAction: Kirigami.Action { 97 | icon.name: "list-add" 98 | text: i18n("Add timer") 99 | onTriggered: root.addTimer() 100 | } 101 | } 102 | 103 | // create timer form 104 | TimerFormDialog { 105 | id: timerFormDialog 106 | } 107 | 108 | // timer card delegate 109 | delegate: TimerListDelegate { 110 | timer: model.timer 111 | 112 | onEditClicked: root.editTimer(timer) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/kclock/qml/timer/TimerRingingPopup.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Devin Lin 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | 11 | import org.kde.kirigami as Kirigami 12 | 13 | import org.kde.kclock 14 | 15 | Kirigami.Dialog { 16 | id: root 17 | 18 | property Timer timer 19 | 20 | readonly property string length: timer ? timer.lengthPretty : "" 21 | readonly property string label: timer ? timer.label : "" 22 | 23 | padding: Kirigami.Units.gridUnit * 3 24 | 25 | title: i18n("Timer has finished") 26 | showCloseButton: false 27 | closePolicy: Popup.NoAutoClose 28 | 29 | ColumnLayout { 30 | width: implicitWidth 31 | implicitWidth: Kirigami.Units.gridUnit * 20 32 | Label { 33 | Layout.alignment: Qt.AlignHCenter 34 | font.weight: Font.Light 35 | font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 2) 36 | wrapMode: Text.Wrap 37 | text: root.length 38 | } 39 | 40 | Label { 41 | Layout.alignment: Qt.AlignHCenter 42 | font.weight: Font.Bold 43 | font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.25) 44 | text: root.label ? i18n("%1 has completed.", root.label) : i18n("The timer has completed.") 45 | wrapMode: Text.Wrap 46 | } 47 | } 48 | 49 | standardButtons: Kirigami.Dialog.NoButton 50 | flatFooterButtons: true 51 | 52 | customFooterActions: [ 53 | Kirigami.Action { 54 | text: i18n("Dismiss") 55 | onTriggered: { 56 | root.timer.dismiss(); 57 | root.close(); 58 | } 59 | } 60 | ] 61 | } 62 | 63 | -------------------------------------------------------------------------------- /src/kclock/savedlocationsmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * Copyright 2021 Nicolas Fella 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | class QJSEngine; 18 | class QQmlEngine; 19 | 20 | class SavedLocationsModel : public QAbstractListModel 21 | { 22 | Q_OBJECT 23 | QML_ELEMENT 24 | QML_SINGLETON 25 | 26 | public: 27 | static SavedLocationsModel *instance(); 28 | static SavedLocationsModel *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); 29 | 30 | enum Roles { 31 | NameRole = Qt::DisplayRole, 32 | TimeStringRole = Qt::UserRole + 1, 33 | RelativeTimeRole, 34 | RelativeDaysRole, 35 | CityRole, 36 | IdRole, 37 | }; 38 | 39 | Q_INVOKABLE void removeLocation(int index); 40 | 41 | int rowCount(const QModelIndex &parent) const override; 42 | QVariant data(const QModelIndex &index, int role) const override; 43 | QHash roleNames() const override; 44 | 45 | public Q_SLOTS: 46 | void load(); 47 | 48 | private: 49 | explicit SavedLocationsModel(QObject *parent = nullptr); 50 | 51 | std::vector m_timeZones; 52 | QSettings m_settings; 53 | }; 54 | -------------------------------------------------------------------------------- /src/kclock/settingsmodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #include "settingsmodel.h" 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | SettingsModel::SettingsModel() 16 | : m_interface(new LocalKClockSettingsInterface(QStringLiteral("org.kde.kclockd"), QStringLiteral("/Settings"), QDBusConnection::sessionBus())) 17 | { 18 | if (m_interface->isValid()) { 19 | m_timeFormat = m_interface->timeFormat(); 20 | m_timerNotification = static_cast(m_interface->timerNotification()); 21 | } else { 22 | // Use system default format if kclockd isn't here 23 | m_timeFormat = QStringLiteral("SystemDefault"); 24 | } 25 | 26 | connect(m_interface, &LocalKClockSettingsInterface::timeFormatChanged, this, [this]() { 27 | QString timeFormat = m_interface->timeFormat(); 28 | 29 | if (timeFormat != m_timeFormat) { 30 | m_timeFormat = timeFormat; 31 | Q_EMIT timeFormatChanged(); 32 | } 33 | }); 34 | 35 | connect(m_interface, &LocalKClockSettingsInterface::timerNotificationChanged, this, [this] { 36 | const auto timerNotification = static_cast(m_interface->timerNotification()); 37 | if (m_timerNotification != timerNotification) { 38 | m_timerNotification = timerNotification; 39 | Q_EMIT timerNotificationChanged(timerNotification); 40 | } 41 | }); 42 | } 43 | 44 | SettingsModel *SettingsModel::instance() 45 | { 46 | static SettingsModel *singleton = new SettingsModel(); 47 | return singleton; 48 | } 49 | 50 | SettingsModel *SettingsModel::create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) 51 | { 52 | Q_UNUSED(qmlEngine); 53 | Q_UNUSED(jsEngine); 54 | auto *model = instance(); 55 | QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership); 56 | return model; 57 | } 58 | 59 | QString SettingsModel::timeFormat() const 60 | { 61 | return m_timeFormat; 62 | } 63 | 64 | void SettingsModel::setTimeFormat(QString timeFormat) 65 | { 66 | m_interface->setProperty("timeFormat", timeFormat); 67 | } 68 | 69 | KClockSettings::EnumTimerNotification SettingsModel::timerNotification() const 70 | { 71 | return m_timerNotification; 72 | } 73 | 74 | void SettingsModel::setTimerNotification(KClockSettings::EnumTimerNotification timerNotification) 75 | { 76 | m_interface->setProperty("timerNotification", static_cast(timerNotification)); 77 | } 78 | 79 | #include "moc_settingsmodel.cpp" 80 | -------------------------------------------------------------------------------- /src/kclock/settingsmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "kclocksettingsinterface.h" 11 | 12 | #include "kclockdsettings.h" 13 | 14 | #include 15 | 16 | #include 17 | 18 | class QJSEngine; 19 | class QQmlEngine; 20 | 21 | struct KClockSettingsForeign { 22 | Q_GADGET 23 | QML_NAMED_ELEMENT(Settings) 24 | QML_UNCREATABLE("Only used for enums") 25 | QML_FOREIGN(KClockSettings) 26 | }; 27 | 28 | class SettingsModel : public QObject 29 | { 30 | Q_OBJECT 31 | QML_ELEMENT 32 | QML_SINGLETON 33 | 34 | Q_PROPERTY(QString timeFormat READ timeFormat WRITE setTimeFormat NOTIFY timeFormatChanged) 35 | Q_PROPERTY(KClockSettings::EnumTimerNotification timerNotification READ timerNotification WRITE setTimerNotification NOTIFY timerNotificationChanged) 36 | 37 | public: 38 | static SettingsModel *instance(); 39 | static SettingsModel *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); 40 | 41 | QString timeFormat() const; 42 | void setTimeFormat(QString timeFormat); 43 | 44 | KClockSettings::EnumTimerNotification timerNotification() const; 45 | void setTimerNotification(KClockSettings::EnumTimerNotification timerNotification); 46 | Q_SIGNAL void timerNotificationChanged(KClockSettings::EnumTimerNotification TimerNotification); 47 | 48 | Q_SIGNALS: 49 | void timeFormatChanged(); 50 | 51 | private: 52 | SettingsModel(); 53 | LocalKClockSettingsInterface *m_interface; 54 | QSettings m_settings; 55 | 56 | QString m_timeFormat; 57 | KClockSettings::EnumTimerNotification m_timerNotification = KClockSettings::EnumTimerNotification::WhenKClockNotRunning; 58 | }; 59 | -------------------------------------------------------------------------------- /src/kclock/stopwatchmodel.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #include "stopwatchmodel.h" 5 | #include "stopwatchtimer.h" 6 | 7 | #include 8 | 9 | StopwatchModel *StopwatchModel::instance() 10 | { 11 | static StopwatchModel *instance = new StopwatchModel; 12 | return instance; 13 | } 14 | 15 | StopwatchModel *StopwatchModel::create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) 16 | { 17 | Q_UNUSED(qmlEngine); 18 | Q_UNUSED(jsEngine); 19 | auto *model = instance(); 20 | QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership); 21 | return model; 22 | } 23 | 24 | StopwatchModel::StopwatchModel(QObject *parent) 25 | : QAbstractListModel{parent} 26 | { 27 | // clear model when stopwatch is reset 28 | connect(StopwatchTimer::instance(), &StopwatchTimer::resetTriggered, this, &StopwatchModel::reset); 29 | } 30 | 31 | int StopwatchModel::rowCount(const QModelIndex &parent) const 32 | { 33 | if (parent.isValid()) { 34 | return 0; 35 | } 36 | return m_laps.size(); 37 | } 38 | 39 | QVariant StopwatchModel::data(const QModelIndex &index, int role) const 40 | { 41 | const int row = index.row(); 42 | 43 | if (row < 0 || row >= m_laps.size()) { 44 | return QVariant{}; 45 | } 46 | 47 | const auto lap = m_laps[row]; 48 | 49 | switch (role) { 50 | case LapNumber: 51 | return lap.lapNumber; 52 | case LapTime: 53 | return lap.lapTime; 54 | case LapTimeSinceBeginning: 55 | return lap.lapTimeSinceBeginning; 56 | case IsBest: 57 | return lap.lapNumber == m_bestLapIndex; 58 | case IsWorst: 59 | return lap.lapNumber == m_worstLapIndex; 60 | } 61 | 62 | return {}; 63 | } 64 | 65 | QHash StopwatchModel::roleNames() const 66 | { 67 | return {{LapNumber, "lapNumber"}, {LapTime, "lapTime"}, {LapTimeSinceBeginning, "lapTimeSinceBeginning"}, {IsBest, "isBest"}, {IsWorst, "isWorst"}}; 68 | } 69 | 70 | void StopwatchModel::addLap() 71 | { 72 | beginInsertRows(QModelIndex(), 0, 0); 73 | 74 | // time since stopwatch started 75 | const qint64 elapsedSinceBeginning = StopwatchTimer::instance()->elapsedTime(); 76 | 77 | StopwatchLap lap{}; 78 | lap.lapNumber = m_laps.size() + 1; // start at 1 79 | 80 | // time since last lap 81 | lap.lapTime = (m_laps.size() > 0) ? (elapsedSinceBeginning - m_laps[0].lapTimeSinceBeginning) : elapsedSinceBeginning; 82 | 83 | // time since stopwatch started 84 | lap.lapTimeSinceBeginning = elapsedSinceBeginning; 85 | 86 | // insert at front 87 | m_laps.insert(0, lap); 88 | 89 | // update most recent lap time 90 | m_mostRecentLapTime = elapsedSinceBeginning; 91 | Q_EMIT mostRecentLapTimeChanged(); 92 | 93 | QList toUpdate; 94 | 95 | // update worst lap time 96 | if (m_worstLapTime <= lap.lapTime || m_worstLapIndex == -1) { 97 | int oldIndex = m_worstLapIndex; 98 | m_worstLapIndex = lap.lapNumber; 99 | m_worstLapTime = lap.lapTime; 100 | 101 | if (oldIndex != -1) { // ensure old row is updated 102 | toUpdate.push_back(m_laps.size() - oldIndex); 103 | } 104 | } 105 | 106 | // update best lap time 107 | if (m_bestLapTime >= lap.lapTime || m_bestLapIndex == -1) { 108 | int oldIndex = m_bestLapIndex; 109 | m_bestLapIndex = lap.lapNumber; 110 | m_bestLapTime = lap.lapTime; 111 | 112 | if (oldIndex != -1) { // ensure old row is updated 113 | toUpdate.push_back(m_laps.size() - oldIndex); 114 | } 115 | } 116 | 117 | endInsertRows(); 118 | 119 | // only emit signal after endInsertRows() 120 | for (int update : toUpdate) { 121 | QModelIndex index = createIndex(update, 0); 122 | Q_EMIT dataChanged(index, index, {IsBest, IsWorst}); 123 | } 124 | } 125 | 126 | qint64 StopwatchModel::mostRecentLapTime() 127 | { 128 | return m_mostRecentLapTime; 129 | } 130 | 131 | void StopwatchModel::reset() 132 | { 133 | // clear model 134 | beginResetModel(); 135 | m_laps.clear(); 136 | endResetModel(); 137 | 138 | // reset fields 139 | m_mostRecentLapTime = 0; 140 | Q_EMIT mostRecentLapTimeChanged(); 141 | m_worstLapIndex = -1; 142 | m_bestLapIndex = -1; 143 | } 144 | 145 | #include "moc_stopwatchmodel.cpp" 146 | -------------------------------------------------------------------------------- /src/kclock/stopwatchmodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Devin Lin 2 | // SPDX-License-Identifier: GPL-2.0-or-later 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #include 9 | 10 | class QJSEngine; 11 | class QQmlEngine; 12 | 13 | // model for stopwatch laps 14 | class StopwatchModel : public QAbstractListModel 15 | { 16 | Q_OBJECT 17 | QML_ELEMENT 18 | QML_SINGLETON 19 | Q_PROPERTY(qint64 mostRecentLapTime READ mostRecentLapTime NOTIFY mostRecentLapTimeChanged) 20 | 21 | public: 22 | static StopwatchModel *instance(); 23 | static StopwatchModel *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); 24 | 25 | struct StopwatchLap { 26 | int lapNumber; 27 | qreal lapTime; 28 | qreal lapTimeSinceBeginning; 29 | }; 30 | 31 | enum Roles { 32 | LapNumber = Qt::DisplayRole, 33 | LapTime, 34 | LapTimeSinceBeginning, 35 | IsBest, 36 | IsWorst, 37 | }; 38 | 39 | int rowCount(const QModelIndex &parent) const override; 40 | QVariant data(const QModelIndex &index, int role) const override; 41 | QHash roleNames() const override; 42 | 43 | Q_INVOKABLE void addLap(); 44 | 45 | qint64 mostRecentLapTime(); 46 | 47 | Q_SIGNALS: 48 | void mostRecentLapTimeChanged(); 49 | 50 | private Q_SLOTS: 51 | void reset(); 52 | 53 | private: 54 | explicit StopwatchModel(QObject *parent = nullptr); 55 | 56 | QList m_laps; // sorted from newest to oldest 57 | qint64 m_mostRecentLapTime = 0; 58 | 59 | qint64 m_worstLapTime = 0; 60 | int m_worstLapIndex = -1; // flipped 61 | 62 | qint64 m_bestLapTime = 0; 63 | int m_bestLapIndex = -1; // flipped 64 | }; 65 | -------------------------------------------------------------------------------- /src/kclock/stopwatchtimer.cpp: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Han Young 2 | // SPDX-FileCopyrightText: 2020-2024 Devin Lin 3 | // SPDX-FileCopyrightText: 2025 Kai Uwe Broulik 4 | // SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | #include "stopwatchtimer.h" 7 | #include "utilmodel.h" 8 | 9 | #include 10 | #include 11 | 12 | const int STOPWATCH_DISPLAY_INTERVAL = 41; // 24fps 13 | 14 | StopwatchTimer *StopwatchTimer::instance() 15 | { 16 | static StopwatchTimer *timer = new StopwatchTimer; 17 | return timer; 18 | } 19 | 20 | StopwatchTimer *StopwatchTimer::create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) 21 | { 22 | Q_UNUSED(qmlEngine); 23 | Q_UNUSED(jsEngine); 24 | auto *timer = instance(); 25 | QQmlEngine::setObjectOwnership(timer, QQmlEngine::CppOwnership); 26 | return timer; 27 | } 28 | 29 | StopwatchTimer::StopwatchTimer(QObject *parent) 30 | : QObject(parent) 31 | { 32 | m_reportTimer.setInterval(STOPWATCH_DISPLAY_INTERVAL); 33 | m_reportTimer.callOnTimeout(this, &StopwatchTimer::timeChanged); 34 | } 35 | 36 | bool StopwatchTimer::paused() const 37 | { 38 | return !m_elapsedTimer.isValid() && m_pausedTime.has_value(); 39 | } 40 | 41 | bool StopwatchTimer::stopped() const 42 | { 43 | return !m_elapsedTimer.isValid() && !m_pausedTime.has_value(); 44 | } 45 | 46 | void StopwatchTimer::toggle() 47 | { 48 | if (m_elapsedTimer.isValid()) { 49 | // pause. 50 | m_pausedTime = m_pausedTime.value_or(0) + m_elapsedTimer.elapsed(); 51 | m_elapsedTimer.invalidate(); 52 | Q_EMIT pausedChanged(); 53 | m_reportTimer.stop(); 54 | Q_EMIT timeChanged(); 55 | } else { 56 | // start or resume. 57 | m_elapsedTimer.start(); 58 | if (m_pausedTime) { 59 | Q_EMIT pausedChanged(); 60 | } else { 61 | Q_EMIT stoppedChanged(); 62 | } 63 | m_reportTimer.start(); 64 | Q_EMIT timeChanged(); 65 | } 66 | } 67 | 68 | void StopwatchTimer::reset() 69 | { 70 | m_elapsedTimer.invalidate(); 71 | m_pausedTime.reset(); 72 | m_reportTimer.stop(); 73 | 74 | Q_EMIT stoppedChanged(); 75 | Q_EMIT pausedChanged(); 76 | Q_EMIT timeChanged(); 77 | 78 | Q_EMIT resetTriggered(); 79 | } 80 | 81 | long long StopwatchTimer::elapsedTime() const 82 | { 83 | long long time = 0; 84 | if (m_elapsedTimer.isValid()) { 85 | time += m_elapsedTimer.elapsed(); 86 | } 87 | if (m_pausedTime) { 88 | time += *m_pausedTime; 89 | } 90 | return time; 91 | } 92 | 93 | qint64 StopwatchTimer::hours() const 94 | { 95 | return UtilModel::instance()->msToHoursPart(elapsedTime()); 96 | } 97 | 98 | qint64 StopwatchTimer::minutes() const 99 | { 100 | return UtilModel::instance()->msToMinutesPart(elapsedTime()); 101 | } 102 | 103 | qint64 StopwatchTimer::seconds() const 104 | { 105 | return UtilModel::instance()->msToSecondsPart(elapsedTime()); 106 | } 107 | 108 | qint64 StopwatchTimer::small() const 109 | { 110 | return UtilModel::instance()->msToSmallPart(elapsedTime()); 111 | } 112 | 113 | QString StopwatchTimer::display() const 114 | { 115 | // Only show hours if we have passed an hour. 116 | if (hours() > 0) { 117 | return QStringLiteral("%1:%2:%3").arg(hoursDisplay(), minutesDisplay(), secondsDisplay()); 118 | } else { 119 | return QStringLiteral("%1:%2").arg(minutesDisplay(), secondsDisplay()); 120 | } 121 | } 122 | 123 | QString StopwatchTimer::hoursDisplay() const 124 | { 125 | qint64 amount = hours(); 126 | return UtilModel::instance()->displayTwoDigits(amount); 127 | } 128 | 129 | QString StopwatchTimer::minutesDisplay() const 130 | { 131 | // % 60 discards anything above 60 minutes. Not used in minutes() because 132 | // it may tamper with seconds() and small(). 133 | qint64 amount = minutes() % 60; 134 | return UtilModel::instance()->displayTwoDigits(amount); 135 | } 136 | 137 | QString StopwatchTimer::secondsDisplay() const 138 | { 139 | qint64 amount = seconds(); 140 | return UtilModel::instance()->displayTwoDigits(amount); 141 | } 142 | 143 | QString StopwatchTimer::smallDisplay() const 144 | { 145 | qint64 amount = small(); 146 | return UtilModel::instance()->displayTwoDigits(amount); 147 | } 148 | 149 | #include "moc_stopwatchtimer.cpp" 150 | -------------------------------------------------------------------------------- /src/kclock/stopwatchtimer.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Han Young 2 | // SPDX-FileCopyrightText: 2020-2024 Devin Lin 3 | // SPDX-FileCopyrightText: 2025 Kai Uwe Broulik 4 | // SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | 16 | class QJSEngine; 17 | class QQmlEngine; 18 | 19 | // class for the timer that manages the stopwatch 20 | class StopwatchTimer : public QObject 21 | { 22 | Q_OBJECT 23 | QML_ELEMENT 24 | QML_SINGLETON 25 | 26 | Q_PROPERTY(bool paused READ paused NOTIFY pausedChanged) 27 | Q_PROPERTY(bool stopped READ stopped NOTIFY stoppedChanged) 28 | Q_PROPERTY(qint64 elapsedTime READ elapsedTime NOTIFY timeChanged) 29 | Q_PROPERTY(QString display READ display NOTIFY timeChanged) 30 | Q_PROPERTY(QString hours READ hoursDisplay NOTIFY timeChanged) 31 | Q_PROPERTY(QString minutes READ minutesDisplay NOTIFY timeChanged) 32 | Q_PROPERTY(QString seconds READ secondsDisplay NOTIFY timeChanged) 33 | Q_PROPERTY(QString small READ smallDisplay NOTIFY timeChanged) 34 | 35 | public: 36 | static StopwatchTimer *instance(); 37 | static StopwatchTimer *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); 38 | 39 | bool paused() const; 40 | bool stopped() const; 41 | 42 | qint64 hours() const; 43 | qint64 minutes() const; 44 | qint64 seconds() const; 45 | qint64 small() const; 46 | QString display() const; 47 | QString hoursDisplay() const; 48 | QString minutesDisplay() const; 49 | QString secondsDisplay() const; 50 | QString smallDisplay() const; 51 | 52 | qint64 elapsedTime() const; 53 | 54 | Q_INVOKABLE void reset(); 55 | Q_INVOKABLE void toggle(); 56 | 57 | Q_SIGNALS: 58 | void pausedChanged(); 59 | void stoppedChanged(); 60 | void timeChanged(); 61 | void resetTriggered(); 62 | 63 | private: 64 | explicit StopwatchTimer(QObject *parent = nullptr); 65 | 66 | QElapsedTimer m_elapsedTimer; 67 | 68 | std::optional m_pausedTime; 69 | 70 | QTimer m_reportTimer; 71 | }; 72 | -------------------------------------------------------------------------------- /src/kclock/timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * Copyright 2021 Boris Petrov 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "timerinterface.h" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | class Timer : public QObject 20 | { 21 | Q_OBJECT 22 | QML_ELEMENT 23 | QML_UNCREATABLE("Managed by TimerModel") 24 | Q_PROPERTY(int length READ length WRITE setLength NOTIFY lengthChanged) 25 | Q_PROPERTY(QString lengthPretty READ lengthPretty NOTIFY lengthChanged) 26 | Q_PROPERTY(int elapsed READ elapsed NOTIFY elapsedChanged) 27 | Q_PROPERTY(QString elapsedPretty READ elapsedPretty NOTIFY elapsedChanged) 28 | Q_PROPERTY(QString remainingPretty READ remainingPretty NOTIFY elapsedChanged) 29 | Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) 30 | Q_PROPERTY(QString commandTimeout READ commandTimeout WRITE setCommandTimeout NOTIFY commandTimeoutChanged) 31 | Q_PROPERTY(bool running READ running NOTIFY runningChanged) 32 | Q_PROPERTY(bool looping READ looping WRITE setLooping NOTIFY loopingChanged) 33 | Q_PROPERTY(bool ringing READ ringing NOTIFY ringingChanged) 34 | 35 | public: 36 | explicit Timer(const QString &uuid = QString{}, QObject *parent = nullptr); 37 | 38 | const QUuid &uuid(); 39 | 40 | int length() const; 41 | QString lengthPretty() const; 42 | void setLength(int length); 43 | 44 | int elapsed() const; 45 | QString elapsedPretty() const; 46 | QString remainingPretty() const; 47 | 48 | QString toPretty(int len) const; 49 | 50 | QString label() const; 51 | void setLabel(const QString &label); 52 | 53 | QString commandTimeout() const; 54 | void setCommandTimeout(const QString &commandTimeout); 55 | 56 | bool running() const; 57 | 58 | bool looping() const; 59 | void setLooping(bool looping); 60 | 61 | bool ringing() const; 62 | 63 | Q_INVOKABLE void toggleRunning(); 64 | Q_INVOKABLE void toggleLooping(); 65 | Q_INVOKABLE void reset(); 66 | Q_INVOKABLE void addMinute(); 67 | Q_INVOKABLE void dismiss(); 68 | 69 | Q_SIGNALS: 70 | void lengthChanged(); 71 | void elapsedChanged(); 72 | void labelChanged(); 73 | void commandTimeoutChanged(); 74 | void runningChanged(); 75 | void loopingChanged(); 76 | void ringingChanged(); 77 | 78 | private Q_SLOTS: 79 | void updateLength(); 80 | void updateElapsed(); 81 | void updateLabel(); 82 | void updateCommandTimeout(); 83 | void updateRunning(); 84 | void updateLooping(); 85 | void updateRinging(); 86 | 87 | private: 88 | void animation(bool start); 89 | 90 | int m_length; 91 | int m_elapsed; 92 | 93 | QString m_label; 94 | QString m_commandTimeout; 95 | 96 | bool m_running; 97 | bool m_looping; 98 | bool m_ringing; 99 | 100 | QUuid m_uuid; 101 | 102 | org::kde::kclock::Timer *m_interface; 103 | 104 | QTimer *m_animationTimer; 105 | }; 106 | -------------------------------------------------------------------------------- /src/kclock/timermodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * Copyright 2021 Boris Petrov 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include "timermodelinterface.h" 19 | 20 | class QQmlEngine; 21 | class QJSEngine; 22 | 23 | class Timer; 24 | class TimerModel : public QAbstractListModel 25 | { 26 | Q_OBJECT 27 | QML_ELEMENT 28 | QML_SINGLETON 29 | Q_PROPERTY(bool connectedToDaemon READ connectedToDaemon NOTIFY connectedToDaemonChanged) 30 | Q_PROPERTY(QString defaultAudioLocation READ defaultAudioLocation WRITE setDefaultAudioLocation NOTIFY defaultAudioLocationChanged) 31 | /** 32 | * If there is a single running timer in the model, this property will return it. 33 | */ 34 | Q_PROPERTY(Timer *runningTimer READ runningTimer NOTIFY runningTimerChanged) 35 | 36 | public: 37 | static TimerModel *instance(); 38 | static TimerModel *create(QQmlEngine *qmlengine, QJSEngine *jsEngine); 39 | 40 | enum { 41 | TimerRole, 42 | }; 43 | 44 | int rowCount(const QModelIndex &parent) const override; 45 | QVariant data(const QModelIndex &index, int role) const override; 46 | QHash roleNames() const override; 47 | 48 | Q_INVOKABLE void addNew(int length, const QString &label, bool looping, const QString &commandTimeout); 49 | Q_INVOKABLE void remove(int index); 50 | 51 | bool connectedToDaemon(); 52 | void setConnectedToDaemon(bool connectedToDaemon); 53 | 54 | QString defaultAudioLocation() const; 55 | void setDefaultAudioLocation(const QString &location); 56 | 57 | Timer *runningTimer() const; 58 | 59 | Q_SIGNALS: 60 | void connectedToDaemonChanged(); 61 | void runningTimerChanged(); 62 | void defaultAudioLocationChanged(); 63 | 64 | private Q_SLOTS: 65 | void addTimer(QString uuid); 66 | void removeTimer(const QString &uuid); 67 | void updateDefaultAudioLocation(); 68 | 69 | private: 70 | explicit TimerModel(QObject *parent = nullptr); 71 | 72 | QList m_timersList; 73 | OrgKdeKclockTimerModelInterface *const m_interface; 74 | QDBusServiceWatcher *m_watcher; 75 | QString m_defaultAudioLocation; 76 | bool m_connectedToDaemon = false; 77 | }; 78 | -------------------------------------------------------------------------------- /src/kclock/timerpresetmodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Swapnil Tripathi 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #include "timerpresetmodel.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | const QString TIMERPRESETS_CFG_GROUP = QStringLiteral("TimerPresets"), TIMERPRESETS_CFG_KEY = QStringLiteral("timerPresets"); 15 | 16 | TimerPreset::TimerPreset(QObject *parent, const QString &presetName, int presetDuration) 17 | : QObject(parent) 18 | , m_presetName(presetName) 19 | , m_presetDuration(presetDuration) 20 | { 21 | } 22 | TimerPreset::TimerPreset(const QJsonObject &obj) 23 | : m_presetName(obj[QStringLiteral("presetName")].toString()) 24 | , m_presetDuration(obj[QStringLiteral("presetDuration")].toInt()) 25 | { 26 | } 27 | 28 | TimerPreset::~TimerPreset() 29 | { 30 | } 31 | 32 | QJsonObject TimerPreset::toJson() const 33 | { 34 | QJsonObject obj; 35 | obj[QStringLiteral("presetName")] = m_presetName; 36 | obj[QStringLiteral("presetDuration")] = m_presetDuration; 37 | return obj; 38 | } 39 | 40 | QString TimerPreset::presetName() const 41 | { 42 | return m_presetName; 43 | } 44 | 45 | int TimerPreset::presetDuration() const 46 | { 47 | return m_presetDuration; 48 | } 49 | 50 | void TimerPreset::setPresetName(const QString &presetName) 51 | { 52 | m_presetName = presetName; 53 | Q_EMIT propertyChanged(); 54 | } 55 | 56 | void TimerPreset::setDurationLength(int presetDuration) 57 | { 58 | m_presetDuration = presetDuration; 59 | Q_EMIT propertyChanged(); 60 | } 61 | 62 | /* - TimerPresetModel - */ 63 | 64 | TimerPresetModel *TimerPresetModel::instance() 65 | { 66 | static TimerPresetModel *s_presetModel = new TimerPresetModel(qApp); 67 | return s_presetModel; 68 | } 69 | 70 | TimerPresetModel *TimerPresetModel::create(QQmlEngine *qmlEngine, QJSEngine *jsEngine) 71 | { 72 | Q_UNUSED(qmlEngine); 73 | Q_UNUSED(jsEngine); 74 | auto *model = instance(); 75 | QQmlEngine::setObjectOwnership(model, QQmlEngine::CppOwnership); 76 | return model; 77 | } 78 | 79 | TimerPresetModel::TimerPresetModel(QObject *parent) 80 | : QAbstractListModel(parent) 81 | { 82 | load(); 83 | } 84 | 85 | TimerPresetModel::~TimerPresetModel() 86 | { 87 | save(); 88 | 89 | qDeleteAll(m_presets); 90 | } 91 | 92 | void TimerPresetModel::load() 93 | { 94 | auto config = KSharedConfig::openConfig(); 95 | KConfigGroup group = config->group(TIMERPRESETS_CFG_GROUP); 96 | QJsonDocument doc = QJsonDocument::fromJson(group.readEntry(TIMERPRESETS_CFG_KEY, "{}").toUtf8()); 97 | 98 | const auto array = doc.array(); 99 | std::transform(array.begin(), array.end(), std::back_inserter(m_presets), [](const QJsonValue &pre) { 100 | return new TimerPreset(pre.toObject()); 101 | }); 102 | } 103 | 104 | void TimerPresetModel::save() 105 | { 106 | QJsonArray arr; 107 | 108 | const auto presets = std::as_const(m_presets); 109 | std::transform(presets.begin(), presets.end(), std::back_inserter(arr), [](const TimerPreset *preset) { 110 | return QJsonValue(preset->toJson()); 111 | }); 112 | 113 | auto config = KSharedConfig::openConfig(); 114 | KConfigGroup group = config->group(TIMERPRESETS_CFG_GROUP); 115 | group.writeEntry(TIMERPRESETS_CFG_KEY, QString::fromStdString(QJsonDocument(arr).toJson(QJsonDocument::Compact).toStdString())); 116 | 117 | group.sync(); 118 | } 119 | 120 | QHash TimerPresetModel::roleNames() const 121 | { 122 | return {{Roles::TimerPresetRole, "preset"}}; 123 | } 124 | 125 | QVariant TimerPresetModel::data(const QModelIndex &index, int role) const 126 | { 127 | if (!index.isValid() || index.row() >= m_presets.count() || index.row() < 0) 128 | return {}; 129 | 130 | auto *preset = m_presets.at(index.row()); 131 | if (role == Roles::TimerPresetRole) 132 | return QVariant::fromValue(preset); 133 | 134 | return {}; 135 | } 136 | 137 | int TimerPresetModel::rowCount(const QModelIndex &parent) const 138 | { 139 | return parent.isValid() ? 0 : m_presets.count(); 140 | } 141 | 142 | void TimerPresetModel::insertPreset(const QString &presetName, int presetDuration) 143 | { 144 | Q_EMIT beginInsertRows({}, 0, 0); 145 | m_presets.insert(0, new TimerPreset(this, presetName, presetDuration)); 146 | Q_EMIT endInsertRows(); 147 | 148 | save(); 149 | } 150 | 151 | void TimerPresetModel::deletePreset(const int index) 152 | { 153 | beginRemoveRows({}, index, index); 154 | m_presets.removeAt(index); 155 | endRemoveRows(); 156 | 157 | save(); 158 | } 159 | 160 | #include "moc_timerpresetmodel.cpp" 161 | -------------------------------------------------------------------------------- /src/kclock/timerpresetmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Swapnil Tripathi 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | class QQmlEngine; 21 | class QJSEngine; 22 | 23 | class TimerPreset : public QObject 24 | { 25 | Q_OBJECT 26 | Q_PROPERTY(QString presetName READ presetName WRITE setPresetName NOTIFY propertyChanged) 27 | Q_PROPERTY(int presetDuration READ presetDuration NOTIFY propertyChanged) 28 | 29 | public: 30 | explicit TimerPreset(QObject *parent = nullptr, const QString &presetName = {}, int presetDuration = 0); 31 | explicit TimerPreset(const QJsonObject &obj); 32 | 33 | ~TimerPreset(); 34 | 35 | QJsonObject toJson() const; 36 | 37 | QString presetName() const; 38 | int presetDuration() const; 39 | void setPresetName(const QString &presetName); 40 | void setDurationLength(int presetDuration); 41 | 42 | private: 43 | QString m_presetName; 44 | int m_presetDuration; 45 | 46 | Q_SIGNALS: 47 | void propertyChanged(); 48 | }; 49 | 50 | class TimerPresetModel : public QAbstractListModel 51 | { 52 | Q_OBJECT 53 | QML_ELEMENT 54 | QML_SINGLETON 55 | 56 | public: 57 | enum Roles { 58 | TimerPresetRole = Qt::UserRole 59 | }; 60 | 61 | static TimerPresetModel *instance(); 62 | static TimerPresetModel *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); 63 | 64 | void load(); 65 | void save(); 66 | 67 | QHash roleNames() const override; 68 | 69 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 70 | 71 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 72 | 73 | Q_INVOKABLE void insertPreset(const QString &presetName, int presetDuration); 74 | Q_INVOKABLE void deletePreset(const int index); 75 | 76 | private: 77 | explicit TimerPresetModel(QObject *parent = nullptr); 78 | ~TimerPresetModel() override; 79 | 80 | QList m_presets; 81 | }; 82 | -------------------------------------------------------------------------------- /src/kclock/utilmodel.h: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2020 Han Young 2 | // SPDX-FileCopyrightText: 2020-2024 Devin Lin 3 | // SPDX-License-Identifier: GPL-2.0-or-later 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | 14 | class QJSEngine; 15 | class QQmlEngine; 16 | 17 | namespace KSvg 18 | { 19 | class Svg; 20 | }; 21 | 22 | class UtilModel : public QObject 23 | { 24 | Q_OBJECT 25 | QML_ELEMENT 26 | QML_SINGLETON 27 | 28 | Q_PROPERTY(QString tzName READ getCurrentTimeZoneName CONSTANT) 29 | Q_PROPERTY(bool use24HourTime READ use24HourTime NOTIFY use24HourTimeChanged) 30 | 31 | public: 32 | static UtilModel *instance(); 33 | static UtilModel *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); 34 | 35 | Q_INVOKABLE QString getDefaultAlarmFileLocation(); 36 | 37 | QString getCurrentTimeZoneName(); 38 | 39 | long long calculateNextRingTime(int hours, int minutes, int daysOfWeek, int snooze = 0); 40 | QString timeToRingFormatted(const long long ×tamp); 41 | 42 | QString timeFormat() const; 43 | bool use24HourTime() const; 44 | 45 | // use to turn milliseconds -> hh:mm:ss.00 46 | Q_INVOKABLE qint64 msToHoursPart(qint64 ms) const; 47 | Q_INVOKABLE qint64 msToMinutesPart(qint64 ms) const; 48 | Q_INVOKABLE qint64 msToSecondsPart(qint64 ms) const; 49 | Q_INVOKABLE qint64 msToSmallPart(qint64 ms) const; // hundredths of second 50 | Q_INVOKABLE QString displayTwoDigits(const qint64 &amount); 51 | Q_INVOKABLE QString repeatFormat(int dayOfWeek) const; 52 | 53 | Q_INVOKABLE void applyPlasmaImageSet(KSvg::Svg *svg); 54 | 55 | private: 56 | explicit UtilModel(QObject *parent = nullptr); 57 | 58 | bool isLocale24HourTime() const; 59 | 60 | Q_SIGNALS: 61 | void use24HourTimeChanged(); 62 | 63 | private: 64 | KSvg::ImageSet *m_plasmaImageSet = nullptr; 65 | }; 66 | -------------------------------------------------------------------------------- /src/kclock/windowexposure.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Kai Uwe Broulik 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | */ 6 | 7 | #include "windowexposure.h" 8 | 9 | #include 10 | #include 11 | 12 | WindowExposure::WindowExposure(QWindow *window, QObject *parent) 13 | : QObject(parent) 14 | , m_window(window) 15 | { 16 | m_exposed = window->isExposed(); 17 | window->installEventFilter(this); 18 | } 19 | 20 | WindowExposure *WindowExposure::qmlAttachedProperties(QObject *object) 21 | { 22 | auto *window = qobject_cast(object); 23 | if (!window) { 24 | qWarning() << "WindowExposure must be attached to a Window"; 25 | return nullptr; 26 | } 27 | return new WindowExposure(window, object); 28 | } 29 | 30 | bool WindowExposure::eventFilter(QObject *watched, QEvent *event) 31 | { 32 | if (event->type() == QEvent::Expose) { 33 | if (m_window->isExposed() != m_exposed) { 34 | m_exposed = m_window->isExposed(); 35 | Q_EMIT exposedChanged(); 36 | } 37 | } 38 | 39 | return QObject::eventFilter(watched, event); 40 | } 41 | 42 | bool WindowExposure::isExposed() const 43 | { 44 | return m_exposed; 45 | } 46 | 47 | #include "moc_windowexposure.cpp" 48 | -------------------------------------------------------------------------------- /src/kclock/windowexposure.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Kai Uwe Broulik 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | class QWindow; 13 | 14 | /** 15 | * This class tracks the isExposed state of a QWindow. 16 | * 17 | * Under Wayland, a window is either exposed or it is not. 18 | * There's no real concept such as minimized or hidden or anything. 19 | */ 20 | class WindowExposure : public QObject 21 | { 22 | Q_OBJECT 23 | QML_ELEMENT 24 | QML_UNCREATABLE("Can only be used as attached property") 25 | QML_ATTACHED(WindowExposure) 26 | 27 | Q_PROPERTY(bool exposed READ isExposed NOTIFY exposedChanged) 28 | 29 | public: 30 | explicit WindowExposure(QWindow *window, QObject *parent = nullptr); 31 | 32 | bool isExposed() const; 33 | Q_SIGNAL void exposedChanged(); 34 | 35 | bool eventFilter(QObject *watched, QEvent *event) override; 36 | 37 | static WindowExposure *qmlAttachedProperties(QObject *object); 38 | 39 | private: 40 | QWindow *m_window; 41 | bool m_exposed = false; 42 | }; 43 | -------------------------------------------------------------------------------- /src/kclockd/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Han Young 3 | # Copyright 2020 Devin Lin 4 | # 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | # 7 | 8 | set(kclockd_SRCS 9 | utilities.cpp 10 | utilities.h 11 | alarm.cpp 12 | alarm.h 13 | alarmmodel.cpp 14 | alarmmodel.h 15 | alarmwaitworker.cpp 16 | alarmwaitworker.h 17 | alarmplayer.cpp 18 | alarmplayer.h 19 | kclockrunner.cpp 20 | kclockrunner.h 21 | timermodel.cpp 22 | timermodel.h 23 | timer.cpp 24 | timer.h 25 | xdgportal.cpp 26 | xdgportal.h 27 | abstractwakeupprovider.h 28 | powerdevilwakeupprovider.cpp 29 | powerdevilwakeupprovider.h 30 | unitylauncher.cpp 31 | unitylauncher.h 32 | waittimerwakeupprovider.cpp 33 | waittimerwakeupprovider.h 34 | ) 35 | # generate KConfig class from xml 36 | kconfig_add_kcfg_files(kclockd_SRCS kclockdsettings.kcfgc GENERATE_MOC) 37 | 38 | # use generated KConfig header file to generate DBus adaptor xml 39 | set(kclockd_SRCS 40 | ${kclockd_SRCS} 41 | ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.KClockSettings.xml 42 | ) 43 | 44 | qt_generate_dbus_interface( 45 | ${CMAKE_CURRENT_BINARY_DIR}/kclockdsettings.h 46 | org.kde.kclockd.KClockSettings.xml 47 | OPTIONS -S -P 48 | ) 49 | qt_generate_dbus_interface( 50 | ${CMAKE_CURRENT_SOURCE_DIR}/alarm.h 51 | org.kde.kclockd.Alarm.xml 52 | OPTIONS -s -m -P 53 | ) 54 | qt_generate_dbus_interface( 55 | ${CMAKE_CURRENT_SOURCE_DIR}/alarmmodel.h 56 | org.kde.kclockd.AlarmModel.xml 57 | OPTIONS -s -m 58 | ) 59 | qt_generate_dbus_interface( 60 | ${CMAKE_CURRENT_SOURCE_DIR}/timermodel.h 61 | org.kde.kclockd.TimerModel.xml 62 | OPTIONS -s -m -p 63 | ) 64 | 65 | qt_generate_dbus_interface( 66 | ${CMAKE_CURRENT_SOURCE_DIR}/timer.h 67 | org.kde.kclockd.Timer.xml 68 | OPTIONS -s -m -P 69 | ) 70 | 71 | qt_generate_dbus_interface( 72 | ${CMAKE_CURRENT_SOURCE_DIR}/utilities.h 73 | org.kde.kclockd.Utility.xml 74 | OPTIONS -s -m -P 75 | ) 76 | # use generated DBus adapator xml to generate adaptor source code 77 | qt_add_dbus_adaptor(kclockd_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.KClockSettings.xml 78 | kclockdsettings.h KClockSettings) 79 | qt_add_dbus_adaptor(kclockd_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.Alarm.xml 80 | alarm.h Alarm) 81 | qt_add_dbus_adaptor(kclockd_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.AlarmModel.xml 82 | alarmmodel.h AlarmModel) 83 | qt_add_dbus_adaptor(kclockd_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.TimerModel.xml 84 | timermodel.h TimerModel) 85 | qt_add_dbus_adaptor(kclockd_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.Timer.xml 86 | timer.h Timer) 87 | qt_add_dbus_adaptor(kclockd_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.Utility.xml 88 | utilities.h Utilities) 89 | 90 | qt_add_dbus_interface(kclockd_SRCS systeminterfaces/org.mpris.MediaPlayer2.Player.xml generated/systeminterfaces/mprisplayer) 91 | qt_add_dbus_adaptor(kclockd_SRCS systeminterfaces/org.kde.krunner1.xml kclockrunner.h KClockRunner) 92 | 93 | # install DBus interface xml to dbus interface directory 94 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.KClockSettings.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) 95 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.Alarm.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) 96 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.AlarmModel.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) 97 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.TimerModel.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) 98 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.Timer.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) 99 | install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.kclockd.Utility.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR}) 100 | 101 | add_executable(kclockd main.cpp ${kclockd_SRCS} ) 102 | target_link_libraries(kclockd PRIVATE 103 | Qt6::Multimedia 104 | Qt6::Widgets 105 | KF6::I18n 106 | KF6::ConfigCore 107 | KF6::ConfigGui 108 | KF6::CoreAddons 109 | KF6::Notifications 110 | KF6::DBusAddons 111 | KF6::StatusNotifierItem 112 | KF6::Crash 113 | ) 114 | 115 | ecm_qt_declare_logging_category(kclockd HEADER debug_timermodel.h 116 | IDENTIFIER TIMERMODEL_DEBUG 117 | CATEGORY_NAME org.kde.kclockd.timermodel 118 | DESCRIPTION "KClockd TimerModel" 119 | ) 120 | 121 | ecm_qt_declare_logging_category(kclockd HEADER debug_runner.h 122 | IDENTIFIER RUNNER_DEBUG 123 | CATEGORY_NAME org.kde.kclock.runner 124 | DESCRIPTION "KClock Runner" 125 | ) 126 | 127 | target_include_directories(kclockd PRIVATE ${CMAKE_BINARY_DIR}) 128 | install(TARGETS kclockd ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) 129 | -------------------------------------------------------------------------------- /src/kclockd/Messages.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # Copyright 2020 Yuri Chornoivan 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | 6 | $XGETTEXT `find . -name \*.cpp -o -name \*.h -o -name \*.qml` -o $podir/kclockd.pot 7 | -------------------------------------------------------------------------------- /src/kclockd/abstractwakeupprovider.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | class AbstractWakeupProvider : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit AbstractWakeupProvider(QObject *parent = nullptr) 17 | : QObject(parent) 18 | { 19 | } 20 | virtual ~AbstractWakeupProvider() = default; 21 | 22 | virtual int scheduleWakeup(quint64 timestamp) = 0; 23 | virtual void clearWakeup(int cookie) = 0; 24 | 25 | Q_SIGNALS: 26 | void wakeup(int cookie); 27 | }; 28 | -------------------------------------------------------------------------------- /src/kclockd/alarmmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include "alarm.h" 11 | 12 | #include 13 | #include 14 | 15 | class Alarm; 16 | 17 | class AlarmModel : public QObject 18 | { 19 | Q_OBJECT 20 | Q_CLASSINFO("D-Bus Interface", "org.kde.kclock.AlarmModel") 21 | 22 | public: 23 | static AlarmModel *instance(); 24 | 25 | void load(); 26 | void save(); 27 | void configureWakeups(); // needs to be called to start worker thread, or configure powerdevil (called in main) 28 | QList alarmsList() const; 29 | Alarm *alarm(const QString &uuid) const; 30 | 31 | Q_SCRIPTABLE void removeAlarm(const QString &uuid); 32 | Q_SCRIPTABLE void addAlarm(const QString &name, int hours, int minutes, int daysOfWeek, const QString &audioPath, int ringDuration, int snoozeDuration); 33 | 34 | Q_SIGNALS: 35 | Q_SCRIPTABLE void alarmAdded(const QString &uuid); 36 | Q_SCRIPTABLE void alarmRemoved(const QString &uuid); 37 | Q_SCRIPTABLE void nextAlarm(quint64 nextAlarmTimeStamp); // next alarm wakeup timestamp, or 0 if there are none 38 | 39 | public Q_SLOTS: 40 | Q_SCRIPTABLE quint64 getNextAlarm(); 41 | void scheduleAlarm(); 42 | void wakeupCallback(int cookie); 43 | 44 | private Q_SLOTS: 45 | void notifierItemActivated(); 46 | void updateNotifierItem(quint64 time); // update notify icon in systemtray 47 | 48 | private: 49 | explicit AlarmModel(QObject *parent = nullptr); 50 | 51 | void removeAlarm(int index); 52 | void initNotifierItem(); 53 | 54 | // next scheduled system wakeup for ringing alarms, in unix time 55 | quint64 m_nextAlarmTime = 0; 56 | 57 | // token for system wakeup call authentication 58 | int m_cookie = -1; 59 | 60 | // list of alarms that will ring on the next scheduled system wakeup 61 | QList alarmsToRing; 62 | 63 | // list of alarms in the model 64 | QList m_alarmsList; 65 | 66 | // system tray notifier item 67 | KStatusNotifierItem *m_item{nullptr}; 68 | }; 69 | -------------------------------------------------------------------------------- /src/kclockd/alarmplayer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020-2021 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #include "alarmplayer.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | using namespace Qt::Literals::StringLiterals; 19 | 20 | AlarmPlayer &AlarmPlayer::instance() 21 | { 22 | static AlarmPlayer singleton; 23 | return singleton; 24 | } 25 | 26 | AlarmPlayer::AlarmPlayer(QObject *parent) 27 | : QObject{parent} 28 | , m_player(new QMediaPlayer(this)) 29 | , m_audio(new QAudioOutput) 30 | , m_soundThemeWatcher(KConfigWatcher::create(KSharedConfig::openConfig(QStringLiteral("kdeglobals")))) 31 | { 32 | m_player->setAudioOutput(m_audio); 33 | connect(m_player, &QMediaPlayer::playbackStateChanged, this, &AlarmPlayer::loopAudio); 34 | } 35 | 36 | void AlarmPlayer::loopAudio(QMediaPlayer::PlaybackState state) 37 | { 38 | if (!m_userStop && state == QMediaPlayer::StoppedState) { 39 | m_player->play(); 40 | } 41 | } 42 | 43 | void AlarmPlayer::play() 44 | { 45 | if (m_player->playbackState() == QMediaPlayer::PlayingState) { 46 | return; 47 | } 48 | 49 | m_userStop = false; 50 | m_player->play(); 51 | } 52 | 53 | void AlarmPlayer::stop() 54 | { 55 | m_userStop = true; 56 | m_player->stop(); 57 | } 58 | 59 | int AlarmPlayer::volume() 60 | { 61 | return m_audio->volume() * 100; 62 | } 63 | 64 | void AlarmPlayer::setVolume(int volume) 65 | { 66 | m_audio->setVolume(static_cast(volume) / 100); 67 | Q_EMIT volumeChanged(); 68 | } 69 | 70 | void AlarmPlayer::setSource(const QString &path) 71 | { 72 | if (path.isEmpty() || !QFileInfo::exists(path)) { 73 | m_player->setSource(QUrl::fromLocalFile(defaultAlarmSoundPath())); 74 | } else { 75 | m_player->setSource(QUrl::fromLocalFile(path)); 76 | } 77 | } 78 | 79 | QString AlarmPlayer::soundThemeName() const 80 | { 81 | const KConfigGroup soundGroup = m_soundThemeWatcher->config()->group(QStringLiteral("Sounds")); 82 | const QString themeName = soundGroup.readEntry(QStringLiteral("Theme"), QStringLiteral("ocean")); 83 | return themeName; 84 | } 85 | 86 | QString AlarmPlayer::defaultAlarmSoundPath() const 87 | { 88 | const QString soundPath = QStringLiteral("sounds/%1/stereo/alarm-clock-elapsed.%2"); 89 | 90 | for (const QString &theme : {soundThemeName(), u"freedesktop"_s}) { 91 | for (const QLatin1String extension : {"wav"_L1, "oga"_L1, "ogg"_L1}) { 92 | const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, soundPath.arg(theme, extension)); 93 | if (!path.isEmpty()) { 94 | return path; 95 | } 96 | } 97 | } 98 | 99 | qCritical() << "Failed to find any alarm clock sound!"; 100 | return QString(); 101 | } 102 | 103 | #include "moc_alarmplayer.cpp" 104 | -------------------------------------------------------------------------------- /src/kclockd/alarmplayer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020 Devin Lin 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | 17 | class AlarmPlayer : public QObject 18 | { 19 | Q_OBJECT 20 | Q_PROPERTY(int volume READ volume WRITE setVolume NOTIFY volumeChanged) 21 | 22 | public: 23 | static AlarmPlayer &instance(); 24 | 25 | int volume(); 26 | Q_INVOKABLE void setVolume(int volume); 27 | Q_INVOKABLE void setSource(const QString &path); 28 | Q_INVOKABLE void play(); 29 | Q_INVOKABLE void stop(); 30 | 31 | QString soundThemeName() const; 32 | QString defaultAlarmSoundPath() const; 33 | 34 | Q_SIGNALS: 35 | void volumeChanged(); 36 | 37 | protected: 38 | explicit AlarmPlayer(QObject *parent = nullptr); 39 | 40 | private: 41 | QMediaPlayer *m_player; 42 | QAudioOutput *m_audio; 43 | KConfigWatcher::Ptr m_soundThemeWatcher; 44 | 45 | bool m_userStop = false; // indicate if user asks to stop 46 | 47 | private Q_SLOTS: 48 | void loopAudio(QMediaPlayer::PlaybackState state); 49 | }; 50 | -------------------------------------------------------------------------------- /src/kclockd/alarmwaitworker.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #include "alarmwaitworker.h" 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | AlarmWaitWorker::AlarmWaitWorker(quint64 timestamp) 17 | : m_timerFd{timerfd_create(CLOCK_REALTIME, 0)} 18 | , m_waitEndTime{timestamp} 19 | { 20 | connect(this, &AlarmWaitWorker::startWait, this, &AlarmWaitWorker::wait); 21 | } 22 | 23 | void AlarmWaitWorker::wait(int waitId) 24 | { 25 | if (m_waitEndTime == 0) { 26 | return; 27 | } 28 | 29 | struct itimerspec timerSpec; 30 | timerSpec.it_value.tv_sec = static_cast(m_waitEndTime); 31 | timerSpec.it_value.tv_nsec = 0; 32 | timerSpec.it_interval.tv_sec = 0; 33 | timerSpec.it_interval.tv_nsec = 0; 34 | 35 | timerfd_settime(m_timerFd, TFD_TIMER_ABSTIME, &timerSpec, nullptr); // absolute time 36 | 37 | struct pollfd fd; // only one fd 38 | fd.fd = m_timerFd; 39 | fd.events = POLLIN | POLLPRI; 40 | 41 | poll(&fd, 1, -1); 42 | 43 | if (fd.revents & POLLNVAL) { 44 | Q_EMIT error(); 45 | return; 46 | } 47 | // ensure that there is one wakeup and not multiple 48 | if (waitId != this->m_waitId) { 49 | qDebug() << "cancel old wakeup" << waitId; 50 | return; 51 | } 52 | 53 | qDebug() << "waiting end" << waitId; 54 | 55 | Q_EMIT finished(); 56 | } 57 | 58 | void AlarmWaitWorker::setNewTime(quint64 timestamp) 59 | { 60 | m_waitId = std::rand(); 61 | 62 | m_waitEndTime = timestamp; 63 | struct itimerspec timerSpec; 64 | timerSpec.it_value.tv_sec = static_cast(m_waitEndTime); 65 | timerSpec.it_value.tv_nsec = 0; 66 | timerSpec.it_interval.tv_sec = 0; 67 | timerSpec.it_interval.tv_nsec = 0; 68 | timerfd_settime(m_timerFd, TFD_TIMER_ABSTIME, &timerSpec, nullptr); // absolute time 69 | 70 | qDebug() << "start waiting, id:" << m_waitId << " Wait Time:" << timestamp; 71 | 72 | Q_EMIT startWait(m_waitId); 73 | } 74 | 75 | #include "moc_alarmwaitworker.cpp" 76 | -------------------------------------------------------------------------------- /src/kclockd/alarmwaitworker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | class AlarmWaitWorker : public QObject 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit AlarmWaitWorker(quint64 timestamp = 0); 17 | 18 | void setNewTime(quint64 timestamp); // set new wait time, if is currently waiting 19 | 20 | Q_SIGNALS: 21 | void finished(); 22 | void error(); 23 | void startWait(int waitId); 24 | 25 | protected Q_SLOTS: 26 | void wait(int waitId); 27 | 28 | private: 29 | int m_timerFd; 30 | int m_waitId; // use only newest poll 31 | quint64 m_waitEndTime; 32 | bool m_isFinished = true; 33 | }; 34 | -------------------------------------------------------------------------------- /src/kclockd/dbusutils_p.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2017, 2018 David Edmundson 3 | SPDX-FileCopyrightText: 2020 Alexander Lohnau 4 | SPDX-FileCopyrightText: 2020 Kai Uwe Broulik 5 | 6 | SPDX-License-Identifier: LGPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | const qreal HighestCategoryRelevance = 100; // KRunner::QueryMatch::CategoryRelevance::Highest 17 | 18 | struct RemoteMatch { 19 | // sssida{sv} 20 | QString id; 21 | QString text; 22 | QString iconName; 23 | int categoryRelevance = HighestCategoryRelevance; 24 | qreal relevance = 0; 25 | QVariantMap properties; 26 | }; 27 | 28 | using RemoteMatches = QList; 29 | 30 | inline QDBusArgument &operator<<(QDBusArgument &argument, const RemoteMatch &match) 31 | { 32 | argument.beginStructure(); 33 | argument << match.id; 34 | argument << match.text; 35 | argument << match.iconName; 36 | argument << match.categoryRelevance; 37 | argument << match.relevance; 38 | argument << match.properties; 39 | argument.endStructure(); 40 | return argument; 41 | } 42 | 43 | inline const QDBusArgument &operator>>(const QDBusArgument &argument, RemoteMatch &match) 44 | { 45 | argument.beginStructure(); 46 | argument >> match.id; 47 | argument >> match.text; 48 | argument >> match.iconName; 49 | argument >> match.categoryRelevance; 50 | argument >> match.relevance; 51 | argument >> match.properties; 52 | argument.endStructure(); 53 | 54 | return argument; 55 | } 56 | 57 | struct RemoteAction { 58 | QString id; 59 | QString text; 60 | QString iconSource; 61 | }; 62 | 63 | using RemoteActions = QList; 64 | 65 | inline QDBusArgument &operator<<(QDBusArgument &argument, const RemoteAction &action) 66 | { 67 | argument.beginStructure(); 68 | argument << action.id; 69 | argument << action.text; 70 | argument << action.iconSource; 71 | argument.endStructure(); 72 | return argument; 73 | } 74 | 75 | inline const QDBusArgument &operator>>(const QDBusArgument &argument, RemoteAction &action) 76 | { 77 | QString id; 78 | QString text; 79 | QString iconSource; 80 | argument.beginStructure(); 81 | argument >> id; 82 | argument >> text; 83 | argument >> iconSource; 84 | argument.endStructure(); 85 | action = RemoteAction{id, iconSource, text}; 86 | return argument; 87 | } 88 | 89 | Q_DECLARE_METATYPE(RemoteAction) 90 | Q_DECLARE_METATYPE(RemoteActions) 91 | Q_DECLARE_METATYPE(RemoteMatch) 92 | Q_DECLARE_METATYPE(RemoteMatches) 93 | -------------------------------------------------------------------------------- /src/kclockd/kclockdsettings.kcfg: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | 13 | 14 | 15 | SystemDefault 16 | 17 | 18 | 19 | WhenKClockNotRunning 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/kclockd/kclockdsettings.kcfgc: -------------------------------------------------------------------------------- 1 | File=kclockdsettings.kcfg 2 | ClassName=KClockSettings 3 | DefaultValueGetters=true 4 | GenerateProperties=true 5 | Singleton=true 6 | Mutators=true 7 | GlobalEnums=true 8 | -------------------------------------------------------------------------------- /src/kclockd/kclockdsettings.kcfgc.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Devin Lin 2 | Copyright 2020 Han Young 3 | SPDX-License-Identifier: GPL-2.0-or-later 4 | -------------------------------------------------------------------------------- /src/kclockd/kclockrunner.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Kai Uwe Broulik 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "dbusutils_p.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | class KClockRunner : public QObject, protected QDBusContext 18 | { 19 | Q_OBJECT 20 | Q_CLASSINFO("D-Bus Interface", "org.kde.kclock.KClockRunner") 21 | public: 22 | explicit KClockRunner(QObject *parent = nullptr); 23 | 24 | void Teardown(); 25 | RemoteActions Actions() const; 26 | RemoteMatches Match(const QString &searchTerm); 27 | void Run(const QString &id, const QString &actionId); 28 | }; 29 | -------------------------------------------------------------------------------- /src/kclockd/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2020 Devin Lin 4 | * Copyright 2019 Nick Reitemeyer 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "alarmmodel.h" 10 | #include "kclockdsettings.h" 11 | #include "kclockrunner.h" 12 | #include "kclocksettingsadaptor.h" 13 | #include "timermodel.h" 14 | #include "utilities.h" 15 | #include "version.h" 16 | #include "xdgportal.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | QCommandLineParser *createParser() 30 | { 31 | QCommandLineParser *parser = new QCommandLineParser; 32 | parser->addOption(QCommandLineOption(QStringLiteral("no-powerdevil"), i18n("Don't use PowerDevil for alarms if it is available"))); 33 | parser->addHelpOption(); 34 | return parser; 35 | } 36 | 37 | int main(int argc, char *argv[]) 38 | { 39 | QApplication::setDesktopSettingsAware(false); 40 | QApplication app(argc, argv); 41 | 42 | KLocalizedString::setApplicationDomain("kclockd"); 43 | KAboutData aboutData(QStringLiteral("kclockd"), 44 | QStringLiteral("KClock daemon"), 45 | QStringLiteral(KCLOCK_VERSION_STRING), 46 | QStringLiteral("KClock daemon"), 47 | KAboutLicense::GPL, 48 | i18n("© 2020-2022 KDE Community")); 49 | aboutData.addAuthor(i18n("Devin Lin"), QLatin1String(), QStringLiteral("devin@kde.org")); 50 | aboutData.addAuthor(i18n("Han Young"), QLatin1String(), QStringLiteral("hanyoung@protonmail.com")); 51 | aboutData.addAuthor(i18n("Kai Uwe Broulik"), i18nc("Author", "Desktop Integration"), QStringLiteral("kde@broulik.de")); 52 | KAboutData::setApplicationData(aboutData); 53 | 54 | KCrash::initialize(); 55 | 56 | // ~~~~ Parse command line arguments ~~~~ 57 | { 58 | QScopedPointer parser(createParser()); 59 | parser->process(app); 60 | 61 | if (parser->isSet(QStringLiteral("no-powerdevil"))) { 62 | Utilities::disablePowerDevil(true); 63 | } 64 | } 65 | 66 | // only allow one instance 67 | KDBusService service(KDBusService::Unique); 68 | 69 | qDebug() << "Starting kclockd" << KCLOCK_VERSION_STRING; 70 | 71 | // call org.freedesktop.portal.Background for autostart in flatpak 72 | if (KSandbox::isFlatpak()) { 73 | XDGPortal *portalInterface = new XDGPortal(); 74 | portalInterface->requestBackground(); 75 | } 76 | 77 | // initialize models 78 | new KClockSettingsAdaptor(KClockSettings::self()); 79 | QDBusConnection::sessionBus().registerObject(QStringLiteral("/Settings"), KClockSettings::self()); 80 | 81 | new KClockRunner(&app); 82 | 83 | // save config 84 | QObject::connect(KClockSettings::self(), &KClockSettings::timeFormatChanged, KClockSettings::self(), &KClockSettings::save); 85 | QObject::connect(KClockSettings::self(), &KClockSettings::timerNotificationChanged, KClockSettings::self(), &KClockSettings::save); 86 | 87 | // start alarm polling 88 | AlarmModel::instance()->configureWakeups(); 89 | TimerModel::instance(); 90 | 91 | return app.exec(); 92 | } 93 | -------------------------------------------------------------------------------- /src/kclockd/powerdevilwakeupprovider.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #include "powerdevilwakeupprovider.h" 8 | #include "utilities.h" 9 | 10 | #include 11 | #include 12 | 13 | PowerDevilWakeupProvider::PowerDevilWakeupProvider(QObject *parent) 14 | : AbstractWakeupProvider(parent) 15 | , m_interface(new QDBusInterface(POWERDEVIL_SERVICE_NAME, 16 | QStringLiteral("/org/kde/Solid/PowerManagement"), 17 | QStringLiteral("org.kde.Solid.PowerManagement"), 18 | QDBusConnection::sessionBus(), 19 | this)) 20 | { 21 | QDBusConnection::sessionBus().connect(POWERDEVIL_SERVICE_NAME, 22 | QStringLiteral("/org/kde/Solid/PowerManagement"), 23 | QStringLiteral("org.kde.Solid.PowerManagement"), 24 | QStringLiteral("wakeupCallback"), 25 | this, 26 | SLOT(wakeupCallback(int))); 27 | } 28 | 29 | int PowerDevilWakeupProvider::scheduleWakeup(quint64 timestamp) 30 | { 31 | QDBusReply reply = m_interface->call(QStringLiteral("scheduleWakeup"), QStringLiteral("org.kde.kclockd"), QDBusObjectPath("/Utility"), timestamp); 32 | if (reply.isValid()) { 33 | m_powerDevilCookies.append(reply.value()); 34 | } else { 35 | qDebug() << "Invalid reply, error: " << reply.error(); 36 | } 37 | return reply.value(); 38 | } 39 | 40 | void PowerDevilWakeupProvider::clearWakeup(int cookie) 41 | { 42 | auto index = m_powerDevilCookies.indexOf(cookie); 43 | if (index != -1) { 44 | m_interface->call(QStringLiteral("clearWakeup"), cookie); 45 | m_powerDevilCookies.removeAt(index); 46 | } 47 | } 48 | 49 | void PowerDevilWakeupProvider::wakeupCallback(int cookie) 50 | { 51 | qDebug() << "PowerDevilWakeupProvider::wakeupCallback called with cookie:" << cookie; 52 | auto index = m_powerDevilCookies.indexOf(cookie); 53 | 54 | if (index == -1) { 55 | // something must be wrong here, return and do nothing 56 | qDebug() << "Callback ignored (wrong cookie)."; 57 | } else { 58 | // remove token 59 | m_powerDevilCookies.removeAt(index); 60 | Q_EMIT wakeup(cookie); 61 | } 62 | } 63 | 64 | #include "moc_powerdevilwakeupprovider.cpp" 65 | -------------------------------------------------------------------------------- /src/kclockd/powerdevilwakeupprovider.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "abstractwakeupprovider.h" 10 | 11 | #include 12 | #include 13 | 14 | class PowerDevilWakeupProvider : public AbstractWakeupProvider 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | explicit PowerDevilWakeupProvider(QObject *parent = nullptr); 20 | 21 | int scheduleWakeup(quint64 timestamp) override; 22 | void clearWakeup(int cookie) override; 23 | 24 | public Q_SLOTS: 25 | Q_SCRIPTABLE void wakeupCallback(int cookie); 26 | 27 | private: 28 | QDBusInterface *m_interface = nullptr; 29 | QList m_powerDevilCookies; // token for PowerDevil: https://invent.kde.org/plasma/powerdevil/-/merge_requests/13 30 | }; 31 | -------------------------------------------------------------------------------- /src/kclockd/systeminterfaces/org.kde.krunner1.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 30 | 35 | 36 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 54 | 55 | 58 | 59 | 62 | 63 | 64 | 70 | 71 | 72 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/kclockd/timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * Copyright 2021 Boris Petrov 4 | * Copyright 2022 Devin Lin 5 | * 6 | * SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "timermodel.h" 12 | #include "utilities.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include 22 | #include 23 | 24 | class Timer : public QObject 25 | { 26 | Q_OBJECT 27 | Q_CLASSINFO("D-Bus Interface", "org.kde.kclock.Timer") 28 | Q_PROPERTY(int length READ length WRITE setLength NOTIFY lengthChanged) 29 | Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) 30 | Q_PROPERTY(QString commandTimeout READ commandTimeout WRITE setCommandTimeout NOTIFY commandTimeoutChanged) 31 | Q_PROPERTY(bool running READ running NOTIFY runningChanged) 32 | Q_PROPERTY(bool looping READ looping WRITE setLooping NOTIFY loopingChanged) 33 | Q_PROPERTY(bool ringing READ ringing NOTIFY ringingChanged) 34 | 35 | public: 36 | explicit Timer(int length = 0, 37 | const QString &label = QString{}, 38 | bool looping = false, 39 | const QString &commandTimeout = QString{}, 40 | bool running = false, 41 | QObject *parent = nullptr); 42 | explicit Timer(const QJsonObject &obj, QObject *parent); 43 | 44 | ~Timer(); 45 | 46 | void init(); 47 | 48 | // serialize this timer to json 49 | QJsonObject serialize(); 50 | 51 | Q_SCRIPTABLE void toggleRunning(); 52 | Q_SCRIPTABLE void toggleLooping(); 53 | Q_SCRIPTABLE void reset(); 54 | Q_SCRIPTABLE int elapsed() const; 55 | 56 | Q_SCRIPTABLE QString uuid() const; 57 | 58 | int length() const; 59 | void setLength(int length); 60 | void addMinute(); 61 | 62 | QString label() const; 63 | void setLabel(const QString &label); 64 | 65 | QString commandTimeout() const; 66 | void setCommandTimeout(const QString &commandTimeout); 67 | 68 | bool looping() const; 69 | void setLooping(bool looping); 70 | 71 | bool running() const; 72 | bool ringing() const; 73 | Q_SCRIPTABLE void dismiss(); 74 | 75 | Q_SIGNALS: 76 | Q_SCRIPTABLE void lengthChanged(); 77 | Q_SCRIPTABLE void labelChanged(); 78 | Q_SCRIPTABLE void commandTimeoutChanged(); 79 | Q_SCRIPTABLE void runningChanged(); 80 | Q_SCRIPTABLE void loopingChanged(); 81 | Q_SCRIPTABLE void ringingChanged(); 82 | 83 | private Q_SLOTS: 84 | void timeUp(int cookie); 85 | void reschedule(); 86 | 87 | private: 88 | void setRunning(bool running); 89 | void ring(); 90 | 91 | // -- properties persisted to storage: -- 92 | 93 | // the uuid of the timer 94 | QUuid m_uuid; 95 | 96 | // the total length of the timer, in seconds 97 | int m_length; 98 | 99 | // the name of the timer, can be blank 100 | QString m_label; 101 | 102 | // the command to run when the timer finishes, can be blank 103 | QString m_commandTimeout; 104 | 105 | // -- properties that are not persisted: -- 106 | 107 | // the unix timestamp (seconds) at which the timer was started 108 | int m_startTime = 0; 109 | 110 | // the time the timer elapsed till the most recent pause/stop, only updated when timer is stopped or finished 111 | int m_hasElapsed = 0; 112 | 113 | // the PowerDevil cookie used for system wakeup when the timer is supposed to go off 114 | int m_cookie = -1; 115 | 116 | // whether the timer is running 117 | bool m_running = false; 118 | 119 | // whether the timer is looping 120 | bool m_looping = false; 121 | 122 | // whether the timer is ringing 123 | bool m_ringing = false; 124 | 125 | KNotification *m_notification = 126 | new KNotification{QStringLiteral("timerFinished"), KNotification::NotificationFlag::LoopSound | KNotification::NotificationFlag::Persistent, this}; 127 | }; 128 | -------------------------------------------------------------------------------- /src/kclockd/timermodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020-2021 Devin Lin 3 | * Copyright 2021 Boris Petrov 4 | * 5 | * SPDX-License-Identifier: GPL-2.0-or-later 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | class Timer; 17 | class UnityLauncher; 18 | 19 | class KNotification; 20 | 21 | class TimerModel : public QObject 22 | { 23 | Q_OBJECT 24 | Q_CLASSINFO("D-Bus Interface", "org.kde.kclock.TimerModel") 25 | Q_PROPERTY(QString defaultAudioLocation READ defaultAudioLocation WRITE setDefaultAudioLocation NOTIFY defaultAudioLocationChanged) 26 | 27 | public: 28 | static TimerModel *instance(); 29 | 30 | void load(); 31 | void save(); 32 | QList timerList() const; 33 | Timer *timer(const QString &uuid) const; 34 | Q_SCRIPTABLE QStringList timers() const; 35 | Q_SCRIPTABLE void addTimer(int length, const QString &label, bool looping, const QString &commandTimeout, bool running); 36 | Q_SCRIPTABLE void removeTimer(const QString &uuid); 37 | 38 | QString defaultAudioLocation() const; 39 | void setDefaultAudioLocation(QString location); 40 | 41 | Q_SIGNALS: 42 | Q_SCRIPTABLE void timerAdded(const QString &); 43 | Q_SCRIPTABLE void timerRemoved(const QString &); 44 | Q_SCRIPTABLE void defaultAudioLocationChanged(); 45 | 46 | private: 47 | void connectTimer(Timer *timer); 48 | void remove(int index); 49 | void timerRunningChanged(); 50 | void updateIndicators(); 51 | 52 | void maybeCreateNotification(Timer *timer); 53 | void updateNotification(Timer *timer); 54 | void sendOrClearAllNotifications(); 55 | 56 | void onServiceRegistered(const QString &service); 57 | void onServiceUnregistered(const QString &service); 58 | void onTimerNotificationSettingChanged(); 59 | 60 | static void launchKClock(const QString &xdgActivationToken); 61 | 62 | explicit TimerModel(); 63 | 64 | QList m_timerList; 65 | 66 | QDBusServiceWatcher m_kclockWatcher; 67 | QUrl m_defaultAudioLocation; 68 | bool m_kclockRunning = false; 69 | 70 | UnityLauncher *m_unityLauncher; 71 | QHash m_notifications; 72 | QTimer m_updateIndicatorsTimer; 73 | }; 74 | -------------------------------------------------------------------------------- /src/kclockd/unitylauncher.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Kai Uwe Broulik 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | */ 6 | 7 | #include "unitylauncher.h" 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | 16 | UnityLauncher::UnityLauncher(QObject *parent) 17 | : QObject(parent) 18 | { 19 | // Clear progress initially in case previous instance didn't properly clean up (crash). 20 | setProgress(qQNaN()); 21 | } 22 | 23 | UnityLauncher::~UnityLauncher() 24 | { 25 | setProgress(qQNaN()); 26 | } 27 | 28 | void UnityLauncher::setProgress(qreal progress) 29 | { 30 | const bool oldHasProgress = !std::isnan(m_progress); 31 | const bool hasProgress = !std::isnan(progress); 32 | 33 | // NaN doesn't equal itself, hence isEmpty check below. 34 | if (m_progress == progress) { 35 | return; 36 | } 37 | 38 | QVariantMap properties; 39 | 40 | // Spec requires progress be a double. 41 | if (hasProgress) { 42 | properties.insert(QStringLiteral("progress"), double(progress)); 43 | } 44 | 45 | if (oldHasProgress != hasProgress) { 46 | properties.insert(QStringLiteral("progress-visible"), hasProgress); 47 | } 48 | 49 | if (properties.isEmpty()) { 50 | return; 51 | } 52 | 53 | QDBusMessage msg = 54 | QDBusMessage::createSignal(QStringLiteral("/org/kde/KClock"), QStringLiteral("com.canonical.Unity.LauncherEntry"), QStringLiteral("Update")); 55 | 56 | msg.setArguments({QStringLiteral("applications://org.kde.kclock"), properties}); 57 | 58 | QDBusConnection::sessionBus().send(msg); 59 | 60 | m_progress = progress; 61 | } 62 | 63 | #include "moc_unitylauncher.cpp" 64 | -------------------------------------------------------------------------------- /src/kclockd/unitylauncher.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Kai Uwe Broulik 3 | * 4 | * SPDX-License-Identifier: LGPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | class UnityLauncher : public QObject 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit UnityLauncher(QObject *parent = nullptr); 16 | ~UnityLauncher() override; 17 | 18 | void setProgress(qreal progress); 19 | 20 | private: 21 | qreal m_progress = 0; 22 | }; 23 | -------------------------------------------------------------------------------- /src/kclockd/utilities.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "alarmwaitworker.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | const QString POWERDEVIL_SERVICE_NAME = QStringLiteral("org.kde.Solid.PowerManagement"); 18 | 19 | class QTimer; 20 | class AbstractWakeupProvider; 21 | 22 | class Utilities : public QObject 23 | { 24 | Q_OBJECT 25 | Q_CLASSINFO("D-Bus Interface", "org.kde.PowerManagement") 26 | 27 | public: 28 | static void disablePowerDevil(bool disable); 29 | static Utilities &instance(); 30 | 31 | bool hasPowerDevil(); 32 | 33 | int scheduleWakeup(quint64 timestamp); 34 | void clearWakeup(int cookie); 35 | void exitAfterTimeout(); 36 | void incfActiveCount(); 37 | void decfActiveCount(); 38 | 39 | static void wakeupNow(); 40 | 41 | static void pauseMprisSources(); 42 | static void resumeMprisSources(); 43 | 44 | static QString formatDuration(const std::chrono::seconds &duration); 45 | 46 | Q_SIGNALS: 47 | void wakeup(int cookie); 48 | void needsReschedule(); 49 | 50 | public Q_SLOTS: 51 | Q_SCRIPTABLE void wakeupCallback(int cookie); 52 | Q_SCRIPTABLE void keepAlive(); 53 | Q_SCRIPTABLE void canExit(); 54 | 55 | private: 56 | explicit Utilities(QObject *parent = nullptr); 57 | 58 | void initWakeupProvider(); 59 | bool hasWakeup(); 60 | QDBusInterface *m_interface = nullptr; 61 | 62 | bool m_hasPowerDevil; 63 | std::atomic m_activeTimerCount{0}; 64 | QTimer *m_timer = nullptr; 65 | AbstractWakeupProvider *m_wakeupProvider = nullptr; 66 | 67 | // which mpris media sources were paused when the alarm/timer started ringing 68 | static QStringList m_pausedSources; 69 | }; 70 | -------------------------------------------------------------------------------- /src/kclockd/waittimerwakeupprovider.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #include "waittimerwakeupprovider.h" 8 | 9 | WaitTimerWakeupProvider::WaitTimerWakeupProvider(QObject *parent) 10 | : AbstractWakeupProvider(parent) 11 | , m_timerThread(nullptr) 12 | , m_cookie(1) 13 | , m_worker(nullptr) 14 | { 15 | initWorker(); 16 | } 17 | 18 | WaitTimerWakeupProvider::~WaitTimerWakeupProvider() 19 | { 20 | if (m_worker) { 21 | m_worker->deleteLater(); 22 | } 23 | } 24 | 25 | int WaitTimerWakeupProvider::scheduleWakeup(quint64 timestamp) 26 | { 27 | m_waitWorkerCookies.append({++m_cookie, timestamp}); 28 | schedule(); 29 | return m_cookie; 30 | } 31 | 32 | void WaitTimerWakeupProvider::clearWakeup(int cookie) 33 | { 34 | for (auto index = m_waitWorkerCookies.begin(); index < m_waitWorkerCookies.end(); ++index) { 35 | int pairCookie = (*index).first; 36 | if (cookie == pairCookie) { 37 | m_waitWorkerCookies.erase(index); 38 | } 39 | } 40 | 41 | // ensure that we schedule the next queued wakeup 42 | schedule(); 43 | } 44 | 45 | void WaitTimerWakeupProvider::schedule() 46 | { 47 | if (!m_worker) { 48 | return; 49 | } 50 | 51 | auto eternity = std::numeric_limits::max(); 52 | auto minTime = eternity; 53 | 54 | for (auto tuple : m_waitWorkerCookies) { 55 | if (minTime > tuple.second) { 56 | minTime = tuple.second; 57 | m_currentCookie = tuple.first; 58 | } 59 | } 60 | 61 | if (minTime != eternity) { // only schedule worker if we have something to wait on 62 | m_worker->setNewTime(minTime); // Unix uses int64 internally for time 63 | } 64 | } 65 | 66 | void WaitTimerWakeupProvider::initWorker() 67 | { 68 | if (!m_timerThread) { 69 | m_timerThread = new QThread(); 70 | m_worker = new AlarmWaitWorker(); 71 | m_worker->moveToThread(m_timerThread); 72 | connect(m_worker, &AlarmWaitWorker::finished, this, [this] { 73 | // notify time is up 74 | Q_EMIT wakeup(m_currentCookie); 75 | clearWakeup(m_currentCookie); 76 | }); 77 | } 78 | m_timerThread->start(); 79 | } 80 | 81 | #include "moc_waittimerwakeupprovider.cpp" 82 | -------------------------------------------------------------------------------- /src/kclockd/waittimerwakeupprovider.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Han Young 3 | * 4 | * SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include "abstractwakeupprovider.h" 10 | #include "alarmwaitworker.h" 11 | 12 | #include 13 | 14 | class WaitTimerWakeupProvider : public AbstractWakeupProvider 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | explicit WaitTimerWakeupProvider(QObject *parent = nullptr); 20 | ~WaitTimerWakeupProvider(); 21 | 22 | int scheduleWakeup(quint64 timestamp) override; 23 | void clearWakeup(int cookie) override; 24 | 25 | private: 26 | void schedule(); // For AlarmWaitWorker use 27 | void initWorker(); 28 | 29 | QList> m_waitWorkerCookies; // . For AlarmWaitWorker use 30 | QThread *m_timerThread = nullptr; 31 | int m_cookie = 1; // For AlarmWaitWorker use 32 | int m_currentCookie; // For AlarmWaitWorker use 33 | 34 | AlarmWaitWorker *m_worker = nullptr; 35 | }; 36 | -------------------------------------------------------------------------------- /src/kclockd/xdgportal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Devin Lin 3 | * SPDX-License-Identifier: GPL-2.0-or-later 4 | */ 5 | 6 | #include "xdgportal.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | void XDGPortal::requestBackground() 19 | { 20 | qDebug() << "Requesting portal to run in the background and autostart..."; 21 | 22 | // create our initial request 23 | QDBusMessage autostartMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Desktop"), 24 | QStringLiteral("/org/freedesktop/portal/desktop"), 25 | QStringLiteral("org.freedesktop.portal.Background"), 26 | QStringLiteral("RequestBackground")); 27 | 28 | // build arguments 29 | QList msgArgs; 30 | msgArgs << QString(); // parent_window - we leave blank since this is a daemon 31 | 32 | QStringList cmdLine = {{QStringLiteral("kclockd")}}; 33 | QMap msgOpts = {{QStringLiteral("handle_token"), m_handleToken}, 34 | {QStringLiteral("reason"), i18n("Allow the clock process to be in the background and launched on startup.")}, 35 | {QStringLiteral("autostart"), true}, 36 | {QStringLiteral("dbus-activatable"), true}, 37 | {QStringLiteral("commandline"), cmdLine}}; 38 | msgArgs << msgOpts; 39 | 40 | autostartMsg.setArguments(msgArgs); 41 | 42 | QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(autostartMsg); 43 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); 44 | 45 | // listen to response 46 | connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *watcher) { 47 | QDBusPendingReply reply = *watcher; 48 | 49 | if (reply.isError()) { 50 | qWarning() << "Could not get reply from org.freedesktop.portal.Background:" << reply.error().message(); 51 | } else { 52 | // get response object from org.freedesktop.portal.Request 53 | QDBusConnection::sessionBus().connect(QString(), 54 | reply.value().path(), 55 | QLatin1String("org.freedesktop.portal.Request"), 56 | QLatin1String("Response"), 57 | this, 58 | SLOT(requestBackgroundResponse(uint, QVariantMap))); 59 | } 60 | }); 61 | } 62 | 63 | void XDGPortal::requestBackgroundResponse(uint response, const QVariantMap &results) 64 | { 65 | if (!response) { 66 | qDebug() << "Request for autostart with org.freedesktop.portal.Background results:"; 67 | for (auto key : results.keys()) { 68 | qDebug() << " " << key << ":" << results[key].toString(); 69 | } 70 | 71 | // stop interaction 72 | QDBusMessage stopMsg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.portal.Request"), 73 | QStringLiteral("/org/freedesktop/portal/Request"), 74 | QStringLiteral("org.freedesktop.portal.Request"), 75 | QStringLiteral("Close")); 76 | 77 | QDBusConnection::sessionBus().call(stopMsg); 78 | } else { 79 | qWarning() << "Failed to receive response from org.freedesktop.portal.Request."; 80 | } 81 | } 82 | 83 | #include "moc_xdgportal.cpp" 84 | -------------------------------------------------------------------------------- /src/kclockd/xdgportal.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Devin Lin 3 | * SPDX-License-Identifier: GPL-2.0-or-later 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | class XDGPortal : QObject 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | void requestBackground(); 16 | 17 | private Q_SLOTS: 18 | void requestBackgroundResponse(uint response, const QVariantMap &results); 19 | 20 | private: 21 | QString m_handleToken = QStringLiteral("u1"); 22 | }; 23 | -------------------------------------------------------------------------------- /src/plasmoid/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2020 Han Young 3 | # Copyright 2020 Devin Lin 4 | # 5 | # SPDX-License-Identifier: GPL-2.0-or-later 6 | # 7 | 8 | add_subdirectory(KClock_1x2) 9 | #add_subdirectory(KClock_KWeather_3x3) 10 | -------------------------------------------------------------------------------- /src/plasmoid/KClock_1x2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # Copyright 2020 Han Young 3 | # 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | # 6 | 7 | add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.kclock_1x2\") 8 | 9 | kcoreaddons_add_plugin(org.kde.plasma.kclock_1x2 SOURCES kclock_1x2.cpp kclock_1x2.h INSTALL_NAMESPACE plasma/applets) 10 | 11 | target_link_libraries(org.kde.plasma.kclock_1x2 12 | Qt6::Gui 13 | Qt6::DBus 14 | Plasma::Plasma 15 | KF6::I18n 16 | KF6::JobWidgets 17 | KF6::KIOGui 18 | ) 19 | 20 | install(FILES kclock_plasmoid_1x2.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps/) 21 | plasma_install_package(package org.kde.plasma.kclock_1x2) 22 | -------------------------------------------------------------------------------- /src/plasmoid/KClock_1x2/kclock_1x2.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2020 HanY 3 | SPDX-FileCopyrightText: 2021 Devin Lin 4 | SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | class KClock_1x2 : public Plasma::Applet 15 | { 16 | Q_OBJECT 17 | Q_PROPERTY(QDateTime datetime READ datetime NOTIFY timeChanged) 18 | Q_PROPERTY(QString date READ date NOTIFY timeChanged) 19 | Q_PROPERTY(QString alarmTime READ alarmTime NOTIFY alarmTimeChanged) 20 | Q_PROPERTY(bool hasAlarm READ hasAlarm NOTIFY hasAlarmChanged) 21 | 22 | public: 23 | KClock_1x2(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args); 24 | ~KClock_1x2(); 25 | 26 | QString date() const; 27 | QDateTime datetime() const; 28 | QString alarmTime() const; 29 | bool hasAlarm() const; 30 | Q_INVOKABLE void openKClock(); 31 | 32 | Q_SIGNALS: 33 | void timeChanged(); 34 | void alarmTimeChanged(); 35 | void hasAlarmChanged(); 36 | 37 | private Q_SLOTS: 38 | void updateAlarm(qulonglong time); 39 | void initialTimeUpdate(); // making sure time is update when minute changes 40 | 41 | private: 42 | QLocale m_locale = QLocale::system(); 43 | bool m_hasAlarm = false; 44 | QString m_string; 45 | QTimer *m_timer; 46 | QProcess *m_process; 47 | }; 48 | -------------------------------------------------------------------------------- /src/plasmoid/KClock_1x2/kclock_plasmoid_1x2.svg.license: -------------------------------------------------------------------------------- 1 | Copyright 2020 Han Young 2 | SPDX-License-Identifier: CC-BY-4.0 3 | 4 | -------------------------------------------------------------------------------- /src/plasmoid/KClock_1x2/package/contents/config/config.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2021 Devin Lin 3 | SPDX-License-Identifier: GPL-3.0-or-later 4 | */ 5 | 6 | import QtQuick 7 | import org.kde.plasma.configuration 2.0 8 | 9 | ConfigModel { 10 | ConfigCategory { 11 | name: i18n("General") 12 | icon: "configure" 13 | source: "configGeneral.qml" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/plasmoid/KClock_1x2/package/contents/config/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | true 13 | 14 | 15 | 16 | true 17 | 18 | 19 | 20 | true 21 | 22 | 23 | 24 | Center 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/plasmoid/KClock_1x2/package/contents/ui/configGeneral.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2021 Devin Lin 3 | SPDX-License-Identifier: GPL-3.0-or-later 4 | */ 5 | 6 | import QtQuick 7 | import QtQuick.Controls 8 | import QtQuick.Layouts 9 | import org.kde.kirigami as Kirigami 10 | import org.kde.kcmutils as KCM 11 | 12 | KCM.SimpleKCM { 13 | property alias cfg_twelveHourTime: twelveHourTime.checked 14 | property alias cfg_showDate: showDate.checked 15 | property alias cfg_showAlarms: showAlarms.checked 16 | property string cfg_textAlignment 17 | 18 | Kirigami.FormLayout { 19 | CheckBox { 20 | id: twelveHourTime 21 | text: i18n("Use 12 hour time") 22 | } 23 | CheckBox { 24 | id: showDate 25 | text: i18n("Show date") 26 | } 27 | CheckBox { 28 | id: showAlarms 29 | text: i18n("Show alarms") 30 | } 31 | ComboBox { 32 | id: textAlignment 33 | Kirigami.FormData.label: i18n("Text alignment:") 34 | textRole: 'label' 35 | model: [ 36 | { 37 | 'label': i18n("Left"), 38 | 'name': "Left", 39 | }, 40 | { 41 | 'label': i18n("Center"), 42 | 'name': "Center", 43 | }, 44 | { 45 | 'label': i18n("Right"), 46 | 'name': "Right", 47 | } 48 | ] 49 | onCurrentIndexChanged: cfg_textAlignment = model[currentIndex]["name"] 50 | Component.onCompleted: { 51 | if (plasmoid.configuration.textAlignment == "") { 52 | plasmoid.configuration.textAlignment = "Center"; 53 | } 54 | for (var i = 0; i < model.length; i++) { 55 | if (model[i]["name"] === plasmoid.configuration.textAlignment) { 56 | textAlignment.currentIndex = i; 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/plasmoid/KClock_1x2/package/contents/ui/main.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2020 HanY 3 | SPDX-FileCopyrightText: 2021 Devin Lin 4 | SPDX-License-Identifier: LGPL-2.1-or-later 5 | */ 6 | 7 | import QtQml 8 | import QtQuick 9 | import QtQuick.Layouts 10 | import org.kde.plasma.core as PlasmaCore 11 | import org.kde.plasma.plasmoid 12 | import org.kde.plasma.components as PlasmaComponents 13 | import org.kde.kirigami as Kirigami 14 | 15 | PlasmoidItem { 16 | Plasmoid.status: plasmoid.hasAlarm ? PlasmaCore.Types.ActiveStatus : PlasmaCore.Types.HiddenStatus 17 | Plasmoid.backgroundHints: "ShadowBackground"; 18 | 19 | fullRepresentation: Item { 20 | property int fontSize: Math.round(mainItem.width / 5) 21 | 22 | id: mainItem 23 | Layout.preferredHeight: Kirigami.Theme.defaultFont.pointSize * 10 24 | Layout.preferredWidth: Kirigami.Settings.isMobile ? Math.round(plasmoid.screenGeometry.width * 0.5) : Kirigami.Units.gridUnit * 12 25 | Layout.alignment: Qt.AlignHCenter 26 | 27 | property var layoutAlignment: plasmoid.configuration.textAlignment == "Left" ? Qt.AlignLeft : 28 | plasmoid.configuration.textAlignment == "Right" ? Qt.AlignRight : Qt.AlignHCenter 29 | 30 | MouseArea { 31 | anchors.fill: parent 32 | onClicked: plasmoid.openKClock() 33 | } 34 | 35 | ColumnLayout { 36 | id: mainDisplay 37 | anchors.centerIn: parent 38 | spacing: 0 39 | 40 | PlasmaComponents.Label { 41 | text: Qt.formatTime(plasmoid.datetime, plasmoid.configuration.twelveHourTime ? "h:mm ap" : "h:mm") 42 | font.pointSize: fontSize 43 | font.weight: Font.ExtraLight 44 | color: "white" 45 | Layout.alignment: mainItem.layoutAlignment 46 | } 47 | 48 | PlasmaComponents.Label { 49 | text: plasmoid.date 50 | font.pointSize: Math.round(fontSize / 4) 51 | color: "white" 52 | Layout.alignment: mainItem.layoutAlignment 53 | visible: plasmoid.configuration.showDate 54 | } 55 | 56 | RowLayout { 57 | Layout.topMargin: Kirigami.Units.smallSpacing 58 | Layout.alignment: mainItem.layoutAlignment 59 | 60 | Kirigami.Icon { 61 | visible: plasmoid.hasAlarm && plasmoid.configuration.showAlarms 62 | source: "notifications" 63 | Layout.preferredHeight: Math.round(alarmTime.height) 64 | Layout.preferredWidth: Math.round(alarmTime.height) 65 | } 66 | PlasmaComponents.Label { 67 | visible: plasmoid.hasAlarm && plasmoid.configuration.showAlarms 68 | id: alarmTime 69 | text: plasmoid.alarmTime 70 | color: "white" 71 | font.pointSize: Math.round(fontSize / 4) 72 | } 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/plasmoid/KClock_1x2/package/metadata.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2020 HanY 2 | SPDX-License-Identifier: LGPL-2.1-or-later 3 | -------------------------------------------------------------------------------- /src/plasmoid/KClock_KWeather_3x3/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # Copyright 2020 Han Young 3 | # 4 | # SPDX-License-Identifier: GPL-2.0-or-later 5 | # 6 | 7 | add_definitions(-DTRANSLATION_DOMAIN=\"plasma_applet_org.kde.plasma.kclock_kweather_3x3\") 8 | 9 | kcoreaddons_add_plugin(plasma_applet_kclock_kweather_3x3 SOURCES kclock_3x3.cpp INSTALL_NAMESPACE plasma/applets) 10 | 11 | target_link_libraries(plasma_applet_kclock_kweather_3x3 12 | Qt6::Gui 13 | Qt6::DBus 14 | Plasma::Plasma 15 | KF6::I18n 16 | ) 17 | 18 | plasma_install_package(package org.kde.plasma.kclock_kweather_3x3) 19 | -------------------------------------------------------------------------------- /src/plasmoid/KClock_KWeather_3x3/kclock_kweather_3x3.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2020 HanY 3 | SPDX-License-Identifier: LGPL-2.1-or-later 4 | */ 5 | 6 | #pragma once 7 | #include 8 | #include 9 | 10 | class QProcess; 11 | class QTimer; 12 | class QDBusInterface; 13 | class KClock_KWeather_3x3 : public Plasma::Applet 14 | { 15 | Q_OBJECT 16 | Q_PROPERTY(QString time READ time NOTIFY timeChanged) 17 | Q_PROPERTY(QString date READ date NOTIFY dateChanged) 18 | Q_PROPERTY(QString alarmTime READ alarmTime NOTIFY propertyChanged) 19 | Q_PROPERTY(bool hasAlarm READ hasAlarm NOTIFY propertyChanged) 20 | Q_PROPERTY(QString cityName READ cityName NOTIFY locationChanged) 21 | Q_PROPERTY(QString description READ description NOTIFY dataUpdated) 22 | Q_PROPERTY(QString weatherIcon READ weatherIcon NOTIFY dataUpdated) 23 | Q_PROPERTY(QString tempNow READ tempNow NOTIFY dataUpdated) 24 | Q_PROPERTY(QString maxTemp READ maxTemp NOTIFY dataUpdated) 25 | Q_PROPERTY(QString minTemp READ minTemp NOTIFY dataUpdated) 26 | public: 27 | KClock_KWeather_3x3(QObject *parent, const QVariantList &args); 28 | ~KClock_KWeather_3x3(); 29 | QString time(); 30 | QString date(); 31 | QString alarmTime() 32 | { 33 | return m_string; 34 | }; 35 | bool hasAlarm() 36 | { 37 | return m_hasAlarm; 38 | }; 39 | QString cityName() 40 | { 41 | return m_cityName; 42 | }; 43 | QString description() 44 | { 45 | return m_description; 46 | }; 47 | QString weatherIcon() 48 | { 49 | return m_weatherIcon; 50 | } 51 | QString tempNow() 52 | { 53 | return m_tempNow; 54 | }; 55 | QString maxTemp() 56 | { 57 | QString arg = QString::number(m_maxTemp); 58 | if (this->m_isCelsius) 59 | arg += "°C"; 60 | else 61 | arg += "°"; 62 | return arg; 63 | }; 64 | QString minTemp() 65 | { 66 | QString arg = QString::number(m_minTemp); 67 | if (this->m_isCelsius) 68 | arg += "°C"; 69 | else 70 | arg += "°"; 71 | return arg; 72 | }; 73 | Q_INVOKABLE void openKClock(); 74 | signals: 75 | void propertyChanged(); 76 | void timeChanged(); 77 | void dataUpdated(); 78 | void locationChanged(); 79 | void dateChanged(); 80 | private slots: 81 | void updateAlarm(qulonglong time); 82 | void initialTimeUpdate(); // align time to minute mark 83 | void updateTime(); 84 | 85 | private: 86 | void parse(QJsonDocument); 87 | 88 | QDBusInterface *m_KWeatherInterface, *m_KClockInterface; 89 | 90 | QString m_cityName; 91 | QString m_weatherIcon; 92 | QString m_description; 93 | QString m_tempNow; 94 | QString m_location; 95 | int m_maxTemp, m_minTemp; 96 | bool m_isCelsius; 97 | 98 | int m_minutesCounter; // count how many minutes have passed to track day change 99 | 100 | QLocale m_local = QLocale::system(); 101 | bool m_hasAlarm = false; 102 | QString m_string; 103 | QTimer *m_timer; 104 | QProcess *m_process; 105 | }; 106 | -------------------------------------------------------------------------------- /src/plasmoid/KClock_KWeather_3x3/package/metadata.json.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2020 HanY 2 | SPDX-License-Identifier: LGPL-2.1-or-later 3 | --------------------------------------------------------------------------------