some more branding

This commit is contained in:
tumillanino
2026-03-26 14:36:06 +11:00
parent 204c1638b9
commit e7d6daf595
317 changed files with 23146 additions and 1 deletions

View File

@@ -0,0 +1,35 @@
/*
SPDX-FileCopyrightText: 2014 Aaron Seigo <aseigo@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.plasma.private.shell
Item {
id: main
property string scriptPath
property alias mode: interactiveConsole.mode
signal visibleChanged(bool visible)
onScriptPathChanged: {
interactiveConsole.loadScript(scriptPath);
}
InteractiveConsoleWindow {
id: interactiveConsole
onVisibleChanged: {
main.visibleChanged(visible);
}
}
Component.onCompleted: {
interactiveConsole.scriptEngine = scriptEngine;
interactiveConsole.visible = true;
}
}

View File

@@ -0,0 +1,376 @@
/*
SPDX-FileCopyrightText: 2014-2020 Ivan Cukic <ivan.cukic(at)kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick 2.0
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.plasma.extras as PlasmaExtras
import org.kde.plasma.activityswitcher as ActivitySwitcher
import org.kde.kirigami 2.20 as Kirigami
import org.kde.kcmutils // KCMLauncher
import "static.js" as S
Item {
id: root
property int innerPadding : Kirigami.Units.gridUnit
property bool current : false
property bool selected : false
property alias title : title.text
property alias icon : icon.source
property alias hasWindows : hasWindowsIndicator.visible
z : current ? 10 :
selected ? 5 : 0
property string activityId : ""
property string background : ""
onBackgroundChanged: if (background[0] !== '#') {
// We have a proper wallpaper, hurroo!
backgroundColor.visible = false;
} else {
// We have only a color
backgroundColor.color = background;
backgroundColor.visible = true;
}
signal clicked
width : 200
height : width * 9.0 / 16.0
Item {
anchors {
fill: parent
}
// Background until we get something real
Rectangle {
id: backgroundColor
anchors.fill: parent
// This is intentional - while waiting for the wallpaper,
// we are showing a semi-transparent black background
color: "black"
opacity: root.selected ? .8 : .5
}
Image {
id: backgroundWallpaper
anchors.fill: parent
visible: !backgroundColor.visible
source: "image://wallpaperthumbnail/" + root.background
sourceSize: Qt.size(width, height)
}
// Title and the icon
Rectangle {
id: shade
width: parent.height
height: parent.width
anchors.centerIn: parent
rotation: 90
gradient: Gradient {
GradientStop { position: 1.0; color: "black" }
GradientStop { position: 0.0; color: "transparent" }
}
opacity : root.selected ? 0.5 : 1.0
}
Rectangle {
id: currentActivityHighlight
visible: root.current
border.width: root.current ? Kirigami.Units.smallSpacing : 0
border.color: Kirigami.Theme.highlightColor
z: 10
anchors {
fill: parent
// Hide the rounding error on the bottom of the rectangle
bottomMargin: -1
}
color: "transparent"
}
Item {
id: titleBar
anchors {
top : parent.top
left : parent.left
right : parent.right
leftMargin : 2 * Kirigami.Units.smallSpacing + 2
topMargin : 2 * Kirigami.Units.smallSpacing
}
PlasmaExtras.ShadowedLabel {
id: title
color : "white"
elide : Text.ElideRight
visible : shade.visible
font.bold : true
anchors {
top : parent.top
left : parent.left
right : icon.left
}
}
PlasmaExtras.ShadowedLabel {
id: description
color : "white"
elide : Text.ElideRight
text : model.description
anchors {
top : title.bottom
left : parent.left
right : icon.left
}
}
Kirigami.Icon {
id: icon
width : Kirigami.Units.iconSizes.medium
height : width
anchors {
right : parent.right
rightMargin : 2 * Kirigami.Units.smallSpacing
}
}
}
Column {
id: statsBar
height: childrenRect.height + Kirigami.Units.smallSpacing
anchors {
bottom : controlBar.top
left : parent.left
right : parent.right
leftMargin : 2 * Kirigami.Units.smallSpacing + 2
rightMargin : 2 * Kirigami.Units.smallSpacing
bottomMargin : Kirigami.Units.smallSpacing
}
Kirigami.Icon {
id : hasWindowsIndicator
source : "window-duplicate"
width : 16
height : width
opacity : .6
visible : false
}
PlasmaExtras.ShadowedLabel {
id: lastUsedDate
color : "white"
elide : Text.ElideRight
text: root.current ?
i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:status currently active activity", "Currently being used") :
model.lastTimeUsedString
}
}
Rectangle {
id: dropHighlight
visible: moveDropAction.isHovered || copyDropAction.isHovered
onVisibleChanged: {
ActivitySwitcher.Backend.setDropMode(visible);
if (visible) {
root.state = "dropAreasShown";
} else {
root.state = "plain";
}
}
anchors {
fill: parent
topMargin: icon.height + 3 * Kirigami.Units.smallSpacing
}
opacity: .75
color: Kirigami.Theme.backgroundColor
}
TaskDropArea {
id: moveDropAction
anchors {
right: parent.horizontalCenter
left: parent.left
top: parent.top
bottom: parent.bottom
}
topPadding: icon.height + 3 * Kirigami.Units.smallSpacing
actionVisible: dropHighlight.visible
actionTitle: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action shows when dragging file over item", "Move to\nthis activity")
onTaskDropped: mimeData => {
ActivitySwitcher.Backend.dropMove(mimeData, root.activityId);
}
onClicked: {
root.clicked();
}
onEntered: {
S.showActivityItemActionsBar(root);
}
visible: ActivitySwitcher.Backend.dropEnabled
}
TaskDropArea {
id: copyDropAction
topPadding: icon.height + 3 * Kirigami.Units.smallSpacing
actionVisible: dropHighlight.visible
anchors {
right: parent.right
left: parent.horizontalCenter
top: parent.top
bottom: parent.bottom
}
actionTitle: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action shows when dragging file over item", "Show also\nin this activity")
onTaskDropped: mimeData => {
ActivitySwitcher.Backend.dropCopy(mimeData, root.activityId);
}
onClicked: {
root.clicked();
}
onEntered: {
S.showActivityItemActionsBar(root);
}
visible: ActivitySwitcher.Backend.dropEnabled
}
// Controls
Item {
id: controlBar
height: root.state == "showingControls" ?
(configButton.height + 4 * Kirigami.Units.smallSpacing) :
0
Behavior on height {
NumberAnimation {
duration: Kirigami.Units.longDuration
}
}
Behavior on opacity {
NumberAnimation {
duration: Kirigami.Units.shortDuration
}
}
clip: true
anchors {
bottom : parent.bottom
left : parent.left
right : parent.right
}
Rectangle {
anchors {
fill: parent
margins: - 2 * Kirigami.Units.smallSpacing
}
opacity: .75
color: Kirigami.Theme.backgroundColor
}
PlasmaComponents.Button {
id: configButton
icon.name: "configure"
PlasmaComponents.ToolTip.delay: Kirigami.Units.toolTipDelay
PlasmaComponents.ToolTip.visible: hovered
PlasmaComponents.ToolTip.text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button tooltip only, opens kcm", "Configure")
onClicked: KCMLauncher.openSystemSettings("kcm_activities", root.activityId);
anchors {
left : parent.left
top : parent.top
leftMargin : 2 * Kirigami.Units.smallSpacing + 2
topMargin : 2 * Kirigami.Units.smallSpacing
}
}
}
}
states: [
State {
name: "plain"
PropertyChanges { shade.visible: true }
PropertyChanges { controlBar.opacity: 0 }
},
State {
name: "showingControls"
PropertyChanges { shade.visible: true }
PropertyChanges { controlBar.opacity: 1 }
},
State {
name: "dropAreasShown"
// PropertyChanges { shade.visible: false }
PropertyChanges { statsBar.visible: false }
PropertyChanges { controlBar.opacity: 0 }
}
]
transitions: [
Transition {
NumberAnimation {
properties : "opacity"
duration : Kirigami.Units.shortDuration
}
}
]
}

View File

@@ -0,0 +1,150 @@
/* vim:set foldmethod=marker:
SPDX-FileCopyrightText: 2014 Ivan Cukic <ivan.cukic(at)kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.plasma.activityswitcher as ActivitySwitcher
Flickable {
id: root
// contentWidth: content.width
contentHeight: content.height
property var model: ActivitySwitcher.Backend.runningActivitiesModel()
property string filterString: ""
property bool showSwitcherOnly: false
property int itemsWidth: 0
property int selectedIndex: -1
function _selectRelativeToCurrent(distance)
{
var startingWithSelected = selectedIndex;
var visited = 0;
do {
selectedIndex += distance;
visited++;
if (selectedIndex >= activitiesList.count) {
selectedIndex = 0;
}
if (selectedIndex < 0) {
selectedIndex = activitiesList.count - 1;
}
if (visited >= activitiesList.count) {
// we've visited all activities but could not find a visible
// one, so stop searching and select the one we started with
selectedIndex = startingWithSelected;
break;
}
// Searching for the first item that is visible
} while (!activitiesList.itemAt(selectedIndex).visible);
_updateSelectedItem();
}
function selectNext()
{
_selectRelativeToCurrent(1);
}
function selectPrevious()
{
_selectRelativeToCurrent(-1);
}
function _updateSelectedItem()
{
for (var i = 0; i < activitiesList.count; i++) {
activitiesList.itemAt(i).selected = (i === selectedIndex);
}
}
function openSelected()
{
var selectedItem = null;
if (selectedIndex >= 0 && selectedIndex < activitiesList.count) {
selectedItem = activitiesList.itemAt(selectedIndex) as ActivityItem;
} else if (root.filterString != "") {
// If we have only one item shown, activate it. It doesn't matter
// that it is not really selected
for (var i = 0; i < activitiesList.count; i++) {
var item = activitiesList.itemAt(i) as ActivityItem;
if (item.visible) {
selectedItem = item;
break;
}
}
}
if (selectedItem !== null) {
ActivitySwitcher.Backend.setCurrentActivity(selectedItem.activityId);
}
}
Column {
id: content
// width: root.width - (root.width % 10)
width: root.itemsWidth
spacing: Kirigami.Units.smallSpacing * 2
Repeater {
id: activitiesList
model: ActivitySwitcher.Backend.runningActivitiesModel()
ActivityItem {
width: content.width
visible : (root.filterString == "") ||
(title.toLowerCase().indexOf(root.filterString) != -1)
activityId : model.id
title : model.name
icon : model.iconSource
background : model.background
current : model.isCurrent
hasWindows : model.hasWindows
innerPadding : 2 * Kirigami.Units.smallSpacing
onClicked : {
ActivitySwitcher.Backend.setCurrentActivity(model.id);
}
}
}
add: Transition {
NumberAnimation {
properties: "x"
from: -100
duration: Kirigami.Units.shortDuration
}
}
move: Transition {
NumberAnimation {
id: animation
properties: "y"
duration: Kirigami.Units.longDuration
}
}
}
}

View File

@@ -0,0 +1,135 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2014 Ivan Cukic <ivan.cukic@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick 2.0
import org.kde.plasma.components 3.0 as PlasmaComponents
import org.kde.kirigami 2.20 as Kirigami
import org.kde.config as KConfig // KAuthorized
import org.kde.kcmutils // KCMLauncher
FocusScope {
id: root
signal closed()
//this is used to perfectly align the filter field and delegates
property int cellWidth: Kirigami.Units.iconSizes.sizeForLabels * 30
property int spacing: 2 * Kirigami.Units.smallSpacing
property bool showSwitcherOnly: false
width: Kirigami.Units.gridUnit * 16
Item {
id: activityBrowser
property int spacing: 2 * Kirigami.Units.smallSpacing
signal closeRequested()
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
if (heading.searchString.length > 0) {
heading.searchString = "";
} else {
activityBrowser.closeRequested();
}
} else if (event.key === Qt.Key_Up) {
activityList.selectPrevious();
} else if (event.key === Qt.Key_Down) {
activityList.selectNext();
} else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
activityList.openSelected();
} else if (event.key === Qt.Key_Tab) {
// console.log("TAB KEY");
} else {
// console.log("OTHER KEY");
// event.accepted = false;
// heading.forceActiveFocus();
}
}
// Rectangle {
// anchors.fill : parent
// opacity : .4
// color : "white"
// }
Heading {
id: heading
anchors {
top: parent.top
left: parent.left
right: parent.right
leftMargin: Kirigami.Units.smallSpacing
rightMargin: Kirigami.Units.smallSpacing
}
visible: !root.showSwitcherOnly
onCloseRequested: activityBrowser.closeRequested()
}
PlasmaComponents.ScrollView {
anchors {
top: heading.visible ? heading.bottom : parent.top
bottom: bottomPanel.visible ? bottomPanel.top : parent.bottom
left: parent.left
right: parent.right
topMargin: activityBrowser.spacing
}
ActivityList {
id: activityList
showSwitcherOnly: root.showSwitcherOnly
filterString: heading.searchString.toLowerCase()
itemsWidth: root.width - Kirigami.Units.smallSpacing
}
}
Item {
id: bottomPanel
height: newActivityButton.height + Kirigami.Units.gridUnit
visible: !root.showSwitcherOnly
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
PlasmaComponents.ToolButton {
id: newActivityButton
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button opens Activity kcm", "Create activity…")
icon.name: "list-add"
width: parent.width
onClicked: KCMLauncher.openSystemSettings("kcm_activities", "newActivity")
visible: KConfig.KAuthorized.authorize("plasma-desktop/add_activities")
}
}
onCloseRequested: root.closed()
anchors.fill: parent
}
}

View File

@@ -0,0 +1,108 @@
/* vim:set foldmethod=marker:
SPDX-FileCopyrightText: 2014 Ivan Cukic <ivan.cukic(at)kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.plasma.components as PlasmaComponents
import org.kde.plasma.extras as PlasmaExtras
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCM
import org.kde.config as KConfig
Item {
id: root
property alias searchString: searchText.text
property bool showingSearch: false
signal closeRequested
function focusSearch() {
searchText.forceActiveFocus()
}
onShowingSearchChanged: if (!showingSearch) searchText.text = ""
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
if (root.showingSearch) {
event.accepted = true;
root.showingSearch = false;
}
}
}
height: childrenRect.height
RowLayout {
id: buttonRow
anchors {
top: parent.top
left: parent.left
right: parent.right
}
Item {
Kirigami.Heading {
id: heading
anchors.fill: parent
level: 1
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:window", "Activities")
textFormat: Text.PlainText
elide: Text.ElideRight
visible: !root.showingSearch
}
PlasmaExtras.SearchField {
id: searchText
anchors.fill: parent
focus: true
visible: root.showingSearch
onTextChanged: if (text != "") root.showingSearch = true
}
Layout.fillWidth: true
Layout.fillHeight: true
}
PlasmaComponents.ToolButton {
id: searchButton
icon.name: "edit-find"
// checkable: true
// onClicked: root.closeRequested()
onClicked: root.showingSearch = !root.showingSearch
checked: root.showingSearch
}
PlasmaComponents.ToolButton {
id: configureButton
icon.name: "configure"
visible: KConfig.KAuthorized.authorizeControlModule("kcm_activities")
onClicked: {
KCM.KCMLauncher.openSystemSettings("kcm_activities");
root.closeRequested();
}
}
PlasmaComponents.ToolButton {
id: closeButton
icon.name: "window-close"
onClicked: root.closeRequested()
}
}
}

View File

@@ -0,0 +1,88 @@
/*
SPDX-FileCopyrightText: 2020 Ivan Cukic <ivan.cukic(at)kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.draganddrop as DND
import org.kde.plasma.extras as PlasmaExtras
DND.DropArea {
id: root
signal taskDropped(var mimeData, var modifiers)
signal clicked()
signal entered()
property int topPadding: 0
property string activityName: ""
property bool selected: false
property string actionTitle: ""
property bool isHovered: false
property bool actionVisible: false
PlasmaExtras.Highlight {
id: dropHighlight
anchors {
fill: parent
// topMargin: icon.height + 3 * Kirigami.Units.smallSpacing
topMargin: root.topPadding
}
visible: root.isHovered
z: -1
}
Text {
id: dropAreaLeftText
anchors {
fill: dropHighlight
leftMargin: Kirigami.Units.gridUnit
rightMargin: Kirigami.Units.gridUnit
}
color: Kirigami.Theme.textColor
visible: root.actionVisible
text: root.actionTitle
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
maximumLineCount: 3
}
anchors {
left: parent.left
right: parent.horizontalCenter
top: parent.top
bottom: parent.bottom
}
preventStealing: true
enabled: true
onDrop: event => {
root.taskDropped(event.mimeData, event.modifiers);
}
onDragEnter: {
root.isHovered = true;
}
onDragLeave: {
root.isHovered = false;
}
MouseArea {
anchors.fill: parent
onClicked: root.clicked()
hoverEnabled: true
onEntered: root.entered()
Accessible.name: root.activityName
Accessible.role: Accessible.Button
Accessible.selected: root.selected
Accessible.onPressAction: root.clicked()
}
}

View File

@@ -0,0 +1,54 @@
/*
SPDX-FileCopyrightText: 2014 Ivan Cukic <ivan.cukic(at)kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
Image {
id: main
width: 480
height: 425
source: "images/window2.png"
anchors.centerIn: parent
Item {
id: title
width: titleText.width + 32
height: titleText.height + 32
Rectangle {
anchors.fill: parent
color: "black"
radius: Kirigami.Units.cornerRadius
opacity: .7
}
Text {
id: titleText
color: "white"
text: "Firefox"
font.pointSize: 24
anchors.centerIn: parent
}
}
Drag.active: mouseArea.drag.active
Drag.hotSpot.x: 32
Drag.hotSpot.y: 32
MouseArea {
id: mouseArea
anchors.fill: parent
drag {
target: title
}
}
}

View File

@@ -0,0 +1,23 @@
/* vim:set foldmethod=marker:
SPDX-FileCopyrightText: 2015 Ivan Cukic <ivan.cukic(at)kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
.pragma library
var currentlyHoveredActivityItem = null;
function showActivityItemActionsBar(activityItem) {
if (activityItem === currentlyHoveredActivityItem) {
return;
}
if (currentlyHoveredActivityItem !== null) {
currentlyHoveredActivityItem.state = "plain";
}
currentlyHoveredActivityItem = activityItem;
currentlyHoveredActivityItem.state = "showingControls";
}

View File

@@ -0,0 +1,262 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Window
import QtQuick.Controls as QQC2
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.components as PC3
import org.kde.kirigami as Kirigami
import org.kde.plasma.plasmoid
PlasmoidItem {
id: root
enum LayoutType {
HorizontalPanel,
VerticalPanel,
Desktop,
DesktopCompact
}
property var errorInformation
readonly property real minimumPreferredWidth: Kirigami.Units.gridUnit * 12
readonly property real minimumPreferredHeight: Kirigami.Units.gridUnit * 12
// To properly show the error message in panel
readonly property int layoutForm: {
if (fullRepresentationItem.width >= root.minimumPreferredWidth) {
if (fullRepresentationItem.height >= root.minimumPreferredHeight) {
return AppletError.Desktop;
} else if (fullRepresentationItem.height >= Kirigami.Units.iconSizes.huge + root.fullRepresentationItem.buttonLayout.implicitHeight) {
return AppletError.DesktopCompact;
}
}
return Plasmoid.formFactor === PlasmaCore.Types.Vertical ? AppletError.VerticalPanel : AppletError.HorizontalPanel;
}
preloadFullRepresentation: true
fullRepresentation: GridLayout {
id: fullRep
property alias buttonLayout: buttonLayout
Layout.minimumWidth: {
switch (root.layoutForm) {
case AppletError.Desktop:
case AppletError.DesktopCompact:
// [Icon] [Text]
// [Button]
// [Information]
return Math.max(root.minimumPreferredWidth, buttonLayout.implicitWidth);
case AppletError.VerticalPanel:
// [Icon]
// [Copy]
// [Open]
return Math.max(headerIcon.implicitWidth, buttonLayout.implicitWidth);
case AppletError.HorizontalPanel:
// [Icon] [Copy] [Open]
return headingLayout.implicitWidth + rowSpacing + buttonLayout.implicitWidth;
}
}
Layout.minimumHeight: {
switch (root.layoutForm) {
case AppletError.Desktop:
return headingLayout.implicitHeight + fullRep.columnSpacing + buttonLayout.implicitHeight + fullRep.columnSpacing + fullContentView.implicitHeight;
case AppletError.DesktopCompact:
return Math.max(headingLayout.implicitHeight, buttonLayout.implicitHeight);
case AppletError.VerticalPanel:
return headingLayout.implicitHeight + fullRep.columnSpacing + buttonLayout.implicitHeight;
case AppletError.HorizontalPanel:
return Math.max(headingLayout.implicitHeight, buttonLayout.implicitHeight);
}
}
// Same as systray popups
Layout.preferredWidth: Kirigami.Units.gridUnit * 24
Layout.preferredHeight: Kirigami.Units.gridUnit * 24
Layout.maximumWidth: Kirigami.Units.gridUnit * 34
Layout.maximumHeight: Kirigami.Units.gridUnit * 34
rowSpacing: textArea.topPadding
columnSpacing: rowSpacing
flow: {
switch (root.layoutForm) {
case AppletError.HorizontalPanel:
return GridLayout.LeftToRight;
default:
return GridLayout.TopToBottom;
}
}
RowLayout {
id: headingLayout
Layout.margins: root.layoutForm !== AppletError.Desktop ? 0 : Kirigami.Units.gridUnit
Layout.maximumWidth: fullRep.width
spacing: 0
Layout.fillWidth: true
Kirigami.Icon {
id: headerIcon
implicitWidth: Math.min(Kirigami.Units.iconSizes.huge, fullRep.width, fullRep.height)
implicitHeight: implicitWidth
activeFocusOnTab: true
source: "dialog-error"
Accessible.description: heading.text
PlasmaCore.ToolTipArea {
anchors.fill: parent
enabled: !heading.visible || heading.truncated
mainText: heading.text
textFormat: Text.PlainText
}
}
Kirigami.Heading {
id: heading
visible: root.layoutForm !== AppletError.VerticalPanel
// Descent is equal to the amount of space above and below capital letters.
// Add descent to the sides to make the spacing around Latin text look more even.
leftPadding: headingFontMetrics.descent
rightPadding: headingFontMetrics.descent
text: root.errorInformation ? root.errorInformation.compactError : "No error information."
textFormat: Text.PlainText
level: 2
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
Layout.fillWidth: true
Layout.maximumHeight: headerIcon.implicitHeight
FontMetrics {
id: headingFontMetrics
font: heading.font
}
}
}
GridLayout {
id: buttonLayout
Layout.alignment: Qt.AlignCenter
rowSpacing: fullRep.rowSpacing
columnSpacing: parent.columnSpacing
flow: {
switch (root.layoutForm) {
case AppletError.HorizontalPanel:
case AppletError.VerticalPanel:
return fullRep.flow;
default:
return GridLayout.LeftToRight;
}
}
PC3.Button {
id: copyButton
display: root.layoutForm === AppletError.HorizontalPanel || root.layoutForm === AppletError.VerticalPanel ? PC3.AbstractButton.IconOnly : PC3.AbstractButton.TextBesideIcon
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Copy to Clipboard")
icon.name: "edit-copy"
onClicked: {
textArea.selectAll()
textArea.copy()
textArea.deselect()
}
PlasmaCore.ToolTipArea {
anchors.fill: parent
enabled: parent.display === PC3.AbstractButton.IconOnly
mainText: parent.text
textFormat: Text.PlainText
}
}
Loader {
id: compactContentLoader
active: root.layoutForm !== AppletError.Desktop
visible: active
sourceComponent: PC3.Button {
display: copyButton.display
icon.name: "window-new"
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button opens dialog", "View Error Details…")
checked: dialog.visible
onClicked: dialog.visible = !dialog.visible
PlasmaCore.ToolTipArea {
anchors.fill: parent
enabled: parent.display === PC3.AbstractButton.IconOnly
mainText: parent.text
textFormat: Text.PlainText
}
QQC2.ApplicationWindow {
id: dialog
flags: Qt.Dialog | Qt.WindowStaysOnTopHint | Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint
minimumWidth: dialogFontMetrics.height * 12
+ dialogTextArea.leftPadding + dialogTextArea.rightPadding
minimumHeight: dialogFontMetrics.height * 12
+ dialogTextArea.topPadding + dialogTextArea.bottomPadding
width: Kirigami.Units.gridUnit * 24
height: Kirigami.Units.gridUnit * 24
color: palette.base
QQC2.ScrollView {
id: dialogScrollView
anchors.fill: parent
QQC2.TextArea {
id: dialogTextArea
// HACK: silence binding loop warnings.
// contentWidth seems to be causing the binding loop,
// but contentWidth is read-only and we have no control
// over how it is calculated.
implicitWidth: 0
wrapMode: TextEdit.Wrap
text: textArea.text
font.family: "monospace"
readOnly: true
selectByMouse: true
background: null
FontMetrics {
id: dialogFontMetrics
font: dialogTextArea.font
}
}
background: null
}
}
}
}
}
PC3.ScrollView {
id: fullContentView
// Not handled by a Loader because we need
// TextEdit::copy() to copy to clipboard.
visible: !compactContentLoader.active
Layout.fillHeight: true
Layout.fillWidth: true
PC3.TextArea {
id: textArea
// HACK: silence binding loop warnings.
// contentWidth seems to be causing the binding loop,
// but contentWidth is read-only and we have no control
// over how it is calculated.
implicitWidth: 0
wrapMode: TextEdit.Wrap
text: root.errorInformation && root.errorInformation.errors ?
root.errorInformation.errors.join("\n\n")
// This is just to suppress warnings. Users should never see this.
: "No error information."
font.family: "monospace"
readOnly: true
selectByMouse: true
}
}
}
}

View File

@@ -0,0 +1,369 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Window
import org.kde.plasma.core as PlasmaCore
import org.kde.ksvg as KSvg
import org.kde.plasma.plasmoid
import org.kde.kquickcontrolsaddons
import org.kde.kirigami as Kirigami
PlasmaCore.ToolTipArea {
id: root
objectName: "org.kde.desktop-CompactApplet"
anchors.fill: parent
mainText: plasmoidItem.toolTipMainText
subText: plasmoidItem.toolTipSubText
location: Plasmoid.location
active: !plasmoidItem.expanded
textFormat: plasmoidItem.toolTipTextFormat
mainItem: plasmoidItem.toolTipItem ? plasmoidItem.toolTipItem : null
readonly property bool vertical: location === PlasmaCore.Types.RightEdge || location === PlasmaCore.Types.LeftEdge
property Item fullRepresentation
property Item compactRepresentation
property Item expandedFeedback: expandedItem
property PlasmoidItem plasmoidItem
onCompactRepresentationChanged: {
if (compactRepresentation) {
compactRepresentation.anchors.fill = null;
compactRepresentation.parent = compactRepresentationParent;
compactRepresentation.anchors.fill = compactRepresentationParent;
compactRepresentation.visible = true;
}
root.visible = true;
}
onFullRepresentationChanged: {
if (fullRepresentation) {
fullRepresentation.anchors.fill = null;
fullRepresentation.parent = appletParent;
fullRepresentation.anchors.fill = appletParent;
// This avoids the content being drawn underneath the
// separator between the panel and the applet.
if (!separator.visible) {
return;
}
if (Plasmoid.location === PlasmaCore.Types.TopEdge) {
fullRepresentation.anchors.topMargin = separator.height
} else if (Plasmoid.location === PlasmaCore.Types.BottomEdge) {
fullRepresentation.anchors.bottomMargin = separator.height
} else if (Plasmoid.location === PlasmaCore.Types.LeftEdge) {
fullRepresentation.anchors.leftMargin = separator.width
} else if (Plasmoid.location === PlasmaCore.Types.RightEdge) {
fullRepresentation.anchors.rightMargin = separator.width
}
}
}
FocusScope {
id: compactRepresentationParent
anchors.fill: parent
activeFocusOnTab: true
onActiveFocusChanged: {
// When the scope gets the active focus, try to focus its first descendant,
// if there is on which has activeFocusOnTab
if (!activeFocus) {
return;
}
let nextItem = nextItemInFocusChain();
let candidate = nextItem;
while (candidate.parent) {
if (candidate === compactRepresentationParent) {
nextItem.forceActiveFocus();
return;
}
candidate = candidate.parent;
}
}
objectName: "expandApplet"
Accessible.name: root.mainText
Accessible.description: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:whatsthis Accessible description for panel widget %1", "Open %1", root.subText)
Accessible.role: Accessible.Button
Accessible.onPressAction: Plasmoid.activated()
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Select:
Plasmoid.activated();
break;
}
}
}
KSvg.FrameSvgItem {
id: expandedItem
z: -100
property var containerMargins: {
let item = root;
while (item.parent) {
item = item.parent;
if (item.isAppletContainer) {
return item.getMargins;
}
}
return undefined;
}
anchors {
fill: parent
property bool returnAllMargins: true
// The above makes sure margin is returned even for side margins, that
// would be otherwise turned off.
bottomMargin: !vertical && containerMargins ? -containerMargins('bottom', returnAllMargins) : 0
topMargin: !vertical && containerMargins ? -containerMargins('top', returnAllMargins) : 0
leftMargin: vertical && containerMargins ? -containerMargins('left', returnAllMargins) : 0
rightMargin: vertical && containerMargins ? -containerMargins('right', returnAllMargins) : 0
}
imagePath: "widgets/tabbar"
visible: opacity > 0
prefix: {
let prefix;
switch (Plasmoid.location) {
case PlasmaCore.Types.LeftEdge:
prefix = "west-active-tab";
break;
case PlasmaCore.Types.TopEdge:
prefix = "north-active-tab";
break;
case PlasmaCore.Types.RightEdge:
prefix = "east-active-tab";
break;
default:
prefix = "south-active-tab";
}
if (!hasElementPrefix(prefix)) {
prefix = "active-tab";
}
return prefix;
}
opacity: root.plasmoidItem.expanded ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: Kirigami.Units.shortDuration
easing.type: Easing.InOutQuad
}
}
}
Timer {
id: expandedSync
interval: 100
onTriggered: root.plasmoidItem.expanded = dialog.visible;
}
Connections {
target: Plasmoid.internalAction("configure")
function onTriggered() {
if (root.plasmoidItem.hideOnWindowDeactivate) {
root.plasmoidItem.expanded = false
}
}
}
Connections {
target: root.Plasmoid
function onContextualActionsAboutToShow() { root.hideImmediately() }
}
PlasmaCore.AppletPopup {
id: dialog
objectName: "popupWindow"
popupDirection: {
switch (Plasmoid.location) {
case PlasmaCore.Types.TopEdge:
return Qt.BottomEdge
case PlasmaCore.Types.LeftEdge:
return Qt.RightEdge
case PlasmaCore.Types.RightEdge:
return Qt.LeftEdge
default:
return Qt.TopEdge
}
}
margin: (Plasmoid.containmentDisplayHints & PlasmaCore.Types.ContainmentPrefersFloatingApplets) ? Kirigami.Units.largeSpacing : 0
Behavior on margin {
NumberAnimation {
// Since the panel animation won't be perfectly in sync,
// using a duration larger than the panel animation results
// in a better-looking animation.
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.OutCubic
}
}
floating: Plasmoid.location === PlasmaCore.Types.Floating
removeBorderStrategy: Plasmoid.location === PlasmaCore.Types.Floating
? PlasmaCore.AppletPopup.AtScreenEdges
: PlasmaCore.AppletPopup.AtScreenEdges | PlasmaCore.AppletPopup.AtPanelEdges
hideOnWindowDeactivate: root.plasmoidItem.hideOnWindowDeactivate
visible: root.plasmoidItem.expanded && root.fullRepresentation
visualParent: root.compactRepresentation
backgroundHints: (Plasmoid.containmentDisplayHints & PlasmaCore.Types.ContainmentPrefersOpaqueBackground) ? PlasmaCore.AppletPopup.SolidBackground : PlasmaCore.AppletPopup.StandardBackground
appletInterface: root.plasmoidItem
property var oldStatus: PlasmaCore.Types.UnknownStatus
onVisibleChanged: {
if (!visible) {
expandedSync.restart();
Plasmoid.status = oldStatus;
} else {
oldStatus = Plasmoid.status;
Plasmoid.status = PlasmaCore.Types.RequiresAttentionStatus;
// This call currently fails and complains at runtime:
// QWindow::setWindowState: QWindow::setWindowState does not accept Qt::WindowActive
dialog.requestActivate();
}
}
//It's a MouseEventListener to get all the events, so the eventfilter will be able to catch them
mainItem: MouseEventListener {
id: appletParent
focus: true
Keys.onEscapePressed: {
root.plasmoidItem.expanded = false;
}
property real extraWidth: 0
property real extraHeight: 0
Layout.minimumWidth: root.fullRepresentation ? root.fullRepresentation.Layout.minimumWidth + extraWidth : 0
Layout.minimumHeight: root.fullRepresentation ? root.fullRepresentation.Layout.minimumHeight + extraHeight : 0
Layout.maximumWidth: root.fullRepresentation ? root.fullRepresentation.Layout.maximumWidth + extraWidth : Infinity
Layout.maximumHeight: root.fullRepresentation ? root.fullRepresentation.Layout.maximumHeight + extraHeight : Infinity
implicitWidth: {
if (root.fullRepresentation !== null) {
/****/ if (root.fullRepresentation.Layout.preferredWidth > 0) {
return root.fullRepresentation.Layout.preferredWidth + extraWidth;
} else if (root.fullRepresentation.implicitWidth > 0) {
return root.fullRepresentation.implicitWidth + extraWidth;
}
}
return Kirigami.Units.iconSizes.sizeForLabels * 35;
}
implicitHeight: {
if (root.fullRepresentation !== null) {
/****/ if (root.fullRepresentation.Layout.preferredHeight > 0) {
return root.fullRepresentation.Layout.preferredHeight + extraHeight;
} else if (root.fullRepresentation.implicitHeight > 0) {
return root.fullRepresentation.implicitHeight + extraHeight;
}
}
return Kirigami.Units.iconSizes.sizeForLabels * 25;
}
onActiveFocusChanged: {
if (activeFocus && root.fullRepresentation) {
root.fullRepresentation.forceActiveFocus()
}
}
// Draws a line between the applet dialog and the panel
KSvg.SvgItem {
id: separator
// Only draw for popups of panel applets, not desktop applets
visible: [PlasmaCore.Types.TopEdge, PlasmaCore.Types.LeftEdge, PlasmaCore.Types.RightEdge, PlasmaCore.Types.BottomEdge]
.includes(Plasmoid.location) && !dialog.margin
anchors {
topMargin: -dialog.topPadding
leftMargin: -dialog.leftPadding
rightMargin: -dialog.rightPadding
bottomMargin: -dialog.bottomPadding
}
z: 999 /* Draw the line on top of the applet */
elementId: (Plasmoid.location === PlasmaCore.Types.TopEdge || Plasmoid.location === PlasmaCore.Types.BottomEdge) ? "horizontal-line" : "vertical-line"
imagePath: "widgets/line"
states: [
State {
when: Plasmoid.location === PlasmaCore.Types.TopEdge
AnchorChanges {
target: separator
anchors {
top: separator.parent.top
left: separator.parent.left
right: separator.parent.right
}
}
PropertyChanges {
separator.height: 1
appletParent.extraHeight: 1
appletParent.extraWidth: 0
}
},
State {
when: Plasmoid.location === PlasmaCore.Types.LeftEdge
AnchorChanges {
target: separator
anchors {
left: separator.parent.left
top: separator.parent.top
bottom: separator.parent.bottom
}
}
PropertyChanges {
separator.width: 1
appletParent.extraHeight: 0
appletParent.extraWidth: 1
}
},
State {
when: Plasmoid.location === PlasmaCore.Types.RightEdge
AnchorChanges {
target: separator
anchors {
top: separator.parent.top
right: separator.parent.right
bottom: separator.parent.bottom
}
}
PropertyChanges {
separator.width: 1
appletParent.extraHeight: 0
appletParent.extraWidth: 1
}
},
State {
when: Plasmoid.location === PlasmaCore.Types.BottomEdge
AnchorChanges {
target: separator
anchors {
left: separator.parent.left
right: separator.parent.right
bottom: separator.parent.bottom
}
}
PropertyChanges {
separator.height: 1
appletParent.extraHeight: 1
appletParent.extraWidth: 0
}
}
]
}
LayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
}
}
}

