mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-10 07:25:37 -05:00
Notepad slideout redesign
This commit is contained in:
@@ -1,502 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtCore
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modals.FileBrowser
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
|
||||
property bool notepadModalVisible: false
|
||||
property bool fileDialogOpen: false
|
||||
property string currentFileName: ""
|
||||
property bool hasUnsavedChanges: false
|
||||
property url currentFileUrl
|
||||
|
||||
function show() {
|
||||
notepadModalVisible = true
|
||||
shouldHaveFocus = Qt.binding(() => {
|
||||
return notepadModalVisible && !fileDialogOpen
|
||||
})
|
||||
open()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (hasUnsavedChanges) {
|
||||
// Could add unsaved changes dialog here
|
||||
}
|
||||
notepadModalVisible = false
|
||||
currentFileName = ""
|
||||
currentFileUrl = ""
|
||||
hasUnsavedChanges = false
|
||||
close()
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (notepadModalVisible)
|
||||
hide()
|
||||
else
|
||||
show()
|
||||
}
|
||||
|
||||
visible: notepadModalVisible
|
||||
width: 700
|
||||
height: 520
|
||||
enableShadow: true
|
||||
onShouldHaveFocusChanged: {
|
||||
}
|
||||
onBackgroundClicked: hide()
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
id: contentItem
|
||||
|
||||
anchors.fill: parent
|
||||
property alias textArea: textArea
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onNotepadModalVisibleChanged() {
|
||||
if (root.notepadModalVisible) {
|
||||
Qt.callLater(() => {
|
||||
textArea.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function newDocument() {
|
||||
if (root.hasUnsavedChanges) {
|
||||
// Could add confirmation dialog here
|
||||
}
|
||||
textArea.text = ""
|
||||
SessionData.notepadContent = ""
|
||||
root.currentFileName = ""
|
||||
root.currentFileUrl = ""
|
||||
root.hasUnsavedChanges = false
|
||||
}
|
||||
|
||||
function openSaveDialog() {
|
||||
root.allowFocusOverride = true
|
||||
root.shouldHaveFocus = false
|
||||
root.fileDialogOpen = true
|
||||
saveBrowser.open()
|
||||
}
|
||||
|
||||
function openLoadDialog() {
|
||||
root.allowFocusOverride = true
|
||||
root.shouldHaveFocus = false
|
||||
root.fileDialogOpen = true
|
||||
loadBrowser.open()
|
||||
}
|
||||
|
||||
function saveToCurrentFile() {
|
||||
if (root.currentFileUrl.toString()) {
|
||||
saveToFile(root.currentFileUrl)
|
||||
} else {
|
||||
openSaveDialog()
|
||||
}
|
||||
}
|
||||
|
||||
function saveToFile(fileUrl) {
|
||||
const content = textArea.text
|
||||
const cleanPath = fileUrl.toString().replace(/^file:\/\//, '')
|
||||
// Use printf to safely handle special characters and escape single quotes
|
||||
const escapedContent = content.replace(/'/g, "'\\''")
|
||||
fileWriter.command = ["sh", "-c", "printf '%s' '" + escapedContent + "' > '" + cleanPath + "'"]
|
||||
fileWriter.running = true
|
||||
}
|
||||
|
||||
function loadFromFile(fileUrl) {
|
||||
const cleanPath = fileUrl.toString().replace(/^file:\/\//, '')
|
||||
fileReader.command = ["cat", cleanPath]
|
||||
fileReader.running = true
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
Row {
|
||||
width: parent.width - closeButton.width
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Column {
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Notepad")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: (root.hasUnsavedChanges ? "● " : "") + (root.currentFileName || qsTr("Untitled"))
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.hasUnsavedChanges ? Theme.primary : Theme.surfaceTextMedium
|
||||
visible: root.currentFileName !== "" || root.hasUnsavedChanges
|
||||
elide: Text.ElideMiddle
|
||||
maximumLineCount: 1
|
||||
width: 200
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: SessionData.notepadContent.length > 0 ? qsTr("%1 characters").arg(SessionData.notepadContent.length) : qsTr("Empty")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Lines: %1").arg(textArea.lineCount)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
visible: SessionData.notepadContent.length > 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: closeButton
|
||||
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: root.hide()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: parent.height - 90
|
||||
color: Theme.surface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
clip: true
|
||||
|
||||
TextArea {
|
||||
id: textArea
|
||||
|
||||
text: SessionData.notepadContent
|
||||
placeholderText: qsTr("Start typing your notes here...")
|
||||
font.family: SettingsData.monoFontFamily
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
wrapMode: TextArea.Wrap
|
||||
focus: root.notepadModalVisible
|
||||
activeFocusOnTab: true
|
||||
textFormat: TextEdit.PlainText
|
||||
persistentSelection: true
|
||||
tabStopDistance: 40
|
||||
leftPadding: Theme.spacingM
|
||||
topPadding: Theme.spacingM
|
||||
rightPadding: Theme.spacingM
|
||||
bottomPadding: Theme.spacingM
|
||||
|
||||
onTextChanged: {
|
||||
if (text !== SessionData.notepadContent) {
|
||||
SessionData.notepadContent = text
|
||||
root.hasUnsavedChanges = true
|
||||
saveTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: (event) => {
|
||||
root.hide()
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switch (event.key) {
|
||||
case Qt.Key_S:
|
||||
event.accepted = true
|
||||
if (root.currentFileUrl.toString()) {
|
||||
contentItem.saveToCurrentFile()
|
||||
} else {
|
||||
contentItem.openSaveDialog()
|
||||
}
|
||||
break
|
||||
case Qt.Key_O:
|
||||
event.accepted = true
|
||||
contentItem.openLoadDialog()
|
||||
break
|
||||
case Qt.Key_N:
|
||||
event.accepted = true
|
||||
contentItem.newDocument()
|
||||
break
|
||||
case Qt.Key_A:
|
||||
event.accepted = true
|
||||
selectAll()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.notepadModalVisible) {
|
||||
Qt.callLater(() => {
|
||||
forceActiveFocus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 40
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankActionButton {
|
||||
iconName: "save"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.primary
|
||||
enabled: root.hasUnsavedChanges || SessionData.notepadContent.length > 0
|
||||
onClicked: contentItem.saveToCurrentFile()
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.currentFileUrl.toString() ? qsTr("Save") : qsTr("Save as...")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankActionButton {
|
||||
iconName: "folder_open"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.secondary
|
||||
onClicked: contentItem.openLoadDialog()
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Open file")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankActionButton {
|
||||
iconName: "note_add"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: contentItem.newDocument()
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("New")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 1
|
||||
height: 1
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: saveTimer.running ? qsTr("Auto-saving...") : (root.hasUnsavedChanges ? qsTr("Unsaved changes") : qsTr("Auto-saved"))
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.hasUnsavedChanges ? Theme.warning : (saveTimer.running ? Theme.primary : Theme.surfaceTextMedium)
|
||||
opacity: SessionData.notepadContent.length > 0 ? 1 : 0
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.shortDuration
|
||||
easing.type: Theme.standardEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: saveTimer
|
||||
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
SessionData.saveSettings()
|
||||
root.hasUnsavedChanges = false
|
||||
}
|
||||
}
|
||||
|
||||
// Improved file I/O using Quickshell Process with better safety
|
||||
Process {
|
||||
id: fileWriter
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
root.hasUnsavedChanges = false
|
||||
} else {
|
||||
console.warn("Notepad: Failed to save file, exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: fileReader
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
textArea.text = text
|
||||
SessionData.notepadContent = text
|
||||
root.hasUnsavedChanges = false
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Notepad: Failed to load file, exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: saveBrowser
|
||||
|
||||
browserTitle: qsTr("Save Notepad File")
|
||||
browserIcon: "save"
|
||||
browserType: "notepad_save"
|
||||
fileExtensions: ["*.txt", "*.md", "*.*"]
|
||||
allowStacking: true
|
||||
saveMode: true
|
||||
defaultFileName: "note.txt"
|
||||
|
||||
onFileSelected: (path) => {
|
||||
root.fileDialogOpen = false
|
||||
const cleanPath = path.toString().replace(/^file:\/\//, '')
|
||||
const fileName = cleanPath.split('/').pop()
|
||||
const fileUrl = "file://" + cleanPath
|
||||
|
||||
root.currentFileName = fileName
|
||||
root.currentFileUrl = fileUrl
|
||||
|
||||
contentItem.saveToFile(fileUrl)
|
||||
close()
|
||||
|
||||
// Restore modal focus
|
||||
root.allowFocusOverride = false
|
||||
root.shouldHaveFocus = Qt.binding(() => {
|
||||
return root.notepadModalVisible && !root.fileDialogOpen
|
||||
})
|
||||
Qt.callLater(() => {
|
||||
textArea.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
root.fileDialogOpen = false
|
||||
// Restore modal focus
|
||||
root.allowFocusOverride = false
|
||||
root.shouldHaveFocus = Qt.binding(() => {
|
||||
return root.notepadModalVisible && !root.fileDialogOpen
|
||||
})
|
||||
Qt.callLater(() => {
|
||||
textArea.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: loadBrowser
|
||||
|
||||
browserTitle: qsTr("Open Notepad File")
|
||||
browserIcon: "folder_open"
|
||||
browserType: "notepad_load"
|
||||
fileExtensions: ["*.txt", "*.md", "*.*"]
|
||||
allowStacking: true
|
||||
|
||||
onFileSelected: (path) => {
|
||||
root.fileDialogOpen = false
|
||||
const cleanPath = path.toString().replace(/^file:\/\//, '')
|
||||
const fileName = cleanPath.split('/').pop()
|
||||
const fileUrl = "file://" + cleanPath
|
||||
|
||||
root.currentFileName = fileName
|
||||
root.currentFileUrl = fileUrl
|
||||
|
||||
contentItem.loadFromFile(fileUrl)
|
||||
close()
|
||||
|
||||
// Restore modal focus
|
||||
root.allowFocusOverride = false
|
||||
root.shouldHaveFocus = Qt.binding(() => {
|
||||
return root.notepadModalVisible && !root.fileDialogOpen
|
||||
})
|
||||
Qt.callLater(() => {
|
||||
textArea.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
root.fileDialogOpen = false
|
||||
// Restore modal focus
|
||||
root.allowFocusOverride = false
|
||||
root.shouldHaveFocus = Qt.binding(() => {
|
||||
return root.notepadModalVisible && !root.fileDialogOpen
|
||||
})
|
||||
Qt.callLater(() => {
|
||||
textArea.forceActiveFocus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
441
Modules/NotepadSlideout.qml
Normal file
441
Modules/NotepadSlideout.qml
Normal file
@@ -0,0 +1,441 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtCore
|
||||
import Quickshell
|
||||
import Quickshell.Wayland
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Modals.FileBrowser
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property bool notepadVisible: false
|
||||
property bool fileDialogOpen: false
|
||||
property string currentFileName: ""
|
||||
property bool hasUnsavedChanges: false
|
||||
property url currentFileUrl
|
||||
property var targetScreen: null
|
||||
property var modelData: null
|
||||
property bool animatingOut: false
|
||||
|
||||
function show() {
|
||||
notepadVisible = true
|
||||
}
|
||||
|
||||
function hide() {
|
||||
animatingOut = true
|
||||
notepadVisible = false
|
||||
hideTimer.start()
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (notepadVisible) {
|
||||
hide()
|
||||
} else {
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
visible: notepadVisible || animatingOut
|
||||
screen: modelData
|
||||
|
||||
anchors.top: true
|
||||
anchors.bottom: true
|
||||
anchors.right: true
|
||||
|
||||
implicitWidth: 480
|
||||
implicitHeight: modelData ? modelData.height : 800
|
||||
|
||||
color: "transparent"
|
||||
|
||||
WlrLayershell.layer: WlrLayershell.Overlay
|
||||
WlrLayershell.exclusiveZone: 0
|
||||
WlrLayershell.keyboardFocus: (notepadVisible && !animatingOut) ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None
|
||||
|
||||
// Background click to close
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: notepadVisible && !animatingOut
|
||||
onClicked: mouse => {
|
||||
var localPos = mapToItem(contentRect, mouse.x, mouse.y)
|
||||
if (localPos.x < 0 || localPos.x > contentRect.width || localPos.y < 0 || localPos.y > contentRect.height) {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
id: contentRect
|
||||
|
||||
anchors.fill: parent
|
||||
color: Theme.surfaceContainer
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
|
||||
transform: Translate {
|
||||
x: notepadVisible ? 0 : 480
|
||||
|
||||
Behavior on x {
|
||||
NumberAnimation {
|
||||
duration: Theme.longDuration
|
||||
easing.type: Theme.emphasizedEasing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
// Header
|
||||
Row {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
Column {
|
||||
width: parent.width - closeButton.width
|
||||
spacing: Theme.spacingXS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Notepad")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: (root.hasUnsavedChanges ? "● " : "") + (root.currentFileName || qsTr("Untitled"))
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.hasUnsavedChanges ? Theme.primary : Theme.surfaceTextMedium
|
||||
visible: root.currentFileName !== "" || root.hasUnsavedChanges
|
||||
elide: Text.ElideMiddle
|
||||
maximumLineCount: 1
|
||||
width: parent.width - Theme.spacingM
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: closeButton
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: root.hide()
|
||||
}
|
||||
}
|
||||
|
||||
// Text area
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: parent.height - 140
|
||||
color: Theme.surface
|
||||
border.color: Theme.outlineMedium
|
||||
border.width: 1
|
||||
radius: Theme.cornerRadius
|
||||
|
||||
ScrollView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 1
|
||||
clip: true
|
||||
|
||||
TextArea {
|
||||
id: textArea
|
||||
text: SessionData.notepadContent
|
||||
placeholderText: qsTr("Start typing your notes here...")
|
||||
font.family: SettingsData.monoFontFamily
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
wrapMode: TextArea.Wrap
|
||||
focus: root.notepadVisible
|
||||
activeFocusOnTab: true
|
||||
textFormat: TextEdit.PlainText
|
||||
persistentSelection: true
|
||||
tabStopDistance: 40
|
||||
leftPadding: Theme.spacingM
|
||||
topPadding: Theme.spacingM
|
||||
rightPadding: Theme.spacingM
|
||||
bottomPadding: Theme.spacingM
|
||||
|
||||
onTextChanged: {
|
||||
if (text !== SessionData.notepadContent) {
|
||||
SessionData.notepadContent = text
|
||||
root.hasUnsavedChanges = true
|
||||
saveTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: (event) => {
|
||||
root.hide()
|
||||
event.accepted = true
|
||||
}
|
||||
|
||||
Keys.onPressed: (event) => {
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
switch (event.key) {
|
||||
case Qt.Key_S:
|
||||
event.accepted = true
|
||||
if (root.currentFileUrl.toString()) {
|
||||
saveToFile(root.currentFileUrl)
|
||||
} else {
|
||||
root.fileDialogOpen = true
|
||||
saveBrowser.open()
|
||||
}
|
||||
break
|
||||
case Qt.Key_O:
|
||||
event.accepted = true
|
||||
root.fileDialogOpen = true
|
||||
loadBrowser.open()
|
||||
break
|
||||
case Qt.Key_N:
|
||||
event.accepted = true
|
||||
textArea.text = ""
|
||||
SessionData.notepadContent = ""
|
||||
root.currentFileName = ""
|
||||
root.currentFileUrl = ""
|
||||
root.hasUnsavedChanges = false
|
||||
break
|
||||
case Qt.Key_A:
|
||||
event.accepted = true
|
||||
selectAll()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: "transparent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom controls
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
DankActionButton {
|
||||
iconName: "save"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.primary
|
||||
enabled: root.hasUnsavedChanges || SessionData.notepadContent.length > 0
|
||||
onClicked: {
|
||||
root.fileDialogOpen = true
|
||||
saveBrowser.open()
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Save")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
DankActionButton {
|
||||
iconName: "folder_open"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.secondary
|
||||
onClicked: {
|
||||
root.fileDialogOpen = true
|
||||
loadBrowser.open()
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("Open")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing: Theme.spacingS
|
||||
DankActionButton {
|
||||
iconName: "note_add"
|
||||
iconSize: Theme.iconSize - 2
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: {
|
||||
textArea.text = ""
|
||||
SessionData.notepadContent = ""
|
||||
root.currentFileName = ""
|
||||
root.currentFileUrl = ""
|
||||
root.hasUnsavedChanges = false
|
||||
}
|
||||
}
|
||||
StyledText {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: qsTr("New")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
spacing: Theme.spacingL
|
||||
|
||||
StyledText {
|
||||
text: SessionData.notepadContent.length > 0 ? qsTr("%1 characters").arg(SessionData.notepadContent.length) : qsTr("Empty")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: qsTr("Lines: %1").arg(textArea.lineCount)
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
visible: SessionData.notepadContent.length > 0
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: saveTimer.running ? qsTr("Auto-saving...") : (root.hasUnsavedChanges ? qsTr("Unsaved changes") : qsTr("Auto-saved"))
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: root.hasUnsavedChanges ? Theme.warning : (saveTimer.running ? Theme.primary : Theme.surfaceTextMedium)
|
||||
opacity: SessionData.notepadContent.length > 0 ? 1 : 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: saveTimer
|
||||
interval: 1000
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
SessionData.saveSettings()
|
||||
root.hasUnsavedChanges = false
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: Theme.longDuration
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
animatingOut = false
|
||||
currentFileName = ""
|
||||
currentFileUrl = ""
|
||||
hasUnsavedChanges = false
|
||||
}
|
||||
}
|
||||
|
||||
// File save/load functionality
|
||||
function saveToFile(fileUrl) {
|
||||
const content = SessionData.notepadContent
|
||||
const cleanPath = fileUrl.toString().replace(/^file:\/\//, '')
|
||||
const escapedContent = content.replace(/'/g, "'\\''")
|
||||
saveProcess.command = ["sh", "-c", "printf '%s' '" + escapedContent + "' > '" + cleanPath + "'"]
|
||||
saveProcess.running = true
|
||||
}
|
||||
|
||||
function loadFromFile(fileUrl) {
|
||||
const cleanPath = fileUrl.toString().replace(/^file:\/\//, '')
|
||||
|
||||
loadProcess.command = ["cat", cleanPath]
|
||||
loadProcess.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: saveProcess
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
root.hasUnsavedChanges = false
|
||||
} else {
|
||||
console.warn("Notepad: Failed to save file, exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: loadProcess
|
||||
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
SessionData.notepadContent = text
|
||||
root.hasUnsavedChanges = false
|
||||
}
|
||||
}
|
||||
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode !== 0) {
|
||||
console.warn("Notepad: Failed to load file, exit code:", exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: saveBrowser
|
||||
|
||||
browserTitle: qsTr("Save Notepad File")
|
||||
browserIcon: "save"
|
||||
browserType: "notepad_save"
|
||||
fileExtensions: ["*.txt", "*.md", "*.*"]
|
||||
allowStacking: true
|
||||
saveMode: true
|
||||
defaultFileName: "note.txt"
|
||||
|
||||
onFileSelected: (path) => {
|
||||
root.fileDialogOpen = false
|
||||
const cleanPath = path.toString().replace(/^file:\/\//, '')
|
||||
const fileName = cleanPath.split('/').pop()
|
||||
const fileUrl = "file://" + cleanPath
|
||||
|
||||
root.currentFileName = fileName
|
||||
root.currentFileUrl = fileUrl
|
||||
|
||||
saveToFile(fileUrl)
|
||||
close()
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
root.fileDialogOpen = false
|
||||
}
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: loadBrowser
|
||||
|
||||
browserTitle: qsTr("Open Notepad File")
|
||||
browserIcon: "folder_open"
|
||||
browserType: "notepad_load"
|
||||
fileExtensions: ["*.txt", "*.md", "*.*"]
|
||||
allowStacking: true
|
||||
|
||||
onFileSelected: (path) => {
|
||||
root.fileDialogOpen = false
|
||||
const cleanPath = path.toString().replace(/^file:\/\//, '')
|
||||
const fileName = cleanPath.split('/').pop()
|
||||
const fileUrl = "file://" + cleanPath
|
||||
|
||||
root.currentFileName = fileName
|
||||
root.currentFileUrl = fileUrl
|
||||
|
||||
loadFromFile(fileUrl)
|
||||
close()
|
||||
}
|
||||
|
||||
onDialogClosed: {
|
||||
root.fileDialogOpen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,8 +186,8 @@ PanelWindow {
|
||||
result = result || vpnPopoutLoader.item.shouldBeVisible
|
||||
if (typeof controlCenterLoader !== "undefined" && controlCenterLoader.item)
|
||||
result = result || controlCenterLoader.item.shouldBeVisible
|
||||
if (typeof notepadModalLoader !== "undefined" && notepadModalLoader.item)
|
||||
result = result || notepadModalLoader.item.visible
|
||||
if (typeof notepadSlideoutLoader !== "undefined" && notepadSlideoutLoader.item)
|
||||
result = result || notepadSlideoutLoader.item.notepadVisible
|
||||
if (typeof clipboardHistoryModalPopup !== "undefined" && clipboardHistoryModalPopup.item)
|
||||
result = result || clipboardHistoryModalPopup.item.visible
|
||||
return result
|
||||
@@ -1237,7 +1237,7 @@ PanelWindow {
|
||||
id: notepadButtonComponent
|
||||
|
||||
NotepadButton {
|
||||
isActive: notepadModalLoader.item ? notepadModalLoader.item.visible : false
|
||||
isActive: notepadSlideoutLoader.item ? notepadSlideoutLoader.item.notepadVisible : false
|
||||
widgetHeight: root.widgetHeight
|
||||
barHeight: root.effectiveBarHeight
|
||||
section: {
|
||||
@@ -1252,14 +1252,14 @@ PanelWindow {
|
||||
return "right"
|
||||
}
|
||||
popupTarget: {
|
||||
notepadModalLoader.active = true
|
||||
return notepadModalLoader.item
|
||||
notepadSlideoutLoader.active = true
|
||||
return notepadSlideoutLoader.item
|
||||
}
|
||||
parentScreen: root.screen
|
||||
onClicked: {
|
||||
notepadModalLoader.active = true
|
||||
if (notepadModalLoader.item) {
|
||||
notepadModalLoader.item.toggle()
|
||||
notepadSlideoutLoader.active = true
|
||||
if (notepadSlideoutLoader.item) {
|
||||
notepadSlideoutLoader.item.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
200
shell.qml
200
shell.qml
@@ -5,8 +5,8 @@ import Quickshell.Io
|
||||
import Quickshell.Widgets
|
||||
import qs.Common
|
||||
import qs.Modals
|
||||
import qs.Modals.Common
|
||||
import qs.Modals.Clipboard
|
||||
import qs.Modals.Common
|
||||
import qs.Modals.Settings
|
||||
import qs.Modals.Spotlight
|
||||
import qs.Modules
|
||||
@@ -32,8 +32,7 @@ ShellRoot {
|
||||
DisplayService.nightModeEnabled
|
||||
}
|
||||
|
||||
WallpaperBackground {
|
||||
}
|
||||
WallpaperBackground {}
|
||||
|
||||
Lock {
|
||||
id: lock
|
||||
@@ -47,7 +46,6 @@ ShellRoot {
|
||||
delegate: TopBar {
|
||||
modelData: item
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Variants {
|
||||
@@ -60,7 +58,6 @@ ShellRoot {
|
||||
dockContextMenuLoader.active = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -72,9 +69,7 @@ ShellRoot {
|
||||
CentcomPopout {
|
||||
id: centcomPopout
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
@@ -85,7 +80,6 @@ ShellRoot {
|
||||
DockContextMenu {
|
||||
id: dockContextMenu
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
@@ -96,7 +90,6 @@ ShellRoot {
|
||||
NotificationCenterPopout {
|
||||
id: notificationCenter
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Variants {
|
||||
@@ -105,7 +98,6 @@ ShellRoot {
|
||||
delegate: NotificationPopupManager {
|
||||
modelData: item
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
@@ -117,34 +109,31 @@ ShellRoot {
|
||||
id: controlCenterPopout
|
||||
|
||||
onPowerActionRequested: (action, title, message) => {
|
||||
powerConfirmModalLoader.active = true
|
||||
if (powerConfirmModalLoader.item) {
|
||||
powerConfirmModalLoader.item.confirmButtonColor =
|
||||
action === "poweroff" ? Theme.error :
|
||||
action === "reboot" ? Theme.warning : Theme.primary
|
||||
powerConfirmModalLoader.item.show(title, message, function() {
|
||||
switch (action) {
|
||||
case "logout":
|
||||
SessionService.logout()
|
||||
break
|
||||
case "suspend":
|
||||
SessionService.suspend()
|
||||
break
|
||||
case "reboot":
|
||||
SessionService.reboot()
|
||||
break
|
||||
case "poweroff":
|
||||
SessionService.poweroff()
|
||||
break
|
||||
}
|
||||
}, function() {})
|
||||
}
|
||||
}
|
||||
powerConfirmModalLoader.active = true
|
||||
if (powerConfirmModalLoader.item) {
|
||||
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
|
||||
powerConfirmModalLoader.item.show(title, message, function () {
|
||||
switch (action) {
|
||||
case "logout":
|
||||
SessionService.logout()
|
||||
break
|
||||
case "suspend":
|
||||
SessionService.suspend()
|
||||
break
|
||||
case "reboot":
|
||||
SessionService.reboot()
|
||||
break
|
||||
case "poweroff":
|
||||
SessionService.poweroff()
|
||||
break
|
||||
}
|
||||
}, function () {})
|
||||
}
|
||||
}
|
||||
onLockRequested: {
|
||||
lock.activate()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
@@ -155,7 +144,6 @@ ShellRoot {
|
||||
WifiPasswordModal {
|
||||
id: wifiPasswordModal
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
@@ -166,7 +154,6 @@ ShellRoot {
|
||||
NetworkInfoModal {
|
||||
id: networkInfoModal
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
@@ -177,7 +164,6 @@ ShellRoot {
|
||||
BatteryPopout {
|
||||
id: batteryPopout
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
@@ -188,7 +174,6 @@ ShellRoot {
|
||||
VpnPopout {
|
||||
id: vpnPopout
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
@@ -200,31 +185,28 @@ ShellRoot {
|
||||
id: powerMenu
|
||||
|
||||
onPowerActionRequested: (action, title, message) => {
|
||||
powerConfirmModalLoader.active = true
|
||||
if (powerConfirmModalLoader.item) {
|
||||
powerConfirmModalLoader.item.confirmButtonColor =
|
||||
action === "poweroff" ? Theme.error :
|
||||
action === "reboot" ? Theme.warning : Theme.primary
|
||||
powerConfirmModalLoader.item.show(title, message, function() {
|
||||
switch (action) {
|
||||
case "logout":
|
||||
SessionService.logout()
|
||||
break
|
||||
case "suspend":
|
||||
SessionService.suspend()
|
||||
break
|
||||
case "reboot":
|
||||
SessionService.reboot()
|
||||
break
|
||||
case "poweroff":
|
||||
SessionService.poweroff()
|
||||
break
|
||||
}
|
||||
}, function() {})
|
||||
}
|
||||
}
|
||||
powerConfirmModalLoader.active = true
|
||||
if (powerConfirmModalLoader.item) {
|
||||
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
|
||||
powerConfirmModalLoader.item.show(title, message, function () {
|
||||
switch (action) {
|
||||
case "logout":
|
||||
SessionService.logout()
|
||||
break
|
||||
case "suspend":
|
||||
SessionService.suspend()
|
||||
break
|
||||
case "reboot":
|
||||
SessionService.reboot()
|
||||
break
|
||||
case "poweroff":
|
||||
SessionService.poweroff()
|
||||
break
|
||||
}
|
||||
}, function () {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
@@ -235,7 +217,6 @@ ShellRoot {
|
||||
ConfirmModal {
|
||||
id: powerConfirmModal
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
@@ -246,7 +227,6 @@ ShellRoot {
|
||||
ProcessListPopout {
|
||||
id: processListPopout
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SettingsModal {
|
||||
@@ -261,7 +241,6 @@ ShellRoot {
|
||||
AppDrawerPopout {
|
||||
id: appDrawerPopout
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SpotlightModal {
|
||||
@@ -284,16 +263,17 @@ ShellRoot {
|
||||
ProcessListModal {
|
||||
id: processListModal
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LazyLoader {
|
||||
id: notepadModalLoader
|
||||
id: notepadSlideoutLoader
|
||||
|
||||
active: false
|
||||
|
||||
NotepadModal {
|
||||
id: notepadModal
|
||||
NotepadSlideout {
|
||||
id: notepadSlideout
|
||||
|
||||
modelData: Quickshell.screens.length > 0 ? Quickshell.screens[0] : null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,31 +286,28 @@ ShellRoot {
|
||||
id: powerMenuModal
|
||||
|
||||
onPowerActionRequested: (action, title, message) => {
|
||||
powerConfirmModalLoader.active = true
|
||||
if (powerConfirmModalLoader.item) {
|
||||
powerConfirmModalLoader.item.confirmButtonColor =
|
||||
action === "poweroff" ? Theme.error :
|
||||
action === "reboot" ? Theme.warning : Theme.primary
|
||||
powerConfirmModalLoader.item.show(title, message, function() {
|
||||
switch (action) {
|
||||
case "logout":
|
||||
SessionService.logout()
|
||||
break
|
||||
case "suspend":
|
||||
SessionService.suspend()
|
||||
break
|
||||
case "reboot":
|
||||
SessionService.reboot()
|
||||
break
|
||||
case "poweroff":
|
||||
SessionService.poweroff()
|
||||
break
|
||||
}
|
||||
}, function() {})
|
||||
}
|
||||
}
|
||||
powerConfirmModalLoader.active = true
|
||||
if (powerConfirmModalLoader.item) {
|
||||
powerConfirmModalLoader.item.confirmButtonColor = action === "poweroff" ? Theme.error : action === "reboot" ? Theme.warning : Theme.primary
|
||||
powerConfirmModalLoader.item.show(title, message, function () {
|
||||
switch (action) {
|
||||
case "logout":
|
||||
SessionService.logout()
|
||||
break
|
||||
case "suspend":
|
||||
SessionService.suspend()
|
||||
break
|
||||
case "reboot":
|
||||
SessionService.reboot()
|
||||
break
|
||||
case "poweroff":
|
||||
SessionService.poweroff()
|
||||
break
|
||||
}
|
||||
}, function () {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
@@ -389,26 +366,29 @@ ShellRoot {
|
||||
|
||||
IpcHandler {
|
||||
function open() {
|
||||
notepadModalLoader.active = true
|
||||
if (notepadModalLoader.item)
|
||||
notepadModalLoader.item.show()
|
||||
|
||||
return "NOTEPAD_OPEN_SUCCESS"
|
||||
notepadSlideoutLoader.active = true
|
||||
if (notepadSlideoutLoader.item) {
|
||||
notepadSlideoutLoader.item.show()
|
||||
return "NOTEPAD_OPEN_SUCCESS"
|
||||
}
|
||||
return "NOTEPAD_OPEN_FAILED"
|
||||
}
|
||||
|
||||
function close() {
|
||||
if (notepadModalLoader.item)
|
||||
notepadModalLoader.item.hide()
|
||||
|
||||
return "NOTEPAD_CLOSE_SUCCESS"
|
||||
if (notepadSlideoutLoader.item) {
|
||||
notepadSlideoutLoader.item.hide()
|
||||
return "NOTEPAD_CLOSE_SUCCESS"
|
||||
}
|
||||
return "NOTEPAD_CLOSE_FAILED"
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
notepadModalLoader.active = true
|
||||
if (notepadModalLoader.item)
|
||||
notepadModalLoader.item.toggle()
|
||||
|
||||
return "NOTEPAD_TOGGLE_SUCCESS"
|
||||
notepadSlideoutLoader.active = true
|
||||
if (notepadSlideoutLoader.item) {
|
||||
notepadSlideoutLoader.item.toggle()
|
||||
return "NOTEPAD_TOGGLE_SUCCESS"
|
||||
}
|
||||
return "NOTEPAD_TOGGLE_FAILED"
|
||||
}
|
||||
|
||||
target: "notepad"
|
||||
@@ -421,7 +401,6 @@ ShellRoot {
|
||||
modelData: item
|
||||
visible: ToastService.toastVisible
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Variants {
|
||||
@@ -430,7 +409,6 @@ ShellRoot {
|
||||
delegate: VolumeOSD {
|
||||
modelData: item
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Variants {
|
||||
@@ -439,7 +417,6 @@ ShellRoot {
|
||||
delegate: MicMuteOSD {
|
||||
modelData: item
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Variants {
|
||||
@@ -448,7 +425,6 @@ ShellRoot {
|
||||
delegate: BrightnessOSD {
|
||||
modelData: item
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Variants {
|
||||
@@ -457,7 +433,5 @@ ShellRoot {
|
||||
delegate: IdleInhibitorOSD {
|
||||
modelData: item
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user