├── .git-blame-ignore-revs ├── .gitignore ├── .gitlab-ci.yml ├── .kde-ci.yml ├── AUTHORS ├── CMakeLists.txt ├── CMakePresets.json ├── ChangeLog ├── LICENSES ├── CC0-1.0.txt └── GPL-2.0-or-later.txt ├── Messages.sh ├── README ├── TODO ├── doc ├── CMakeLists.txt └── kcontrol │ ├── CMakeLists.txt │ ├── index.docbook │ ├── kcronstart.png │ ├── newtask.png │ └── newvariable.png ├── logo.png ├── org.kde.kcron.metainfo.xml ├── po ├── af │ └── kcron.po ├── ar │ └── kcron.po ├── ast │ └── kcron.po ├── be │ └── kcron.po ├── bg │ └── kcron.po ├── br │ └── kcron.po ├── bs │ └── kcron.po ├── ca │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ └── index.docbook │ └── kcron.po ├── ca@valencia │ └── kcron.po ├── cs │ └── kcron.po ├── cy │ └── kcron.po ├── da │ └── kcron.po ├── de │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ ├── index.docbook │ │ │ ├── kcronstart.png │ │ │ ├── newtask.png │ │ │ └── newvariable.png │ └── kcron.po ├── el │ └── kcron.po ├── en_GB │ └── kcron.po ├── eo │ └── kcron.po ├── es │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ ├── index.docbook │ │ │ └── print.png │ └── kcron.po ├── et │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ └── index.docbook │ └── kcron.po ├── eu │ └── kcron.po ├── fa │ └── kcron.po ├── fi │ └── kcron.po ├── fr │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ ├── index.docbook │ │ │ ├── kcron.png │ │ │ ├── kcronstart.png │ │ │ ├── newtask.png │ │ │ ├── newvariable.png │ │ │ └── print.png │ └── kcron.po ├── ga │ └── kcron.po ├── gl │ └── kcron.po ├── he │ └── kcron.po ├── hi │ └── kcron.po ├── hne │ └── kcron.po ├── hr │ └── kcron.po ├── hu │ └── kcron.po ├── ia │ └── kcron.po ├── is │ └── kcron.po ├── it │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ └── index.docbook │ └── kcron.po ├── ja │ └── kcron.po ├── ka │ └── kcron.po ├── kk │ └── kcron.po ├── km │ └── kcron.po ├── ko │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ └── index.docbook │ └── kcron.po ├── ku │ └── kcron.po ├── lt │ └── kcron.po ├── lv │ └── kcron.po ├── mk │ └── kcron.po ├── mr │ └── kcron.po ├── ms │ └── kcron.po ├── nb │ └── kcron.po ├── nds │ └── kcron.po ├── ne │ └── kcron.po ├── nl │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ ├── index.docbook │ │ │ ├── newtask.png │ │ │ ├── newvariable.png │ │ │ └── print.png │ └── kcron.po ├── nn │ └── kcron.po ├── oc │ └── kcron.po ├── pa │ └── kcron.po ├── pl │ └── kcron.po ├── pt │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ ├── index.docbook │ │ │ ├── kcron.png │ │ │ ├── kcronstart.png │ │ │ ├── newtask.png │ │ │ ├── newvariable.png │ │ │ └── print.png │ └── kcron.po ├── pt_BR │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ ├── index.docbook │ │ │ ├── kcronstart.png │ │ │ ├── newtask.png │ │ │ └── newvariable.png │ └── kcron.po ├── ro │ └── kcron.po ├── ru │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ └── index.docbook │ └── kcron.po ├── sa │ └── kcron.po ├── se │ └── kcron.po ├── sk │ └── kcron.po ├── sl │ └── kcron.po ├── sq │ └── kcron.po ├── sr │ └── kcron.po ├── sr@ijekavian │ └── kcron.po ├── sr@ijekavianlatin │ └── kcron.po ├── sr@latin │ └── kcron.po ├── sv │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ ├── index.docbook │ │ │ ├── kcronstart.png │ │ │ ├── newtask.png │ │ │ └── newvariable.png │ └── kcron.po ├── ta │ └── kcron.po ├── tg │ └── kcron.po ├── th │ └── kcron.po ├── tr │ └── kcron.po ├── ug │ └── kcron.po ├── uk │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ ├── index.docbook │ │ │ ├── kcronstart.png │ │ │ ├── newtask.png │ │ │ └── newvariable.png │ └── kcron.po ├── vi │ └── kcron.po ├── wa │ └── kcron.po ├── xh │ └── kcron.po ├── zh_CN │ ├── docs │ │ └── kcontrol │ │ │ └── kcron │ │ │ └── index.docbook │ └── kcron.po ├── zh_HK │ └── kcron.po └── zh_TW │ └── kcron.po └── src ├── CMakeLists.txt ├── cronPrinter.cpp ├── cronPrinter.h ├── crontablib ├── ctHelper.cpp ├── ctHelper.h ├── ctInitializationError.cpp ├── ctInitializationError.h ├── ctSaveStatus.cpp ├── ctSaveStatus.h ├── ctSystemCron.cpp ├── ctSystemCron.h ├── ctcron.cpp ├── ctcron.h ├── ctdom.cpp ├── ctdom.h ├── ctdow.cpp ├── ctdow.h ├── cthost.cpp ├── cthost.h ├── cthour.cpp ├── cthour.h ├── ctminute.cpp ├── ctminute.h ├── ctmonth.cpp ├── ctmonth.h ├── cttask.cpp ├── cttask.h ├── ctunit.cpp ├── ctunit.h ├── ctvariable.cpp └── ctvariable.h ├── genericmodel.cpp ├── genericmodel.h ├── helper ├── CMakeLists.txt ├── README ├── kcronhelper.cpp ├── kcronhelper.h └── local.kcron.crontab.actions ├── kcmCron.cpp ├── kcmCron.h ├── kcm_cron.json ├── task.cpp ├── task.h ├── tasksmodel.cpp ├── tasksmodel.h ├── taskvalidator.cpp ├── taskvalidator.h ├── ui ├── CheckedModel.qml ├── Onboarding.qml ├── Table.qml ├── TaskPage.qml ├── TasksComponent.qml ├── TimeCard.qml ├── VariablePage.qml ├── VariablesComponent.qml └── main.qml ├── variable.cpp ├── variable.h ├── variablesmodel.cpp └── variablesmodel.h /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # clang-format 2 | a1b6a3b54631559be5470a49a6e62ad8ad76597d 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the following files 2 | *~ 3 | *.[oa] 4 | *.diff 5 | *.kate-swp 6 | *.kdev4 7 | .kdev_include_paths 8 | *.kdevelop.pcs 9 | *.moc 10 | *.moc.cpp 11 | *.orig 12 | *.user 13 | .directory 14 | .*.swp 15 | .swp.* 16 | Doxyfile 17 | Makefile 18 | avail 19 | random_seed 20 | /build*/ 21 | CMakeLists.txt.user* 22 | *.unc-backup* 23 | .cmake/ 24 | .clang-format 25 | .cache/* 26 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: None 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | include: 5 | - project: sysadmin/ci-utilities 6 | file: 7 | - /gitlab-templates/linux-qt6.yml 8 | - /gitlab-templates/linux-qt6-next.yml 9 | - /gitlab-templates/freebsd-qt6.yml 10 | - /gitlab-templates/clang-format.yml 11 | 12 | - /gitlab-templates/xml-lint.yml 13 | - /gitlab-templates/yaml-lint.yml 14 | -------------------------------------------------------------------------------- /.kde-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: None 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | Dependencies: 5 | - 'on': ['@all'] 6 | 'require': 7 | 'frameworks/extra-cmake-modules': '@latest-kf6' 8 | 'frameworks/kauth': '@latest-kf6' 9 | 'frameworks/kcmutils': '@latest-kf6' 10 | 'frameworks/kconfigwidgets': '@latest-kf6' 11 | 'frameworks/kcoreaddons': '@latest-kf6' 12 | 'frameworks/kdoctools': '@latest-kf6' 13 | 'frameworks/ki18n': '@latest-kf6' 14 | 'libraries/kirigami-addons': '@latest-kf6' 15 | 16 | 17 | Options: 18 | require-passing-tests-on: ['Linux', 'FreeBSD'] 19 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Gary Meyer 2 | Robert Berry 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.16.0 FATAL_ERROR) 2 | project(kcron) 3 | 4 | set (QT_MIN_VERSION "6.8.0") 5 | set (KF_MIN_VERSION "6.11.0") 6 | 7 | find_package (ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE) 8 | set (CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) 9 | 10 | add_definitions(-DTRANSLATION_DOMAIN="kcron") 11 | 12 | include(KDEInstallDirs) 13 | include(KDECompilerSettings NO_POLICY_SCOPE) 14 | include(KDECMakeSettings) 15 | include(ECMQtDeclareLoggingCategory) 16 | include(KDEGitCommitHooks) 17 | include(KDEClangFormat) 18 | include(ECMDeprecationSettings) 19 | file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.c) 20 | kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) 21 | 22 | find_package (Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS 23 | Core 24 | PrintSupport 25 | Quick 26 | ) 27 | 28 | find_package(KF6 REQUIRED COMPONENTS 29 | ConfigWidgets 30 | CoreAddons 31 | Auth 32 | I18n 33 | KCMUtils 34 | ) 35 | 36 | find_package(KF6KirigamiAddons 0.11.76 REQUIRED) 37 | 38 | find_package(KF6DocTools ${KF_MIN_VERSION}) 39 | set_package_properties(KF6DocTools PROPERTIES DESCRIPTION 40 | "Tools to generate documentation" 41 | TYPE OPTIONAL 42 | ) 43 | 44 | ecm_set_disabled_deprecation_versions(QT 6.9.0 45 | KF 6.13 46 | ) 47 | 48 | add_subdirectory(src) 49 | 50 | install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.kcron.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) 51 | 52 | ecm_qt_install_logging_categories( 53 | EXPORT KCRON 54 | FILE kcron.categories 55 | DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} 56 | ) 57 | 58 | kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) 59 | ki18n_install(po) 60 | if (KF6DocTools_FOUND) 61 | kdoctools_install(po) 62 | add_subdirectory(doc) 63 | endif() 64 | 65 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 66 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "configurePresets": [ 4 | { 5 | "name": "base", 6 | "displayName": "base preset", 7 | "generator": "Ninja", 8 | "binaryDir": "${sourceDir}/build-${presetName}", 9 | "installDir": "$env{KF6}", 10 | "hidden": true, 11 | "cacheVariables": { 12 | "BUILD_QCH": "ON" 13 | } 14 | }, 15 | { 16 | "name": "dev-mold", 17 | "displayName": "Build as debug + using mold linker", 18 | "cacheVariables": { 19 | "CMAKE_BUILD_TYPE": "Debug", 20 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON", 21 | "CMAKE_SHARED_LINKER_FLAGS": "-fuse-ld=mold" 22 | }, 23 | "inherits": [ 24 | "base" 25 | ] 26 | }, 27 | { 28 | "name": "dev", 29 | "displayName": "Build as debug", 30 | "cacheVariables": { 31 | "CMAKE_BUILD_TYPE": "Debug", 32 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" 33 | }, 34 | "inherits": [ 35 | "base" 36 | ] 37 | }, 38 | { 39 | "name": "asan", 40 | "displayName": "Build with Asan support.", 41 | "cacheVariables": { 42 | "CMAKE_BUILD_TYPE": "Debug", 43 | "ECM_ENABLE_SANITIZERS" : "'address;undefined'" 44 | }, 45 | "inherits": [ 46 | "base" 47 | ] 48 | }, 49 | { 50 | "name": "dev-clang", 51 | "displayName": "dev-clang", 52 | "cacheVariables": { 53 | "CMAKE_BUILD_TYPE": "Debug", 54 | "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" 55 | }, 56 | "environment": { 57 | "CXX": "clang++", 58 | "CCACHE_DISABLE": "ON" 59 | }, 60 | "inherits": [ 61 | "base" 62 | ] 63 | }, 64 | { 65 | "name": "unity", 66 | "displayName": "Build with CMake unity support.", 67 | "cacheVariables": { 68 | "CMAKE_BUILD_TYPE": "Debug", 69 | "USE_UNITY_CMAKE_SUPPORT": "ON" 70 | }, 71 | "inherits": [ 72 | "base" 73 | ] 74 | }, 75 | { 76 | "name": "release", 77 | "displayName": "Build as release mode.", 78 | "cacheVariables": { 79 | "CMAKE_BUILD_TYPE": "Release", 80 | "BUILD_TESTING": "OFF" 81 | }, 82 | "inherits": [ 83 | "base" 84 | ] 85 | }, 86 | { 87 | "name": "profile", 88 | "displayName": "profile", 89 | "cacheVariables": { 90 | "CMAKE_BUILD_TYPE": "RelWithDebInfo" 91 | }, 92 | "inherits": [ 93 | "base" 94 | ] 95 | }, 96 | { 97 | "name": "coverage", 98 | "displayName": "coverage", 99 | "cacheVariables": { 100 | "CMAKE_BUILD_TYPE": "Debug", 101 | "USE_UNITY_CMAKE_SUPPORT": "OFF", 102 | "BUILD_COVERAGE": "ON" 103 | }, 104 | "inherits": [ 105 | "base" 106 | ] 107 | }, 108 | { 109 | "name": "clazy", 110 | "displayName": "clazy", 111 | "cacheVariables": { 112 | "CMAKE_BUILD_TYPE": "Debug" 113 | }, 114 | "environment": { 115 | "CXX": "clazy", 116 | "CCACHE_DISABLE": "ON" 117 | }, 118 | "inherits": [ 119 | "base" 120 | ] 121 | }, 122 | { 123 | "name": "pch", 124 | "displayName": "pch", 125 | "cacheVariables": { 126 | "CMAKE_BUILD_TYPE": "Debug", 127 | "USE_PRECOMPILED_HEADERS": "ON", 128 | "BUILD_COVERAGE": "ON" 129 | }, 130 | "inherits": [ 131 | "base" 132 | ] 133 | } 134 | ], 135 | "buildPresets": [ 136 | { 137 | "name": "dev", 138 | "configurePreset": "dev" 139 | }, 140 | { 141 | "name": "dev-mold", 142 | "configurePreset": "dev-mold" 143 | }, 144 | { 145 | "name": "dev-clang", 146 | "configurePreset": "dev-clang" 147 | }, 148 | { 149 | "name": "pch", 150 | "configurePreset": "pch" 151 | }, 152 | { 153 | "name": "release", 154 | "configurePreset": "release" 155 | }, 156 | { 157 | "name": "unity", 158 | "configurePreset": "unity" 159 | }, 160 | { 161 | "name": "coverage", 162 | "configurePreset": "coverage" 163 | }, 164 | { 165 | "name": "asan", 166 | "configurePreset": "asan" 167 | }, 168 | { 169 | "name": "clazy", 170 | "configurePreset": "clazy", 171 | "environment": { 172 | "CLAZY_CHECKS" : "level0,level1,detaching-member,ifndef-define-typo,isempty-vs-count,qrequiredresult-candidates,reserve-candidates,signal-with-return-value,unneeded-cast,function-args-by-ref,function-args-by-value,returning-void-expression,no-ctor-missing-parent-argument,isempty-vs-count,qhash-with-char-pointer-key,raw-environment-function,qproperty-type-mismatch,old-style-connect,qstring-allocations,container-inside-loop,heap-allocated-small-trivial-type,inefficient-qlist,qstring-varargs,level2,detaching-member,heap-allocated-small-trivial-type,isempty-vs-count,qstring-varargs,qvariant-template-instantiation,raw-environment-function,reserve-candidates,signal-with-return-value,thread-with-slots,no-ctor-missing-parent-argument,no-missing-typeinfo", 173 | "CCACHE_DISABLE" : "ON" 174 | } 175 | } 176 | ], 177 | "testPresets": [ 178 | { 179 | "name": "dev", 180 | "configurePreset": "dev", 181 | "output": {"outputOnFailure": true}, 182 | "execution": {"noTestsAction": "error", "stopOnFailure": false} 183 | }, 184 | { 185 | "name": "asan", 186 | "configurePreset": "asan", 187 | "output": {"outputOnFailure": true}, 188 | "execution": {"noTestsAction": "error", "stopOnFailure": true} 189 | }, 190 | { 191 | "name": "unity", 192 | "configurePreset": "unity", 193 | "output": {"outputOnFailure": true}, 194 | "execution": {"noTestsAction": "error", "stopOnFailure": true} 195 | }, 196 | { 197 | "name": "coverage", 198 | "configurePreset": "coverage", 199 | "output": {"outputOnFailure": true}, 200 | "execution": {"noTestsAction": "error", "stopOnFailure": true} 201 | } 202 | ] 203 | } 204 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | nicolas.ternisien@gmail.com February 27, 1999 2 | Major rewrite of KCron. 3 | 4 | gary@meyer.net September 6, 1999 5 | 6 | Fixed bug. Program didn't exit after editing. KDE2 only. 7 | Fixed bug. Variable editor didn't update icon. KDE2 only. 8 | Changed default sorting. Alphabetical with System Crontab on top. 9 | Removed -lkfm from Makefile. 10 | Change kcron.kdelnk to kcron.desktop. 11 | 12 | gary@meyer.net September 7, 1999 13 | 14 | cttask.h, cttash.cpp, kttask.cpp 15 | Fixed bug. New syscron task didn't let user enter "run as" field. 16 | 17 | gary@meyer.net September 10, 1999 18 | 19 | Initial implementation of cut/copy/paste. 20 | 21 | gary@meyer.net September 17, 1999 22 | 23 | Work around for Qt 2.0 ~QListViewItem bug. 24 | 25 | gary@meyer.net November 1, 1999 26 | 27 | Released 0.5. 28 | 29 | gary@meyer.net November 7, 1999 30 | 31 | Added KCron handbook. Various bug fixes. 32 | 33 | gary@meyer.net November 15, 1999 34 | 35 | Fixed bug for supporting strftime (in CTTask::describe()) on different 36 | platforms. 37 | 38 | gary@meyer.net November 19, 1999 39 | 40 | Code clean up effort. 41 | 42 | gary@meyer.net November 29, 1999 43 | 44 | Removed dependency on langinfo.h for internationalization of days of week 45 | and months. Not broadly supported across platforms/distributions. 46 | 47 | gary@meyer.net December 2, 1999 48 | 49 | Removed CTDebug, don't really need anymore. 50 | Addeed note for translators so they'll look at README.translators. 51 | Cleaned up CTUnit: moved implementation to cpp file, removed macro, 52 | added support for "sun, Mon" and "jan, FEB" in crontab file. 53 | 54 | code@jamesots.com January 27, 2004 55 | 56 | * Fixed bug. If only one hour wasn't checked '*' was still being used. 57 | * Fixed bug. Removed autoWrap from KTVariable, as text is wrapped 58 | in the line edit box anyway. Changed to use QTextEdit instead of the 59 | deprecated QMultiLineEdit. 60 | * Fixed bug. Strip out newlines from variable comments, because we 61 | only read one line of comment from crontab for each entry. 62 | * Fixed bug. System Crontab uses correct icon. 63 | * Using standard icons for everything now. 64 | * Added Set/Clear All buttons. 65 | * Added New, Modify and Delete buttons on toolbar. (Bug 54399 and 66 | part of bug 55684. Note about 55684: Use Ctrl-X as shortcut to delete 67 | item.) 68 | * Enabled New action when variables and tasks are selected, instead of 69 | only when the containing label is selected. 70 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES 4 | NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE 5 | AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION 6 | ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE 7 | OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS 8 | LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION 9 | OR WORKS PROVIDED HEREUNDER. 10 | 11 | Statement of Purpose 12 | 13 | The laws of most jurisdictions throughout the world automatically confer exclusive 14 | Copyright and Related Rights (defined below) upon the creator and subsequent 15 | owner(s) (each and all, an "owner") of an original work of authorship and/or 16 | a database (each, a "Work"). 17 | 18 | Certain owners wish to permanently relinquish those rights to a Work for the 19 | purpose of contributing to a commons of creative, cultural and scientific 20 | works ("Commons") that the public can reliably and without fear of later claims 21 | of infringement build upon, modify, incorporate in other works, reuse and 22 | redistribute as freely as possible in any form whatsoever and for any purposes, 23 | including without limitation commercial purposes. These owners may contribute 24 | to the Commons to promote the ideal of a free culture and the further production 25 | of creative, cultural and scientific works, or to gain reputation or greater 26 | distribution for their Work in part through the use and efforts of others. 27 | 28 | For these and/or other purposes and motivations, and without any expectation 29 | of additional consideration or compensation, the person associating CC0 with 30 | a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 31 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 32 | and publicly distribute the Work under its terms, with knowledge of his or 33 | her Copyright and Related Rights in the Work and the meaning and intended 34 | legal effect of CC0 on those rights. 35 | 36 | 1. Copyright and Related Rights. A Work made available under CC0 may be protected 37 | by copyright and related or neighboring rights ("Copyright and Related Rights"). 38 | Copyright and Related Rights include, but are not limited to, the following: 39 | 40 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 41 | and translate a Work; 42 | 43 | ii. moral rights retained by the original author(s) and/or performer(s); 44 | 45 | iii. publicity and privacy rights pertaining to a person's image or likeness 46 | depicted in a Work; 47 | 48 | iv. rights protecting against unfair competition in regards to a Work, subject 49 | to the limitations in paragraph 4(a), below; 50 | 51 | v. rights protecting the extraction, dissemination, use and reuse of data 52 | in a Work; 53 | 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal protection 56 | of databases, and under any national implementation thereof, including any 57 | amended or successor version of such directive); and 58 | 59 | vii. other similar, equivalent or corresponding rights throughout the world 60 | based on applicable law or treaty, and any national implementations thereof. 61 | 62 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 63 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 64 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 65 | and Related Rights and associated claims and causes of action, whether now 66 | known or unknown (including existing as well as future claims and causes of 67 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 68 | duration provided by applicable law or treaty (including future time extensions), 69 | (iii) in any current or future medium and for any number of copies, and (iv) 70 | for any purpose whatsoever, including without limitation commercial, advertising 71 | or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the 72 | benefit of each member of the public at large and to the detriment of Affirmer's 73 | heirs and successors, fully intending that such Waiver shall not be subject 74 | to revocation, rescission, cancellation, termination, or any other legal or 75 | equitable action to disrupt the quiet enjoyment of the Work by the public 76 | as contemplated by Affirmer's express Statement of Purpose. 77 | 78 | 3. Public License Fallback. Should any part of the Waiver for any reason be 79 | judged legally invalid or ineffective under applicable law, then the Waiver 80 | shall be preserved to the maximum extent permitted taking into account Affirmer's 81 | express Statement of Purpose. In addition, to the extent the Waiver is so 82 | judged Affirmer hereby grants to each affected person a royalty-free, non 83 | transferable, non sublicensable, non exclusive, irrevocable and unconditional 84 | license to exercise Affirmer's Copyright and Related Rights in the Work (i) 85 | in all territories worldwide, (ii) for the maximum duration provided by applicable 86 | law or treaty (including future time extensions), (iii) in any current or 87 | future medium and for any number of copies, and (iv) for any purpose whatsoever, 88 | including without limitation commercial, advertising or promotional purposes 89 | (the "License"). The License shall be deemed effective as of the date CC0 90 | was applied by Affirmer to the Work. Should any part of the License for any 91 | reason be judged legally invalid or ineffective under applicable law, such 92 | partial invalidity or ineffectiveness shall not invalidate the remainder of 93 | the License, and in such case Affirmer hereby affirms that he or she will 94 | not (i) exercise any of his or her remaining Copyright and Related Rights 95 | in the Work or (ii) assert any associated claims and causes of action with 96 | respect to the Work, in either case contrary to Affirmer's express Statement 97 | of Purpose. 98 | 99 | 4. Limitations and Disclaimers. 100 | 101 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, 102 | licensed or otherwise affected by this document. 103 | 104 | b. Affirmer offers the Work as-is and makes no representations or warranties 105 | of any kind concerning the Work, express, implied, statutory or otherwise, 106 | including without limitation warranties of title, merchantability, fitness 107 | for a particular purpose, non infringement, or the absence of latent or other 108 | defects, accuracy, or the present or absence of errors, whether or not discoverable, 109 | all to the greatest extent permissible under applicable law. 110 | 111 | c. Affirmer disclaims responsibility for clearing rights of other persons 112 | that may apply to the Work or any use thereof, including without limitation 113 | any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims 114 | responsibility for obtaining any necessary consents, permissions or other 115 | rights required for any use of the Work. 116 | 117 | d. Affirmer understands and acknowledges that Creative Commons is not a party 118 | to this document and has no duty or obligation with respect to this CC0 or 119 | use of the Work. 120 | -------------------------------------------------------------------------------- /Messages.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | $XGETTEXT `find . -name \*.qml -o -name "*.cpp"` -o $podir/kcron.pot 3 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | KCron 2 | 3 | KDE Task Scheduler 4 | 5 | GUI crontab editor 6 | 7 | Requires: 8 | 9 | Unix POSIX libraries for localized dates and times (glibc) 10 | Cron (vixie-cron) 11 | Crontab (crontabs) 12 | 13 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | Ideas for new features : 3 | 4 | - Add a way to see next tasks scheduling in KOrganizer 5 | - Support for at command (schedule a one shot task in the future, and remove it after execute it) 6 | -------------------------------------------------------------------------------- /doc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(kcontrol) 2 | -------------------------------------------------------------------------------- /doc/kcontrol/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ########### install files ############### 2 | 3 | kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kcontrol/kcron) 4 | -------------------------------------------------------------------------------- /doc/kcontrol/kcronstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/doc/kcontrol/kcronstart.png -------------------------------------------------------------------------------- /doc/kcontrol/newtask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/doc/kcontrol/newtask.png -------------------------------------------------------------------------------- /doc/kcontrol/newvariable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/doc/kcontrol/newvariable.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/logo.png -------------------------------------------------------------------------------- /po/de/docs/kcontrol/kcron/kcronstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/de/docs/kcontrol/kcron/kcronstart.png -------------------------------------------------------------------------------- /po/de/docs/kcontrol/kcron/newtask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/de/docs/kcontrol/kcron/newtask.png -------------------------------------------------------------------------------- /po/de/docs/kcontrol/kcron/newvariable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/de/docs/kcontrol/kcron/newvariable.png -------------------------------------------------------------------------------- /po/es/docs/kcontrol/kcron/print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/es/docs/kcontrol/kcron/print.png -------------------------------------------------------------------------------- /po/fr/docs/kcontrol/kcron/kcron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/fr/docs/kcontrol/kcron/kcron.png -------------------------------------------------------------------------------- /po/fr/docs/kcontrol/kcron/kcronstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/fr/docs/kcontrol/kcron/kcronstart.png -------------------------------------------------------------------------------- /po/fr/docs/kcontrol/kcron/newtask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/fr/docs/kcontrol/kcron/newtask.png -------------------------------------------------------------------------------- /po/fr/docs/kcontrol/kcron/newvariable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/fr/docs/kcontrol/kcron/newvariable.png -------------------------------------------------------------------------------- /po/fr/docs/kcontrol/kcron/print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/fr/docs/kcontrol/kcron/print.png -------------------------------------------------------------------------------- /po/nl/docs/kcontrol/kcron/newtask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/nl/docs/kcontrol/kcron/newtask.png -------------------------------------------------------------------------------- /po/nl/docs/kcontrol/kcron/newvariable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/nl/docs/kcontrol/kcron/newvariable.png -------------------------------------------------------------------------------- /po/nl/docs/kcontrol/kcron/print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/nl/docs/kcontrol/kcron/print.png -------------------------------------------------------------------------------- /po/pt/docs/kcontrol/kcron/kcron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/pt/docs/kcontrol/kcron/kcron.png -------------------------------------------------------------------------------- /po/pt/docs/kcontrol/kcron/kcronstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/pt/docs/kcontrol/kcron/kcronstart.png -------------------------------------------------------------------------------- /po/pt/docs/kcontrol/kcron/newtask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/pt/docs/kcontrol/kcron/newtask.png -------------------------------------------------------------------------------- /po/pt/docs/kcontrol/kcron/newvariable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/pt/docs/kcontrol/kcron/newvariable.png -------------------------------------------------------------------------------- /po/pt/docs/kcontrol/kcron/print.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/pt/docs/kcontrol/kcron/print.png -------------------------------------------------------------------------------- /po/pt_BR/docs/kcontrol/kcron/kcronstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/pt_BR/docs/kcontrol/kcron/kcronstart.png -------------------------------------------------------------------------------- /po/pt_BR/docs/kcontrol/kcron/newtask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/pt_BR/docs/kcontrol/kcron/newtask.png -------------------------------------------------------------------------------- /po/pt_BR/docs/kcontrol/kcron/newvariable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/pt_BR/docs/kcontrol/kcron/newvariable.png -------------------------------------------------------------------------------- /po/sv/docs/kcontrol/kcron/kcronstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/sv/docs/kcontrol/kcron/kcronstart.png -------------------------------------------------------------------------------- /po/sv/docs/kcontrol/kcron/newtask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/sv/docs/kcontrol/kcron/newtask.png -------------------------------------------------------------------------------- /po/sv/docs/kcontrol/kcron/newvariable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/sv/docs/kcontrol/kcron/newvariable.png -------------------------------------------------------------------------------- /po/uk/docs/kcontrol/kcron/kcronstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/uk/docs/kcontrol/kcron/kcronstart.png -------------------------------------------------------------------------------- /po/uk/docs/kcontrol/kcron/newtask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/uk/docs/kcontrol/kcron/newtask.png -------------------------------------------------------------------------------- /po/uk/docs/kcontrol/kcron/newvariable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/kcron/629a9b4aecbd8e56a8870b29b21c6e7ddc10de48/po/uk/docs/kcontrol/kcron/newvariable.png -------------------------------------------------------------------------------- /po/zh_CN/docs/kcontrol/kcron/index.docbook: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | ]> 7 | 8 |
9 | 任务调度器 11 | 12 | 13 | 14 | Morgan N. Sandquist
&Morgan.N.Sandquist.mail;
24 |
25 |
26 | 27 | Gary Meyer
&Gary.Meyer.mail;
35 |
36 | 开发者 38 |
39 | 40 | Lauri Watts
&Lauri.Watts.mail;
48 |
49 | 审阅者 51 |
52 | 53 | Funda WangBoyuanYang 66 | 67 |
68 | 69 | 70 | 2000 72 | &Morgan.N.Sandquist; 74 | 75 | 76 | 2017-01-12 78 | 应用版本 16.12 80 | 81 | 82 | KDE 84 | cron 86 | crontab 88 | scheduler,计划 90 | 91 | 92 |
93 | 94 | 95 | 简介 97 | 98 | 本模块用来调度在后台运行的程序。它是cron(&UNIX;系统调度器)的一个图形用户界面。 102 | 103 | 不要忘记先让系统启动crond即 cron 守护进程,否则本模块的设置将不会起作用。 109 | 110 | 111 | 启动界面 113 | 114 | 当您启动该模块后,您将可以看到一个显示已有的计划任务和与其相关联的环境变量的简要界面。如果您以 root 用户身份启动,您可以看到计算机上所有用户以及系统的计划任务。 116 | 117 | 118 | 启动界面 120 | 121 | 122 | 123 | 启动界面 127 | 128 | 129 | 130 | 131 | 计划任务 133 | 134 | 计划任务在任务列表中显示。对每一个计划任务,下列项目将会显示: 138 | 139 | 140 | 141 | 142 | 143 | 计划 147 | 该列可能显示“@reboot”字段以表示该任务在启动时进行,或是显示对应的 crontab 记录。 151 | 152 | 153 | 命令 157 | 程序文件及其参数。 161 | 162 | 164 | 状态 168 | 已启用或已禁用。 172 | 174 | 176 | 描述 180 | 对任务的描述,比如该任务的目的。 184 | 186 | 187 | 计划细节 191 | 计划任务的自然语言描述。 195 | 196 | 197 | 198 | 199 | 200 | 201 | 环境变量 203 | 204 | 环境变量显示在环境变量列表中。对每个环境变量,下列项目将会显示: 208 | 209 | 210 | 211 | 变量 215 | 变量名称。 219 | 220 | 221 | 225 | 变量的值。 229 | 230 | 231 | 状态 235 | 已启用或已禁用。 239 | 240 | 241 | 注释 245 | 变量的自然语言描述。 249 | 250 | 251 | 252 | 此处显示的环境变量将会覆盖所有计划任务任何先前已有的环境变量。 254 | 255 | 256 | 257 | 258 | 259 | 添加计划任务 261 | 262 | 要创建新计划任务,点击新建任务...按钮。除此之外,您还可以从鼠标右键上下文菜单中选择该动作。 268 | 269 | 270 | 任务编辑对话框 272 | 273 | 274 | 编辑任务对话框。 278 | 279 | 281 | 编辑任务对话框 287 | 288 | 289 | 290 | 291 | 292 | 命令 296 | 输入程序名称。您可以使用绝对路径或者相对路径。如果您需要寻找某个程序,请点击浏览图标。 301 | 303 | 304 | 305 | 注释 309 | 输入计划任务的描述。 312 | 314 | 315 | 316 | 启用此任务 320 | 要启用或者禁用任务,请选中或者取消选中该项。 323 | 324 | 325 | 326 | 327 | 在系统启动时运行 331 | 332 | 选中该项以使该任务在系统启动时运行。 334 | 335 | 336 | 337 | 338 | 每天运行 342 | 343 | 如果您想让该任务每天运行,请选中该项。 345 | 346 | 347 | 348 | 349 | 月份 353 | 选择该任务计划运行的月份。 356 | 357 | 358 | 359 | 日期 363 | 选择要计划任务的月份中的日期。 366 | 368 | 369 | 370 | 星期中的日 374 | 选择该任务计划运行的星期中的日子。 377 | 379 | 380 | 381 | 小时 385 | 选择该任务计划运行的小时数。 389 | 390 | 391 | 分钟 395 | 选择该任务计划运行的分钟数。cron不支持设置小于一分钟的计划任务时间间隔。 400 | 401 | 402 | 403 | 确定 407 | 完成任务的创建。 410 | 411 | 412 | 413 | 取消 417 | 取消任务的创建。 420 | 421 | 422 | 423 | 424 | 如果您既选择了一月中的天数,又选择了一周中的天数,任务在其中任何一个条件满足时都会运行。例如,如果您选择了每月的第一天和第十五天,又选择了星期日,则程序会在每个选定月份的第一天和第十五天运行(无论那天是星期几),也会在选定月份的每个星期日运行(无论那天是每月的第几天)。 426 | 427 | 所计划的任务不会实际生效,直到crontab得到保存。 431 | 432 | 433 | 434 | 435 | 436 | 管理任务计划 438 | 439 | 对于创建新任务,对任务的修改不会实际生效,直到crontab得到保存。 443 | 444 | 使用任务列表右侧的按钮来修改、删除、打印或者直接运行选中的任务。您也可以从右键菜单中选择这些动作。另外,右键菜单中也提供了诸如剪切、复制和粘贴任务的动作。 446 | 447 | 448 | 449 | 450 | 添加环境变量 452 | 453 | 如需创建一个新的环境变量,点击新建变量...按钮。 457 | 458 | 另外,您也可以使用鼠标右键按钮的菜单来选择这个动作。 462 | 463 | 464 | 变量编辑对话框 466 | 467 | 468 | 编辑变量对话框。 472 | 473 | 475 | 编辑变量对话框。 481 | 482 | 483 | 484 | 485 | 486 | 变量 491 | 输入环境变量名。您可以使用下拉列表按钮选择计划任务最常用的环境变量。它们包括: 494 | 495 | 496 | 497 | 498 | HOME 502 | 用于替代默认用户的家目录。 506 | 507 | 508 | 509 | MAILTO 513 | 用于向指定的邮件地址发出电子邮件,而不使用用户默认的电子邮件地址。 517 | 518 | 519 | 520 | PATH 524 | 用于指定搜索程序的文件夹。 528 | 529 | 530 | 531 | SHELL 535 | 用于替代用户的默认值。 539 | 540 | 541 | 542 | LD_CONFIG_PATH 546 | 指定动态链接库的位置。它使得 cron 任务能够运行那些所使用的动态链接库在系统库路径之外的应用程序。 550 | 551 | 552 | 553 | 554 | 555 | 556 | 560 | 输入环境变量值。 564 | 565 | 566 | 注释 570 | 输入环境变量的描述信息,例如其目的。 574 | 575 | 576 | 启用此变量 580 | 581 | 要启用或者禁用变量,请选中或者取消选中该项。 583 | 584 | 585 | 586 | 确定 590 | 591 | 完成该变量的设置。 593 | 594 | 595 | 596 | 取消 600 | 601 | 取消该变量的设置。 603 | 604 | 605 | 606 | 607 | 所设置的环境变量不会实际生效,直到crontab得到保存。 611 | 612 | 613 | 614 | 615 | 616 | 管理环境变量 618 | 619 | 对于创建新变量,其变量的修改不会实际生效,直到crontab得到保存。 625 | 626 | 使用任务列表右侧的按钮来修改或删除选中的变量。您也可以从右键菜单中选择这些动作。另外,右键菜单中也提供了诸如剪切、复制和粘贴变量的动作。 628 | 629 | 630 | 631 |
632 | 633 | 642 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | ########### Build ############### 3 | 4 | include_directories( 5 | ${CMAKE_CURRENT_SOURCE_DIR}/crontablib 6 | ${CMAKE_CURRENT_SOURCE_DIR} 7 | ) 8 | 9 | ########## KCM Module ############### 10 | kcmutils_add_qml_kcm(kcm_cron) 11 | ecm_qt_declare_logging_category(kcm_cron 12 | HEADER kcm_cron_debug.h 13 | IDENTIFIER KCM_CRON_LOG 14 | CATEGORY_NAME org.kde.kcm.cron 15 | DESCRIPTION "kcm cron" 16 | EXPORT KCRON 17 | ) 18 | 19 | target_sources(kcm_cron PRIVATE 20 | crontablib/cthost.cpp crontablib/cthost.h 21 | crontablib/ctcron.cpp crontablib/ctcron.h 22 | crontablib/ctmonth.cpp crontablib/ctmonth.h 23 | crontablib/ctminute.cpp crontablib/ctminute.h 24 | crontablib/cthour.cpp crontablib/cthour.h 25 | crontablib/ctdom.cpp crontablib/ctdom.h 26 | crontablib/ctdow.cpp crontablib/ctdow.h 27 | crontablib/cttask.cpp crontablib/cttask.h 28 | crontablib/ctunit.cpp crontablib/ctunit.h 29 | crontablib/ctvariable.cpp crontablib/ctvariable.h 30 | crontablib/ctSystemCron.cpp crontablib/ctSystemCron.h 31 | crontablib/ctInitializationError.cpp crontablib/ctInitializationError.h 32 | crontablib/ctSaveStatus.cpp crontablib/ctSaveStatus.h 33 | crontablib/ctHelper.cpp crontablib/ctHelper.h 34 | 35 | genericmodel.cpp genericmodel.h 36 | 37 | variablesmodel.cpp variablesmodel.h 38 | variable.cpp variable.h 39 | 40 | tasksmodel.cpp tasksmodel.h 41 | task.cpp task.h 42 | 43 | cronPrinter.cpp cronPrinter.h 44 | 45 | taskvalidator.cpp taskvalidator.h 46 | 47 | kcmCron.cpp kcmCron.h 48 | ) 49 | 50 | target_link_libraries(kcm_cron 51 | Qt6::Core 52 | Qt6::PrintSupport 53 | KF6::ConfigWidgets 54 | KF6::I18n 55 | KF6::CoreAddons 56 | KF6::AuthCore 57 | KF6::KCMUtils 58 | ) 59 | 60 | # For root permissions. 61 | add_subdirectory(helper) 62 | -------------------------------------------------------------------------------- /src/cronPrinter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "cronPrinter.h" 10 | 11 | #include "ctcron.h" 12 | #include "cttask.h" 13 | #include "ctvariable.h" 14 | 15 | #include "kcm_cron_debug.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace HTML 23 | { 24 | static constexpr QLatin1StringView html("%1"); 25 | static constexpr QLatin1StringView documentBody("%1"); 26 | static constexpr QLatin1StringView documentHead( 27 | "" 28 | "" 50 | ""); 51 | 52 | static constexpr QLatin1StringView beginTable(""); 53 | static constexpr QLatin1StringView beginTableHeader(""); 54 | static constexpr QLatin1StringView endTableHeader(""); 55 | static constexpr QLatin1StringView beginTableBody(""); 56 | static constexpr QLatin1StringView endTableBody(""); 57 | static constexpr QLatin1StringView endTable("
"); 58 | static constexpr QLatin1StringView tableHeader("%1"); 59 | static constexpr QLatin1StringView tableRow("%1"); 60 | static constexpr QLatin1StringView tableData("
%1
"); 61 | 62 | const static auto tasks = kli18nc("@title Note: that is an HTML tag", "Tasks"); 63 | const static auto variables = kli18nc("@title Note: that is an HTML tag", "Environment Variables"); 64 | } 65 | 66 | CronPrinter::CronPrinter(QObject *parent) 67 | : QObject(parent) 68 | { 69 | } 70 | 71 | void CronPrinter::print(CTCron *cron) 72 | { 73 | if (!cron) { 74 | qCWarning(KCM_CRON_LOG) << "Invalid cron for print"; 75 | return; 76 | } 77 | 78 | QString title = getTitle(cron); 79 | QString tasksTable = getTasksTable(cron); 80 | QString variablesTable = getVariablesTable(cron); 81 | 82 | QPrinter *printer = new QPrinter(QPrinter::HighResolution); 83 | printer->setFullPage(true); 84 | printer->setOutputFormat(QPrinter::PdfFormat); 85 | 86 | QPrintDialog *dlg = new QPrintDialog(printer); 87 | dlg->setOptions(QAbstractPrintDialog::PrintToFile); 88 | 89 | if (dlg->exec() == QDialog::Accepted) { 90 | QTextEdit textEdit; 91 | textEdit.setHtml(HTML::html.arg(HTML::documentHead 92 | + HTML::documentBody.arg(title + HTML::tasks.toString() + tasksTable + HTML::variables.toString() + variablesTable))); 93 | textEdit.print(printer); 94 | } 95 | 96 | delete dlg; 97 | delete printer; 98 | } 99 | 100 | QString CronPrinter::getTasksTable(CTCron *cron) 101 | { 102 | QString header = HTML::tableRow.arg(HTML::tableHeader.arg(i18nc("@title:column", "Scheduling")) + HTML::tableHeader.arg(i18nc("@title:column", "Command")) 103 | + HTML::tableHeader.arg(i18nc("@title:column", "Description"))); 104 | 105 | QString table = HTML::beginTable + HTML::beginTableHeader + header + HTML::endTableHeader + HTML::beginTableBody; 106 | 107 | for (CTTask *task : cron->tasks()) { 108 | table.append(getRow(task)); 109 | } 110 | 111 | return table.append(HTML::endTableBody + HTML::endTable); 112 | } 113 | 114 | QString CronPrinter::getVariablesTable(CTCron *cron) 115 | { 116 | QString header = HTML::tableRow.arg(HTML::tableHeader.arg(i18nc("@title:column", "Variable")) + HTML::tableHeader.arg(i18nc("@title:column", "Value")) 117 | + HTML::tableHeader.arg(i18nc("@title:column", "Comment"))); 118 | 119 | QString table = HTML::beginTable + HTML::beginTableHeader + header + HTML::endTableHeader + HTML::beginTableBody; 120 | for (CTVariable *variable : cron->variables()) { 121 | table.append(getRow(variable)); 122 | } 123 | 124 | return table.append(HTML::endTableBody + HTML::endTable); 125 | } 126 | 127 | QString CronPrinter::getRow(CTTask *task) 128 | { 129 | return HTML::tableRow.arg(HTML::tableData.arg(task->schedulingCronFormat()) + HTML::tableData.arg(task->command) + HTML::tableData.arg(task->comment)); 130 | } 131 | 132 | QString CronPrinter::getRow(CTVariable *variable) 133 | { 134 | return HTML::tableRow.arg(HTML::tableData.arg(variable->variable) + HTML::tableData.arg(variable->value) + HTML::tableData.arg(variable->comment)); 135 | } 136 | 137 | QString CronPrinter::getTitle(CTCron *cron) 138 | { 139 | QString title; 140 | 141 | title.append(QStringLiteral("
"));
142 | 
143 |     if (cron->isSystemCron()) {
144 |         title.append(i18nc("@title", "System Crontab"));
145 |     } else {
146 |         title.append(i18nc("@title", "Crontab of user %1", cron->userLogin()));
147 |     }
148 | 
149 |     title.append(QStringLiteral("\n
")); 150 | 151 | return title; 152 | } 153 | 154 | #include "cronPrinter.moc" 155 | 156 | #include "moc_cronPrinter.cpp" 157 | -------------------------------------------------------------------------------- /src/cronPrinter.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | class CTTask; 14 | class CTVariable; 15 | class CTCron; 16 | 17 | class CronPrinter : public QObject 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | CronPrinter(QObject *parent); 23 | 24 | public Q_SLOTS: 25 | void print(CTCron *cron); 26 | 27 | private: 28 | QString getTasksTable(CTCron *cron); 29 | QString getVariablesTable(CTCron *cron); 30 | 31 | QString getRow(CTTask *task); 32 | QString getRow(CTVariable *variable); 33 | 34 | QString getTitle(CTCron *cron); 35 | }; 36 | -------------------------------------------------------------------------------- /src/crontablib/ctHelper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Month Implementation 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "ctHelper.h" 10 | 11 | #include 12 | 13 | #include 14 | 15 | QString CTHelper::exportComment(const QString &comment) 16 | { 17 | QString exportComment; 18 | 19 | if (comment.isEmpty()) { 20 | const QString noComment = i18n("No comment"); 21 | exportComment += QLatin1String("#") + noComment + QLatin1String("\n"); 22 | return exportComment; 23 | } 24 | 25 | const QStringList lines = comment.split(QStringLiteral("\n")); 26 | for (const QString &line : lines) { 27 | exportComment += QLatin1String("#") + line + QLatin1String("\n"); 28 | } 29 | 30 | return exportComment; 31 | } 32 | -------------------------------------------------------------------------------- /src/crontablib/ctHelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Hour Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | class CTHelper 14 | { 15 | public: 16 | static QString exportComment(const QString &comment); 17 | }; 18 | -------------------------------------------------------------------------------- /src/crontablib/ctInitializationError.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Month Implementation 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "ctInitializationError.h" 10 | 11 | QString CTInitializationError::errorMessage() const 12 | { 13 | return mError; 14 | } 15 | 16 | void CTInitializationError::setErrorMessage(const QString &errorMessage) 17 | { 18 | mError = errorMessage; 19 | } 20 | 21 | bool CTInitializationError::hasErrorMessage() const 22 | { 23 | if (mError.isEmpty()) { 24 | return false; 25 | } 26 | 27 | return true; 28 | } 29 | -------------------------------------------------------------------------------- /src/crontablib/ctInitializationError.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Hour Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | class CTInitializationError 14 | { 15 | public: 16 | QString errorMessage() const; 17 | 18 | void setErrorMessage(const QString &errorMessage); 19 | 20 | bool hasErrorMessage() const; 21 | 22 | private: 23 | QString mError; 24 | }; 25 | -------------------------------------------------------------------------------- /src/crontablib/ctSaveStatus.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Month Implementation 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "ctSaveStatus.h" 10 | 11 | CTSaveStatus::CTSaveStatus() 12 | { 13 | mErrorStatus = false; 14 | } 15 | 16 | CTSaveStatus::CTSaveStatus(const QString &errorMessage, const QString &detailErrorMessage) 17 | : mErrorStatus(true) 18 | , mError(errorMessage) 19 | , mDetailError(detailErrorMessage) 20 | { 21 | } 22 | 23 | QString CTSaveStatus::errorMessage() const 24 | { 25 | return mError; 26 | } 27 | 28 | QString CTSaveStatus::detailErrorMessage() const 29 | { 30 | return mDetailError; 31 | } 32 | 33 | bool CTSaveStatus::isError() const 34 | { 35 | return mErrorStatus; 36 | } 37 | -------------------------------------------------------------------------------- /src/crontablib/ctSaveStatus.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Hour Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | class CTSaveStatus 14 | { 15 | public: 16 | CTSaveStatus(); 17 | 18 | CTSaveStatus(const QString &errorMessage, const QString &detailErrorMessage); 19 | 20 | QString errorMessage() const; 21 | 22 | QString detailErrorMessage() const; 23 | 24 | bool isError() const; 25 | 26 | private: 27 | bool mErrorStatus = true; 28 | 29 | const QString mError; 30 | 31 | const QString mDetailError; 32 | }; 33 | -------------------------------------------------------------------------------- /src/crontablib/ctSystemCron.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Implementation 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "ctSystemCron.h" 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | #include "cthost.h" 17 | #include "cttask.h" 18 | #include "ctvariable.h" 19 | 20 | CTSystemCron::CTSystemCron(const QString &crontabBinary) 21 | : CTCron() 22 | { 23 | d->systemCron = true; 24 | d->multiUserCron = true; 25 | d->currentUserCron = false; 26 | 27 | d->crontabBinary = crontabBinary; 28 | 29 | d->userLogin = i18n("root"); 30 | d->userRealName = d->userLogin; 31 | 32 | d->initialTaskCount = 0; 33 | d->initialVariableCount = 0; 34 | 35 | // Don't set error if it can't be read, it means the user 36 | // doesn't have a crontab. 37 | const QString crontabFile = QStringLiteral("/etc/crontab"); 38 | if (QFileInfo::exists(crontabFile)) { 39 | parseFile(crontabFile); 40 | } 41 | 42 | d->initialTaskCount = d->task.size(); 43 | d->initialVariableCount = d->variable.size(); 44 | } 45 | 46 | CTSystemCron::~CTSystemCron() 47 | { 48 | } 49 | -------------------------------------------------------------------------------- /src/crontablib/ctSystemCron.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include "ctcron.h" 14 | 15 | class CTSystemCron : public CTCron 16 | { 17 | public: 18 | /** 19 | * Constructs the scheduled tasks, environment variables from crontab 20 | * files and obtains some information about the user from the system. 21 | * 22 | * Default is to construct from the user's crontab. Can also be called, 23 | * passing TRUE, to construct from the system crontab. Throws an 24 | * exception if the crontab file can not be found, read, or parsed. 25 | */ 26 | explicit CTSystemCron(const QString &cronBinary); 27 | 28 | /** 29 | * Destructor. 30 | */ 31 | ~CTSystemCron() override; 32 | }; 33 | -------------------------------------------------------------------------------- /src/crontablib/ctcron.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class CTTask; 16 | class CTVariable; 17 | class CTInitializationError; 18 | 19 | class QTextStream; 20 | 21 | struct passwd; 22 | 23 | #include "ctSaveStatus.h" 24 | 25 | class CommandLineStatus 26 | { 27 | public: 28 | int exitCode; 29 | 30 | QString commandLine; 31 | 32 | QString standardOutput; 33 | QString standardError; 34 | }; 35 | 36 | class CommandLine 37 | { 38 | public: 39 | QString commandLine; 40 | 41 | QStringList parameters; 42 | 43 | CommandLineStatus execute(); 44 | }; 45 | 46 | class CTCronPrivate 47 | { 48 | public: 49 | /** 50 | * Indicates whether or not the crontab belongs to the system. 51 | */ 52 | bool systemCron; 53 | 54 | /** 55 | * Indicates if this cron could have tasks and variables from different users. 56 | */ 57 | bool multiUserCron; 58 | 59 | /** 60 | * Indicates whether or not the crontab belongs to the current user. 61 | */ 62 | bool currentUserCron; 63 | 64 | /** 65 | * User login. 66 | */ 67 | QString userLogin; 68 | 69 | /** 70 | * User real name. 71 | */ 72 | QString userRealName; 73 | 74 | /** 75 | * User's scheduled tasks. 76 | */ 77 | QList task; 78 | 79 | /** 80 | * User's environment variables. Note: These are only environment variables 81 | * found in the user's crontab file and does not include any set in a 82 | * login or shell script such as ".bash_profile". 83 | */ 84 | QList variable; 85 | 86 | int initialTaskCount; 87 | int initialVariableCount; 88 | 89 | /** 90 | * Contains path to the crontab binary file. 91 | */ 92 | QString crontabBinary; 93 | }; 94 | 95 | /** 96 | * A user (encapsulation of a single crontab file). Encapsulates 97 | * file i/o, parsing, tokenization, and natural language description. 98 | */ 99 | class CTCron 100 | { 101 | public: 102 | /** 103 | * If you already have a struct passwd, use it instead. 104 | * This is never used for the system crontab. 105 | */ 106 | explicit CTCron(const QString &cronBinary, const struct passwd *userInfos, bool currentUserCron, CTInitializationError &ctInitializationError); 107 | 108 | /** 109 | * Destructor. 110 | */ 111 | virtual ~CTCron(); 112 | 113 | /** 114 | * Copy one user's tasks and environment variables to another user. 115 | */ 116 | CTCron &operator=(const CTCron &source); 117 | 118 | virtual QList tasks() const; 119 | 120 | virtual QList variables() const; 121 | 122 | virtual void addTask(CTTask *task); 123 | virtual void addVariable(CTVariable *variable); 124 | 125 | virtual void modifyTask(CTTask *task); 126 | virtual void modifyVariable(CTVariable *variable); 127 | 128 | virtual void removeVariable(CTVariable *variable); 129 | virtual void removeTask(CTTask *task); 130 | 131 | /** 132 | * Tokenizes to crontab file format. 133 | */ 134 | QString exportCron() const; 135 | 136 | /** 137 | * Apply changes. 138 | */ 139 | CTSaveStatus save(); 140 | 141 | /** 142 | * Cancel changes. 143 | */ 144 | void cancel(); 145 | 146 | /** 147 | * Indicates whether or not dirty. 148 | */ 149 | bool isDirty() const; 150 | 151 | /** 152 | * Returns the PATH environment variable value. 153 | * A short cut to iterating the tasks vector. 154 | */ 155 | QString path() const; 156 | 157 | /** 158 | * Returns true if this cron could have tasks and variables from a different user. 159 | */ 160 | bool isMultiUserCron() const; 161 | 162 | /** 163 | * Returns true if this cron is the system cron. 164 | */ 165 | bool isSystemCron() const; 166 | 167 | /** 168 | * Returns true if this cron is the cron of the user who launched this app. 169 | */ 170 | bool isCurrentUserCron() const; 171 | 172 | QString userLogin() const; 173 | 174 | /** 175 | * TODO 176 | * Bugged method for the moment (need to parse x,x,x,x data from /etc/passwd). 177 | */ 178 | QString userRealName() const; 179 | 180 | protected: 181 | /** 182 | * Help constructor for subclasses. 183 | */ 184 | explicit CTCron(); 185 | 186 | // Initialize member variables from the struct passwd. 187 | bool initializeFromUserInfos(const struct passwd *userInfos); 188 | 189 | private: 190 | /** 191 | * Can't copy a user! 192 | */ 193 | CTCron(const CTCron &source); 194 | 195 | protected: 196 | /** 197 | * Parses crontab file format. 198 | */ 199 | void parseFile(const QString &fileName); 200 | void parseTextStream(QTextStream *stream); 201 | 202 | CTSaveStatus prepareSaveStatusError(const CommandLineStatus &commandLineStatus); 203 | // d probably stands for data. 204 | CTCronPrivate *const d; 205 | }; 206 | -------------------------------------------------------------------------------- /src/crontablib/ctdom.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Day of Month Implementation 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "ctdom.h" 10 | #include 11 | 12 | QList CTDayOfMonth::shortName; 13 | 14 | CTDayOfMonth::CTDayOfMonth(const QString &tokStr) 15 | : CTUnit(CTDayOfMonth::MINIMUM, CTDayOfMonth::MAXIMUM, tokStr) 16 | { 17 | } 18 | 19 | QString CTDayOfMonth::describe() const 20 | { 21 | initializeNames(); 22 | return (enabledCount() == CTDayOfMonth::MAXIMUM) ? i18n("every day ") : CTUnit::genericDescribe(shortName); 23 | } 24 | 25 | QString CTDayOfMonth::getName(const int ndx) 26 | { 27 | initializeNames(); 28 | return shortName[ndx]; 29 | } 30 | 31 | void CTDayOfMonth::initializeNames() 32 | { 33 | if (shortName.isEmpty()) { 34 | shortName << QLatin1String("") << i18n("1st") << i18n("2nd") << i18n("3rd") << i18n("4th") << i18n("5th") << i18n("6th") << i18n("7th") << i18n("8th") 35 | << i18n("9th") << i18n("10th") << i18n("11th") << i18n("12th") << i18n("13th") << i18n("14th") << i18n("15th") << i18n("16th") << i18n("17th") 36 | << i18n("18th") << i18n("19th") << i18n("20th") << i18n("21st") << i18n("22nd") << i18n("23rd") << i18n("24th") << i18n("25th") 37 | << i18n("26th") << i18n("27th") << i18n("28th") << i18n("29th") << i18n("30th") << i18n("31st"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/crontablib/ctdom.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Day of Month Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | #include "ctunit.h" 15 | 16 | /** 17 | * Scheduled task days of month. 18 | */ 19 | class CTDayOfMonth : public CTUnit 20 | { 21 | public: 22 | /** 23 | * Constructs from a tokenized string. 24 | */ 25 | explicit CTDayOfMonth(const QString &tokStr = QLatin1String("")); 26 | 27 | /** 28 | * Get natural language description. 29 | */ 30 | virtual QString describe() const; 31 | 32 | /** 33 | * Get day of month name. 34 | */ 35 | static QString getName(const int ndx); 36 | 37 | static const int MINIMUM = 1; 38 | static const int MAXIMUM = 31; 39 | 40 | private: 41 | static void initializeNames(); 42 | static QList shortName; 43 | }; 44 | -------------------------------------------------------------------------------- /src/crontablib/ctdow.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Day Of Week Implementation 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "ctdow.h" 10 | #include 11 | 12 | QList CTDayOfWeek::shortName; 13 | 14 | QList CTDayOfWeek::longName; 15 | 16 | CTDayOfWeek::CTDayOfWeek(const QString &tokStr) 17 | : CTUnit(CTDayOfWeek::MINIMUM, CTDayOfWeek::MAXIMUM, tokStr) 18 | { 19 | // Compensate for cron supporting Sunday as both 0 and 7. 20 | 21 | if (isEnabled(0)) { 22 | setEnabled(0, false); 23 | setEnabled(7, true); 24 | } 25 | } 26 | 27 | void CTDayOfWeek::initialize(const QString &tokStr) 28 | { 29 | CTUnit::initialize(tokStr); 30 | 31 | // Compensate for cron supporting Sunday as both 0 and 7. 32 | 33 | if (isEnabled(0)) { 34 | setEnabled(0, false); 35 | setEnabled(7, true); 36 | apply(); 37 | } 38 | } 39 | 40 | QString CTDayOfWeek::describe() const 41 | { 42 | initializeNames(); 43 | if (enabledCount() == CTDayOfWeek::MAXIMUM) { 44 | return i18n("every day "); 45 | } else { 46 | return CTUnit::genericDescribe(shortName); 47 | } 48 | } 49 | 50 | QString CTDayOfWeek::getName(const int ndx, const bool format) 51 | { 52 | initializeNames(); 53 | return (format == shortFormat) ? shortName.at(ndx) : longName.at(ndx); 54 | } 55 | 56 | void CTDayOfWeek::initializeNames() 57 | { 58 | if (shortName.isEmpty()) { 59 | shortName << QLatin1String("") << i18n("Mon") << i18n("Tue") << i18n("Wed") << i18n("Thu") << i18n("Fri") << i18n("Sat") << i18n("Sun"); 60 | 61 | longName << QLatin1String("") << i18n("Monday") << i18n("Tuesday") << i18n("Wednesday") << i18n("Thursday") << i18n("Friday") << i18n("Saturday") 62 | << i18n("Sunday"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/crontablib/ctdow.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Day of Week Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | #include "ctunit.h" 15 | 16 | /** 17 | * Scheduled task days of week. 18 | */ 19 | class CTDayOfWeek : public CTUnit 20 | { 21 | public: 22 | /** 23 | * Constructs from a tokenized string. 24 | */ 25 | explicit CTDayOfWeek(const QString &tokStr = QLatin1String("")); 26 | 27 | /** 28 | * Override initialize to support crontab using both 0 and 7 for 29 | * Sunday. 30 | */ 31 | void initialize(const QString &tokStr = QLatin1String("")); 32 | 33 | /** 34 | * Get natural language description. 35 | */ 36 | virtual QString describe() const; 37 | 38 | /** 39 | * Get day of week name. 40 | */ 41 | static QString getName(const int ndx, const bool format = CTDayOfWeek::longFormat); 42 | 43 | static const int MINIMUM = 1; 44 | static const int MAXIMUM = 7; 45 | 46 | private: 47 | static void initializeNames(); 48 | static QList shortName; 49 | static QList longName; 50 | }; 51 | -------------------------------------------------------------------------------- /src/crontablib/cthost.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | -------------------------------------------------------------------- 3 | CT Host Implementation 4 | -------------------------------------------------------------------- 5 | SPDX-FileCopyrightText: 1999 Gary Meyer 6 | -------------------------------------------------------------------- 7 | SPDX-License-Identifier: GPL-2.0-or-later 8 | */ 9 | 10 | #include "cthost.h" 11 | 12 | #include 13 | #include 14 | #include // getuid() 15 | 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include "ctInitializationError.h" 22 | #include "ctSystemCron.h" 23 | #include "ctcron.h" 24 | 25 | #include "kcm_cron_debug.h" 26 | 27 | CTHost::CTHost(const QString &cronBinary, CTInitializationError &ctInitializationError) 28 | { 29 | struct passwd *userInfos = nullptr; 30 | 31 | mCrontabBinary = cronBinary; 32 | 33 | // If it is the root user 34 | if (getuid() == 0) { 35 | // Read /etc/passwd 36 | setpwent(); // restart 37 | while ((userInfos = getpwent())) { 38 | if (allowDeny(userInfos->pw_name)) { 39 | const QString errorMessage = createCTCron(userInfos); 40 | if (!errorMessage.isEmpty()) { 41 | ctInitializationError.setErrorMessage(errorMessage); 42 | return; 43 | } 44 | } 45 | // delete userInfos; 46 | } 47 | setpwent(); // restart again for others 48 | } 49 | // Non-root user, so just create user's cron table. 50 | else { 51 | // Get name from UID, check it against AllowDeny() 52 | unsigned int uid = getuid(); 53 | setpwent(); // restart 54 | while ((userInfos = getpwent())) { 55 | if ((userInfos->pw_uid == uid) && (!allowDeny(userInfos->pw_name))) { 56 | ctInitializationError.setErrorMessage( 57 | i18n("You have been blocked from using KCron\ 58 | by either the /etc/cron.allow file or the /etc/cron.deny file.\ 59 | \n\nCheck the crontab man page for further details.")); 60 | 61 | return; 62 | } 63 | // delete userInfos; 64 | } 65 | 66 | setpwent(); // restart again for others 67 | 68 | passwd *currentUserPassword = getpwuid(uid); 69 | 70 | const QString errorMessage = createCTCron(currentUserPassword); 71 | if (!errorMessage.isEmpty()) { 72 | ctInitializationError.setErrorMessage(errorMessage); 73 | return; 74 | } 75 | 76 | // delete currentUserPassword; 77 | } 78 | // Create the system cron table. 79 | createSystemCron(); 80 | } 81 | 82 | CTHost::~CTHost() 83 | { 84 | qDeleteAll(mCrons); 85 | } 86 | 87 | bool CTHost::allowDeny(char *name) 88 | { 89 | QFile allow(QStringLiteral("/etc/cron.allow")); 90 | 91 | // if cron.allow exists make sure user is listed 92 | if (allow.open(QFile::ReadOnly)) { 93 | QTextStream stream(&allow); 94 | while (!stream.atEnd()) { 95 | if (stream.readLine() == QLatin1String(name)) { 96 | allow.close(); 97 | return true; 98 | } 99 | } 100 | allow.close(); 101 | return false; 102 | } else { 103 | allow.close(); 104 | QFile deny(QStringLiteral("/etc/cron.deny")); 105 | 106 | // else if cron.deny exists make sure user is not listed 107 | if (deny.open(QFile::ReadOnly)) { 108 | QTextStream stream(&deny); 109 | while (!stream.atEnd()) { 110 | if (stream.readLine() == QLatin1String(name)) { 111 | deny.close(); 112 | return false; 113 | } 114 | } 115 | deny.close(); 116 | return true; 117 | } else { 118 | deny.close(); 119 | return true; 120 | } 121 | } 122 | } 123 | 124 | CTSaveStatus CTHost::save(CTCron *ctCron) 125 | { 126 | qCDebug(KCM_CRON_LOG) << "Save current cron."; 127 | 128 | // Retrieve the current cron to use. This could either be a user cron or a system cron. 129 | // Implements system cron entry point. 130 | return ctCron->save(); 131 | } 132 | 133 | void CTHost::cancel() 134 | { 135 | for (CTCron *ctCron : std::as_const(mCrons)) { 136 | ctCron->cancel(); 137 | } 138 | } 139 | 140 | bool CTHost::isDirty() 141 | { 142 | bool isDirty = false; 143 | 144 | for (CTCron *ctCron : std::as_const(mCrons)) { 145 | if (ctCron->isDirty()) { 146 | isDirty = true; 147 | } 148 | } 149 | 150 | return isDirty; 151 | } 152 | 153 | CTCron *CTHost::createSystemCron() 154 | { 155 | CTCron *p = new CTSystemCron(mCrontabBinary); 156 | 157 | mCrons.append(p); 158 | 159 | return p; 160 | } 161 | 162 | QString CTHost::createCTCron(const struct passwd *userInfos) 163 | { 164 | bool currentUserCron = false; 165 | if (userInfos->pw_uid == getuid()) { 166 | currentUserCron = true; 167 | } 168 | 169 | CTInitializationError ctInitializationError; 170 | auto p = new CTCron(mCrontabBinary, userInfos, currentUserCron, ctInitializationError); 171 | if (ctInitializationError.hasErrorMessage()) { 172 | delete p; 173 | return ctInitializationError.errorMessage(); 174 | } 175 | 176 | mCrons.append(p); 177 | 178 | return QString(); 179 | } 180 | 181 | CTCron *CTHost::findCurrentUserCron() const 182 | { 183 | // Because multiple users may exist, return only the currently logged in user's cron in user cron mode. 184 | for (CTCron *ctCron : std::as_const(mCrons)) { 185 | if (ctCron->isCurrentUserCron()) { 186 | return ctCron; 187 | } 188 | } 189 | 190 | qCDebug(KCM_CRON_LOG) << "Unable to find the current user Cron. Please report this bug and your crontab config to the developers."; 191 | return nullptr; 192 | } 193 | 194 | CTCron *CTHost::findSystemCron() const 195 | { 196 | // Return the cron belonging to root. 197 | for (CTCron *ctCron : std::as_const(mCrons)) { 198 | if (ctCron->isMultiUserCron()) { 199 | return ctCron; 200 | } 201 | } 202 | 203 | qCDebug(KCM_CRON_LOG) << "Unable to find the system Cron. Please report this bug and your crontab config to the developers."; 204 | return nullptr; 205 | } 206 | 207 | CTCron *CTHost::findUserCron(const QString &userLogin) const 208 | { 209 | for (CTCron *ctCron : std::as_const(mCrons)) { 210 | if (ctCron->userLogin() == userLogin) { 211 | return ctCron; 212 | } 213 | } 214 | 215 | qCDebug(KCM_CRON_LOG) << "Unable to find the user Cron " << userLogin << ". Please report this bug and your crontab config to the developers."; 216 | return nullptr; 217 | } 218 | 219 | CTCron *CTHost::findCronContaining(CTTask *ctTask) const 220 | { 221 | for (CTCron *ctCron : std::as_const(mCrons)) { 222 | if (ctCron->tasks().contains(ctTask)) { 223 | return ctCron; 224 | } 225 | } 226 | 227 | qCDebug(KCM_CRON_LOG) << "Unable to find the cron of this task. Please report this bug and your crontab config to the developers."; 228 | return nullptr; 229 | } 230 | 231 | CTCron *CTHost::findCronContaining(CTVariable *ctVariable) const 232 | { 233 | for (CTCron *ctCron : std::as_const(mCrons)) { 234 | if (ctCron->variables().contains(ctVariable)) { 235 | return ctCron; 236 | } 237 | } 238 | 239 | qCDebug(KCM_CRON_LOG) << "Unable to find the cron of this variable. Please report this bug and your crontab config to the developers."; 240 | return nullptr; 241 | } 242 | -------------------------------------------------------------------------------- /src/crontablib/cthost.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Host Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | #include "ctSaveStatus.h" 15 | 16 | class CTTask; 17 | class CTVariable; 18 | class CTCron; 19 | class CTInitializationError; 20 | 21 | struct passwd; 22 | 23 | /** 24 | * The host machine, or computer (encapsulation of crontab files on the 25 | * host). 26 | * 27 | * If the user is the root user, the cron vector will have a member for 28 | * each user of the host plus one for the system crontab. 29 | * 30 | * If the user is a non-root user, there will be only one member in the 31 | * cron vector. 32 | */ 33 | class CTHost 34 | { 35 | public: 36 | /** 37 | * Constructs the user(s), scheduled tasks, and environment variables 38 | * from crontab files. 39 | */ 40 | CTHost(const QString &cronBinary, CTInitializationError &ctInitializationError); 41 | 42 | /** 43 | * Destroys the user(s), scheduled tasks, and environment variable 44 | * objects. Does not make any changes to the crontab files. Any unapplied 45 | * changes are consequently "cancelled." 46 | */ 47 | ~CTHost(); 48 | 49 | /** 50 | * Apply changes. 51 | * return an empty string if no problem ocurred. 52 | */ 53 | CTSaveStatus save(CTCron *ctCron); 54 | 55 | /** 56 | * Cancel changes. 57 | */ 58 | void cancel(); 59 | 60 | /** 61 | * Indicates whether or not dirty. 62 | */ 63 | bool isDirty(); 64 | 65 | CTCron *findCurrentUserCron() const; 66 | CTCron *findSystemCron() const; 67 | CTCron *findUserCron(const QString &userLogin) const; 68 | 69 | CTCron *findCronContaining(CTTask *ctTask) const; 70 | CTCron *findCronContaining(CTVariable *ctVariable) const; 71 | 72 | /** 73 | * User(s). 74 | * 75 | * If the user is the root user, the cron vector will have a member for 76 | * each user of the host plus one for the system crontab. 77 | * 78 | * If the user is a non-root user, there will be only one member in the 79 | * cron vector. 80 | */ 81 | QList mCrons; 82 | 83 | private: 84 | /** 85 | * Copy construction not allowed. 86 | */ 87 | CTHost(const CTHost &source); 88 | 89 | /** 90 | * Assignment not allowed 91 | */ 92 | CTHost &operator=(const CTHost &source); 93 | 94 | /** 95 | * Factory create a cron table. Appends to the end of cron. 96 | */ 97 | CTCron *createSystemCron(); 98 | QString createCTCron(const struct passwd *password); 99 | 100 | /** 101 | * Check /etc/cron.allow, /etc/cron.deny 102 | */ 103 | bool allowDeny(char *name); 104 | 105 | QString mCrontabBinary; 106 | }; 107 | -------------------------------------------------------------------------------- /src/crontablib/cthour.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Month Implementation 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "cthour.h" 10 | 11 | /** 12 | * Constructs from a tokenized string. 13 | */ 14 | CTHour::CTHour(const QString &tokStr) 15 | : CTUnit(0, 23, tokStr) 16 | { 17 | } 18 | 19 | int CTHour::findPeriod() const 20 | { 21 | const QList periods{2, 3, 4, 6, 8}; 22 | 23 | return CTUnit::findPeriod(periods); 24 | } 25 | 26 | QString CTHour::exportUnit() const 27 | { 28 | const int period = findPeriod(); 29 | if (period != 0 && period != 1) { 30 | return QStringLiteral("*/%1").arg(QString::number(period)); 31 | } 32 | 33 | return CTUnit::exportUnit(); 34 | } 35 | -------------------------------------------------------------------------------- /src/crontablib/cthour.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Hour Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "ctunit.h" 12 | 13 | /** 14 | * Scheduled task hours. 15 | */ 16 | class CTHour : public CTUnit 17 | { 18 | public: 19 | /** 20 | * Constructs from a tokenized string. 21 | */ 22 | explicit CTHour(const QString &tokStr = QLatin1String("")); 23 | 24 | int findPeriod() const; 25 | 26 | QString exportUnit() const override; 27 | }; 28 | -------------------------------------------------------------------------------- /src/crontablib/ctminute.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Month Implementation 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "ctminute.h" 10 | 11 | /** 12 | * Constructs from a tokenized string. 13 | */ 14 | CTMinute::CTMinute(const QString &tokStr) 15 | : CTUnit(0, 59, tokStr) 16 | { 17 | } 18 | 19 | CTMinute::CTMinute() 20 | : CTUnit(0, 59, QLatin1String("")) 21 | { 22 | } 23 | 24 | int CTMinute::findPeriod() const 25 | { 26 | const QList periods{1, 2, 5, 10, 15, 20, 30}; 27 | 28 | return CTUnit::findPeriod(periods); 29 | } 30 | 31 | QString CTMinute::exportUnit() const 32 | { 33 | const int period = findPeriod(); 34 | if (period != 0 && period != 1) { 35 | return QStringLiteral("*/%1").arg(QString::number(period)); 36 | } 37 | 38 | return CTUnit::exportUnit(); 39 | } 40 | -------------------------------------------------------------------------------- /src/crontablib/ctminute.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Minute Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "ctunit.h" 12 | 13 | /** 14 | * Scheduled task minutes. 15 | */ 16 | class CTMinute : public CTUnit 17 | { 18 | public: 19 | /** 20 | * Constructs from a tokenized string. 21 | */ 22 | explicit CTMinute(const QString &tokStr); 23 | 24 | CTMinute(); 25 | 26 | int findPeriod() const; 27 | 28 | QString exportUnit() const override; 29 | }; 30 | -------------------------------------------------------------------------------- /src/crontablib/ctmonth.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Month Implementation 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "ctmonth.h" 10 | #include 11 | 12 | QList CTMonth::shortName; 13 | 14 | CTMonth::CTMonth(const QString &tokStr) 15 | : CTUnit(CTMonth::MINIMUM, CTMonth::MAXIMUM, tokStr) 16 | { 17 | } 18 | 19 | QString CTMonth::describe() const 20 | { 21 | initializeNames(); 22 | return (enabledCount() == CTMonth::MAXIMUM) ? i18n("every month") : CTUnit::genericDescribe(shortName); 23 | } 24 | 25 | QString CTMonth::getName(const int ndx) 26 | { 27 | initializeNames(); 28 | return shortName.at(ndx); 29 | } 30 | 31 | void CTMonth::initializeNames() 32 | { 33 | if (shortName.isEmpty()) { 34 | shortName << QLatin1String("") << i18n("January") << i18n("February") << i18n("March") << i18n("April") << i18nc("May long", "May") << i18n("June") 35 | << i18nc("July long", "July") << i18n("August") << i18n("September") << i18n("October") << i18n("November") << i18n("December"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/crontablib/ctmonth.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Month Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | #include "ctunit.h" 15 | 16 | /** 17 | * Scheduled task months. 18 | */ 19 | class CTMonth : public CTUnit 20 | { 21 | public: 22 | /** 23 | * Constructs from a tokenized string. 24 | */ 25 | explicit CTMonth(const QString &tokStr = QLatin1String("")); 26 | 27 | /** 28 | * Get natural language description. 29 | */ 30 | virtual QString describe() const; 31 | 32 | /** 33 | * Get month name. 34 | */ 35 | static QString getName(const int ndx); 36 | 37 | static const int MINIMUM = 1; 38 | static const int MAXIMUM = 12; 39 | 40 | private: 41 | static void initializeNames(); 42 | static QList shortName; 43 | }; 44 | -------------------------------------------------------------------------------- /src/crontablib/cttask.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Task Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "ctdom.h" 17 | #include "ctdow.h" 18 | #include "cthour.h" 19 | #include "ctminute.h" 20 | #include "ctmonth.h" 21 | 22 | /** 23 | * A scheduled task (encapsulation of crontab entry). Encapsulates 24 | * parsing, tokenization, and natural language description. 25 | */ 26 | class CTTask 27 | { 28 | public: 29 | /** 30 | * Constructs scheduled task from crontab format string. 31 | */ 32 | explicit CTTask(const QString &tokenString, const QString &_comment, const QString &_userLogin, bool syscron = false); 33 | 34 | /** 35 | * Copy constructor. 36 | */ 37 | CTTask(const CTTask &source); 38 | 39 | /** 40 | * Assignment operator. 41 | */ 42 | CTTask &operator=(const CTTask &source); 43 | 44 | /** 45 | * Tokenizes scheduled task to crontab format. 46 | */ 47 | QString exportTask(); 48 | 49 | /** 50 | * Scheduling using the cron format. 51 | */ 52 | QString schedulingCronFormat() const; 53 | 54 | /** 55 | * Mark changes as applied. 56 | */ 57 | void apply(); 58 | 59 | /** 60 | * Cancel changes. 61 | */ 62 | void cancel(); 63 | 64 | /** 65 | * Indicates whether or not the task has been modified. 66 | */ 67 | bool dirty() const; 68 | 69 | /** 70 | * Returns natural language description of the task's schedule. 71 | */ 72 | QString describe() const; 73 | 74 | /** 75 | * Indicates whether or not the task belongs to the system crontab. 76 | */ 77 | bool isSystemCrontab() const; 78 | 79 | void setSystemCrontab(bool systemCrontab); 80 | 81 | QIcon commandIcon() const; 82 | 83 | /** 84 | * Internal methods 85 | */ 86 | QPair unQuoteCommand() const; 87 | QStringList separatePathCommand(const QString &command, bool quoted) const; 88 | QString decryptBinaryCommand(const QString &command) const; 89 | 90 | QString completeCommandPath() const; 91 | 92 | CTMonth month; 93 | CTDayOfMonth dayOfMonth; 94 | CTDayOfWeek dayOfWeek; 95 | CTHour hour; 96 | CTMinute minute; 97 | 98 | QString userLogin; 99 | QString command; 100 | QString comment; 101 | 102 | bool enabled; 103 | bool reboot; 104 | 105 | private: 106 | inline bool isSpaceAt(const QString &token, int pos) 107 | { 108 | if (pos >= token.length()) { 109 | return false; 110 | } 111 | 112 | if (token.at(pos) == QLatin1Char(' ')) { 113 | return true; 114 | } 115 | 116 | return false; 117 | } 118 | 119 | QString describeDayOfMonth() const; 120 | QString describeDayOfWeek() const; 121 | QString describeDateAndHours() const; 122 | 123 | QString createTimeFormat() const; 124 | QString createDateFormat() const; 125 | 126 | bool mSystemCrontab; 127 | 128 | QString mInitialUserLogin; 129 | QString mInitialCommand; 130 | QString mInitialComment; 131 | bool mInitialEnabled; 132 | bool mInitialReboot; 133 | }; 134 | -------------------------------------------------------------------------------- /src/crontablib/ctunit.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Unit of Time Interval Implementation 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "ctunit.h" 10 | 11 | #include 12 | 13 | CTUnit::CTUnit(int _min, int _max, const QString &tokStr) 14 | { 15 | mMin = _min; 16 | mMax = _max; 17 | initialize(tokStr); 18 | } 19 | 20 | CTUnit::CTUnit(const CTUnit &source) 21 | { 22 | mMin = source.mMin; 23 | mMax = source.mMax; 24 | 25 | mInitialEnabled.clear(); 26 | mEnabled.clear(); 27 | mInitialEnabled.reserve(mMax + 1); 28 | for (int i = 0; i <= mMax; i++) { 29 | mInitialEnabled.append(false); 30 | mEnabled.append(source.mEnabled.at(i)); 31 | } 32 | 33 | mInitialTokStr = QLatin1String(""); 34 | mDirty = true; 35 | } 36 | 37 | CTUnit::~CTUnit() 38 | { 39 | } 40 | 41 | CTUnit &CTUnit::operator=(const CTUnit &unit) 42 | { 43 | if (this == &unit) { 44 | return *this; 45 | } 46 | 47 | mMin = unit.mMin; 48 | mMax = unit.mMax; 49 | 50 | mEnabled.clear(); 51 | for (int i = 0; i <= mMax; i++) { 52 | mEnabled.append(unit.mEnabled[i]); 53 | } 54 | mDirty = true; 55 | 56 | return *this; 57 | } 58 | 59 | void CTUnit::initialize(const QString &tokStr) 60 | { 61 | mEnabled.clear(); 62 | for (int i = 0; i <= mMax; i++) { 63 | mEnabled.append(false); 64 | mInitialEnabled.append(false); 65 | } 66 | 67 | for (int i = mMin; i <= mMax; i++) { 68 | mInitialEnabled[i] = mEnabled[i]; 69 | } 70 | 71 | parse(tokStr); 72 | mInitialTokStr = tokStr; 73 | mDirty = false; 74 | } 75 | 76 | void CTUnit::parse(const QString &tokenString) 77 | { 78 | QString tokStr = tokenString; 79 | 80 | // subelement is that which is between commas 81 | QString subelement; 82 | int commapos, slashpos, dashpos; 83 | int beginat, endat, step; 84 | 85 | // loop through each subelement 86 | tokStr += QLatin1Char(','); 87 | while ((commapos = tokStr.indexOf(QLatin1Char(','))) > 0) { 88 | subelement = tokStr.mid(0, commapos); 89 | 90 | // find "/" to determine step 91 | slashpos = subelement.indexOf(QLatin1Char('/')); 92 | if (slashpos == -1) { 93 | step = 1; 94 | slashpos = subelement.length(); 95 | } else { 96 | step = fieldToValue(subelement.mid(slashpos + 1, subelement.length() - slashpos - 1)); 97 | if (step < 1) { 98 | step = 1; 99 | } 100 | } 101 | 102 | // find "=" to determine range 103 | dashpos = subelement.indexOf(QLatin1Char('-')); 104 | if (dashpos == -1) { 105 | // deal with "*" 106 | if (subelement.mid(0, slashpos) == QLatin1Char('*')) { 107 | beginat = mMin; 108 | endat = mMax; 109 | } else { 110 | beginat = fieldToValue(subelement.mid(0, slashpos)); 111 | endat = beginat; 112 | } 113 | } else { 114 | beginat = fieldToValue(subelement.mid(0, dashpos)); 115 | endat = fieldToValue(subelement.mid(dashpos + 1, slashpos - dashpos - 1)); 116 | } 117 | 118 | // ignore out of range 119 | if (beginat < 0) { 120 | beginat = 0; 121 | } 122 | if (endat > mMax) { 123 | endat = mMax; 124 | } 125 | 126 | // setup enabled 127 | for (int i = beginat; i <= endat; i += step) { 128 | mInitialEnabled[i] = mEnabled[i] = true; 129 | } 130 | 131 | tokStr = tokStr.mid(commapos + 1, tokStr.length() - commapos - 1); 132 | } 133 | } 134 | 135 | QString CTUnit::exportUnit() const 136 | { 137 | if (!mDirty) { 138 | return mInitialTokStr; 139 | } 140 | 141 | if (isAllEnabled()) { 142 | return QStringLiteral("*"); 143 | } 144 | 145 | int total = enabledCount(); 146 | int count = 0; 147 | QString tokenizeUnit; 148 | 149 | for (int num = mMin; num <= mMax; num++) { 150 | if (mEnabled[num]) { 151 | tokenizeUnit += QString::number(num); 152 | count++; 153 | 154 | if (count < total) { 155 | tokenizeUnit += QLatin1Char(','); 156 | } 157 | } 158 | } 159 | 160 | return tokenizeUnit; 161 | } 162 | 163 | QString CTUnit::genericDescribe(const QList &label) const 164 | { 165 | int total(enabledCount()); 166 | int count(0); 167 | QString tmpStr; 168 | for (int i = mMin; i <= mMax; i++) { 169 | if (mEnabled[i]) { 170 | tmpStr += label.at(i); 171 | count++; 172 | switch (total - count) { 173 | case 0: 174 | break; 175 | case 1: 176 | if (total > 2) { 177 | tmpStr += i18n(","); 178 | } 179 | tmpStr += i18n(" and "); 180 | break; 181 | default: 182 | tmpStr += i18n(", "); 183 | break; 184 | } 185 | } 186 | } 187 | return tmpStr; 188 | } 189 | 190 | int CTUnit::minimum() const 191 | { 192 | return mMin; 193 | } 194 | 195 | int CTUnit::maximum() const 196 | { 197 | return mMax; 198 | } 199 | 200 | bool CTUnit::isEnabled(int pos) const 201 | { 202 | return mEnabled.at(pos); 203 | } 204 | 205 | bool CTUnit::isAllEnabled() const 206 | { 207 | for (int i = mMin; i <= mMax; i++) { 208 | if (!mEnabled.at(i)) { 209 | return false; 210 | } 211 | } 212 | 213 | return true; 214 | } 215 | 216 | void CTUnit::setEnabled(int pos, bool value) 217 | { 218 | mEnabled[pos] = value; 219 | mDirty = true; 220 | return; 221 | } 222 | 223 | bool CTUnit::isDirty() const 224 | { 225 | return mDirty; 226 | } 227 | 228 | int CTUnit::enabledCount() const 229 | { 230 | int total(0); 231 | for (int i = mMin; i <= mMax; i++) { 232 | total += (mEnabled[i] == true); 233 | } 234 | return total; 235 | } 236 | 237 | void CTUnit::apply() 238 | { 239 | mInitialTokStr = exportUnit(); 240 | for (int i = mMin; i <= mMax; i++) { 241 | mInitialEnabled[i] = mEnabled.at(i); 242 | } 243 | mDirty = false; 244 | } 245 | 246 | void CTUnit::cancel() 247 | { 248 | for (int i = mMin; i <= mMax; i++) { 249 | mEnabled[i] = mInitialEnabled.at(i); 250 | } 251 | mDirty = false; 252 | } 253 | 254 | int CTUnit::fieldToValue(const QString &entry) const 255 | { 256 | QString lower = entry.toLower(); 257 | 258 | // check for days 259 | QList days; 260 | days << QStringLiteral("sun") << QStringLiteral("mon") << QStringLiteral("tue") << QStringLiteral("wed") << QStringLiteral("thu") << QStringLiteral("fri") 261 | << QStringLiteral("sat"); 262 | 263 | int day = days.indexOf(lower); 264 | if (day != -1) { 265 | return day; 266 | } 267 | 268 | // check for months 269 | QList months; 270 | months << QLatin1String("") << QStringLiteral("jan") << QStringLiteral("feb") << QStringLiteral("mar") << QStringLiteral("apr") << QStringLiteral("may") 271 | << QStringLiteral("jun") << QStringLiteral("jul") << QStringLiteral("aug") << QStringLiteral("sep") << QStringLiteral("oct") << QStringLiteral("nov") 272 | << QStringLiteral("dec"); 273 | 274 | int month = months.indexOf(lower); 275 | if (month != -1) { 276 | return month; 277 | } 278 | 279 | // If the string does not match a day ora month, then it's a simple number (minute, hour or day of month) 280 | return entry.toInt(); 281 | } 282 | 283 | /** 284 | * Find a period in enabled values 285 | * If no period has been found, return 0 286 | */ 287 | int CTUnit::findPeriod(const QList &periods) const 288 | { 289 | for (int period : periods) { 290 | bool validPeriod = true; 291 | 292 | for (int i = minimum(); i <= maximum(); i++) { 293 | bool periodTesting; 294 | if ((double)i / (double)period == i / period) { 295 | periodTesting = true; 296 | } else { 297 | periodTesting = false; 298 | } 299 | 300 | if (isEnabled(i) != periodTesting) { 301 | validPeriod = false; 302 | break; 303 | } 304 | } 305 | 306 | if (validPeriod) { 307 | return period; 308 | } 309 | } 310 | 311 | return 0; 312 | } 313 | -------------------------------------------------------------------------------- /src/crontablib/ctunit.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Unit of Time Interval Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | /** 15 | * A cron table unit parser and tokenizer. 16 | * Parses/tokenizes unit such as "0-3,5,6,10-30/5" 17 | * Also provides default natural language description. 18 | */ 19 | class CTUnit 20 | { 21 | protected: 22 | CTUnit(int min, int max, const QString &tokStr = QLatin1String("")); 23 | 24 | /** 25 | * Get default natural language description. 26 | */ 27 | virtual QString genericDescribe(const QList &label) const; 28 | 29 | public: 30 | /** 31 | * Base initial image as empty and copy enabled intervals. 32 | */ 33 | CTUnit(const CTUnit &source); 34 | 35 | /** 36 | * Destructor. 37 | */ 38 | virtual ~CTUnit(); 39 | 40 | /** 41 | * Copy enabled intervals. 42 | */ 43 | CTUnit &operator=(const CTUnit &unit); 44 | 45 | /** 46 | * Tokenizes unit into string such as 47 | * "0,1,2,3,5,6,10,15,20,25,30". 48 | */ 49 | virtual QString exportUnit() const; 50 | 51 | /** 52 | * Parses unit such as "0-3,5,6,10-30/5". 53 | * And initialize array of enabled intervals. 54 | */ 55 | void initialize(const QString &tokStr = QLatin1String("")); 56 | 57 | /** 58 | * Lower bound. 59 | */ 60 | int minimum() const; 61 | 62 | /** 63 | * Upper bound. 64 | */ 65 | int maximum() const; 66 | 67 | /** 68 | * Accessor. 69 | */ 70 | bool isEnabled(int pos) const; 71 | 72 | bool isAllEnabled() const; 73 | 74 | void setEnabled(int pos, bool value); 75 | 76 | /** 77 | * Indicates whether enabled intervals have been modified. 78 | */ 79 | bool isDirty() const; 80 | 81 | /** 82 | * Total count of enabled intervals. 83 | */ 84 | int enabledCount() const; 85 | 86 | /** 87 | * Mark changes as applied. 88 | */ 89 | void apply(); 90 | 91 | /** 92 | * Mark cancel changes and revert to initial or last applied 93 | * image. 94 | */ 95 | void cancel(); 96 | 97 | /** 98 | * Find a period in enabled values 99 | * If no period has been found, return 0 100 | */ 101 | int findPeriod(const QList &periods) const; 102 | 103 | protected: 104 | /** 105 | * Parses unit such as "0-3,5,6,10-30/5". 106 | * Does not initialize array of enabled intervals. 107 | */ 108 | void parse(const QString &tokenString = QLatin1String("")); 109 | 110 | private: 111 | int mMin; 112 | int mMax; 113 | 114 | int fieldToValue(const QString &entry) const; 115 | bool mDirty; 116 | 117 | QList mEnabled; 118 | QList mInitialEnabled; 119 | 120 | QString mInitialTokStr; 121 | 122 | public: 123 | /** 124 | * Constant indicating short format. 125 | */ 126 | static const bool shortFormat = false; 127 | 128 | /** 129 | * Constant indicating long format. 130 | */ 131 | static const bool longFormat = true; 132 | }; 133 | -------------------------------------------------------------------------------- /src/crontablib/ctvariable.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Environment Variable Implementation 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "ctvariable.h" 10 | 11 | #include 12 | #include 13 | 14 | #include "ctHelper.h" 15 | 16 | CTVariable::CTVariable(const QString &tokenString, const QString &_comment, const QString &_userLogin) 17 | { 18 | QString tokStr = tokenString; 19 | 20 | if (tokStr.mid(0, 2) == QLatin1String("#\\")) { 21 | tokStr = tokStr.mid(2, tokStr.length() - 2); 22 | enabled = false; 23 | } else { 24 | enabled = true; 25 | } 26 | 27 | const int spacepos = tokStr.indexOf(QRegularExpression(QLatin1String("[ =]"))); 28 | variable = tokStr.mid(0, spacepos); 29 | 30 | value = tokStr.mid(spacepos + 1, tokStr.length() - spacepos - 1); 31 | comment = _comment; 32 | 33 | userLogin = _userLogin; 34 | 35 | mInitialVariable = variable; 36 | mInitialValue = value; 37 | mInitialComment = comment; 38 | mInitialUserLogin = userLogin; 39 | mInitialEnabled = enabled; 40 | } 41 | 42 | CTVariable::CTVariable(const CTVariable &source) 43 | : variable(source.variable) 44 | , value(source.value) 45 | , comment(source.comment) 46 | , userLogin(source.userLogin) 47 | , enabled(source.enabled) 48 | , mInitialVariable(QLatin1String("")) 49 | , mInitialValue(QLatin1String("")) 50 | , mInitialComment(QLatin1String("")) 51 | , mInitialUserLogin(QLatin1String("")) 52 | , mInitialEnabled(true) 53 | { 54 | } 55 | 56 | CTVariable &CTVariable::operator=(const CTVariable &source) 57 | { 58 | if (this == &source) { 59 | return *this; 60 | } 61 | 62 | variable = source.variable; 63 | value = source.value; 64 | comment = source.comment; 65 | userLogin = source.userLogin; 66 | enabled = source.enabled; 67 | 68 | mInitialVariable = QLatin1String(""); 69 | mInitialValue = QLatin1String(""); 70 | mInitialComment = QLatin1String(""); 71 | mInitialUserLogin = QLatin1String(""); 72 | mInitialEnabled = true; 73 | return *this; 74 | } 75 | 76 | QString CTVariable::exportVariable() 77 | { 78 | QString exportVariable = CTHelper::exportComment(comment); 79 | 80 | if (!enabled) { 81 | exportVariable += QLatin1String("#\\"); 82 | } 83 | 84 | exportVariable += variable + QLatin1String("=") + value + QLatin1String("\n"); 85 | 86 | return exportVariable; 87 | } 88 | 89 | void CTVariable::apply() 90 | { 91 | mInitialVariable = variable; 92 | mInitialValue = value; 93 | mInitialComment = comment; 94 | mInitialUserLogin = userLogin; 95 | 96 | mInitialEnabled = enabled; 97 | } 98 | 99 | void CTVariable::cancel() 100 | { 101 | variable = mInitialVariable; 102 | value = mInitialValue; 103 | comment = mInitialComment; 104 | userLogin = mInitialUserLogin; 105 | enabled = mInitialEnabled; 106 | } 107 | 108 | bool CTVariable::dirty() const 109 | { 110 | return (variable != mInitialVariable) || (value != mInitialValue) || (comment != mInitialComment) || (userLogin != mInitialUserLogin) 111 | || (enabled != mInitialEnabled); 112 | } 113 | 114 | QIcon CTVariable::variableIcon() const 115 | { 116 | if (variable == QLatin1String("MAILTO")) { 117 | return QIcon::fromTheme(QLatin1String("mail-message")); 118 | } else if (variable == QLatin1String("SHELL")) { 119 | return QIcon::fromTheme(QLatin1String("utilities-terminal")); 120 | } else if (variable == QLatin1String("HOME")) { 121 | return QIcon::fromTheme(QLatin1String("go-home")); 122 | } else if (variable == QLatin1String("PATH")) { 123 | return QIcon::fromTheme(QLatin1String("folder")); 124 | } else if (variable == QLatin1String("LD_CONFIG_PATH")) { 125 | return QIcon::fromTheme(QLatin1String("application-x-sharedlib")); 126 | } 127 | 128 | return QIcon::fromTheme(QLatin1String("text-plain")); 129 | } 130 | 131 | QString CTVariable::information() const 132 | { 133 | if (variable == QLatin1String("HOME")) { 134 | return i18n("Override default home folder."); 135 | } else if (variable == QLatin1String("MAILTO")) { 136 | return i18n("Email output to specified account."); 137 | } else if (variable == QLatin1String("SHELL")) { 138 | return i18n("Override default shell."); 139 | } else if (variable == QLatin1String("PATH")) { 140 | return i18n("Folders to search for program files."); 141 | } else if (variable == QLatin1String("LD_CONFIG_PATH")) { 142 | return i18n("Dynamic libraries location."); 143 | } 144 | 145 | return i18n("Local Variable"); 146 | } 147 | -------------------------------------------------------------------------------- /src/crontablib/ctvariable.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Environment Variable Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | /** 15 | * An environment variable (encapsulation of crontab environment variable 16 | * entry). Encapsulates parsing and tokenization. 17 | */ 18 | class CTVariable 19 | { 20 | public: 21 | /** 22 | * Constructs environment variable from crontab format string. 23 | */ 24 | explicit CTVariable(const QString &tokenString, const QString &_comment, const QString &_userLogin); 25 | 26 | /** 27 | * Copy constructor. 28 | */ 29 | CTVariable(const CTVariable &source); 30 | 31 | /** 32 | * Assignment operator. 33 | */ 34 | CTVariable &operator=(const CTVariable &source); 35 | 36 | /** 37 | * Tokenizes environment variable to crontab format. 38 | */ 39 | QString exportVariable(); 40 | 41 | /** 42 | * Mark changes as applied. 43 | */ 44 | void apply(); 45 | 46 | /** 47 | * Cancel changes. 48 | */ 49 | void cancel(); 50 | 51 | /** 52 | * Indicates whether or not the environment variable has been modified. 53 | */ 54 | bool dirty() const; 55 | 56 | QIcon variableIcon() const; 57 | 58 | QString information() const; 59 | 60 | QString variable; 61 | QString value; 62 | QString comment; 63 | QString userLogin; 64 | 65 | bool enabled; 66 | 67 | private: 68 | QString mInitialVariable; 69 | QString mInitialValue; 70 | QString mInitialComment; 71 | QString mInitialUserLogin; 72 | bool mInitialEnabled; 73 | }; 74 | -------------------------------------------------------------------------------- /src/genericmodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "genericmodel.h" 10 | 11 | #include 12 | #include 13 | 14 | #include "kcm_cron_debug.h" 15 | 16 | GenericModel::GenericModel(QObject *parent) noexcept 17 | : QAbstractListModel(parent) 18 | , mProxyModel(new QSortFilterProxyModel(this)) 19 | , mSelectionModel(new QItemSelectionModel(this)) 20 | { 21 | mProxyModel->setSourceModel(this); 22 | mProxyModel->setSortRole(Qt::UserRole + 1); 23 | 24 | mSelectionModel->setModel(mProxyModel); 25 | } 26 | 27 | void GenericModel::selectAll() 28 | { 29 | qCDebug(KCM_CRON_LOG) << "Trying to select all table elements"; 30 | 31 | const int rowCount = mSelectionModel->model()->rowCount(); 32 | if (rowCount < 1) { 33 | return; 34 | } 35 | 36 | for (int i = 0; i < rowCount; ++i) { 37 | QModelIndex index = mProxyModel->index(i, 0); 38 | mSelectionModel->select(index, QItemSelectionModel::SelectionFlag::Select); 39 | } 40 | mSelectionModel->setCurrentIndex(mProxyModel->mapToSource(mProxyModel->index(0, 0)), QItemSelectionModel::SelectionFlag::NoUpdate); 41 | } 42 | 43 | QSortFilterProxyModel *GenericModel::proxyModel() const 44 | { 45 | return mProxyModel; 46 | } 47 | 48 | QItemSelectionModel *GenericModel::selectionModel() const 49 | { 50 | return mSelectionModel; 51 | } 52 | 53 | #include "genericmodel.moc" 54 | 55 | #include "moc_genericmodel.cpp" 56 | -------------------------------------------------------------------------------- /src/genericmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | class QSortFilterProxyModel; 14 | class QItemSelectionModel; 15 | class CTCron; 16 | 17 | class GenericModel : public QAbstractListModel 18 | { 19 | Q_OBJECT 20 | 21 | Q_PROPERTY(QItemSelectionModel *selectionModel READ selectionModel CONSTANT FINAL) 22 | Q_PROPERTY(QSortFilterProxyModel *proxyModel READ proxyModel CONSTANT FINAL) 23 | 24 | Q_PROPERTY(bool needUserColumn READ needUserColumn NOTIFY needUserColumnChanged FINAL) 25 | Q_PROPERTY(Qt::CheckState enabledState READ enabledState WRITE setEnabledState NOTIFY enabledStateChanged) 26 | 27 | public: 28 | explicit GenericModel(QObject *parent) noexcept; 29 | ~GenericModel() override = default; 30 | 31 | Q_INVOKABLE void selectAll(); 32 | 33 | Q_INVOKABLE virtual void removeSelected() = 0; 34 | 35 | Q_INVOKABLE virtual void copy() = 0; 36 | Q_INVOKABLE virtual void cut() = 0; 37 | Q_INVOKABLE virtual void paste() = 0; 38 | 39 | virtual bool needUserColumn() = 0; 40 | 41 | virtual Qt::CheckState enabledState() = 0; 42 | virtual void setEnabledState(Qt::CheckState state) = 0; 43 | 44 | Q_SIGNALS: 45 | void needUserColumnChanged(); 46 | void enabledStateChanged(); 47 | 48 | public Q_SLOTS: 49 | virtual void refresh(CTCron *ctCron) = 0; 50 | 51 | protected: 52 | virtual void clear() = 0; 53 | virtual int enabledCount() = 0; 54 | 55 | public: 56 | QSortFilterProxyModel *proxyModel() const; 57 | QItemSelectionModel *selectionModel() const; 58 | 59 | protected: 60 | QSortFilterProxyModel *const mProxyModel; 61 | QItemSelectionModel *const mSelectionModel; 62 | 63 | CTCron *mCtCron = nullptr; 64 | }; 65 | -------------------------------------------------------------------------------- /src/helper/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | add_executable(kcron_helper kcronhelper.cpp kcronhelper.h) 3 | target_link_libraries(kcron_helper PRIVATE Qt6::Core KF6::AuthCore) 4 | 5 | ecm_qt_declare_logging_category(kcron_helper 6 | HEADER kcm_cron_helper_debug.h 7 | IDENTIFIER KCM_CRON_HELPER_LOG 8 | CATEGORY_NAME org.kde.kcm.cron_helper 9 | DESCRIPTION "kcm cron helper" 10 | EXPORT KCRON_HELPER 11 | ) 12 | 13 | # The following need to be disabled in debug builds, if lacking root permissions. 14 | # Build the project to generate the files, then install them manually: 15 | # /usr/share/dbus-1/system-services/ <- local.kcron.crontab.service 16 | # /usr/share/polkit-1/actions/ <- local.kcron.crontab.policy 17 | # /usr/share/dbus-1/system.d/ <- local.kcron.crontab.conf 18 | install(TARGETS kcron_helper DESTINATION ${KAUTH_HELPER_INSTALL_DIR}) 19 | kauth_install_helper_files(kcron_helper local.kcron.crontab root) 20 | kauth_install_actions(local.kcron.crontab local.kcron.crontab.actions) 21 | -------------------------------------------------------------------------------- /src/helper/README: -------------------------------------------------------------------------------- 1 | The contents of this folder, as well as a few tweaks 2 | regarding KAuth and polkit permissions were sourced 3 | and modified from the GPL-licensed project Zeit by lolmu. 4 | 5 | The original project can be found here https://github.com/loimu/zeit. 6 | -------------------------------------------------------------------------------- /src/helper/kcronhelper.cpp: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Copyright (C) 2015-2021 Blaze 3 | * 4 | * This file is part of Zeit. 5 | * 6 | * Zeit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Zeit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Zeit. If not, see . 18 | * ======================================================================== * 19 | * 20 | * This file was modified to fit into the project Kcron. 21 | * The same license terms apply. 22 | * 23 | * ======================================================================== */ 24 | 25 | #include 26 | 27 | #include "kcm_cron_helper_debug.h" 28 | 29 | #include "kcronhelper.h" 30 | 31 | ActionReply KcronHelper::save(const QVariantMap &args) 32 | { 33 | qCDebug(KCM_CRON_HELPER_LOG) << "running actions"; 34 | 35 | QByteArray newCronData; 36 | { 37 | const QString source = args[QLatin1String("source")].toString(); 38 | QFile sourceFile(source); 39 | if (!sourceFile.open(QIODevice::ReadOnly)) { 40 | qCWarning(KCM_CRON_HELPER_LOG) << "can't open source file for reading" << source << sourceFile.errorString(); 41 | ActionReply reply = ActionReply::HelperErrorReply(); 42 | reply.setErrorDescription(sourceFile.errorString()); 43 | return reply; 44 | } 45 | 46 | newCronData = sourceFile.readAll(); 47 | } 48 | 49 | { 50 | const QString destination = QStringLiteral("/etc/crontab"); 51 | QFile destinationFile(destination); 52 | if (!destinationFile.open(QIODevice::WriteOnly)) { 53 | ActionReply reply = ActionReply::HelperErrorReply(); 54 | qCWarning(KCM_CRON_HELPER_LOG) << "can't open destination file for writing" << destinationFile.errorString(); 55 | reply.setErrorDescription(destinationFile.errorString()); 56 | return reply; 57 | } 58 | 59 | if (destinationFile.write(newCronData) < 0) { 60 | ActionReply reply = ActionReply::HelperErrorReply(); 61 | qCWarning(KCM_CRON_HELPER_LOG) << "writing to destination file failed" << destinationFile.errorString(); 62 | reply.setErrorDescription(destinationFile.errorString()); 63 | } 64 | } 65 | 66 | return ActionReply::SuccessReply(); 67 | } 68 | 69 | KAUTH_HELPER_MAIN("local.kcron.crontab", KcronHelper) 70 | 71 | #include "moc_kcronhelper.cpp" 72 | -------------------------------------------------------------------------------- /src/helper/kcronhelper.h: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Copyright (C) 2015-2021 Blaze 3 | * 4 | * This file is part of Zeit. 5 | * 6 | * Zeit is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * Zeit is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with Zeit. If not, see . 18 | * ======================================================================== * 19 | * 20 | * This file was modified to fit into the project Kcron. 21 | * The same license terms apply. 22 | * 23 | * ======================================================================== */ 24 | 25 | #pragma once 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | using namespace KAuth; 32 | 33 | // usage of fully qualified type here renders the helper app fully ;) unusable 34 | // clazy:excludeall=fully-qualified-moc-types 35 | class KcronHelper : public QObject 36 | { 37 | Q_OBJECT 38 | 39 | public Q_SLOTS: 40 | ActionReply save(const QVariantMap &args); 41 | }; 42 | -------------------------------------------------------------------------------- /src/helper/local.kcron.crontab.actions: -------------------------------------------------------------------------------- 1 | [Domain] 2 | Name=Crontab Actions 3 | Name[ar]=إجراءات كرونتاب 4 | Name[ast]=Aiciones de crontab 5 | Name[be]=Дзеянні Crontab 6 | Name[bg]=Действия на Crontab 7 | Name[ca]=Accions de «crontab» 8 | Name[ca@valencia]=Accions de «crontab» 9 | Name[cs]=Činnosti Crontabu 10 | Name[de]=Crontab-Aktionen 11 | Name[el]=Crontab ενέργειες 12 | Name[en_GB]=Crontab Actions 13 | Name[eo]=Crontab-agoj 14 | Name[es]=Acciones de crontab 15 | Name[eu]=Crontab ekintzak 16 | Name[fi]=Crontab-toiminnot 17 | Name[fr]=Actions de Crontab 18 | Name[gl]=Accións de crontab 19 | Name[he]=פעולות Crontab 20 | Name[hu]=Crontab műveletek 21 | Name[ia]=Actiones de Crontab 22 | Name[is]=Crontab-aðgerðir 23 | Name[it]=Azioni Crontab 24 | Name[ka]=Crontab -ის ქმედებები 25 | Name[ko]=crontab 동작 26 | Name[lt]=Crontab veiksmai 27 | Name[lv]=„Crontab“ darbības 28 | Name[nl]=Crontab-acties 29 | Name[nn]=Crontab-handlingar 30 | Name[pl]=Działania Crontab 31 | Name[pt]=Acções do Crontab 32 | Name[pt_BR]=Ações do crontab 33 | Name[ro]=Acțiuni Crontab 34 | Name[ru]=Действия Crontab 35 | Name[sa]=क्रोन्टब क्रियाएँ 36 | Name[sk]=Akcie Crontab 37 | Name[sl]=Dejavnosti Crontab 38 | Name[sv]=Åtgärder i crontab 39 | Name[tr]=Crontab Eylemleri 40 | Name[uk]=Дії Crontab 41 | Name[vi]=Hành động crontab 42 | Name[x-test]=xxCrontab Actionsxx 43 | Name[zh_CN]=Crontab 动作 44 | Name[zh_TW]=Crontab 動作 45 | Policy=auth_admin 46 | Persistence=session 47 | 48 | [local.kcron.crontab.save] 49 | Name=Write Crontab 50 | Name[ar]=اكتب كرونتاب 51 | Name[be]=Запісаць Crontab 52 | Name[bg]=Запис в Crontab 53 | Name[ca]=Escriu el «contrab» 54 | Name[ca@valencia]=Escriu el «contrab» 55 | Name[cs]=Zapsat do Crontabu 56 | Name[de]=Crontab schreiben 57 | Name[el]=Εγγραφή στο Crontab 58 | Name[en_GB]=Write Crontab 59 | Name[eo]=Skribi Crontab 60 | Name[es]=Escribir crontab 61 | Name[eu]=Idatzi Crontab 62 | Name[fi]=Kirjoita crontab 63 | Name[fr]=Écrire Crontab 64 | Name[gl]=Escribir o crontab 65 | Name[he]=כתיבת Crontab 66 | Name[hu]=Crontab írása 67 | Name[ia]=Scribe Crontab 68 | Name[is]=Skrifa crontab 69 | Name[it]=Scrivi Crontab 70 | Name[ka]=Crontab -ის ჩაწერა 71 | Name[ko]=crontab 쓰기 72 | Name[lt]=Rašyti Crontab 73 | Name[lv]=Rakstiet „Crontab“ 74 | Name[nl]=Crontab schrijven 75 | Name[nn]=Lagra Crontab 76 | Name[pl]=Zapisz Crontab 77 | Name[pt]=Gravar o Crontab 78 | Name[pt_BR]=Gravar crontab 79 | Name[ro]=Scrie Crontab 80 | Name[ru]=Запись Crontab 81 | Name[sa]=क्रोनटब लिखें 82 | Name[sk]=Zapísať crontab 83 | Name[sl]=Napiši Crontab 84 | Name[sv]=Skriv crontab 85 | Name[tr]=Crontab Yaz 86 | Name[uk]=Записати Crontab 87 | Name[vi]=Ghi crontab 88 | Name[x-test]=xxWrite Crontabxx 89 | Name[zh_CN]=编写 Crontab 90 | Name[zh_TW]=寫入 crontab 91 | Description=Write into the system crontab file 92 | Description[ar]=اكتب في ملف كرونتاب النظام 93 | Description[be]=Запісаць у сістэмны файл crontab 94 | Description[bg]=Запис в системна таблица на задачите 95 | Description[ca]=Escriu dins del fitxer «contrab» del sistema 96 | Description[ca@valencia]=Escriu dins del fitxer «contrab» del sistema 97 | Description[cs]=Zapíše do systémového souboru crontab 98 | Description[de]=In die Crontab-Datei des Systems schreiben 99 | Description[el]=Εγγραφή στο αρχείο crontab του συστήματος 100 | Description[en_GB]=Write into the system crontab file 101 | Description[eo]=Skribi en la sisteman crontab-dosieron 102 | Description[es]=Escribir en el archivo crontab del sistema 103 | Description[eu]=Idatzi sistemako crontab fitxategian 104 | Description[fi]=Kirjoita järjestelmän crontab-tiedostoon 105 | Description[fr]=Écrire dans le fichier « crontab » du système 106 | Description[gl]=Escribir no ficheiro crontab do sistema 107 | Description[he]=כתיבה לקובץ ה־crontab של המערכת 108 | Description[hu]=Írás a rendszer crontab fájljába 109 | Description[ia]=Scribe in le file de systena de crontab 110 | Description[is]=Skrifa í crontab-skrá kerfis 111 | Description[it]=Scrivi nel file crontab del sistema 112 | Description[ka]=სისტემური Crontab -ის ჩაწერა 113 | Description[ko]=시스템 crontab 파일에 쓰기 114 | Description[lt]=Rašyti į sistemos crontab failą 115 | Description[lv]=Rakstiet sistēmas „crontab“ datnē 116 | Description[nl]=In het systeem-crontab-bestand schrijven 117 | Description[nn]=Lagra til system-crontab-fila 118 | Description[pl]=Zapisz do pliku systemu crontab 119 | Description[pt]=Gravar no ficheiro 'crontab' do sistema 120 | Description[pt_BR]=Gravar no arquivo crontab do sistema 121 | Description[ro]=Scrie în fișierul crontab al sistemului 122 | Description[ru]=Запись в системный файл crontab 123 | Description[sa]=सिस्टम् crontab सञ्चिकायां लिखन्तु 124 | Description[sk]=Zapísať do systémového súboru crontab 125 | Description[sl]=Zapiši v sistemsko datoteko crontab 126 | Description[sv]=Skriv i systemets crontab-fil 127 | Description[tr]=Sistem crontab dosyasına yaz 128 | Description[uk]=Записати до загальносистемного файла crontab 129 | Description[vi]=Ghi vào tệp crontab hệ thống 130 | Description[x-test]=xxWrite into the system crontab filexx 131 | Description[zh_CN]=写入系统 crontab 文件 132 | Description[zh_TW]=寫入系統 crontab 檔案 133 | Policy=auth_admin 134 | Persistence=session 135 | -------------------------------------------------------------------------------- /src/kcmCron.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | KT icons implementation. 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 6 | -------------------------------------------------------------------- 7 | SPDX-License-Identifier: GPL-2.0-or-later 8 | */ 9 | 10 | #include "kcmCron.h" 11 | 12 | #include "cronPrinter.h" 13 | #include "ctInitializationError.h" 14 | #include "ctcron.h" 15 | #include "cthost.h" 16 | #include "task.h" 17 | #include "tasksmodel.h" 18 | #include "taskvalidator.h" 19 | #include "variable.h" 20 | #include "variablesmodel.h" 21 | 22 | #include "kcm_cron_debug.h" 23 | 24 | K_PLUGIN_FACTORY_WITH_JSON(KCMCronQmlFactory, "kcm_cron.json", registerPlugin();) 25 | 26 | KCMCron::KCMCron(QObject *parent, const KPluginMetaData &data) 27 | : KQuickManagedConfigModule(parent, data) 28 | , mTasksModel(new TasksModel(this)) 29 | , mVariablesModel(new VariablesModel(this)) 30 | , mPrinter(new CronPrinter(this)) 31 | { 32 | setButtons(Help | Apply | Default); 33 | registerTypes(); 34 | init(); 35 | 36 | QObject::connect(this, &KCMCron::mainUiReady, this, &KCMCron::onMainUiReady); 37 | 38 | QObject::connect(mVariablesModel, &VariablesModel::addVariable, this, &KCMCron::addVariable); 39 | QObject::connect(mVariablesModel, &VariablesModel::modifyVariable, this, &KCMCron::modifyVariable); 40 | QObject::connect(mVariablesModel, &VariablesModel::removeVariable, this, &KCMCron::removeVariable); 41 | 42 | QObject::connect(mTasksModel, &TasksModel::addTask, this, &KCMCron::addTask); 43 | QObject::connect(mTasksModel, &TasksModel::modifyTask, this, &KCMCron::modifyTask); 44 | QObject::connect(mTasksModel, &TasksModel::removeTask, this, &KCMCron::removeTask); 45 | } 46 | 47 | KCMCron::~KCMCron() 48 | { 49 | delete mCtHost; 50 | } 51 | 52 | void KCMCron::print() 53 | { 54 | qCWarning(KCM_CRON_LOG) << "Calling print"; 55 | mPrinter->print(currentCron()); 56 | } 57 | 58 | void KCMCron::load() 59 | { 60 | qCDebug(KCM_CRON_LOG) << "Calling load"; 61 | 62 | mCtHost->cancel(); 63 | } 64 | 65 | void KCMCron::save() 66 | { 67 | qCDebug(KCM_CRON_LOG) << "Saving crontab..."; 68 | 69 | CTSaveStatus saveStatus = mCtHost->save(currentCron()); 70 | if (saveStatus.isError()) { 71 | qCWarning(KCM_CRON_LOG) << saveStatus.errorMessage() << saveStatus.detailErrorMessage(); 72 | Q_EMIT showError(saveStatus.errorMessage(), saveStatus.detailErrorMessage()); 73 | } 74 | 75 | qCDebug(KCM_CRON_LOG) << "saved ct host"; 76 | } 77 | 78 | void KCMCron::defaults() 79 | { 80 | qCDebug(KCM_CRON_LOG) << "Loading defaults"; 81 | 82 | mCtHost->cancel(); 83 | } 84 | 85 | void KCMCron::addTask(Task *task) 86 | { 87 | currentCron()->addTask(task->task()); 88 | setNeedsSave(true); 89 | } 90 | 91 | void KCMCron::addVariable(Variable *variable) 92 | { 93 | currentCron()->addVariable(variable->variable()); 94 | setNeedsSave(true); 95 | } 96 | 97 | void KCMCron::modifyTask(Task *task) 98 | { 99 | currentCron()->modifyTask(task->task()); 100 | setNeedsSave(true); 101 | } 102 | 103 | void KCMCron::modifyVariable(Variable *variable) 104 | { 105 | currentCron()->modifyVariable(variable->variable()); 106 | setNeedsSave(true); 107 | } 108 | 109 | void KCMCron::removeTask(Task *task) 110 | { 111 | currentCron()->removeTask(task->task()); 112 | setNeedsSave(true); 113 | } 114 | 115 | void KCMCron::removeVariable(Variable *variable) 116 | { 117 | currentCron()->removeVariable(variable->variable()); 118 | setNeedsSave(true); 119 | } 120 | 121 | void KCMCron::onMainUiReady() 122 | { 123 | // Display greeting screen. 124 | // If there currently are no scheduled tasks... 125 | int taskCount = 0; 126 | for (CTCron *ctCron : std::as_const(mCtHost->mCrons)) { 127 | taskCount += ctCron->tasks().count(); 128 | } 129 | 130 | if (taskCount == 0) { 131 | Q_EMIT showOnboarding(); 132 | } 133 | } 134 | 135 | void KCMCron::init() 136 | { 137 | CTInitializationError ctInitializationError; 138 | mCtHost = new CTHost(QStringLiteral(CRONTAB_BINARY), ctInitializationError); 139 | 140 | if (ctInitializationError.hasErrorMessage()) { 141 | qCWarning(KCM_CRON_LOG) << "An error occurred while creating the CTHost object"; 142 | qCWarning(KCM_CRON_LOG) << "Message:" << ctInitializationError.errorMessage(); 143 | Q_EMIT showError(ctInitializationError.errorMessage()); 144 | } 145 | 146 | for (CTCron *ctCron : std::as_const(mCtHost->mCrons)) { 147 | mUserList.append(ctCron->userLogin()); 148 | } 149 | 150 | refreshCron(); 151 | } 152 | 153 | void KCMCron::registerTypes() 154 | { 155 | // @uri org.kde.private.kcms.cron 156 | const char uri[] = "org.kde.private.kcms.cron"; 157 | 158 | qmlRegisterUncreatableType(uri, 1, 0, "VariablesModel", QStringLiteral("Cannot create instances of VariablesModel")); 159 | qmlRegisterUncreatableType(uri, 1, 0, "TasksModel", QStringLiteral("Cannot create instances of TasksModel")); 160 | qmlRegisterType(uri, 1, 0, "TaskValidator"); 161 | } 162 | 163 | CTCron *KCMCron::currentCron() 164 | { 165 | if (mIsPersonalUse) { 166 | return mCtHost->findCurrentUserCron(); 167 | } else { 168 | return mCtHost->findSystemCron(); 169 | } 170 | } 171 | 172 | void KCMCron::refreshCron() 173 | { 174 | CTCron *ctCron = currentCron(); 175 | 176 | mTasksModel->refresh(ctCron); 177 | mVariablesModel->refresh(ctCron); 178 | } 179 | 180 | void KCMCron::refreshPages() 181 | { 182 | while (this->depth() > 1) { 183 | this->pop(); 184 | } 185 | } 186 | 187 | TasksModel *KCMCron::tasksModel() const noexcept 188 | { 189 | return mTasksModel; 190 | } 191 | 192 | VariablesModel *KCMCron::variablesModel() const noexcept 193 | { 194 | return mVariablesModel; 195 | } 196 | 197 | bool KCMCron::isPersonalUse() const noexcept 198 | { 199 | return mIsPersonalUse; 200 | } 201 | 202 | void KCMCron::setIsPersonalUse(bool flag) 203 | { 204 | if (flag != mIsPersonalUse) { 205 | mIsPersonalUse = flag; 206 | refreshCron(); 207 | Q_EMIT isPersonalUseChanged(); 208 | } 209 | } 210 | 211 | QStringList KCMCron::userList() const noexcept 212 | { 213 | return mUserList; 214 | } 215 | 216 | #include "kcmCron.moc" 217 | 218 | #include "moc_kcmCron.cpp" 219 | -------------------------------------------------------------------------------- /src/kcmCron.h: -------------------------------------------------------------------------------- 1 | /* 2 | KT icons. 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 1999 Gary Meyer 5 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 6 | -------------------------------------------------------------------- 7 | SPDX-License-Identifier: GPL-2.0-or-later 8 | */ 9 | 10 | #pragma once 11 | 12 | /** 13 | * Crontab binary executable location 14 | * The $PATH variable could be used 15 | */ 16 | #define CRONTAB_BINARY "crontab" 17 | 18 | #include 19 | 20 | class CTHost; 21 | class CTCron; 22 | class TasksModel; 23 | class Task; 24 | class VariablesModel; 25 | class Variable; 26 | class CronPrinter; 27 | 28 | class KCMCron : public KQuickManagedConfigModule 29 | { 30 | Q_OBJECT 31 | 32 | Q_PROPERTY(TasksModel *tasksModel READ tasksModel CONSTANT) 33 | Q_PROPERTY(VariablesModel *variablesModel READ variablesModel CONSTANT) 34 | 35 | Q_PROPERTY(bool isPersonalUse READ isPersonalUse WRITE setIsPersonalUse NOTIFY isPersonalUseChanged) 36 | Q_PROPERTY(QStringList userList READ userList CONSTANT) 37 | 38 | public: 39 | KCMCron(QObject *parent, const KPluginMetaData &data); 40 | ~KCMCron() override; 41 | 42 | Q_INVOKABLE void print(); 43 | 44 | /** 45 | * Remove the excess pages before pushing new page 46 | */ 47 | Q_INVOKABLE void refreshPages(); 48 | 49 | public Q_SLOTS: 50 | void load() override; 51 | void save() override; 52 | void defaults() override; 53 | 54 | Q_SIGNALS: 55 | void isPersonalUseChanged(); 56 | void showError(const QString &errorString, const QString &details = QString()); 57 | void showOnboarding(); 58 | 59 | private Q_SLOTS: 60 | void addTask(Task *task); 61 | void addVariable(Variable *variable); 62 | 63 | void modifyTask(Task *task); 64 | void modifyVariable(Variable *variable); 65 | 66 | void removeTask(Task *task); 67 | void removeVariable(Variable *variable); 68 | 69 | void onMainUiReady(); 70 | 71 | private: 72 | /** 73 | * Additional init 74 | */ 75 | void init(); 76 | 77 | /** 78 | * Registers model roles for use in qml 79 | */ 80 | void registerTypes(); 81 | 82 | /** 83 | * Returns the current user's data if the "Personal Cron" button is selected, otherwise returns system data 84 | */ 85 | CTCron *currentCron(); 86 | 87 | /** 88 | * Complete update of data in task and variable models 89 | */ 90 | void refreshCron(); 91 | 92 | TasksModel *tasksModel() const noexcept; 93 | VariablesModel *variablesModel() const noexcept; 94 | 95 | bool isPersonalUse() const noexcept; 96 | void setIsPersonalUse(bool flag); 97 | 98 | QStringList userList() const noexcept; 99 | 100 | private: 101 | CTHost *mCtHost = nullptr; 102 | TasksModel *const mTasksModel; 103 | VariablesModel *const mVariablesModel; 104 | CronPrinter *const mPrinter; 105 | 106 | bool mIsPersonalUse = true; 107 | QStringList mUserList; 108 | }; 109 | -------------------------------------------------------------------------------- /src/kcm_cron.json: -------------------------------------------------------------------------------- 1 | { 2 | "KPlugin": { 3 | "Description": "Configure and schedule tasks", 4 | "Description[ar]": "اضبط وجدول المهامّ", 5 | "Description[be]": "Наладжванне і планаванне задач", 6 | "Description[bg]": "Задаване и настройване на задачи", 7 | "Description[ca@valencia]": "Configura i planifica les tasques", 8 | "Description[ca]": "Configura i planifica les tasques", 9 | "Description[cs]": "Nastavte a plánujte úlohy", 10 | "Description[de]": "Aufgaben einrichten und planen", 11 | "Description[el]": "Διαμορφώνει και χρονοπρογραμματίζει εργασίες", 12 | "Description[en_GB]": "Configure and schedule tasks", 13 | "Description[eo]": "Agordi kaj vicigi taskojn", 14 | "Description[es]": "Configurar y planificar tareas", 15 | "Description[eu]": "Atazak konfiguratu eta antolatu", 16 | "Description[fi]": "Määritä ja ajasta tehtäviä", 17 | "Description[fr]": "Configurer et planifier des tâches", 18 | "Description[gl]": "Configura e planifica tarefas", 19 | "Description[he]": "הגדרת משימות ותזמונן", 20 | "Description[hu]": "Feladatok beállítása és időzítése", 21 | "Description[ia]": "Configura e planifica cargas", 22 | "Description[is]": "Grunnstilla og tímasetja verk", 23 | "Description[it]": "Configura e pianifica le operazioni", 24 | "Description[ka]": "მოირგეთ და დაგეგმეთ ამოცანები", 25 | "Description[ko]": "작업을 설정하고 예약하기", 26 | "Description[lt]": "Konfigūruoti ir planuoti užduotis", 27 | "Description[lv]": "Konfigurējiet un plānojiet uzdevumus", 28 | "Description[nl]": "Taken configureren en plannen", 29 | "Description[nn]": "Planlegg og set opp oppgåver", 30 | "Description[pl]": "Ustawienia i planowanie zadań", 31 | "Description[pt]": "Configurar e agendar tarefas", 32 | "Description[pt_BR]": "Configura e agenda tarefas", 33 | "Description[ro]": "Configurează și planifică sarcinile", 34 | "Description[ru]": "Настройка и планирование заданий", 35 | "Description[sa]": "कार्याणि विन्यस्य समयनिर्धारणं च कुर्वन्तु", 36 | "Description[sk]": "Nastavenie a plánovanie úloh", 37 | "Description[sl]": "Konfigurirajte in razporedite opravila", 38 | "Description[sv]": "Anpassa och schemalägg jobb", 39 | "Description[tr]": "Görevleri zamanla ve yapılandır", 40 | "Description[uk]": "Налаштовування і планування завдань", 41 | "Description[x-test]": "xxConfigure and schedule tasksxx", 42 | "Description[zh_CN]": "配置并调度计划任务", 43 | "Description[zh_TW]": "設定與排程工作", 44 | "Icon": "preferences-system-time", 45 | "Name": "Task Scheduler", 46 | "Name[ar]": "مجدول المهامّ", 47 | "Name[be]": "Планіроўшчык задач", 48 | "Name[bg]": "Диспечер на задачи", 49 | "Name[bs]": "Raspored Zadataka", 50 | "Name[ca@valencia]": "Planificador de tasques", 51 | "Name[ca]": "Planificador de tasques", 52 | "Name[cs]": "Plánovač úloh", 53 | "Name[da]": "Opgaveskemalægger", 54 | "Name[de]": "Aufgabenplaner", 55 | "Name[el]": "Task Scheduler", 56 | "Name[en_GB]": "Task Scheduler", 57 | "Name[eo]": "Taskplanilo", 58 | "Name[es]": "Planificador de tareas", 59 | "Name[et]": "Tegumihaldur", 60 | "Name[eu]": "Atazen antolatzailea", 61 | "Name[fa]": "برنامه ریز تکلیف", 62 | "Name[fi]": "Tehtävien ajastus", 63 | "Name[fr]": "Planificateur de tâches", 64 | "Name[ga]": "Sceidealóir na dTascanna", 65 | "Name[gl]": "Programador de tarefas", 66 | "Name[he]": "מתזמן משימות", 67 | "Name[hne]": "काम जमइया", 68 | "Name[hr]": "Raspoređivač zadataka", 69 | "Name[hu]": "Feladatütemező", 70 | "Name[ia]": "Planificator de Cargas", 71 | "Name[id]": "Penjadwal Tugas", 72 | "Name[is]": "Verkröðun", 73 | "Name[it]": "Pianificatore di operazioni", 74 | "Name[ja]": "タスクスケジューラ", 75 | "Name[ka]": "ამოცანების დამგეგმავი", 76 | "Name[kk]": "Тапсырма жоспарлағышы", 77 | "Name[ko]": "작업 스케줄러", 78 | "Name[lt]": "Užduočių planuoklė", 79 | "Name[lv]": "Uzdevumu plānotājs", 80 | "Name[ml]": "കര്‍ത്തവ്യങ്ങള്‍ ആസൂത്രണം ചെയ്യുവാന്‍", 81 | "Name[mr]": "कार्य नियोजक", 82 | "Name[nb]": "Oppgavebehandler", 83 | "Name[nds]": "Opgavenpleger", 84 | "Name[nl]": "Takenplanner", 85 | "Name[nn]": "Oppgåveplanleggjar", 86 | "Name[pa]": "ਟਾਸਕ ਸੈਡਿਊਲਰ", 87 | "Name[pl]": "Harmonogram zadań", 88 | "Name[pt]": "Escalonador de Tarefas", 89 | "Name[pt_BR]": "Agendador de tarefas", 90 | "Name[ro]": "Planificator de sarcini", 91 | "Name[ru]": "Планировщик заданий", 92 | "Name[sa]": "कार्यानुसूची", 93 | "Name[se]": "Bargoplánejeaddji", 94 | "Name[sk]": "Plánovač úloh", 95 | "Name[sl]": "Razporejevalnik opravil", 96 | "Name[sq]": "Skeduesi i Detyrave", 97 | "Name[sr@ijekavian]": "Распоређивач задатака", 98 | "Name[sr@ijekavianlatin]": "Raspoređivač zadataka", 99 | "Name[sr@latin]": "Raspoređivač zadataka", 100 | "Name[sr]": "Распоређивач задатака", 101 | "Name[sv]": "Jobbschemaläggare", 102 | "Name[tg]": "Замонасози вазифаҳо", 103 | "Name[th]": "ตัวจัดการตารางงาน", 104 | "Name[tr]": "Görev Zamanlayıcısı", 105 | "Name[ug]": "ۋەزىپە پىلانلاش پروگراممىسى", 106 | "Name[uk]": "Планувальник завдань", 107 | "Name[vi]": "Trình lập lịch tác vụ", 108 | "Name[x-test]": "xxTask Schedulerxx", 109 | "Name[zh_CN]": "任务计划程序", 110 | "Name[zh_TW]": "工作排程器" 111 | }, 112 | "X-DocPath": "kcontrol/kcron/index.html", 113 | "X-KDE-Keywords": "cron,crontab,scheduled,tasks,task,schedule,vixie", 114 | "X-KDE-Keywords[ar]": "cron,كرونتاب,جدولة,مهام,مهمّة,مهمة,كرون", 115 | "X-KDE-Keywords[be]": "cron,crontab,запланавана,задачы,задача,расклад,vixie", 116 | "X-KDE-Keywords[bg]": "cron,crontab,планирани задачи,задачи,vixie", 117 | "X-KDE-Keywords[bs]": "cron,crontab,scheduled,tasks,raspoređeni,zadaci,zadato, redoslijed,task,schedule,vixie", 118 | "X-KDE-Keywords[ca@valencia]": "cron,crontab,planificació,planificat,planificada,tasques,tasca,planificació,vixie", 119 | "X-KDE-Keywords[ca]": "cron,crontab,planificació,planificat,planificada,tasques,tasca,planificació,vixie", 120 | "X-KDE-Keywords[cs]": "cron,crontab,naplánováno,úlohy,úloha,naplánovat,vixie", 121 | "X-KDE-Keywords[da]": "cron,crontab,skemalagt,opgaver,opgaveskema,skema,vixie", 122 | "X-KDE-Keywords[de]": "aufgaben,planung", 123 | "X-KDE-Keywords[el]": "cron,crontab,προγραμματισμένες,εργασίες,εργασία,προγραμματισμός,vixie", 124 | "X-KDE-Keywords[en_GB]": "cron,crontab,scheduled,tasks,task,schedule,vixie", 125 | "X-KDE-Keywords[eo]": "cron,crontab,planita,taskoj,tasko,plano,vixie", 126 | "X-KDE-Keywords[es]": "cron,crontab,programado,tareas,tarea,programar,vixie", 127 | "X-KDE-Keywords[et]": "cron,crontab,ajastamine,ülesanded,ülesanne,ajastaja,vixie", 128 | "X-KDE-Keywords[eu]": "cron,crontab,scheduled,antolatuta,tasks,task,atazak,ataza,schedule,antolatu,vixie", 129 | "X-KDE-Keywords[fa]": "corn, crontab, برنامه‌ریزی، تکالیف، تکلیف،زمان‌بندی، vixie", 130 | "X-KDE-Keywords[fi]": "cron,crontab,ajastus,ajasta,tehtävät,työt,ajastin,vixie", 131 | "X-KDE-Keywords[fr]": "cron, crontab, planification, tâche, tâches, planifiée, vixie", 132 | "X-KDE-Keywords[ga]": "cron,crontab,sceidealta,tascanna,tasc,sceideal,vixie", 133 | "X-KDE-Keywords[gl]": "cron,crontab,planificada,tarefa,planificar,vixie", 134 | "X-KDE-Keywords[he]": "cron,crontab,מתוזמן,משימות,משימה,לו״ז,vixie,לוח זמנים", 135 | "X-KDE-Keywords[hu]": "cron,crontab,ütemezett,feladatok,feladat,ütemezés,vixie", 136 | "X-KDE-Keywords[ia]": "cron,crontab,planificate,cargas,carga,planifica,vixie", 137 | "X-KDE-Keywords[id]": "cron,crontab,terjadwal,tugas,jadwal,vixie", 138 | "X-KDE-Keywords[it]": "cron,crontab,piano,compiti,compito,pianificare,vixie", 139 | "X-KDE-Keywords[ka]": "cron,crontab,scheduled,tasks,task,schedule,vixie", 140 | "X-KDE-Keywords[kk]": "cron,crontab,scheduled,tasks,task,schedule,vixie", 141 | "X-KDE-Keywords[ko]": "cron,crontab,scheduled,tasks,task,schedule,vixie,작업,예약,예약된 작업", 142 | "X-KDE-Keywords[lt]": "cron,crontab,nustatyta,suplanuota,planuota,užduotys,uzduotys,uzduotis,užduotis,tvarkaraštis,tvarkarastis,planuoti,vixie", 143 | "X-KDE-Keywords[lv]": "cron,crontab,plānots,grafiks,uzdevumi,darbi,uzdevums,plāns,vixie", 144 | "X-KDE-Keywords[mr]": "क्रोन,क्रोनटॅब,नियोजीत,कार्य,कार्ये,नियोजन,विक्सी", 145 | "X-KDE-Keywords[nb]": "cron,crontab,planlagt,oppgaver,oppgave,plan,vixie", 146 | "X-KDE-Keywords[nds]": "cron,Opgaav,Opgaven,Opgaventabell,plaant,Plaan,vixie", 147 | "X-KDE-Keywords[nl]": "cron,crontab,gepland,taken,taak,planning,vixie", 148 | "X-KDE-Keywords[nn]": "cron,crontab,oppgåveplanleggjar,tidsplan,regelmessig,oppgåver,oppgåve,timeplan,schedule,vixie", 149 | "X-KDE-Keywords[pl]": "cron,crontab,program_planujący,zadania,zadanie,planuj,vixie", 150 | "X-KDE-Keywords[pt_BR]": "cron,crontab,agendadas,tarefas,tarefa,agendar,vixie", 151 | "X-KDE-Keywords[ru]": "cron,crontab,scheduled,tasks,task,schedule,vixie,планирование,задания,задание,задачи,задача,планировщик,запуск,автозапуск", 152 | "X-KDE-Keywords[sa]": "cron,crontab,निर्धारित,कार्य,कार्य,निर्धारित,vixie", 153 | "X-KDE-Keywords[sk]": "cron,crontab,plánované,úlohy,úloha,plánovanie,vixie", 154 | "X-KDE-Keywords[sl]": "cron,crontab,razporejeno,opravila,opravilo,razpored,vixie", 155 | "X-KDE-Keywords[sr@ijekavian]": "cron,crontab,scheduled,tasks,task,schedule,vixie,крон,кронтаб,распоред,задаци,распоређен", 156 | "X-KDE-Keywords[sr@ijekavianlatin]": "cron,crontab,scheduled,tasks,task,schedule,vixie,cron,crontab,raspored,zadaci,raspoređen", 157 | "X-KDE-Keywords[sr@latin]": "cron,crontab,scheduled,tasks,task,schedule,vixie,cron,crontab,raspored,zadaci,raspoređen", 158 | "X-KDE-Keywords[sr]": "cron,crontab,scheduled,tasks,task,schedule,vixie,крон,кронтаб,распоред,задаци,распоређен", 159 | "X-KDE-Keywords[sv]": "cron,crontab,schemalagd,uppgifter,uppgift,schema,vixie", 160 | "X-KDE-Keywords[tg]": "cron,crontab,вақт,вазифаҳо,вазифа,замонсозӣ,vixie", 161 | "X-KDE-Keywords[tr]": "cron,crontab,zamanlanmış,görevler,görev,zaman,vixie", 162 | "X-KDE-Keywords[uk]": "cron,crontab,scheduled,tasks,task,schedule,vixie,таблиця планування,список планування,завдання,план,запуск", 163 | "X-KDE-Keywords[vi]": "cron,crontab,scheduled,tasks,task,schedule,vixie,lập lịch,tác vụ", 164 | "X-KDE-Keywords[x-test]": "xxcronxx,xxcrontabxx,xxscheduledxx,xxtasksxx,xxtaskxx,xxschedulexx,xxvixiexx", 165 | "X-KDE-Keywords[zh_CN]": "cron,crontab,scheduled,tasks,task,schedule,vixie,jihua,jihuarenwu,renwu,计划,计划任务,任务", 166 | "X-KDE-Keywords[zh_TW]": "cron,crontab,scheduled,tasks,task,schedule,vixie", 167 | "X-KDE-ParentApp": "kcontrol", 168 | "X-KDE-System-Settings-Parent-Category": "session" 169 | } 170 | -------------------------------------------------------------------------------- /src/task.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "task.h" 10 | 11 | #include "cttask.h" 12 | 13 | Task::Task(CTTask *ctTask, QObject *parent) noexcept 14 | : QObject(parent) 15 | , mTask(ctTask) 16 | { 17 | Q_ASSERT(ctTask); 18 | } 19 | 20 | QString Task::comment() const noexcept 21 | { 22 | return mTask->comment; 23 | } 24 | 25 | bool Task::setComment(const QString &comment) noexcept 26 | { 27 | if (comment != mTask->comment) { 28 | mTask->comment = comment; 29 | Q_EMIT commentChanged(); 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | 36 | QString Task::command() const noexcept 37 | { 38 | return mTask->command; 39 | } 40 | 41 | bool Task::setCommand(const QString &command) noexcept 42 | { 43 | if (command != mTask->command) { 44 | mTask->command = command; 45 | Q_EMIT commandChanged(); 46 | return true; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | QString Task::userLogin() const noexcept 53 | { 54 | return mTask->userLogin; 55 | } 56 | 57 | bool Task::setUserLogin(const QString &userLogin) noexcept 58 | { 59 | if (userLogin != mTask->userLogin) { 60 | mTask->userLogin = userLogin; 61 | Q_EMIT userLoginChanged(); 62 | return true; 63 | } 64 | 65 | return false; 66 | } 67 | 68 | bool Task::enabled() const noexcept 69 | { 70 | return mTask->enabled; 71 | } 72 | 73 | bool Task::setEnabled(bool enabled) noexcept 74 | { 75 | if (enabled != mTask->enabled) { 76 | mTask->enabled = enabled; 77 | Q_EMIT enabledChanged(); 78 | return true; 79 | } 80 | 81 | return false; 82 | } 83 | 84 | bool Task::reboot() const noexcept 85 | { 86 | return mTask->reboot; 87 | } 88 | 89 | bool Task::setReboot(bool reboot) noexcept 90 | { 91 | if (reboot != mTask->reboot) { 92 | mTask->reboot = reboot; 93 | Q_EMIT rebootChanged(); 94 | return true; 95 | } 96 | 97 | return false; 98 | } 99 | 100 | bool Task::systemCron() const noexcept 101 | { 102 | return mTask->isSystemCrontab(); 103 | } 104 | 105 | bool Task::setSystemCron(bool systemCron) noexcept 106 | { 107 | if (systemCron != mTask->isSystemCrontab()) { 108 | mTask->setSystemCrontab(systemCron); 109 | Q_EMIT systemCronChanged(); 110 | return true; 111 | } 112 | 113 | return false; 114 | } 115 | 116 | QString Task::exportTask() const 117 | { 118 | return mTask->exportTask(); 119 | } 120 | 121 | CTTask *Task::task() const noexcept 122 | { 123 | return mTask; 124 | } 125 | 126 | void Task::updateTask(const CTTask &task) noexcept 127 | { 128 | *mTask = task; 129 | } 130 | 131 | void Task::updateTask(CTTask *task) noexcept 132 | { 133 | *mTask = *task; 134 | } 135 | 136 | void Task::apply() 137 | { 138 | mTask->apply(); 139 | Q_EMIT applyed(); 140 | } 141 | 142 | void Task::cancel() 143 | { 144 | mTask->cancel(); 145 | Q_EMIT canceled(); 146 | } 147 | 148 | bool Task::hasChanges() const 149 | { 150 | return mTask->dirty(); 151 | } 152 | 153 | QString Task::schedulingCronFormat() const 154 | { 155 | return mTask->schedulingCronFormat(); 156 | } 157 | 158 | QString Task::describe() const 159 | { 160 | return mTask->describe(); 161 | } 162 | 163 | bool Task::isEveryDay() const 164 | { 165 | return mTask->dayOfWeek.isAllEnabled() && mTask->month.isAllEnabled() && mTask->dayOfMonth.isAllEnabled(); 166 | } 167 | 168 | bool Task::isDayOfMonthEnabled(int day) const 169 | { 170 | return mTask->dayOfMonth.isEnabled(day); 171 | } 172 | 173 | bool Task::isMonthEnabled(int month) const 174 | { 175 | return mTask->month.isEnabled(month); 176 | } 177 | 178 | bool Task::isWeekDayEnabled(int day) const 179 | { 180 | return mTask->dayOfWeek.isEnabled(day); 181 | } 182 | 183 | bool Task::isMinuteEnabled(int minute) const 184 | { 185 | return mTask->minute.isEnabled(minute); 186 | } 187 | 188 | bool Task::isHourEnabled(int hour) const 189 | { 190 | return mTask->hour.isEnabled(hour); 191 | } 192 | 193 | void Task::setMonthEnabled(int month, bool enabled) 194 | { 195 | mTask->month.setEnabled(month, enabled); 196 | } 197 | 198 | void Task::setDayOfMonthEnabled(int day, bool enabled) 199 | { 200 | mTask->dayOfMonth.setEnabled(day, enabled); 201 | } 202 | 203 | void Task::setDayOfWeekEnabled(int day, bool enabled) 204 | { 205 | mTask->dayOfWeek.setEnabled(day, enabled); 206 | } 207 | 208 | void Task::setHourEnabled(int hour, bool enabled) 209 | { 210 | mTask->hour.setEnabled(hour, enabled); 211 | } 212 | 213 | void Task::setMinuteEnabled(int minute, bool enabled) 214 | { 215 | mTask->minute.setEnabled(minute, enabled); 216 | } 217 | 218 | #include "task.moc" 219 | 220 | #include "moc_task.cpp" 221 | -------------------------------------------------------------------------------- /src/task.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | class CTTask; 14 | class Task final : public QObject 15 | { 16 | Q_OBJECT 17 | 18 | Q_PROPERTY(QString comment READ comment WRITE setComment NOTIFY commentChanged FINAL) 19 | Q_PROPERTY(QString command READ command WRITE setCommand NOTIFY commandChanged FINAL) 20 | Q_PROPERTY(QString userLogin READ userLogin WRITE setUserLogin NOTIFY userLoginChanged FINAL) 21 | Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL) 22 | Q_PROPERTY(bool reboot READ reboot WRITE setReboot NOTIFY rebootChanged FINAL) 23 | Q_PROPERTY(bool systemCron READ systemCron WRITE setSystemCron NOTIFY systemCronChanged FINAL) 24 | 25 | public: 26 | explicit Task(CTTask *ctTask, QObject *parent) noexcept; 27 | ~Task() override = default; 28 | 29 | QString comment() const noexcept; 30 | bool setComment(const QString &comment) noexcept; 31 | 32 | QString command() const noexcept; 33 | bool setCommand(const QString &command) noexcept; 34 | 35 | QString userLogin() const noexcept; 36 | bool setUserLogin(const QString &userLogin) noexcept; 37 | 38 | bool enabled() const noexcept; 39 | bool setEnabled(bool enabled) noexcept; 40 | 41 | bool reboot() const noexcept; 42 | bool setReboot(bool reboot) noexcept; 43 | 44 | bool systemCron() const noexcept; 45 | bool setSystemCron(bool systemCron) noexcept; 46 | 47 | QString exportTask() const; 48 | 49 | CTTask *task() const noexcept; 50 | 51 | void updateTask(const CTTask &task) noexcept; 52 | void updateTask(CTTask *task) noexcept; 53 | 54 | public: 55 | Q_INVOKABLE void apply(); 56 | Q_INVOKABLE void cancel(); 57 | 58 | Q_INVOKABLE bool hasChanges() const; 59 | 60 | Q_INVOKABLE QString schedulingCronFormat() const; 61 | Q_INVOKABLE QString describe() const; 62 | 63 | Q_INVOKABLE bool isEveryDay() const; 64 | 65 | Q_INVOKABLE bool isMonthEnabled(int month) const; 66 | Q_INVOKABLE bool isDayOfMonthEnabled(int day) const; 67 | Q_INVOKABLE bool isWeekDayEnabled(int day) const; 68 | Q_INVOKABLE bool isHourEnabled(int hour) const; 69 | Q_INVOKABLE bool isMinuteEnabled(int minute) const; 70 | 71 | Q_INVOKABLE void setMonthEnabled(int month, bool enabled); 72 | Q_INVOKABLE void setDayOfMonthEnabled(int day, bool enabled); 73 | Q_INVOKABLE void setDayOfWeekEnabled(int day, bool enabled); 74 | Q_INVOKABLE void setHourEnabled(int hour, bool enabled); 75 | Q_INVOKABLE void setMinuteEnabled(int minute, bool enabled); 76 | 77 | Q_SIGNALS: 78 | void commentChanged(); 79 | void commandChanged(); 80 | void userLoginChanged(); 81 | void enabledChanged(); 82 | void rebootChanged(); 83 | void systemCronChanged(); 84 | 85 | void applyed(); 86 | void canceled(); 87 | 88 | private: 89 | CTTask *const mTask; 90 | }; 91 | -------------------------------------------------------------------------------- /src/tasksmodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "tasksmodel.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "ctcron.h" 20 | #include "cttask.h" 21 | #include "ctvariable.h" 22 | #include "task.h" 23 | 24 | #include "kcm_cron_debug.h" 25 | 26 | TasksModel::TasksModel(QObject *parent) noexcept 27 | : GenericModel(parent) 28 | , mTmpTask(new Task(new CTTask(QStringLiteral(""), QStringLiteral(""), QStringLiteral("")), this)) 29 | { 30 | mProxyModel->setSortRole(Roles::CommandRole); 31 | mProxyModel->sort(0, Qt::AscendingOrder); 32 | } 33 | 34 | TasksModel::~TasksModel() 35 | { 36 | this->clear(); 37 | } 38 | 39 | void TasksModel::run() 40 | { 41 | if (!mSelectionModel->hasSelection()) { 42 | return; 43 | } 44 | 45 | QModelIndex currentIndex = mProxyModel->mapToSource(mSelectionModel->currentIndex()); 46 | Task *task = mTasks.at(currentIndex.row()); 47 | 48 | const QString echoMessage = i18nc("Do not use any quote characters (') in this string", "End of script execution. Type Enter or Ctrl+C to exit."); 49 | 50 | const auto variables = mCtCron->variables(); 51 | 52 | QStringList commandList; 53 | commandList.reserve(variables.count() + 5); 54 | for (const auto &variable : variables) { 55 | if (variable->enabled) { 56 | commandList << QStringLiteral("export %1=%2").arg(variable->variable, variable->value); 57 | } 58 | } 59 | 60 | commandList << task->command(); 61 | commandList << QStringLiteral("echo '-------------------------------------------------------------------------'"); 62 | commandList << QStringLiteral("echo %1").arg(echoMessage); 63 | commandList << QStringLiteral("echo '-------------------------------------------------------------------------'"); 64 | commandList << QStringLiteral("read"); 65 | 66 | QStringList parameters; 67 | parameters << QStringLiteral("-e") << QStringLiteral("bash") << QStringLiteral("-c"); 68 | parameters << commandList.join(QLatin1Char(';')); 69 | 70 | QProcess::startDetached(QStringLiteral("konsole"), parameters); 71 | } 72 | 73 | Task *TasksModel::create() 74 | { 75 | // reset values of task from prev use 76 | CTTask ctTask = CTTask(QStringLiteral(""), QStringLiteral(""), mCtCron->userLogin(), mCtCron->isSystemCron()); 77 | mTmpTask->updateTask(ctTask); 78 | return mTmpTask; 79 | } 80 | 81 | void TasksModel::applyCreate() 82 | { 83 | Task *task = new Task(new CTTask(QStringLiteral(""), QStringLiteral(""), mCtCron->userLogin(), mCtCron->isSystemCron()), this); 84 | task->updateTask(mTmpTask->task()); 85 | 86 | this->add(task); 87 | Q_EMIT addTask(task); 88 | Q_EMIT enabledStateChanged(); 89 | } 90 | 91 | Task *TasksModel::modify() 92 | { 93 | if (!mSelectionModel->hasSelection()) { 94 | qCWarning(KCM_CRON_LOG) << "There are no elements selected in the model"; 95 | return this->create(); 96 | } 97 | 98 | QModelIndex currentIndex = mProxyModel->mapToSource(mSelectionModel->currentIndex()); 99 | Task *task = mTasks.at(currentIndex.row()); 100 | 101 | mTmpTask->updateTask(task->task()); 102 | return mTmpTask; 103 | } 104 | 105 | void TasksModel::applyModify() 106 | { 107 | if (!mSelectionModel->hasSelection()) { 108 | qCWarning(KCM_CRON_LOG) << "There are no elements selected in the model, nothing can be updated"; 109 | return; 110 | } 111 | 112 | QModelIndex currentIndex = mProxyModel->mapToSource(mSelectionModel->currentIndex()); 113 | Task *task = mTasks.at(currentIndex.row()); 114 | task->updateTask(mTmpTask->task()); 115 | task->apply(); 116 | } 117 | 118 | int TasksModel::rowCount(const QModelIndex &parent) const 119 | { 120 | if (parent.isValid()) { 121 | return 0; 122 | } 123 | 124 | return mTasks.count(); 125 | } 126 | 127 | QVariant TasksModel::data(const QModelIndex &index, int role) const 128 | { 129 | if (!checkIndex(index, CheckIndexOption::ParentIsInvalid | CheckIndexOption::IndexIsValid)) { 130 | return QVariant{}; 131 | } 132 | 133 | Task *task = mTasks.at(index.row()); 134 | 135 | switch (role) { 136 | case Roles::CommandRole: 137 | return QVariant::fromValue(task->command()); 138 | case Roles::UserLoginRole: 139 | return QVariant::fromValue(task->userLogin()); 140 | case Roles::EnabledRole: 141 | return QVariant::fromValue(task->enabled()); 142 | case Roles::CommentRole: 143 | return QVariant::fromValue(task->comment()); 144 | case Roles::CronFormatRole: 145 | return QVariant::fromValue(task->schedulingCronFormat()); 146 | case Roles::DescriptionRole: 147 | return QVariant::fromValue(task->describe()); 148 | default: 149 | Q_ASSERT(false); 150 | } 151 | 152 | return QVariant{}; 153 | } 154 | 155 | bool TasksModel::setData(const QModelIndex &index, const QVariant &value, int role) 156 | { 157 | if (!checkIndex(index, CheckIndexOption::ParentIsInvalid | CheckIndexOption::IndexIsValid)) { 158 | return false; 159 | } 160 | 161 | if (role != Roles::EnabledRole) { 162 | return false; 163 | } 164 | 165 | if (!value.canConvert(QMetaType(QMetaType::Bool))) { 166 | return false; 167 | } 168 | 169 | Task *task = mTasks.at(index.row()); 170 | if (task->setEnabled(value.toBool())) { 171 | task->apply(); 172 | return true; 173 | } 174 | 175 | return false; 176 | } 177 | 178 | QHash TasksModel::roleNames() const 179 | { 180 | static QHash roles{ 181 | {CommandRole, "command"}, 182 | {CommentRole, "comment"}, 183 | {UserLoginRole, "userLogin"}, 184 | {EnabledRole, "enabled"}, 185 | {CronFormatRole, "cronFormat"}, 186 | {DescriptionRole, "description"}, 187 | }; 188 | return roles; 189 | } 190 | 191 | void TasksModel::removeSelected() 192 | { 193 | if (!mSelectionModel->hasSelection()) { 194 | return; 195 | } 196 | 197 | for (int i = mTasks.count() - 1; i >= 0; --i) { 198 | QModelIndex index = mProxyModel->mapFromSource(this->index(i, 0)); 199 | if (mSelectionModel->isSelected(index)) { 200 | beginRemoveRows(QModelIndex(), i, i); 201 | 202 | Task *task = mTasks.at(i); 203 | Q_EMIT removeTask(task); 204 | this->remove(task); 205 | 206 | endRemoveRows(); 207 | } 208 | } 209 | 210 | mSelectionModel->clear(); 211 | 212 | Q_EMIT enabledStateChanged(); 213 | } 214 | 215 | void TasksModel::copy() 216 | { 217 | if (!mSelectionModel->hasSelection()) { 218 | return; 219 | } 220 | 221 | QString copyString; 222 | for (const auto &selectedIndex : mSelectionModel->selectedIndexes()) { 223 | QModelIndex index = mProxyModel->mapToSource(selectedIndex); 224 | copyString.append(mTasks.at(index.row())->exportTask()); 225 | copyString.append(QLatin1Char('\n')); 226 | } 227 | 228 | if (!copyString.isEmpty()) { 229 | QApplication::clipboard()->setText(copyString); 230 | } 231 | } 232 | 233 | void TasksModel::cut() 234 | { 235 | if (!mSelectionModel->hasSelection()) { 236 | return; 237 | } 238 | 239 | this->copy(); 240 | this->removeSelected(); 241 | } 242 | 243 | void TasksModel::paste() 244 | { 245 | QString clipboard = QApplication::clipboard()->text(); 246 | if (clipboard.isEmpty()) { 247 | return; 248 | } 249 | 250 | auto isTask = [](const QString &line) -> bool { 251 | static QRegularExpression whiteSpace = QRegularExpression(QLatin1String("[ \t]")); 252 | 253 | int firstWhiteSpace = line.indexOf(whiteSpace); 254 | int firstEquals = line.indexOf(QLatin1String("=")); 255 | 256 | return firstEquals <= 0 || (firstWhiteSpace != -1 && firstWhiteSpace <= firstEquals); 257 | }; 258 | 259 | auto isComment = [](const QString &line) -> bool { 260 | return line.indexOf(QLatin1String("#")) == 0 && line.indexOf(QLatin1String("\\")) != 1; 261 | }; 262 | 263 | auto lines = clipboard.split(QLatin1Char('\n')); 264 | QString comment; 265 | for (QString line : lines) { 266 | // skip empty lines, empty lines are space between commented cron expressions 267 | if (line.isEmpty()) { 268 | comment.clear(); 269 | continue; 270 | } 271 | 272 | // search for comments "#" but not disabled tasks "#\" 273 | // It's always loading comments user added by crontab -e command 274 | // or user added by Kcron 275 | if (isComment(line)) { 276 | line = line.mid(1, line.length() - 1); 277 | if (comment.isEmpty()) { 278 | comment = line.trimmed(); 279 | } else { 280 | comment += QLatin1Char('\n') + line.trimmed(); 281 | } 282 | continue; 283 | } 284 | 285 | if (isTask(line)) { 286 | Task *task = new Task(new CTTask(line, comment, mCtCron->userLogin(), mCtCron->isSystemCron()), this); 287 | this->add(task); 288 | Q_EMIT addTask(task); 289 | Q_EMIT enabledStateChanged(); 290 | comment.clear(); 291 | } 292 | } 293 | } 294 | 295 | bool TasksModel::needUserColumn() 296 | { 297 | return mCtCron && mCtCron->isMultiUserCron(); 298 | } 299 | 300 | Qt::CheckState TasksModel::enabledState() 301 | { 302 | int count = enabledCount(); 303 | 304 | if (count == 0) { 305 | return Qt::CheckState::Unchecked; 306 | } 307 | 308 | if (count == mTasks.length()) { 309 | return Qt::CheckState::Checked; 310 | } 311 | 312 | return Qt::CheckState::PartiallyChecked; 313 | } 314 | 315 | void TasksModel::setEnabledState(Qt::CheckState state) 316 | { 317 | bool status = state == Qt::CheckState::Checked; 318 | 319 | for (auto &task : mTasks) { 320 | task->setEnabled(status); 321 | task->apply(); 322 | } 323 | } 324 | 325 | void TasksModel::refresh(CTCron *ctCron) 326 | { 327 | qCDebug(KCM_CRON_LOG) << "Refreshing tasks model"; 328 | 329 | mCtCron = ctCron; 330 | 331 | this->clear(); 332 | 333 | for (CTTask *ctTask : mCtCron->tasks()) { 334 | Task *task = new Task(ctTask, this); 335 | this->add(task); 336 | } 337 | 338 | Q_EMIT needUserColumnChanged(); 339 | Q_EMIT enabledStateChanged(); 340 | } 341 | 342 | void TasksModel::clear() 343 | { 344 | if (mTasks.isEmpty()) { 345 | return; 346 | } 347 | 348 | qCDebug(KCM_CRON_LOG) << "Clearing tasks model"; 349 | 350 | qDeleteAll(mTasks.begin(), mTasks.end()); 351 | mTasks.clear(); 352 | mSelectionModel->clear(); 353 | 354 | beginResetModel(); 355 | endResetModel(); 356 | } 357 | 358 | int TasksModel::enabledCount() 359 | { 360 | int ret = 0; 361 | for (const auto &task : mTasks) { 362 | if (task->enabled()) { 363 | ret++; 364 | } 365 | } 366 | 367 | return ret; 368 | } 369 | 370 | void TasksModel::add(Task *task) 371 | { 372 | beginInsertRows(QModelIndex(), mTasks.count(), mTasks.count()); 373 | mTasks.push_back(task); 374 | endInsertRows(); 375 | 376 | QModelIndex modelIndex = index(mTasks.count() - 1, 0); 377 | QObject::connect(task, &Task::applyed, this, [this, modelIndex, task]() -> void { 378 | Q_EMIT dataChanged(modelIndex, modelIndex, {}); 379 | Q_EMIT modifyTask(task); 380 | Q_EMIT enabledStateChanged(); 381 | }); 382 | } 383 | 384 | void TasksModel::remove(Task *task) 385 | { 386 | mTasks.removeAll(task); 387 | delete task; 388 | } 389 | 390 | #include "tasksmodel.moc" 391 | 392 | #include "moc_tasksmodel.cpp" 393 | -------------------------------------------------------------------------------- /src/tasksmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "genericmodel.h" 12 | 13 | class CTCron; 14 | class Task; 15 | 16 | class TasksModel final : public GenericModel 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | enum Roles { 22 | CommandRole = Qt::UserRole + 1, 23 | UserLoginRole, 24 | EnabledRole, 25 | CommentRole, 26 | CronFormatRole, 27 | DescriptionRole, 28 | }; 29 | Q_ENUM(Roles) 30 | 31 | public: 32 | explicit TasksModel(QObject *parent) noexcept; 33 | ~TasksModel() override; 34 | 35 | Q_INVOKABLE void run(); 36 | 37 | Q_INVOKABLE Task *create(); 38 | Q_INVOKABLE void applyCreate(); 39 | 40 | Q_INVOKABLE Task *modify(); 41 | Q_INVOKABLE void applyModify(); 42 | 43 | Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override; 44 | Q_INVOKABLE QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 45 | Q_INVOKABLE bool setData(const QModelIndex &index, const QVariant &value, int role) override; 46 | QHash roleNames() const override; 47 | 48 | Q_INVOKABLE void removeSelected() override; 49 | 50 | Q_INVOKABLE void copy() override; 51 | Q_INVOKABLE void cut() override; 52 | Q_INVOKABLE void paste() override; 53 | 54 | bool needUserColumn() override; 55 | 56 | Qt::CheckState enabledState() override; 57 | void setEnabledState(Qt::CheckState state) override; 58 | 59 | public Q_SLOTS: 60 | void refresh(CTCron *ctCron) override; 61 | 62 | Q_SIGNALS: 63 | void addTask(Task *task); 64 | void modifyTask(Task *task); 65 | void removeTask(Task *task); 66 | 67 | private: 68 | void clear() override; 69 | int enabledCount() override; 70 | 71 | void add(Task *task); 72 | void remove(Task *task); 73 | 74 | private: 75 | QList mTasks; 76 | 77 | Task *const mTmpTask; 78 | }; 79 | -------------------------------------------------------------------------------- /src/taskvalidator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "taskvalidator.h" 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include "cttask.h" 16 | #include "task.h" 17 | 18 | TaskValidator::TaskValidator(QObject *parent) noexcept 19 | : QObject(parent) 20 | { 21 | // Initialize special valid commands 22 | mSpecialValidCommands << QStringLiteral("cd"); 23 | } 24 | 25 | TaskValidator::~TaskValidator() 26 | { 27 | } 28 | 29 | QString TaskValidator::errorString() const noexcept 30 | { 31 | return mErrorString; 32 | } 33 | 34 | Task *TaskValidator::task() const noexcept 35 | { 36 | return mTask; 37 | } 38 | 39 | void TaskValidator::setTask(Task *task) 40 | { 41 | if (mTask) { 42 | QObject::disconnect(mTask, &Task::commandChanged, this, &TaskValidator::validate); 43 | QObject::disconnect(mTask, &Task::commentChanged, this, &TaskValidator::validate); 44 | QObject::disconnect(mTask, &Task::rebootChanged, this, &TaskValidator::validate); 45 | QObject::disconnect(mTask, &Task::userLoginChanged, this, &TaskValidator::validate); 46 | } 47 | 48 | if (!task) { 49 | return; 50 | } 51 | 52 | mTask = task; 53 | 54 | QObject::connect(mTask, &Task::commandChanged, this, &TaskValidator::validate); 55 | QObject::connect(mTask, &Task::commentChanged, this, &TaskValidator::validate); 56 | QObject::connect(mTask, &Task::rebootChanged, this, &TaskValidator::validate); 57 | QObject::connect(mTask, &Task::userLoginChanged, this, &TaskValidator::validate); 58 | 59 | Q_EMIT taskChanged(); 60 | this->validate(); 61 | } 62 | 63 | bool TaskValidator::validate() 64 | { 65 | if (!mTask) { 66 | return false; 67 | } 68 | 69 | if (!this->validateCommand()) { 70 | return false; 71 | } 72 | 73 | if (mTask->reboot()) { 74 | mErrorString.clear(); 75 | Q_EMIT errorStringChanged(); 76 | return true; 77 | } 78 | 79 | if (!this->validateMonth()) { 80 | return false; 81 | } 82 | 83 | if (!this->validateDays()) { 84 | return false; 85 | } 86 | 87 | if (!this->validateHours()) { 88 | return false; 89 | } 90 | 91 | if (!this->validateMinutes()) { 92 | return false; 93 | } 94 | 95 | mErrorString.clear(); 96 | Q_EMIT errorStringChanged(); 97 | 98 | return true; 99 | } 100 | 101 | bool TaskValidator::validateCommand() 102 | { 103 | if (mTask->task()->command.isEmpty()) { 104 | mErrorString = xi18nc("@info", "Choose a program to run"); 105 | Q_EMIT errorStringChanged(); 106 | return false; 107 | } 108 | 109 | QPair commandQuoted = mTask->task()->unQuoteCommand(); 110 | 111 | if (commandQuoted.first.isEmpty()) { 112 | mErrorString = xi18nc("@info", "Please type a valid command line"); 113 | Q_EMIT errorStringChanged(); 114 | return false; 115 | } 116 | 117 | QStringList pathCommand = mTask->task()->separatePathCommand(commandQuoted.first, commandQuoted.second); 118 | if (pathCommand.isEmpty()) { 119 | mErrorString = xi18nc("@info", "Please type a valid command line"); 120 | Q_EMIT errorStringChanged(); 121 | return false; 122 | } 123 | 124 | const QString &path = pathCommand.at(0); 125 | const QString &binaryCommand = pathCommand.at(1); 126 | 127 | bool found = false; 128 | bool exec = false; 129 | if (!QStandardPaths::findExecutable(binaryCommand, QStringList() << path).isEmpty() || mSpecialValidCommands.contains(binaryCommand)) { 130 | found = true; 131 | } 132 | 133 | if (found) { 134 | exec = true; 135 | } 136 | 137 | if (found && !exec) { 138 | mErrorString = xi18nc("@info", "Please select an executable program"); 139 | Q_EMIT errorStringChanged(); 140 | return false; 141 | } 142 | 143 | if (!found) { 144 | mErrorString = xi18nc("@info", "Choose a program to run"); 145 | Q_EMIT errorStringChanged(); 146 | return false; 147 | } 148 | 149 | return true; 150 | } 151 | 152 | bool TaskValidator::validateMonth() 153 | { 154 | bool isValid = false; 155 | for (int mo = CTMonth::MINIMUM; mo <= CTMonth::MAXIMUM; mo++) { 156 | if (mTask->isMonthEnabled(mo)) { 157 | isValid = true; 158 | break; 159 | } 160 | } 161 | 162 | if (!isValid) { 163 | mErrorString = xi18nc("@info", "Please select from the Months section"); 164 | Q_EMIT errorStringChanged(); 165 | } 166 | 167 | return isValid; 168 | } 169 | 170 | bool TaskValidator::validateDays() 171 | { 172 | bool isValid = false; 173 | for (int dm = CTDayOfMonth::MINIMUM; dm <= CTDayOfMonth::MAXIMUM; dm++) { 174 | if (mTask->isDayOfMonthEnabled(dm)) { 175 | isValid = true; 176 | break; 177 | } 178 | } 179 | 180 | for (int dw = CTDayOfWeek::MINIMUM; dw <= CTDayOfWeek::MAXIMUM; dw++) { 181 | if (mTask->isWeekDayEnabled(dw)) { 182 | isValid = true; 183 | break; 184 | } 185 | } 186 | 187 | if (!isValid) { 188 | mErrorString = xi18nc("@info", "Please select from either the Days of Month or the Days of Week section"); 189 | Q_EMIT errorStringChanged(); 190 | } 191 | 192 | return isValid; 193 | } 194 | 195 | bool TaskValidator::validateHours() 196 | { 197 | bool isValid = false; 198 | for (int ho = 0; ho <= 23; ho++) { 199 | if (mTask->isHourEnabled(ho)) { 200 | isValid = true; 201 | break; 202 | } 203 | } 204 | 205 | if (!isValid) { 206 | mErrorString = xi18nc("@info", "Please select from the Hours section"); 207 | Q_EMIT errorStringChanged(); 208 | } 209 | 210 | return isValid; 211 | } 212 | 213 | bool TaskValidator::validateMinutes() 214 | { 215 | bool isValid = false; 216 | for (int mi = 0; mi <= 59; ++mi) { 217 | if (mTask->isMinuteEnabled(mi)) { 218 | isValid = true; 219 | break; 220 | } 221 | } 222 | 223 | if (!isValid) { 224 | mErrorString = xi18nc("@info", "Please select from the Minutes section"); 225 | Q_EMIT errorStringChanged(); 226 | } 227 | 228 | return isValid; 229 | } 230 | 231 | #include "taskvalidator.moc" 232 | 233 | #include "moc_taskvalidator.cpp" 234 | -------------------------------------------------------------------------------- /src/taskvalidator.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | class Task; 15 | class TaskValidator : public QObject 16 | { 17 | Q_OBJECT 18 | 19 | Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged FINAL) 20 | Q_PROPERTY(Task *task READ task WRITE setTask NOTIFY taskChanged FINAL) 21 | QML_ELEMENT 22 | 23 | public: 24 | explicit TaskValidator(QObject *parent = nullptr) noexcept; 25 | ~TaskValidator() override; 26 | 27 | QString errorString() const noexcept; 28 | 29 | Task *task() const noexcept; 30 | void setTask(Task *task); 31 | 32 | public Q_SLOTS: 33 | bool validate(); 34 | 35 | Q_SIGNALS: 36 | void errorStringChanged(); 37 | void taskChanged(); 38 | 39 | private: 40 | bool validateCommand(); 41 | bool validateMonth(); 42 | bool validateDays(); 43 | bool validateHours(); 44 | bool validateMinutes(); 45 | 46 | private: 47 | QString mErrorString; 48 | QStringList mSpecialValidCommands; 49 | 50 | Task *mTask = nullptr; 51 | }; 52 | -------------------------------------------------------------------------------- /src/ui/CheckedModel.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | 9 | ListModel { 10 | function toggleAll(): void { 11 | const state = getCheckState(); 12 | 13 | if (state === Qt.Checked) 14 | setAllUnchecked(); 15 | else 16 | setAllChecked(); 17 | } 18 | 19 | function getCheckState() { 20 | if (isAllUnchecked()) { 21 | return Qt.Unchecked; 22 | } 23 | 24 | if (isAllChecked()) { 25 | return Qt.Checked; 26 | } 27 | 28 | return Qt.PartiallyChecked; 29 | } 30 | 31 | function toogle(index): void { 32 | setProperty(index, "checked", !get(index)["checked"]); 33 | } 34 | 35 | function setAllChecked(): void { 36 | for (let i = 0; i < count; i++) { 37 | setProperty(i, "checked", true); 38 | } 39 | } 40 | 41 | function setAllUnchecked(): void { 42 | for (let i = 0; i < count; i++) { 43 | setProperty(i, "checked", false); 44 | } 45 | } 46 | 47 | function isAllChecked(): bool { 48 | for (let i = 0; i < count; i++) { 49 | if (!get(i)["checked"]) { 50 | return false; 51 | } 52 | } 53 | 54 | return true; 55 | } 56 | 57 | function isAllUnchecked(): bool { 58 | for (let i = 0; i < count; i++) { 59 | if (get(i)["checked"]) { 60 | return false; 61 | } 62 | } 63 | 64 | return true; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ui/Onboarding.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Controls as QQC2 9 | 10 | import org.kde.kirigami as Kirigami 11 | import org.kde.kcmutils as KCM 12 | 13 | KCM.SimpleKCM { 14 | id: root 15 | 16 | implicitWidth: Kirigami.Units.gridUnit * 35 17 | implicitHeight: Kirigami.Units.gridUnit * 25 18 | 19 | Kirigami.PlaceholderMessage { 20 | anchors.centerIn: parent 21 | width: Math.min(fontMetrics.averageCharacterWidth * 70, root.width - Kirigami.Units.largeSpacing * 4) 22 | text: i18n("Welcome to Task Scheduler!") 23 | icon.name: "preferences-system-time" 24 | explanation: xi18nc("@info", "Schedule programs to run in the background. Set tasks to run on a fixed schedule and automate repetitive tasks like downloading files or sending emails. Learn more", "https://man7.org/linux/man-pages/man8/cron.8.html") 25 | 26 | helpfulAction: QQC2.Action { 27 | text: i18nc("@action:button", "Go to Dashboard") 28 | onTriggered: kcm.pop() 29 | } 30 | 31 | onLinkActivated: (link) => Qt.openUrlExternally(link); 32 | } 33 | 34 | FontMetrics { 35 | id: fontMetrics 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/ui/Table.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Layouts 9 | import QtQuick.Controls as QQC2 10 | 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kirigamiaddons.tableview as TableView 13 | 14 | ColumnLayout { 15 | id: main 16 | 17 | spacing: Kirigami.Units.smallSpacing 18 | 19 | property string title 20 | property string iconName 21 | property var model 22 | 23 | default property alias columns: table.headerComponents 24 | property alias contextMenuActions: table.actions 25 | property list actions 26 | 27 | readonly property bool hasSelection: table.selectionModel.hasSelection 28 | readonly property bool hasFocus: table.activeFocus || table.focus 29 | 30 | signal create() 31 | signal edit() 32 | 33 | Kirigami.IconTitleSubtitle { 34 | title: main.title 35 | icon.name: main.iconName 36 | font.bold: true 37 | } 38 | 39 | RowLayout { 40 | spacing: Kirigami.Units.smallSpacing 41 | 42 | QQC2.ScrollView { 43 | Layout.fillWidth: true 44 | Layout.fillHeight: true 45 | 46 | Component.onCompleted: { 47 | if (background) { 48 | background.visible = true 49 | } 50 | } 51 | 52 | TableView.ListTableView { 53 | id: table 54 | 55 | clip: true 56 | 57 | selectionModel: main.model.selectionModel 58 | model: main.model.proxyModel 59 | 60 | onRowDoubleClicked: (row, mouse) => main.edit() 61 | 62 | sortOrder: Qt.AscendingOrder 63 | sortRole: table.model.sortRole 64 | 65 | onColumnClicked: function (index, headerComponent) { 66 | if (table.model.sortRole === headerComponent.role) { 67 | table.sortOrder = table.sortOrder === Qt.DescendingOrder ? Qt.AscendingOrder : Qt.DescendingOrder; 68 | } 69 | 70 | table.model.sortRole = headerComponent.role; 71 | table.model.sort(0, table.sortOrder); 72 | __resetSelection(); 73 | } 74 | 75 | function __getSourceIndex(row) { 76 | let proxyIndex = table.model.index(row, 0); 77 | return table.model.mapToSource(proxyIndex); 78 | } 79 | 80 | function __resetSelection(): void { 81 | // NOTE: Making a forced copy of the list 82 | let selectedIndexes = Array(...table.selectionModel.selectedIndexes); 83 | let currentIndex = table.selectionModel.currentIndex.row; 84 | table.selectionModel.clear(); 85 | 86 | for (let i in selectedIndexes) { 87 | table.selectionModel.select(selectedIndexes[i], ItemSelectionModel.Select); 88 | } 89 | 90 | table.selectionModel.setCurrentIndex(table.model.index(currentIndex, 0), ItemSelectionModel.Select); 91 | } 92 | } 93 | } 94 | 95 | ColumnLayout { 96 | spacing: Kirigami.Units.smallSpacing 97 | Layout.alignment: Qt.AlignTop 98 | 99 | Repeater { 100 | model: main.actions 101 | 102 | delegate: QQC2.Button { 103 | action: modelData 104 | } 105 | } 106 | } 107 | } 108 | 109 | Connections { 110 | target: main.model 111 | 112 | function onNeedUserColumnChanged() { 113 | table.updateModel() 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/ui/TasksComponent.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Layouts 9 | import QtQuick.Controls as QQC2 10 | 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kirigamiaddons.tableview as TableView 13 | 14 | import org.kde.private.kcms.cron as Private 15 | 16 | Table { 17 | id: main 18 | 19 | title: i18nc("@title:group", "Scheduled Tasks") 20 | iconName: "system-run-symbolic" 21 | model: kcm.tasksModel 22 | 23 | onCreate: root.pushPage("TaskPage.qml", {"edited": false, "task": main.model.create()}) 24 | onEdit: root.pushPage("TaskPage.qml", {"edited": true, "task": main.model.modify()}) 25 | 26 | TableView.HeaderComponent { 27 | width: Kirigami.Units.gridUnit * 2 28 | minimumWidth: Kirigami.Units.gridUnit * 2 29 | 30 | textRole: "enabled" 31 | draggable: false 32 | resizable: false 33 | 34 | itemDelegate: QQC2.CheckBox { 35 | checked: model?.enabled ?? false 36 | onToggled: model.enabled = !model.enabled 37 | } 38 | 39 | headerDelegate: QQC2.CheckBox { 40 | tristate: true 41 | checkState: main.model.enabledState 42 | onClicked: { 43 | if (main.model.enabledState === Qt.Checked) { 44 | main.model.enabledState = Qt.Unchecked; 45 | } else { 46 | main.model.enabledState = Qt.Checked; 47 | } 48 | } 49 | } 50 | } 51 | 52 | TableView.HeaderComponent { 53 | width: Kirigami.Units.gridUnit * 8 54 | minimumWidth: Kirigami.Units.gridUnit * 8 55 | role: Private.TasksModel.UserLoginRole 56 | title: i18nc("@title:column", "User") 57 | textRole: "userLogin" 58 | visible: main.model.needUserColumn 59 | } 60 | 61 | TableView.HeaderComponent { 62 | width: Kirigami.Units.gridUnit * 8 63 | minimumWidth: Kirigami.Units.gridUnit * 8 64 | 65 | role: Private.TasksModel.CronFormatRole 66 | title: i18nc("@title:column", "Scheduling") 67 | textRole: "cronFormat" 68 | } 69 | 70 | TableView.HeaderComponent { 71 | width: Kirigami.Units.gridUnit * 12 72 | minimumWidth: Kirigami.Units.gridUnit * 12 73 | 74 | role: Private.TasksModel.CommandRole 75 | title: i18nc("@title:column", "Command") 76 | textRole: "command" 77 | } 78 | 79 | TableView.HeaderComponent { 80 | width: Kirigami.Units.gridUnit * 12 81 | minimumWidth: Kirigami.Units.gridUnit * 12 82 | 83 | role: Private.TasksModel.CommentRole 84 | title: i18nc("@title:column", "Comment") 85 | textRole: "comment" 86 | } 87 | 88 | TableView.HeaderComponent { 89 | width: Kirigami.Units.gridUnit * 10 90 | minimumWidth: Kirigami.Units.gridUnit * 10 91 | role: Private.TasksModel.DescriptionRole 92 | title: i18nc("@title:column", "Scheduling Details") 93 | textRole: "description" 94 | } 95 | 96 | contextMenuActions: [ 97 | Kirigami.Action { 98 | text: i18nc("@action:button", "Add Task…") 99 | icon.name: "document-new-symbolic" 100 | shortcut: "Ctrl+N" 101 | enabled: main.hasFocus 102 | onTriggered: main.createTask() 103 | }, 104 | Kirigami.Action { 105 | text: i18nc("@action:button", "Edit Task…") 106 | icon.name: "document-open-symbolic" 107 | shortcut: "Ctrl+M" 108 | enabled: main.hasSelection && main.hasFocus 109 | onTriggered: main.edit() 110 | }, 111 | Kirigami.Separator {}, 112 | Kirigami.Action { 113 | text: i18nc("@action:button", "Select All") 114 | icon.name: "edit-select-all-symbolic" 115 | shortcut: "Ctrl+A" 116 | enabled: main.hasFocus 117 | onTriggered: main.model.selectAll() 118 | }, 119 | Kirigami.Action { 120 | text: i18nc("@action:button", "Copy") 121 | icon.name: "edit-copy-symbolic" 122 | shortcut: "Ctrl+C" 123 | enabled: main.hasSelection && main.hasFocus 124 | onTriggered: main.model.copy() 125 | }, 126 | Kirigami.Action { 127 | text: i18nc("@action:button", "Cut") 128 | icon.name: "edit-cut-symbolic" 129 | shortcut: "Ctrl+X" 130 | enabled: main.hasSelection && main.hasFocus 131 | onTriggered: main.model.cut() 132 | }, 133 | Kirigami.Action { 134 | text: i18nc("@action:button", "Paste") 135 | icon.name: "edit-paste-symbolic" 136 | shortcut: "Ctrl+V" 137 | // TODO: enabled with qclipboard has content 138 | enabled: main.hasFocus 139 | onTriggered: main.model.paste() 140 | }, 141 | Kirigami.Action { 142 | text: i18nc("@action:button", "Delete") 143 | icon.name: "edit-delete-symbolic" 144 | shortcut: "Ctrl+Del" 145 | enabled: main.hasSelection && main.hasFocus 146 | onTriggered: main.model.removeSelected() 147 | }, 148 | Kirigami.Separator {}, 149 | Kirigami.Action { 150 | text: i18nc("@action:button", "Run task") 151 | icon.name: "system-run-symbolic" 152 | shortcut: "Ctrl+R" 153 | enabled: main.hasSelection && main.hasFocus 154 | onTriggered: main.model.run() 155 | } 156 | ] 157 | 158 | actions: [ 159 | Kirigami.Action { 160 | icon.name: "document-new-symbolic" 161 | onTriggered: main.create() 162 | text: i18nc("@action:button", "Add…") 163 | }, 164 | Kirigami.Action { 165 | icon.name: "document-edit-symbolic" 166 | enabled: main.hasSelection 167 | onTriggered: main.edit() 168 | text: i18nc("@action:button", "Edit…") 169 | }, 170 | Kirigami.Action { 171 | icon.name: "edit-delete-symbolic" 172 | enabled: main.hasSelection 173 | onTriggered: main.model.removeSelected() 174 | text: i18nc("@action:button", "Delete") 175 | }, 176 | Kirigami.Action { 177 | icon.name: "system-run-symbolic" 178 | enabled: main.hasSelection 179 | onTriggered: main.model.run() 180 | text: i18nc("@action:button", "Run") 181 | } 182 | ] 183 | } 184 | -------------------------------------------------------------------------------- /src/ui/TimeCard.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Layouts 9 | import QtQuick.Controls as QQC2 10 | 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kirigamiaddons.formcard as FormCard 13 | 14 | ColumnLayout { 15 | id: card 16 | 17 | property alias title: header.title 18 | property var model 19 | property real delegateWidth: Kirigami.Units.gridUnit * 5 20 | 21 | property alias columns: grid.columns 22 | property alias footer: formcard.delegates 23 | 24 | signal changed(int index) 25 | signal allChanged(bool checked) 26 | 27 | spacing: 0 28 | 29 | FormCard.FormHeader { 30 | id: header 31 | 32 | trailing: QQC2.CheckBox { 33 | text: i18nc("@label:checkbox", "Select all") 34 | tristate: true 35 | checkState: card.model.getCheckState() 36 | 37 | onClicked: { 38 | card.model.toggleAll() 39 | card.allChanged(checked) 40 | } 41 | } 42 | } 43 | 44 | FormCard.FormCard { 45 | id: formcard 46 | 47 | FormCard.AbstractFormDelegate { 48 | background: null 49 | contentItem: Item { 50 | implicitHeight: grid.implicitHeight 51 | 52 | GridLayout { 53 | id: grid 54 | 55 | width: parent.width 56 | columnSpacing: Kirigami.Units.smallSpacing 57 | rowSpacing: Kirigami.Units.smallSpacing 58 | 59 | Repeater { 60 | model: card.model 61 | delegate: Kirigami.Chip { 62 | closable: false 63 | Layout.fillWidth: true 64 | text: model.text 65 | checked: model.checked 66 | visible: model.visible ?? true 67 | checkable: true 68 | onToggled: { 69 | card.model.toogle(index) 70 | card.changed(index) 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/ui/VariablePage.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Layouts 9 | import QtQuick.Controls as QQC2 10 | 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kcmutils as KCM 13 | 14 | KCM.AbstractKCM { 15 | id: main 16 | 17 | title: main.edited ? i18nc("@title", "Edit Variable") : 18 | i18nc("@title", "New Variable") 19 | 20 | property bool edited 21 | property var variable 22 | 23 | headerPaddingEnabled: false 24 | 25 | actions: [ 26 | Kirigami.Action { 27 | text: i18nc("@action:button", "Edit Variable") 28 | Accessible.name: i18nc("@action:button accessible", "Edit Variable") 29 | icon.name: "document-open-symbolic" 30 | enabled: !errorMessage.visible 31 | visible: main.edited 32 | onTriggered: { 33 | kcm.pop(); 34 | kcm.variablesModel.applyModify(); 35 | } 36 | }, 37 | Kirigami.Action { 38 | text: i18nc("@action:button", "New Variable") 39 | Accessible.name: i18nc("@action:button accessible", "New Variable") 40 | icon.name: "document-new-symbolic" 41 | enabled: !errorMessage.visible 42 | visible: !main.edited 43 | onTriggered: { 44 | kcm.pop(); 45 | kcm.variablesModel.applyCreate(); 46 | } 47 | } 48 | ] 49 | 50 | header: Kirigami.InlineMessage { 51 | id: errorMessage 52 | visible: text !== "" 53 | position: Kirigami.InlineMessage.Position.Header 54 | type: Kirigami.MessageType.Error 55 | text: { 56 | if (variableName.editText.trim() === "") { 57 | return xi18nc("@info", "Please enter the variable name"); 58 | } else if (variableValue.text.trim() === "") { 59 | return xi18nc("@info", "Please enter the variable value"); 60 | } 61 | 62 | return "" 63 | } 64 | } 65 | 66 | contentItem: Kirigami.FormLayout { 67 | Kirigami.IconTitleSubtitle { 68 | icon.source: main.variable.icon 69 | title: main.variable.information 70 | } 71 | 72 | QQC2.ComboBox { 73 | id: variableName 74 | editable: true 75 | wheelEnabled: false 76 | Kirigami.FormData.label: i18nc("@label:listbox", "Variable:") 77 | Layout.preferredWidth: Kirigami.Units.gridUnit * 16 78 | currentIndex: -1 79 | model: [ 80 | "HOME", 81 | "MAILTO", 82 | "SHELL", 83 | "PATH", 84 | "LD_CONFIG_PATH", 85 | ] 86 | 87 | Component.onCompleted: variableName.editText = main.variable.name 88 | onActivated: main.variable.name = variableName.editText 89 | onEditTextChanged: main.variable.name = variableName.editText 90 | } 91 | 92 | QQC2.ComboBox { 93 | id: variableUser 94 | editable: false 95 | wheelEnabled: false 96 | model: kcm?.userList ?? [] 97 | Kirigami.FormData.label: i18nc("@label:listbox", "User:") 98 | Layout.preferredWidth: Kirigami.Units.gridUnit * 16 99 | visible: kcm?.variablesModel.needUserColumn ?? false 100 | 101 | Component.onCompleted: { 102 | if (main.variable) { 103 | variableUser.currentIndex = variableUser.find(main.variable.userLogin) 104 | } 105 | } 106 | 107 | onActivated: main.variable.userLogin = variableName.currentText 108 | } 109 | 110 | QQC2.TextField { 111 | id: variableValue 112 | Kirigami.FormData.label: i18nc("@label:textbox", "Value:") 113 | text: main.variable.value 114 | onTextChanged: main.variable.value = text 115 | } 116 | 117 | QQC2.TextField { 118 | id: variableComment 119 | Kirigami.FormData.label: i18nc("@label:textbox", "Comment:") 120 | text: main.variable.comment 121 | onTextChanged: main.variable.comment = text 122 | } 123 | 124 | QQC2.CheckBox { 125 | id: variableEnabled 126 | text: i18nc("@label:checkbox", "Enable this variable") 127 | checked: main.variable.enabled 128 | onToggled: main.variable.enabled = checked 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/ui/VariablesComponent.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Layouts 9 | import QtQuick.Controls as QQC2 10 | 11 | import org.kde.kirigami as Kirigami 12 | import org.kde.kirigamiaddons.tableview as TableView 13 | 14 | import org.kde.private.kcms.cron as Private 15 | 16 | Table { 17 | id: main 18 | 19 | title: i18nc("@title:group", "Environment Variables") 20 | iconName: "text-plain-symbolic" 21 | model: kcm.variablesModel 22 | 23 | onCreate: root.pushPage("VariablePage.qml", {"edited": false, "variable": main.model.create()}) 24 | onEdit: root.pushPage("VariablePage.qml", {"edited": true, "variable": main.model.modify()}) 25 | 26 | 27 | TableView.HeaderComponent { 28 | width: Kirigami.Units.gridUnit * 2 29 | minimumWidth: Kirigami.Units.gridUnit * 2 30 | 31 | textRole: "enabled" 32 | draggable: false 33 | resizable: false 34 | 35 | itemDelegate: QQC2.CheckBox { 36 | checked: model?.enabled ?? false 37 | onToggled: model.enabled = !model.enabled 38 | } 39 | 40 | headerDelegate: QQC2.CheckBox { 41 | tristate: true 42 | checkState: main.model.enabledState 43 | onClicked: { 44 | if (main.model.enabledState === Qt.Checked) { 45 | main.model.enabledState = Qt.Unchecked; 46 | } else { 47 | main.model.enabledState = Qt.Checked; 48 | } 49 | } 50 | } 51 | } 52 | 53 | TableView.HeaderComponent { 54 | width: Kirigami.Units.gridUnit * 8 55 | minimumWidth: Kirigami.Units.gridUnit * 8 56 | role: Private.VariablesModel.UserLoginRole 57 | title: i18nc("@title:column", "User") 58 | textRole: "userLogin" 59 | visible: main.model.needUserColumn 60 | } 61 | 62 | TableView.HeaderComponent { 63 | width: Kirigami.Units.gridUnit * 8 64 | minimumWidth: Kirigami.Units.gridUnit * 8 65 | 66 | role: Private.VariablesModel.NameRole 67 | title: i18nc("@title:column", "Variable") 68 | textRole: "name" 69 | 70 | itemDelegate: Item { 71 | width: parent.width 72 | 73 | Kirigami.IconTitleSubtitle { 74 | id: iconTitleSubtitle 75 | x: Kirigami.Units.largeSpacing 76 | width: parent.width - Kirigami.Units.largeSpacing * 2 77 | anchors.verticalCenter: parent.verticalCenter 78 | title: modelData 79 | icon.name: model?.icon ?? "" 80 | icon.width: Kirigami.Units.iconSizes.small 81 | 82 | QQC2.ToolTip.visible: truncated && handler.hovered 83 | QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay 84 | QQC2.ToolTip.text: title 85 | 86 | HoverHandler { 87 | id: handler 88 | enabled: iconTitleSubtitle.truncated 89 | } 90 | } 91 | } 92 | } 93 | 94 | TableView.HeaderComponent { 95 | width: Kirigami.Units.gridUnit * 12 96 | minimumWidth: Kirigami.Units.gridUnit * 12 97 | role: Private.VariablesModel.ValueRole 98 | title: i18nc("@title:column", "Value") 99 | textRole: "value" 100 | } 101 | 102 | TableView.HeaderComponent { 103 | width: Kirigami.Units.gridUnit * 12 104 | minimumWidth: Kirigami.Units.gridUnit * 12 105 | role: Private.VariablesModel.CommentRole 106 | title: i18nc("@title:column", "Comment") 107 | textRole: "comment" 108 | } 109 | 110 | contextMenuActions: [ 111 | Kirigami.Action { 112 | text: i18nc("@action:button", "Add Variable…") 113 | icon.name: "document-new-symbolic" 114 | enabled: main.hasFocus 115 | onTriggered: main.createVariable() 116 | }, 117 | Kirigami.Action { 118 | text: i18nc("@action:button", "Edit Variable…") 119 | icon.name: "document-open-symbolic" 120 | enabled: main.hasSelection && main.hasFocus 121 | onTriggered: main.edit() 122 | }, 123 | Kirigami.Separator {}, 124 | Kirigami.Action { 125 | text: i18nc("@action:button", "Select All") 126 | icon.name: "edit-select-all-symbolic" 127 | shortcut: "Ctrl+A" 128 | enabled: main.hasFocus 129 | onTriggered: main.model.selectAll() 130 | }, 131 | Kirigami.Action { 132 | text: i18nc("@action:button", "Copy") 133 | icon.name: "edit-copy-symbolic" 134 | shortcut: "Ctrl+C" 135 | enabled: main.hasSelection && main.hasFocus 136 | onTriggered: main.model.copy() 137 | }, 138 | Kirigami.Action { 139 | text: i18nc("@action:button", "Cut") 140 | icon.name: "edit-cut-symbolic" 141 | shortcut: "Ctrl+X" 142 | enabled: main.hasSelection && main.hasFocus 143 | onTriggered: main.model.cut() 144 | }, 145 | Kirigami.Action { 146 | text: i18nc("@action:button", "Paste") 147 | icon.name: "edit-paste-symbolic" 148 | shortcut: "Ctrl+V" 149 | // TODO: enabled with qclipboard has content 150 | enabled: main.hasFocus 151 | onTriggered: main.model.paste() 152 | }, 153 | Kirigami.Action { 154 | text: i18nc("@action:button", "Delete") 155 | icon.name: "edit-delete-symbolic" 156 | shortcut: "Ctrl+Del" 157 | enabled: main.hasSelection && main.hasFocus 158 | onTriggered: main.model.removeSelected() 159 | } 160 | ] 161 | 162 | actions: [ 163 | Kirigami.Action { 164 | icon.name: "document-new-symbolic" 165 | onTriggered: main.create() 166 | text: i18nc("@action:button", "Add…") 167 | }, 168 | Kirigami.Action { 169 | icon.name: "document-edit-symbolic" 170 | enabled: main.hasSelection 171 | onTriggered: main.edit() 172 | text: i18nc("@action:button", "Edit…") 173 | }, 174 | Kirigami.Action { 175 | icon.name: "edit-delete-symbolic" 176 | enabled: main.hasSelection 177 | onTriggered: main.model.removeSelected() 178 | text: i18nc("@action:button", "Delete") 179 | } 180 | ] 181 | } 182 | -------------------------------------------------------------------------------- /src/ui/main.qml: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 3 | 4 | SPDX-License-Identifier: GPL-2.0-or-later 5 | */ 6 | 7 | import QtCore 8 | import QtQuick 9 | import QtQuick.Layouts 10 | import QtQuick.Controls as QQC2 11 | 12 | import org.kde.kirigami as Kirigami 13 | import org.kde.kcmutils as KCM 14 | 15 | KCM.AbstractKCM { 16 | id: root 17 | 18 | implicitWidth: Kirigami.Units.gridUnit * 35 19 | implicitHeight: Kirigami.Units.gridUnit * 25 20 | 21 | headerPaddingEnabled: false 22 | 23 | header: ColumnLayout { 24 | spacing: Kirigami.Units.smallSpacing 25 | Kirigami.InlineMessage { 26 | id: errorMessage 27 | 28 | Layout.fillWidth: true 29 | position: Kirigami.InlineMessage.Position.Header 30 | type: Kirigami.MessageType.Error 31 | showCloseButton: true 32 | 33 | onVisibleChanged: { 34 | if (!visible) { 35 | errorMessage.text = ""; 36 | } 37 | } 38 | } 39 | 40 | RowLayout { 41 | spacing: Kirigami.Units.smallSpacing 42 | Layout.margins: Kirigami.Units.largeSpacing 43 | 44 | QQC2.Label { 45 | text: i18nc("@text", "Show:") 46 | } 47 | 48 | QQC2.RadioButton { 49 | text: i18nc("@title:tab", "User Schedule") 50 | checked: kcm.isPersonalUse 51 | onToggled: kcm.isPersonalUse = true 52 | } 53 | 54 | QQC2.RadioButton { 55 | text: i18nc("@title:tab", "System Schedule") 56 | checked: !kcm.isPersonalUse 57 | onToggled: kcm.isPersonalUse = false 58 | } 59 | } 60 | } 61 | 62 | actions: [ 63 | Kirigami.Action { 64 | text: i18nc("@action:button", "Print Summary…") 65 | icon.name: "cups" 66 | onTriggered: kcm.print() 67 | } 68 | ] 69 | 70 | contentItem: QQC2.SplitView { 71 | id: view 72 | orientation: Qt.Vertical 73 | 74 | TasksComponent { 75 | clip: true 76 | QQC2.SplitView.fillWidth: true 77 | QQC2.SplitView.fillHeight: true 78 | } 79 | 80 | VariablesComponent { 81 | clip: true 82 | QQC2.SplitView.fillWidth: true 83 | } 84 | } 85 | 86 | Settings { 87 | id: persistentSettings 88 | 89 | property var viewState 90 | } 91 | 92 | Component.onCompleted: view.restoreState(persistentSettings.viewState) 93 | Component.onDestruction: persistentSettings.viewState = view.saveState() 94 | 95 | Connections { 96 | target: kcm 97 | 98 | function onShowOnboarding() { 99 | kcm.push("Onboarding.qml") 100 | } 101 | 102 | function onShowError(errorString, details) { 103 | errorMessage.text = ""; 104 | errorMessage.text += errorString; 105 | errorMessage.text += ""; 106 | errorMessage.text += details; 107 | errorMessage.visible = true; 108 | } 109 | } 110 | 111 | function pushPage(pageName, properties): void { 112 | kcm.refreshPages(); 113 | kcm.push(pageName, properties); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/variable.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "variable.h" 10 | 11 | #include "ctvariable.h" 12 | 13 | Variable::Variable(CTVariable *variable, QObject *parent) noexcept 14 | : QObject(parent) 15 | , mVariable(variable) 16 | { 17 | Q_ASSERT(variable); 18 | } 19 | 20 | Variable::~Variable() 21 | { 22 | } 23 | 24 | QString Variable::name() const noexcept 25 | { 26 | return mVariable->variable; 27 | } 28 | 29 | bool Variable::setName(const QString &name) noexcept 30 | { 31 | if (name != mVariable->variable) { 32 | mVariable->variable = name; 33 | Q_EMIT nameChanged(); 34 | return true; 35 | } 36 | 37 | return false; 38 | } 39 | 40 | QString Variable::value() const noexcept 41 | { 42 | return mVariable->value; 43 | } 44 | 45 | bool Variable::setValue(const QString &value) noexcept 46 | { 47 | if (value != mVariable->value) { 48 | mVariable->value = value; 49 | Q_EMIT valueChanged(); 50 | return true; 51 | } 52 | 53 | return false; 54 | } 55 | 56 | QString Variable::comment() const noexcept 57 | { 58 | return mVariable->comment; 59 | } 60 | 61 | bool Variable::setComment(const QString &comment) noexcept 62 | { 63 | if (comment != mVariable->comment) { 64 | mVariable->comment = comment; 65 | Q_EMIT commentChanged(); 66 | return true; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | QString Variable::userLogin() const noexcept 73 | { 74 | return mVariable->userLogin; 75 | } 76 | 77 | bool Variable::setUserLogin(const QString &userLogin) noexcept 78 | { 79 | if (userLogin != mVariable->userLogin) { 80 | mVariable->userLogin = userLogin; 81 | Q_EMIT userLoginChanged(); 82 | return true; 83 | } 84 | 85 | return false; 86 | } 87 | 88 | bool Variable::enabled() const noexcept 89 | { 90 | return mVariable->enabled; 91 | } 92 | 93 | bool Variable::setEnabled(bool enabled) noexcept 94 | { 95 | if (enabled != mVariable->enabled) { 96 | mVariable->enabled = enabled; 97 | Q_EMIT enabledChanged(); 98 | return true; 99 | } 100 | 101 | return false; 102 | } 103 | 104 | QString Variable::icon() const noexcept 105 | { 106 | return mVariable->variableIcon().name(); 107 | } 108 | 109 | QString Variable::information() const noexcept 110 | { 111 | return mVariable->information(); 112 | } 113 | 114 | QString Variable::exportVariable() 115 | { 116 | return mVariable->exportVariable(); 117 | } 118 | 119 | CTVariable *Variable::variable() const noexcept 120 | { 121 | return mVariable; 122 | } 123 | 124 | void Variable::updateVariable(const CTVariable &variable) noexcept 125 | { 126 | *mVariable = variable; 127 | } 128 | 129 | void Variable::updateVariable(CTVariable *variable) noexcept 130 | { 131 | *mVariable = *variable; 132 | } 133 | 134 | void Variable::apply() 135 | { 136 | mVariable->apply(); 137 | Q_EMIT applyed(); 138 | } 139 | 140 | void Variable::cancel() 141 | { 142 | mVariable->cancel(); 143 | Q_EMIT canceled(); 144 | } 145 | 146 | bool Variable::hasChanges() const 147 | { 148 | return mVariable->dirty(); 149 | } 150 | 151 | #include "variable.moc" 152 | 153 | #include "moc_variable.cpp" 154 | -------------------------------------------------------------------------------- /src/variable.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | class CTVariable; 14 | class Variable final : public QObject 15 | { 16 | Q_OBJECT 17 | 18 | Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL) 19 | Q_PROPERTY(QString value READ value WRITE setValue NOTIFY valueChanged FINAL) 20 | Q_PROPERTY(QString comment READ comment WRITE setComment NOTIFY commentChanged FINAL) 21 | Q_PROPERTY(QString userLogin READ userLogin WRITE setUserLogin NOTIFY userLoginChanged FINAL) 22 | Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged FINAL) 23 | 24 | Q_PROPERTY(QString icon READ icon NOTIFY nameChanged FINAL) 25 | Q_PROPERTY(QString information READ information NOTIFY nameChanged FINAL) 26 | 27 | public: 28 | explicit Variable(CTVariable *variable, QObject *parent = nullptr) noexcept; 29 | ~Variable() override; 30 | 31 | QString name() const noexcept; 32 | bool setName(const QString &name) noexcept; 33 | 34 | QString value() const noexcept; 35 | bool setValue(const QString &value) noexcept; 36 | 37 | QString comment() const noexcept; 38 | bool setComment(const QString &comment) noexcept; 39 | 40 | QString userLogin() const noexcept; 41 | bool setUserLogin(const QString &userLogin) noexcept; 42 | 43 | bool enabled() const noexcept; 44 | bool setEnabled(bool enabled) noexcept; 45 | 46 | QString icon() const noexcept; 47 | 48 | QString information() const noexcept; 49 | 50 | QString exportVariable(); 51 | 52 | CTVariable *variable() const noexcept; 53 | 54 | void updateVariable(const CTVariable &variable) noexcept; 55 | void updateVariable(CTVariable *variable) noexcept; 56 | 57 | public: 58 | Q_INVOKABLE void apply(); 59 | Q_INVOKABLE void cancel(); 60 | 61 | Q_INVOKABLE bool hasChanges() const; 62 | 63 | Q_SIGNALS: 64 | void nameChanged(); 65 | void valueChanged(); 66 | void commentChanged(); 67 | void userLoginChanged(); 68 | void enabledChanged(); 69 | 70 | void applyed(); 71 | void canceled(); 72 | 73 | private: 74 | CTVariable *const mVariable; 75 | }; 76 | -------------------------------------------------------------------------------- /src/variablesmodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #include "variablesmodel.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "ctcron.h" 17 | #include "ctvariable.h" 18 | #include "variable.h" 19 | 20 | #include "kcm_cron_debug.h" 21 | 22 | VariablesModel::VariablesModel(QObject *parent) noexcept 23 | : GenericModel(parent) 24 | , mTmpVariable(new Variable(new CTVariable(QStringLiteral(""), QStringLiteral(""), QStringLiteral("")), this)) 25 | { 26 | mProxyModel->setSortRole(Roles::NameRole); 27 | mProxyModel->sort(0, Qt::AscendingOrder); 28 | } 29 | 30 | VariablesModel::~VariablesModel() 31 | { 32 | this->clear(); 33 | } 34 | 35 | Variable *VariablesModel::create() 36 | { 37 | // reset values of variable from prev use 38 | CTVariable ctVariable = CTVariable(QStringLiteral(""), QStringLiteral(""), mCtCron->userLogin()); 39 | mTmpVariable->updateVariable(ctVariable); 40 | return mTmpVariable; 41 | } 42 | 43 | void VariablesModel::applyCreate() 44 | { 45 | Variable *variable = new Variable(new CTVariable(QStringLiteral(""), QStringLiteral(""), mCtCron->userLogin()), this); 46 | variable->updateVariable(mTmpVariable->variable()); 47 | 48 | this->add(variable); 49 | Q_EMIT addVariable(variable); 50 | Q_EMIT enabledStateChanged(); 51 | } 52 | 53 | Variable *VariablesModel::modify() 54 | { 55 | if (!mSelectionModel->hasSelection()) { 56 | qCWarning(KCM_CRON_LOG) << "There are no elements selected in the model"; 57 | return this->create(); 58 | } 59 | 60 | QModelIndex currentIndex = mProxyModel->mapToSource(mSelectionModel->currentIndex()); 61 | Variable *variable = mVariables.at(currentIndex.row()); 62 | 63 | mTmpVariable->updateVariable(variable->variable()); 64 | return mTmpVariable; 65 | } 66 | 67 | void VariablesModel::applyModify() 68 | { 69 | if (!mSelectionModel->hasSelection()) { 70 | qCWarning(KCM_CRON_LOG) << "There are no elements selected in the model, nothing can be updated"; 71 | return; 72 | } 73 | 74 | QModelIndex currentIndex = mProxyModel->mapToSource(mSelectionModel->currentIndex()); 75 | Variable *variable = mVariables.at(currentIndex.row()); 76 | variable->updateVariable(mTmpVariable->variable()); 77 | variable->apply(); 78 | } 79 | 80 | int VariablesModel::rowCount(const QModelIndex &parent) const 81 | { 82 | if (parent.isValid()) { 83 | return 0; 84 | } 85 | 86 | return mVariables.count(); 87 | } 88 | 89 | QVariant VariablesModel::data(const QModelIndex &index, int role) const 90 | { 91 | if (!checkIndex(index, CheckIndexOption::ParentIsInvalid | CheckIndexOption::IndexIsValid)) { 92 | return QVariant{}; 93 | } 94 | 95 | Variable *variable = mVariables.at(index.row()); 96 | 97 | switch (role) { 98 | case Roles::NameRole: 99 | return QVariant::fromValue(variable->name()); 100 | case Roles::ValueRole: 101 | return QVariant::fromValue(variable->value()); 102 | case Roles::CommentRole: 103 | return QVariant::fromValue(variable->comment()); 104 | case Roles::UserLoginRole: 105 | return QVariant::fromValue(variable->userLogin()); 106 | case Roles::EnabledRole: 107 | return QVariant::fromValue(variable->enabled()); 108 | case Roles::IconRole: 109 | return QVariant::fromValue(variable->icon()); 110 | case Roles::InformationRole: 111 | return QVariant::fromValue(variable->information()); 112 | default: 113 | Q_ASSERT(false); 114 | } 115 | 116 | return QVariant{}; 117 | } 118 | 119 | bool VariablesModel::setData(const QModelIndex &index, const QVariant &value, int role) 120 | { 121 | if (!checkIndex(index, CheckIndexOption::ParentIsInvalid | CheckIndexOption::IndexIsValid)) { 122 | return false; 123 | } 124 | 125 | if (role != Roles::EnabledRole) { 126 | return false; 127 | } 128 | 129 | if (!value.canConvert(QMetaType(QMetaType::Bool))) { 130 | return false; 131 | } 132 | 133 | Variable *variable = mVariables.at(index.row()); 134 | if (variable->setEnabled(value.toBool())) { 135 | variable->apply(); 136 | return true; 137 | } 138 | 139 | return false; 140 | } 141 | 142 | QHash VariablesModel::roleNames() const 143 | { 144 | static QHash roles{ 145 | {NameRole, "name"}, 146 | {ValueRole, "value"}, 147 | {CommentRole, "comment"}, 148 | {UserLoginRole, "userLogin"}, 149 | {EnabledRole, "enabled"}, 150 | {IconRole, "icon"}, 151 | {InformationRole, "information"}, 152 | }; 153 | return roles; 154 | } 155 | 156 | void VariablesModel::removeSelected() 157 | { 158 | if (!mSelectionModel->hasSelection()) { 159 | return; 160 | } 161 | 162 | for (int i = mVariables.count() - 1; i >= 0; --i) { 163 | QModelIndex index = mProxyModel->mapFromSource(this->index(i, 0)); 164 | if (mSelectionModel->isSelected(index)) { 165 | beginRemoveRows(QModelIndex(), i, i); 166 | 167 | Variable *variable = mVariables.at(i); 168 | Q_EMIT removeVariable(variable); 169 | this->remove(variable); 170 | 171 | endRemoveRows(); 172 | } 173 | } 174 | 175 | mSelectionModel->clear(); 176 | 177 | Q_EMIT enabledStateChanged(); 178 | } 179 | 180 | void VariablesModel::copy() 181 | { 182 | if (!mSelectionModel->hasSelection()) { 183 | return; 184 | } 185 | 186 | QString copyString; 187 | for (const auto &selectedIndex : mSelectionModel->selectedIndexes()) { 188 | QModelIndex index = mProxyModel->mapToSource(selectedIndex); 189 | copyString.append(mVariables.at(index.row())->exportVariable()); 190 | copyString.append(QLatin1Char('\n')); 191 | } 192 | 193 | if (!copyString.isEmpty()) { 194 | QApplication::clipboard()->setText(copyString); 195 | } 196 | } 197 | 198 | void VariablesModel::cut() 199 | { 200 | if (!mSelectionModel->hasSelection()) { 201 | return; 202 | } 203 | 204 | this->copy(); 205 | this->removeSelected(); 206 | } 207 | 208 | void VariablesModel::paste() 209 | { 210 | QString clipboard = QApplication::clipboard()->text(); 211 | if (clipboard.isEmpty()) { 212 | return; 213 | } 214 | 215 | auto isVariable = [](const QString &line) -> bool { 216 | static QRegularExpression whiteSpace = QRegularExpression(QLatin1String("[ \t]")); 217 | 218 | int firstWhiteSpace = line.indexOf(whiteSpace); 219 | int firstEquals = line.indexOf(QLatin1String("=")); 220 | 221 | return (firstEquals > 0) && ((firstWhiteSpace == -1) || firstWhiteSpace > firstEquals); 222 | }; 223 | 224 | auto isComment = [](const QString &line) -> bool { 225 | return line.indexOf(QLatin1String("#")) == 0 && line.indexOf(QLatin1String("\\")) != 1; 226 | }; 227 | 228 | auto lines = clipboard.split(QLatin1Char('\n')); 229 | QString comment; 230 | for (QString line : lines) { 231 | // skip empty lines, empty lines are space between commented cron expressions 232 | if (line.isEmpty()) { 233 | comment.clear(); 234 | continue; 235 | } 236 | 237 | // search for comments "#" but not disabled tasks "#\" 238 | // It's always loading comments user added by crontab -e command 239 | // or user added by Kcron 240 | if (isComment(line)) { 241 | line = line.mid(1, line.length() - 1); 242 | if (comment.isEmpty()) { 243 | comment = line.trimmed(); 244 | } else { 245 | comment += QLatin1Char('\n') + line.trimmed(); 246 | } 247 | continue; 248 | } 249 | 250 | if (isVariable(line)) { 251 | Variable *variable = new Variable(new CTVariable(line, comment, mCtCron->userLogin()), this); 252 | this->add(variable); 253 | Q_EMIT addVariable(variable); 254 | Q_EMIT enabledStateChanged(); 255 | comment.clear(); 256 | } 257 | } 258 | } 259 | 260 | bool VariablesModel::needUserColumn() 261 | { 262 | return mCtCron && mCtCron->isMultiUserCron() && !mCtCron->isSystemCron(); 263 | } 264 | 265 | Qt::CheckState VariablesModel::enabledState() 266 | { 267 | int count = enabledCount(); 268 | 269 | if (count == 0) { 270 | return Qt::CheckState::Unchecked; 271 | } 272 | 273 | if (count == mVariables.length()) { 274 | return Qt::CheckState::Checked; 275 | } 276 | 277 | return Qt::CheckState::PartiallyChecked; 278 | } 279 | 280 | void VariablesModel::setEnabledState(Qt::CheckState state) 281 | { 282 | bool status = state == Qt::CheckState::Checked; 283 | 284 | for (Variable *variable : mVariables) { 285 | variable->setEnabled(status); 286 | variable->apply(); 287 | } 288 | } 289 | 290 | void VariablesModel::refresh(CTCron *ctCron) 291 | { 292 | qCDebug(KCM_CRON_LOG) << "Refreshing variables model"; 293 | 294 | mCtCron = ctCron; 295 | 296 | this->clear(); 297 | 298 | for (CTVariable *ctVariable : mCtCron->variables()) { 299 | Variable *variable = new Variable(ctVariable, this); 300 | this->add(variable); 301 | } 302 | 303 | Q_EMIT needUserColumnChanged(); 304 | Q_EMIT enabledStateChanged(); 305 | } 306 | 307 | void VariablesModel::clear() 308 | { 309 | if (mVariables.isEmpty()) { 310 | return; 311 | } 312 | 313 | qCDebug(KCM_CRON_LOG) << "Clearing variables model"; 314 | 315 | qDeleteAll(mVariables.begin(), mVariables.end()); 316 | mVariables.clear(); 317 | mSelectionModel->clear(); 318 | 319 | beginResetModel(); 320 | endResetModel(); 321 | } 322 | 323 | int VariablesModel::enabledCount() 324 | { 325 | int ret = 0; 326 | for (const auto &variable : mVariables) { 327 | if (variable->enabled()) { 328 | ret++; 329 | } 330 | } 331 | 332 | return ret; 333 | } 334 | 335 | void VariablesModel::add(Variable *variable) 336 | { 337 | beginInsertRows(QModelIndex(), mVariables.count(), mVariables.count()); 338 | mVariables.push_back(variable); 339 | endInsertRows(); 340 | 341 | QModelIndex modelIndex = index(mVariables.count() - 1, 0); 342 | QObject::connect(variable, &Variable::applyed, this, [this, modelIndex, variable]() -> void { 343 | Q_EMIT dataChanged(modelIndex, modelIndex, {}); 344 | Q_EMIT modifyVariable(variable); 345 | Q_EMIT enabledStateChanged(); 346 | }); 347 | } 348 | 349 | void VariablesModel::remove(Variable *variable) 350 | { 351 | mVariables.removeAll(variable); 352 | delete variable; 353 | } 354 | 355 | #include "variablesmodel.moc" 356 | 357 | #include "moc_variablesmodel.cpp" 358 | -------------------------------------------------------------------------------- /src/variablesmodel.h: -------------------------------------------------------------------------------- 1 | /* 2 | CT Cron Header 3 | -------------------------------------------------------------------- 4 | SPDX-FileCopyrightText: 2024 Evgeny Chesnokov 5 | -------------------------------------------------------------------- 6 | SPDX-License-Identifier: GPL-2.0-or-later 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "genericmodel.h" 12 | 13 | class CTCron; 14 | class Variable; 15 | 16 | class VariablesModel final : public GenericModel 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | enum Roles { 22 | NameRole = Qt::UserRole + 1, 23 | ValueRole, 24 | CommentRole, 25 | UserLoginRole, 26 | EnabledRole, 27 | IconRole, 28 | InformationRole, 29 | }; 30 | Q_ENUM(Roles) 31 | 32 | public: 33 | explicit VariablesModel(QObject *parent = nullptr) noexcept; 34 | ~VariablesModel() override; 35 | 36 | Q_INVOKABLE Variable *create(); 37 | Q_INVOKABLE void applyCreate(); 38 | 39 | Q_INVOKABLE Variable *modify(); 40 | Q_INVOKABLE void applyModify(); 41 | 42 | Q_INVOKABLE int rowCount(const QModelIndex &parent = QModelIndex()) const override; 43 | Q_INVOKABLE QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 44 | Q_INVOKABLE bool setData(const QModelIndex &index, const QVariant &value, int role) override; 45 | QHash roleNames() const override; 46 | 47 | Q_INVOKABLE void removeSelected() override; 48 | 49 | Q_INVOKABLE void copy() override; 50 | Q_INVOKABLE void cut() override; 51 | Q_INVOKABLE void paste() override; 52 | 53 | bool needUserColumn() override; 54 | 55 | Qt::CheckState enabledState() override; 56 | void setEnabledState(Qt::CheckState state) override; 57 | 58 | public Q_SLOTS: 59 | void refresh(CTCron *ctCron) override; 60 | 61 | Q_SIGNALS: 62 | void addVariable(Variable *variable); 63 | void modifyVariable(Variable *variable); 64 | void removeVariable(Variable *variable); 65 | 66 | private: 67 | void clear() override; 68 | int enabledCount() override; 69 | 70 | void add(Variable *variable); 71 | void remove(Variable *variable); 72 | 73 | private: 74 | QList mVariables; 75 | 76 | Variable *const mTmpVariable; 77 | }; 78 | --------------------------------------------------------------------------------