View File

@@ -0,0 +1,130 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.plasmoid
import org.kde.plasma.workspace.components as WorkspaceComponents
import org.kde.kirigami as Kirigami
Kirigami.Icon {
id: defaultCompactRepresentation
property PlasmoidItem plasmoidItem
readonly property bool inPanel: [PlasmaCore.Types.TopEdge, PlasmaCore.Types.RightEdge, PlasmaCore.Types.BottomEdge, PlasmaCore.Types.LeftEdge]
.includes(Plasmoid.location)
Layout.minimumWidth: {
switch (Plasmoid.formFactor) {
case PlasmaCore.Types.Vertical:
return 0;
case PlasmaCore.Types.Horizontal:
return height;
default:
return Kirigami.Units.gridUnit * 3;
}
}
Layout.minimumHeight: {
switch (Plasmoid.formFactor) {
case PlasmaCore.Types.Vertical:
return width;
case PlasmaCore.Types.Horizontal:
return 0;
default:
return Kirigami.Units.gridUnit * 3;
}
}
source: Plasmoid.icon || "plasma"
active: mouseArea.containsMouse
activeFocusOnTab: true
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Select:
Plasmoid.activated();
event.accepted = true; // BUG 481393: Prevent system tray from receiving the event
break;
}
}
Accessible.name: Plasmoid.title
Accessible.description: plasmoidItem.toolTipSubText ?? ""
Accessible.role: Accessible.Button
MouseArea {
id: mouseArea
property bool wasExpanded: false
anchors.fill: parent
hoverEnabled: true
onPressed: wasExpanded = defaultCompactRepresentation.plasmoidItem.expanded
onClicked: mouse => {
if (mouse.button === Qt.MiddleButton) {
Plasmoid.secondaryActivated();
} else {
defaultCompactRepresentation.plasmoidItem.expanded = !wasExpanded;
}
}
}
// Open the FullRepresentation on drag-hover if the applet wants it
Loader {
anchors.fill: parent
active: defaultCompactRepresentation.plasmoidItem.expandedOnDragHover
sourceComponent: DropArea {
anchors.fill: parent
onEntered: dropTimer.restart()
onExited: dropTimer.stop()
Timer {
id: dropTimer
interval: 250 // matches taskmanager delay
onTriggered: {
defaultCompactRepresentation.plasmoidItem.expanded = true;
mouseArea.wasExpanded = true;
}
}
}
}
Loader {
id: badgeLoader
anchors.bottom: defaultCompactRepresentation.bottom
anchors.right: defaultCompactRepresentation.right
active: defaultCompactRepresentation.plasmoidItem.badgeText.length != 0
sourceComponent: WorkspaceComponents.BadgeOverlay {
text: defaultCompactRepresentation.plasmoidItem.badgeText
icon: defaultCompactRepresentation
}
// Non-default state to center if the badge is wider than the icon
states: [
State {
when: badgeLoader.width >= defaultCompactRepresentation.width
AnchorChanges {
target: badgeLoader
anchors.right: undefined
anchors.horizontalCenter: defaultCompactRepresentation.horizontalCenter
}
}
]
}
}

View File

@@ -0,0 +1,248 @@
/*
SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.plasma.plasmoid
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCM
/**
* A copy of Kirigami.AboutPage adapted to KPluginMetadata instead of KAboutData
*/
KCM.SimpleKCM {
id: page
title: i18nc("@title:window About this widget", "About")
property var metaData: Plasmoid.metaData
Component {
id: personDelegate
RowLayout {
height: implicitHeight + (Kirigami.Units.smallSpacing * 2)
spacing: Kirigami.Units.smallSpacing * 2
Kirigami.Icon {
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: implicitWidth
source: "user"
}
QQC2.Label {
Layout.fillWidth: true
text: modelData.name
textFormat: Text.PlainText
}
Row {
// Group action buttons together
spacing: 0
QQC2.Button {
visible: modelData.emailAddress
width: height
icon.name: "mail-sent"
display: QQC2.AbstractButton.IconOnly
text: i18ndc("@action:button icononly for tooltip & accessible", "plasma_shell_org.kde.plasma.desktop", "Send an email to %1", modelData.emailAddress)
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: text
onClicked: Qt.openUrlExternally("mailto:%1".arg(modelData.emailAddress))
}
QQC2.Button {
visible: modelData.webAddress
width: height
icon.name: "globe"
display: QQC2.AbstractButton.IconOnly
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button icononly for tooltip & accessible %1 is url", "Open website %1", modelData.webAddress)
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: modelData.webAddress
onClicked: Qt.openUrlExternally(modelData.webAddress)
}
}
}
}
Component {
id: licenseComponent
Kirigami.OverlaySheet {
property alias text: licenseLabel.text
onClosed: destroy()
Kirigami.SelectableLabel {
id: licenseLabel
implicitWidth: Math.max(Kirigami.Units.gridUnit * 25, Math.round(page.width / 2), contentWidth)
wrapMode: Text.WordWrap
}
Component.onCompleted: open();
}
}
Item {
height: childrenRect.height
ColumnLayout {
id: column
readonly property int headingTopSpacing: Kirigami.Units.smallSpacing
readonly property int dataLeftSpacing: Kirigami.Units.smallSpacing
anchors.horizontalCenter: parent.horizontalCenter
spacing: Kirigami.Units.largeSpacing
GridLayout {
columns: 2
Layout.fillWidth: true
Kirigami.Icon {
Layout.rowSpan: 2
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
Layout.preferredWidth: height
Layout.maximumWidth: page.width / 3;
Layout.rightMargin: Kirigami.Units.largeSpacing
source: page.metaData.iconName || page.metaData.pluginId
fallback: "application-x-plasma"
}
Kirigami.Heading {
Layout.fillWidth: true
Layout.alignment: Qt.AlignBottom
text: page.metaData.name + " " + page.metaData.version
textFormat: Text.PlainText
}
Kirigami.Heading {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
Layout.maximumWidth: Kirigami.Units.gridUnit * 15
level: 3
type: Kirigami.Heading.Type.Secondary
wrapMode: Text.WordWrap
text: page.metaData.description
textFormat: Text.PlainText
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
Kirigami.Heading {
Layout.topMargin: column.headingTopSpacing
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group", "Website")
textFormat: Text.PlainText
}
Kirigami.UrlButton {
Layout.leftMargin: column.dataLeftSpacing
url: page.metaData.website
visible: url.length > 0
}
Kirigami.Heading {
Layout.topMargin: column.headingTopSpacing
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group", "Get Help")
textFormat: Text.PlainText
}
Kirigami.UrlButton {
Layout.leftMargin: column.dataLeftSpacing
textFormat: Text.PlainText
url: page.metaData.bugReportUrl
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button urlbutton", "Report an issue")
visible: page.metaData.bugReportUrl.length > 0
}
Kirigami.Heading {
Layout.topMargin: column.headingTopSpacing
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title license information", "Copyright")
textFormat: Text.PlainText
}
ColumnLayout {
spacing: Kirigami.Units.smallSpacing
Layout.leftMargin: column.dataLeftSpacing
QQC2.Label {
text: page.metaData.copyrightText
textFormat: Text.PlainText
visible: text.length > 0
}
RowLayout {
spacing: Kirigami.Units.smallSpacing
QQC2.Label {
Layout.fillWidth: true
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@label %1 is the short SPDX text for the license", "License: %1", page.metaData.license)
textFormat: Text.PlainText
}
QQC2.Button {
icon.name: "view-readermode"
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Read License")
onClicked: {
licenseComponent.incubateObject(page.Window.window.contentItem, {
"text": page.metaData.licenseText,
"title": page.metaData.license,
}, Qt.Asynchronous);
}
}
}
}
Kirigami.Heading {
Layout.fillWidth: true
Layout.topMargin: column.headingTopSpacing
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group", "Authors")
textFormat: Text.PlainText
visible: page.metaData.authors.length > 0
}
Repeater {
Layout.leftMargin: column.dataLeftSpacing
model: page.metaData.authors
delegate: personDelegate
}
Kirigami.Heading {
Layout.topMargin: column.headingTopSpacing
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group", "Credits")
textFormat: Text.PlainText
visible: repCredits.count > 0
}
Repeater {
id: repCredits
Layout.leftMargin: column.dataLeftSpacing
model: page.metaData.otherContributors
delegate: personDelegate
}
Kirigami.Heading {
Layout.topMargin: column.headingTopSpacing
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group", "Translators")
textFormat: Text.PlainText
visible: repTranslators.count > 0
}
Repeater {
id: repTranslators
Layout.leftMargin: column.dataLeftSpacing
model: page.metaData.translators
delegate: personDelegate
}
}
}
}

View File

@@ -0,0 +1,483 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2020 Nicolas Fella <nicolas.fella@gmx.de>
SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
SPDX-FileCopyrightText: 2022-2023 ivan tkachenko <me@ratijas.tk>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kcmutils as KCMUtils
import org.kde.kirigami as Kirigami
import org.kde.kitemmodels as KItemModels
import org.kde.plasma.configuration
import org.kde.plasma.plasmoid
Rectangle {
id: root
implicitWidth: Kirigami.Units.gridUnit * 45
implicitHeight: Kirigami.Units.gridUnit * 35
Layout.minimumWidth: Kirigami.Units.gridUnit * 30
Layout.minimumHeight: Kirigami.Units.gridUnit * 21
LayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
color: Kirigami.Theme.backgroundColor
property bool isContainment: false
property ConfigModel globalConfigModel: globalAppletConfigModel
property url currentSource
property bool wasConfigurationChangedSignalSent: false
function closing() {
if (applyButton.enabled) {
messageDialog.item = null;
messageDialog.open();
return false;
}
return true;
}
function saveConfig() {
const config = Plasmoid.configuration; // type: KConfigPropertyMap
// call applet's own config handling first so it can set cfg_ properties if needed
if (app.pageStack.currentItem.saveConfig) {
app.pageStack.currentItem.saveConfig()
}
config.keys().forEach(key => {
const cfgKey = "cfg_" + key;
if (cfgKey in app.pageStack.currentItem) {
config[key] = app.pageStack.currentItem[cfgKey];
}
})
plasmoid.configuration.writeConfig();
}
function isConfigurationChanged() {
const config = Plasmoid.configuration;
return config.keys().some(key => {
const cfgKey = "cfg_" + key
if (!app.pageStack.currentItem.hasOwnProperty(cfgKey))
return false
return config[key] != app.pageStack.currentItem[cfgKey] &&
config[key].toString() != app.pageStack.currentItem[cfgKey].toString()
})
}
Connections {
target: configDialog
function onClosing(event) {
event.accepted = closing();
}
}
ConfigModel {
id: globalAppletConfigModel
ConfigCategory {
name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group for configuration dialog page", "Keyboard Shortcuts")
icon: "preferences-desktop-keyboard"
source: Qt.resolvedUrl("ConfigurationShortcuts.qml")
}
}
KItemModels.KSortFilterProxyModel {
id: configDialogFilterModel
sourceModel: configDialog.configModel
filterRowCallback: (row, parent) => {
return sourceModel.data(sourceModel.index(row, 0), ConfigModel.VisibleRole);
}
}
function settingValueChanged() {
applyButton.enabled = wasConfigurationChangedSignalSent || isConfigurationChanged() || (app?.pageStack?.currentItem?.unsavedChanges ?? false);
}
function pushReplace(item, config) {
let page;
if (app.pageStack.depth === 0) {
page = app.pageStack.push(item, config);
} else {
page = app.pageStack.replace(item, config);
}
app.currentConfigPage = page;
}
function open(item) {
app.isAboutPage = false;
root.currentSource = item.source
if (item.configUiModule && item.configUiComponent) {
root.currentSource = item.configUiModule + item.configUiComponent; // Just for the highlight status
const config = Plasmoid.configuration; // type: KConfigPropertyMap
const props = {
"title": item.name,
};
config.keys().forEach(key => {
props["cfg_" + key] = config[key];
});
pushReplace(Qt.createComponent(item.configUiModule, item.configUiComponent), props);
} else if (item.source) {
app.isAboutPage = item.source === Qt.resolvedUrl("AboutPlugin.qml");
const config = Plasmoid.configuration; // type: KConfigPropertyMap
const props = { "title": item.name };
config.keys().forEach(key => {
props["cfg_" + key] = config[key];
});
pushReplace(Qt.resolvedUrl(item.source), props);
} else {
app.pageStack.pop();
}
applyButton.enabled = false
}
Connections {
target: app.currentConfigPage
ignoreUnknownSignals: true
// This is an artifact of old internal architecture. If control beyond the automated
// monitoring based on cfg_ properties is required, plasmoids should not emit
// settingValueChanged() but configurationChanged() to force prompting the user
// for saving changes, or use the unsavedChanges property to dynamically indicate
// whether saving is needed in the current state).
// We keep it around for now as third-party plasmoids might use it (even though they
// really shouldn't as it's not documented).
// TODO Plasma 7: remove and document in porting guide.
function onSettingValueChanged() {
wasConfigurationChangedSignalSent = true;
}
function onUnsavedChangesChanged() {
root.settingValueChanged()
}
}
Connections {
target: app.pageStack
function onCurrentItemChanged() {
if (app.pageStack.currentItem !== null) {
const config = Plasmoid.configuration; // type: KConfigPropertyMap
config.keys().forEach(key => {
const changedSignal = app.pageStack.currentItem["cfg_" + key + "Changed"];
if (changedSignal) {
changedSignal.connect(() => root.settingValueChanged());
}
});
const configurationChangedSignal = app.pageStack.currentItem.configurationChanged;
if (configurationChangedSignal) {
configurationChangedSignal.connect(() => {
root.wasConfigurationChangedSignalSent = true
root.settingValueChanged()
});
}
const configurationUnsavedChangesSignal = app.pageStack.currentItem.unsavedChangesChanged
if (configurationUnsavedChangesSignal) {
configurationUnsavedChangesSignal.connect(() => root.settingValueChanged())
}
}
}
}
Component.onCompleted: {
// if we are a containment then the first item will be ConfigurationContainmentAppearance
// if the applet does not have own configs then the first item will be Shortcuts
if (isContainment || !configDialog.configModel || configDialog.configModel.count === 0) {
open(root.globalConfigModel.get(0))
} else {
open(configDialog.configModel.get(0))
}
}
function applicationWindow() {
return app;
}
QQC2.ScrollView {
id: categoriesScroll
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
width: Kirigami.Units.gridUnit * 7
contentWidth: availableWidth
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
activeFocusOnTab: true
focus: true
Accessible.role: Accessible.PageTabList
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
Keys.onUpPressed: {
const buttons = categories.children
let foundPrevious = false
for (let i = buttons.length - 1; i >= 0; --i) {
const button = buttons[i];
if (!button.hasOwnProperty("highlighted")) {
// not a ConfigCategoryDelegate
continue;
}
if (foundPrevious) {
categories.openCategory(button.item)
categoriesScroll.forceActiveFocus(Qt.TabFocusReason)
return
} else if (button.highlighted) {
foundPrevious = true
}
}
event.accepted = false
}
Keys.onDownPressed: {
const buttons = categories.children
let foundNext = false
for (let i = 0, length = buttons.length; i < length; ++i) {
const button = buttons[i];
if (!button.hasOwnProperty("highlighted")) {
continue;
}
if (foundNext) {
categories.openCategory(button.item)
categoriesScroll.forceActiveFocus(Qt.TabFocusReason)
return
} else if (button.highlighted) {
foundNext = true
}
}
event.accepted = false
}
ColumnLayout {
id: categories
spacing: 0
width: categoriesScroll.contentWidth
focus: true
function openCategory(item) {
if (applyButton.enabled) {
messageDialog.item = item;
messageDialog.open();
return;
}
open(item)
}
Component {
id: categoryDelegate
ConfigCategoryDelegate {
id: delegate
onActivated: categories.openCategory(model);
highlighted: {
if (app.pageStack.currentItem) {
if (model.configUiModule && model.configUiComponent) {
return root.currentSource == (model.configUiModule + model.configUiComponent)
} else {
return root.currentSource == model.source
}
}
return false
}
item: model
}
}
Repeater {
Layout.fillWidth: true
model: root.isContainment ? globalConfigModel : undefined
delegate: categoryDelegate
}
Repeater {
Layout.fillWidth: true
model: configDialogFilterModel
delegate: categoryDelegate
}
Repeater {
Layout.fillWidth: true
model: !root.isContainment ? globalConfigModel : undefined
delegate: categoryDelegate
}
Repeater {
Layout.fillWidth: true
model: ConfigModel {
ConfigCategory{
name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group for About dialog page", "About")
icon: "help-about"
source: Qt.resolvedUrl("AboutPlugin.qml")
}
}
delegate: categoryDelegate
}
}
}
Kirigami.Separator {
anchors {
left: parent.left
right: parent.right
top: parent.top
}
z: 1
}
Kirigami.Separator {
id: verticalSeparator
anchors {
top: parent.top
left: categoriesScroll.right
bottom: parent.bottom
}
z: 1
}
Kirigami.ApplicationItem {
id: app
anchors {
top: parent.top
left: verticalSeparator.right
right: parent.right
bottom: parent.bottom
}
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.Auto
wideScreen: true
pageStack.globalToolBar.separatorVisible: bottomSeparator.visible
pageStack.globalToolBar.colorSet: Kirigami.Theme.Window
property var currentConfigPage: null
property bool isAboutPage: false
Kirigami.PromptDialog {
id: messageDialog
property var item
title: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:window dialog title", "Apply Settings")
subtitle: i18ndc("plasma_shell_org.kde.plasma.desktop", "@label dialog body", "The current page has unsaved changes. Apply the changes or discard them?")
standardButtons: Kirigami.Dialog.Apply | Kirigami.Dialog.Discard | Kirigami.Dialog.Cancel
onApplied: {
applyAction.trigger()
discarded();
}
onDiscarded: {
wasConfigurationChangedSignalSent = false;
if (item) {
root.open(item);
messageDialog.close();
} else {
applyButton.enabled = false;
configDialog.close();
}
}
}
footer: QQC2.Pane {
padding: Kirigami.Units.largeSpacing
contentItem: RowLayout {
id: buttonsRow
spacing: Kirigami.Units.smallSpacing
Item {
Layout.fillWidth: true
}
QQC2.Button {
icon.name: "dialog-ok"
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "OK")
onClicked: acceptAction.trigger()
}
QQC2.Button {
id: applyButton
enabled: false
icon.name: "dialog-ok-apply"
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Apply")
visible: !app.isAboutPage && app.pageStack.currentItem
onClicked: applyAction.trigger()
}
QQC2.Button {
icon.name: "dialog-cancel"
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Cancel")
onClicked: cancelAction.trigger()
visible: !app.isAboutPage
KeyNavigation.tab: categories
}
}
background: Item {
Kirigami.Separator {
id: bottomSeparator
visible: (app.pageStack.currentItem
&& app.pageStack.currentItem.flickable
&& !(app.pageStack.currentItem.flickable instanceof KCMUtils.GridViewKCM)
&& !(app.pageStack.currentItem.flickable.atYBeginning
&& app.pageStack.currentItem.flickable.atYEnd)) ?? false
anchors {
left: parent.left
right: parent.right
top: parent.top
}
}
}
}
QQC2.Action {
id: acceptAction
onTriggered: {
applyAction.trigger();
configDialog.close();
}
}
QQC2.Action {
id: applyAction
onTriggered: {
root.saveConfig()
wasConfigurationChangedSignalSent = false
applyButton.enabled = false;
}
}
QQC2.Action {
id: cancelAction
onTriggered: {
if (root.closing()) {
configDialog.close();
}
}
}
Keys.onReturnPressed: acceptAction.trigger();
Keys.onEscapePressed: cancelAction.trigger();
}
}

View File

@@ -0,0 +1,72 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Window
import org.kde.kirigami as Kirigami
QQC2.ItemDelegate {
id: delegate
signal activated()
//BEGIN properties
Layout.fillWidth: true
hoverEnabled: true
Accessible.role: Accessible.PageTab
Accessible.name: model.name
Accessible.description: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:whatsthis Accessible description for sidebar entries opening configuration page", "Open configuration page")
Accessible.onPressAction: delegate.clicked()
focus: highlighted // need to actually focus highlighted items for the screen reader to see them
property var item
//END properties
//BEGIN connections
onClicked: {
if (highlighted) {
return;
}
activated()
}
//END connections
//BEGIN UI components
contentItem: ColumnLayout {
id: delegateContents
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
Layout.alignment: Qt.AlignHCenter
implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: Kirigami.Units.iconSizes.medium
source: model.icon
selected: Window.active && (delegate.highlighted || delegate.pressed)
}
QQC2.Label {
id: nameLabel
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing
text: model.name
textFormat: Text.PlainText
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
color: Window.active && (delegate.highlighted || delegate.pressed) ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
font.bold: delegate.highlighted && delegate.parent.activeFocus
Accessible.ignored: true
}
}
//END UI components
}

