Warning, /plasma/kdeplasma-addons/applets/notes/package/contents/ui/main.qml is written in an unsupported language. File is not indexed.
0001 /* 0002 SPDX-FileCopyrightText: 2014 David Edmundson <davidedmundson@kde.org> 0003 SPDX-FileCopyrightText: 2014, 2015 Kai Uwe Broulik <kde@privat.broulik.de> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 import QtQuick 0009 import QtQuick.Controls as QQC2 // just for desktop-styled context menus 0010 import QtQuick.Layouts 0011 import QtQuick.Window 0012 import QtQuick.Dialogs 0013 0014 import org.kde.draganddrop 2.0 as DragDrop 0015 0016 import org.kde.plasma.core as PlasmaCore 0017 import org.kde.kirigami 2.20 as Kirigami 0018 import org.kde.ksvg 1.0 as KSvg 0019 import org.kde.plasma.components 3.0 as PlasmaComponents3 0020 import org.kde.plasma.extras 2.0 as PlasmaExtras 0021 import org.kde.plasma.plasmoid 2.0 0022 0023 import org.kde.plasma.private.notes 0.1 0024 0025 PlasmoidItem { 0026 id: root 0027 0028 switchWidth: Kirigami.Units.gridUnit * 5 0029 switchHeight: Kirigami.Units.gridUnit * 5 0030 0031 Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground 0032 0033 // this isn't a frameSVG, the default SVG margins take up around 7% of the frame size, so we use that 0034 readonly property real horizontalMargins: fullRepresentationItem.width * 0.07 0035 readonly property real verticalMargins: fullRepresentationItem.height * 0.07 0036 readonly property PlasmaComponents3.TextArea mainTextArea: fullRepresentationItem.mainTextArea 0037 0038 // note is of type Note 0039 property QtObject note: noteManager.loadNote(Plasmoid.configuration.noteId); 0040 0041 // define colors used for icons in ToolButtons and for text in TextArea. 0042 // this is deliberately _NOT_ the theme color as we are over a known bright background! 0043 // an unknown colour over a known colour is a bad move as you end up with white on yellow. 0044 readonly property color textIconColor: { 0045 if (Plasmoid.configuration.color === "black" || Plasmoid.configuration.color === "translucent-light") { 0046 return "#dfdfdf"; 0047 } 0048 return "#202020"; 0049 } 0050 0051 onExternalData: (mimetype, data) => { 0052 // if we dropped a text file, we want its contents, 0053 // otherwise we take the external data verbatim 0054 var contents = NotesHelper.fileContents(data) || data 0055 mainTextArea.text = String(contents).replace(/\n/g, "<br>") // what about richtext? 0056 0057 // place cursor at the end of text, there's no "just move the cursor" function 0058 mainTextArea.moveCursorSelection(mainTextArea.length) 0059 mainTextArea.deselect() 0060 } 0061 0062 Timer { 0063 id: forceFocusTimer 0064 interval: 1 0065 onTriggered: mainTextArea.forceActiveFocus() 0066 } 0067 0068 Connections { 0069 target: Plasmoid 0070 function onActivated() { 0071 // FIXME doing forceActiveFocus here directly doesn't work 0072 forceFocusTimer.restart() 0073 } 0074 } 0075 0076 NoteManager { 0077 id: noteManager 0078 } 0079 0080 // Only exists because the default CompactRepresentation doesn't open on drag. 0081 // TODO remove once it gains that feature (perhaps optionally?) 0082 compactRepresentation: DragDrop.DropArea { 0083 id: compactDropArea 0084 onDragEnter: activationTimer.restart() 0085 onDragLeave: activationTimer.stop() 0086 0087 Timer { 0088 id: activationTimer 0089 interval: 250 // matches taskmanager delay 0090 onTriggered: root.expanded = true 0091 } 0092 0093 MouseArea { 0094 anchors.fill: parent 0095 hoverEnabled: true 0096 0097 property bool wasExpanded 0098 0099 onPressed: wasExpanded = root.expanded 0100 onClicked: root.expanded = !wasExpanded 0101 0102 Kirigami.Icon { 0103 anchors.fill: parent 0104 source: "knotes-symbolic" 0105 active: parent.containsMouse 0106 } 0107 } 0108 } 0109 0110 preloadFullRepresentation: true 0111 fullRepresentation: KSvg.SvgItem { 0112 id: backgroundItem 0113 0114 property alias mainTextArea: mainTextArea 0115 Layout.preferredWidth: Kirigami.Units.gridUnit * 25 0116 Layout.preferredHeight: Kirigami.Units.gridUnit * 25 0117 Layout.minimumWidth: Kirigami.Units.gridUnit * 2 0118 Layout.minimumHeight: Kirigami.Units.gridUnit * 2 0119 0120 imagePath: "widgets/notes" 0121 elementId: Plasmoid.configuration.color + "-notes" 0122 0123 DocumentHandler { 0124 id: documentHandler 0125 target: mainTextArea 0126 cursorPosition: mainTextArea.cursorPosition 0127 selectionStart: mainTextArea.selectionStart 0128 selectionEnd: mainTextArea.selectionEnd 0129 defaultFontSize: Plasmoid.configuration.fontSize 0130 } 0131 0132 Connections { 0133 target: root 0134 function onExpandedChanged(expanded) { 0135 // don't autofocus when we're on the desktop 0136 if (expanded && (Plasmoid.formFactor === PlasmaCore.Types.Vertical || Plasmoid.formFactor === PlasmaCore.Types.Horizontal)) { 0137 mainTextArea.forceActiveFocus() 0138 } 0139 } 0140 } 0141 Component.onDestruction: { 0142 note.save(mainTextArea.text); 0143 } 0144 0145 FocusScope { 0146 id: focusScope 0147 anchors { 0148 fill: parent 0149 leftMargin: horizontalMargins 0150 rightMargin: horizontalMargins 0151 topMargin: verticalMargins 0152 bottomMargin: verticalMargins 0153 } 0154 0155 PlasmaComponents3.ScrollView { 0156 id: scrollview 0157 anchors { 0158 top: parent.top 0159 left: parent.left 0160 right: parent.right 0161 bottom: fontButtons.top 0162 bottomMargin: Kirigami.Units.largeSpacing 0163 } 0164 0165 clip: true 0166 0167 PlasmaComponents3.TextArea { 0168 id: mainTextArea 0169 property int cfgFontPointSize: Plasmoid.configuration.fontSize 0170 0171 textFormat: TextEdit.RichText 0172 onLinkActivated: Qt.openUrlExternally(link) 0173 background: null 0174 color: textIconColor 0175 persistentSelection: true 0176 wrapMode: TextEdit.Wrap 0177 0178 font.pointSize: cfgFontPointSize 0179 0180 Keys.onPressed: event => { 0181 if (event.key === Qt.Key_Escape) { 0182 root.expanded = false; 0183 event.accepted = true; 0184 } else if (event.modifiers === Qt.ControlModifier) { 0185 if (event.key === Qt.Key_B) { 0186 documentHandler.bold = !documentHandler.bold; 0187 event.accepted = true; 0188 } else if (event.key === Qt.Key_I) { 0189 documentHandler.italic = !documentHandler.italic; 0190 event.accepted = true; 0191 } else if (event.key === Qt.Key_U) { 0192 documentHandler.underline = !documentHandler.underline; 0193 event.accepted = true; 0194 } else if (event.key === Qt.Key_S) { 0195 documentHandler.strikeOut = !documentHandler.strikeOut; 0196 event.accepted = true; 0197 } else if (event.matches(StandardKey.Paste)) { 0198 documentHandler.pasteWithoutFormatting(); 0199 documentHandler.reset(); 0200 event.accepted = true; 0201 } else if (event.matches(StandardKey.Cut)) { 0202 cut(); 0203 documentHandler.reset(); 0204 event.accepted = true; 0205 } 0206 } 0207 } 0208 0209 // Apply the font size change to existing texts 0210 onCfgFontPointSizeChanged: { 0211 const start = selectionStart; 0212 const end = selectionEnd; 0213 0214 selectAll(); 0215 documentHandler.fontSize = cfgFontPointSize; 0216 select(start, end); 0217 } 0218 0219 // update the note if the source changes, but only if the user isn't editing it currently 0220 Binding { 0221 target: mainTextArea 0222 property: "text" 0223 value: note.noteText 0224 when: !mainTextArea.activeFocus 0225 // don't restore an empty value (which IS empty by default when the applet starts up), 0226 // instead only remove this binding for the time when the user edits the content. 0227 restoreMode: Binding.RestoreBinding 0228 } 0229 0230 onActiveFocusChanged: { 0231 const window = Window.window; 0232 if (activeFocus && window && (window.flags & Qt.WindowDoesNotAcceptFocus)) { 0233 Plasmoid.status = PlasmaCore.Types.AcceptingInputStatus 0234 } else { 0235 Plasmoid.status = PlasmaCore.Types.ActiveStatus 0236 note.save(text); 0237 } 0238 } 0239 0240 onPressed: event => { 0241 if (event.button === Qt.RightButton) { 0242 event.accepted = true; 0243 contextMenu.popup(); 0244 forceActiveFocus(); 0245 } 0246 if (event.button === Qt.LeftButton && contextMenu.visible === true) { 0247 event.accepted = true; 0248 contextMenu.dismiss(); 0249 forceActiveFocus(); 0250 } 0251 } 0252 0253 Component.onCompleted: { 0254 if (!Plasmoid.configuration.fontSize) { 0255 // Set fontSize to default if it is not set 0256 Plasmoid.configuration.fontSize = font.pointSize 0257 } 0258 } 0259 0260 QQC2.Menu { 0261 id: contextMenu 0262 0263 ShortcutMenuItem { 0264 _sequence: StandardKey.Undo 0265 _enabled: mainTextArea.canUndo 0266 _iconName: "edit-undo" 0267 _text: i18n("Undo") 0268 onTriggered: contextMenu.retFocus(() => mainTextArea.undo()) 0269 } 0270 0271 ShortcutMenuItem { 0272 _sequence: StandardKey.Redo 0273 _enabled: mainTextArea.canRedo 0274 _iconName: "edit-redo" 0275 _text: i18n("Redo") 0276 onTriggered: contextMenu.retFocus(() => mainTextArea.redo()) 0277 } 0278 0279 QQC2.MenuSeparator {} 0280 0281 ShortcutMenuItem { 0282 _sequence: StandardKey.Cut 0283 _enabled: mainTextArea.selectedText.length > 0 0284 _iconName: "edit-cut" 0285 _text: i18n("Cut") 0286 onTriggered: contextMenu.retFocus(() => mainTextArea.cut()) 0287 } 0288 0289 ShortcutMenuItem { 0290 _sequence: StandardKey.Copy 0291 _enabled: mainTextArea.selectedText.length > 0 0292 _iconName: "edit-copy" 0293 _text: i18n("Copy") 0294 onTriggered: contextMenu.retFocus(() => mainTextArea.copy()) 0295 } 0296 0297 ShortcutMenuItem { 0298 _sequence: StandardKey.Paste 0299 _enabled: mainTextArea.canPaste 0300 _iconName: "edit-paste" 0301 _text: i18n("Paste") 0302 onTriggered: contextMenu.retFocus(() => documentHandler.pasteWithoutFormatting()) 0303 } 0304 0305 ShortcutMenuItem { 0306 _enabled: mainTextArea.canPaste 0307 _text: i18n("Paste with Full Formatting") 0308 _iconName: "edit-paste" 0309 onTriggered: contextMenu.retFocus(() => mainTextArea.paste()) 0310 } 0311 0312 ShortcutMenuItem { 0313 _enabled: mainTextArea.selectedText.length > 0 0314 _text: i18nc("@action:inmenu", "Remove Formatting") 0315 _iconName: "edit-clear-all" 0316 onTriggered: { 0317 var richText = mainTextArea.getFormattedText(mainTextArea.selectionStart, mainTextArea.selectionEnd) 0318 var unformattedText = documentHandler.strip(richText) 0319 unformattedText = unformattedText.replace(/\n/g, "<br>") 0320 mainTextArea.remove(mainTextArea.selectionStart, mainTextArea.selectionEnd) 0321 contextMenu.retFocus(() => mainTextArea.insert(mainTextArea.selectionStart, unformattedText)) 0322 } 0323 } 0324 0325 ShortcutMenuItem { 0326 _sequence: StandardKey.Delete 0327 _enabled: mainTextArea.selectedText.length > 0 0328 _iconName: "edit-delete" 0329 _text: i18n("Delete") 0330 onTriggered: contextMenu.retFocus(() => mainTextArea.remove(mainTextArea.selectionStart, mainTextArea.selectionEnd)) 0331 } 0332 0333 ShortcutMenuItem { 0334 _enabled: mainTextArea.text.length > 0 0335 _iconName: "edit-clear" 0336 _text: i18n("Clear") 0337 onTriggered: contextMenu.retFocus(() => mainTextArea.clear()) 0338 } 0339 0340 QQC2.MenuSeparator {} 0341 0342 ShortcutMenuItem { 0343 _sequence: StandardKey.SelectAll 0344 _enabled: mainTextArea.text.length > 0 0345 _iconName: "edit-select-all" 0346 _text: i18n("Select All") 0347 onTriggered: contextMenu.retFocus(() => mainTextArea.selectAll()) 0348 } 0349 0350 function retFocus(f) { 0351 f() 0352 documentHandler.reset() 0353 mainTextArea.forceActiveFocus() 0354 } 0355 } 0356 } 0357 0358 // Save scrolling position when it changes, but throttle to avoid 0359 // killing a storage disk. 0360 Connections { 0361 target: scrollview.contentItem 0362 function onContentXChanged() { 0363 throttedScrollSaver.restart(); 0364 } 0365 function onContentYChanged() { 0366 throttedScrollSaver.restart(); 0367 } 0368 } 0369 Connections { 0370 target: mainTextArea 0371 function onCursorPositionChanged() { 0372 throttedScrollSaver.restart(); 0373 } 0374 } 0375 0376 Timer { 0377 id: throttedScrollSaver 0378 interval: Kirigami.Units.humanMoment 0379 repeat: false 0380 running: false 0381 onTriggered: scrollview.saveScroll() 0382 } 0383 0384 function saveScroll() { 0385 const flickable = scrollview.contentItem; 0386 Plasmoid.configuration.scrollX = flickable.contentX; 0387 Plasmoid.configuration.scrollY = flickable.contentY; 0388 Plasmoid.configuration.cursorPosition = mainTextArea.cursorPosition; 0389 } 0390 0391 function restoreScroll() { 0392 const flickable = scrollview.contentItem; 0393 flickable.contentX = Plasmoid.configuration.scrollX; 0394 flickable.contentY = Plasmoid.configuration.scrollY; 0395 mainTextArea.cursorPosition = Plasmoid.configuration.cursorPosition; 0396 } 0397 0398 // Give it some time to lay out the text, because at this 0399 // point in time content size is not reliable yet. 0400 Component.onCompleted: Qt.callLater(restoreScroll) 0401 Component.onDestruction: saveScroll() 0402 } 0403 0404 DragDrop.DropArea { 0405 id: dropArea 0406 anchors.fill: scrollview 0407 0408 function positionOfDrop(event) { 0409 return mainTextArea.positionAt(event.x, event.y) 0410 } 0411 0412 onDrop: { 0413 var mimeData = event.mimeData 0414 var text = "" 0415 if (mimeData.hasUrls) { 0416 var urls = mimeData.urls 0417 for (var i = 0, j = urls.length; i < j; ++i) { 0418 var url = urls[i] 0419 text += "<a href=\"" + url + "\">" + url + "</a><br>" 0420 } 0421 } else { 0422 text = mimeData.text.replace(/\n/g, "<br>") 0423 } 0424 0425 mainTextArea.insert(positionOfDrop(event), text) 0426 event.accept(Qt.CopyAction) 0427 } 0428 onDragMove: { 0429 // there doesn't seem to be a "just move the cursor", so we move 0430 // the selection and then unselect so the cursor follows the mouse 0431 mainTextArea.moveCursorSelection(positionOfDrop(event)) 0432 mainTextArea.deselect() 0433 } 0434 0435 MouseArea { 0436 anchors.fill: parent 0437 cursorShape: mainTextArea.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor 0438 acceptedButtons: Qt.NoButton 0439 } 0440 } 0441 0442 RowLayout { 0443 id: fontButtons 0444 spacing: Kirigami.Units.smallSpacing 0445 anchors { 0446 bottom: parent.bottom 0447 left: parent.left 0448 right: parent.right 0449 } 0450 height: visible ? implicitHeight : 0 0451 visible: opacity > 0 0452 opacity: focusScope.activeFocus ? 1 : 0 0453 Behavior on opacity { NumberAnimation { duration: Kirigami.Units.longDuration } } 0454 0455 readonly property int requiredWidth: formatButtonsRow.width + spacing + settingsButton.width + removeButton.width 0456 readonly property bool showFormatButtons: width > requiredWidth 0457 0458 Row { 0459 id: formatButtonsRow 0460 spacing: Kirigami.Units.smallSpacing 0461 // show format buttons if TextField or any of the buttons have focus 0462 enabled: opacity > 0 0463 visible: fontButtons.showFormatButtons 0464 0465 PlasmaComponents3.ToolButton { 0466 focusPolicy: Qt.TabFocus 0467 icon.name: "format-text-bold" 0468 icon.color: textIconColor 0469 checked: documentHandler.bold 0470 onClicked: documentHandler.bold = !documentHandler.bold 0471 Accessible.name: boldTooltip.text 0472 PlasmaComponents3.ToolTip { 0473 id: boldTooltip 0474 text: i18nc("@info:tooltip", "Bold") 0475 } 0476 } 0477 PlasmaComponents3.ToolButton { 0478 focusPolicy: Qt.TabFocus 0479 icon.name: "format-text-italic" 0480 icon.color: textIconColor 0481 checked: documentHandler.italic 0482 onClicked: documentHandler.italic = !documentHandler.italic 0483 Accessible.name: italicTooltip.text 0484 PlasmaComponents3.ToolTip { 0485 id: italicTooltip 0486 text: i18nc("@info:tooltip", "Italic") 0487 } 0488 } 0489 PlasmaComponents3.ToolButton { 0490 focusPolicy: Qt.TabFocus 0491 icon.name: "format-text-underline" 0492 icon.color: textIconColor 0493 checked: documentHandler.underline 0494 onClicked: documentHandler.underline = !documentHandler.underline 0495 Accessible.name: underlineTooltip.text 0496 PlasmaComponents3.ToolTip { 0497 id: underlineTooltip 0498 text: i18nc("@info:tooltip", "Underline") 0499 } 0500 } 0501 PlasmaComponents3.ToolButton { 0502 focusPolicy: Qt.TabFocus 0503 icon.name: "format-text-strikethrough" 0504 icon.color: textIconColor 0505 checked: documentHandler.strikeOut 0506 onClicked: documentHandler.strikeOut = !documentHandler.strikeOut 0507 Accessible.name: strikethroughTooltip.text 0508 PlasmaComponents3.ToolTip { 0509 id: strikethroughTooltip 0510 text: i18nc("@info:tooltip", "Strikethrough") 0511 } 0512 } 0513 } 0514 0515 Item { // spacer 0516 Layout.fillWidth: true 0517 Layout.fillHeight: true 0518 } 0519 0520 PlasmaComponents3.ToolButton { 0521 id: settingsButton 0522 focusPolicy: Qt.TabFocus 0523 icon.name: "configure" 0524 icon.color: textIconColor 0525 onClicked: Plasmoid.internalAction("configure").trigger() 0526 Accessible.name: settingsTooltip.text 0527 PlasmaComponents3.ToolTip { 0528 id: settingsTooltip 0529 text: Plasmoid.internalAction("configure").text 0530 } 0531 } 0532 0533 PlasmaComponents3.ToolButton { 0534 id: removeButton 0535 focusPolicy: Qt.TabFocus 0536 icon.name: "edit-delete" 0537 icon.color: textIconColor 0538 onClicked: { 0539 // No need to ask for confirmation in the cases when... 0540 // ...the note is blank 0541 if (mainTextArea.length === 0 || 0542 // ...the note's content is equal to the clipboard text 0543 0544 // Note that we are intentionally not using 0545 // mainTextArea.getText() because it has a method of 0546 // converting the text to plainText that does not produce 0547 // the same exact output of various other methods, and if 0548 // we go out of our way to match it, we will be 0549 // depending on an implementation detail. So we instead 0550 // roll our own version to ensure that the conversion 0551 // is done in the same way every time. 0552 documentHandler.stripAndSimplify(mainTextArea.text) === documentHandler.strippedClipboardText() 0553 ) { 0554 Plasmoid.internalAction("remove").trigger(); 0555 } else { 0556 discardConfirmationDialogLoader.open(); 0557 } 0558 } 0559 Accessible.name: removeTooltip.text 0560 PlasmaComponents3.ToolTip { 0561 id: removeTooltip 0562 text: Plasmoid.internalAction("remove").text 0563 } 0564 } 0565 } 0566 } 0567 0568 Loader { 0569 id: discardConfirmationDialogLoader 0570 0571 function open() { 0572 if (item) { 0573 item.open(); 0574 } else { 0575 active = true; 0576 } 0577 item.visible = true; 0578 } 0579 0580 active: false 0581 0582 sourceComponent: MessageDialog { 0583 visible: false 0584 title: i18n("Discard this note?") 0585 text: i18n("Are you sure you want to discard this note?") 0586 0587 buttons: MessageDialog.Discard | MessageDialog.Cancel 0588 0589 onButtonClicked: (button, role) => { 0590 if (button === MessageDialog.Discard) { 0591 Plasmoid.internalAction("remove").trigger() 0592 visible = false; 0593 } 0594 } 0595 0596 onRejected: { 0597 visible = false 0598 } 0599 } 0600 } 0601 } 0602 0603 Plasmoid.contextualActions: [ 0604 PlasmaCore.Action { 0605 text: i18nc("@item:inmenu", "White") 0606 onTriggered: Plasmoid.configuration.color = "white" 0607 }, 0608 PlasmaCore.Action { 0609 text: i18nc("@item:inmenu", "Black") 0610 onTriggered: Plasmoid.configuration.color = "black" 0611 }, 0612 PlasmaCore.Action { 0613 text: i18nc("@item:inmenu", "Red") 0614 onTriggered: Plasmoid.configuration.color = "red" 0615 }, 0616 PlasmaCore.Action { 0617 text: i18nc("@item:inmenu", "Orange") 0618 onTriggered: Plasmoid.configuration.color = "orange" 0619 }, 0620 PlasmaCore.Action { 0621 text: i18nc("@item:inmenu", "Yellow") 0622 onTriggered: Plasmoid.configuration.color = "yellow" 0623 }, 0624 PlasmaCore.Action { 0625 text: i18nc("@item:inmenu", "Green") 0626 onTriggered: Plasmoid.configuration.color = "green" 0627 }, 0628 PlasmaCore.Action { 0629 text: i18nc("@item:inmenu", "Blue") 0630 onTriggered: Plasmoid.configuration.color = "blue" 0631 }, 0632 PlasmaCore.Action { 0633 text: i18nc("@item:inmenu", "Pink") 0634 onTriggered: Plasmoid.configuration.color = "pink" 0635 }, 0636 PlasmaCore.Action { 0637 text: i18nc("@item:inmenu", "Transparent") 0638 onTriggered: Plasmoid.configuration.color = "translucent" 0639 }, 0640 PlasmaCore.Action { 0641 text: i18nc("@item:inmenu", "Transparent Light") 0642 onTriggered: Plasmoid.configuration.color = "translucent-light" 0643 }, 0644 PlasmaCore.Action { 0645 isSeparator: true 0646 } 0647 ] 0648 0649 Component.onCompleted: { 0650 // Plasmoid configuration doesn't check before emitting change signal 0651 // explicit check is needed (at time of writing) 0652 if (note.id != Plasmoid.configuration.noteId) { 0653 Plasmoid.configuration.noteId = note.id; 0654 } 0655 } 0656 }