├── .github └── workflows │ └── releases.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs └── img │ └── AllInOne.png ├── package ├── contents │ ├── config │ │ ├── config.qml │ │ └── main.xml │ └── ui │ │ ├── ActiveTasksModel.qml │ │ ├── ActiveWindow.qml │ │ ├── ConfigManager.qml │ │ ├── ContextualActions.qml │ │ ├── ExecutableDataSource.qml │ │ ├── KWinConfig.qml │ │ ├── KWindowSystemAdapter.qml │ │ ├── MouseHandlers.qml │ │ ├── SortableItemsRow.qml │ │ ├── WidgetDragHandler.qml │ │ ├── WidgetEffectsRepeater.qml │ │ ├── WidgetElement.qml │ │ ├── WidgetTapHandler.qml │ │ ├── WidgetToolTip.qml │ │ ├── WidgetWheelHandler.qml │ │ ├── WindowControlButton.qml │ │ ├── common │ │ ├── JsonListModel.qml │ │ └── JsonModel.qml │ │ ├── config │ │ ├── AddWidgetElement.qml │ │ ├── ConfigAppearance.qml │ │ ├── ConfigBehavior.qml │ │ ├── FormSlider.qml │ │ ├── KWinShortcutComboBox.qml │ │ ├── TitleReplacements.qml │ │ ├── WidgetElements.qml │ │ └── effect │ │ │ ├── ActiveWindowFlagCondition.qml │ │ │ ├── ActiveWindowRegExpCondition.qml │ │ │ ├── ActiveWindowStringCondition.qml │ │ │ ├── AddPresetEffect.qml │ │ │ ├── Checkable.qml │ │ │ ├── CheckableColorSlider.qml │ │ │ ├── CheckableSlider.qml │ │ │ ├── ConfigEffects.qml │ │ │ ├── EditEffect.qml │ │ │ ├── EditableEffectRow.qml │ │ │ ├── EditableRuleRow.qml │ │ │ ├── EffectModel.qml │ │ │ ├── MergedMultiEffect.qml │ │ │ ├── RuleModel.qml │ │ │ └── effect.js │ │ ├── main.qml │ │ ├── theme │ │ ├── AuroraeWindowControlButton.qml │ │ ├── BreezeWindowControlButton.qml │ │ ├── BreezeWindowControlButtonIcon.qml │ │ ├── OxygenWindowControlButton.qml │ │ ├── OxygenWindowControlButtonIcon.qml │ │ ├── PlasmaWindowControlButton.qml │ │ └── PlasmaWindowControlButtonIcon.qml │ │ ├── utils.js │ │ └── windowControlButton.js └── metadata.json └── test └── functional-test.py /.github/workflows/releases.yml: -------------------------------------------------------------------------------- 1 | name: Releases 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | steps: 13 | - uses: actions/checkout@master 14 | - name: Archive Release 15 | uses: thedoctor0/zip-release@0.7.5 16 | with: 17 | type: 'zip' 18 | filename: 'application-title-bar.plasmoid' 19 | directory: 'package' 20 | - name: Upload Release 21 | uses: ncipollo/release-action@v1.12.0 22 | with: 23 | artifacts: "package/application-title-bar.plasmoid" 24 | token: ${{ secrets.GITHUB_TOKEN }} 25 | generateReleaseNotes: true 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | application-title-bar.iml 2 | .idea/.gitignore 3 | .idea/misc.xml 4 | .idea/modules.xml 5 | .idea/vcs.xml 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Application Title Bar 2 | 3 | [![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg)](https://opensource.org/licenses/) 4 | ![GitHub Release](https://img.shields.io/github/v/release/antroids/application-title-bar) 5 | 6 | ### NOTE: Plasma 6.3 - 6.3.3 Compatibility issues 7 | A Drag action handler does not work properly on Plasma 6.3 - 6.3.3 with ButtonsRebind KWin plugin enabled. 8 | The plugin can be disabled with the following command: 9 | ```bash 10 | kwriteconfig6 --file ~/.config/kwinrc --group Plugins --key buttonsrebindEnabled false && qdbus6 org.kde.KWin /Plugins UnloadPlugin "buttonsrebind" 11 | ``` 12 | The names of `kwriteconfig6` and `qdbus6` utilities can be different for your distribution. 13 | 14 | To enable the plugin again: 15 | ```bash 16 | kwriteconfig6 --file ~/.config/kwinrc --group Plugins --key buttonsrebindEnabled true && qdbus6 org.kde.KWin /Plugins LoadPlugin "buttonsrebind" 17 | ``` 18 | 19 | ## Description 20 | 21 | KDE plasmoid compatible with Qt6 with window title and buttons. 22 | I like minimalistic display layout and used Active Window Control plasmoid, but it's abandoned for several years and now incompatible with Plasma6. 23 | So, I decided to create my own widget with the minimal set of features. 24 | 25 | 26 | 27 | ### Goal 28 | 29 | Stable and fast widget with control buttons and window title, ideally with the same functionality as Unity panel. 30 | I would like to keep the widget pure QML to avoid incompatibility and maintenance issues. 31 | 32 | Disadvantages of pure QML widget: 33 | * Only icons can be used from Aurorae themes, the rest is ignored. Binary themes are unsupported at all (Issues [#18](https://github.com/antroids/application-title-bar/issues/18), [#6](https://github.com/antroids/application-title-bar/issues/6)). 34 | * I cannot see the way to build menu with current plasmoid API (Issue [#13](https://github.com/antroids/application-title-bar/issues/13)) 35 | 36 | ### Features 37 | 38 | * Close, minimize, maximize, keep below/above buttons. 39 | * Title with app name. 40 | * Configure actions on mouse events. 41 | * Configurable elements set and order. 42 | * Different theming options. Internal Breeze icons, System icons and Aurorae theme. 43 | * Configurable layout and geometry. 44 | * Click and drag widget to reposition window (as if you'd dragged the window's integrated title bar) 45 | 46 | ## Installing 47 | 48 | 1. Bash script 49 | - Update: `wget https://github.com/antroids/application-title-bar/releases/latest/download/application-title-bar.plasmoid -O ${TMPDIR:-/tmp}/application-title-bar.plasmoid && kpackagetool6 -t Plasma/Applet -u ${TMPDIR:-/tmp}/application-title-bar.plasmoid && systemctl --user restart plasma-plasmashell.service` 50 | 51 | - Install `wget https://github.com/antroids/application-title-bar/releases/latest/download/application-title-bar.plasmoid -O ${TMPDIR:-/tmp}/application-title-bar.plasmoid && kpackagetool6 -t Plasma/Applet -i ${TMPDIR:-/tmp}/application-title-bar.plasmoid && systemctl --user restart plasma-plasmashell.service` 52 | 53 | 2. Manual with Plasma UI 54 | - Install via "Add Widgets..." -> "Get New Widgets..." -> "Download..." 55 | - Install from [KDE Store](https://store.kde.org/p/2135509) 56 | - Download Latest \*.plasmoid from [Releases page](https://github.com/antroids/application-title-bar/releases) and install it via "Add Widgets..." -> "Get New Widgets..." -> "Install Widget From Local file" 57 | 58 | 3. Nix (needs Nixpkgs unstable 24.11 or later) 59 | 60 | On NixOS: 61 | ```nix 62 | environment.systemPackages = with pkgs; [ 63 | application-title-bar 64 | ]; 65 | ``` 66 | Other distros: 67 | ``` 68 | # without flakes: 69 | nix-env -iA nixpkgs.application-title-bar 70 | # with flakes: 71 | nix profile install nixpkgs#application-title-bar 72 | ``` 73 | ### Additional packages 74 | 75 | - Debian, Ubuntu, Kali Linux, Raspbian: `apt-get install qdbus` 76 | - Alpine: `apk add qt5-qttools` 77 | - Arch Linux: `pacman -S qdbus-qt5` 78 | - CentOS: `yum install qdbus-qt5` 79 | - Fedora: `dnf install qt5-qttools` 80 | 81 | ## 🆘 In cases of panel freezes or crashes 🆘 82 | 83 | Although the widget is being used by me and a lot of other people, there is still a chance that it would be incompatible with your OS distribution. The worst that can happen is some Binding loop that can freeze your Plasma panel. 84 | 85 | In such cases you can use the following script to downgrade the panel version: 86 | ` 87 | wget https://github.com/antroids/application-title-bar/releases/download/v0.6.8/application-title-bar.plasmoid -O ${TMPDIR:-/tmp}/application-title-bar.plasmoid && kpackagetool6 -t Plasma/Applet -u ${TMPDIR:-/tmp}/application-title-bar.plasmoid && systemctl --user restart plasma-plasmashell.service 88 | ` 89 | 90 | Or you can remove the widget: `kpackagetool6 --type Plasma/Applet --remove com.github.antroids.application-title-bar` 91 | 92 | Please, don't forget to fill the report about the issues. 93 | 94 | ## License 95 | 96 | This project is licensed under the GPL-3.0-or-later License - see the LICENSE.md file for details 97 | 98 | ## Contributing 99 | 100 | Pull requests and Issue reports are always welcome. 101 | -------------------------------------------------------------------------------- /docs/img/AllInOne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antroids/application-title-bar/1d6f84c86252f826243d2e80c6d6df746f6b4d93/docs/img/AllInOne.png -------------------------------------------------------------------------------- /package/contents/config/config.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import org.kde.plasma.configuration 9 | 10 | ConfigModel { 11 | ConfigCategory { 12 | name: i18n("Appearance") 13 | icon: "preferences-desktop-color" 14 | source: "config/ConfigAppearance.qml" 15 | } 16 | 17 | ConfigCategory { 18 | name: i18n("Behavior") 19 | icon: "preferences-desktop" 20 | source: "config/ConfigBehavior.qml" 21 | } 22 | 23 | ConfigCategory { 24 | name: i18n("Replacements") 25 | icon: "document-replace" 26 | source: "config/TitleReplacements.qml" 27 | } 28 | 29 | ConfigCategory { 30 | name: i18n("Effects") 31 | icon: "special-effects-symbolic" 32 | source: "config/effect/ConfigEffects.qml" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /package/contents/config/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 1 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 0 20 | 21 | 22 | 23 | 24 | 25 | 0 26 | 27 | 28 | 100 29 | 30 | 31 | 100 32 | 33 | 34 | 0 35 | 36 | 37 | 1 38 | 39 | 40 | 128 41 | 42 | 43 | false 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 2 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 0 60 | 61 | 62 | windowCloseButton,windowMaximizeButton,windowMinimizeButton,windowIcon,windowTitle 63 | 64 | 65 | 66 | false 67 | 68 | 69 | windowCloseButton,windowMaximizeButton,windowMinimizeButton,windowIcon,windowTitle 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 2 79 | 80 | 81 | 82 | 0 83 | 84 | 85 | 640 86 | 87 | 88 | 11 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 0 98 | 99 | 100 | true 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 2 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 0 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 2 127 | 128 | 129 | false 130 | 131 | 132 | <Unknown> 133 | 134 | 135 | 10 136 | 137 | 138 | 0 139 | 140 | 141 | 0 142 | 143 | 144 | 10 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 0 156 | 157 | 158 | true 159 | 160 | 161 | true 162 | 163 | 164 | true 165 | 166 | 167 | false 168 | 169 | 170 | false 171 | 172 | 173 | 174 | true 175 | 176 | 177 | true 178 | 179 | 180 | 10 181 | 182 | 183 | 184 | true 185 | 186 | 187 | true 188 | 189 | 190 | 191 | Window Move 192 | 193 | 194 | 195 | 196 | 197 | Window Maximize 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | Window Close 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 120 218 | 219 | 220 | 0 221 | 222 | 223 | Window Maximize 224 | 225 | 226 | Window Minimize 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | -------------------------------------------------------------------------------- /package/contents/ui/ActiveTasksModel.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | import QtQuick 7 | import org.kde.taskmanager as TaskManager 8 | 9 | TaskManager.TasksModel { 10 | id: tasksModel 11 | 12 | enum ActiveTaskSource { 13 | ActiveTask, 14 | LastActiveTask, 15 | LastActiveMaximized 16 | } 17 | 18 | signal activeWindowUpdated 19 | 20 | property ActiveWindow activeWindow 21 | property bool hasActiveWindow: activeTaskIndex.valid 22 | property TaskManager.VirtualDesktopInfo virtualDesktopInfo 23 | property TaskManager.ActivityInfo activityInfo 24 | property Repeater filteredTasksRepeater 25 | property var activeTaskIndex: getInvalidIndex() 26 | property Loader showingDesktopStateListener: Loader { 27 | property bool showingDesktop: false 28 | 29 | source: "KWindowSystemAdapter.qml" 30 | onStatusChanged: function () { 31 | if (status == Loader.Ready) { 32 | showingDesktop = Qt.binding(function () { 33 | return item.showingDesktop; 34 | }); 35 | } else { 36 | showingDesktop = false; 37 | } 38 | } 39 | onShowingDesktopChanged: updateActiveTaskIndex() 40 | } 41 | 42 | function getInvalidIndex() { 43 | return index(-1, -1); 44 | } 45 | 46 | function getFirstRowIndex() { 47 | return index(0, 0); 48 | } 49 | 50 | function updateActiveTaskIndex() { 51 | if (showingDesktopStateListener.showingDesktop) { 52 | activeTaskIndex = getInvalidIndex(); 53 | } else { 54 | switch (plasmoid.configuration.widgetActiveTaskSource) { 55 | case ActiveTasksModel.ActiveTaskSource.ActiveTask: 56 | activeTaskIndex = filterTask(activeTask) ? activeTask : getInvalidIndex(); 57 | break; 58 | case ActiveTasksModel.ActiveTaskSource.LastActiveTask: 59 | case ActiveTasksModel.ActiveTaskSource.LastActiveMaximized: 60 | activeTaskIndex = hasIndex(0, 0) && filterTask(getFirstRowIndex()) ? getFirstRowIndex() : getInvalidIndex(); 61 | break; 62 | } 63 | } 64 | activeWindow.update(); 65 | } 66 | 67 | function filterTask(index) { 68 | if (!index || !index.valid) 69 | return false; 70 | if (plasmoid.configuration.widgetActiveTaskFilterNotMaximized) 71 | return tasksModel.data(index, TaskManager.AbstractTasksModel.IsMaximized) || false; 72 | return true; 73 | } 74 | 75 | screenGeometry: plasmoid.containment.screenGeometry 76 | activity: activityInfo.currentActivity 77 | virtualDesktop: virtualDesktopInfo.currentDesktop 78 | filterByActivity: plasmoid.configuration.widgetActiveTaskFilterByActivity 79 | filterByScreen: plasmoid.configuration.widgetActiveTaskFilterByScreen 80 | filterByVirtualDesktop: plasmoid.configuration.widgetActiveTaskFilterByVirtualDesktop 81 | filterHidden: true 82 | filterMinimized: true 83 | filterNotMaximized: plasmoid.configuration.widgetActiveTaskSource == ActiveTasksModel.ActiveTaskSource.LastActiveMaximized 84 | onDataChanged: function (from, to, roles) { 85 | if (hasActiveWindow && activeTaskIndex >= from && activeTaskIndex <= to) 86 | updateActiveTaskIndex(); 87 | else if (!hasActiveWindow && getFirstRowIndex() >= from && getFirstRowIndex() <= to) 88 | updateActiveTaskIndex(); 89 | } 90 | onActiveTaskChanged: updateActiveTaskIndex() 91 | onCountChanged: updateActiveTaskIndex() 92 | sortMode: TaskManager.TasksModel.SortLastActivated 93 | groupMode: TaskManager.TasksModel.GroupDisabled 94 | Component.onCompleted: updateActiveTaskIndex() 95 | 96 | virtualDesktopInfo: TaskManager.VirtualDesktopInfo {} 97 | 98 | activityInfo: TaskManager.ActivityInfo { 99 | readonly property string nullUuid: "00000000-0000-0000-0000-000000000000" 100 | } 101 | 102 | activeWindow: ActiveWindow { 103 | function update() { 104 | minimizable = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsMinimizable) || false; 105 | maximizable = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsMaximizable) || false; 106 | closable = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsClosable) || false; 107 | movable = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsMovable) || false; 108 | minimized = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsMinimized) || false; 109 | maximized = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsMaximized) || false; 110 | shadeable = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsShadeable) || false; 111 | shaded = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsShaded) || false; 112 | keepAbove = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsKeepAbove) || false; 113 | keepBelow = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsKeepBelow) || false; 114 | hasAppMenu = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.ApplicationMenuServiceName) || false; 115 | onAllVirtualDesktops = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsOnAllVirtualDesktops) || false; 116 | fullScreenable = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsFullScreenable) || false; 117 | fullScreen = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsFullScreen) || false; 118 | resizable = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsResizable) || false; 119 | active = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.IsActive) || false; 120 | appName = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.AppName); 121 | genericAppName = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.GenericAppName); 122 | decoration = tasksModel.data(activeTaskIndex, TaskManager.AbstractTasksModel.Decoration); 123 | icon = tasksModel.data(activeTaskIndex, Qt.DecorationRole); 124 | tasksModel.activeWindowUpdated(); 125 | } 126 | 127 | onActionCall: function (action) { 128 | switch (action) { 129 | case ActiveWindow.Action.Close: 130 | return tasksModel.requestClose(activeTaskIndex); 131 | case ActiveWindow.Action.Minimize: 132 | return tasksModel.requestToggleMinimized(activeTaskIndex); 133 | case ActiveWindow.Action.Maximize: 134 | return tasksModel.requestToggleMaximized(activeTaskIndex); 135 | case ActiveWindow.Action.Move: 136 | return tasksModel.requestMove(activeTaskIndex); 137 | case ActiveWindow.Action.KeepAbove: 138 | return tasksModel.requestToggleKeepAbove(activeTaskIndex); 139 | case ActiveWindow.Action.KeepBelow: 140 | return tasksModel.requestToggleKeepBelow(activeTaskIndex); 141 | case ActiveWindow.Action.Shade: 142 | return tasksModel.requestToggleShaded(activeTaskIndex); 143 | case ActiveWindow.Action.Activate: 144 | return tasksModel.requestActivate(activeTaskIndex); 145 | case ActiveWindow.Action.FullScreen: 146 | return tasksModel.requestToggleFullScreen(activeTaskIndex); 147 | case ActiveWindow.Action.Resize: 148 | return tasksModel.requestResize(activeTaskIndex); 149 | } 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /package/contents/ui/ActiveWindow.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | 9 | QtObject { 10 | id: activeWindow 11 | 12 | enum Action { 13 | Close, 14 | Minimize, 15 | Maximize, 16 | Move, 17 | AllDesktops, 18 | KeepAbove, 19 | KeepBelow, 20 | Shade, 21 | Help, 22 | Menu, 23 | AppMenu, 24 | Activate, 25 | FullScreen, 26 | Resize 27 | } 28 | 29 | property bool minimizable: false 30 | property bool maximizable: false 31 | property bool closable: false 32 | property bool movable: false 33 | property bool minimized: false 34 | property bool maximized: false 35 | property bool shadeable: false 36 | property bool shaded: false 37 | property bool hasAppMenu: false 38 | property bool onAllVirtualDesktops: false 39 | property bool keepAbove: false 40 | property bool keepBelow: false 41 | property bool fullScreenable: false 42 | property bool fullScreen: false 43 | property bool resizable: false 44 | property bool active: false 45 | property var appName 46 | property var genericAppName 47 | property var decoration 48 | property var icon 49 | 50 | signal actionCall(int action) 51 | 52 | function actionSupported(action) { 53 | switch (action) { 54 | case ActiveWindow.Action.Close: 55 | return closable; 56 | case ActiveWindow.Action.Minimize: 57 | return minimizable; 58 | case ActiveWindow.Action.Maximize: 59 | return maximizable; 60 | case ActiveWindow.Action.Move: 61 | return movable; 62 | case ActiveWindow.Action.Shade: 63 | return shadeable; 64 | case ActiveWindow.Action.AppMenu: 65 | return hasAppMenu; 66 | case ActiveWindow.Action.Fullscreen: 67 | return fullScreenable; 68 | case ActiveWindow.Action.Resize: 69 | return resizable; 70 | default: 71 | return true; 72 | } 73 | } 74 | 75 | function buttonChecked(windowControlButtonType) { 76 | switch (windowControlButtonType) { 77 | case WindowControlButton.Type.MinimizeButton: 78 | return minimized; 79 | case WindowControlButton.Type.MaximizeButton: 80 | return maximized; 81 | case WindowControlButton.Type.KeepAboveButton: 82 | return keepAbove; 83 | case WindowControlButton.Type.KeepBelowButton: 84 | return keepBelow; 85 | case WindowControlButton.Type.ShadeButton: 86 | return shaded; 87 | default: 88 | return false; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /package/contents/ui/ConfigManager.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtCore 9 | import QtQuick.Dialogs 10 | 11 | Loader { 12 | id: settingsLoader 13 | 14 | active: false 15 | 16 | property string name 17 | property date dateCreated 18 | property string settingsJson 19 | property url location 20 | property bool writeMode: true 21 | 22 | signal configSaved(url fileUrl) 23 | signal configLoaded(url fileUrl, var settings) 24 | 25 | property var configExportFileDialog: FileDialog { 26 | nameFilters: ["Configuration (*.cfg)"] 27 | defaultSuffix: "cfg" 28 | fileMode: FileDialog.SaveFile 29 | onAccepted: savePlasmoidConfig(selectedFile) 30 | } 31 | 32 | property var configImportFileDialog: FileDialog { 33 | nameFilters: ["Configuration (*.cfg)"] 34 | defaultSuffix: "cfg" 35 | fileMode: FileDialog.OpenFile 36 | onAccepted: loadPlasmoidConfig(selectedFile) 37 | } 38 | 39 | sourceComponent: Settings { 40 | location: settingsLoader.location 41 | property int version: 0 42 | property string name 43 | property date dateCreated 44 | property string settingsJson 45 | } 46 | 47 | onLoaded: function () { 48 | if (writeMode) { 49 | item.name = name; 50 | item.dateCreated = new Date(); 51 | item.settingsJson = settingsJson; 52 | configSaved(item.location); 53 | } else { 54 | name = item.value("name", ""); 55 | dateCreated = item.value("dateCreated", new Date()); 56 | settingsJson = item.value("settingsJson", "{}"); 57 | configLoaded(item.location, JSON.parse(settingsJson)); 58 | } 59 | active = false; 60 | } 61 | 62 | function showConfigExportFileDialog() { 63 | configExportFileDialog.open(); 64 | } 65 | 66 | function showConfigImportFileDialog() { 67 | configImportFileDialog.open(); 68 | } 69 | 70 | function saveSettings(name, settings, fileUrl) { 71 | name = name; 72 | settingsJson = JSON.stringify(settings); 73 | location = fileUrl; 74 | writeMode = true; 75 | active = true; 76 | } 77 | 78 | function loadSettings(fileUrl) { 79 | location = fileUrl; 80 | writeMode = false; 81 | active = true; 82 | } 83 | 84 | function getPlasmoidConfig() { 85 | let configMap = {}; 86 | const config = plasmoid.configuration; 87 | const propertyNames = config.keys(); 88 | for (let index = 0; index < propertyNames.length; index++) { 89 | const propertyName = propertyNames[index]; 90 | const propertyValue = config[propertyName]; 91 | configMap[propertyName] = propertyValue; 92 | } 93 | return configMap; 94 | } 95 | 96 | function savePlasmoidConfig(fileUrl) { 97 | const config = getPlasmoidConfig(); 98 | saveSettings("Application Title Bar Configuration", config, fileUrl); 99 | } 100 | 101 | function loadPlasmoidConfig(fileUrl) { 102 | const loadedConfig = loadSettings(fileUrl); 103 | } 104 | 105 | function updatePlasmoidConfig(loadedConfig) { 106 | let plasmoidConfig = plasmoid.configuration; 107 | for (const propertyName in loadedConfig) { 108 | let loadedValue = loadedConfig[propertyName]; 109 | let loadedValueString = JSON.stringify(loadedValue); 110 | let plasmoidValue = plasmoidConfig[propertyName]; 111 | let plasmoidValueString = JSON.stringify(plasmoidValue); 112 | if (loadedValueString !== plasmoidValueString && !plasmoidConfig.isImmutable(propertyName)) { 113 | //console.log("Diff in " + propertyName + " : '" + plasmoidConfig[propertyName] + "' !== '" + loadedValue + "'"); 114 | plasmoidConfig[propertyName] = loadedValue; 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /package/contents/ui/ContextualActions.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtCore 9 | import QtQuick.Dialogs 10 | import QtQuick.Controls 11 | import org.kde.plasma.core as PlasmaCore 12 | import org.kde.plasma.plasmoid 13 | 14 | QtObject { 15 | property Item configManager: ConfigManager { 16 | onConfigLoaded: function (fileUrl, loadedConfig) { 17 | updatePlasmoidConfig(loadedConfig); 18 | } 19 | } 20 | 21 | Plasmoid.contextualActions: [ 22 | PlasmaCore.Action { 23 | text: i18n("Ma&ximize") 24 | enabled: tasksModel.activeWindow.maximizable 25 | checked: tasksModel.activeWindow.maximized 26 | icon.name: "window-maximize" 27 | checkable: true 28 | onTriggered: tasksModel.activeWindow.actionCall(ActiveWindow.Action.Maximize) 29 | }, 30 | PlasmaCore.Action { 31 | text: i18n("Mi&nimize") 32 | enabled: tasksModel.activeWindow.minimizable 33 | icon.name: "window-minimize" 34 | checkable: false 35 | onTriggered: tasksModel.activeWindow.actionCall(ActiveWindow.Action.Minimize) 36 | }, 37 | PlasmaCore.Action { 38 | text: i18n("Keep &Above Others") 39 | checked: tasksModel.activeWindow.keepAbove 40 | icon.name: "window-keep-above" 41 | checkable: true 42 | onTriggered: tasksModel.activeWindow.actionCall(ActiveWindow.Action.KeepAbove) 43 | }, 44 | PlasmaCore.Action { 45 | text: i18n("Keep &Below Others") 46 | checked: tasksModel.activeWindow.keepBelow 47 | icon.name: "window-keep-below" 48 | checkable: true 49 | onTriggered: tasksModel.activeWindow.actionCall(ActiveWindow.Action.KeepBelow) 50 | }, 51 | PlasmaCore.Action { 52 | text: i18n("&Fullscreen") 53 | enabled: tasksModel.activeWindow.fullScreenable 54 | checked: tasksModel.activeWindow.fullScreen 55 | icon.name: "view-fullscreen" 56 | checkable: true 57 | onTriggered: tasksModel.activeWindow.actionCall(ActiveWindow.Action.FullScreen) 58 | }, 59 | PlasmaCore.Action { 60 | text: i18n("&Move") 61 | icon.name: "transform-move" 62 | enabled: tasksModel.activeTask.movable 63 | checkable: false 64 | onTriggered: tasksModel.activeWindow.actionCall(ActiveWindow.Action.Move) 65 | }, 66 | PlasmaCore.Action { 67 | text: i18n("&Resize") 68 | icon.name: "image-resize-symbolic" 69 | enabled: tasksModel.activeTask.resizable 70 | checkable: false 71 | onTriggered: tasksModel.activeWindow.actionCall(ActiveWindow.Action.Resize) 72 | }, 73 | PlasmaCore.Action { 74 | text: i18n("&Close") 75 | icon.name: "window-close" 76 | enabled: tasksModel.activeTask.closable 77 | checkable: false 78 | onTriggered: tasksModel.activeWindow.actionCall(ActiveWindow.Action.Close) 79 | }, 80 | PlasmaCore.Action { 81 | text: i18n("&Export configuration...") 82 | icon.name: "document-export" 83 | checkable: false 84 | onTriggered: configManager.showConfigExportFileDialog() 85 | }, 86 | PlasmaCore.Action { 87 | text: i18n("&Import configuration...") 88 | icon.name: "document-import" 89 | checkable: false 90 | onTriggered: configManager.showConfigImportFileDialog() 91 | } 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /package/contents/ui/ExecutableDataSource.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | import org.kde.plasma.plasma5support as P5Support 7 | 8 | // Replace by QML plugin when it will be available: https://invent.kde.org/plasma/plasma-workspace/-/issues/54 9 | P5Support.DataSource { 10 | id: executable 11 | 12 | signal exited(string cmd, int exitCode, int exitStatus, string stdout, string stderr) 13 | 14 | function exec(cmd) { 15 | if (cmd) 16 | connectSource(cmd); 17 | } 18 | 19 | engine: "executable" 20 | connectedSources: [] 21 | onNewData: function (sourceName, data) { 22 | var exitCode = data["exit code"]; 23 | var exitStatus = data["exit status"]; 24 | var stdout = data["stdout"]; 25 | var stderr = data["stderr"]; 26 | exited(sourceName, exitCode, exitStatus, stdout, stderr); 27 | disconnectSource(sourceName); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /package/contents/ui/KWinConfig.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | import "../" 7 | import Qt.labs.folderlistmodel 8 | import QtCore 9 | import QtQuick 10 | 11 | Item { 12 | id: kWinConfig 13 | 14 | readonly property string auroraeThemesPath: "aurorae/themes/" 15 | property string setBorderlessMaximizedWindowsCommand: kwriteconfigCommandName !== "" ? kwriteconfigCommandName + " --file kwinrc --group Windows --key BorderlessMaximizedWindows " : "" 16 | property string getBorderlessMaximizedWindowsCommand: kreadconfigCommandName !== "" ? kreadconfigCommandName + " --file kwinrc --group Windows --key BorderlessMaximizedWindows --default false" : "" 17 | property string reconfigureCommand: qdbusCommandName !== "" ? qdbusCommandName + " org.kde.KWin /KWin reconfigure" : "" 18 | property string getAllKWinShortcutNamesCommand: qdbusCommandName !== "" ? qdbusCommandName + " org.kde.kglobalaccel /component/kwin org.kde.kglobalaccel.Component.shortcutNames" : "" 19 | property string invokeKWinShortcutCommand: qdbusCommandName !== "" ? qdbusCommandName + " org.kde.kglobalaccel /component/kwin org.kde.kglobalaccel.Component.invokeShortcut " : "" 20 | property var borderlessMaximizedWindows 21 | property var callbacksOnExited: [] 22 | property var auroraeThemesLocations: StandardPaths.locateAll(StandardPaths.GenericDataLocation, auroraeThemesPath, StandardPaths.LocateDirectory) 23 | property ListModel auroraeThemes 24 | property var shortcutNames: [] 25 | property string qdbusCommandName: "qdbus" 26 | property string kwriteconfigCommandName: "kwriteconfig6" 27 | property string kreadconfigCommandName: "kreadconfig6" 28 | property string lastError: "" 29 | 30 | function findExistingFromListCommand(commandsList) { 31 | let cmd = ""; 32 | if (commandsList) { 33 | cmd += "if command -v " + commandsList[0] + " > /dev/null; then echo " + commandsList[0] + "; "; 34 | for (const c of commandsList.slice(1)) { 35 | cmd += "elif command -v " + c + " > /dev/null; then echo " + c + "; "; 36 | } 37 | cmd += "else exit 1; fi"; 38 | } 39 | return cmd; 40 | } 41 | 42 | function setBorderlessMaximizedWindows(val) { 43 | if (setBorderlessMaximizedWindowsCommand === "") { 44 | return; 45 | } 46 | let cmd = setBorderlessMaximizedWindowsCommand + val + " && " + reconfigureCommand + " && " + getBorderlessMaximizedWindowsCommand; 47 | callbacksOnExited.push({ 48 | "cmd": cmd, 49 | "callback": function (cmd, exitCode, exitStatus, stdout, stderr) { 50 | if (exitCode == 0) { 51 | borderlessMaximizedWindows = stdout.trim() == "true"; 52 | } else { 53 | lastError = "Unable to update set BorderlessMaximizedWindows status: '" + stderr + "'"; 54 | } 55 | } 56 | }); 57 | executable.exec(cmd); 58 | } 59 | 60 | function updateBorderlessMaximizedWindows() { 61 | if (getBorderlessMaximizedWindowsCommand === "") { 62 | return; 63 | } 64 | let cmd = getBorderlessMaximizedWindowsCommand; 65 | callbacksOnExited.push({ 66 | "cmd": cmd, 67 | "callback": function (cmd, exitCode, exitStatus, stdout, stderr) { 68 | if (exitCode == 0) { 69 | borderlessMaximizedWindows = stdout.trim() == "true"; 70 | } else { 71 | lastError = "Unable to update get BorderlessMaximizedWindows status: '" + stderr + "'"; 72 | } 73 | } 74 | }); 75 | executable.exec(cmd); 76 | } 77 | 78 | function updateAuroraeThemes() { 79 | auroraeThemes.clear(); 80 | for (var locationIndex = 0; locationIndex < auroraeThemesLocationsRepeater.count; locationIndex++) { 81 | let locationItem = auroraeThemesLocationsRepeater.itemAt(locationIndex); 82 | for (var themeIndex = 0; themeIndex < locationItem.count; themeIndex++) { 83 | let themeModel = locationItem.itemAt(themeIndex); 84 | auroraeThemes.append({ 85 | "name": themeModel.fileName, 86 | "folder": themeModel.fileName, 87 | "path": themeModel.filePath 88 | }); 89 | } 90 | } 91 | auroraeThemesChanged(); 92 | } 93 | 94 | function updateKWinShortcutNames() { 95 | if (getAllKWinShortcutNamesCommand === "") { 96 | return; 97 | } 98 | let cmd = getAllKWinShortcutNamesCommand; 99 | callbacksOnExited.push({ 100 | "cmd": cmd, 101 | "callback": function (cmd, exitCode, exitStatus, stdout, stderr) { 102 | if (exitCode == 0) { 103 | shortcutNames = stdout.trim().split(/\r?\n/).sort(); 104 | } else { 105 | lastError = "Unable to update KWin shortcuts: '" + stderr + "'"; 106 | } 107 | } 108 | }); 109 | executable.exec(cmd); 110 | } 111 | 112 | function invokeKWinShortcut(shortcut) { 113 | let cmd = invokeKWinShortcutCommand; 114 | let trimmedShortcut = shortcut.trim(); 115 | if (shortcutNames.length === 0 || shortcutNames.includes(trimmedShortcut)) 116 | executable.exec(cmd + "\"" + trimmedShortcut + "\""); 117 | else 118 | print("Error: shortcut '" + trimmedShortcut + "' not found in the list!"); 119 | } 120 | 121 | function updateQdbusCommandName() { 122 | updateCommandName(["/usr/lib/qt6/bin/qdbus", "qdbus", "qdbus6", "qdbus-qt6"], function (commandName) { 123 | qdbusCommandName = commandName; 124 | qdbusCommandNameChanged(); 125 | }); 126 | } 127 | 128 | function updateKwriteconfigCommandName() { 129 | updateCommandName(["kwriteconfig6", "kwriteconfig"], function (commandName) { 130 | kwriteconfigCommandName = commandName; 131 | kwriteconfigCommandNameChanged(); 132 | }); 133 | } 134 | 135 | function updateKreadconfigCommandName() { 136 | updateCommandName(["kreadconfig6", "kreadconfig"], function (commandName) { 137 | kreadconfigCommandName = commandName; 138 | kreadconfigCommandNameChanged(); 139 | }); 140 | } 141 | 142 | function updateCommandName(commandsList, setCommandNameCallback) { 143 | const cmd = findExistingFromListCommand(commandsList); 144 | callbacksOnExited.push({ 145 | "cmd": cmd, 146 | "callback": function (cmd, exitCode, exitStatus, stdout, stderr) { 147 | if (exitCode == 0) { 148 | setCommandNameCallback(stdout.trim()); 149 | } else { 150 | setCommandNameCallback(""); 151 | lastError = "Unable to find command from list: " + commandsList; 152 | } 153 | } 154 | }); 155 | executable.exec(cmd); 156 | } 157 | 158 | function clearLastError() { 159 | lastError = ""; 160 | } 161 | 162 | ExecutableDataSource { 163 | id: executable 164 | } 165 | 166 | Connections { 167 | function onExited(cmd, exitCode, exitStatus, stdout, stderr) { 168 | //print("onExited: cmd: " + cmd + "; exitCode:" + exitCode + "; stdout: " + stdout + "; stderr: " + stderr); 169 | if (exitCode == 0) { 170 | clearLastError(); 171 | } 172 | for (var i = 0; i < callbacksOnExited.length; i++) { 173 | if (callbacksOnExited[i].cmd === cmd) { 174 | callbacksOnExited[i].callback(cmd, exitCode, exitStatus, stdout, stderr); 175 | callbacksOnExited.splice(i, 1); 176 | break; 177 | } 178 | } 179 | } 180 | 181 | target: executable 182 | } 183 | 184 | Repeater { 185 | id: auroraeThemesLocationsRepeater 186 | 187 | model: auroraeThemesLocations 188 | 189 | delegate: Repeater { 190 | required property string modelData 191 | 192 | model: FolderListModel { 193 | folder: modelData 194 | showDirs: true 195 | showFiles: false 196 | showHidden: true 197 | onCountChanged: kWinConfig.updateAuroraeThemes() 198 | } 199 | 200 | delegate: Item { 201 | required property string fileName 202 | required property string filePath 203 | } 204 | } 205 | } 206 | 207 | auroraeThemes: ListModel {} 208 | 209 | Component.onCompleted: function () { 210 | updateQdbusCommandName(); 211 | updateKwriteconfigCommandName(); 212 | updateKreadconfigCommandName(); 213 | } 214 | 215 | onQdbusCommandNameChanged: function () { 216 | updateKWinShortcutNames(); 217 | } 218 | 219 | onKreadconfigCommandNameChanged: function () { 220 | updateBorderlessMaximizedWindows(); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /package/contents/ui/KWindowSystemAdapter.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | import QtQuick 7 | import org.kde.kwindowsystem 8 | 9 | QtObject { 10 | property bool showingDesktop: KWindowSystem.showingDesktop 11 | } 12 | -------------------------------------------------------------------------------- /package/contents/ui/MouseHandlers.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | 9 | Item { 10 | id: handlers 11 | anchors.fill: parent 12 | 13 | signal invokeKWinShortcut(string shortcut) 14 | 15 | WidgetDragHandler { 16 | id: dragHandler 17 | Component.onCompleted: { 18 | invokeKWinShortcut.connect(handlers.invokeKWinShortcut); 19 | } 20 | onInvokeKWinShortcut: tapHandler.stopLongPressTimer() 21 | } 22 | 23 | WidgetTapHandler { 24 | id: tapHandler 25 | Component.onCompleted: { 26 | invokeKWinShortcut.connect(handlers.invokeKWinShortcut); 27 | } 28 | onInvokeKWinShortcut: dragHandler.stopDrag() 29 | } 30 | 31 | WidgetWheelHandler { 32 | orientation: Qt.Vertical 33 | Component.onCompleted: { 34 | invokeKWinShortcut.connect(handlers.invokeKWinShortcut); 35 | } 36 | } 37 | 38 | WidgetWheelHandler { 39 | orientation: Qt.Horizontal 40 | Component.onCompleted: { 41 | invokeKWinShortcut.connect(handlers.invokeKWinShortcut); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /package/contents/ui/SortableItemsRow.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import QtQuick.Controls 9 | import QtQuick.Layouts 10 | 11 | Item { 12 | id: listItemsContainer 13 | 14 | property alias model: listItemsRepeater.model 15 | property Component sourceComponent 16 | property bool dragActive: false 17 | 18 | MouseArea { 19 | id: backgroundCursorShapeMouseArea 20 | anchors.fill: parent 21 | enabled: false 22 | 23 | states: State { 24 | when: listItemsContainer.dragActive 25 | 26 | PropertyChanges { 27 | backgroundCursorShapeMouseArea.cursorShape: Qt.DragMoveCursor 28 | } 29 | } 30 | } 31 | 32 | RowLayout { 33 | Component { 34 | id: listItemDelegate 35 | 36 | MouseArea { 37 | id: dragArea 38 | 39 | required property var modelData 40 | 41 | drag.axis: Drag.XAxis 42 | drag.target: contentLoader 43 | Layout.preferredWidth: contentLoader.width 44 | Layout.preferredHeight: contentLoader.height 45 | cursorShape: listItemsContainer.dragActive ? backgroundCursorShapeMouseArea.cursorShape : Qt.OpenHandCursor 46 | 47 | DropArea { 48 | anchors.fill: parent 49 | anchors.margins: 5 50 | onEntered: drag => { 51 | listItemsRepeater.model.move(drag.source.DelegateModel.itemsIndex, dragArea.DelegateModel.itemsIndex, 1); 52 | } 53 | } 54 | 55 | Loader { 56 | id: contentLoader 57 | 58 | Drag.source: dragArea 59 | Drag.active: dragArea.drag.active 60 | Drag.hotSpot.x: width / 2 61 | Drag.hotSpot.y: height / 2 62 | onLoaded: item.modelData = modelData 63 | sourceComponent: listItemsContainer.sourceComponent 64 | } 65 | 66 | Binding { 67 | target: listItemsContainer 68 | property: "dragActive" 69 | value: dragArea.drag.active 70 | } 71 | 72 | states: State { 73 | when: dragArea.drag.active 74 | 75 | ParentChange { 76 | target: contentLoader 77 | parent: listItemsContainer 78 | } 79 | 80 | AnchorChanges { 81 | target: contentLoader 82 | 83 | anchors { 84 | horizontalCenter: undefined 85 | verticalCenter: undefined 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | Repeater { 93 | id: listItemsRepeater 94 | 95 | delegate: listItemDelegate 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /package/contents/ui/WidgetDragHandler.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | import org.kde.plasma.plasmoid 9 | 10 | /* 11 | * Drag handler implemented in that strange way as workaround for cases, when window layout changed during the drag interaction. 12 | */ 13 | PointHandler { 14 | property bool dragInProgress: false 15 | property var cfg: plasmoid.configuration 16 | 17 | signal invokeKWinShortcut(string shortcut) 18 | 19 | function distance(p1, p2) { 20 | let dx = p2.x - p1.x; 21 | let dy = p2.y - p1.y; 22 | return Math.sqrt(dx * dx + dy * dy); 23 | } 24 | 25 | function stopDrag() { 26 | dragInProgress = false; 27 | } 28 | 29 | enabled: cfg.windowTitleDragEnabled 30 | dragThreshold: cfg.windowTitleDragThreshold 31 | acceptedButtons: Qt.LeftButton | Qt.MiddleButton 32 | onActiveChanged: function () { 33 | if (active && (!cfg.windowTitleDragOnlyMaximized || tasksModel.activeWindow.maximized)) 34 | dragInProgress = true; 35 | else 36 | stopDrag(); 37 | } 38 | onPointChanged: function () { 39 | if (active && dragInProgress && point && point.pressPosition && point.position) { 40 | if (distance(point.pressPosition, point.position) > dragThreshold) { 41 | stopDrag(); 42 | if (point.pressedButtons & Qt.LeftButton && cfg.widgetMouseAreaLeftDragAction != "") 43 | invokeKWinShortcut(cfg.widgetMouseAreaLeftDragAction); 44 | else if (point.pressedButtons & Qt.MiddleButton && cfg.widgetMouseAreaMiddleDragAction != "") 45 | invokeKWinShortcut(cfg.widgetMouseAreaMiddleDragAction); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /package/contents/ui/WidgetEffectsRepeater.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2025 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | import "config/effect/" 7 | import "config/effect/effect.js" as EffectUtils 8 | import QtQuick 9 | 10 | Repeater { 11 | id: effectsRepeater 12 | 13 | model: plasmoid.configuration.effects 14 | 15 | property var effectRules: [] 16 | 17 | function updateEffectRules() { 18 | let rulesList = []; 19 | for (var i = 0; i < plasmoid.configuration.effectRules.length; i++) { 20 | const ruleModel = JSON.parse(plasmoid.configuration.effectRules[i]); 21 | const rule = EffectUtils.Condition.fromModel(ruleModel); 22 | rulesList[i] = rule; 23 | } 24 | effectRules = rulesList; 25 | updateEffectsState(); 26 | } 27 | 28 | function updateEffectsState() { 29 | var effectsState = []; 30 | for (var i = 0; i < effectsRepeater.effectRules.length; i++) { 31 | if (!effectsState[i]) { 32 | const rule = effectsRepeater.effectRules[i]; 33 | effectsState[rule.effectIndex] = rule.checkCondition(root); 34 | } 35 | } 36 | for (var i = 0; i < effectsRepeater.count; i++) { 37 | const item = effectsRepeater.itemAt(i); 38 | if (item) { 39 | item.opacity = effectsState[i] ? 1.0 : 0.0; 40 | } 41 | } 42 | } 43 | 44 | MergedMultiEffect { 45 | anchors.fill: widgetRow 46 | source: widgetRow 47 | opacity: 0.0 48 | visible: opacity !== 0.0 49 | 50 | required property int index 51 | required property var modelData 52 | 53 | onModelDataChanged: function () { 54 | effectModel.updateFromJson(modelData); 55 | } 56 | 57 | Behavior on opacity { 58 | NumberAnimation { 59 | duration: plasmoid.configuration.widgetButtonsAnimation 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /package/contents/ui/WidgetElement.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | 9 | QtObject { 10 | 11 | enum Type { 12 | WindowControlButton, 13 | WindowTitle, 14 | WindowIcon, 15 | Spacer 16 | } 17 | 18 | enum DisabledMode { 19 | Deactivated, 20 | HideKeepSpace, 21 | Hide 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package/contents/ui/WidgetTapHandler.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | 9 | /* 10 | * Tap handler implemented in that strange way as workaround for cases, when window layout changed during the long press interaction. 11 | */ 12 | TapHandler { 13 | id: tapHandler 14 | 15 | property var cfg: plasmoid.configuration 16 | property Timer longPressTimer: Timer { 17 | property int pressedButtons: 0 18 | interval: Qt.styleHints.mousePressAndHoldInterval 19 | onTriggered: function () { 20 | if (pressedButtons & Qt.LeftButton && cfg.widgetMouseAreaLeftLongPressAction != "") 21 | invokeKWinShortcut(cfg.widgetMouseAreaLeftLongPressAction); 22 | else if (pressedButtons & Qt.MiddleButton && cfg.widgetMouseAreaMiddleLongPressAction != "") 23 | invokeKWinShortcut(cfg.widgetMouseAreaMiddleLongPressAction); 24 | } 25 | } 26 | 27 | signal invokeKWinShortcut(string shortcut) 28 | 29 | enabled: cfg.widgetMouseAreaClickEnabled 30 | acceptedButtons: Qt.LeftButton | Qt.MiddleButton 31 | longPressThreshold: 0 32 | exclusiveSignals: TapHandler.SingleTap | TapHandler.DoubleTap 33 | onSingleTapped: function (eventPoint, button) { 34 | if (button === Qt.LeftButton && cfg.widgetMouseAreaLeftClickAction != "") 35 | invokeKWinShortcut(cfg.widgetMouseAreaLeftClickAction); 36 | else if (button === Qt.MiddleButton && cfg.widgetMouseAreaMiddleClickAction != "") 37 | invokeKWinShortcut(cfg.widgetMouseAreaMiddleClickAction); 38 | } 39 | onDoubleTapped: function (eventPoint, button) { 40 | if (button === Qt.LeftButton && cfg.widgetMouseAreaLeftDoubleClickAction != "") 41 | invokeKWinShortcut(cfg.widgetMouseAreaLeftDoubleClickAction); 42 | else if (button === Qt.MiddleButton && cfg.widgetMouseAreaMiddleDoubleClickAction != "") 43 | invokeKWinShortcut(cfg.widgetMouseAreaMiddleDoubleClickAction); 44 | } 45 | 46 | onPointChanged: function () { 47 | if (point.pressedButtons & acceptedButtons) { 48 | restartLongPressTimer(point.pressedButtons); 49 | } else { 50 | stopLongPressTimer(); 51 | } 52 | } 53 | 54 | function restartLongPressTimer(pressedButtons) { 55 | longPressTimer.restart(); 56 | longPressTimer.pressedButtons = pressedButtons; 57 | } 58 | 59 | function stopLongPressTimer() { 60 | longPressTimer.stop(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /package/contents/ui/WidgetToolTip.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import org.kde.plasma.core as PlasmaCore 8 | import org.kde.taskmanager as TaskManager 9 | 10 | PlasmaCore.ToolTipArea { 11 | enum ToolTipMode { 12 | Disabled, 13 | MaximizedWindow, 14 | Always 15 | } 16 | 17 | active: showToolTip() 18 | mainText: tasksModel.activeWindow.genericAppName || "" 19 | icon: tasksModel.activeWindow.icon 20 | 21 | property TaskManager.TasksModel tasksModel 22 | 23 | function showToolTip() { 24 | switch (plasmoid.configuration.widgetToolTipMode) { 25 | case WidgetToolTip.ToolTipMode.Disabled: 26 | return false; 27 | case WidgetToolTip.ToolTipMode.MaximizedWindow: 28 | return tasksModel.hasActiveWindow && tasksModel.activeWindow.maximized; 29 | default: 30 | return tasksModel.hasActiveWindow; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package/contents/ui/WidgetWheelHandler.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtQuick 8 | 9 | WheelHandler { 10 | property var cfg: plasmoid.configuration 11 | property int verticalRotation: 0 12 | property int horizontalRotation: 0 13 | property bool firstHorizontalEvent: true 14 | property bool firstVerticalEvent: true 15 | property int firstEventDistance: cfg.widgetMouseAreaWheelFirstEventDistance 16 | property int nextEventDistance: cfg.widgetMouseAreaWheelNextEventDistance 17 | 18 | signal invokeKWinShortcut(string shortcut) 19 | signal wheelUp 20 | signal wheelDown 21 | signal wheelLeft 22 | signal wheelRight 23 | 24 | enabled: cfg.widgetMouseAreaWheelEnabled 25 | target: null 26 | acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad 27 | onActiveChanged: function () { 28 | if (!active) { 29 | verticalRotation = 0; 30 | horizontalRotation = 0; 31 | firstHorizontalEvent = true; 32 | firstVerticalEvent = true; 33 | } 34 | } 35 | onWheel: function (wheelEvent) { 36 | let dx = wheelEvent.angleDelta.x; 37 | let dy = wheelEvent.angleDelta.y; 38 | if (orientation == Qt.Horizontal) { 39 | horizontalRotation = (horizontalRotation < 0) == (dx < 0) ? (horizontalRotation + dx) : dx; 40 | let distance = Math.abs(horizontalRotation); 41 | if ((firstHorizontalEvent && distance >= firstEventDistance) || (nextEventDistance > 0 && distance >= nextEventDistance)) { 42 | if (horizontalRotation < 0) 43 | wheelRight(); 44 | else 45 | wheelLeft(); 46 | if (firstHorizontalEvent) { 47 | firstHorizontalEvent = false; 48 | horizontalRotation -= Math.sign(horizontalRotation) * firstEventDistance; 49 | } else { 50 | horizontalRotation -= Math.sign(horizontalRotation) * nextEventDistance; 51 | } 52 | } 53 | } else { 54 | verticalRotation = (verticalRotation < 0) == (dy < 0) ? (verticalRotation + dy) : dy; 55 | let distance = Math.abs(verticalRotation); 56 | if ((firstVerticalEvent && distance >= firstEventDistance) || (nextEventDistance > 0 && distance >= nextEventDistance)) { 57 | if (verticalRotation < 0) 58 | wheelDown(); 59 | else 60 | wheelUp(); 61 | if (firstVerticalEvent) { 62 | firstVerticalEvent = false; 63 | verticalRotation -= Math.sign(verticalRotation) * firstEventDistance; 64 | } else { 65 | verticalRotation -= Math.sign(verticalRotation) * nextEventDistance; 66 | } 67 | } 68 | } 69 | } 70 | onWheelUp: function () { 71 | if (cfg.widgetMouseAreaWheelUpAction != "") 72 | invokeKWinShortcut(cfg.widgetMouseAreaWheelUpAction); 73 | } 74 | onWheelDown: function () { 75 | if (cfg.widgetMouseAreaWheelDownAction != "") 76 | invokeKWinShortcut(cfg.widgetMouseAreaWheelDownAction); 77 | } 78 | onWheelLeft: function () { 79 | if (cfg.widgetMouseAreaWheelLeftAction != "") 80 | invokeKWinShortcut(cfg.widgetMouseAreaWheelLeftAction); 81 | } 82 | onWheelRight: function () { 83 | if (cfg.widgetMouseAreaWheelRightAction != "") 84 | invokeKWinShortcut(cfg.widgetMouseAreaWheelRightAction); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /package/contents/ui/WindowControlButton.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import QtCore 8 | import QtQuick 9 | import "windowControlButton.js" as WCB 10 | 11 | Item { 12 | id: button 13 | 14 | enum Type { 15 | MinimizeButton, 16 | MaximizeButton, 17 | RestoreButton, 18 | CloseButton, 19 | AllDesktopsButton, 20 | KeepAboveButton, 21 | KeepBelowButton, 22 | ShadeButton, 23 | HelpButton, 24 | MenuButton, 25 | AppMenuButton 26 | } 27 | 28 | function getAccessibleName(type) { 29 | switch (type) { 30 | case WindowControlButton.Type.MinimizeButton: 31 | return "Minimize"; 32 | case WindowControlButton.Type.MaximizeButton: 33 | return "Maximize"; 34 | case WindowControlButton.Type.RestoreButton: 35 | return "Restore"; 36 | case WindowControlButton.Type.CloseButton: 37 | return "Close"; 38 | case WindowControlButton.Type.AllDesktopsButton: 39 | return "All Desktops"; 40 | case WindowControlButton.Type.KeepAboveButton: 41 | return "Keep Above"; 42 | case WindowControlButton.Type.KeepBelowButton: 43 | return "Keep Below"; 44 | case WindowControlButton.Type.ShadeButton: 45 | return "Shade"; 46 | case WindowControlButton.Type.HelpButton: 47 | return "Help"; 48 | case WindowControlButton.Type.MenuButton: 49 | return "Menu"; 50 | case WindowControlButton.Type.AppMenuButton: 51 | return "Application Menu"; 52 | default: 53 | return ""; 54 | } 55 | } 56 | 57 | /* 58 | * Active* - icons for active windows. 59 | * *Disabled - disabled non-interactive buttons 60 | */ 61 | enum IconState { 62 | Active, 63 | ActiveHover, 64 | ActivePressed, 65 | ActiveChecked, 66 | ActiveHoverChecked, 67 | ActiveCheckedDisabled, 68 | ActiveDisabled, 69 | Inactive, 70 | InactiveHover, 71 | InactivePressed, 72 | InactiveChecked, 73 | InactiveHoverChecked, 74 | InactiveCheckedDisabled, 75 | InactiveDisabled 76 | } 77 | 78 | enum IconTheme { 79 | Plasma, 80 | Breeze, 81 | Aurorae, 82 | Oxygen 83 | } 84 | 85 | property string themeName 86 | property int buttonType 87 | property string action: WCB.getAction(buttonType) 88 | property bool active: true 89 | property bool hovered: hoverHandler.hovered 90 | property bool pressed: tapHandler.pressed 91 | property bool checked: false 92 | property int iconTheme: WindowControlButton.IconTheme.Plasma 93 | property int animationDuration: 100 94 | property int iconState: WindowControlButton.IconState.Active 95 | property bool mouseAreaEnabled: enabled 96 | property int verticalPadding: 0 97 | 98 | Accessible.role: Accessible.Button 99 | Accessible.focusable: true 100 | Accessible.name: i18n(getAccessibleName(buttonType)) 101 | Accessible.onPressAction: buttonActionCall() 102 | Accessible.checked: checked 103 | Accessible.pressed: pressed 104 | 105 | signal actionCall(int action) 106 | 107 | function updateIconState() { 108 | iconState = WCB.calculateIconState(button); 109 | } 110 | 111 | onEnabledChanged: Qt.callLater(updateIconState) 112 | onActiveChanged: Qt.callLater(updateIconState) 113 | onHoveredChanged: Qt.callLater(updateIconState) 114 | onPressedChanged: Qt.callLater(updateIconState) 115 | onCheckedChanged: Qt.callLater(updateIconState) 116 | 117 | function buttonActionCall() { 118 | button.actionCall(WCB.getAction(button.buttonType)); 119 | } 120 | 121 | HoverHandler { 122 | id: hoverHandler 123 | enabled: button.mouseAreaEnabled 124 | } 125 | 126 | TapHandler { 127 | id: tapHandler 128 | enabled: button.mouseAreaEnabled 129 | acceptedButtons: Qt.LeftButton 130 | gesturePolicy: TapHandler.WithinBounds 131 | exclusiveSignals: TapHandler.SingleTap 132 | 133 | onTapped: function () { 134 | buttonActionCall(); 135 | } 136 | } 137 | 138 | Repeater { 139 | id: iconStateRepeater 140 | anchors.fill: parent 141 | model: WCB.statePropertiesMap 142 | 143 | Loader { 144 | id: controlButtonDelegeate 145 | required property int index 146 | required property var modelData 147 | anchors.fill: parent 148 | anchors.topMargin: button.verticalPadding 149 | anchors.bottomMargin: button.verticalPadding 150 | source: WCB.getButtonComponentSourcePath(button.iconTheme) 151 | onLoaded: function () { 152 | let buttonComponent = item; 153 | let buttonState = index; 154 | buttonComponent.hovered = modelData.hovered; 155 | buttonComponent.checked = modelData.checked; 156 | buttonComponent.pressed = modelData.pressed; 157 | buttonComponent.active = modelData.active; 158 | buttonComponent.disabled = modelData.disabled; 159 | buttonComponent.buttonType = Qt.binding(function () { 160 | return button.buttonType; 161 | }); 162 | buttonComponent.animationDuration = Qt.binding(function () { 163 | return button.animationDuration; 164 | }); 165 | buttonComponent.opacity = Qt.binding(function () { 166 | return buttonState == button.iconState ? 1 : 0; 167 | }); 168 | if (iconTheme === WindowControlButton.IconTheme.Aurorae) { 169 | buttonComponent.themeName = Qt.binding(function () { 170 | return button.themeName; 171 | }); 172 | } 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /package/contents/ui/common/JsonListModel.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | import QtQuick 7 | 8 | ListModel { 9 | id: effectsListModel 10 | 11 | required property var tempModel 12 | property list stringListModel 13 | dynamicRoles: true 14 | 15 | function updateModelFromConfig() { 16 | clear(); 17 | for (let i = 0; i < stringListModel.length; i++) { 18 | const effectJson = stringListModel[i]; 19 | tempModel.updateFromJson(effectJson); 20 | append(tempModel.toJsonObject()); 21 | } 22 | } 23 | 24 | function updateConfigFromModel() { 25 | Qt.callLater(_updateConfigFromModel); 26 | } 27 | 28 | function _updateConfigFromModel() { 29 | const length = count; 30 | stringListModel.length = length; 31 | for (let i = 0; i < length; i++) { 32 | tempModel.updateFromJsonObject(get(i)); 33 | stringListModel[i] = tempModel.toJson(); 34 | } 35 | } 36 | 37 | function pushModel(model) { 38 | tempModel.updateFromJsonObject(model); 39 | append(tempModel.toJsonObject()); 40 | updateConfigFromModel(); 41 | } 42 | 43 | function deleteModel(index) { 44 | remove(index); 45 | updateConfigFromModel(); 46 | } 47 | 48 | Component.onCompleted: updateModelFromConfig() 49 | } 50 | -------------------------------------------------------------------------------- /package/contents/ui/common/JsonModel.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | import QtQuick 7 | import QtQuick.Effects 8 | 9 | QtObject { 10 | id: model 11 | 12 | required property list _propertyNames 13 | 14 | signal propertyChanged(string propertyName, var propertyValue) 15 | 16 | Component.onCompleted: { 17 | for (const propertyName of model._propertyNames) { 18 | model[propertyName + "Changed"].connect(function () { 19 | propertyChanged(propertyName, model[propertyName]); 20 | }); 21 | } 22 | } 23 | 24 | function toJson() { 25 | let cloned = toJsonObject(); 26 | return JSON.stringify(cloned); 27 | } 28 | 29 | function toJsonObject() { 30 | let cloned = {}; 31 | for (const propertyName of model._propertyNames) { 32 | const value = model[propertyName]; 33 | if (value !== undefined && value !== null) { 34 | cloned[propertyName] = value; 35 | } 36 | } 37 | return cloned; 38 | } 39 | 40 | function updateFromJson(json) { 41 | const obj = JSON.parse(json); 42 | updateFromJsonObject(obj); 43 | } 44 | 45 | function updateFromJsonObject(jsonObject) { 46 | for (const propertyName of model._propertyNames) { 47 | const value = jsonObject[propertyName]; 48 | model[propertyName] = value === null ? undefined : value; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /package/contents/ui/config/AddWidgetElement.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | import QtQuick 7 | import QtQuick.Controls 8 | import org.kde.kirigami as Kirigami 9 | 10 | ComboBox { 11 | textRole: "name" 12 | valueRole: "value" 13 | displayText: currentText ? i18n(currentText) : "" 14 | 15 | model: ListModel { 16 | ListElement { 17 | name: "Add element..." 18 | } 19 | 20 | ListElement { 21 | name: "Window close button" 22 | value: "windowCloseButton" 23 | } 24 | 25 | ListElement { 26 | name: "Window minimize button" 27 | value: "windowMinimizeButton" 28 | } 29 | 30 | ListElement { 31 | name: "Window maximize button" 32 | value: "windowMaximizeButton" 33 | } 34 | 35 | ListElement { 36 | name: "Keep window above button" 37 | value: "windowKeepAboveButton" 38 | } 39 | 40 | ListElement { 41 | name: "Keep window below button" 42 | value: "windowKeepBelowButton" 43 | } 44 | 45 | ListElement { 46 | name: "Shade window button" 47 | value: "windowShadeButton" 48 | } 49 | 50 | ListElement { 51 | name: "Window title" 52 | value: "windowTitle" 53 | } 54 | 55 | ListElement { 56 | name: "Window Icon" 57 | value: "windowIcon" 58 | } 59 | 60 | ListElement { 61 | name: "Spacer" 62 | value: "spacer" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /package/contents/ui/config/ConfigAppearance.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2024 Anton Kharuzhy 3 | * 4 | * SPDX-License-Identifier: GPL-3.0-or-later 5 | */ 6 | 7 | import "../" 8 | import "../config" 9 | import QtQuick 10 | import QtQuick.Controls 11 | import QtQuick.Layouts 12 | import org.kde.kcmutils as KCM 13 | import org.kde.kirigami as Kirigami 14 | import org.kde.plasma.components as PlasmaComponents 15 | 16 | KCM.SimpleKCM { 17 | id: page 18 | 19 | property alias cfg_widgetButtonsIconsTheme: widgetButtonsIconsTheme.currentIndex 20 | property string cfg_widgetButtonsAuroraeTheme 21 | property alias cfg_widgetButtonsMargins: widgetButtonsMargins.value 22 | property alias cfg_widgetButtonsAspectRatio: widgetButtonsAspectRatio.value 23 | property alias cfg_widgetButtonsAnimation: widgetButtonsAnimation.value 24 | property alias cfg_widgetElementsDisabledMode: widgetElementsDisabledMode.currentIndex 25 | property alias cfg_widgetMargins: widgetMargins.value 26 | property alias cfg_widgetSpacing: widgetSpacing.value 27 | property int cfg_widgetHorizontalAlignment 28 | property int cfg_widgetVerticalAlignment 29 | property alias cfg_widgetFillWidth: widgetFillWidth.checked 30 | property alias cfg_widgetToolTipMode: widgetToolTipMode.currentIndex 31 | property alias cfg_windowTitleMinimumWidth: windowTitleMinimumWidth.value 32 | property alias cfg_windowTitleMaximumWidth: windowTitleMaximumWidth.value 33 | property alias cfg_windowTitleFontSize: windowTitleFontSize.value 34 | property alias cfg_windowTitleFontBold: windowTitleFontBold.checked 35 | property alias cfg_windowTitleFontSizeMode: windowTitleFontSizeMode.currentIndex 36 | property alias cfg_windowTitleSource: windowTitleSource.currentIndex 37 | property alias cfg_windowTitleHorizontalAlignment: windowTitleHorizontalAlignment.currentIndex 38 | property alias cfg_windowTitleVerticalAlignment: windowTitleVerticalAlignment.currentIndex 39 | property alias cfg_windowTitleHideEmpty: windowTitleHideEmpty.checked 40 | property alias cfg_windowTitleUndefined: windowTitleUndefined.text 41 | property alias cfg_windowTitleMarginsLeft: windowTitleMarginsLeft.value 42 | property alias cfg_windowTitleMarginsTop: windowTitleMarginsTop.value 43 | property alias cfg_windowTitleMarginsBottom: windowTitleMarginsBottom.value 44 | property alias cfg_windowTitleMarginsRight: windowTitleMarginsRight.value 45 | property alias cfg_widgetElements: widgetElements.elements 46 | property alias cfg_overrideElementsMaximized: overrideElementsMaximized.checked 47 | property alias cfg_widgetElementsMaximized: widgetElementsMaximized.elements 48 | property alias cfg_windowTitleSourceMaximized: windowTitleSourceMaximized.currentIndex 49 | 50 | Kirigami.FormLayout { 51 | anchors.left: parent.left 52 | anchors.right: parent.right 53 | wideMode: true 54 | 55 | KWinConfig { 56 | id: kWinConfig 57 | 58 | onAuroraeThemesChanged: widgetButtonsAuroraeTheme.updateCurrentIndex() 59 | } 60 | 61 | Kirigami.InlineMessage { 62 | anchors.left: parent.left 63 | anchors.right: parent.right 64 | text: kWinConfig.lastError 65 | type: Kirigami.MessageType.Error 66 | visible: kWinConfig.lastError !== "" 67 | } 68 | 69 | Kirigami.Separator { 70 | Kirigami.FormData.isSection: true 71 | Kirigami.FormData.label: i18n("Widget Layout") 72 | } 73 | 74 | SpinBox { 75 | id: widgetMargins 76 | 77 | Kirigami.FormData.label: i18n("Widget margins:") 78 | from: 0 79 | to: 32 80 | } 81 | 82 | SpinBox { 83 | id: widgetSpacing 84 | 85 | Kirigami.FormData.label: i18n("Spacing between elements:") 86 | from: 0 87 | to: 32 88 | } 89 | 90 | ComboBox { 91 | id: widgetHorizontalAlignment 92 | 93 | Component.onCompleted: currentIndex = indexOfValue(cfg_widgetHorizontalAlignment) 94 | onActivated: cfg_widgetHorizontalAlignment = currentValue 95 | textRole: "text" 96 | valueRole: "value" 97 | Kirigami.FormData.label: i18n("Horizontal alignment:") 98 | model: [ 99 | { 100 | "value": Qt.AlignLeft, 101 | "text": i18n("Left") 102 | }, 103 | { 104 | "value": Qt.AlignHCenter, 105 | "text": i18n("Center") 106 | }, 107 | { 108 | "value": Qt.AlignRight, 109 | "text": i18n("Right") 110 | }, 111 | { 112 | "value": Qt.AlignJustify, 113 | "text": i18n("Justify") 114 | } 115 | ] 116 | } 117 | 118 | ComboBox { 119 | id: widgetVerticalAlignment 120 | 121 | Component.onCompleted: currentIndex = indexOfValue(cfg_widgetVerticalAlignment) 122 | onActivated: cfg_widgetVerticalAlignment = currentValue 123 | textRole: "text" 124 | valueRole: "value" 125 | Kirigami.FormData.label: i18n("Vertical alignment:") 126 | model: [ 127 | { 128 | "value": Qt.AlignLeft, 129 | "text": i18n("Top") 130 | }, 131 | { 132 | "value": Qt.AlignVCenter, 133 | "text": i18n("Center") 134 | }, 135 | { 136 | "value": Qt.AlignBottom, 137 | "text": i18n("Bottom") 138 | }, 139 | { 140 | "value": Qt.AlignBaseline, 141 | "text": i18n("Baseline") 142 | } 143 | ] 144 | } 145 | 146 | ComboBox { 147 | id: widgetElementsDisabledMode 148 | 149 | textRole: "text" 150 | valueRole: "value" 151 | Kirigami.FormData.label: i18n("Show disabled elements:") 152 | model: [ 153 | { 154 | "value": WidgetElement.DisabledMode.Deactivated, 155 | "text": i18n("Deactivated") 156 | }, 157 | { 158 | "value": WidgetElement.DisabledMode.HideKeepSpace, 159 | "text": i18n("Hide, keep space") 160 | }, 161 | { 162 | "value": WidgetElement.DisabledMode.Hide, 163 | "text": i18n("Hide") 164 | } 165 | ] 166 | } 167 | 168 | CheckBox { 169 | id: widgetFillWidth 170 | 171 | Kirigami.FormData.label: i18n("Fill free space on Panel:") 172 | } 173 | 174 | ComboBox { 175 | id: widgetToolTipMode 176 | 177 | Kirigami.FormData.label: i18n("Tooltip Mode:") 178 | model: [i18n("Disabled"), i18n("Only for maximized windows"), i18n("Enabled")] 179 | } 180 | 181 | WidgetElements { 182 | id: widgetElements 183 | 184 | Kirigami.FormData.label: i18n("Elements:") 185 | } 186 | 187 | AddWidgetElement { 188 | onCurrentValueChanged: function () { 189 | if (currentValue) { 190 | widgetElements.model.append({ 191 | "value": currentValue 192 | }); 193 | currentIndex = 0; 194 | } 195 | } 196 | } 197 | 198 | Kirigami.Separator { 199 | Kirigami.FormData.isSection: true 200 | Kirigami.FormData.label: i18n("Window Control Buttons") 201 | } 202 | 203 | ComboBox { 204 | id: widgetButtonsIconsTheme 205 | 206 | Kirigami.FormData.label: i18n("Button icons source:") 207 | Layout.minimumWidth: Kirigami.Units.gridUnit * 15 208 | model: [i18n("Plasma: Global icon theme"), i18n("Breeze: Implicit Breeze icons"), i18n("Aurorae: Window decorations theme"), i18n("Oxygen: Implicit Oxygen icons")] 209 | } 210 | 211 | RowLayout { 212 | Kirigami.FormData.label: i18n("Aurorae theme:") 213 | 214 | ComboBox { 215 | id: widgetButtonsAuroraeTheme 216 | 217 | function updateCurrentIndex() { 218 | currentIndex = indexOfValue(cfg_widgetButtonsAuroraeTheme); 219 | } 220 | 221 | Layout.minimumWidth: Kirigami.Units.gridUnit * 15 222 | enabled: widgetButtonsIconsTheme.currentIndex == 2 && model.count > 0 223 | Component.onCompleted: updateCurrentIndex() 224 | onActivated: cfg_widgetButtonsAuroraeTheme = currentValue 225 | displayText: !!currentText ? currentText : (model.count > 0) ? i18n("