View File

@@ -0,0 +1,146 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kcmutils as KCM
KCM.SimpleKCM {
id: root
signal configurationChanged
property var prettyStrings: {
"LeftButton": i18ndc("plasma_shell_org.kde.plasma.desktop", "@label for shortcuts", "Left-Button"),
"RightButton": i18ndc("plasma_shell_org.kde.plasma.desktop", "@label for shortcuts", "Right-Button"),
"MiddleButton": i18ndc("plasma_shell_org.kde.plasma.desktop", "@label for shortcuts", "Middle-Button"),
"BackButton": i18ndc("plasma_shell_org.kde.plasma.desktop", "@label for shortcuts", "Back-Button"),
"ForwardButton": i18ndc("plasma_shell_org.kde.plasma.desktop", "@label for shortcuts", "Forward-Button"),
"wheel:Vertical": i18ndc("plasma_shell_org.kde.plasma.desktop", "@label for shortcuts", "Vertical-Scroll"),
"wheel:Horizontal": i18ndc("plasma_shell_org.kde.plasma.desktop", "@label for shortcuts", "Horizontal-Scroll"),
"ShiftModifier": i18ndc("plasma_shell_org.kde.plasma.desktop", "@label for shortcuts", "Shift"),
"ControlModifier": i18ndc("plasma_shell_org.kde.plasma.desktop", "@label for shortcuts", "Ctrl"),
"AltModifier": i18ndc("plasma_shell_org.kde.plasma.desktop", "@label for shortcuts", "Alt"),
"MetaModifier": i18ndc("plasma_shell_org.kde.plasma.desktop", "@label for shortcuts", "Meta")
}
function saveConfig() {
configDialog.currentContainmentActionsModel.save();
}
Connections {
target: configDialog.currentContainmentActionsModel
function onConfigurationChanged() {
root.configurationChanged()
}
}
GridLayout {
id: mainColumn
flow: GridLayout.TopToBottom
width: parent.width
Repeater {
id: actionsRepeater
model: configDialog.currentContainmentActionsModel
MouseEventInputButton {
Layout.column: 0
Layout.row: index
Layout.fillWidth: true
Layout.minimumWidth: implicitWidth
defaultText: {
var splitAction = model.action.split(';');
var button = splitAction[0];
var modifiers = (splitAction[1] || "").split('|').filter(function (item) {
return item !== "NoModifier";
});
var parts = modifiers;
modifiers.push(button);
return parts.map(function (item) {
return root.prettyStrings[item] || item;
}).join(i18ndc("plasma_shell_org.kde.plasma.desktop", "Concatenation sign for shortcuts, e.g. Ctrl+Shift", "+"));
}
eventString: model.action
onEventStringChanged: {
configDialog.currentContainmentActionsModel.update(index, eventString, model.pluginName);
}
}
}
Repeater {
model: configDialog.currentContainmentActionsModel
QQC2.ComboBox {
id: pluginsCombo
// "index" argument of onActivated shadows the model index
readonly property int pluginIndex: index
Layout.fillWidth: true
Layout.column: 1
Layout.row: index
// both MouseEventInputButton and this ComboBox have fillWidth for a uniform layout
// however, their implicit sizes is taken into account and they compete against
// each other for available space. By setting an insane preferredWidth we give
// ComboBox a greater share of the available space
Layout.preferredWidth: 9000
model: configDialog.containmentActionConfigModel
textRole: "name"
valueRole: "pluginName"
property bool initialized: false
Component.onCompleted: {
currentIndex = indexOfValue(pluginName)
pluginsCombo.initialized = true;
}
onActivated: {
if (initialized) {
var newPluginName = currentValue;
if (newPluginName !== pluginName) {
configDialog.currentContainmentActionsModel.update(pluginIndex, action, newPluginName);
}
}
}
}
}
Repeater {
model: configDialog.currentContainmentActionsModel
RowLayout {
Layout.column: 2
Layout.row: index
QQC2.Button {
icon.name: "configure"
enabled: model.hasConfigurationInterface
onClicked: {
configDialog.currentContainmentActionsModel.showConfiguration(index, this);
}
}
QQC2.Button {
icon.name: "list-remove"
onClicked: {
configDialog.currentContainmentActionsModel.remove(index);
}
}
}
}
MouseEventInputButton {
defaultText: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Add Action");
icon.name: checked ? "input-mouse-symbolic" : "list-add"
onEventStringChanged: {
configDialog.currentContainmentActionsModel.append(eventString, "org.kde.contextmenu");
}
}
}
}

View File

@@ -0,0 +1,253 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQml
import org.kde.newstuff as NewStuff
import org.kde.kirigami as Kirigami
import org.kde.kcmutils
import org.kde.plasma.plasmoid
import org.kde.plasma.configuration
SimpleKCM {
id: appearanceRoot
signal configurationChanged
leftPadding: 0 // let wallpaper config touch the edges in case there's a List/GridView'
rightPadding: 0
bottomPadding: 0
property int formAlignment: wallpaperComboBox.Kirigami.ScenePosition.x - appearanceRoot.Kirigami.ScenePosition.x + Kirigami.Units.smallSpacing
property string originalWallpaper: ""
property alias parentLayout: parentLayout
property bool unsavedChanges: false
function saveConfig() {
if (main.currentItem.saveConfig) {
main.currentItem.saveConfig()
}
configDialog.currentWallpaper = wallpaperComboBox.currentValue
appearanceRoot.originalWallpaper = wallpaperComboBox.currentValue
configDialog.wallpaperConfiguration.keys().forEach(key => {
if (main.currentItem["cfg_"+key] !== undefined) {
configDialog.wallpaperConfiguration[key] = main.currentItem["cfg_"+key]
}
})
configDialog.applyWallpaper()
configDialog.containmentPlugin = pluginComboBox.currentValue
appearanceRoot.closeContainmentWarning()
appearanceRoot.unsavedChanges = false
}
function checkUnsavedChanges() {
const wallpaperConfig = configDialog.wallpaperConfiguration
appearanceRoot.unsavedChanges = configDialog.currentWallpaper != appearanceRoot.originalWallpaper ||
configDialog.containmentPlugin != pluginComboBox.currentValue ||
wallpaperConfig.keys().some(key => {
const cfgKey = "cfg_" + key
if (!(cfgKey in main.currentItem) || key.startsWith("PreviewImage") || key.endsWith("Default")) {
return false
}
return main.currentItem[cfgKey] != wallpaperConfig[key] &&
main.currentItem[cfgKey].toString() != wallpaperConfig[key].toString()
})
}
function closeContainmentWarning() {
if (main.currentItem?.objectName === "switchContainmentWarningItem") {
main.pop();
categoriesScroll.enabled = true;
}
}
ColumnLayout {
width: appearanceRoot.availableWidth
height: Math.max(implicitHeight, appearanceRoot.availableHeight)
spacing: 0 // unless it's 0 there will be an additional gap between two FormLayouts
Kirigami.InlineMessage {
visible: Plasmoid.immutable || animating
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:status inlinemessage", "Layout changes have been restricted by the system administrator")
showCloseButton: true
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing
Layout.bottomMargin: Kirigami.Units.smallSpacing * 2 // we need this because ColumnLayout's spacing is 0
}
Kirigami.FormLayout {
id: parentLayout // needed for twinFormLayouts to work in wallpaper plugins
twinFormLayouts: main.currentItem.formLayout || []
Layout.fillWidth: true
QQC2.ComboBox {
id: pluginComboBox
Layout.preferredWidth: Math.max(implicitWidth, wallpaperComboBox.implicitWidth)
Kirigami.FormData.label: i18ndc("plasma_shell_org.kde.plasma.desktop", "@label:listbox", "Layout:")
enabled: !Plasmoid.immutable
model: configDialog.containmentPluginsConfigModel
textRole: "name"
valueRole: "pluginName"
onActivated: {
if (configDialog.containmentPlugin !== pluginComboBox.currentValue) {
main.push(switchContainmentWarning);
categoriesScroll.enabled = false;
} else {
closeContainmentWarning()
}
appearanceRoot.checkUnsavedChanges()
}
Component.onCompleted: {
currentIndex = indexOfValue(configDialog.containmentPlugin)
activated(currentIndex)
}
}
RowLayout {
Layout.fillWidth: true
enabled: main.currentItem.objectName !== "switchContainmentWarningItem"
Kirigami.FormData.label: i18ndc("plasma_shell_org.kde.plasma.desktop", "@label:listbox", "Wallpaper type:")
Kirigami.FormData.buddyFor: wallpaperComboBox
QQC2.ComboBox {
id: wallpaperComboBox
function selectCurrentWallpaperPlugin() {
currentIndex = indexOfValue(configDialog.currentWallpaper)
appearanceRoot.originalWallpaper = currentValue
activated(currentIndex)
}
Layout.preferredWidth: Math.max(implicitWidth, pluginComboBox.implicitWidth)
model: configDialog.wallpaperConfigModel
textRole: "name"
valueRole: "pluginName"
onActivated: {
var idx = configDialog.wallpaperConfigModel.index(currentIndex, 0)
if (configDialog.currentWallpaper === currentValue && main.sourceFile !== "tbd") {
return;
}
configDialog.currentWallpaper = currentValue
main.sourceFile = idx.data(ConfigModel.SourceRole)
appearanceRoot.checkUnsavedChanges()
}
Component.onCompleted: {
selectCurrentWallpaperPlugin();
}
Connections {
enabled: true
target: configDialog.wallpaperConfigModel
function onWallpaperPluginsChanged() {
wallpaperComboBox.selectCurrentWallpaperPlugin();
}
}
}
NewStuff.Button {
configFile: "wallpaperplugin.knsrc"
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Get New Plugins…")
visibleWhenDisabled: true // don't hide on disabled
Layout.preferredHeight: wallpaperComboBox.height
}
}
}
Item {
id: emptyConfig
}
QQC2.StackView {
id: main
implicitHeight: main.empty ? 0 : currentItem.implicitHeight
Layout.fillHeight: true;
Layout.fillWidth: true;
// Bug 360862: if wallpaper has no config, sourceFile will be ""
// so we wouldn't load emptyConfig and break all over the place
// hence set it to some random value initially
property string sourceFile: "tbd"
onSourceFileChanged: loadSourceFile()
function loadSourceFile() {
const wallpaperConfig = configDialog.wallpaperConfiguration
// BUG 407619: wallpaperConfig can be null before calling `ContainmentItem::loadWallpaper()`
if (wallpaperConfig && sourceFile) {
var props = {
"configDialog": configDialog,
"wallpaperConfiguration": Qt.binding(() => Plasmoid.wallpaperGraphicsObject.configuration)
}
// Some third-party wallpaper plugins need the config keys to be set initially.
// We should not break them within one Plasma major version, but setting everything
// will lead to an error message for every unused property (and some, like KConfigXT
// default values, are used by almost no plugin configuration). We load the config
// page in a temp variable first, then use that to figure out which ones we need to
// set initially.
// TODO Plasma 7: consider whether we can drop this workaround
const temp = Qt.createComponent(Qt.resolvedUrl(sourceFile)).createObject(appearanceRoot, props)
wallpaperConfig.keys().forEach(key => {
const cfgKey = "cfg_" + key;
if (cfgKey in temp) {
props[cfgKey] = wallpaperConfig[key]
}
})
temp.destroy()
var newItem = replace(Qt.resolvedUrl(sourceFile), props)
wallpaperConfig.keys().forEach(key => {
const cfgKey = "cfg_" + key;
if (cfgKey in newItem) {
let changedSignal = main.currentItem[cfgKey + "Changed"]
if (changedSignal) {
changedSignal.connect(appearanceRoot.checkUnsavedChanges)
}
}
});
const configurationChangedSignal = newItem.configurationChanged
if (configurationChangedSignal) {
configurationChangedSignal.connect(appearanceRoot.configurationChanged)
}
} else {
replace(emptyConfig)
}
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
}
Component {
id: switchContainmentWarning
Item {
objectName: "switchContainmentWarningItem"
Kirigami.PlaceholderMessage {
id: message
width: parent.width - Kirigami.Units.largeSpacing * 8
anchors.centerIn: parent
icon.name: "documentinfo"
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info", "Layout changes must be applied before other changes can be made")
helpfulAction: QQC2.Action {
icon.name: "dialog-ok-apply"
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Apply Now")
onTriggered: appearanceRoot.saveConfig()
}
}
}
}
}

View File

@@ -0,0 +1,35 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.kquickcontrols
import org.kde.kirigami as Kirigami
import org.kde.plasma.plasmoid
import org.kde.kcmutils as KCM
KCM.SimpleKCM {
id: root
title: i18nc("@title:window for configuration page", "Shortcuts")
property bool unsavedChanges: false
function saveConfig() {
Plasmoid.globalShortcut = button.keySequence
unsavedChanges = false
}
Kirigami.FormLayout {
KeySequenceItem {
id: button
Kirigami.FormData.label: i18nc("@action:button set keyboard shortcut for", "Activate widget as if clicked:")
keySequence: Plasmoid.globalShortcut
patterns: ShortcutPattern.Modifier | ShortcutPattern.ModifierAndKey
onKeySequenceModified: root.unsavedChanges = keySequence !== Plasmoid.globalShortcut
}
}
}

View File

@@ -0,0 +1,38 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.plasma.configuration
AppletConfiguration {
id: root
isContainment: true
Layout.minimumWidth: Kirigami.Units.gridUnit * 35
Layout.minimumHeight: Kirigami.Units.gridUnit * 30
Layout.preferredWidth: Kirigami.Units.gridUnit * 32
Layout.preferredHeight: Kirigami.Units.gridUnit * 36
//BEGIN model
globalConfigModel: globalContainmentConfigModel
ConfigModel {
id: globalContainmentConfigModel
ConfigCategory {
name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group for configuration dialog page", "Wallpaper")
icon: "preferences-desktop-wallpaper"
source: "ConfigurationContainmentAppearance.qml"
}
ConfigCategory {
name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group for configuration dialog page", "Mouse Actions")
icon: "preferences-desktop-mouse"
source: "ConfigurationContainmentActions.qml"
}
}
//END model
}

View File

@@ -0,0 +1,57 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
QQC2.Button {
id: mouseInputButton
property string defaultText: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button inactive state for button recording mouse input" ,"Add Action")
text: defaultText
checkable: true
property string eventString
onCheckedChanged: {
if (checked) {
text = i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button active state for button recording mouse input", "Input Here");
mouseInputArea.enabled = true;
}
}
MouseArea {
id: mouseInputArea
anchors.fill: parent
acceptedButtons: Qt.AllButtons
enabled: false
onClicked: mouse => {
var newEventString = configDialog.currentContainmentActionsModel.mouseEventString(mouse.button, mouse.modifiers);
if (mouseInputButton.eventString === newEventString || !configDialog.currentContainmentActionsModel.isTriggerUsed(newEventString)) {
if (mouseInputButton.eventString === newEventString) {
// fire changed signal so deleted button can return if needed
mouseInputButton.eventStringChanged()
} else {
mouseInputButton.eventString = newEventString;
}
mouseInputButton.text = mouseInputButton.defaultText;
mouseInputButton.checked = false;
enabled = false;
}
}
onWheel: wheel => {
var newEventString = configDialog.currentContainmentActionsModel.wheelEventString(wheel);
if (mouseInputButton.eventString === newEventString || !configDialog.currentContainmentActionsModel.isTriggerUsed(newEventString)) {
mouseInputButton.eventString = newEventString;
mouseInputButton.text = mouseInputButton.defaultText;
mouseInputButton.checked = false;
enabled = false;
}
}
}
}

View File

@@ -0,0 +1,762 @@
/*
SPDX-FileCopyrightText: 2023 Niccolò Venerandi <niccolo.venerandi@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.extras as PlasmaExtras
import org.kde.kirigami as Kirigami
import org.kde.plasma.components as PC3
import org.kde.plasma.shell.panel as Panel
import org.kde.kquickcontrols
import "panelconfiguration"
ColumnLayout {
id: dialogRoot
spacing: Kirigami.Units.largeSpacing * 2
LayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
signal closeContextMenu
required property QtObject panelConfiguration
property bool vertical: (panel.location === PlasmaCore.Types.LeftEdge || panel.location === PlasmaCore.Types.RightEdge)
readonly property int headingLevel: 2
property Item panelRuler: Ruler {
id: ruler
prefix: {
switch (panel.location) {
case PlasmaCore.Types.TopEdge:
return "north"
case PlasmaCore.Types.LeftEdge:
return "west"
case PlasmaCore.Types.RightEdge:
return "east"
case PlasmaCore.Types.BottomEdge:
default:
return "south"
}
}
Item {
activeFocusOnTab: true
onActiveFocusChanged: {
if (activeFocus && dialogRoot.Window.window && dialogRoot.Window.window.visible) {
dialogRoot.Window.window.requestActivate()
}
}
}
// This item is used to "pass" focus to the main window when we're at the last of the control of the ruler
Item {
parent: dialogRoot.parent // Used to not take space in the ColumnLayout
activeFocusOnTab: true
onActiveFocusChanged: {
let window = dialogRoot.Window.window
if (activeFocus && window && window.visible) {
window.requestActivate()
}
}
}
}
Connections {
target: panel
function onOffsetChanged() {
ruler.offset = panel.offset
}
function onMinimumLengthChanged() {
ruler.minimumLength = panel.minimumLength
}
function onMaximumLengthChanged() {
ruler.maximumLength = panel.maximumLength
}
}
Component.onCompleted: {
if (panel.lengthMode === Panel.Global.Custom) {
Qt.callLater(()=> {
panelConfiguration.panelRulerView.visible = true
})
}
}
PlasmaExtras.PlasmoidHeading {
RowLayout {
anchors.fill: parent
spacing: Kirigami.Units.largeSpacing
Kirigami.Heading {
Layout.leftMargin: Kirigami.Units.smallSpacing
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:window", "Panel Settings")
textFormat: Text.PlainText
}
Item { Layout.fillWidth: true }
PC3.ToolButton {
id: addNewButton
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button label of button to open a menu that lets you add widgets or spacers", "Add New")
icon.name: "list-add-symbolic"
down: addMenu.opened
Accessible.role: Accessible.ButtonMenu
onClicked: {
if (addMenu.visible) {
addMenu.dismiss();
} else {
addMenu.open();
}
}
PC3.Menu {
id: addMenu
y: addNewButton.height
PC3.MenuItem {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:menu add a spacer", "Spacer")
icon.name: "distribute-horizontal-x"
onClicked: configDialog.addPanelSpacer()
}
PC3.MenuItem {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:menu add a widget", "Widgets…")
icon.name: "view-group-symbolic"
onClicked: {
configDialog.close()
configDialog.showAddWidgetDialog()
}
}
}
}
}
}
GridLayout {
Layout.leftMargin: columnSpacing
Layout.rightMargin: columnSpacing
Layout.alignment: Qt.AlignHCenter
Layout.minimumWidth: (positionRepresentation.implicitWidth + columnSpacing) * columns + columnSpacing
rowSpacing: dialogRoot.spacing
columnSpacing: Kirigami.Units.smallSpacing
rows: 2
columns: 3
uniformCellWidths: true
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Kirigami.Units.mediumSpacing
Kirigami.Heading {
Layout.alignment: Qt.AlignHCenter
level: dialogRoot.headingLevel
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group", "Position")
textFormat: Text.PlainText
}
PanelRepresentation {
id: positionRepresentation
text: (panel.location === PlasmaCore.Types.TopEdge ? i18ndc("plasma_shell_org.kde.plasma.desktop", "@label panel screen edge", "Top") :
panel.location === PlasmaCore.Types.RightEdge ? i18ndc("plasma_shell_org.kde.plasma.desktop", "@label panel screen edge", "Right") :
panel.location === PlasmaCore.Types.LeftEdge ? i18ndc("plasma_shell_org.kde.plasma.desktop", "@label panel screen edge", "Left") :
i18ndc("plasma_shell_org.kde.plasma.desktop", "@label panel screen edge", "Bottom"))
Layout.alignment: Qt.AlignHCenter
alignment: (panel.location === PlasmaCore.Types.TopEdge ? Qt.AlignHCenter | Qt.AlignTop :
panel.location === PlasmaCore.Types.RightEdge ? Qt.AlignVCenter | Qt.AlignRight :
panel.location === PlasmaCore.Types.LeftEdge ? Qt.AlignVCenter | Qt.AlignLeft :
Qt.AlignHCenter | Qt.AlignBottom)
isVertical: dialogRoot.vertical
mainIconSource: (panel.location === PlasmaCore.Types.TopEdge ? "arrow-up" :
panel.location === PlasmaCore.Types.RightEdge ? "arrow-right" :
panel.location === PlasmaCore.Types.LeftEdge ? "arrow-left": "arrow-down")
onClicked: {
setPositionButton.checked = !setPositionButton.checked
setPositionButton.forceActiveFocus()
}
}
PC3.Button {
id: setPositionButton
Layout.minimumHeight: transparencyBox.height
Layout.minimumWidth: positionRepresentation.width
Layout.alignment: Qt.AlignHCenter
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "action:button", "Set Position…")
checkable: true
function moveTo(newLocation: int, associatedWindow = null) {
if (!setPositionButton.checked) {
return;
}
panel.location = newLocation;
if (associatedWindow !== null) {
panel.screenToFollow = dialogRoot.panelConfiguration.screenFromWindow(associatedWindow);
}
setPositionButton.checked = false;
}
Keys.onLeftPressed: moveTo(PlasmaCore.Types.LeftEdge)
Keys.onRightPressed: moveTo(PlasmaCore.Types.RightEdge)
Keys.onUpPressed: moveTo(PlasmaCore.Types.TopEdge)
Keys.onDownPressed: moveTo(PlasmaCore.Types.BottomEdge)
}
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Kirigami.Units.mediumSpacing
Kirigami.Heading {
Layout.alignment: Qt.AlignHCenter
level: dialogRoot.headingLevel
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group", "Alignment")
textFormat: Text.PlainText
}
PanelRepresentation {
id: alignmentRepresentation
Layout.alignment: Qt.AlignHCenter
mainIconSource: {
if (dialogRoot.vertical) {
if (alignmentBox.previewIndex === 0) {
return "align-vertical-top"
} else if (alignmentBox.previewIndex === 1) {
return "align-vertical-center"
} else {
return "align-vertical-bottom"
}
} else {
if (alignmentBox.previewIndex === 0) {
return "align-horizontal-left"
} else if (alignmentBox.previewIndex === 1) {
return "align-horizontal-center"
} else {
return "align-horizontal-right"
}
}
}
alignment: {
let first, second;
if (dialogRoot.vertical) {
if (alignmentBox.previewIndex === 0) {
first = Qt.AlignTop
} else if (alignmentBox.previewIndex === 1) {
first = Qt.AlignVCenter
} else {
first = Qt.AlignBottom
}
if (panel.location === PlasmaCore.Types.LeftEdge) {
second = Qt.AlignLeft
} else {
second = Qt.AlignRight
}
} else {
if (alignmentBox.previewIndex === 0) {
first = Qt.AlignLeft
} else if (alignmentBox.previewIndex === 1) {
first = Qt.AlignHCenter
} else {
first = Qt.AlignRight
}
if (panel.location === PlasmaCore.Types.TopEdge) {
second = Qt.AlignTop
} else {
second = Qt.AlignBottom
}
}
return first | second;
}
onClicked: alignmentBox.popup.visible = true
isVertical: dialogRoot.vertical
}
PC3.ComboBox {
id: alignmentBox
property int previewIndex: highlightedIndex > -1 ? highlightedIndex : currentIndex
Layout.alignment: Qt.AlignHCenter
Layout.minimumWidth: alignmentRepresentation.width
model: [
dialogRoot.vertical ? i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Top") : i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Left"),
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Center"),
dialogRoot.vertical ? i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Bottom") : i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Right")
]
currentIndex: (panel.alignment === Qt.AlignLeft ? 0 :
panel.alignment === Qt.AlignCenter ? 1 : 2)
onActivated: (index) => {
if (index === 0) {
panel.alignment = Qt.AlignLeft
} else if (index === 1) {
panel.alignment = Qt.AlignCenter
} else {
panel.alignment = Qt.AlignRight
}
}
}
PC3.ComboBox {
model: [alignmentBox.model.reduce((a, b) => a.length > b.length ? a : b)]
Component.onCompleted: {
parent.Layout.minimumWidth = implicitWidth
destroy()
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Kirigami.Units.mediumSpacing
Kirigami.Heading {
level: dialogRoot.headingLevel
Layout.alignment: Qt.AlignHCenter
text: dialogRoot.vertical ? i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group panel height", "Height")
: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group panel width", "Width")
textFormat: Text.PlainText
}
PanelRepresentation {
id: lengthRepresentation
Layout.alignment: Qt.AlignHCenter
mainIconSource: (widthBox.previewIndex === 1 ? "gnumeric-ungroup" :
widthBox.previewIndex === 0 ? (dialogRoot.vertical ? "panel-fit-height" : "panel-fit-width") : "kdenlive-custom-effect")
isVertical: dialogRoot.vertical
alignment: positionRepresentation.alignment
fillAvailable: widthBox.previewIndex === 0
onClicked: widthBox.popup.visible = true
}
PC3.ComboBox {
id: widthBox
property int previewIndex: highlightedIndex > -1 ? highlightedIndex : currentIndex
Layout.alignment: Qt.AlignHCenter
Layout.minimumWidth: lengthRepresentation.width
model: [
dialogRoot.vertical ? i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox panel fills the full height of the display", "Fill height") : i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox panel fills the full width of the display", "Fill width"),
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox panel is just big enough to fit its content", "Fit content"),
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox panel size", "Custom")
]
currentIndex: (panel.lengthMode === Panel.Global.FillAvailable ? 0 :
panel.lengthMode === Panel.Global.FitContent ? 1 : 2)
onActivated: (index) => {
if (index === 0) {
panel.lengthMode = Panel.Global.FillAvailable
panelConfiguration.panelRulerView.visible = false
} else if (index === 1) {
panel.lengthMode = Panel.Global.FitContent
panelConfiguration.panelRulerView.visible = false
} else {
panel.lengthMode = Panel.Global.Custom
panelConfiguration.panelRulerView.visible = true
}
}
}
PC3.ComboBox {
model: [widthBox.model.reduce((a, b) => a.length > b.length ? a : b)]
Component.onCompleted: {
parent.Layout.minimumWidth = implicitWidth
destroy()
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Kirigami.Units.mediumSpacing
Kirigami.Heading {
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
level: dialogRoot.headingLevel
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group", "Visibility")
textFormat: Text.PlainText
}
PanelRepresentation {
id: visibilityRepresentation
Layout.alignment: Qt.AlignHCenter
sunkenPanel: autoHideBox.previewIndex == 1 || autoHideBox.previewIndex == 2
onClicked: autoHideBox.popup.visible = true
windowVisible: true
panelReservesSpace: autoHideBox.previewIndex == 0
isVertical: dialogRoot.vertical
alignment: positionRepresentation.alignment
}
PC3.ComboBox {
id: autoHideBox
property int previewIndex: popup.visible ? highlightedIndex : currentIndex
property int animationIndex: popup.visible ? highlightedIndex : -1
model: [
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Always visible"),
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Auto hide"),
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Dodge windows"),
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Windows go below"),
]
onAnimationIndexChanged: {
if (animationIndex == 0 || animationIndex == 3) {
visibilityRepresentation.maximizeWindow()
} else if (animationIndex == 1) {
visibilityRepresentation.hidePanel()
} else if (animationIndex == 2) {
visibilityRepresentation.dodgePanel()
}
}
Layout.alignment: Qt.AlignHCenter
Layout.minimumWidth: visibilityRepresentation.width
currentIndex: {
switch (panel.visibilityMode) {
case Panel.Global.AutoHide:
return 1;
case Panel.Global.DodgeWindows:
return 2;
case Panel.Global.WindowsGoBelow:
return 3;
case Panel.Global.NormalPanel:
default:
return 0;
}
}
onActivated: (index) => {
switch (index) {
case 1:
panel.visibilityMode = Panel.Global.AutoHide;
break;
case 2:
panel.visibilityMode = Panel.Global.DodgeWindows;
break;
case 3:
panel.visibilityMode = Panel.Global.WindowsGoBelow;
break;
case 0:
default:
panel.visibilityMode = Panel.Global.NormalPanel;
break;
}
}
}
PC3.ComboBox {
model: [autoHideBox.model.reduce((a, b) => a.length > b.length ? a : b)]
Component.onCompleted: {
parent.Layout.minimumWidth = implicitWidth
destroy()
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Kirigami.Units.mediumSpacing
Kirigami.Heading {
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
level: dialogRoot.headingLevel
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group", "Opacity")
textFormat: Text.PlainText
}
PanelRepresentation {
id: opacityRepresentation
Layout.alignment: Qt.AlignHCenter
adaptivePanel: transparencyBox.previewIndex === 0
translucentPanel: transparencyBox.previewIndex === 2
onClicked: transparencyBox.popup.visible = true
isVertical: dialogRoot.vertical
alignment: positionRepresentation.alignment
}
PC3.ComboBox {
id: transparencyBox
readonly property int previewIndex: popup.visible ? highlightedIndex : currentIndex
model: [
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Adaptive"),
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Opaque"),
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox", "Translucent")
]
Layout.alignment: Qt.AlignHCenter
Layout.minimumWidth: opacityRepresentation.width
currentIndex: (panel.opacityMode === Panel.Global.Adaptive ? 0 :
panel.opacityMode === Panel.Global.Opaque ? 1 : 2)
onActivated: (index) => {
if (index === 0) {
panel.opacityMode = Panel.Global.Adaptive
} else if (index === 1) {
panel.opacityMode = Panel.Global.Opaque
} else {
panel.opacityMode = Panel.Global.Translucent
}
}
}
PC3.ComboBox {
model: [transparencyBox.model.reduce((a, b) => a.length > b.length ? a : b)]
Component.onCompleted: {
parent.Layout.minimumWidth = implicitWidth
destroy()
}
}
}
ColumnLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Kirigami.Units.mediumSpacing
Kirigami.Heading {
level: dialogRoot.headingLevel
Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
text: i18ndc("plasma_shell_org.kde.plasma.desktop","@title:group Degree of floatingness of panel and applets", "Floating")
textFormat: Text.PlainText
}
PanelRepresentation {
Layout.alignment: Qt.AlignHCenter
floatingGap: Kirigami.Units.smallSpacing * (floatingBox.previewIndex === 2)
onClicked: floatingBox.popup.visible = true
visibleApplet: true
floatingApplet: floatingBox.previewIndex !== 0
isVertical: dialogRoot.vertical
alignment: positionRepresentation.alignment
}
PC3.ComboBox {
id: floatingBox
readonly property int previewIndex: popup.visible ? highlightedIndex : currentIndex
model: [
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox Option to disable floating panels or applets", "Disabled"),
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox Option to make only panel applets always float", "Applets only"),
i18ndc("plasma_shell_org.kde.plasma.desktop", "@item:inlistbox Option to make panel and applets floating", "Panel and applets")
]
Layout.alignment: Qt.AlignHCenter
Layout.minimumHeight: transparencyBox.height
Layout.minimumWidth: opacityRepresentation.width
currentIndex: (panel.floating ? 2 : panel.floatingApplets ? 1 : 0)
onActivated: (index) => {
if (index === 0) {
panel.floating = panel.floatingApplets = false
} else if (index === 1) {
panel.floating = false
panel.floatingApplets = true
} else {
panel.floating = true
}
}
}
}
RowLayout {
Layout.fillWidth: true
Layout.columnSpan: 3
spacing: Kirigami.Units.smallSpacing
visible: panel.unsupportedConfiguration
Kirigami.Icon {
source: "data-warning-symbolic"
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
}
PC3.Label {
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
// Popup doesn't have to expand to the implicit size of this label, let it wrap.
Layout.preferredWidth: 0
text: panel.unsupportedConfigurationDescription
wrapMode: Text.Wrap
}
PC3.ToolButton {
Layout.alignment: Qt.AlignVCenter
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button Revert an unsupported configuration back to the defaults", "Fix it")
icon.name: "tools-wizard-symbolic"
onClicked: {
panel.fixUnsupportedConfiguration();
}
}
}
}
Instantiator {
id: dialogInstantiator
active: setPositionButton.checked || clonePanelButton.checked
asynchronous: true
model: Application.screens
Item {
width: 0
height: 0
required property var modelData
component Indicator : PlasmaCore.Dialog {
id: root
property string iconSource
property var onClickedLocation
flags: Qt.WindowStaysOnTopHint | Qt.WindowDoesNotAcceptFocus | Qt.BypassWindowManagerHint
location: PlasmaCore.Types.Floating
visible: dialogInstantiator.active && (panel.location !== onClickedLocation || modelData.name !== panel.screenToFollow.name)
x: modelData.virtualX + Kirigami.Units.largeSpacing
y: modelData.virtualY + modelData.height / 2 - mainItem.height / 2 - margins.top
mainItem: PC3.ToolButton {
width: Kirigami.Units.iconSizes.enormous
height: Kirigami.Units.iconSizes.enormous
icon.name: root.iconSource
onClicked: {
if (setPositionButton.checked) {
setPositionButton.moveTo(root.onClickedLocation, Window.window)
} else if (clonePanelButton.checked) {
panel.clonePanelTo(root.onClickedLocation, dialogRoot.panelConfiguration.screenFromWindow(Window.window))
clonePanelButton.checked = false
}
}
}
}
Indicator {
x: modelData.virtualX + Kirigami.Units.largeSpacing
y: modelData.virtualY + modelData.height / 2 - mainItem.height / 2 - margins.top
iconSource: "arrow-left"
onClickedLocation: PlasmaCore.Types.LeftEdge
}
Indicator {
x: modelData.virtualX + modelData.width - Kirigami.Units.largeSpacing - margins.left - margins.right - mainItem.width
y: modelData.virtualY + modelData.height / 2 - mainItem.height / 2 - margins.top
iconSource: "arrow-right"
onClickedLocation: PlasmaCore.Types.RightEdge
}
Indicator {
x: modelData.virtualX + modelData.width / 2 - mainItem.width / 2 - margins.left
y: modelData.virtualY + Kirigami.Units.largeSpacing
iconSource: "arrow-up"
onClickedLocation: PlasmaCore.Types.TopEdge
}
Indicator {
x: modelData.virtualX + modelData.width / 2 - mainItem.width / 2 - margins.left
y: modelData.virtualY + modelData.height - mainItem.height - margins.top - margins.bottom - Kirigami.Units.largeSpacing
iconSource: "arrow-down"
onClickedLocation: PlasmaCore.Types.BottomEdge
}
}
}
GridLayout {
Layout.alignment: Qt.AlignHCenter
rowSpacing: Kirigami.Units.largeSpacing
columnSpacing: Kirigami.Units.largeSpacing
rows: 2
columns: 2
PC3.Label {
id: spinBoxLabel
Layout.alignment: Qt.AlignRight
wrapMode: Text.Wrap
text: panel.location === PlasmaCore.Types.LeftEdge || panel.location === PlasmaCore.Types.RightEdge
? i18ndc("plasma_shell_org.kde.plasma.desktop", "@label:spinbox", "Panel Width:")
: i18ndc("plasma_shell_org.kde.plasma.desktop", "@label:spinbox", "Panel Height:")
textFormat: Text.PlainText
}
PC3.SpinBox {
id: spinBox
editable: true
focus: !Kirigami.InputMethod.willShowOnActive
from: Math.max(20, panel.minThickness) // below this size, the panel is mostly unusable
to: panel.location === PlasmaCore.Types.LeftEdge || panel.location === PlasmaCore.Types.RightEdge
? panel.screenToFollow.geometry.width / 2
: panel.screenToFollow.geometry.height / 2
stepSize: 2
value: panel.thickness
onValueModified: {
panel.thickness = value
}
}
PC3.Label {
Layout.alignment: Qt.AlignRight
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group shortcut that moves focus to the panel", "Focus shortcut:")
textFormat: Text.PlainText
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
}
PC3.ToolTip {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:tooltip", "Press this keyboard shortcut to move focus to the Panel")
visible: mouseArea.containsMouse
}
}
KeySequenceItem {
id: button
Accessible.name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button accessible name", "Focus Shortcut Setter")
Accessible.description: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:whatsthis Accessible description for button", "Button to set the shortcut for the panel to gain focus")
Accessible.onPressAction: startCapturing()
keySequence: plasmoid.globalShortcut
onCaptureFinished: {
plasmoid.globalShortcut = button.keySequence
}
}
}
PlasmaExtras.PlasmoidHeading {
position: PlasmaExtras.PlasmoidHeading.Footer
Layout.topMargin: Kirigami.Units.smallSpacing
topPadding: Kirigami.Units.smallSpacing * 2
leftPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
Layout.fillWidth: true
RowLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
spacing: Kirigami.Units.largeSpacing
PC3.ToolButton {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button Delete the panel", "Delete Panel")
icon.name: "delete"
PC3.ToolTip.text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:tooltip for button", "Remove this panel; this action can be undone")
PC3.ToolTip.delay: Kirigami.Units.toolTipDelay
PC3.ToolTip.visible: hovered
onClicked: plasmoid.internalAction("remove").trigger()
}
PC3.ToolButton {
id: clonePanelButton
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button Clone the panel", "Clone Panel")
icon.name: "edit-copy-symbolic"
checkable: true
PC3.ToolTip.text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:tooltip for button", "Create a new panel with the same settings and applets")
PC3.ToolTip.delay: Kirigami.Units.toolTipDelay
PC3.ToolTip.visible: hovered
}
Item {Layout.fillWidth: true}
PC3.ToolButton {
text: plasmoid.corona.enteredEditModeViaDesktop() ? i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button Close the panel configuration window", "Close") : i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button Close the panel configuration window and exit edit mode", "Exit Edit Mode")
icon.name: "dialog-ok-symbolic"
PC3.ToolTip.text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:tooltip for button", "Close Panel Settings window and exit Edit Mode")
PC3.ToolTip.delay: Kirigami.Units.toolTipDelay
PC3.ToolTip.visible: hovered
onClicked: plasmoid.internalAction("configure").trigger()
}
}
}
// This item is used to "pass" focus to the ruler with tab when we're at the last of the control of this window
Item {
parent: dialogRoot.parent // Used to not take space in the ColumnLayout
activeFocusOnTab: true
onActiveFocusChanged: {
let window = ruler.Window.window
if (activeFocus && window && window.visible) {
window.requestActivate()
}
}
}
}

View File

@@ -0,0 +1,85 @@
/*
SPDX-FileCopyrightText: 2021 Cyril Rossi <cyril.rossi@enioka.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Window
import org.kde.kirigami as Kirigami
import "shellcontainmentconfiguration"
Kirigami.AbstractApplicationWindow {
id: root
title: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:window", "Manage Panels and Desktops")
width: Kirigami.Units.gridUnit * 40
height: Kirigami.Units.gridUnit * 32
minimumWidth: Kirigami.Units.gridUnit * 30
minimumHeight: Kirigami.Units.gridUnit * 25
header: QQC2.ToolBar {
anchors {
left: parent.left
right: parent.right
}
contentItem: QQC2.Label {
Layout.fillWidth: parent
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:usagetip", "You can drag Panels and Desktops around to move them to different screens.")
textFormat: Text.PlainText
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
}
footer: QQC2.Control {
contentItem: QQC2.DialogButtonBox {
QQC2.Button {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button Close window", "Close")
onClicked: Window.window.close()
}
}
background: Item {
// FIXME: automate that somehow?
Kirigami.Separator {
anchors {
left: parent.left
top: parent.top
right: parent.right
}
visible: mainPage.flickable.contentHeight > mainPage.flickable.height
}
}
}
Kirigami.ScrollablePage {
id: mainPage
anchors.fill: parent
leftPadding: 0
topPadding: 0
rightPadding: 0
bottomPadding: 0
Flow {
id: mainGrid
width: mainPage.flickable.width
spacing: 0
Repeater {
id: repeater
model: ShellContainmentModel
delegate: Delegate {
viewPort: mainPage
}
}
}
}
}

View File

@@ -0,0 +1,351 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import org.kde.plasma.components as PC3
import org.kde.plasma.extras as PlasmaExtras
import org.kde.kirigami as Kirigami
Item {
id: root
property string text
property /*Qt::Alignment*/int alignment: Qt.AlignHCenter | Qt.AlignBottom
property string tooltip
property bool isVertical: false
property bool isHorizontal: !isVertical
property bool checked: false
property bool windowVisible: false
property bool panelVisible: true
property bool isTop: !!(alignment & Qt.AlignTop)
property bool isBottom: !!(alignment & Qt.AlignBottom)
property bool isLeft: !!(alignment & Qt.AlignLeft)
property bool isRight: !!(alignment & Qt.AlignRight)
property bool translucentPanel: false
property bool sunkenPanel: false
property bool adaptivePanel: false
property bool panelReservesSpace: true
property bool fillAvailable: false
property int floatingGap: 0
property var mainIconSource: null
property int screenHeight: Math.round(screenRect.height / 2)
property bool visibleApplet: false
property bool floatingApplet: false
readonly property bool iconAndLabelsShouldlookSelected: checked || mouseArea.pressed
// Layouts automatically mirror on RTL languages, and we use
// layouts to position the panel here. As a result, RTL languages
// show the panel on the opposite side, which does not match
// where the panel actually is. We instead disable layout
// mirroring to avoid that.
LayoutMirroring.enabled: false
LayoutMirroring.childrenInherit: true
function maximizeWindow() {
hidePanelLater.stop()
hidePanel.stop()
showPanel.restart()
moveWindowOverPanel.stop()
resetWindowOverPanel.restart()
maximizeAnimation.restart()
}
function hidePanel() {
hidePanelLater.stop()
maximizeAnimation.stop()
resetMaximize.restart()
moveWindowOverPanel.stop()
resetWindowOverPanel.restart()
hidePanel.restart()
}
function dodgePanel() {
hidePanel.stop()
showPanel.restart()
maximizeAnimation.stop()
resetMaximize.restart()
moveWindowOverPanel.restart()
hidePanelLater.restart()
}
Timer {
id: hidePanelLater
interval: 200
onTriggered: hidePanel.restart()
running: false
}
signal clicked()
implicitHeight: mainItem.height
implicitWidth: mainItem.width
PC3.ToolTip {
text: root.tooltip
visible: mouseArea.containsMouse && text.length > 0
}
PlasmaExtras.Highlight {
anchors.fill: parent
anchors.margins: -Kirigami.Units.smallSpacing
hovered: mouseArea.containsMouse
pressed: root.iconAndLabelsShouldlookSelected
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: root.clicked()
}
ColumnLayout {
id: mainItem
spacing: Kirigami.Units.smallSpacing
Rectangle {
id: screenRect
Layout.alignment: Qt.AlignHCenter
implicitWidth: Math.round(Math.min(Kirigami.Units.gridUnit * 6, Screen.width * 0.1))
implicitHeight: Math.round(Math.min(Kirigami.Units.gridUnit * 4, Screen.width * 0.1))
color: Qt.tint(Kirigami.Theme.backgroundColor, Qt.rgba(1, 1, 1, 0.3))
border.color: Kirigami.Theme.highlightColor
radius: Kirigami.Units.cornerRadius
clip: root.sunkenPanel
RowLayout {
anchors.fill: parent
z: 1
Rectangle {
id: panelImage
property real sunkenValue: 0
Component.onCompleted: {
panelImage.sunkenValue = root.sunkenPanel / 2
}
implicitWidth: root.isVertical ? Math.round(parent.width / 6) : Math.round(parent.width * (root.fillAvailable ? 1 : 0.7))
implicitHeight: root.isVertical ? Math.round(parent.height * (root.fillAvailable ? 1 : 0.8)) : Math.round(parent.height / 4)
Layout.alignment: root.alignment
Layout.bottomMargin: root.isBottom ? sunkenValue * -Math.round(height) + root.floatingGap : 0
Layout.topMargin: root.isTop ? sunkenValue * -Math.round(height) + root.floatingGap : 0
Layout.leftMargin: root.isLeft ? sunkenValue * -Math.round(width) + root.floatingGap : 0
Layout.rightMargin: root.isRight ? sunkenValue * -Math.round(width) + root.floatingGap : 0
color: root.translucentPanel ? screenRect.color : Kirigami.Theme.backgroundColor
opacity: root.translucentPanel ? 0.8 : 1.0
border.color: "transparent"
visible: root.panelVisible
clip: root.adaptivePanel
radius: Kirigami.Units.cornerRadius
SequentialAnimation on sunkenValue {
id: hidePanel
running: false
NumberAnimation {
to: 0
duration: Kirigami.Units.shortDuration
}
NumberAnimation {
to: 1
duration: Kirigami.Units.veryLongDuration
}
PauseAnimation {
duration: Kirigami.Units.veryLongDuration * 2
}
NumberAnimation {
to: 0.5
duration: Kirigami.Units.veryLongDuration
}
}
SequentialAnimation on sunkenValue {
id: showPanel
running: false
NumberAnimation {
to: 0
duration: Kirigami.Units.shortDuration
}
}
Loader {
id: horizontalAdaptivePanelLoader
active: root.adaptivePanel && root.isHorizontal
sourceComponent: Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Math.round(panelImage.width / 3)
color: Qt.lighter(screenRect.color)
border.color: Kirigami.Theme.highlightColor
width: panelImage.width
height: Math.round(panelImage.height * 4)
radius: Math.round(height / 2)
rotation: 45
}
}
Loader {
id: verticalAdaptivePanelLoader
active: root.adaptivePanel && root.isVertical
sourceComponent: Rectangle {
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: Math.round(panelImage.height / 4)
color: Qt.lighter(screenRect.color)
border.color: Kirigami.Theme.highlightColor
width: Math.round(panelImage.width * 2)
height: panelImage.height
radius: Math.round(height / 2)
rotation: 45
}
}
Rectangle {
id: panelBorder
anchors.fill: parent
color: "transparent"
border.color: Kirigami.Theme.highlightColor
radius: panelImage.radius
}
}
}
Rectangle {
x: panelImage.x + root.isLeft * panelImage.width - root.isRight * width + root.isVertical * panelSpacing
y: panelImage.y + root.isTop * panelImage.height - root.isBottom * height + (root.isHorizontal) * panelSpacing
height: Math.round(parent.height / 2)
width: Math.round(parent.width / 3)
visible: root.visibleApplet
property int panelSpacing: Kirigami.Units.smallSpacing * (root.floatingApplet ? -1 : 1) * (root.isRight || root.isBottom ? 1 : -1)
color: window.color
radius: 5
Behavior on y {
NumberAnimation {
duration: Kirigami.Units.shortDuration
}
}
Behavior on x {
NumberAnimation {
duration: Kirigami.Units.shortDuration
}
}
}
Rectangle {
id: window
property real maximized: 0
property real windowOverPanel: 0
width: Math.round(parent.width * (0.4 + 0.6 * maximized) - panelImage.width * root.panelReservesSpace * maximized * root.isVertical)
height: Math.round(parent.height * (0.4 + 0.6 * maximized) - panelImage.height * root.panelReservesSpace * maximized * root.isHorizontal)
visible: root.windowVisible
radius: 5
color: Kirigami.Theme.highlightColor
border.color: "transparent"
x: Math.round(screenRect.width / 2 - width / 2) * (1 - maximized) + windowOverPanel * Kirigami.Units.mediumSpacing * 2 * root.isVertical * (root.isLeft ? - 1 : 1) + panelImage.width * maximized * root.isLeft * root.panelReservesSpace
y: Math.round(screenRect.height / 2 - height / 2) * (1 - maximized) + windowOverPanel * Kirigami.Units.mediumSpacing * 2 * root.isHorizontal * (root.isTop ? - 1 : 1) + panelImage.height * maximized * root.isTop * root.panelReservesSpace
z: 0
SequentialAnimation on maximized {
id: maximizeAnimation
running: false
NumberAnimation {
to: 0
duration: Kirigami.Units.shortDuration
}
NumberAnimation {
to: 1
duration: Kirigami.Units.longDuration
}
PauseAnimation {
duration: Kirigami.Units.veryLongDuration * 2
}
NumberAnimation {
to: 0
duration: Kirigami.Units.longDuration
}
}
NumberAnimation on maximized {
id: resetMaximize
running: false
to: 0
duration: Kirigami.Units.shortDuration
}
SequentialAnimation on windowOverPanel {
id: moveWindowOverPanel
NumberAnimation {
to: 0
duration: Kirigami.Units.shortDuration
}
NumberAnimation {
to: 1
duration: Kirigami.Units.veryLongDuration
}
PauseAnimation {
duration: Kirigami.Units.veryLongDuration * 2
}
NumberAnimation {
to: 0
duration: Kirigami.Units.veryLongDuration
}
}
NumberAnimation on windowOverPanel {
id: resetWindowOverPanel
running: false
to: 0
duration: Kirigami.Units.shortDuration
}
Row {
anchors.top: parent.top
anchors.right: parent.right
anchors.margins: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Repeater {
model: 3
delegate: Rectangle {
width: Math.round(Kirigami.Units.gridUnit / 6)
height: width
radius: Math.round(height / 2)
color: Kirigami.Theme.textColor
}
}
}
}
Kirigami.Icon {
id: mainIcon
visible: valid
anchors.centerIn: parent
transform: Translate {
y: root.isVertical ? 0 : Math.round((mainIcon.y - panelImage.y) / 4)
x: root.isVertical ? Math.round((mainIcon.x - panelImage.x) / 4) : 0
}
height: parent.height / 2
source: root.mainIconSource
}
}
}
}

