SessionMenuTab: add per entry countdown toggle (implements #746

)
This commit is contained in:
Ly-sec
2025-11-15 13:48:39 +01:00
parent 79b079a436
commit 7176e890af
2 changed files with 298 additions and 23 deletions
+45 -4
View File
@@ -88,7 +88,7 @@ SmartPanel {
}
// Build powerOptions from settings, filtering enabled ones and adding metadata
readonly property var powerOptions: (function () {
property var powerOptions: {
var options = []
var settingsOptions = Settings.data.sessionMenu.powerOptions || []
@@ -100,13 +100,39 @@ SmartPanel {
"action": settingOption.action,
"icon": metadata.icon,
"title": metadata.title,
"isShutdown": metadata.isShutdown
"isShutdown": metadata.isShutdown,
"countdownEnabled": settingOption.countdownEnabled !== undefined ? settingOption.countdownEnabled : true
})
}
}
return options
})()
}
// Update powerOptions when settings change
Connections {
target: Settings.data.sessionMenu
function onPowerOptionsChanged() {
var options = []
var settingsOptions = Settings.data.sessionMenu.powerOptions || []
for (var i = 0; i < settingsOptions.length; i++) {
var settingOption = settingsOptions[i]
if (settingOption.enabled && actionMetadata[settingOption.action]) {
var metadata = actionMetadata[settingOption.action]
options.push({
"action": settingOption.action,
"icon": metadata.icon,
"title": metadata.title,
"isShutdown": metadata.isShutdown,
"countdownEnabled": settingOption.countdownEnabled !== undefined ? settingOption.countdownEnabled : true
})
}
}
root.powerOptions = options
}
}
// Lifecycle handlers
onOpened: {
@@ -120,12 +146,27 @@ SmartPanel {
// Timer management
function startTimer(action) {
// If countdown is disabled, execute immediately
// Check if global countdown is disabled
if (!Settings.data.sessionMenu.enableCountdown) {
executeAction(action)
return
}
// Check per-item countdown setting
var option = null
for (var i = 0; i < powerOptions.length; i++) {
if (powerOptions[i].action === action) {
option = powerOptions[i]
break
}
}
// If this specific action has countdown disabled, execute immediately
if (option && option.countdownEnabled === false) {
executeAction(action)
return
}
if (timerActive && pendingAction === action) {
// Second click - execute immediately
executeAction(action)
+253 -19
View File
@@ -49,7 +49,8 @@ ColumnLayout {
for (var i = 0; i < entriesModel.length; i++) {
toSave.push({
"action": entriesModel[i].id,
"enabled": entriesModel[i].enabled
"enabled": entriesModel[i].enabled,
"countdownEnabled": entriesModel[i].countdownEnabled !== undefined ? entriesModel[i].countdownEnabled : true
})
}
Settings.data.sessionMenu.powerOptions = toSave
@@ -66,6 +67,8 @@ ColumnLayout {
if (settingEntry.action === entriesDefault[j].id) {
var entry = entriesDefault[j]
entry.enabled = settingEntry.enabled
// Default countdownEnabled to true for backward compatibility
entry.countdownEnabled = settingEntry.countdownEnabled !== undefined ? settingEntry.countdownEnabled : true
entriesModel.push(entry)
}
}
@@ -83,6 +86,8 @@ ColumnLayout {
if (!found) {
var entry = entriesDefault[i]
// Default countdownEnabled to true for new entries
entry.countdownEnabled = true
entriesModel.push(entry)
}
}
@@ -172,7 +177,7 @@ ColumnLayout {
// Entries Management Section
ColumnLayout {
spacing: Style.marginXXS
spacing: Style.marginM
Layout.fillWidth: true
NHeader {
@@ -180,24 +185,253 @@ ColumnLayout {
description: I18n.tr("settings.session-menu.entries.section.description")
}
NReorderCheckboxes {
// List of items
Item {
Layout.fillWidth: true
model: entriesModel
disabledIds: []
onItemToggled: function (index, enabled) {
var newModel = entriesModel.slice()
newModel[index] = Object.assign({}, newModel[index], {
"enabled": enabled
})
entriesModel = newModel
saveEntries()
}
onItemsReordered: function (fromIndex, toIndex) {
var newModel = entriesModel.slice()
var item = newModel.splice(fromIndex, 1)[0]
newModel.splice(toIndex, 0, item)
entriesModel = newModel
saveEntries()
implicitHeight: listView.contentHeight
ListView {
id: listView
anchors.fill: parent
spacing: Style.marginS
interactive: false
clip: true
model: entriesModel
// Store reference to root's saveEntries function
property var saveEntriesFunc: root.saveEntries
delegate: Item {
id: delegateItem
width: listView.width
height: contentRow.height
required property int index
required property var modelData
property bool dragging: false
property int dragStartY: 0
property int dragStartIndex: -1
property int dragTargetIndex: -1
property var saveEntriesFunc: listView.saveEntriesFunc
Rectangle {
anchors.fill: parent
radius: Style.radiusM
color: delegateItem.dragging ? Color.mSurfaceVariant : Color.transparent
border.color: delegateItem.dragging ? Color.mOutline : Color.transparent
border.width: Style.borderS
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
}
RowLayout {
id: contentRow
width: parent.width
spacing: Style.marginS
// Drag handle
Rectangle {
Layout.preferredWidth: Style.baseWidgetSize * 0.7
Layout.preferredHeight: Style.baseWidgetSize * 0.7
Layout.alignment: Qt.AlignVCenter
radius: Style.radiusXS
color: dragHandleMouseArea.containsMouse ? Color.mSurfaceVariant : Color.transparent
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
ColumnLayout {
anchors.centerIn: parent
spacing: 2
Repeater {
model: 3
Rectangle {
Layout.preferredWidth: Style.baseWidgetSize * 0.28
Layout.preferredHeight: 2
radius: 1
color: Color.mOutline
}
}
}
MouseArea {
id: dragHandleMouseArea
anchors.fill: parent
cursorShape: Qt.SizeVerCursor
hoverEnabled: true
preventStealing: false
z: 1000
onPressed: mouse => {
delegateItem.dragStartIndex = delegateItem.index
delegateItem.dragTargetIndex = delegateItem.index
delegateItem.dragStartY = delegateItem.y
delegateItem.dragging = true
delegateItem.z = 999
preventStealing = true
}
onPositionChanged: mouse => {
if (delegateItem.dragging) {
var dy = mouse.y - height / 2
var newY = delegateItem.y + dy
newY = Math.max(0, Math.min(newY, listView.contentHeight - delegateItem.height))
delegateItem.y = newY
var targetIndex = Math.floor((newY + delegateItem.height / 2) / (delegateItem.height + Style.marginS))
targetIndex = Math.max(0, Math.min(targetIndex, listView.count - 1))
delegateItem.dragTargetIndex = targetIndex
}
}
onReleased: {
preventStealing = false
if (delegateItem.dragStartIndex !== -1 && delegateItem.dragTargetIndex !== -1 && delegateItem.dragStartIndex !== delegateItem.dragTargetIndex) {
var newModel = entriesModel.slice()
var item = newModel.splice(delegateItem.dragStartIndex, 1)[0]
newModel.splice(delegateItem.dragTargetIndex, 0, item)
entriesModel = newModel
root.saveEntries()
}
delegateItem.dragging = false
delegateItem.dragStartIndex = -1
delegateItem.dragTargetIndex = -1
delegateItem.z = 0
}
onCanceled: {
preventStealing = false
delegateItem.dragging = false
delegateItem.dragStartIndex = -1
delegateItem.dragTargetIndex = -1
delegateItem.z = 0
}
}
}
// Enable checkbox
Rectangle {
Layout.preferredWidth: Style.baseWidgetSize * 0.7
Layout.preferredHeight: Style.baseWidgetSize * 0.7
Layout.alignment: Qt.AlignVCenter
radius: Style.radiusXS
color: modelData.enabled ? Color.mPrimary : Color.mSurface
border.color: Color.mOutline
border.width: Style.borderS
Behavior on color {
ColorAnimation {
duration: Style.animationFast
}
}
NIcon {
visible: modelData.enabled
anchors.centerIn: parent
anchors.horizontalCenterOffset: -1
icon: "check"
color: Color.mOnPrimary
pointSize: Math.max(Style.fontSizeXS, Style.baseWidgetSize * 0.35)
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
var newModel = entriesModel.slice()
newModel[index] = Object.assign({}, newModel[index], {
"enabled": !modelData.enabled
})
entriesModel = newModel
root.saveEntries()
}
}
}
// Label
NText {
Layout.fillWidth: true
text: modelData.text
color: Color.mOnSurface
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
// Countdown toggle with icon (only shown when global countdown is enabled)
RowLayout {
visible: Settings.data.sessionMenu.enableCountdown
spacing: Style.marginXS
Layout.alignment: Qt.AlignVCenter
NIcon {
icon: "clock"
color: Color.mOnSurfaceVariant
pointSize: Style.fontSizeS
}
NToggle {
checked: modelData.countdownEnabled !== undefined ? modelData.countdownEnabled : true
onToggled: function (checked) {
var newModel = entriesModel.slice()
newModel[delegateItem.index] = Object.assign({}, newModel[delegateItem.index], {
"countdownEnabled": checked
})
entriesModel = newModel
delegateItem.saveEntriesFunc()
}
}
}
}
// Position binding for non-dragging state
y: {
if (delegateItem.dragging) {
return delegateItem.y
}
var draggedIndex = -1
var targetIndex = -1
for (var i = 0; i < listView.count; i++) {
var item = listView.itemAtIndex(i)
if (item && item.dragging) {
draggedIndex = item.dragStartIndex
targetIndex = item.dragTargetIndex
break
}
}
if (draggedIndex !== -1 && targetIndex !== -1 && draggedIndex !== targetIndex) {
var currentIndex = delegateItem.index
if (draggedIndex < targetIndex) {
if (currentIndex > draggedIndex && currentIndex <= targetIndex) {
return (currentIndex - 1) * (delegateItem.height + Style.marginS)
}
} else {
if (currentIndex >= targetIndex && currentIndex < draggedIndex) {
return (currentIndex + 1) * (delegateItem.height + Style.marginS)
}
}
}
return delegateItem.index * (delegateItem.height + Style.marginS)
}
Behavior on y {
enabled: !delegateItem.dragging
NumberAnimation {
duration: Style.animationNormal
easing.type: Easing.OutQuad
}
}
}
}
}
}