Warning, /frameworks/qqc2-desktop-style/org.kde.desktop/private/TextFieldContextMenu.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
0003     SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
0004     SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 pragma Singleton
0010 
0011 import QtQml.Models
0012 import QtQuick
0013 import QtQuick.Controls as QQC2
0014 import org.kde.kirigami as Kirigami
0015 import org.kde.sonnet as Sonnet
0016 
0017 QQC2.Menu {
0018     id: root
0019 
0020     property Item target
0021     property bool deselectWhenMenuClosed: true
0022     property int restoredCursorPosition: 0
0023     property int restoredSelectionStart
0024     property int restoredSelectionEnd
0025     property bool persistentSelectionSetting
0026 
0027     // assuming that Instantiator::active is bound to target.Kirigami.SpellCheck.enabled
0028     property Instantiator/*<Sonnet.SpellcheckHighlighter>*/ spellcheckHighlighterInstantiator
0029 
0030     // assuming that spellchecker's active state is not writable, use target.Kirigami.SpellCheck.enabled instead.
0031     readonly property Sonnet.SpellcheckHighlighter spellcheckHighlighter:
0032         spellcheckHighlighterInstantiator?.object as Sonnet.SpellcheckHighlighter
0033 
0034     property /*list<string>*/var spellcheckSuggestions: []
0035 
0036     Component.onCompleted: persistentSelectionSetting = persistentSelectionSetting // break binding
0037 
0038     property var runOnMenuClose: () => {}
0039 
0040     function storeCursorAndSelection() {
0041         restoredCursorPosition = target.cursorPosition;
0042         restoredSelectionStart = target.selectionStart;
0043         restoredSelectionEnd = target.selectionEnd;
0044     }
0045 
0046     // target is pressed with mouse
0047     function targetClick(
0048         handlerPoint,
0049         target,
0050         spellcheckHighlighterInstantiator,
0051         mousePosition,
0052     ) {
0053         if (!(target instanceof TextInput || target instanceof TextEdit)) {
0054             console.warn("Target not supported by standard context menu:", target);
0055             return;
0056         }
0057         if (handlerPoint.pressedButtons === Qt.RightButton) { // only accept just right click
0058             if (visible) {
0059                 deselectWhenMenuClosed = false; // don't deselect text if menu closed by right click on textfield
0060                 dismiss();
0061             } else {
0062                 this.target = target;
0063                 target.persistentSelection = true; // persist selection when menu is opened
0064 
0065                 this.spellcheckHighlighterInstantiator = spellcheckHighlighterInstantiator;
0066 
0067                 spellcheckSuggestions = (spellcheckHighlighter && mousePosition)
0068                     ? spellcheckHighlighter.suggestions(mousePosition)
0069                     : [];
0070 
0071                 storeCursorAndSelection();
0072                 popup(target);
0073                 // slightly locate context menu away from mouse so no item is selected when menu is opened
0074                 x += 1
0075                 y += 1
0076             }
0077         } else {
0078             dismiss();
0079         }
0080     }
0081 
0082     // context menu keyboard key
0083     function targetKeyPressed(event, target) {
0084         if (event.modifiers === Qt.NoModifier && event.key === Qt.Key_Menu) {
0085             this.target = target;
0086             target.persistentSelection = true; // persist selection when menu is opened
0087             storeCursorAndSelection();
0088             popup(target);
0089         }
0090     }
0091 
0092     function __hasSelectedText(): bool {
0093         return target !== null
0094             && target.selectedText !== "";
0095     }
0096 
0097     function __editable(): bool {
0098         return target !== null
0099             && !target.readOnly;
0100     }
0101 
0102     function __hasSpellcheckCapability(): bool {
0103         return __editable()
0104             && spellcheckHighlighterInstantiator !== null;
0105     }
0106 
0107     function __showSpellcheckActions(): bool {
0108         return __editable()
0109             && spellcheckHighlighter !== null
0110             && spellcheckHighlighter.active
0111             && spellcheckHighlighter.wordIsMisspelled;
0112     }
0113 
0114     // Show actions which should normally be hidden for password field
0115     function __showPasswordRestrictedActions(): bool {
0116         return target !== null
0117             && target.echoMode !== TextInput.PasswordEchoOnEdit
0118             && target.echoMode !== TextInput.Password;
0119     }
0120 
0121     // Show text editing actions which should normally be hidden for password field
0122     function __showPasswordRestrictedEditingActions(): bool {
0123         return __showPasswordRestrictedActions() && !target.readOnly;
0124     }
0125 
0126     modal: true
0127 
0128     // deal with whether text should be deselected
0129     onClosed: {
0130         // reset parent, so OverlayZStacking could refresh z order next time
0131         // this menu is about to open for the same item that might have been
0132         // reparented to a different popup.
0133         parent = null;
0134 
0135         // restore text field's original persistent selection setting
0136         target.persistentSelection = persistentSelectionSetting
0137         // deselect text field text if menu is closed not because of a right click on the text field
0138         if (deselectWhenMenuClosed) {
0139             target.deselect();
0140         }
0141         deselectWhenMenuClosed = true;
0142 
0143         // restore cursor position
0144         target.forceActiveFocus();
0145         target.cursorPosition = restoredCursorPosition;
0146         target.select(restoredSelectionStart, restoredSelectionEnd);
0147 
0148         // run action, and free memory
0149         try {
0150             runOnMenuClose();
0151         } catch (e) {
0152             console.error(e);
0153             console.trace();
0154         }
0155         runOnMenuClose = () => {};
0156 
0157         // clean up spellchecker
0158         spellcheckHighlighterInstantiator = null;
0159         spellcheckSuggestions = [];
0160     }
0161 
0162     onOpened: {
0163         runOnMenuClose = () => {};
0164     }
0165 
0166     Instantiator {
0167         active: root.__showSpellcheckActions()
0168 
0169         model: root.spellcheckSuggestions
0170         delegate: QQC2.MenuItem {
0171             required property string modelData
0172 
0173             text: modelData
0174 
0175             onClicked: {
0176                 root.deselectWhenMenuClosed = false;
0177                 root.runOnMenuClose = () => {
0178                     root.spellcheckHighlighter.replaceWord(modelData);
0179                 };
0180             }
0181         }
0182         onObjectAdded: (index, object) => {
0183             root.insertItem(0, object);
0184         }
0185         onObjectRemoved: (index, object) => {
0186             root.removeItem(object);
0187         }
0188     }
0189 
0190     QQC2.MenuItem {
0191         visible: root.__showSpellcheckActions() && root.spellcheckSuggestions.length === 0
0192         action: QQC2.Action {
0193             enabled: false
0194             text: root.spellcheckHighlighter
0195                 ? qsTr('No Suggestions for "%1"')
0196                     .arg(root.spellcheckHighlighter.wordUnderMouse)
0197                 : ""
0198         }
0199     }
0200 
0201     QQC2.MenuSeparator {
0202         visible: root.__showSpellcheckActions()
0203     }
0204 
0205     QQC2.MenuItem {
0206         visible: root.__showSpellcheckActions()
0207         action: QQC2.Action {
0208             text: root.spellcheckHighlighter
0209                 ? qsTr('Add "%1" to Dictionary')
0210                     .arg(root.spellcheckHighlighter.wordUnderMouse)
0211                 : ""
0212 
0213             onTriggered: {
0214                 root.deselectWhenMenuClosed = false;
0215                 root.runOnMenuClose = () => {
0216                     root.spellcheckHighlighter.addWordToDictionary(root.spellcheckHighlighter.wordUnderMouse);
0217                 };
0218             }
0219         }
0220     }
0221 
0222     QQC2.MenuItem {
0223         visible: root.__showSpellcheckActions()
0224         action: QQC2.Action {
0225             text: qsTr("Ignore")
0226             onTriggered: {
0227                 root.deselectWhenMenuClosed = false;
0228                 root.runOnMenuClose = () => {
0229                     root.spellcheckHighlighter.ignoreWord(root.spellcheckHighlighter.wordUnderMouse);
0230                 };
0231             }
0232         }
0233     }
0234 
0235     QQC2.MenuItem {
0236         visible: root.__hasSpellcheckCapability()
0237 
0238         checkable: true
0239         checked: root.target?.Kirigami.SpellCheck.enabled ?? false
0240         text: qsTr("Spell Check")
0241 
0242         onToggled: {
0243             if (root.target) {
0244                 root.target.Kirigami.SpellCheck.enabled = checked;
0245             }
0246         }
0247     }
0248 
0249     QQC2.MenuSeparator {
0250         visible: root.__hasSpellcheckCapability()
0251             && (root.__editable() || root.__showPasswordRestrictedActions())
0252     }
0253 
0254     QQC2.MenuItem {
0255         action: QQC2.Action {
0256             icon.name: "edit-undo-symbolic"
0257             text: qsTr("Undo")
0258             shortcut: StandardKey.Undo
0259         }
0260         visible: root.__showPasswordRestrictedEditingActions()
0261         enabled: root.target?.canUndo ?? false
0262         onTriggered: {
0263             root.deselectWhenMenuClosed = false;
0264             root.runOnMenuClose = () => {
0265                 root.target.undo();
0266             };
0267         }
0268     }
0269     QQC2.MenuItem {
0270         action: QQC2.Action {
0271             icon.name: "edit-redo-symbolic"
0272             text: qsTr("Redo")
0273             shortcut: StandardKey.Redo
0274         }
0275         visible: root.__showPasswordRestrictedEditingActions()
0276         enabled: root.target?.canRedo ?? false
0277         onTriggered: {
0278             root.deselectWhenMenuClosed = false;
0279             root.runOnMenuClose = () => {
0280                 root.target.redo();
0281             };
0282         }
0283     }
0284     QQC2.MenuSeparator {
0285         visible: root.__showPasswordRestrictedEditingActions()
0286     }
0287     QQC2.MenuItem {
0288         action: QQC2.Action {
0289             icon.name: "edit-cut-symbolic"
0290             text: qsTr("Cut")
0291             shortcut: StandardKey.Cut
0292         }
0293         visible: root.__showPasswordRestrictedEditingActions()
0294         enabled: root.__hasSelectedText()
0295         onTriggered: {
0296             root.deselectWhenMenuClosed = false;
0297             root.runOnMenuClose = () => {
0298                 root.target.cut();
0299             };
0300         }
0301     }
0302     QQC2.MenuItem {
0303         action: QQC2.Action {
0304             icon.name: "edit-copy-symbolic"
0305             text: qsTr("Copy")
0306             shortcut: StandardKey.Copy
0307         }
0308         visible: root.__showPasswordRestrictedActions()
0309         enabled: root.__hasSelectedText()
0310         onTriggered: {
0311             root.deselectWhenMenuClosed = false;
0312             root.runOnMenuClose = () => {
0313                 root.target.copy();
0314             };
0315         }
0316     }
0317     QQC2.MenuItem {
0318         action: QQC2.Action {
0319             icon.name: "edit-paste-symbolic"
0320             text: qsTr("Paste")
0321             shortcut: StandardKey.Paste
0322         }
0323         visible: root.__editable()
0324         enabled: target?.canPaste ?? false
0325         onTriggered: {
0326             root.deselectWhenMenuClosed = false;
0327             root.runOnMenuClose = () => {
0328                 root.target.paste();
0329             };
0330         }
0331     }
0332     QQC2.MenuItem {
0333         action: QQC2.Action {
0334             icon.name: "edit-delete-symbolic"
0335             text: qsTr("Delete")
0336             shortcut: StandardKey.Delete
0337         }
0338         visible: root.__editable()
0339         enabled: root.__hasSelectedText()
0340         onTriggered: {
0341             root.deselectWhenMenuClosed = false;
0342             root.runOnMenuClose = () => {
0343                 root.target.remove(root.target.selectionStart, root.target.selectionEnd);
0344             };
0345         }
0346     }
0347     QQC2.MenuSeparator {
0348         visible: root.target !== null
0349             && (root.__editable() || root.__showPasswordRestrictedActions())
0350     }
0351     QQC2.MenuItem {
0352         action: QQC2.Action {
0353             icon.name: "edit-select-all-symbolic"
0354             text: qsTr("Select All")
0355             shortcut: StandardKey.SelectAll
0356         }
0357         visible: root.target !== null
0358         onTriggered: {
0359             root.deselectWhenMenuClosed = false;
0360             root.runOnMenuClose = () => {
0361                 root.target.selectAll();
0362             };
0363         }
0364     }
0365 }