View File

@@ -0,0 +1,243 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.ksvg as KSvg
import org.kde.plasma.shell.panel as Panel
KSvg.FrameSvgItem {
id: root
anchors.fill: parent
//Those properties get updated by PanelConfiguration.qml whenever a value in the panel changes
property alias offset: offsetHandle.value
property alias minimumLength: rightMinimumLengthHandle.value
property alias maximumLength: rightMaximumLengthHandle.value
property bool isHorizontal: root.prefix[0] === 'north' || root.prefix[0] === 'south'
property string maximumText: (
dialogRoot.vertical
? i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:tooltip slider handle", "Drag to change maximum height.")
: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:tooltip slider handle", "Drag to change maximum width.")
) + "\n" + i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:tooltip slider handle", "Double-click to reset.")
property string minimumText: (
dialogRoot.vertical
? i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:tooltip slider handle", "Drag to change minimum height.")
: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:tooltip slider handle", "Drag to change minimum width.")
) + "\n" + i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:tooltip slider handle", "Double-click to reset.")
imagePath: "widgets/containment-controls"
implicitWidth: Math.max(offsetHandle.width, rightMinimumLengthHandle.width + rightMaximumLengthHandle.width)
implicitHeight: Math.max(offsetHandle.height, rightMinimumLengthHandle.height + rightMaximumLengthHandle.height)
onMinimumLengthChanged: rightMinimumLengthHandle.value = leftMinimumLengthHandle.value = minimumLength
onMaximumLengthChanged: rightMaximumLengthHandle.value = leftMaximumLengthHandle.value = maximumLength
/* As offset and length have a different meaning in all alignments, the panel shifts on alignment change.
* This could result in wrong panel positions (e.g. panel shifted over monitor border).
* The fancy version would be a recalculation of all values, so that the panel stays at it's current position,
* but this would be error prone and complicated. As the panel alignment is rarely changed, it's not worth it.
* The more easy approach is just setting the panel offset to zero. This makes sure the panel has a valid position and size.
*/
Connections {
target: panel
function onAlignmentChanged() {
offset = 0
}
}
Component.onCompleted: {
offsetHandle.value = panel.offset
rightMinimumLengthHandle.value = panel.minimumLength
rightMaximumLengthHandle.value = panel.maximumLength
leftMinimumLengthHandle.value = panel.minimumLength
leftMaximumLengthHandle.value = panel.maximumLength
}
KSvg.SvgItem {
id: centerMark
imagePath: "widgets/containment-controls"
elementId: dialogRoot.vertical ? "vertical-centerindicator" : "horizontal-centerindicator"
visible: panel.alignment === Qt.AlignCenter
width: dialogRoot.vertical ? parent.width : naturalSize.width
height: dialogRoot.vertical ? naturalSize.height : parent.height
anchors.centerIn: parent
}
SliderHandle {
id: offsetHandle
anchors {
right: !root.isHorizontal ? root.right : undefined
bottom: root.isHorizontal ? root.bottom : undefined
}
graphicElementName: "offsetslider"
description: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:tooltip slider handle",
"Drag to change position on this screen edge.\nDouble-click to reset.")
offset: panel.alignment === Qt.AlignCenter ? 0 : (dialogRoot.vertical ? panel.height : panel.width) / 2
property int position: (dialogRoot.vertical) ? y + height / 2 : x + width / 2
onPositionChanged: {
if (!offsetHandle.hasEverBeenMoved) return;
let panelLength = dialogRoot.vertical ? panel.height : panel.width
let rootLength = dialogRoot.vertical ? root.height : root.width
// Snap at the center
if (Math.abs(position - rootLength / 2) < 5) {
if (panel.alignment !== Qt.AlignCenter) {
panel.alignment = Qt.AlignCenter
// Coordinate change: since we switch from measuring the min/max
// length from the side of the panel to the center of the panel,
// we need to double the distance between the min/max indicators
// and the panel side.
panel.minimumLength += panel.minimumLength - panelLength
panel.maximumLength += panel.maximumLength - panelLength
}
panel.offset = 0
} else if (position > rootLength / 2) {
if (panel.alignment === Qt.AlignCenter) {
// This is the opposite of the previous comment, as we are
// cutting in half the distance between the min/max indicators
// and the side of the panel.
panel.minimumLength -= (panel.minimumLength - panelLength) / 2
panel.maximumLength -= (panel.maximumLength - panelLength) / 2
}
panel.alignment = Qt.AlignRight
panel.offset = Math.round(rootLength - position - offset)
} else if (position <= rootLength / 2) {
if (panel.alignment === Qt.AlignCenter) {
panel.minimumLength -= (panel.minimumLength - panelLength) / 2
panel.maximumLength -= (panel.maximumLength - panelLength) / 2
}
panel.alignment = Qt.AlignLeft
panel.offset = Math.round(position - offset)
}
}
/* The maximum/minimumPosition values are needed to prevent the user from moving a panel with
* center alignment to the left and then drag the position handle to the left.
* This would make the panel to go off the monitor:
* |<- V -> |
* | -> | <- |
* ^move this slider to the left
*/
minimumPosition: {
var size = dialogRoot.vertical ? height : width
switch(panel.alignment){
case Qt.AlignLeft:
return -size / 2 + offset
case Qt.AlignRight:
return leftMaximumLengthHandle.value - size / 2 - offset
default:
return panel.maximumLength / 2 - size / 2
}
}
//Needed for the same reason as above
maximumPosition: {
var size = dialogRoot.vertical ? height : width
var rootSize = dialogRoot.vertical ? root.height : root.width
switch(panel.alignment){
case Qt.AlignLeft:
return rootSize - rightMaximumLengthHandle.value - size / 2 + offset
case Qt.AlignRight:
return rootSize - size / 2 - offset
default:
return rootSize - panel.maximumLength / 2 - size / 2
}
}
function defaultPosition(): int /*override*/ {
return 0;
}
}
/* The maximumPosition value for the right handles and the minimumPosition value for the left handles are
* needed to prevent the user from moving a panel with center alignment to the left (right) and then pull one of the
* right (left) sliders to the right (left).
* Because the left and right sliders are coupled, this would make the left (right) sliders to go off the monitor.
*
* |<- V -> |
* | -> | <- |
* ^move this slider to the right
*
* The other max/min Position values just set a minimum panel size
*/
SliderHandle {
id: rightMinimumLengthHandle
anchors {
left: !root.isHorizontal ? root.left : undefined
top: root.isHorizontal ? root.top : undefined
}
description: root.minimumText
alignment: panel.alignment | Qt.AlignLeft
visible: panel.alignment !== Qt.AlignRight
offset: panel.offset
graphicElementName: "minslider"
onValueChanged: panel.minimumLength = value
minimumPosition: offsetHandle.position + Kirigami.Units.gridUnit * 3
maximumPosition: {
var rootSize = dialogRoot.vertical ? root.height : root.width
var size = dialogRoot.vertical ? height : width
panel.alignment === Qt.AlignCenter ? Math.min(rootSize - size/2, rootSize + offset * 2 - size/2) : rootSize - size/2
}
}
SliderHandle {
id: rightMaximumLengthHandle
anchors {
right: !root.isHorizontal ? root.right : undefined
bottom: root.isHorizontal ? root.bottom : undefined
}
description: root.maximumText
alignment: panel.alignment | Qt.AlignLeft
visible: panel.alignment !== Qt.AlignRight
offset: panel.offset
graphicElementName: "maxslider"
onValueChanged: panel.maximumLength = value
minimumPosition: offsetHandle.position + Kirigami.Units.gridUnit * 3
maximumPosition: {
var rootSize = dialogRoot.vertical ? root.height : root.width
var size = dialogRoot.vertical ? height : width
panel.alignment === Qt.AlignCenter ? Math.min(rootSize - size/2, rootSize + offset * 2 - size/2) : rootSize - size/2
}
}
SliderHandle {
id: leftMinimumLengthHandle
anchors {
left: !root.isHorizontal ? root.left : undefined
top: root.isHorizontal ? root.top : undefined
}
description: root.minimumText
alignment: panel.alignment | Qt.AlignRight
visible: panel.alignment !== Qt.AlignLeft
offset: panel.offset
graphicElementName: "maxslider"
onValueChanged: panel.minimumLength = value
maximumPosition: offsetHandle.position - Kirigami.Units.gridUnit * 3
minimumPosition: {
var size = dialogRoot.vertical ? height : width
panel.alignment === Qt.AlignCenter ? Math.max(-size/2, offset*2 - size/2) : -size/2
}
}
SliderHandle {
id: leftMaximumLengthHandle
anchors {
right: !root.isHorizontal ? root.right : undefined
bottom: root.isHorizontal ? root.bottom : undefined
}
description: root.maximumText
alignment: panel.alignment | Qt.AlignRight
visible: panel.alignment !== Qt.AlignLeft
offset: panel.offset
graphicElementName: "minslider"
onValueChanged: panel.maximumLength = value
maximumPosition: offsetHandle.position - Kirigami.Units.gridUnit * 3
minimumPosition: {
var size = dialogRoot.vertical ? height : width
panel.alignment === Qt.AlignCenter ? Math.max(-size/2, offset*2 - size/2) : -size/2
}
}
}

