Spellchecking with new Sonnet declarative API
This replaces all the custom code with a shared implementation in QQC2-desktop style
This commit is contained in:
@@ -9,8 +9,7 @@ import QtQuick.Templates 2.15 as T
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtQuick.Window 2.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
|
||||
import org.kde.kirigami 2.18 as Kirigami
|
||||
import org.kde.neochat 1.0
|
||||
|
||||
ToolBar {
|
||||
@@ -69,7 +68,7 @@ ToolBar {
|
||||
font: inputField.font
|
||||
}
|
||||
|
||||
T.TextArea {
|
||||
TextArea {
|
||||
id: inputField
|
||||
focus: true
|
||||
/* Some QQC2 styles will have their own predefined backgrounds for TextAreas.
|
||||
@@ -101,16 +100,9 @@ ToolBar {
|
||||
wrapMode: Text.Wrap
|
||||
readOnly: currentRoom.usesEncryption
|
||||
|
||||
palette: Kirigami.Theme.palette
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
implicitWidth: Math.max(contentWidth + leftPadding + rightPadding,
|
||||
implicitBackgroundWidth + leftInset + rightInset,
|
||||
placeholder.implicitWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(contentHeight + topPadding + bottomPadding,
|
||||
implicitBackgroundHeight + topInset + bottomInset,
|
||||
placeholder.implicitHeight + topPadding + bottomPadding)
|
||||
Kirigami.SpellChecking.enabled: true
|
||||
|
||||
color: Kirigami.Theme.textColor
|
||||
selectionColor: Kirigami.Theme.highlightColor
|
||||
@@ -123,65 +115,6 @@ ToolBar {
|
||||
|
||||
selectByMouse: !Kirigami.Settings.tabletMode
|
||||
|
||||
cursorDelegate: Loader {
|
||||
visible: inputField.activeFocus && !inputField.readOnly && inputField.selectionStart === inputField.selectionEnd
|
||||
active: visible
|
||||
sourceComponent: CursorDelegate { target: inputField }
|
||||
}
|
||||
|
||||
CursorHandle {
|
||||
id: selectionStartHandle
|
||||
target: inputField
|
||||
}
|
||||
|
||||
CursorHandle {
|
||||
id: selectionEndHandle
|
||||
target: inputField
|
||||
isSelectionEnd: true
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
// unfortunately, taphandler's pressed event only triggers when the press is lifted
|
||||
// we need to use the longpress signal since it triggers when the button is first pressed
|
||||
longPressThreshold: 0
|
||||
onLongPressed: TextFieldContextMenu.targetClick(point, inputField, spellcheckhighlighter, inputField.positionAt(point.position.x, point.position.y));
|
||||
}
|
||||
|
||||
onPressAndHold: {
|
||||
if (!Kirigami.Settings.tabletMode) {
|
||||
return;
|
||||
}
|
||||
forceActiveFocus();
|
||||
cursorPosition = positionAt(event.x, event.y);
|
||||
selectWord();
|
||||
}
|
||||
|
||||
onFocusChanged: {
|
||||
if (focus) {
|
||||
MobileTextActionsToolBar.controlRoot = inputField;
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
id: placeholder
|
||||
x: inputField.leftPadding
|
||||
y: inputField.topPadding
|
||||
width: inputField.width - (inputField.leftPadding + inputField.rightPadding)
|
||||
height: inputField.height - (inputField.topPadding + inputField.bottomPadding)
|
||||
|
||||
text: inputField.placeholderText
|
||||
font: inputField.font
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
horizontalAlignment: inputField.horizontalAlignment
|
||||
verticalAlignment: inputField.verticalAlignment
|
||||
visible: !inputField.length && !inputField.preeditText && (!inputField.activeFocus || inputField.horizontalAlignment !== Qt.AlignHCenter)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
|
||||
ChatDocumentHandler {
|
||||
id: documentHandler
|
||||
document: inputField.textDocument
|
||||
@@ -191,18 +124,6 @@ ToolBar {
|
||||
room: currentRoom ?? null
|
||||
}
|
||||
|
||||
SpellcheckHighlighter {
|
||||
id: spellcheckhighlighter
|
||||
document: inputField.textDocument
|
||||
cursorPosition: inputField.cursorPosition
|
||||
selectionStart: inputField.selectionStart
|
||||
selectionEnd: inputField.selectionEnd
|
||||
onChangeCursorPosition: {
|
||||
inputField.cursorPosition = start;
|
||||
inputField.moveCursorSelection(end, TextEdit.SelectCharacters);
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timeoutTimer
|
||||
repeat: false
|
||||
@@ -243,9 +164,6 @@ ToolBar {
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
// trigger if context menu button is pressed
|
||||
TextFieldContextMenu.targetKeyPressed(event, inputField)
|
||||
|
||||
if (event.key === Qt.Key_PageDown) {
|
||||
switchRoomDown();
|
||||
} else if (event.key === Qt.Key_PageUp) {
|
||||
@@ -329,10 +247,7 @@ ToolBar {
|
||||
chatBar.complete();
|
||||
}
|
||||
|
||||
onPressed: MobileTextActionsToolBar.shouldBeVisible = true;
|
||||
|
||||
onTextChanged: {
|
||||
MobileTextActionsToolBar.shouldBeVisible = false;
|
||||
timeoutTimer.restart()
|
||||
repeatTimer.start()
|
||||
currentRoom.cachedInput = text
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
|
||||
* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Templates 2.15
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property alias target: root.parent
|
||||
|
||||
Rectangle {
|
||||
id: cursorLine
|
||||
property real previousX: 0
|
||||
property real previousY: 0
|
||||
parent: target
|
||||
implicitWidth: target.cursorRectangle.width
|
||||
implicitHeight: target.cursorRectangle.height
|
||||
x: Math.floor(target.cursorRectangle.x)
|
||||
y: Math.floor(target.cursorRectangle.y)
|
||||
|
||||
color: target.color
|
||||
SequentialAnimation {
|
||||
id: blinkAnimation
|
||||
running: root.visible && Qt.styleHints.cursorFlashTime != 0 && target.selectionStart === target.selectionEnd
|
||||
PropertyAction {
|
||||
target: cursorLine
|
||||
property: "opacity"
|
||||
value: 1
|
||||
}
|
||||
PauseAnimation {
|
||||
duration: Qt.styleHints.cursorFlashTime/2
|
||||
}
|
||||
SequentialAnimation {
|
||||
loops: Animation.Infinite
|
||||
OpacityAnimator {
|
||||
target: cursorLine
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Qt.styleHints.cursorFlashTime/2
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
OpacityAnimator {
|
||||
target: cursorLine
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Qt.styleHints.cursorFlashTime/2
|
||||
easing.type: Easing.OutCubic
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.target
|
||||
function onCursorPositionChanged() {
|
||||
blinkAnimation.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
/* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
|
||||
* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtQuick.Templates 2.15
|
||||
import org.kde.kirigami 2.14 as Kirigami
|
||||
|
||||
Loader {
|
||||
id: root
|
||||
property Item target
|
||||
property bool isSelectionEnd: false
|
||||
visible: Kirigami.Settings.tabletMode && target.activeFocus && (isSelectionEnd ? target.selectionStart !== target.selectionEnd : true)
|
||||
active: visible
|
||||
sourceComponent: Kirigami.ShadowedRectangle {
|
||||
id: handle
|
||||
property real selectionStartX: Math.floor(Qt.inputMethod.anchorRectangle.x + (Qt.inputMethod.cursorRectangle.width - width)/2)
|
||||
property real selectionStartY: Math.floor(Qt.inputMethod.anchorRectangle.y + Qt.inputMethod.cursorRectangle.height + pointyBitVerticalOffset)
|
||||
property real selectionEndX: Math.floor(Qt.inputMethod.cursorRectangle.x + (Qt.inputMethod.cursorRectangle.width - width)/2)
|
||||
property real selectionEndY: Math.floor(Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height + pointyBitVerticalOffset)
|
||||
property real pointyBitVerticalOffset: Math.abs(pointyBit.y*2)
|
||||
parent: Overlay.overlay
|
||||
x: isSelectionEnd ? selectionEndX : selectionStartX
|
||||
y: isSelectionEnd ? selectionEndY : selectionStartY
|
||||
|
||||
// HACK: make it appear above most popups that show up in the
|
||||
// overlay in case any of them use TextField or TextArea
|
||||
z: 999
|
||||
|
||||
//opacity: target.activeFocus ? 1 : 0
|
||||
implicitHeight: {
|
||||
let h = Kirigami.Units.gridUnit
|
||||
return h - (h % 2 == 0 ? 1 : 0)
|
||||
}
|
||||
implicitWidth: implicitHeight
|
||||
radius: width/2
|
||||
|
||||
color: target.selectionColor
|
||||
|
||||
shadow {
|
||||
color: Qt.rgba(0,0,0,0.2)
|
||||
size: 3
|
||||
yOffset: 1
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: pointyBit
|
||||
x: (parent.width - width)/2
|
||||
y: -height/4 + 0.2 // magic number to get it to line up with the edge of the circle
|
||||
implicitHeight: parent.implicitHeight/2
|
||||
implicitWidth: implicitHeight
|
||||
antialiasing: true
|
||||
rotation: 45
|
||||
color: parent.color
|
||||
}
|
||||
|
||||
Kirigami.ShadowedRectangle {
|
||||
id: inner
|
||||
visible: target.selectionStart !== target.selectionEnd && (handle.y < selectionStartY || handle.y < selectionEndY)
|
||||
anchors.fill: parent
|
||||
anchors.margins: Kirigami.Units.smallBorder
|
||||
color: target.selectedTextColor
|
||||
radius: height/2
|
||||
Rectangle {
|
||||
id: innerPointyBit
|
||||
x: (parent.width - width)/2
|
||||
y: -height/4 + 0.8 // magic number to get it to line up with the edge of the circle
|
||||
implicitHeight: pointyBit.implicitHeight
|
||||
implicitWidth: implicitHeight
|
||||
antialiasing: true
|
||||
rotation: 45
|
||||
color: parent.color
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
enabled: handle.visible
|
||||
anchors.fill: parent
|
||||
// preventStealing: true
|
||||
onPositionChanged: {
|
||||
let pos = mapToItem(root.target, mouse.x, mouse.y);
|
||||
pos = root.target.positionAt(pos.x, pos.y - handle.height - handle.pointyBitVerticalOffset);
|
||||
|
||||
if (target.selectionStart !== target.selectionEnd) {
|
||||
if (!isSelectionEnd) {
|
||||
root.target.select(Math.min(pos, root.target.selectionEnd - 1), root.target.selectionEnd);
|
||||
} else {
|
||||
root.target.select(root.target.selectionStart, Math.max(pos, root.target.selectionStart + 1));
|
||||
}
|
||||
} else {
|
||||
root.target.cursorPosition = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick 2.1
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 2.15
|
||||
import org.kde.kirigami 2.5 as Kirigami
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property Item controlRoot
|
||||
parent: controlRoot ? controlRoot.Window.contentItem : undefined
|
||||
modal: false
|
||||
focus: false
|
||||
closePolicy: Popup.NoAutoClose
|
||||
property bool shouldBeVisible: false
|
||||
|
||||
x: {
|
||||
if (!controlRoot || !controlRoot.Window.contentItem) {
|
||||
return 0;
|
||||
}
|
||||
return Math.min(Math.max(0, controlRoot.mapToItem(root.parent, controlRoot.positionToRectangle(controlRoot.selectionStart).x, 0).x - root.width/2), controlRoot.Window.contentItem.width - root.width);
|
||||
}
|
||||
|
||||
y: {
|
||||
if (!controlRoot || !controlRoot.Window.contentItem) {
|
||||
return 0;
|
||||
}
|
||||
var desiredY = controlRoot.mapToItem(root.parent, 0, controlRoot.positionToRectangle(controlRoot.selectionStart).y).y - root.height;
|
||||
|
||||
if (desiredY >= 0) {
|
||||
return Math.min(desiredY, controlRoot.Window.contentItem.height - root.height);
|
||||
} else {
|
||||
return Math.min(Math.max(0, controlRoot.mapToItem(root.parent, 0, controlRoot.positionToRectangle(controlRoot.selectionEnd).y + Math.round(Kirigami.Units.gridUnit*1.5)).y), controlRoot.Window.contentItem.height - root.height);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
visible: controlRoot ? shouldBeVisible && Qt.platform.os !== "android" && Kirigami.Settings.tabletMode && (controlRoot.selectedText.length > 0 || controlRoot.canPaste) : false
|
||||
|
||||
width: contentItem.implicitWidth + leftPadding + rightPadding
|
||||
|
||||
contentItem: RowLayout {
|
||||
ToolButton {
|
||||
focusPolicy: Qt.NoFocus
|
||||
icon.name: "edit-cut"
|
||||
visible: controlRoot && controlRoot.selectedText.length > 0 && (!controlRoot.hasOwnProperty("echoMode") || controlRoot.echoMode === TextInput.Normal)
|
||||
onClicked: {
|
||||
controlRoot.cut();
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
focusPolicy: Qt.NoFocus
|
||||
icon.name: "edit-copy"
|
||||
visible: controlRoot && controlRoot.selectedText.length > 0 && (!controlRoot.hasOwnProperty("echoMode") || controlRoot.echoMode === TextInput.Normal)
|
||||
onClicked: {
|
||||
controlRoot.copy();
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
focusPolicy: Qt.NoFocus
|
||||
icon.name: "edit-paste"
|
||||
visible: controlRoot && controlRoot.canPaste
|
||||
onClicked: {
|
||||
controlRoot.paste();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,259 +0,0 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2020 Devin Lin <espidev@gmail.com>
|
||||
SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
pragma Singleton
|
||||
|
||||
import QtQuick 2.6
|
||||
import QtQml 2.2
|
||||
import QtQuick.Controls 2.15
|
||||
import org.kde.kirigami 2.5 as Kirigami
|
||||
|
||||
Menu {
|
||||
id: contextMenu
|
||||
|
||||
property Item target
|
||||
property bool deselectWhenMenuClosed: true
|
||||
property int restoredCursorPosition: 0
|
||||
property int restoredSelectionStart
|
||||
property int restoredSelectionEnd
|
||||
property bool persistentSelectionSetting
|
||||
property var spellcheckhighlighter: null
|
||||
property var suggestions: ([])
|
||||
Component.onCompleted: persistentSelectionSetting = persistentSelectionSetting // break binding
|
||||
|
||||
property var runOnMenuClose
|
||||
|
||||
parent: Overlay.overlay
|
||||
|
||||
function storeCursorAndSelection() {
|
||||
contextMenu.restoredCursorPosition = target.cursorPosition;
|
||||
contextMenu.restoredSelectionStart = target.selectionStart;
|
||||
contextMenu.restoredSelectionEnd = target.selectionEnd;
|
||||
}
|
||||
|
||||
// target is pressed with mouse
|
||||
function targetClick(handlerPoint, newTarget, spellcheckhighlighter, mousePosition) {
|
||||
if (handlerPoint.pressedButtons === Qt.RightButton) { // only accept just right click
|
||||
if (contextMenu.visible) {
|
||||
deselectWhenMenuClosed = false; // don't deselect text if menu closed by right click on textfield
|
||||
dismiss();
|
||||
} else {
|
||||
contextMenu.target = newTarget;
|
||||
contextMenu.target.persistentSelection = true; // persist selection when menu is opened
|
||||
contextMenu.spellcheckhighlighter = spellcheckhighlighter
|
||||
contextMenu.suggestions = spellcheckhighlighter.suggestions(mousePosition);
|
||||
storeCursorAndSelection();
|
||||
popup(contextMenu.target);
|
||||
// slightly locate context menu away from mouse so no item is selected when menu is opened
|
||||
x += 1
|
||||
y += 1
|
||||
}
|
||||
} else {
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
// context menu keyboard key
|
||||
function targetKeyPressed(event, newTarget) {
|
||||
if (event.modifiers === Qt.NoModifier && event.key === Qt.Key_Menu) {
|
||||
contextMenu.target = newTarget;
|
||||
target.persistentSelection = true; // persist selection when menu is opened
|
||||
storeCursorAndSelection();
|
||||
popup(contextMenu.target);
|
||||
}
|
||||
}
|
||||
|
||||
readonly property bool targetIsPassword: target !== null && (target.echoMode === TextInput.PasswordEchoOnEdit || target.echoMode === TextInput.Password)
|
||||
|
||||
onAboutToShow: {
|
||||
if (Overlay.overlay) {
|
||||
let tempZ = 0
|
||||
for (let i in Overlay.overlay.visibleChildren) {
|
||||
tempZ = Math.max(tempZ, Overlay.overlay.visibleChildren[i].z)
|
||||
}
|
||||
z = tempZ + 1
|
||||
}
|
||||
}
|
||||
|
||||
// deal with whether or not text should be deselected
|
||||
onClosed: {
|
||||
// restore text field's original persistent selection setting
|
||||
target.persistentSelection = persistentSelectionSetting
|
||||
// deselect text field text if menu is closed not because of a right click on the text field
|
||||
if (deselectWhenMenuClosed) {
|
||||
target.deselect();
|
||||
}
|
||||
deselectWhenMenuClosed = true;
|
||||
|
||||
// restore cursor position
|
||||
target.forceActiveFocus();
|
||||
target.cursorPosition = restoredCursorPosition;
|
||||
target.select(restoredSelectionStart, restoredSelectionEnd);
|
||||
|
||||
// run action
|
||||
runOnMenuClose();
|
||||
}
|
||||
|
||||
onOpened: {
|
||||
runOnMenuClose = function() {};
|
||||
}
|
||||
|
||||
Instantiator {
|
||||
active: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
||||
model: suggestions
|
||||
delegate: MenuItem {
|
||||
text: modelData
|
||||
onClicked: {
|
||||
deselectWhenMenuClosed = false;
|
||||
runOnMenuClose = function() {
|
||||
spellcheckhighlighter.replaceWord(modelData);
|
||||
};
|
||||
}
|
||||
}
|
||||
onObjectAdded: contextMenu.insertItem(0, object)
|
||||
onObjectRemoved: contextMenu.removeItem(0)
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled && suggestions.length === 0
|
||||
action: Action {
|
||||
text: spellcheckhighlighter ? i18nc("@action:inmenu", "No suggestions for %1", spellcheckhighlighter.wordUnderMouse) : ""
|
||||
enabled: false
|
||||
}
|
||||
}
|
||||
|
||||
MenuSeparator {
|
||||
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
||||
action: Action {
|
||||
text: i18n("Add to dictionary")
|
||||
onTriggered: {
|
||||
deselectWhenMenuClosed = false;
|
||||
runOnMenuClose = function() {
|
||||
spellcheckhighlighter.addWordToDictionary(spellcheckhighlighter.wordUnderMouse)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
||||
action: Action {
|
||||
text: i18n("Ignore")
|
||||
onTriggered: {
|
||||
deselectWhenMenuClosed = false;
|
||||
runOnMenuClose = function() {
|
||||
spellcheckhighlighter.ignoreWord(spellcheckhighlighter.wordUnderMouse)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MenuSeparator {
|
||||
visible: target !== null && !target.readOnly && spellcheckhighlighter !== null && spellcheckhighlighter.wordIsMisspelled
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
visible: target !== null && !target.readOnly
|
||||
action: Action {
|
||||
icon.name: "edit-undo-symbolic"
|
||||
text: i18nc("@action:inmenu", "Undo")
|
||||
shortcut: StandardKey.Undo
|
||||
}
|
||||
enabled: target !== null && target.canUndo
|
||||
onTriggered: {
|
||||
deselectWhenMenuClosed = false;
|
||||
runOnMenuClose = function() {target.undo()};
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
visible: target !== null && !target.readOnly
|
||||
action: Action {
|
||||
icon.name: "edit-redo-symbolic"
|
||||
text: i18nc("@action:inmenu", "Redo")
|
||||
shortcut: StandardKey.Redo
|
||||
}
|
||||
enabled: target !== null && target.canRedo
|
||||
onTriggered: {
|
||||
deselectWhenMenuClosed = false;
|
||||
runOnMenuClose = function() {target.redo()};
|
||||
}
|
||||
}
|
||||
MenuSeparator {
|
||||
visible: target !== null && !target.readOnly
|
||||
}
|
||||
MenuItem {
|
||||
visible: target !== null && !target.readOnly && !targetIsPassword
|
||||
action: Action {
|
||||
icon.name: "edit-cut-symbolic"
|
||||
text: i18nc("@action:inmenu", "Cut")
|
||||
shortcut: StandardKey.Cut
|
||||
}
|
||||
enabled: target !== null && target.selectedText
|
||||
onTriggered: {
|
||||
deselectWhenMenuClosed = false;
|
||||
runOnMenuClose = function() {target.cut()}
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
action: Action {
|
||||
icon.name: "edit-copy-symbolic"
|
||||
text: i18nc("@action:inmenu", "Copy")
|
||||
shortcut: StandardKey.Copy
|
||||
}
|
||||
enabled: target !== null && target.selectedText
|
||||
visible: !targetIsPassword
|
||||
onTriggered: {
|
||||
deselectWhenMenuClosed = false;
|
||||
runOnMenuClose = function() {target.copy()}
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
visible: target !== null && !target.readOnly
|
||||
action: Action {
|
||||
icon.name: "edit-paste-symbolic"
|
||||
text: i18nc("@action:inmenu", "Paste")
|
||||
shortcut: StandardKey.Paste
|
||||
}
|
||||
enabled: target !== null && target.canPaste
|
||||
onTriggered: {
|
||||
deselectWhenMenuClosed = false;
|
||||
runOnMenuClose = function() {target.paste()};
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
visible: target !== null && !target.readOnly
|
||||
action: Action {
|
||||
icon.name: "edit-delete-symbolic"
|
||||
text: i18nc("@action:inmenu", "Delete")
|
||||
shortcut: StandardKey.Delete
|
||||
}
|
||||
enabled: target !== null && target.selectedText
|
||||
onTriggered: {
|
||||
deselectWhenMenuClosed = false;
|
||||
runOnMenuClose = function() {target.remove(target.selectionStart, target.selectionEnd)};
|
||||
}
|
||||
}
|
||||
MenuSeparator {
|
||||
visible: !targetIsPassword
|
||||
}
|
||||
MenuItem {
|
||||
action: Action {
|
||||
icon.name: "edit-select-all-symbolic"
|
||||
text: i18nc("@action:inmenu", "Select All")
|
||||
shortcut: StandardKey.SelectAll
|
||||
}
|
||||
visible: !targetIsPassword
|
||||
onTriggered: {
|
||||
deselectWhenMenuClosed = false;
|
||||
runOnMenuClose = function() {target.selectAll()};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,3 @@ ReplyPane 1.0 ReplyPane.qml
|
||||
AttachmentPane 1.0 AttachmentPane.qml
|
||||
CompletionMenu 1.0 CompletionMenu.qml
|
||||
EmojiPickerPane 1.0 EmojiPickerPane.qml
|
||||
singleton TextFieldContextMenu 1.0 TextFieldContextMenu.qml
|
||||
CursorDelegate 1.0 CursorDelegate.qml
|
||||
CursorHandle 1.0 CursorHandle.qml
|
||||
singleton MobileTextActionsToolBar 1.0 MobileTextActionsToolBar.qml
|
||||
|
||||
@@ -7,6 +7,7 @@ import QtQuick.Controls 2.15 as QQC2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import org.kde.kirigami 2.15 as Kirigami
|
||||
import org.kde.sonnet 1.0 as Sonnet
|
||||
|
||||
import org.kde.neochat 1.0
|
||||
import NeoChat.Settings 1.0
|
||||
@@ -65,6 +66,11 @@ Kirigami.ScrollablePage {
|
||||
iconName: "network-connect"
|
||||
onTriggered: pageSettingStack.push("qrc:/imports/NeoChat/Page/DevicesPage.qml")
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Spell Checking")
|
||||
iconName: "tools-check-spelling"
|
||||
onTriggered: pageSettingStack.push(spellCheckingPage)
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("About NeoChat")
|
||||
icon.name: "help-about"
|
||||
@@ -85,4 +91,11 @@ Kirigami.ScrollablePage {
|
||||
aboutData: Controller.aboutData
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: spellCheckingPage
|
||||
Sonnet.ConfigPage {
|
||||
wideMode: pageSettingStack.wideMode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
res.qrc
4
res.qrc
@@ -26,10 +26,6 @@
|
||||
<file>imports/NeoChat/Component/ChatBox/AttachmentPane.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/ReplyPane.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/CompletionMenu.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/CursorHandle.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/CursorDelegate.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/MobileTextActionsToolBar.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/TextFieldContextMenu.qml</file>
|
||||
<file>imports/NeoChat/Component/ChatBox/qmldir</file>
|
||||
<file>imports/NeoChat/Component/Emoji/EmojiPicker.qml</file>
|
||||
<file>imports/NeoChat/Component/Emoji/qmldir</file>
|
||||
|
||||
Reference in New Issue
Block a user