/* * SPDX-FileCopyrightText: 2024 Anton Kharuzhy * * SPDX-License-Identifier: GPL-3.0-or-later */ import QtQuick import QtQuick.Layouts import org.kde.kirigami as Kirigami import org.kde.plasma.components as PlasmaComponents import org.kde.plasma.core as PlasmaCore import org.kde.plasma.extras as PlasmaExtras import org.kde.plasma.plasmoid import org.kde.taskmanager as TaskManager import "utils.js" as Utils import "config/effect/" import "config/effect/effect.js" as EffectUtils PlasmoidItem { id: root property TaskManager.TasksModel tasksModel property real widgetHeight: (vertical ? width : height) property real elementHeight: widgetHeight - plasmoid.configuration.widgetMargins * 2 property real buttonMargins: plasmoid.configuration.widgetButtonsMargins property real buttonHeight: elementHeight property real buttonWidth: (plasmoid.configuration.widgetButtonsAspectRatio) / 100 * (buttonHeight - buttonMargins * 2) property var widgetAlignment: plasmoid.configuration.widgetHorizontalAlignment | plasmoid.configuration.widgetVerticalAlignment property KWinConfig kWinConfig property bool widgetHovered: widgetHoverHandler.hovered property bool vertical: plasmoid.formFactor === PlasmaCore.Types.Vertical property bool leftEdgeLocation: plasmoid.location === PlasmaCore.Types.LeftEdge property bool hideWidget: !tasksModel.hasActiveWindow && plasmoid.configuration.widgetElementsDisabledMode === WidgetElement.DisabledMode.Hide property bool editMode: Plasmoid.containment.corona?.editMode ?? false signal invokeKWinShortcut(string shortcut) signal widgetElementsLayoutUpdated Plasmoid.constraintHints: Plasmoid.CanFillArea Plasmoid.status: hideWidget ? PlasmaCore.Types.HiddenStatus : PlasmaCore.Types.ActiveStatus Layout.fillWidth: !vertical && plasmoid.configuration.widgetFillWidth Layout.fillHeight: vertical && plasmoid.configuration.widgetFillWidth preferredRepresentation: fullRepresentation onInvokeKWinShortcut: function (shortcut) { if (tasksModel.hasActiveWindow) tasksModel.activeWindow.actionCall(ActiveWindow.Action.Activate); kWinConfig.invokeKWinShortcut(shortcut); } ContextualActions {} Component { id: widgetElementLoaderDelegate Loader { id: widgetElementLoader required property int index required property var modelData property bool repeaterVisible: false onLoaded: function () { Utils.copyLayoutConstraint(item, widgetElementLoader); widgetElementLoader.Layout.preferredWidthChanged.connect(root.widgetElementsLayoutUpdated); item.modelData = modelData; } sourceComponent: { switch (modelData.type) { case WidgetElement.Type.WindowControlButton: return windowControlButton; case WidgetElement.Type.WindowTitle: return windowTitle; case WidgetElement.Type.WindowIcon: return windowIcon; case WidgetElement.Type.Spacer: return spacerIcon; } } Binding { when: status === Loader.Ready widgetElementLoader.visible: repeaterVisible && (plasmoid.configuration.widgetElementsDisabledMode === WidgetElement.DisabledMode.Hide ? item.enabled : true) } Binding { function itemVisible(itemEnabled) { switch (plasmoid.configuration.widgetElementsDisabledMode) { case WidgetElement.DisabledMode.Hide: return itemEnabled; case WidgetElement.DisabledMode.HideKeepSpace: return itemEnabled; default: return true; } } when: status === Loader.Ready target: item property: "visible" value: itemVisible(item.enabled) } } } Component { id: windowControlButton WindowControlButton { id: windowControlButton property var modelData Layout.alignment: root.widgetAlignment Layout.preferredWidth: root.buttonWidth Layout.preferredHeight: root.buttonHeight verticalPadding: root.buttonMargins buttonType: modelData.windowControlButtonType themeName: plasmoid.configuration.widgetButtonsAuroraeTheme iconTheme: plasmoid.configuration.widgetButtonsIconsTheme animationDuration: plasmoid.configuration.widgetButtonsAnimation onActionCall: action => { return tasksModel.activeWindow.actionCall(action); } enabled: tasksModel.hasActiveWindow && tasksModel.activeWindow.actionSupported(action) && (!plasmoid.configuration.disableButtonsForNotHoveredWidget || root.widgetHovered) checked: tasksModel.hasActiveWindow && tasksModel.activeWindow.buttonChecked(modelData.windowControlButtonType) active: tasksModel.hasActiveWindow && tasksModel.activeWindow.active } } Component { id: windowIcon Kirigami.Icon { property var modelData height: root.elementHeight width: height Layout.alignment: root.widgetAlignment Layout.preferredWidth: width source: tasksModel.activeWindow.icon || "window" enabled: tasksModel.hasActiveWindow && !!tasksModel.activeWindow.icon } } Component { id: spacerIcon Rectangle { property var modelData height: root.elementHeight width: height / 3 Layout.alignment: root.widgetAlignment Layout.preferredWidth: width color: "transparent" enabled: tasksModel.hasActiveWindow } } Component { id: windowTitle PlasmaComponents.Label { id: windowTitleLabel readonly property var horizontalAlignmentValues: [Text.AlignLeft, Text.AlignRight, Text.AlignHCenter, Text.AlignJustify] readonly property var verticalAlignmentValues: [Text.AlignTop, Text.AlignBottom, Text.AlignVCenter] property var modelData property bool empty: text === undefined || text === "" property bool hideEmpty: empty && plasmoid.configuration.windowTitleHideEmpty property int windowTitleSource: plasmoid.configuration.overrideElementsMaximized && tasksModel.activeWindow.maximized ? plasmoid.configuration.windowTitleSourceMaximized : plasmoid.configuration.windowTitleSource property var titleTextReplacements: [] Layout.minimumWidth: plasmoid.configuration.windowTitleMinimumWidth Layout.maximumWidth: !hideEmpty ? plasmoid.configuration.windowTitleMaximumWidth : 0 Layout.alignment: root.widgetAlignment Layout.fillWidth: plasmoid.configuration.widgetFillWidth Layout.fillHeight: true Layout.preferredWidth: textMetrics.advanceWidth + leftPadding + rightPadding + 1 // Magic number text: titleText(windowTitleSource) || plasmoid.configuration.windowTitleUndefined font.pointSize: plasmoid.configuration.windowTitleFontSize font.bold: plasmoid.configuration.windowTitleFontBold fontSizeMode: plasmoid.configuration.windowTitleFontSizeMode maximumLineCount: 1 elide: Text.ElideRight wrapMode: Text.WrapAnywhere enabled: tasksModel.hasActiveWindow horizontalAlignment: horizontalAlignmentValues[plasmoid.configuration.windowTitleHorizontalAlignment] verticalAlignment: verticalAlignmentValues[plasmoid.configuration.windowTitleVerticalAlignment] bottomPadding: !hideEmpty ? plasmoid.configuration.windowTitleMarginsBottom : 0 leftPadding: !hideEmpty ? plasmoid.configuration.windowTitleMarginsLeft : 0 rightPadding: !hideEmpty ? plasmoid.configuration.windowTitleMarginsRight : 0 topPadding: !hideEmpty ? plasmoid.configuration.windowTitleMarginsTop : 0 Accessible.role: Accessible.TitleBar Accessible.name: text TextMetrics { id: textMetrics font: windowTitleLabel.font text: windowTitleLabel.text } Connections { target: plasmoid.configuration function onTitleReplacementsTypesChanged() { updateTitleTextReplacements(); } function onTitleReplacementsPatternsChanged() { updateTitleTextReplacements(); } function onTitleReplacementsTemplatesChanged() { updateTitleTextReplacements(); } } Component.onCompleted: updateTitleTextReplacements() function titleText(windowTitleSource) { let titleTextResult = ""; switch (windowTitleSource) { case 0: titleTextResult = tasksModel.activeWindow.appName; break; case 1: titleTextResult = tasksModel.activeWindow.decoration; break; case 2: titleTextResult = tasksModel.activeWindow.genericAppName; break; case 3: titleTextResult = plasmoid.configuration.windowTitleUndefined; break; } if (titleTextResult) { titleTextResult = Utils.Replacement.applyReplacementList(titleTextResult, titleTextReplacements); } return titleTextResult; } function updateTitleTextReplacements() { Qt.callLater(_updateTitleTextReplacements); } function _updateTitleTextReplacements() { titleTextReplacements = Utils.Replacement.createReplacementList(plasmoid.configuration.titleReplacementsTypes, plasmoid.configuration.titleReplacementsPatterns, plasmoid.configuration.titleReplacementsTemplates); } } } kWinConfig: KWinConfig { Component.onCompleted: updateKWinShortcutNames() } tasksModel: ActiveTasksModel { id: tasksModel } HoverHandler { id: widgetHoverHandler enabled: plasmoid.configuration.disableButtonsForNotHoveredWidget } fullRepresentation: Item { id: representationProxy Layout.fillWidth: root.vertical ? null : plasmoid.configuration.widgetFillWidth Layout.fillHeight: root.vertical ? plasmoid.configuration.widgetFillWidth : null Layout.minimumWidth: root.vertical ? widgetRow.Layout.minimumHeight : widgetRow.Layout.minimumWidth Layout.minimumHeight: root.vertical ? widgetRow.Layout.minimumWidth : widgetRow.Layout.minimumHeight Layout.maximumWidth: root.vertical ? widgetRow.Layout.maximumHeight : widgetRow.Layout.maximumWidth Layout.maximumHeight: root.vertical ? widgetRow.Layout.maximumWidth : widgetRow.Layout.maximumHeight Layout.preferredWidth: root.vertical ? widgetRow.Layout.preferredHeight : widgetRow.Layout.preferredWidth Layout.preferredHeight: root.vertical ? widgetRow.Layout.preferredWidth : widgetRow.Layout.preferredHeight MouseHandlers { Component.onCompleted: { invokeKWinShortcut.connect(root.invokeKWinShortcut); } } WidgetToolTip { anchors.fill: parent tasksModel: root.tasksModel } RowLayout { id: widgetRow spacing: plasmoid.configuration.widgetSpacing anchors.left: parent.left anchors.verticalCenter: root.vertical ? undefined : parent.verticalCenter anchors.horizontalCenter: root.vertical ? parent.horizontalCenter : undefined width: root.vertical ? representationProxy.height : representationProxy.width height: root.vertical ? representationProxy.width : representationProxy.height Accessible.role: Accessible.Grouping Accessible.name: i18n("Application title bar") transform: [ Rotation { angle: root.vertical ? 90 : 0 origin.x: widgetHeight / 2 - plasmoid.configuration.widgetMargins / 2 // IDK why origin.y: widgetHeight / 2 - plasmoid.configuration.widgetMargins / 2 }, Rotation { angle: root.leftEdgeLocation ? 180 : 0 origin.x: width / 2 origin.y: height / 2 } ] PlasmaComponents.Label { id: editModePlaceholder text: Plasmoid.metaData.name visible: editMode } Repeater { id: widgetElementsRepeater property var elements: plasmoid.configuration.widgetElements onElementsChanged: function () { let array = []; for (var i = 0; i < elements.length; i++) { array.push(Utils.widgetElementModelFromName(elements[i])); } model = array; } model: [] delegate: widgetElementLoaderDelegate visible: !plasmoid.configuration.overrideElementsMaximized || !tasksModel.activeWindow.maximized onItemAdded: function (index, item) { item.repeaterVisible = Qt.binding(function () { return visible; }); } } Repeater { id: widgetElementsMaximizedRepeater property var elements: plasmoid.configuration.overrideElementsMaximized ? plasmoid.configuration.widgetElementsMaximized : [] onElementsChanged: function () { let array = []; for (var i = 0; i < elements.length; i++) { array.push(Utils.widgetElementModelFromName(elements[i])); } model = array; } model: [] delegate: widgetElementLoaderDelegate visible: !widgetElementsRepeater.visible onItemAdded: function (index, item) { item.repeaterVisible = Qt.binding(function () { return visible; }); } } Connections { target: root function onWidgetElementsLayoutUpdated() { var preferredWidth = plasmoid.configuration.widgetFillWidth ? widgetRow.calculatePreferredWidth() : -1; widgetRow.Layout.preferredWidth = preferredWidth; } } function calculatePreferredWidth() { var repeater = widgetElementsRepeater.visible ? widgetElementsRepeater : widgetElementsMaximizedRepeater; var preferredWidth = (repeater.count - 1) * widgetRow.spacing; for (var i = 0; i < repeater.count; i++) { var item = repeater.itemAt(i); preferredWidth += Utils.calculateItemPreferredWidth(item); } if (preferredWidth < widgetRow.Layout.minimumWidth) { return widgetRow.Layout.minimumWidth; } else if (preferredWidth > widgetRow.Layout.maximumWidth) { return widgetRow.Layout.maximumWidth; } else { return preferredWidth; } } } WidgetEffectsRepeater { id: effectsRepeater } Component.onCompleted: effectsRepeater.updateEffectRules() Connections { target: root.tasksModel function onActiveWindowUpdated() { effectsRepeater.updateEffectsState(); } } Connections { target: plasmoid.configuration function onEffectRulesChanged() { effectsRepeater.updateEffectRules(); } function onEffectsChanged() { effectsRepeater.updateEffectRules(); } } } }