View File

@@ -0,0 +1,236 @@
/*
SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.plasma.extras as PlasmaExtras
import org.kde.kirigami as Kirigami
import org.kde.ksvg as KSvg
import org.kde.plasma.components as PC3
import org.kde.plasma.shell.panel as Panel
KSvg.SvgItem {
id: root
//Those properties get updated by PanelConfiguration.qml whenever a value changes
imagePath: "widgets/containment-controls"
elementId: parent.prefix + '-' + graphicElementName
width: naturalSize.width
height: naturalSize.height
//value expressed by this slider, this is the distance to offset
property int value
//name of the graphics to load
property string graphicElementName
//where the point "0" is
property int offset: 0
/*handle type: behave in different ways based on the alignment:
* alignment === Qt.AlignRight: Panel aligned to right and handle value relative to the right
* alignment === Qt.AlignLeft: Panel aligned to left and handle relative to the left
* (alignment !== Qt.AlignRight) && (alignment & Qt.AlignRight): Panel aligned to the center and handle right of offset and value doubled
* (alignment !== Qt.AlignLeft) && (alignment & Qt.AlignLeft): Panel aligned to the center and handle left of offset and value doubled
* else: Panel aligned to center and handle relative to the center
* Note that right/left and top/bottom are interchangeable
*/
property int alignment: panel.alignment
//The maximum/minimum Position (X/Y) the silder can be moved to
property int minimumPosition
property int maximumPosition
//Provide default position for "reset" action.
function defaultPosition(): int {
var dialogSize, panelSize;
if (dialogRoot.vertical) {
dialogSize = dialogRoot.height;
panelSize = panel.height;
} else {
dialogSize = dialogRoot.width;
panelSize = panel.width;
}
return (value === panelSize) ? dialogSize : panelSize;
}
// Handle name displayed as a tooltip.
property string description
property bool hasEverBeenMoved: false
function syncPos() {
if (dialogRoot.vertical) {
let newY = 0
if (alignment === Qt.AlignRight) {
newY = root.parent.height - (value + offset + root.height/2)
} else if (alignment === Qt.AlignLeft) {
newY = value + offset - root.height/2
} else {
if (root.alignment & Qt.AlignRight) {
newY = root.parent.height/2 - value/2 + offset - root.height/2
} else if (root.alignment & Qt.AlignLeft) {
newY = root.parent.height/2 + value/2 + offset - root.height/2
} else {
newY = root.parent.height/2 + value + offset -root.height/2
}
}
y = Math.max(-height/2, Math.min(parent.height - height/2, newY))
} else {
let newX = 0
if (alignment === Qt.AlignRight) {
newX = root.parent.width - (value + offset + root.width/2)
} else if (alignment === Qt.AlignLeft) {
newX = value + offset - root.width/2
} else {
if (root.alignment & Qt.AlignRight) {
newX = root.parent.width/2 - value/2 + offset - root.width/2
} else if (root.alignment & Qt.AlignLeft) {
newX = root.parent.width/2 + value/2 + offset -root.width/2
} else {
newX = root.parent.width/2 + value + offset -root.width/2
}
}
x = Math.max(-width/2, Math.min(parent.width - width/2, newX))
}
}
onValueChanged: syncPos()
onOffsetChanged: syncPos()
onAlignmentChanged: syncPos()
Connections {
target: root.parent
function onWidthChanged() {
syncPos()
}
function onHeightChanged() {
syncPos()
}
}
PC3.ToolTip {
text: root.description
visible: root.description !== "" && ((area.containsMouse && !area.containsPress) || area.activeFocus)
}
MouseArea {
id: area
drag {
target: parent
axis: (dialogRoot.vertical) ? Drag.YAxis : Drag.XAxis
minimumX: root.minimumPosition
minimumY: root.minimumPosition
maximumX: root.maximumPosition
maximumY: root.maximumPosition
}
anchors {
fill: parent
leftMargin: (dialogRoot.vertical) ? 0 : -Kirigami.Units.gridUnit
rightMargin: (dialogRoot.vertical) ? 0 : -Kirigami.Units.gridUnit
topMargin: (dialogRoot.vertical) ? -Kirigami.Units.gridUnit : 0
bottomMargin: (dialogRoot.vertical) ? -Kirigami.Units.gridUnit : 0
}
readonly property int keyboardMoveStepSize: Math.ceil((root.maximumPosition - root.minimumPosition) / 20)
activeFocusOnTab: true
hoverEnabled: true
cursorShape: dialogRoot.vertical ? Qt.SizeVerCursor : Qt.SizeHorCursor
Accessible.description: root.description
Keys.onEnterPressed: doubleClicked(null);
Keys.onReturnPressed: doubleClicked(null);
Keys.onSpacePressed: doubleClicked(null);
// BEGIN Arrow keys
Keys.onUpPressed: if (dialogRoot.vertical) {
root.y = Math.max(root.minimumPosition, root.y - ((event.modifiers & Qt.ShiftModifier) ? 1 : keyboardMoveStepSize));
changePosition();
} else {
event.accepted = false;
}
Keys.onDownPressed: if (dialogRoot.vertical) {
root.y = Math.min(root.maximumPosition, root.y + ((event.modifiers & Qt.ShiftModifier) ? 1 : keyboardMoveStepSize));
changePosition();
} else {
event.accepted = false;
}
Keys.onLeftPressed: if (!dialogRoot.vertical) {
root.x = Math.max(root.minimumPosition, root.x - ((event.modifiers & Qt.ShiftModifier) ? 1 : keyboardMoveStepSize));
changePosition();
} else {
event.accepted = false;
}
Keys.onRightPressed: if (!dialogRoot.vertical) {
root.x = Math.min(root.maximumPosition, root.x + ((event.modifiers & Qt.ShiftModifier) ? 1 : keyboardMoveStepSize));
changePosition();
} else {
event.accepted = false;
}
// END Arrow keys
onPositionChanged: {
if (!drag.active) {
return;
}
changePosition();
}
onDoubleClicked: {
root.value = root.defaultPosition();
}
function changePosition() {
root.hasEverBeenMoved = true
if (dialogRoot.vertical) {
if (root.alignment === Qt.AlignRight) {
root.value = root.parent.height - (root.y + root.offset + root.height/2)
} else if (root.alignment === Qt.AlignLeft) {
root.value = root.y - root.offset + root.height/2
//Center
} else {
if (root.alignment & Qt.AlignRight) {
root.value = (root.parent.height/2 - root.y + root.offset)*2 - root.height
} else if (root.alignment & Qt.AlignLeft) {
root.value = (root.y - root.offset - root.parent.height/2)*2 + root.height
} else {
var value = root.y - root.parent.height/2 - root.offset + root.height/2
//Snap
if (Math.abs(value) < 5) {
root.value = 0
} else {
root.value = value
}
}
}
} else {
if (root.alignment === Qt.AlignRight) {
root.value = root.parent.width - (root.x + root.offset + root.width/2)
} else if (root.alignment === Qt.AlignLeft) {
root.value = root.x - root.offset + root.width/2
//Center
} else {
if (root.alignment & Qt.AlignRight) {
root.value = (root.parent.width/2 - root.x + root.offset)*2 - root.width
} else if (root.alignment & Qt.AlignLeft) {
root.value = (root.x - root.offset - root.parent.width/2)*2 + root.width
} else {
var value = root.x - root.parent.width/2 - root.offset + root.width/2
//Snap
if (Math.abs(value) < 5) {
root.value = 0
} else {
root.value = value
}
}
}
}
}
PlasmaExtras.Highlight {
anchors.fill: parent
visible: parent.activeFocus
hovered: true
}
}
}

View File

@@ -0,0 +1,284 @@
/*
SPDX-FileCopyrightText: 2021 Cyril Rossi <cyril.rossi@enioka.com>
SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Window
import org.kde.kirigami as Kirigami
QQC2.Control {
id: delegate
property Item viewPort
readonly property string screenName: model.screenName
readonly property int screenId: model.screenId
property bool containsDrag
property alias contentsLayout: contentsLayout
width: Math.min(Kirigami.Units.gridUnit * 25, Math.floor(viewPort.width / Math.min(repeater.count, Math.floor(viewPort.width / (Kirigami.Units.gridUnit * 12)))))
contentItem: ColumnLayout {
id: contentsLayout
width: Math.min(parent.width, Kirigami.Units.gridUnit * 15)
Rectangle {
id: screenRect
Layout.fillWidth: true
Layout.preferredHeight: width / 1.6
color: Kirigami.Theme.backgroundColor
border.color: Kirigami.Theme.textColor
Rectangle {
anchors.fill: parent
z: 9
color: "black"
opacity: delegate.containsDrag ? 0.3 : 0
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
Repeater {
id: containmentRepeater
model: containments
Rectangle {
id: contRect
property real homeX
property real homeY
property string oldState
readonly property int edgeDistance: {
return (state === "left" || state === "right" ? width : height) * model.edgePosition;
}
width: moveButton.width
height: moveButton.height
border.color: Kirigami.Theme.textColor
color: Kirigami.Theme.backgroundColor
state: model.edge
z: state === "floating" ? 0 : 1
visible: !model.isDestroyed
HoverHandler {
cursorShape: Qt.OpenHandCursor
}
DragHandler {
id: dragHandler
property QQC2.Control targetDelegate
cursorShape: Qt.ClosedHandCursor
onActiveChanged: {
if (active) {
delegate.z = 1;
} else {
if (targetDelegate) {
resetAnim.restart();
containmentRepeater.model.moveContainementToScreen(model.containmentId, targetDelegate.screenId)
targetDelegate.containsDrag = false;
targetDelegate = null;
} else {
resetAnim.restart();
}
}
}
onTranslationChanged: {
if (!active) {
if (targetDelegate) {
targetDelegate.containsDrag = false;
targetDelegate = null;
}
return;
}
let pos = contRect.mapToItem(delegate.parent, dragHandler.centroid.position.x, dragHandler.centroid.position.y);
let otherDelegate = delegate.parent.childAt(pos.x, pos.y);
if (targetDelegate && targetDelegate !== otherDelegate) {
targetDelegate.containsDrag = false;
}
if (!otherDelegate || otherDelegate === delegate) {
targetDelegate = null;
} else if (otherDelegate && otherDelegate !== delegate
&& otherDelegate.hasOwnProperty("screenId")
&& otherDelegate.hasOwnProperty("containsDrag")) {
targetDelegate = otherDelegate;
targetDelegate.containsDrag = true;
}
}
}
SequentialAnimation {
id: resetAnim
property var targetDelegatePos: dragHandler.targetDelegate
? dragHandler.targetDelegate.contentsLayout.mapToItem(delegate.contentsLayout, 0, 0)
: Qt.point(0, 0)
ParallelAnimation {
XAnimator {
target: contRect
from: contRect.x
to: contRect.homeX + resetAnim.targetDelegatePos.x
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
YAnimator {
target: contRect
from: contRect.y
to: contRect.homeY + resetAnim.targetDelegatePos.y
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
PropertyAction {
target: delegate
property: "z"
value: 0
}
}
Image {
id: containmentImage
anchors {
fill: parent
margins: 1
}
// It needs to reload the image from disk when the file changes
cache: false
source: model.imageSource
fillMode: model.edge == "floating" ? Image.PreserveAspectCrop : Image.PreserveAspectFit
}
QQC2.Button {
id: moveButton
icon.name: "open-menu-symbolic"
visible: contextMenuRepeater.anyActionAvailable || removeItem.itemVisible
checked: contextMenu.visible
anchors {
right: parent.right
top: parent.top
topMargin: model.edge == "floating"
? model.panelCountAtTop * moveButton.height + Kirigami.Units.largeSpacing
: 0
rightMargin: model.edge == "floating"
? (moveButton.LayoutMirroring.enabled ? model.panelCountAtLeft : model.panelCountAtRight) * moveButton.height + Kirigami.Units.largeSpacing
: 0
}
onClicked: {
contextMenu.open()
}
QQC2.Menu {
id: contextMenu
y: moveButton.height
Repeater {
id: contextMenuRepeater
model: ShellContainmentModel
// There will always be at least one hidden action for the
// current screen; if there's any other, then we should
// display them.
property bool anyActionAvailable: count > 1
QQC2.MenuItem {
text: edge == "floating"
? i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:inmenu", "Swap with Desktop on Screen %1", model.screenName)
: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:inmenu", "Move to Screen %1", model.screenName)
visible: model.screenName !== delegate.screenName
height: visible ? implicitHeight : 0
onTriggered: {
containmentRepeater.model.moveContainementToScreen(containmentId, screenId)
}
}
}
QQC2.MenuSeparator {
visible: removeItem.visible && contextMenuRepeater.anyActionAvailable
}
QQC2.MenuItem {
id: removeItem
text: contRect.state === "floating"
? i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:inmenu", "Remove Desktop")
: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:inmenu", "Remove Panel")
icon.name: "edit-delete"
onTriggered: {
if (contRect.state === "floating") {
ShellContainmentModel.remove(screenId);
} else {
containments.remove(containmentId);
}
}
// We read this variable elsewhere to know if this item
// would be visible if the context menu were to be shown.
property bool itemVisible: contRect.state !== "floating" || !model.active
visible: itemVisible
}
}
}
states: [
State {
name: "floating"
PropertyChanges {
target: contRect;
width: screenRect.width
height: screenRect.height
color: "transparent"
}
},
State {
name: "top"
PropertyChanges {
target: contRect;
width: screenRect.width
y: homeY
homeX: 0
homeY: contRect.edgeDistance
}
},
State {
name: "right"
PropertyChanges {
target: contRect;
x: homeX
homeX: screenRect.width - contRect.width - contRect.edgeDistance;
height: screenRect.height
homeY: 0
}
},
State {
name: "bottom"
PropertyChanges {
target: contRect;
y: homeY
homeX: 0
homeY: screenRect.height - contRect.height - contRect.edgeDistance;
width: screenRect.width
}
},
State {
name: "left"
PropertyChanges {
target: contRect;
height: screenRect.height
x: homeX
homeX: contRect.edgeDistance
homeY: 0
}
}
]
}
}
}
QQC2.Label {
Layout.fillWidth: true
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
text: model.isPrimary
? i18ndc("plasma_shell_org.kde.plasma.desktop", "@info % is screen name", "%1 (primary)", model.screenName)
: model.screenName
textFormat: Text.PlainText
}
}
}

View File

@@ -0,0 +1,14 @@
[Desktop]
Containment=org.kde.plasma.folder
ToolBox=
RuntimePlatform=Desktop
[Desktop][ContainmentActions]
RightButton;NoModifier=org.kde.contextmenu
MiddleButton;NoModifier=org.kde.paste
[Panel]
Containment=org.kde.panel
ToolBox=org.kde.paneltoolbox
[Panel][ContainmentActions]
RightButton;NoModifier=org.kde.contextmenu

View File

@@ -0,0 +1,211 @@
/*
SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Window
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.components as PlasmaComponents3
import org.kde.plasma.extras as PlasmaExtras
import org.kde.plasma.plasmoid
import org.kde.kirigami as Kirigami
import org.kde.plasma.private.shell
import org.kde.plasma.shell
PlasmaCore.Dialog {
id: dialog
required property AlternativesHelper alternativesHelper
visualParent: alternativesHelper.applet
location: alternativesHelper.applet.Plasmoid.location
hideOnWindowDeactivate: true
backgroundHints: (alternativesHelper.applet.Plasmoid.containmentDisplayHints & PlasmaCore.Types.ContainmentPrefersOpaqueBackground) ? PlasmaCore.Dialog.SolidBackground : PlasmaCore.Dialog.StandardBackground
Component.onCompleted: {
flags = flags | Qt.WindowStaysOnTopHint;
dialog.show();
}
ColumnLayout {
id: root
signal configurationChanged
Layout.minimumWidth: Kirigami.Units.gridUnit * 20
Layout.minimumHeight: Math.min(Screen.height - Kirigami.Units.gridUnit * 10, implicitHeight)
LayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
property string currentPlugin: ""
Shortcut {
sequence: "Escape"
onActivated: dialog.close()
}
Shortcut {
sequence: "Return"
onActivated: root.savePluginAndClose()
}
Shortcut {
sequence: "Enter"
onActivated: root.savePluginAndClose()
}
WidgetExplorer {
id: widgetExplorer
provides: dialog.alternativesHelper.appletProvides
}
PlasmaExtras.PlasmoidHeading {
Kirigami.Heading {
id: heading
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:window for widget alternatives explorer", "Alternative Widgets")
textFormat: Text.PlainText
}
}
// This timer checks with a short delay whether a new item in the list has been hovered by the cursor.
// If not, then the cursor has left the view and thus no item should be selected.
Timer {
id: resetCurrentIndex
property string oldPlugin
interval: 100
onTriggered: {
if (root.currentPlugin === oldPlugin) {
mainList.currentIndex = -1
root.currentPlugin = ""
}
}
}
function savePluginAndClose() {
dialog.alternativesHelper.loadAlternative(currentPlugin);
dialog.close();
}
PlasmaComponents3.ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: mainList.contentHeight
focus: true
ListView {
id: mainList
focus: dialog.visible
model: widgetExplorer.widgetsModel
boundsBehavior: Flickable.StopAtBounds
highlight: PlasmaExtras.Highlight {
pressed: mainList.currentItem && mainList.currentItem.pressed
}
highlightMoveDuration : 0
highlightResizeDuration: 0
height: contentHeight+Kirigami.Units.smallSpacing
delegate: PlasmaComponents3.ItemDelegate {
id: listItem
implicitHeight: contentLayout.implicitHeight + Kirigami.Units.smallSpacing * 2
width: ListView.view.width
Accessible.name: model.name
Accessible.description: model.description
onHoveredChanged: {
if (hovered) {
resetCurrentIndex.stop()
mainList.currentIndex = index
} else {
resetCurrentIndex.oldPlugin = model.pluginName
resetCurrentIndex.restart()
}
}
Connections {
target: mainList
function onCurrentIndexChanged() {
if (mainList.currentIndex === index) {
root.currentPlugin = model.pluginName
}
}
}
onClicked: root.savePluginAndClose()
Component.onCompleted: {
if (model.pluginName === dialog.alternativesHelper.currentPlugin) {
root.currentPlugin = model.pluginName
setAsCurrent.restart()
}
}
// we don't want to select any entry by default
// this cannot be set in Component.onCompleted
Timer {
id: setAsCurrent
interval: 100
onTriggered: {
mainList.currentIndex = index
}
}
contentItem: RowLayout {
id: contentLayout
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
implicitWidth: Kirigami.Units.iconSizes.huge
implicitHeight: Kirigami.Units.iconSizes.huge
source: model.decoration
}
ColumnLayout {
id: labelLayout
readonly property color textColor: listItem.pressed ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
Layout.fillHeight: true
Layout.fillWidth: true
spacing: 0 // The labels bring their own bottom margins
Kirigami.Heading {
level: 4
Layout.fillWidth: true
text: model.name
textFormat: Text.PlainText
elide: Text.ElideRight
type: model.pluginName === dialog.alternativesHelper.currentPlugin ? PlasmaExtras.Heading.Type.Primary : PlasmaExtras.Heading.Type.Normal
color: labelLayout.textColor
}
PlasmaComponents3.Label {
Layout.fillWidth: true
text: model.description
textFormat: Text.PlainText
font.pointSize: Kirigami.Theme.smallFont.pointSize
font.family: Kirigami.Theme.smallFont.family
font.bold: model.pluginName === dialog.alternativesHelper.currentPlugin
opacity: 0.75
maximumLineCount: 2
wrapMode: Text.WordWrap
elide: Text.ElideRight
color: labelLayout.textColor
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,315 @@
/*
SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2015 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.plasma.components as PlasmaComponents
import org.kde.plasma.core as PlasmaCore
import org.kde.kwindowsystem
import org.kde.kirigami as Kirigami
import org.kde.graphicaleffects as KGraphicalEffects
Item {
id: delegate
readonly property string pluginName: model.pluginName
readonly property bool pendingUninstall: pendingUninstallTimer.applets.indexOf(pluginName) > -1
readonly property bool pressed: tapHandler.pressed
readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software
width: list.cellWidth
height: list.cellHeight
Accessible.name: i18nc("@action:button accessible only, %1 is widget name", "Add %1", model.name) + (model.isSupported ? "" : unsupportedTooltip.mainText)
Accessible.description: (model.isSupported ? "" : model.unsupportedMessage) + model.description + (overlayedBadge.visible ? countLabel.Accessible.name : "")
Accessible.role: Accessible.Button
HoverHandler {
id: hoverHandler
onHoveredChanged: if (hovered) delegate.GridView.view.currentIndex = index
}
TapHandler {
id: tapHandler
enabled: !delegate.pendingUninstall && model.isSupported
onTapped: widgetExplorer.addApplet(delegate.pluginName)
}
PlasmaCore.ToolTipArea {
id: unsupportedTooltip
anchors.fill: parent
visible: !model.isSupported
mainText: i18nc("@info:tooltip", "Unsupported Widget")
subText: model.unsupportedMessage
}
// Avoid repositioning delegate item after dragFinished
Item {
anchors.fill: parent
enabled: model.isSupported
Drag.dragType: Drag.Automatic
Drag.supportedActions: Qt.MoveAction | Qt.LinkAction
Drag.mimeData: {
"text/x-plasmoidservicename" : delegate.pluginName,
}
Drag.onDragStarted: {
KWindowSystem.showingDesktop = true;
main.draggingWidget = true;
delegate.forceActiveFocus()
}
Drag.onDragFinished: {
main.draggingWidget = false;
}
DragHandler {
id: dragHandler
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
enabled: !delegate.pendingUninstall && model.isSupported
onActiveChanged: if (active) {
iconContainer.grabToImage(function(result) {
if (!dragHandler.active) {
return;
}
parent.Drag.imageSource = result.url;
parent.Drag.active = dragHandler.active;
}, Qt.size(Kirigami.Units.iconSizes.huge, Kirigami.Units.iconSizes.huge));
} else {
parent.Drag.active = false;
parent.Drag.imageSource = "";
}
}
DragHandler {
id: touchDragHandler
acceptedDevices: PointerDevice.Stylus | PointerDevice.TouchScreen
enabled: dragHandler.enabled
yAxis.enabled: false
onActiveChanged: if (active) {
iconContainer.grabToImage(function(result) {
if (!touchDragHandler.active) {
return;
}
parent.Drag.imageSource = result.url;
parent.Drag.active = touchDragHandler.active;
}, Qt.size(Kirigami.Units.iconSizes.huge, Kirigami.Units.iconSizes.huge));
} else {
parent.Drag.active = false;
parent.Drag.imageSource = "";
}
}
}
ColumnLayout {
id: mainLayout
readonly property color textColor: tapHandler.pressed ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
spacing: Kirigami.Units.smallSpacing
anchors {
left: parent.left
right: parent.right
//bottom: parent.bottom
margins: Kirigami.Units.smallSpacing * 2
rightMargin: Kirigami.Units.smallSpacing * 2 // don't cram the text to the border too much
top: parent.top
}
Item {
id: iconContainer
width: Kirigami.Units.iconSizes.enormous
height: width
Layout.alignment: Qt.AlignHCenter
opacity: delegate.pendingUninstall ? 0.6 : 1
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Item {
id: iconWidget
anchors.fill: parent
Kirigami.Icon {
anchors.fill: parent
source: model.decoration
visible: model.screenshot === ""
selected: tapHandler.pressed
enabled: model.isSupported
}
Image {
width: Kirigami.Units.iconSizes.enormous
height: width
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: model.screenshot
}
}
Item {
id: badgeMask
anchors.fill: parent
Rectangle {
x: Math.round(-Kirigami.Units.smallSpacing * 1.5 / 2)
y: x
width: overlayedBadge.width + Math.round(Kirigami.Units.smallSpacing * 1.5)
height: overlayedBadge.height + Math.round(Kirigami.Units.smallSpacing * 1.5)
radius: height
visible: (running && delegate.GridView.isCurrentItem) ?? false
}
}
KGraphicalEffects.BadgeEffect {
anchors.fill: parent
source: ShaderEffectSource {
sourceItem: iconWidget
hideSource: !softwareRendering
live: false
}
mask: ShaderEffectSource {
id: maskShaderSource
sourceItem: badgeMask
hideSource: true
live: false
}
}
Rectangle {
id: overlayedBadge
width: countLabel.width + height
height: Math.round(Kirigami.Units.iconSizes.sizeForLabels * 1.3)
radius: height
color: (running && delegate.GridView.isCurrentItem) ? Kirigami.Theme.highlightColor : Kirigami.Theme.positiveTextColor
visible: ((running && delegate.GridView.isCurrentItem) || model.recent) ?? false
onVisibleChanged: maskShaderSource.scheduleUpdate()
PlasmaComponents.Label {
id: countLabel
height: parent.height
verticalAlignment: Text.AlignVCenter
anchors.centerIn: parent
text: (running && delegate.GridView.isCurrentItem) ? running : i18ndc("plasma_shell_org.kde.plasma.desktop", "Text displayed on top of newly installed widgets", "New!")
Accessible.name: running
? i18ncp("@info:other accessible for badge showing applet count", "%1 widget active", "%1 widgets active", running)
: i18nc(" @info:other accessible for badge indicating new widget", "Recently installed")
textFormat: Text.PlainText
}
}
PlasmaComponents.ToolButton {
id: uninstallButton
anchors {
top: parent.top
right: parent.right
}
text: delegate.pendingUninstall ? i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button tooltip and accessible cancel uninstall widget", "Cancel Uninstallation")
: i18ndc("plasma_shell_org.kde.plasma.desktop","@action:button tooltip and accessible uninstall widget", "Mark for Uninstallation")
icon.name: delegate.pendingUninstall ? "edit-undo" : "edit-delete"
display: PlasmaComponents.AbstractButton.IconOnly
Accessible.description: delegate.pendingUninstall
? i18nc("@action:button accessible only, %1 is widget name", "Cancel pending uninstallation for widget %1", model.name)
: i18nc("@action:button accessible only, %1 is widget name", "Mark widget %1 for uninstallation. Requires confirmation", model.name)
// we don't really "undo" anything but we'll pretend to the user that we do
PlasmaComponents.ToolTip.delay: Kirigami.Units.toolTipDelay
PlasmaComponents.ToolTip.visible: hovered
PlasmaComponents.ToolTip.text: text
flat: false
visible: (model.local && delegate.GridView.isCurrentItem && !dragHandler.active && !touchDragHandler.active) ?? false
onHoveredChanged: {
if (hovered) {
// hovering the uninstall button triggers onExited of the main mousearea
delegate.GridView.view.currentIndex = index
}
}
onClicked: {
let pending = pendingUninstallTimer.applets
if (delegate.pendingUninstall) {
let index = pending.indexOf(pluginName)
if (index > -1) {
pending.splice(index, 1)
}
} else {
pending.push(pluginName)
}
pendingUninstallTimer.applets = pending
if (pending.length) {
pendingUninstallTimer.restart()
} else {
pendingUninstallTimer.stop()
}
}
}
PlasmaComponents.ToolButton {
id: removeInstancesButton
anchors {
top: parent.top
right: uninstallButton.visible ? uninstallButton.left : parent.right
rightMargin: uninstallButton.visible ? Kirigami.Units.smallSpacing : 0
}
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Remove all instances")
display: PlasmaComponents.AbstractButton.IconOnly
icon.name: "edit-clear-all"
Accessible.description: i18ncp("@action:button accessible description, %1 number of instances %2 is widget name",
"Remove running instance of widget %2",
"Remove all %1 running instances of widget %2", running ,model.name)
// we don't really "undo" anything but we'll pretend to the user that we do
PlasmaComponents.ToolTip.delay: Kirigami.Units.toolTipDelay
PlasmaComponents.ToolTip.visible: hovered
PlasmaComponents.ToolTip.text: text
flat: false
visible: (running && delegate.GridView.isCurrentItem && !dragHandler.active) ?? false
onHoveredChanged: {
if (hovered) {
// hovering the uninstall button triggers onExited of the main mousearea
delegate.GridView.view.currentIndex = index
}
}
onClicked: widgetExplorer.removeAllInstances(pluginName)
}
}
Kirigami.Heading {
id: heading
Layout.fillWidth: true
level: 4
text: model.name
textFormat: Text.PlainText
elide: Text.ElideRight
wrapMode: Text.WordWrap
maximumLineCount: 3
lineHeight: 0.95
horizontalAlignment: Text.AlignHCenter
color: mainLayout.textColor
}
PlasmaComponents.Label {
Layout.fillWidth: true
// otherwise causes binding loop due to the way the Plasma sets the height
text: model.description
textFormat: Text.PlainText
font: Kirigami.Theme.smallFont
wrapMode: Text.WordWrap
elide: Text.ElideRight
maximumLineCount: 5 - heading.lineCount
horizontalAlignment: Text.AlignHCenter
color: mainLayout.textColor
}
}
}

View File

@@ -0,0 +1,392 @@
/*
SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.plasma.components as PC3
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.extras as PlasmaExtras
import org.kde.kquickcontrolsaddons
import org.kde.kwindowsystem
import org.kde.kirigami as Kirigami
import QtQuick.Window
import QtQuick.Layouts
import org.kde.plasma.private.shell
PC3.Page {
id: main
width: Math.max(heading.paintedWidth, Kirigami.Units.iconSizes.enormous * 3 + Kirigami.Units.smallSpacing * 4 + Kirigami.Units.gridUnit * 5)
height: 800//Screen.height
opacity: draggingWidget ? 0.3 : 1
readonly property int contentMargins: Kirigami.Units.largeSpacing
property QtObject containment
property PlasmaCore.Dialog sidePanel
//external drop events can cause a raise event causing us to lose focus and
//therefore get deleted whilst we are still in a drag exec()
//this is a clue to the owning dialog that hideOnWindowDeactivate should be deleted
//See https://bugs.kde.org/show_bug.cgi?id=332733
property bool preventWindowHide: draggingWidget || categoriesDialog.status !== PlasmaExtras.Menu.Closed
|| getWidgetsDialog.status !== PlasmaExtras.Menu.Closed
// We might've lost focus during the widget drag and drop or whilst using
// the "get widgets" dialog; however we prevented the sidebar to hide.
// This might get the sidebar stuck, since we only hide when losing focus.
// To avoid this we reclaim focus as soon as the drag and drop is done,
// or the get widgets window is closed.
onPreventWindowHideChanged: {
if (!preventWindowHide && !sidePanel.active) {
sidePanel.requestActivate()
}
}
property bool outputOnly: draggingWidget
property Item categoryButton
property bool draggingWidget: false
signal closed()
onClosed: {
// If was called from a panel, open the panel config
if (root.widgetExplorer.containment &&
root.widgetExplorer.containment.containmentType == 1 &&
!root.widgetExplorer.containment.userConfiguring) {
root.widgetExplorer.containment.internalAction("configure").trigger()
}
}
onVisibleChanged: {
if (!visible) {
KWindowSystem.showingDesktop = false
}
}
Component.onCompleted: {
if (!root.widgetExplorer) {
root.widgetExplorer = widgetExplorerComponent.createObject(root)
}
root.widgetExplorer.containment = main.containment
}
Component.onDestruction: {
if (pendingUninstallTimer.running) {
// we're not being destroyed so at least reset the filters
widgetExplorer.widgetsModel.filterQuery = ""
widgetExplorer.widgetsModel.filterType = ""
widgetExplorer.widgetsModel.searchTerm = ""
} else {
root.widgetExplorer.destroy()
root.widgetExplorer = null
}
}
function addCurrentApplet() {
var pluginName = list.currentItem ? list.currentItem.pluginName : ""
if (pluginName) {
widgetExplorer.addApplet(pluginName)
}
}
QQC2.Action {
shortcut: "Escape"
onTriggered: {
if (searchInput.length > 0) {
searchInput.text = ""
} else {
main.closed()
}
}
}
QQC2.Action {
shortcut: "Enter"
onTriggered: addCurrentApplet()
}
QQC2.Action {
shortcut: "Return"
onTriggered: addCurrentApplet()
}
Component {
id: widgetExplorerComponent
WidgetExplorer {
//view: desktop
onShouldClose: main.closed();
}
}
PlasmaExtras.ModelContextMenu {
id: categoriesDialog
visualParent: categoryButton
// model set on first invocation
onClicked: model => {
list.contentX = 0
list.contentY = 0
categoryButton.text = (model.filterData ? model.display : i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button like listbox, switches category to all widgets", "All Widgets"))
widgetExplorer.widgetsModel.filterQuery = model.filterData
widgetExplorer.widgetsModel.filterType = model.filterType
}
}
PlasmaExtras.ModelContextMenu {
id: getWidgetsDialog
visualParent: getWidgetsButton
placement: PlasmaExtras.Menu.TopPosedLeftAlignedPopup
// model set on first invocation
onClicked: model.trigger()
}
header: PlasmaExtras.PlasmoidHeading {
// Subtract page's own margins since we touch the top, left, and right
topPadding: - main.sidePanel.margins.top
leftPadding: main.contentMargins - main.sidePanel.margins.left
rightPadding: main.contentMargins - main.sidePanel.margins.right
bottomPadding: main.contentMargins
contentItem: ColumnLayout {
spacing: Kirigami.Units.smallSpacing
RowLayout {
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
id: heading
level: 1
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@title:group for widget grid", "Widgets")
textFormat: Text.PlainText
elide: Text.ElideRight
Layout.fillWidth: true
}
PC3.ToolButton {
id: getWidgetsButton
icon.name: "get-hot-new-stuff"
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button The word 'new' refers to widgets", "Get New…")
Accessible.name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Get New Widgets…")
KeyNavigation.right: closeButton
KeyNavigation.down: searchInput
onClicked: {
getWidgetsDialog.model = widgetExplorer.widgetsMenuActions
getWidgetsDialog.openRelative()
}
}
PC3.ToolButton {
id: closeButton
text: i18nc("@action:button accessible for close button", "Close Widget Explorer")
icon.name: "window-close"
display: PC3.AbstractButton.IconOnly
KeyNavigation.down: categoryButton
onClicked: main.closed()
}
}
RowLayout {
spacing: Kirigami.Units.smallSpacing
PlasmaExtras.SearchField {
id: searchInput
Layout.fillWidth: true
Accessible.name: i18nc("@label:textbox accessible", "Search through widgets")
KeyNavigation.down: list
KeyNavigation.right: categoryButton
onTextChanged: {
list.positionViewAtBeginning()
list.currentIndex = -1
widgetExplorer.widgetsModel.searchTerm = text
}
Component.onCompleted: if (!Kirigami.InputMethod.willShowOnActive) { forceActiveFocus() }
}
PC3.ToolButton {
id: categoryButton
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button like listbox, switches category to all widgets", "All Widgets")
icon.name: "view-filter"
Accessible.role: Accessible.ButtonMenu
down: categoriesDialog.status == PlasmaExtras.ModelContextMenu.Open || pressed
KeyNavigation.down: list
onClicked: {
categoriesDialog.model = widgetExplorer.filterModel
categoriesDialog.open(0, categoryButton.height)
}
PC3.ToolTip {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button tooltip only", "Categories")
}
}
}
}
}
footer: PlasmaExtras.PlasmoidHeading {
id: footerContainer
// Subtract page's own margins since we touch the left, right, and bottom
topPadding: main.contentMargins
leftPadding: main.contentMargins - main.sidePanel.margins.left
rightPadding: main.contentMargins - main.sidePanel.margins.right
bottomPadding: main.contentMargins - main.sidePanel.margins.bottom
position: PC3.ToolBar.Footer
visible: pendingUninstallTimer.applets.length > 0
contentItem: PC3.Button {
id: uninstallButton
icon.name: "delete"
text: i18ndcp("plasma_shell_org.kde.plasma.desktop", "@action:button uninstall widgets in widget explorer", "Uninstall (%1 Widget)", "Uninstall (%1 Widgets)", pendingUninstallTimer.applets.length)
onClicked: pendingUninstallTimer.uninstall()
}
}
Timer {
id: setModelTimer
interval: 20
running: true
onTriggered: list.model = widgetExplorer.widgetsModel
}
PC3.ScrollView {
id: scrollView
anchors {
fill: parent
// Subtract page's own margins since we touch the left, right, and sometimes bottom
topMargin: -1 // account for PlasmoidHeading's pixel
leftMargin: - main.sidePanel.margins.left
rightMargin: - main.sidePanel.margins.right
bottomMargin: footerContainer.visible ? 0 : - main.sidePanel.margins.bottom
}
// The scrollbar changing visibility can lead to the content size changing due to word wrap
// A delayed binding gives some extra time, it'll come to a stop as there's only one scroll bar
property bool scrollBarVisible
Binding on scrollBarVisible {
value: list.contentHeight > scrollView.height
delayed: true
}
PC3.ScrollBar.horizontal.policy: PC3.ScrollBar.AlwaysOff
PC3.ScrollBar.vertical.visible: scrollBarVisible
// hide the flickering by fading in nicely
opacity: setModelTimer.running ? 0 : 1
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
GridView {
id: list
readonly property int effectiveWidth: width
- leftMargin
- rightMargin
// model set delayed by Timer above
topMargin: main.contentMargins
leftMargin: main.contentMargins
rightMargin: main.contentMargins
bottomMargin: main.contentMargins
activeFocusOnTab: true
cellWidth: Math.floor(effectiveWidth / 3)
cellHeight: Kirigami.Units.iconSizes.enormous + Kirigami.Units.smallSpacing * 4 + headingFontMetrics.height * 3 * referenceHeading.lineHeight + descriptionFontMetrics.height * 2
// This element is used as a reference to size the
// cellHeight and should be kept in sync with the
// heading within the AppletDelegate.
Kirigami.Heading {
id: referenceHeading
visible: false
level: 4
lineHeight: 0.95
}
FontMetrics {
id: headingFontMetrics
font: referenceHeading.font
}
FontMetrics {
id: descriptionFontMetrics
font: Kirigami.Theme.smallFont
}
delegate: AppletDelegate {}
highlight: PlasmaExtras.Highlight {
pressed: list.currentItem && list.currentItem.pressed
}
highlightMoveDuration: 0
//highlightResizeDuration: 0
//slide in to view from the left
add: Transition {
// Work around https://bugreports.qt.io/browse/QTBUG-127709
enabled: Kirigami.Units.shortDuration > 0
NumberAnimation {
properties: "x"
from: -list.width
duration: Kirigami.Units.shortDuration
}
}
//slide out of view to the right
remove: Transition {
// Work around https://bugreports.qt.io/browse/QTBUG-127709
enabled: Kirigami.Units.shortDuration > 0
NumberAnimation {
properties: "x"
to: list.width
duration: Kirigami.Units.shortDuration
}
}
//if we are adding other items into the view use the same animation as normal adding
//this makes everything slide in together
//if we make it move everything ends up weird
addDisplaced: list.add
//moved due to filtering
displaced: Transition {
NumberAnimation {
properties: "x,y"
duration: Kirigami.Units.shortDuration
}
}
KeyNavigation.up: searchInput
KeyNavigation.down: uninstallButton
}
}
PlasmaExtras.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.gridUnit * 4)
iconName: "edit-none"
text: searchInput.text.length > 0 ? i18ndc("plasma_shell_org.kde.plasma.desktop", "@info placeholdermessage", "No widgets matched the search terms") : i18ndc("plasma_shell_org.kde.plasma.desktop", "@info placeholdermessage", "No widgets available")
visible: list.count == 0 && !setModelTimer.running
}
}

View File

@@ -0,0 +1,72 @@
/*
SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.ksvg as KSvg
import org.kde.kirigami as Kirigami
import org.kde.plasma.workspace.osd
KSvg.FrameSvgItem {
id: osd
property alias timeout: osdItem.timeout
property alias osdValue: osdItem.osdValue
property alias osdMaxValue: osdItem.osdMaxValue
property alias icon: osdItem.icon
property alias showingProgress: osdItem.showingProgress
objectName: "onScreenDisplay"
visible: false
width: osdItem.width + margins.left + margins.right
height: osdItem.height + margins.top + margins.bottom
imagePath: "dialogs/background"
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Window
function show() {
osd.visible = true;
hideAnimation.restart();
}
// avoid leaking ColorScope of lock screen theme into the OSD "popup"
Item {
width: osdItem.width
height: osdItem.height
anchors.centerIn: parent
OsdItem {
id: osdItem
}
}
SequentialAnimation {
id: hideAnimation
ScriptAction {
// prevent opacity layering of ProgressBar.
script: osd.layer.enabled = true
}
// prevent press and hold from flickering
PauseAnimation { duration: osd.timeout }
NumberAnimation {
target: osd
property: "opacity"
from: 1
to: 0
duration: Kirigami.Units.shortDuration
easing.type: Easing.InQuad
}
ScriptAction {
script: {
osd.visible = false;
osd.opacity = 1;
osd.icon = "";
osd.osdValue = 0;
osd.layer.enabled = false;
}
}
}
}

View File

@@ -0,0 +1,34 @@
/*
SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
Item {
id: root
property bool debug: false
property string notification
signal clearPassword()
signal notificationRepeated()
// These are magical properties that kscreenlocker looks for
property bool viewVisible: false
property bool suspendToRamSupported: false
property bool suspendToDiskSupported: false
// These are magical signals that kscreenlocker looks for
signal suspendToDisk()
signal suspendToRam()
LayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
implicitWidth: 800
implicitHeight: 600
LockScreenUi {
anchors.fill: parent
}
}

View File

@@ -0,0 +1,427 @@
/*
SPDX-FileCopyrightText: 2014 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQml
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import org.kde.plasma.components as PlasmaComponents3
import org.kde.plasma.workspace.components as PW
import org.kde.plasma.private.keyboardindicator as KeyboardIndicator
import org.kde.kirigami as Kirigami
import org.kde.kscreenlocker as ScreenLocker
import org.kde.plasma.private.sessions
import org.kde.breeze.components
Item {
id: lockScreenUi
// If we're using software rendering, draw outlines instead of shadows
// See https://bugs.kde.org/show_bug.cgi?id=398317
readonly property bool softwareRendering: GraphicsInfo.api === GraphicsInfo.Software
function handleMessage(msg) {
if (!root.notification) {
root.notification += msg;
} else if (root.notification.includes(msg)) {
root.notificationRepeated();
} else {
root.notification += "\n" + msg
}
}
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
Connections {
target: authenticator
function onFailed(kind) {
if (kind != 0) { // if this is coming from the noninteractive authenticators
return;
}
const msg = i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:status", "Unlocking failed");
lockScreenUi.handleMessage(msg);
graceLockTimer.restart();
notificationRemoveTimer.restart();
rejectPasswordAnimation.start();
}
function onSucceeded() {
if (authenticator.hadPrompt) {
Qt.quit();
} else {
mainStack.replace(null, Qt.resolvedUrl("NoPasswordUnlock.qml"),
{
userListModel: users
},
StackView.Immediate,
);
mainStack.forceActiveFocus();
}
}
function onInfoMessageChanged() {
lockScreenUi.handleMessage(authenticator.infoMessage);
}
function onErrorMessageChanged() {
lockScreenUi.handleMessage(authenticator.errorMessage);
}
function onPromptChanged(msg) {
lockScreenUi.handleMessage(authenticator.prompt);
}
function onPromptForSecretChanged(msg) {
mainBlock.showPassword = false;
mainBlock.mainPasswordBox.forceActiveFocus();
}
}
SessionManagement {
id: sessionManagement
}
KeyboardIndicator.KeyState {
id: capsLockState
key: Qt.Key_CapsLock
}
Connections {
target: sessionManagement
function onAboutToSuspend() {
root.clearPassword();
}
}
RejectPasswordAnimation {
id: rejectPasswordAnimation
target: mainBlock
}
MouseArea {
id: lockScreenRoot
property bool uiVisible: false
property bool seenPositionChange: false
property bool blockUI: containsMouse && (mainStack.depth > 1 || mainBlock.mainPasswordBox.text.length > 0 || inputPanel.keyboardActive)
x: parent.x
y: parent.y
width: parent.width
height: parent.height
hoverEnabled: true
cursorShape: uiVisible ? Qt.ArrowCursor : Qt.BlankCursor
drag.filterChildren: true
onPressed: uiVisible = true;
onPositionChanged: {
uiVisible = seenPositionChange;
seenPositionChange = true;
}
onUiVisibleChanged: {
if (uiVisible) {
Window.window.requestActivate();
}
if (blockUI) {
fadeoutTimer.running = false;
} else if (uiVisible) {
fadeoutTimer.restart();
}
authenticator.startAuthenticating();
}
onBlockUIChanged: {
if (blockUI) {
fadeoutTimer.running = false;
uiVisible = true;
} else {
fadeoutTimer.restart();
}
}
onExited: {
uiVisible = false;
}
Keys.onEscapePressed: {
// If the escape key is pressed, kscreenlocker will turn off the screen.
// We do not want to show the password prompt in this case.
if (uiVisible) {
uiVisible = false;
if (inputPanel.keyboardActive) {
inputPanel.showHide();
}
root.clearPassword();
}
}
Keys.onPressed: event => {
uiVisible = true;
event.accepted = false;
}
Timer {
id: fadeoutTimer
interval: 10000
onTriggered: {
if (!lockScreenRoot.blockUI) {
mainBlock.mainPasswordBox.showPassword = false;
lockScreenRoot.uiVisible = false;
}
}
}
Timer {
id: notificationRemoveTimer
interval: 3000
onTriggered: root.notification = ""
}
Timer {
id: graceLockTimer
interval: 3000
onTriggered: {
root.clearPassword();
authenticator.startAuthenticating();
}
}
PropertyAnimation {
id: launchAnimation
target: lockScreenRoot
property: "opacity"
from: 0
to: 1
duration: Kirigami.Units.veryLongDuration * 2
}
Component.onCompleted: launchAnimation.start();
WallpaperFader {
anchors.fill: parent
state: lockScreenRoot.uiVisible ? "on" : "off"
source: wallpaper
mainStack: mainStack
footer: footer
clock: clock
alwaysShowClock: config.alwaysShowClock && !config.hideClockWhenIdle
}
DropShadow {
id: clockShadow
anchors.fill: clock
source: clock
visible: !lockScreenUi.softwareRendering && config.alwaysShowClock
radius: 7
verticalOffset: 0.8
samples: 15
spread: 0.2
color : Qt.rgba(0, 0, 0, 0.7)
opacity: lockScreenRoot.uiVisible ? 0 : 1
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.veryLongDuration * 2
easing.type: Easing.InOutQuad
}
}
}
Clock {
id: clock
property Item shadow: clockShadow
visible: y > 0 && config.alwaysShowClock
anchors.horizontalCenter: parent.horizontalCenter
y: (mainBlock.userList.y + mainStack.y)/2 - height/2
Layout.alignment: Qt.AlignBaseline
}
ListModel {
id: users
Component.onCompleted: {
users.append({
name: kscreenlocker_userName,
realName: kscreenlocker_userName,
icon: kscreenlocker_userImage !== ""
? "file://" + kscreenlocker_userImage.split("/").map(encodeURIComponent).join("/")
: "",
})
}
}
StackView {
id: mainStack
anchors {
left: parent.left
right: parent.right
}
height: lockScreenRoot.height + Kirigami.Units.gridUnit * 3
focus: true //StackView is an implicit focus scope, so we need to give this focus so the item inside will have it
// this isn't implicit, otherwise items still get processed for the scenegraph
visible: opacity > 0
initialItem: MainBlock {
id: mainBlock
lockScreenUiVisible: lockScreenRoot.uiVisible
showUserList: userList.y + mainStack.y > 0
enabled: !graceLockTimer.running
StackView.onStatusChanged: {
// prepare for presenting again to the user
if (StackView.status === StackView.Activating) {
mainPasswordBox.clear();
mainPasswordBox.focus = true;
root.notification = "";
}
}
userListModel: users
notificationMessage: {
const parts = [];
if (capsLockState.locked) {
parts.push(i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:status", "Caps Lock is on"));
}
if (root.notification) {
parts.push(root.notification);
}
return parts.join(" • ");
}
onPasswordResult: password => {
authenticator.respond(password)
}
actionItems: [
ActionButton {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Slee&p")
icon.name: "system-suspend"
onClicked: root.suspendToRam()
visible: root.suspendToRamSupported
},
ActionButton {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "&Hibernate")
icon.name: "system-suspend-hibernate"
onClicked: root.suspendToDisk()
visible: root.suspendToDiskSupported
},
ActionButton {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Switch &User")
icon.name: "system-switch-user"
onClicked: {
sessionManagement.switchUser();
}
visible: sessionManagement.canSwitchUser
}
]
Loader {
Layout.topMargin: Kirigami.Units.smallSpacing // some distance to the password field
Layout.fillWidth: true
Layout.preferredHeight: item ? item.implicitHeight : 0
active: config.showMediaControls
source: "MediaControls.qml"
}
}
}
VirtualKeyboardLoader {
id: inputPanel
z: 1
screenRoot: lockScreenRoot
mainStack: mainStack
mainBlock: mainBlock
passwordField: mainBlock.mainPasswordBox
}
Loader {
z: 2
active: root.viewVisible
source: "LockOsd.qml"
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: Kirigami.Units.gridUnit
}
}
// Note: Containment masks stretch clickable area of their buttons to
// the screen edges, essentially making them adhere to Fitts's law.
// Due to virtual keyboard button having an icon, buttons may have
// different heights, so fillHeight is required.
//
// Note for contributors: Keep this in sync with SDDM Main.qml footer.
RowLayout {
id: footer
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
margins: Kirigami.Units.smallSpacing
}
spacing: Kirigami.Units.smallSpacing
PlasmaComponents3.ToolButton {
id: virtualKeyboardButton
focusPolicy: Qt.TabFocus
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "Button to show/hide virtual keyboard", "Virtual Keyboard")
icon.name: inputPanel.keyboardActive ? "input-keyboard-virtual-on" : "input-keyboard-virtual-off"
onClicked: {
// Otherwise the password field loses focus and virtual keyboard
// keystrokes get eaten
mainBlock.mainPasswordBox.forceActiveFocus();
inputPanel.showHide()
}
visible: inputPanel.status === Loader.Ready
Layout.fillHeight: true
containmentMask: Item {
parent: virtualKeyboardButton
anchors.fill: parent
anchors.leftMargin: -footer.anchors.margins
anchors.bottomMargin: -footer.anchors.margins
}
}
PlasmaComponents3.ToolButton {
id: keyboardButton
focusPolicy: Qt.TabFocus
Accessible.description: i18ndc("plasma_shell_org.kde.plasma.desktop", "Button to change keyboard layout", "Switch layout")
icon.name: "input-keyboard"
PW.KeyboardLayoutSwitcher {
id: keyboardLayoutSwitcher
anchors.fill: parent
acceptedButtons: Qt.NoButton
}
text: keyboardLayoutSwitcher.layoutNames.longName
onClicked: keyboardLayoutSwitcher.keyboardLayout.switchToNextLayout()
visible: keyboardLayoutSwitcher.hasMultipleKeyboardLayouts
Layout.fillHeight: true
containmentMask: Item {
parent: keyboardButton
anchors.fill: parent
anchors.leftMargin: virtualKeyboardButton.visible ? 0 : -footer.anchors.margins
anchors.bottomMargin: -footer.anchors.margins
}
}
Item {
Layout.fillWidth: true
}
Battery {}
}
}
}

View File

@@ -0,0 +1,169 @@
/*
SPDX-FileCopyrightText: 2016 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.plasma.components as PlasmaComponents3
import org.kde.plasma.extras as PlasmaExtras
import org.kde.kirigami as Kirigami
import org.kde.kscreenlocker as ScreenLocker
import org.kde.breeze.components
SessionManagementScreen {
id: sessionManager
readonly property alias mainPasswordBox: passwordBox
property bool lockScreenUiVisible: false
property alias showPassword: passwordBox.showPassword
//the y position that should be ensured visible when the on screen keyboard is visible
property int visibleBoundary: mapFromItem(loginButton, 0, 0).y
onHeightChanged: visibleBoundary = mapFromItem(loginButton, 0, 0).y + loginButton.height + Kirigami.Units.smallSpacing
/*
* Login has been requested with the following username and password
* If username field is visible, it will be taken from that, otherwise from the "name" property of the currentIndex
*/
signal passwordResult(string password)
onUserSelected: {
const nextControl = (passwordBox.visible ? passwordBox : loginButton);
// Don't startLogin() here, because the signal is connected to the
// Escape key as well, for which it wouldn't make sense to trigger
// login. Using TabFocusReason, so that the loginButton gets the
// visual highlight.
nextControl.forceActiveFocus(Qt.TabFocusReason);
}
function startLogin() {
const password = passwordBox.text
// This is partly because it looks nicer, but more importantly it
// works round a Qt bug that can trigger if the app is closed with a
// TextField focused.
//
// See https://bugreports.qt.io/browse/QTBUG-55460
loginButton.forceActiveFocus();
passwordResult(password);
}
RowLayout {
Layout.fillWidth: true
PlasmaExtras.PasswordField {
id: passwordBox
font.pointSize: Kirigami.Theme.defaultFont.pointSize + 1
Layout.fillWidth: true
text: PasswordSync.password
placeholderText: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:placeholder in text field", "Password")
focus: true
enabled: !authenticator.graceLocked
// In Qt this is implicitly active based on focus rather than visibility
// in any other application having a focussed invisible object would be weird
// but here we are using to wake out of screensaver mode
// We need to explicitly disable cursor flashing to avoid unnecessary renders
cursorVisible: visible
onAccepted: {
if (sessionManager.lockScreenUiVisible) {
sessionManager.startLogin();
}
}
//if empty and left or right is pressed change selection in user switch
//this cannot be in keys.onLeftPressed as then it doesn't reach the password box
Keys.onPressed: event => {
if (event.key === Qt.Key_Left && !text) {
sessionManager.userList.decrementCurrentIndex();
event.accepted = true
}
if (event.key === Qt.Key_Right && !text) {
sessionManager.userList.incrementCurrentIndex();
event.accepted = true
}
}
Connections {
target: root
function onClearPassword() {
passwordBox.forceActiveFocus()
passwordBox.text = "";
passwordBox.text = Qt.binding(() => PasswordSync.password);
}
function onNotificationRepeated() {
sessionManager.playHighlightAnimation();
}
}
}
Binding {
target: PasswordSync
property: "password"
value: passwordBox.text
}
PlasmaComponents3.Button {
id: loginButton
Accessible.name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button accessible only", "Unlock")
Layout.preferredHeight: passwordBox.implicitHeight
Layout.preferredWidth: loginButton.Layout.preferredHeight
icon.name: LayoutMirroring.enabled ? "go-previous" : "go-next"
onClicked: sessionManager.startLogin()
Keys.onEnterPressed: clicked()
Keys.onReturnPressed: clicked()
}
}
component FailableLabel : PlasmaComponents3.Label {
id: _failableLabel
required property int kind
required property string label
visible: authenticator.authenticatorTypes & kind
text: label
textFormat: Text.PlainText
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
RejectPasswordAnimation {
id: _rejectAnimation
target: _failableLabel
onFinished: _timer.restart()
}
Connections {
target: authenticator
function onNoninteractiveError(kind, authenticator) {
if (kind & _failableLabel.kind) {
_failableLabel.text = Qt.binding(() => authenticator.errorMessage)
_rejectAnimation.start()
}
}
}
Timer {
id: _timer
interval: Kirigami.Units.humanMoment
onTriggered: {
_failableLabel.text = Qt.binding(() => _failableLabel.label)
}
}
}
FailableLabel {
kind: ScreenLocker.Authenticator.Fingerprint
label: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:usagetip", "(or scan your fingerprint on the reader)")
}
FailableLabel {
kind: ScreenLocker.Authenticator.Smartcard
label: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:usagetip", "(or scan your smartcard)")
}
}

View File

@@ -0,0 +1,118 @@
/*
SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.plasma.components as PlasmaComponents3
import org.kde.plasma.extras as PlasmaExtras
import org.kde.kirigami as Kirigami
import org.kde.plasma.private.mpris as Mpris
Item {
visible: instantiator.count > 0
implicitHeight: Kirigami.Units.gridUnit * 3
implicitWidth: Kirigami.Units.gridUnit * 16
Repeater {
id: instantiator
model: Mpris.MultiplexerModel { }
RowLayout {
id: controlsRow
anchors.fill: parent
spacing: 0
enabled: model.canControl
Image {
id: albumArt
Layout.preferredWidth: height
Layout.fillHeight: true
visible: status === Image.Loading || status === Image.Ready
asynchronous: true
fillMode: Image.PreserveAspectFit
source: model.artUrl
sourceSize.height: height * Screen.devicePixelRatio
}
Item { // spacer
implicitWidth: Kirigami.Units.smallSpacing
implicitHeight: 1
}
ColumnLayout {
Layout.fillWidth: true
spacing: 0
PlasmaComponents3.Label {
Layout.fillWidth: true
elide: Text.ElideRight
font.pointSize: Kirigami.Theme.defaultFont.pointSize + 1
maximumLineCount: 1
text: model.track.length > 0
? model.track
: (model.playbackStatus > Mpris.PlaybackStatus.Stopped
? i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:status", "No title")
: i18ndc("plasma_shell_org.kde.plasma.desktop", "@info:status", "No media playing"))
textFormat: Text.PlainText
wrapMode: Text.NoWrap
}
PlasmaExtras.DescriptiveLabel {
Layout.fillWidth: true
wrapMode: Text.NoWrap
elide: Text.ElideRight
// if no artist is given, show player name instead
text: model.artist || model.identity
textFormat: Text.PlainText
font.pointSize: Kirigami.Theme.smallFont.pointSize + 1
maximumLineCount: 1
}
}
PlasmaComponents3.ToolButton {
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
Layout.preferredWidth: Layout.preferredHeight
visible: model.canGoBack || model.canGoNext
enabled: model.canGoPrevious
focusPolicy: Qt.TabFocus
icon.name: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward"
onClicked: {
fadeoutTimer.running = false
model.container.Previous()
}
Accessible.name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button Accessible only", "Previous track")
}
PlasmaComponents3.ToolButton {
Layout.fillHeight: true
Layout.preferredWidth: height // make this button bigger
focusPolicy: Qt.TabFocus
icon.name: model.playbackStatus === Mpris.PlaybackStatus.Playing ? "media-playback-pause" : "media-playback-start"
onClicked: {
fadeoutTimer.running = false
model.container.PlayPause()
}
Accessible.name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button Accessible only", "Play or Pause media")
}
PlasmaComponents3.ToolButton {
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
Layout.preferredWidth: Layout.preferredHeight
visible: model.canGoBack || model.canGoNext
enabled: model.canGoNext
focusPolicy: Qt.TabFocus
icon.name: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward"
Accessible.name: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button Accessible only", "Next track")
onClicked: {
fadeoutTimer.running = false
model.container.Next()
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2022 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.plasma.components as PlasmaComponents3
import org.kde.breeze.components
SessionManagementScreen {
focus: true
PlasmaComponents3.Button {
id: loginButton
focus: true
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button no-password unlock", "Unlock")
icon.name: "unlock"
onClicked: Qt.quit();
Keys.onEnterPressed: clicked()
Keys.onReturnPressed: clicked()
}
Component.onCompleted: {
forceActiveFocus();
}
}

View File

@@ -0,0 +1,13 @@
/*
SPDX-FileCopyrightText: 2025 Yifan Zhu <fanzhuyifan@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
pragma Singleton
import QtQuick
QtObject {
property string password
}

View File

@@ -0,0 +1,79 @@
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCM
Kirigami.FormLayout {
id: configForm
// TODO Plasma 7: Make this an enum.
property bool cfg_alwaysShowClock
property bool cfg_hideClockWhenIdle
property bool cfg_alwaysShowClockDefault: true
property bool cfg_hideClockWhenIdleDefault: false
property alias cfg_showMediaControls: showMediaControls.checked
property bool cfg_showMediaControlsDefault: false
twinFormLayouts: parentLayout
QQC2.RadioButton {
Kirigami.FormData.label: i18ndc("plasma_shell_org.kde.plasma.desktop",
"@title: group",
"Show clock:")
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@option:radio Clock always shown", "Always")
Accessible.name: i18nc("@option:radio", "Always show clock")
checked: configForm.cfg_alwaysShowClock && !configForm.cfg_hideClockWhenIdle
onToggled: {
configForm.cfg_alwaysShowClock = true;
configForm.cfg_hideClockWhenIdle = false;
}
KCM.SettingHighlighter {
id: clockAlwaysHighlighter
highlight: configForm.cfg_alwaysShowClock != configForm.cfg_alwaysShowClockDefault
|| configForm.cfg_hideClockWhenIdle != configForm.cfg_hideClockWhenIdleDefault
}
}
QQC2.RadioButton {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@option:radio Clock shown only while unlock prompt is visible", "On unlocking prompt")
Accessible.name: i18nc("@option:radio", "Show clock only on unlocking prompt")
checked: configForm.cfg_alwaysShowClock && configForm.cfg_hideClockWhenIdle
onToggled: {
configForm.cfg_alwaysShowClock = true;
configForm.cfg_hideClockWhenIdle = true;
}
KCM.SettingHighlighter {
highlight: clockAlwaysHighlighter.highlight
}
}
QQC2.RadioButton {
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@option:radio Clock never shown", "Never")
Accessible.name: i18nc("@option:radio", "Never show clock")
checked: !configForm.cfg_alwaysShowClock
onToggled: {
configForm.cfg_alwaysShowClock = false;
}
KCM.SettingHighlighter {
highlight: clockAlwaysHighlighter.highlight
}
}
QQC2.CheckBox {
id: showMediaControls
Kirigami.FormData.label: i18ndc("plasma_shell_org.kde.plasma.desktop",
"@title: group UI controls for playback of multimedia content",
"Media controls:")
text: i18ndc("plasma_shell_org.kde.plasma.desktop",
"@option:check",
"Show under unlocking prompt")
KCM.SettingHighlighter {
highlight: configForm.cfg_showMediaControlsDefault != configForm.cfg_showMediaControls
}
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<kcfgfile name=""/>
<group name="General">
<!-- TODO Plasma 7: Make this an enum. -->
<entry name="alwaysShowClock" type="Bool">
<label>Show a clock.</label>
<default>true</default>
</entry>
<entry name="hideClockWhenIdle" type="Bool">
<label>Hide clock when prompt is hidden.</label>
<default>false</default>
</entry>
<entry name="showMediaControls" type="Bool">
<label>If true, shows any currently playing media along with controls to pause it.</label>
<default>true</default>
</entry>
</group>
</kcfg>

View File

@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: 2025 Yifan Zhu <fanzhuyifan@gmail.com>
# SPDX-License-Identifier: GPL-2.0-or-later
singleton PasswordSync 1.0 PasswordSync.qml

View File

@@ -0,0 +1,9 @@
loadTemplate("org.kde.plasma.desktop.defaultPanel")
var desktopsArray = desktopsForActivity(currentActivity());
for( var j = 0; j < desktopsArray.length; j++) {
desktopsArray[j].wallpaperPlugin = 'org.kde.image';
//var clock = desktopsArray[j].addWidget("org.kde.plasma.analogclock");
}

View File

@@ -0,0 +1,51 @@
/*
00-start-here-kde-fedora.js - Set launcher icon to start-here-kde-fedora
Copyright (C) 2010 Kevin Kofler <kevin.kofler@chello.at>
Copyright (C) 2010 Rex Dieter <rdieter@fedoraproject.org>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Portions lifted from 01-kubuntu-10.04.js:
Harald Sitter, apachelogger@ubuntu.com 2010-04-02
Jonathan Riddell, jriddell@ubuntu.com 2010-02-18
Copyright Canonical Ltd, may be copied under the GNU GPL 2 or later
*/
launcherFound = false;
pids = panelIds;
for (i = 0; i < pids.length; ++i) {
p = panelById(pids[i]);
if (!p) continue;
ids = p.widgetIds;
for (j = 0; j < ids.length; ++j) {
w = p.widgetById(ids[j]);
if (!w) continue;
if ( w.type != "org.kde.plasma.kickoff" &&
w.type != "org.kde.plasma.kicker" &&
w.type != "org.kde.plasma.kickerdash" )
continue;
launcherFound = true;
if ( w.readConfig("icon", "start-here-kde") == "start-here-kde-fedora" ) {
w.currentConfigGroup = ["General"];
w.writeConfig("icon", "start-here");
}
break;
}
if (launcherFound) break;
}
if (!launcherFound)
print("No launcher found");

View File

@@ -0,0 +1,31 @@
const allPanels = panels();
for (let i = 0; i < allPanels.length; ++i) {
const panel = allPanels[i];
const widgets = panel.widgets();
for (let j = 0; j < widgets.length; ++j) {
const widget = widgets[j];
if (widget.type === "org.kde.plasma.icontasks") {
widget.currentConfigGroup = ["General"];
// Read the current launchers value
const currentLaunchers = widget.readConfig("launchers", "");
// Only set our default if launchers is empty
if (!currentLaunchers || currentLaunchers.trim() === "") {
widget.writeConfig("launchers", [
"preferred://browser",
"applications:steam.desktop",
"applications:net.lutris.Lutris.desktop",
"applications:org.kde.konsole.desktop",
"applications:io.github.kolunmi.Bazaar.desktop",
"preferred://filemanager"
]);
widget.reloadConfig();
}
}
}
}

View File

@@ -0,0 +1,15 @@
// MidButton got deprecated and doesn't really work anymore as a stringified enum value
// for the middle button Qt::MouseButton, we need to update our config to "MiddleButton"
var plasmaConfig = ConfigFile("plasma-org.kde.plasma.desktop-appletsrc", "ActionPlugins");
for (let i in plasmaConfig.groupList) {
let subGroup = ConfigFile(plasmaConfig, plasmaConfig.groupList[i])
for (let j in subGroup.keyList) {
let key = subGroup.keyList[j];
if (key.indexOf("MidButton") !== -1) {
subGroup.writeEntry(key.replace("MidButton", "MiddleButton"), subGroup.readEntry(key));
subGroup.deleteEntry(key);
}
}
}

View File

@@ -0,0 +1,53 @@
/*
SPDX-FileCopyrightText: 2022 Jin Liu <ad.liu.jin@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
/*
Plasma 5.26 introduced a new config entry autoFontAndSize which defaults to true.
This means if the user customized font before (fontFamily, boldText, italicText),
in 5.26 these settings are ignored.
So we need to set autoFontAndSize=false if:
1. Any of these 3 old entries above is set.
2. No new entries introduced in 5.26 (autoFontAndSize, fontSize, fontWeight, fontStyleName)
are set, so this is a config from 5.25.
And fontWeight should be set to 75 (Font.Bold) if boldText==true.
See https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/1809
*/
const containments = desktops().concat(panels());
for (var i in containments) {
var cont = containments[i];
const widgets = cont.widgets();
for (var j in widgets) {
var widget = widgets[j];
if (widget.type == "org.kde.plasma.digitalclock") {
widget.currentConfigGroup = new Array('Appearance')
if ((widget.readConfig("fontFamily", "").length > 0
|| widget.readConfig("boldText", false)
|| widget.readConfig("italicText", false))
&&
(widget.readConfig("autoFontAndSize", true)
&& widget.readConfig("fontSize", 10) === 10
&& widget.readConfig("fontWeight", 50) === 50
&& widget.readConfig("fontStyleName", "").length === 0)) {
widget.writeConfig("autoFontAndSize", false)
if (widget.readConfig("boldText", false)) {
widget.writeConfig("fontWeight", 75)
}
// Set the font size to the largest value (72) in the font dialog,
// so the font autofits the panel when the panel height is less
// than 72pt. This should keep 5.25's autosize behavior for custom
// font.
// For panels taller than 72pt, with custom font set in 5.25, the
// digital clock's look may still change, though.
widget.writeConfig("fontSize", 72)
}
}
}
}

View File

@@ -0,0 +1,21 @@
/*
SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
/**
@c showSeconds option now supports showing seconds only in the tooltip.
@see https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/2232
@since 6.0
*/
const containments = desktops().concat(panels());
containments.forEach(containment => containment.widgets("org.kde.plasma.digitalclock").forEach(widget => {
widget.currentConfigGroup = ["Appearance"];
if (widget.readConfig("showSeconds", false /* Default: never show seconds */) === true /* Changed by the user */) {
widget.writeConfig("showSeconds", 2 /* Always show seconds */);
}
}));

View File

@@ -0,0 +1,23 @@
// Find all digital clock applets in all containments and change
// displayTimezoneAsCode=false
// to
// displayTimezoneFormat=FullText
// See https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/751
var containments = desktops().concat(panels());
for (var i in containments) {
var cont = containments[i];
for (var j in cont.widgetIds) {
var widget = cont.widgetById(cont.widgetIds[j]);
if (widget.type == "org.kde.plasma.digitalclock") {
widget.currentConfigGroup = new Array('Appearance')
if (widget.readConfig("displayTimezoneAsCode", true) == false) {
widget.writeConfig("displayTimezoneFormat", "FullText")
// Work around not being able to delete config file keys using widget interface
widget.writeConfig("displayTimezoneAsCode", "")
}
}
}
}

View File

@@ -0,0 +1,109 @@
/* vim:set foldmethod=marker:
SPDX-FileCopyrightText: 2023 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
function filterDisabled(entries) {
let filteredEntries = [];
// 0 = screen, 1 = activity, 2 = how many entries, 3 = desktop entry
let state = 0;
let entriesForCurrentScreen = 0;
let currentScreen = -1;
let currentActivity = "";
let currentEntrtriesNumber = 0;
let currentEntry = 0;
let currentEntries = [];
for (let e of entries) {
switch (state) {
case 0: // Screen
currentScreen = e;
state = 1;
break;
case 1: // Activity
currentActivity = e;
state = 2;
break;
case 2: // Entries number
currentEntrtriesNumber = Number(e);
state = 3;
break;
case 3: // Desktop file
if (e.indexOf("desktop:/") !== 0) { // User has a folderview not in desktop:/
currentEntries.push(e);
currentEntry++;
} else {
let count = (e.match(/\//g) || []).length;
if (count == 1) {
currentEntries.push(e);
currentEntry++;
} else {
currentEntrtriesNumber--;
}
}
if (currentEntry === currentEntrtriesNumber) {
state = 0;
if (currentEntries.length > 0) {
filteredEntries = filteredEntries.concat([currentScreen, currentActivity, currentEntrtriesNumber]);
filteredEntries = filteredEntries.concat(currentEntries);
currentEntries = [];
}
}
break;
}
}
return filteredEntries;
}
function filterEnabled(entries) {
let filteredEntries = [];
// 0 = desktop entry, 1 = screen 2 = activity
let state = 0;
let shouldDrop = false; //true when this entry should be dropped
for (let e of entries) {
switch (state) {
case 0: // Desktop file
if (e.indexOf("desktop:/") !== 0) { // User has a folderview not in desktop:/
filteredEntries.push(e);
shouldDrop = false;
} else {
let count = (e.match(/\//g) || []).length;
if (count == 1) {
filteredEntries.push(e);
shouldDrop = false;
} else {
shouldDrop = true;
}
}
break;
case 1: // Screen
case 2: // Activity
if (!shouldDrop) {
filteredEntries.push(e);
}
}
state = (state + 1) % 3;
}
return filteredEntries;
}
const config = ConfigFile('plasma-org.kde.plasma.desktop-appletsrc');
config.group = 'ScreenMapping';
let entries = config.readEntry("itemsOnDisabledScreens").split(",");
let filteredEntries = filterDisabled(entries);
config.writeEntry("itemsOnDisabledScreens", filteredEntries.join(","));
entries = config.readEntry("screenMapping").split(",");
filteredEntries = filterEnabled(entries);
config.writeEntry("screenMapping", filteredEntries.join(","));

View File

@@ -0,0 +1,33 @@
// Find all Keyboard Layout applets in all containments and change
// showFlag=true
// to
// displayStyle=Flag
// See https://invent.kde.org/plasma/plasma-workspace/-/merge_requests/1131
const containments = desktops().concat(panels());
for (var i in containments) {
forEachWidgetInContainment(containments[i]);
}
function forEachWidgetInContainment(containment) {
const widgets = containment.widgets();
for (var i in widgets) {
const widget = widgets[i];
switch(widget.type) {
case "org.kde.plasma.systemtray":
systemtrayId = widget.readConfig("SystrayContainmentId");
if (systemtrayId) {
forEachWidgetInContainment(desktopById(systemtrayId))
}
break;
case "org.kde.plasma.keyboardlayout":
widget.currentConfigGroup = new Array('General')
if (widget.readConfig("showFlag", false) == true) {
widget.writeConfig("displayStyle", "Flag")
// Work around not being able to delete config file keys using widget interface
widget.writeConfig("showFlag", "")
}
break;
}
}
}

View File

@@ -0,0 +1,25 @@
const containments = desktops().concat(panels());
for (var i in containments) {
forEachWidgetInContainment(containments[i]);
}
function forEachWidgetInContainment(containment) {
const widgets = containment.widgets();
for (var i in widgets) {
const widget = widgets[i];
switch(widget.type) {
case "org.kde.plasma.systemtray":
systemtrayId = widget.readConfig("SystrayContainmentId");
if (systemtrayId) {
forEachWidgetInContainment(desktopById(systemtrayId))
}
break;
case "org.kde.plasma.keyboardlayout":
if (widget.globalShortcut) {
print("Shortcut to remove: " + widget.globalShortcut);
widget.globalShortcut = "";
}
break;
}
}
}

View File

@@ -0,0 +1,14 @@
/*
Previously, Klipper "clear history" dialog used to not ask again even if user answered No.
This was changed so that only a Yes answer will lead to no more asking, by using warningContinueCancel instead of questionYesNo.
Now the behaviour of previous config value really_clear_history has inverted: true/undefined => ask again; false => don't ask.
This update script migrates old configs to use the new config value, renamed to klipperClearHistoryAskAgain.
*/
config = ConfigFile("plasmashellrc", "Notification Messages");
oldVal = config.readEntry("really_clear_history");
if (oldVal === "true") {
// Clear and don't ask again -- preserve this choice
config.writeEntry("klipperClearHistoryAskAgain", false)
}
config.deleteEntry("really_clear_history");

View File

@@ -0,0 +1,46 @@
// This script updates users' Folder View icon sizes following a change in what
// they mean in https://invent.kde.org/plasma/plasma-desktop/-/merge_requests/111
var allDesktops = desktops();
for (var i = 0; i < allDesktops.length; ++i) {
var desktop = allDesktops[i];
desktop.currentConfigGroup = ["General"];
var currentIconSize = desktop.readConfig("iconSize");
// Don't do anything if there is no value in the config file, since in this
// case, no change is needed because the new default works properly
if (currentIconSize) {
currentIconSize = parseInt(currentIconSize);
// No change needed for iconSize=0 or 5
if (currentIconSize != 0 && currentIconSize != 5) {
print("Current icon size is " + currentIconSize);
var newIconSize = 3;
switch(currentIconSize) {
case 1:
newIconSize = 0;
break;
case 2:
newIconSize = 1;
break;
case 3:
newIconSize = 2;
break;
case 4:
newIconSize = 3;
break;
// We should never reach here, but in case we do anyway, reset to
// the default value
default:
break;
}
desktop.writeConfig("iconSize", newIconSize);
desktop.reloadConfig()
}
}
}

View File

@@ -0,0 +1,23 @@
/*
SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
/**
The Media Frame widget removes the useBackground option and uses ConfigurableBackground
hint to support toggling the background directly from the widget toolbar.
The option only applies to media frame widgets on desktop.
@see https://invent.kde.org/plasma/kdeplasma-addons/-/merge_requests/238
@since 5.27
*/
desktops().forEach(containment => containment.widgets("org.kde.plasma.mediaframe").forEach(widget => {
widget.currentConfigGroup = ["General"];
if (widget.readConfig("useBackground", true /* Default */) === false /* Changed by the user */) {
widget.writeConfig("useBackground", "");
widget.currentConfigGroup = []; // Root Configuration
widget.writeConfig("UserBackgroundHints", "NoBackground");
}
}));

View File

@@ -0,0 +1,44 @@
/*
SPDX-FileCopyrightText: 2023 Akseli Lahtinen <akselmo@akselmo.dev>
SPDX-License-Identifier: GPL-2.0-or-later
*/
// Find all depicted widgets and migrate their font weights from qt5 to qt6 style
var containments = desktops().concat(panels());
for (var c in containments) {
const cont = containments[c];
const widgets = cont.widgets();
for (var w in widgets) {
var widget = widgets[w];
switch(widget.type) {
case "org.kde.plasma.digitalclock":
widget.currentConfigGroup = ['Appearance'];
// Use "normal" weight as default if weight is not set
const oldFontWeight = widget.readConfig("fontWeight", 400);
const newFontWeight = migrateFontWeight(Number(oldFontWeight));
widget.writeConfig("fontWeight", newFontWeight);
break;
}
}
}
function migrateFontWeight(oldWeight) {
// Takes old weight (Qt5 weight) and returns the Qt6 equivalent
// Qt5 font weights: https://doc.qt.io/qt-5/qfont.html#Weight-enum
// Qt6 font weights: https://doc.qt.io/qt-6/qfont.html#Weight-enum
var newWeight = 400;
if (oldWeight === 0) { newWeight = 100; }
else if (oldWeight === 12) { newWeight = 200; }
else if (oldWeight === 25) { newWeight = 300; }
else if (oldWeight === 50) { newWeight = 400; }
else if (oldWeight === 57) { newWeight = 500; }
else if (oldWeight === 63) { newWeight = 600; }
else if (oldWeight === 75) { newWeight = 700; }
else if (oldWeight === 81) { newWeight = 800; }
else if (oldWeight === 87) { newWeight = 900; }
return newWeight;
}

View File

@@ -0,0 +1,10 @@
var allDesktops = desktops();
for (var i = 0; i < allDesktops.length; ++i) {
var desktop = allDesktops[i];
desktop.currentConfigGroup = ["General"];
var serializedItems = desktop.readConfig("ItemsGeometries");
desktop.currentConfigGroup = [];
desktop.writeConfig("ItemGeometriesHorizontal", serializedItems);
desktop.reloadConfig()
}

View File

@@ -0,0 +1,26 @@
// In the past, panels were configured to add a note on middle-click. This was changed,
// but the "MiddleButton;NoModifier=org.kde.paste" action was never removed from the
// config file, so some people still got this undesirable behavior with no GIU method
// to change it.
//
// This script removes it.
var plasmaConfig = ConfigFile("plasma-org.kde.plasma.desktop-appletsrc", "ActionPlugins");
for (let i in plasmaConfig.groupList) {
let subSubGroupKeys = [];
let subGroup = ConfigFile(plasmaConfig, plasmaConfig.groupList[i]);
for (let j in subGroup.groupList) {
let subSubGroup = ConfigFile(subGroup, subGroup.groupList[j]);
subSubGroupKeys = subSubGroup.keyList;
}
if (subSubGroupKeys.indexOf("_sep1") === -1) {
print("Containment " + i + " Does not have a _sep1 item; it must be a panel.\n");
// No _sep1 item; this must be a panel
let mmbAction = subGroup.readEntry("MiddleButton;NoModifier");
if (mmbAction === "org.kde.paste") {
print("Panel " + i + " Seems to have a middle-click paste action defined; deleting it!");
subGroup.deleteEntry("MiddleButton;NoModifier");
}
}
}

View File

@@ -0,0 +1,39 @@
function swapWidget(cont, oldWidget, newType, geometry) {
oldWidget.remove();
cont.addWidget(newType, geometry.x, geometry.y, geometry.width, geometry.height);
}
var containments = desktops().concat(panels());
for (var i in containments) {
var cont = containments[i];
for (var j in cont.widgetIds) {
var widget = cont.widgetById(cont.widgetIds[j]);
let newType = ""
if (widget.type == "org.kde.plasma.systemloadviewer") {
let geometry = widget.geometry;
geometry.width = geometry.width/3
widget.remove();
cont.addWidget("org.kde.plasma.systemmonitor.cpuusage", geometry.x, geometry.y, geometry.width, geometry.height);
geometry.x += geometry.width;
cont.addWidget("org.kde.plasma.systemmonitor.memoryusage", geometry.x, geometry.y, geometry.width, geometry.height);
geometry.x += geometry.width;
let swapWidget = cont.addWidget("org.kde.plasma.systemmonitor", geometry.x, geometry.y, geometry.width, geometry.height);
swapWidget.currentConfigGroup = ["Appearance"];
swapWidget.writeConfig("title", "Swap");
swapWidget.currentConfigGroup = ["Sensors"];
swapWidget.writeConfig("highPrioritySensorIds", "[\"mem/swap/used\",\"mem/swap/free\"]");
swapWidget.writeConfig("totalSensors", "[\"mem/swap/used\"]");
swapWidget.currentConfigGroup = ["SensorColors"];
swapWidget.writeConfig("mem/swap/free", "230,230,230");
swapWidget.reloadconfiguration();
}
}
}

View File

@@ -0,0 +1,17 @@
// Update all applets with wheelEnabled=true to wheelEnabled=AllTask
var containments = desktops().concat(panels());
containments.forEach(function(cont) {
cont.widgetIds.forEach(function(id) {
var widget = cont.widgetById(id);
widget.currentConfigGroup = new Array("General");
if (widget.readConfig("wheelEnabled", false) === true) {
widget.writeConfig("wheelEnabled", "AllTask");
widget.reloadConfig();
}
});
});

View File

@@ -0,0 +1,2 @@
__AppInterface.locked = false;

View File

@@ -0,0 +1,419 @@
/*
SPDX-FileCopyrightText: 2012 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2014 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.plasma.core as PlasmaCore
import org.kde.kwindowsystem
import org.kde.plasma.activityswitcher as ActivitySwitcher
import "../activitymanager"
import "../explorer"
import org.kde.kirigami as Kirigami
Item {
id: root
property Item containment
property QtObject widgetExplorer
Connections {
target: ActivitySwitcher.Backend
function onShouldShowSwitcherChanged(): void {
if (ActivitySwitcher.Backend.shouldShowSwitcher) {
if (sidePanelStack.state !== "activityManager") {
root.toggleActivityManager();
}
} else {
if (sidePanelStack.state === "activityManager") {
root.toggleActivityManager();
}
}
}
}
function toggleWidgetExplorer(containment) {
if (sidePanelStack.state === "widgetExplorer") {
sidePanelStack.state = "closed";
} else {
sidePanelStack.state = "widgetExplorer";
sidePanelStack.setSource(Qt.resolvedUrl("../explorer/WidgetExplorer.qml"), {
containment,
sidePanel,
});
}
}
function toggleActivityManager() {
if (sidePanelStack.state === "activityManager") {
sidePanelStack.state = "closed";
} else {
sidePanelStack.state = "activityManager";
sidePanelStack.setSource(Qt.resolvedUrl("../activitymanager/ActivityManager.qml"));
}
}
readonly property rect editModeRect: {
if (!containment) {
return Qt.rect(0,0,0,0);
}
let screenRect = desktop.strictAvailableScreenRect;
let panelConfigRect = Qt.rect(0,0,0,0);
if (containment.plasmoid.corona.panelBeingConfigured
&& containment.plasmoid.corona.panelBeingConfigured.screenToFollow === desktop.screenToFollow) {
panelConfigRect = containment.plasmoid.corona.panelBeingConfigured.relativeConfigRect;
}
if (panelConfigRect.width <= 0) {
; // Do nothing
} else if (panelConfigRect.x > width - (panelConfigRect.x + panelConfigRect.width)) {
screenRect = Qt.rect(screenRect.x, screenRect.y, panelConfigRect.x - screenRect.x, screenRect.height);
} else {
const diff = Math.max(0, panelConfigRect.x + panelConfigRect.width - screenRect.x);
screenRect = Qt.rect(Math.max(screenRect.x, panelConfigRect.x + panelConfigRect.width), screenRect.y, screenRect.width - diff, screenRect.height);
}
if (sidePanel.visible) {
if (sidePanel.sideBarOnRightEdge) {
screenRect = Qt.rect(screenRect.x, screenRect.y, screenRect.width - sidePanel.width, screenRect.height);
} else {
screenRect = Qt.rect(screenRect.x + sidePanel.width, screenRect.y, screenRect.width - sidePanel.width, screenRect.height);
}
}
return screenRect;
}
MouseArea {
id: desktopMouseArea
anchors.fill: parent
onClicked: containment.plasmoid.corona.editMode = false
}
MouseArea {
id: containmentParent
x: editModeLoader.item ? editModeLoader.item.centerX - width / 2 : 0
y: editModeLoader.item ? editModeLoader.item.centerY - height / 2 : 0
width: root.width
height: root.height
readonly property real extraScale: desktop.configuredPanel || sidePanel.visible ? 0.95 : 0.9
property real scaleFactor: Math.min(editModeRect.width / root.width, editModeRect.height / root.height) * extraScale
scale: containment?.plasmoid.corona.editMode ? scaleFactor : 1
}
Loader {
id: editModeLoader
anchors.fill: parent
sourceComponent: DesktopEditMode {}
active: containment?.plasmoid.corona.editMode || editModeUiTimer.running
Timer {
id: editModeUiTimer
property bool editMode: containment?.plasmoid.corona.editMode ?? false
onEditModeChanged: restart()
interval: 200
}
}
Loader {
id: wallpaperColors
active: root.containment && root.containment.wallpaper && desktop.usedInAccentColor
asynchronous: true
sourceComponent: Kirigami.ImageColors {
id: imageColors
source: root.containment.wallpaper
readonly property color backgroundColor: Kirigami.Theme.backgroundColor
readonly property color textColor: Kirigami.Theme.textColor
// Ignore the initial dominant color
onPaletteChanged: {
if (!Qt.colorEqual(root.containment.wallpaper.accentColor, "transparent")) {
desktop.accentColor = root.containment.wallpaper.accentColor;
}
if (this.palette.length === 0) {
desktop.accentColor = "transparent";
} else {
desktop.accentColor = this.dominant;
}
}
Kirigami.Theme.inherit: false
Kirigami.Theme.backgroundColor: backgroundColor
Kirigami.Theme.textColor: textColor
onBackgroundColorChanged: Qt.callLater(update)
onTextColorChanged: Qt.callLater(update)
readonly property Connections __repaintConnection: Connections {
target: root.containment.wallpaper
function onAccentColorChanged() {
if (Qt.colorEqual(root.containment.wallpaper.accentColor, "transparent")) {
imageColors.update();
} else {
imageColors.paletteChanged();
}
}
}
}
}
Timer {
id: pendingUninstallTimer
// keeps track of the applets the user wants to uninstall
property list<string> applets: []
function uninstall() {
for (const applet of applets) {
widgetExplorer.uninstall(applet);
}
applets = [];
if (sidePanelStack.state !== "widgetExplorer" && widgetExplorer) {
widgetExplorer.destroy();
widgetExplorer = null;
}
}
interval: 60000 // one minute
onTriggered: uninstall()
}
PlasmaCore.Dialog {
id: sidePanel
// If we are currently in edit mode, all panels are being shown
// and we use the strictAvailableScreenRect, which accounts for all
// of them. If we're not configuring anything, we instead use the
// entire screen rect, without fear of overlapping panels.
property var referenceRect: containment?.plasmoid.corona.editMode ? desktop.strictAvailableScreenRect : Qt.rect(0, 0, desktop.screenGeometry.width, desktop.screenGeometry.height)
readonly property bool sideBarOnRightEdge: {
if (!sidePanelStack.active) {
return false;
}
const item = sidePanelStack.item;
if (!item) {
return false;
}
const rightEdgeParent = (item.containment
&& item.containment !== containment.plasmoid
&& item.containment.location == PlasmaCore.Types.RightEdge);
return rightEdgeParent || Application.layoutDirection === Qt.RightToLeft;
}
location: sideBarOnRightEdge ? PlasmaCore.Types.RightEdge : PlasmaCore.Types.LeftEdge
type: PlasmaCore.Dialog.Dock
flags: Qt.WindowStaysOnTopHint
hideOnWindowDeactivate: true
x: {
let result = desktop.x;
if (!containment) {
return result;
}
const rect = referenceRect;
result += rect.x;
if (sideBarOnRightEdge) {
result += rect.width - sidePanel.width;
}
return result;
}
y: desktop.y + (containment ? referenceRect.y : 0)
onVisibleChanged: {
if (!visible) {
sidePanelStack.state = "closed";
ActivitySwitcher.Backend.shouldShowSwitcher = false;
}
}
mainItem: Loader {
id: sidePanelStack
asynchronous: true
width: item ? item.width : 0
height: containment ? sidePanel.referenceRect.height - sidePanel.margins.top - sidePanel.margins.bottom : 1000
state: "closed"
function bindingWithItem(callback: var, defaults: var): var {
return Qt.binding(() => {
const item = this.item;
return item !== null ? callback(item) : defaults;
});
}
LayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
onLoaded: {
if (item) {
item.closed.connect(() => {
state = "closed";
});
switch (state) {
case "activityManager":
item.showSwitcherOnly = ActivitySwitcher.Backend.shouldShowSwitcher;
sidePanel.hideOnWindowDeactivate = bindingWithItem(
item => !ActivitySwitcher.Backend.shouldShowSwitcher && !item.showingDialog,
false,
);
item.forceActiveFocus();
break;
case "widgetExplorer":
sidePanel.hideOnWindowDeactivate = bindingWithItem(item => !item.preventWindowHide, false);
sidePanel.opacity = bindingWithItem(item => item.opacity, 1);
sidePanel.outputOnly = bindingWithItem(item => item.outputOnly, false);
break;
default:
sidePanel.hideOnWindowDeactivate = true;
break;
}
}
sidePanel.visible = true;
if (KWindowSystem.isPlatformX11) {
KX11Extras.forceActiveWindow(sidePanel);
}
}
onStateChanged: {
if (state === "closed") {
sidePanel.visible = false;
source = ""; //unload all elements
}
}
}
}
Connections {
target: desktop
function onStrictAvailableScreenRectChanged() {
if (sidePanel.visible) {
sidePanel.requestActivate();
}
}
}
onContainmentChanged: {
if (containment === null) {
return;
}
containment.parent = containmentParent
if (switchAnim.running) {
//If the animation was still running, stop it and reset
//everything so that a consistent state can be kept
switchAnim.running = false;
internal.newContainment.visible = false;
internal.oldContainment.visible = false;
internal.oldContainment = null;
}
internal.newContainment = containment;
containment.visible = true;
if (internal.oldContainment !== null && internal.oldContainment !== containment) {
switchAnim.running = true;
} else {
containment.anchors.left = containmentParent.left;
containment.anchors.top = containmentParent.top;
containment.anchors.right = containmentParent.right;
containment.anchors.bottom = containmentParent.bottom;
if (internal.oldContainment) {
internal.oldContainment.visible = false;
}
internal.oldContainment = containment;
}
}
//some properties that shouldn't be accessible from elsewhere
QtObject {
id: internal
property Item oldContainment: null
property Item newContainment: null
}
SequentialAnimation {
id: switchAnim
ScriptAction {
script: {
if (containment) {
containment.z = 1;
containment.x = root.width;
}
if (internal.oldContainment) {
internal.oldContainment.z = 0;
internal.oldContainment.x = 0;
}
}
}
ParallelAnimation {
NumberAnimation {
target: internal.oldContainment
properties: "x"
to: internal.newContainment != null ? -root.width : 0
duration: Kirigami.Units.veryLongDuration
easing.type: Easing.InOutQuad
}
NumberAnimation {
target: internal.newContainment
properties: "x"
to: 0
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
ScriptAction {
script: {
if (internal.oldContainment) {
internal.oldContainment.visible = false;
}
if (containment) {
internal.oldContainment = containment;
}
}
}
}
Loader {
id: previewBannerLoader
function shouldBeActive(): bool {
// Loader::active is true by default at the time of creation, so
// it shouldn't be used in other bindings as a guard.
return root.containment !== null && (desktop.showPreviewBanner ?? false);
}
readonly property point pos: root.containment?.plasmoid.availableScreenRegion, shouldBeActive() && item !== null
? root.containment.adjustToAvailableScreenRegion(
root.containment.width + root.containment.x - item.width - Kirigami.Units.largeSpacing,
root.containment.height + root.containment.y - item.height - Kirigami.Units.largeSpacing,
item.width + Kirigami.Units.largeSpacing,
item.height + Kirigami.Units.largeSpacing)
: Qt.point(0, 0)
x: pos.x
y: pos.y
z: (root.containment?.z ?? 0) + 1
active: shouldBeActive()
visible: active
source: "PreviewBanner.qml"
}
}

View File

@@ -0,0 +1,183 @@
/*
SPDX-FileCopyrightText: 2024 Marco Martin <mart@kde.org>
SPDX-FileCopyrightText: 2014 David Edmundson <davidedmundson@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Effects
import QtQuick.Layouts
import org.kde.plasma.core as PlasmaCore
import org.kde.plasma.components as PC
import org.kde.kirigami as Kirigami
import org.kde.kcmutils as KCM
Item {
id: editModeItem
property real centerX: Math.round(editModeUi.x + editModeUi.width/2)
property real centerY: Math.round(editModeUi.y + editModeUi.height/2)
property real roundedRootWidth: Math.round(root.width)
property real roundedRootHeight: Math.round(root.height)
property bool open: false
Component.onCompleted: {
open = Qt.binding(() => {return containment.plasmoid.corona.editMode})
}
// Those 2 elements have the same parameters as the overview effect
MultiEffect {
source: containment
anchors.fill: parent
blurEnabled: true
blurMax: 64
blur: 1.0
}
Rectangle {
anchors.fill: parent
color: Kirigami.Theme.backgroundColor
opacity: 0.7
}
Item {
id: editModeUi
visible: editModeItem.open || xAnim.running
x: Math.round(editModeItem.open ? editModeRect.x + editModeRect.width/2 - zoomedWidth/2 : 0)
y: Math.round(editModeItem.open ? editModeRect.y + editModeRect.height/2 - zoomedHeight/2 + toolBar.height/2 : 0)
width: editModeItem.open ? zoomedWidth : editModeItem.roundedRootWidth
height: editModeItem.open ? zoomedHeight : editModeItem.roundedRootHeight
property real zoomedWidth: Math.round(root.width * containmentParent.scaleFactor)
property real zoomedHeight: Math.round(root.height * containmentParent.scaleFactor)
Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
width: Math.round(parent.width)
height: Math.round(parent.height + toolBar.height + Kirigami.Units.largeSpacing)
y: - toolBar.height - Kirigami.Units.largeSpacing
radius: editModeItem.open ? Kirigami.Units.cornerRadius : 0
Behavior on radius {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
shadow {
size: Kirigami.Units.gridUnit * 2
color: Qt.rgba(0, 0, 0, 0.3)
yOffset: 3
}
RowLayout {
id: toolBar
LayoutMirroring.enabled: Application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
spacing: Kirigami.Units.smallSpacing
anchors {
left: parent.left
top: parent.top
right: parent.right
margins: Kirigami.Units.smallSpacing
}
Flow {
Layout.fillWidth: true
Layout.minimumHeight: implicitHeight
spacing: Kirigami.Units.smallSpacing
PC.ToolButton {
id: addWidgetButton
property QtObject qAction: containment?.plasmoid.internalAction("add widgets") || null
text: qAction?.text
icon.name: "view-group-symbolic"
onClicked: qAction.trigger()
}
PC.ToolButton {
id: addPanelButton
height: addWidgetButton.height
property QtObject qAction: containment?.plasmoid.corona.action("add panel") || null
text: qAction?.text
icon.name: "list-add"
Accessible.role: Accessible.ButtonMenu
onClicked: containment.plasmoid.corona.showAddPanelContextMenu(mapToGlobal(0, height))
}
PC.ToolButton {
id: manageContainmentsButton
property QtObject qAction: containment?.plasmoid.corona.action("manage-containments") || null
text: qAction?.text
visible: qAction?.visible || false
icon.name: "configure-symbolic"
onClicked: qAction.trigger()
}
}
PC.ToolButton {
Layout.alignment: Qt.AlignTop
visible: Kirigami.Settings.hasTransientTouchInput || Kirigami.Settings.tabletMode
icon.name: "overflow-menu"
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "More")
onClicked: {
containment.openContextMenu(mapToGlobal(0, height));
}
}
PC.ToolButton {
Layout.alignment: Qt.AlignTop
icon.name: "dialog-ok-symbolic"
text: i18ndc("plasma_shell_org.kde.plasma.desktop", "@action:button", "Exit Edit Mode")
onClicked: containment.plasmoid.corona.editMode = false
}
}
}
Behavior on x {
NumberAnimation {
id: xAnim
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Behavior on y {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Behavior on width {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Behavior on height {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
MultiEffect {
anchors.fill: parent
source: containment
layer.enabled: true
layer.smooth: true
layer.effect: Kirigami.ShadowedTexture {
width: editModeItem.roundedRootWidth
height: editModeItem.roundedRootHeight
color: "transparent"
radius: editModeItem.open ? Kirigami.Units.cornerRadius : 0
Behavior on radius {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
}
}
}

View File

@@ -0,0 +1,375 @@
/*
SPDX-FileCopyrightText: 2012 Marco Martin <mart@kde.org>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQml
import org.kde.plasma.core as PlasmaCore
import org.kde.ksvg as KSvg
import org.kde.taskmanager as TaskManager
import org.kde.kwindowsystem
import org.kde.kirigami as Kirigami
import org.kde.plasma.shell.panel as Panel
import org.kde.plasma.plasmoid
Item {
id: root
property Item containment
property bool floatingPrefix: floatingPanelSvg.usedPrefix === "floating"
readonly property bool verticalPanel: containment?.plasmoid?.formFactor === PlasmaCore.Types.Vertical
readonly property real spacingAtMinSize: Math.floor(Math.max(1, panel.thickness - Kirigami.Units.iconSizes.smallMedium)/2)
KSvg.FrameSvgItem {
id: thickPanelSvg
visible: false
prefix: 'thick'
imagePath: "widgets/panel-background"
}
KSvg.FrameSvgItem {
id: floatingPanelSvg
visible: false
prefix: ['floating', '']
imagePath: "widgets/panel-background"
}
readonly property bool rightEdge: containment?.plasmoid?.location === PlasmaCore.Types.RightEdge
readonly property bool bottomEdge: containment?.plasmoid?.location === PlasmaCore.Types.BottomEdge
readonly property int bottomFloatingPadding: Math.round(fixedBottomFloatingPadding * floatingness)
readonly property int leftFloatingPadding: Math.round(fixedLeftFloatingPadding * floatingness)
readonly property int rightFloatingPadding: Math.round(fixedRightFloatingPadding * floatingness)
readonly property int topFloatingPadding: Math.round(fixedTopFloatingPadding * floatingness)
// NOTE: Many of the properties in this file are accessed directly in C++ PanelView!
// If you change these, make sure to also correct the related code in panelview.cpp.
readonly property int fixedBottomFloatingPadding: floating && (floatingPrefix ? floatingPanelSvg.fixedMargins.bottom : 8)
readonly property int fixedLeftFloatingPadding: floating && (floatingPrefix ? floatingPanelSvg.fixedMargins.left : 8)
readonly property int fixedRightFloatingPadding: floating && (floatingPrefix ? floatingPanelSvg.fixedMargins.right : 8)
readonly property int fixedTopFloatingPadding: floating && (floatingPrefix ? floatingPanelSvg.fixedMargins.top : 8)
readonly property int topPadding: Math.round(Math.min(thickPanelSvg.fixedMargins.top + Kirigami.Units.smallSpacing, spacingAtMinSize));
readonly property int bottomPadding: Math.round(Math.min(thickPanelSvg.fixedMargins.bottom + Kirigami.Units.smallSpacing, spacingAtMinSize));
readonly property int leftPadding: Math.round(Math.min(thickPanelSvg.fixedMargins.left + Kirigami.Units.smallSpacing, spacingAtMinSize));
readonly property int rightPadding: Math.round(Math.min(thickPanelSvg.fixedMargins.right + Kirigami.Units.smallSpacing, spacingAtMinSize));
readonly property int minPanelHeight: translucentItem.minimumDrawingHeight
readonly property int minPanelWidth: translucentItem.minimumDrawingWidth
// This value is read from panelview.cpp which needs it to decide which border should be enabled
property real topShadowMargin: -floatingTranslucentItem.y
property real leftShadowMargin: -floatingTranslucentItem.x
property real rightShadowMargin: -(width - floatingTranslucentItem.width - floatingTranslucentItem.x)
property real bottomShadowMargin: -(height - floatingTranslucentItem.height - floatingTranslucentItem.y)
property var panelMask: floatingness === 0 ? (panelOpacity === 1 ? opaqueItem.mask : translucentItem.mask) : (panelOpacity === 1 ? floatingOpaqueItem.mask : floatingTranslucentItem.mask)
// The point is read from panelview.cpp and is used as an offset for the mask
readonly property point floatingTranslucentItemOffset: Qt.point(floatingTranslucentItem.x, floatingTranslucentItem.y)
TaskManager.VirtualDesktopInfo {
id: virtualDesktopInfo
}
TaskManager.ActivityInfo {
id: activityInfo
}
// We need to have a little gap between the raw visibleWindowsModel count
// and actually determining if a window is touching.
// This is because certain dialog windows start off with a position of (screenwidth/2, screenheight/2)
// and they register as "touching" in the split-second before KWin can place them correctly.
// This avoids the panel flashing if it is auto-hide etc and such a window is shown.
// Examples of such windows: properties of a file on desktop, or portal "open with" dialog
property bool touchingWindow: false
property bool touchingWindowDirect: visibleWindowsModel.count > 0
property bool showingDesktop: KWindowSystem.showingDesktop
Timer {
id: touchingWindowDebounceTimer
interval: 10 // ms, I find that this value is enough while not causing unresponsiveness while dragging windows close
onTriggered: root.touchingWindow = !KWindowSystem.showingDesktop && root.touchingWindowDirect
}
onTouchingWindowDirectChanged: touchingWindowDebounceTimer.start()
onShowingDesktopChanged: touchingWindowDebounceTimer.start()
TaskManager.TasksModel {
id: visibleWindowsModel
filterByVirtualDesktop: true
filterByActivity: true
filterByScreen: false
filterByRegion: TaskManager.RegionFilterMode.Intersect
filterHidden: true
filterMinimized: true
screenGeometry: panel.screenGeometry
virtualDesktop: virtualDesktopInfo.currentDesktop
activity: activityInfo.currentActivity
groupMode: TaskManager.TasksModel.GroupDisabled
Binding on regionGeometry {
delayed: true
value: panel.width, panel.height, panel.x, panel.y, panel.dogdeGeometryByDistance(panel.visibilityMode === Panel.Global.DodgeWindows ? -1 : 1) // +1 is for overlap detection, -1 is for snapping to panel
}
}
Connections {
target: root.containment?.plasmoid ?? null
function onActivated() {
if (root.containment.plasmoid.status === PlasmaCore.Types.AcceptingInputStatus) {
root.containment.plasmoid.status = PlasmaCore.Types.PassiveStatus;
} else {
root.containment.plasmoid.status = PlasmaCore.Types.AcceptingInputStatus;
}
}
}
// Floatingness is a value in [0, 1] that's multiplied to the floating margin; 0: not floating, 1: floating, between 0 and 1: animation between the two states
readonly property int floatingnessAnimationDuration: Kirigami.Units.longDuration
property double floatingnessTarget: 0.0 // The animation is handled in panelview.cpp for efficiency
property double floatingness: 0.0
// PanelOpacity is a value in [0, 1] that's used as the opacity of the opaque elements over the transparent ones; values between 0 and 1 are used for animations
property double panelOpacity
Behavior on panelOpacity {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.OutCubic
}
}
KSvg.FrameSvgItem {
id: translucentItem
visible: root.floatingness === 0 && root.panelOpacity !== 1
enabledBorders: panel.enabledBorders
anchors.fill: floatingTranslucentItem
imagePath: containment?.plasmoid?.backgroundHints === PlasmaCore.Types.NoBackground ? "" : "widgets/panel-background"
}
KSvg.FrameSvgItem {
id: floatingTranslucentItem
visible: root.floatingness !== 0 && root.panelOpacity !== 1
x: root.rightEdge ? root.fixedLeftFloatingPadding + root.fixedRightFloatingPadding * (1 - root.floatingness) : root.leftFloatingPadding
y: root.bottomEdge ? root.fixedTopFloatingPadding + root.fixedBottomFloatingPadding * (1 - root.floatingness) : root.topFloatingPadding
width: root.verticalPanel ? panel.thickness : parent.width - root.leftFloatingPadding - root.rightFloatingPadding
height: root.verticalPanel ? parent.height - root.topFloatingPadding - root.bottomFloatingPadding : panel.thickness
imagePath: containment?.plasmoid?.backgroundHints === PlasmaCore.Types.NoBackground ? "" : "widgets/panel-background"
}
KSvg.FrameSvgItem {
id: floatingOpaqueItem
visible: root.floatingness !== 0 && root.panelOpacity !== 0
opacity: root.panelOpacity
anchors.fill: floatingTranslucentItem
imagePath: containment?.plasmoid?.backgroundHints === PlasmaCore.Types.NoBackground ? "" : "solid/widgets/panel-background"
}
KSvg.FrameSvgItem {
id: opaqueItem
visible: root.panelOpacity !== 0 && root.floatingness === 0
opacity: root.panelOpacity
enabledBorders: panel.enabledBorders
anchors.fill: floatingTranslucentItem
imagePath: containment?.plasmoid?.backgroundHints === PlasmaCore.Types.NoBackground ? "" : "solid/widgets/panel-background"
}
Keys.onEscapePressed: {
root.parent.focus = false
}
property bool isOpaque: panel.opacityMode === Panel.Global.Opaque
property bool isTransparent: panel.opacityMode === Panel.Global.Translucent
property bool isAdaptive: panel.opacityMode === Panel.Global.Adaptive
property bool floating: panel.floating
property bool hasCompositing: KWindowSystem.isPlatformX11 ? KX11Extras.compositingActive : true
property var stateTriggers: [floating, touchingWindow, isOpaque, isAdaptive, isTransparent, hasCompositing, containment, panel.floatingApplets]
onStateTriggersChanged: {
let opaqueApplets = false
let floatingApplets = false
if ((!floating || touchingWindow) && (isOpaque || (touchingWindow && isAdaptive))) {
panelOpacity = 1
opaqueApplets = true
floatingnessTarget = 0
floatingApplets = (panel.floatingApplets && !floating)
} else if ((!floating || touchingWindow) && (isTransparent || (!touchingWindow && isAdaptive))) {
panelOpacity = 0
floatingnessTarget = 0
floatingApplets = (panel.floatingApplets && !floating)
} else if ((floating && !touchingWindow) && (isTransparent || isAdaptive)) {
panelOpacity = 0
floatingnessTarget = 1
floatingApplets = true
} else if (floating && !touchingWindow && isOpaque) {
panelOpacity = 1
opaqueApplets = true
floatingnessTarget = 1
floatingApplets = true
}
// Exceptions: panels with not NormalPanel visibilityMode
// should never de-float, and we should not have transparent
// panels when on X11 with compositing not active.
if (panel.visibilityMode != Panel.Global.NormalPanel && floating) {
floatingnessTarget = 1
floatingApplets = true
}
if (!KWindowSystem.isPlatformWayland && !KX11Extras.compositingActive) {
opaqueApplets = false
panelOpacity = 0
}
// Not using panelOpacity to check as it has a NumberAnimation, and it will thus
// be still read as the initial value here, before the animation starts.
if (containment) {
if (opaqueApplets) {
containment.plasmoid.containmentDisplayHints |= PlasmaCore.Types.ContainmentPrefersOpaqueBackground
} else {
containment.plasmoid.containmentDisplayHints &= ~PlasmaCore.Types.ContainmentPrefersOpaqueBackground
}
if (floatingApplets) {
containment.plasmoid.containmentDisplayHints |= PlasmaCore.Types.ContainmentPrefersFloatingApplets
} else {
containment.plasmoid.containmentDisplayHints &= ~PlasmaCore.Types.ContainmentPrefersFloatingApplets
}
}
}
function adjustPrefix() {
if (!containment) {
return "";
}
var pre;
switch (containment.plasmoid.location) {
case PlasmaCore.Types.LeftEdge:
pre = "west";
break;
case PlasmaCore.Types.TopEdge:
pre = "north";
break;
case PlasmaCore.Types.RightEdge:
pre = "east";
break;
case PlasmaCore.Types.BottomEdge:
pre = "south";
break;
default:
pre = "";
break;
}
translucentItem.prefix = opaqueItem.prefix = floatingTranslucentItem.prefix = floatingOpaqueItem.prefix = [pre, ""];
}
onContainmentChanged: {
if (!containment) {
return;
}
containment.parent = containmentParent;
containment.visible = true;
containment.anchors.fill = containmentParent;
containment.plasmoid.locationChanged.connect(adjustPrefix);
adjustPrefix();
}
Binding {
target: panel
property: "length"
when: containment
value: {
if (!containment) {
return;
}
if (root.verticalPanel) {
if (containment.Layout.fillHeight) {
if (panel.lengthMode == Panel.Global.Custom) {
return panel.maximumHeight
} else {
return panel.screenGeometry.height
}
}
return containment.Layout.preferredHeight
} else {
if (containment.Layout.fillWidth) {
if (panel.lengthMode == Panel.Global.Custom) {
return panel.maximumWidth
} else {
return panel.screenGeometry.width
}
}
return containment.Layout.preferredWidth
}
}
restoreMode: Binding.RestoreBinding
}
Binding {
target: panel
property: "backgroundHints"
when: containment
value: {
if (!containment) {
return;
}
return containment.plasmoid.backgroundHints;
}
restoreMode: Binding.RestoreBinding
}
KSvg.FrameSvgItem {
Accessible.name: i18nc("@info:whatsthis Accessible name", "Panel Focus Indicator")
x: root.verticalPanel || !panel.activeFocusItem
? translucentItem.x
: Math.max(panel.activeFocusItem.Kirigami.ScenePosition.x, panel.activeFocusItem.Kirigami.ScenePosition.x)
y: root.verticalPanel && panel.activeFocusItem
? Math.max(panel.activeFocusItem.Kirigami.ScenePosition.y, panel.activeFocusItem.Kirigami.ScenePosition.y)
: translucentItem.y
width: panel.activeFocusItem
? (root.verticalPanel ? translucentItem.width : Math.min(panel.activeFocusItem.width, panel.activeFocusItem.width))
: 0
height: panel.activeFocusItem
? (root.verticalPanel ? Math.min(panel.activeFocusItem.height, panel.activeFocusItem.height) : translucentItem.height)
: 0
visible: panel.active && panel.activeFocusItem
imagePath: "widgets/tabbar"
prefix: {
if (!root.containment) {
return "";
}
var prefix = ""
switch (root.containment.plasmoid.location) {
case PlasmaCore.Types.LeftEdge:
prefix = "west-active-tab";
break;
case PlasmaCore.Types.TopEdge:
prefix = "north-active-tab";
break;
case PlasmaCore.Types.RightEdge:
prefix = "east-active-tab";
break;
default:
prefix = "south-active-tab";
}
if (!hasElementPrefix(prefix)) {
prefix = "active-tab";
}
return prefix;
}
}
Item {
id: containmentParent
anchors.centerIn: root.isOpaque ? floatingOpaqueItem : floatingTranslucentItem
width: root.verticalPanel ? panel.thickness : root.width - root.fixedLeftFloatingPadding - root.fixedRightFloatingPadding
height: root.verticalPanel ? root.height - root.fixedBottomFloatingPadding - root.fixedTopFloatingPadding : panel.thickness
}
}

View File

@@ -0,0 +1,48 @@
/*
SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
import QtQuick
import org.kde.plasma.extras as PlasmaExtras
import org.kde.kirigami as Kirigami
Item {
// Using childrenRect.width causes a binding loop since we can only get the
// actual width, not the implicitWidth--which is what we would want
width: Math.max(title.implicitWidth, subtitle.implicitWidth)
height: childrenRect.height
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
TapHandler {
acceptedButtons: Qt.LeftButton | Qt.RightButton
onTapped: desktop.showPreviewBannerMenu(mapToGlobal(point.position))
}
PlasmaExtras.ShadowedLabel {
id: title
anchors {
top: parent.top
right: parent.right
}
z: 2
text: desktop.previewBannerTitle
// Emulate the size of a level 1 heading
font.pointSize: Math.round(Kirigami.Theme.defaultFont.pointSize * 1.35)
}
PlasmaExtras.ShadowedLabel {
id: subtitle
anchors {
top: title.bottom
right: parent.right
}
z: 2
text: desktop.previewBannerText
}
}