diff options
Diffstat (limited to 'src/components/Keyboard.qml')
-rw-r--r-- | src/components/Keyboard.qml | 1828 |
1 files changed, 1828 insertions, 0 deletions
diff --git a/src/components/Keyboard.qml b/src/components/Keyboard.qml new file mode 100644 index 00000000..45ca8dca --- /dev/null +++ b/src/components/Keyboard.qml @@ -0,0 +1,1828 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +import QtQuick +// Deliberately imported after QtQuick to avoid missing restoreMode property in Binding. Fix in Qt 6. +import QtQml +import QtQuick.Layouts +import QtQuick.Window +import QtQuick.VirtualKeyboard +import QtQuick.VirtualKeyboard.Styles +import QtQuick.VirtualKeyboard.Settings +import QtQuick.VirtualKeyboard.Plugins +import Qt.labs.folderlistmodel + +Item { + id: keyboard + objectName: "keyboard" + + property alias style: styleLoader.item + property alias wordCandidateView: wordCandidateView + property alias shadowInputControl: shadowInputControl + property alias alternativeKeys: alternativeKeys + property alias characterPreview: characterPreview + property alias wordCandidateContextMenu: wordCandidateContextMenu + property alias fullScreenModeSelectionControl: fullScreenModeSelectionControl + property alias naviationHighlight: naviationHighlight + property alias keyboardInputArea: keyboardInputArea + property Item activeKey: null + property TouchPoint activeTouchPoint + property int localeIndex: -1 + property var availableLocaleIndices: [] + property var availableCustomLocaleIndices: [] + property string locale: localeIndex >= 0 && localeIndex < layoutsModel.count ? layoutsModel.get(localeIndex, "fileName") : "" + property string inputLocale + property int defaultLocaleIndex: -1 + readonly property bool latinOnly: InputContext.inputMethodHints & (Qt.ImhLatinOnly | Qt.ImhEmailCharactersOnly | Qt.ImhUrlCharactersOnly) + readonly property bool preferNumbers: InputContext.inputMethodHints & Qt.ImhPreferNumbers + readonly property bool dialableCharactersOnly: InputContext.inputMethodHints & Qt.ImhDialableCharactersOnly + readonly property bool formattedNumbersOnly: InputContext.inputMethodHints & Qt.ImhFormattedNumbersOnly + readonly property bool digitsOnly: InputContext.inputMethodHints & Qt.ImhDigitsOnly + property string layout + property string layoutType: { + if (keyboard.handwritingMode) return "handwriting" + if (keyboard.dialableCharactersOnly) return "dialpad" + if (keyboard.formattedNumbersOnly) return "numbers" + if (keyboard.digitsOnly) return "digits" + if (keyboard.symbolMode) return "symbols" + return "main" + } + property bool active: Qt.inputMethod.visible + property bool handwritingMode + property bool fullScreenHandwritingMode + property bool symbolMode + property bool fullScreenMode: VirtualKeyboardSettings.fullScreenMode + property var defaultInputMethod: initDefaultInputMethod() + property var plainInputMethod: PlainInputMethod {} + property var customInputMethod: null + property var customInputMethodSharedLayouts: [] + property int defaultInputMode: InputEngine.InputMode.Latin + property bool inputMethodNeedsReset: true + property bool inputModeNeedsReset: true + property bool navigationModeActive: false + readonly property bool languagePopupListActive: languagePopupList.enabled + property alias soundEffect: soundEffect + property alias keyboardLayoutLoader: keyboardLayoutLoader + property real screenHeight: parent.parent ? parent.parent.height : Screen.height + property bool noAnimations + property int pressAndHoldDelay: 500 + + function initDefaultInputMethod() { + try { + return Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard.Plugins; DefaultInputMethod {}', keyboard, "defaultInputMethod") + } catch (e) { } + return plainInputMethod + } + + Component.onCompleted: InputContext.priv.registerInputPanel(parent) + + width: keyboardBackground.width + height: keyboardBackground.height + onActiveChanged: { + hideLanguagePopup() + if (active && symbolMode && !preferNumbers) + symbolMode = false + keyboardInputArea.reset() + wordCandidateViewAutoHideTimer.stop() + } + onActiveKeyChanged: { + if (InputContext.inputEngine.activeKey !== Qt.Key_unknown) + InputContext.inputEngine.virtualKeyCancel() + } + Connections { + target: VirtualKeyboardSettings + function onLocaleChanged() { + updateDefaultLocale() + localeIndex = defaultLocaleIndex + } + function onActiveLocalesChanged() { + updateDefaultLocale() + if (!isValidLocale(localeIndex) || VirtualKeyboardSettings.locale) + localeIndex = defaultLocaleIndex + } + function onDefaultInputMethodDisabledChanged() { + updateInputMethod() + } + } + onAvailableLocaleIndicesChanged: hideLanguagePopup() + onAvailableCustomLocaleIndicesChanged: hideLanguagePopup() + onLocaleChanged: { + hideLanguagePopup() + inputMethodNeedsReset = true + inputModeNeedsReset = true + updateLayout() + } + onInputLocaleChanged: { + if (Qt.locale(inputLocale).name !== "C") + InputContext.priv.locale = inputLocale + } + onLayoutChanged: hideLanguagePopup() + onLayoutTypeChanged: { + updateAvailableLocaleIndices() + updateLayout() + } + onLatinOnlyChanged: inputModeNeedsReset = true + onPreferNumbersChanged: { + keyboard.symbolMode = !keyboard.handwritingMode && preferNumbers + inputModeNeedsReset = true + } + onDialableCharactersOnlyChanged: inputModeNeedsReset = true + onFormattedNumbersOnlyChanged: inputModeNeedsReset = true + onDigitsOnlyChanged: inputModeNeedsReset = true + onHandwritingModeChanged: if (!keyboard.handwritingMode) keyboard.fullScreenHandwritingMode = false + onFullScreenHandwritingModeChanged: if (keyboard.fullScreenHandwritingMode) keyboard.handwritingMode = true + onLanguagePopupListActiveChanged: { + if (languagePopupListActive && navigationModeActive) + keyboardInputArea.initialKey = null + } + + Connections { + target: InputContext + function onInputMethodHintsChanged() { + if (InputContext.priv.focus) + updateInputMethod() + } + } + Connections { + target: InputContext.priv + function onInputItemChanged() { + keyboard.hideLanguagePopup() + if (active && symbolMode && !preferNumbers) + symbolMode = false + } + function onFocusChanged() { + if (InputContext.priv.focus) + updateInputMethod() + } + function onNavigationKeyPressed(key, isAutoRepeat) { + var initialKey + var direction = wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight ? 1 : -1 + switch (key) { + case Qt.Key_Left: + if (keyboard.navigationModeActive && !keyboardInputArea.initialKey) { + if (languagePopupListActive) { + hideLanguagePopup() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + break + } + if (alternativeKeys.active) { + if (alternativeKeys.listView.currentIndex > 0) { + alternativeKeys.listView.decrementCurrentIndex() + } else { + alternativeKeys.close() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + } + break + } + if (functionPopupList.active) { + if (functionPopupList.listView.currentIndex > 0) { + functionPopupList.listView.decrementCurrentIndex() + } else { + functionPopupList.close() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + } + break + } + if (wordCandidateContextMenu.active) { + hideWordCandidateContextMenu() + break + } + if (wordCandidateView.count) { + if (wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight && + wordCandidateView.currentIndex > 0) { + wordCandidateView.decrementCurrentIndex() + } else if (wordCandidateView.effectiveLayoutDirection == Qt.RightToLeft && + wordCandidateView.currentIndex + 1 < wordCandidateView.count) { + wordCandidateView.incrementCurrentIndex() + } else { + keyboardInputArea.navigateToNextKey(0, 0, false) + initialKey = keyboardInputArea.initialKey + while (keyboardInputArea.navigateToNextKey(0, 1 * direction, false)) + initialKey = keyboardInputArea.initialKey + while (keyboardInputArea.navigateToNextKey(1, 0, false)) + initialKey = keyboardInputArea.initialKey + keyboardInputArea.initialKey = initialKey + keyboardInputArea.navigateToNextKey(0, 0, false) + } + break + } + } + initialKey = keyboardInputArea.initialKey + if (!keyboardInputArea.navigateToNextKey(-1 * direction, 0, false)) { + keyboardInputArea.initialKey = initialKey + if (!keyboardInputArea.navigateToNextKey(0, -1 * direction, false)) { + if (wordCandidateView.count) { + wordCandidateView.currentIndex = + wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight ? + (wordCandidateView.count - 1) : 0 + break + } + keyboardInputArea.initialKey = initialKey + keyboardInputArea.navigateToNextKey(0, -1 * direction, true) + } + keyboardInputArea.navigateToNextKey(-1 * direction, 0, true) + } + break + case Qt.Key_Up: + if (languagePopupListActive) { + if (languagePopupList.currentIndex > 0) { + languagePopupList.decrementCurrentIndex() + } else if (languagePopupList.keyNavigationWraps) { + languagePopupList.currentIndex = languagePopupList.count - 1 + } else { + hideLanguagePopup() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + } + } else if (alternativeKeys.active) { + alternativeKeys.close() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + } else if (functionPopupList.active) { + functionPopupList.close() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + } else if (wordCandidateContextMenu.active) { + if (wordCandidateContextMenuList.currentIndex > 0) { + wordCandidateContextMenuList.decrementCurrentIndex() + } else if (wordCandidateContextMenuList.keyNavigationWraps && wordCandidateContextMenuList.count > 1) { + wordCandidateContextMenuList.currentIndex = wordCandidateContextMenuList.count - 1 + } else { + hideWordCandidateContextMenu() + } + } else if (keyboard.navigationModeActive && !keyboardInputArea.initialKey && wordCandidateView.count) { + keyboardInputArea.navigateToNextKey(0, 0, false) + initialKey = keyboardInputArea.initialKey + if (!keyboardInputArea.navigateToNextKey(0, -1, false)) { + keyboardInputArea.initialKey = initialKey + keyboardInputArea.navigateToNextKey(0, -1, true) + } else { + keyboardInputArea.navigateToNextKey(0, 1, false) + } + } else if (!keyboardInputArea.navigateToNextKey(0, -1, !keyboard.navigationModeActive || !keyboardInputArea.initialKey || wordCandidateView.count == 0)) { + if (wordCandidateView.currentIndex === -1) + wordCandidateView.incrementCurrentIndex() + } + break + case Qt.Key_Right: + if (keyboard.navigationModeActive && !keyboardInputArea.initialKey) { + if (languagePopupListActive) { + hideLanguagePopup() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + break + } + if (alternativeKeys.active) { + if (alternativeKeys.listView.currentIndex + 1 < alternativeKeys.listView.count) { + alternativeKeys.listView.incrementCurrentIndex() + } else { + alternativeKeys.close() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + } + break + } + if (functionPopupList.active) { + if (functionPopupList.listView.currentIndex + 1 < functionPopupList.listView.count) { + functionPopupList.listView.incrementCurrentIndex() + } else { + functionPopupList.close() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + } + break + } + if (wordCandidateContextMenu.active) { + hideWordCandidateContextMenu() + break + } + if (wordCandidateView.count) { + if (wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight && + wordCandidateView.currentIndex + 1 < wordCandidateView.count) { + wordCandidateView.incrementCurrentIndex() + } else if (wordCandidateView.effectiveLayoutDirection == Qt.RightToLeft && + wordCandidateView.currentIndex > 0) { + wordCandidateView.decrementCurrentIndex() + } else { + keyboardInputArea.navigateToNextKey(0, 0, false) + initialKey = keyboardInputArea.initialKey + while (keyboardInputArea.navigateToNextKey(0, -1 * direction, false)) + initialKey = keyboardInputArea.initialKey; + while (keyboardInputArea.navigateToNextKey(-1, 0, false)) + initialKey = keyboardInputArea.initialKey; + keyboardInputArea.initialKey = initialKey + keyboardInputArea.navigateToNextKey(0, 0, false) + } + break + } + } + initialKey = keyboardInputArea.initialKey + if (!keyboardInputArea.navigateToNextKey(1 * direction, 0, false)) { + keyboardInputArea.initialKey = initialKey + if (!keyboardInputArea.navigateToNextKey(0, 1 * direction, false)) { + if (wordCandidateView.count) { + wordCandidateView.currentIndex = + wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight ? + 0 : (wordCandidateView.count - 1) + break + } + keyboardInputArea.initialKey = initialKey + keyboardInputArea.navigateToNextKey(0, 1 * direction, true) + } + keyboardInputArea.navigateToNextKey(1 * direction, 0, true) + } + break + case Qt.Key_Down: + if (languagePopupListActive) { + if (languagePopupList.currentIndex + 1 < languagePopupList.count) { + languagePopupList.incrementCurrentIndex() + } else if (languagePopupList.keyNavigationWraps) { + languagePopupList.currentIndex = 0 + } else { + hideLanguagePopup() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + } + } else if (alternativeKeys.active) { + alternativeKeys.close() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + } else if (functionPopupList.active) { + functionPopupList.close() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + } else if (wordCandidateContextMenu.active) { + if (wordCandidateContextMenuList.currentIndex + 1 < wordCandidateContextMenuList.count) { + wordCandidateContextMenuList.incrementCurrentIndex() + } else if (wordCandidateContextMenuList.keyNavigationWraps && wordCandidateContextMenuList.count > 1) { + wordCandidateContextMenuList.currentIndex = 0 + } else { + hideWordCandidateContextMenu() + keyboardInputArea.setActiveKey(null) + keyboardInputArea.navigateToNextKey(0, 0, false) + } + } else if (keyboard.navigationModeActive && !keyboardInputArea.initialKey && wordCandidateView.count) { + keyboardInputArea.navigateToNextKey(0, 0, false) + initialKey = keyboardInputArea.initialKey + if (!keyboardInputArea.navigateToNextKey(0, 1, false)) { + keyboardInputArea.initialKey = initialKey + keyboardInputArea.navigateToNextKey(0, 1, true) + } else { + keyboardInputArea.navigateToNextKey(0, -1, false) + } + } else if (!keyboardInputArea.navigateToNextKey(0, 1, !keyboard.navigationModeActive || !keyboardInputArea.initialKey || wordCandidateView.count == 0)) { + if (wordCandidateView.currentIndex === -1) + wordCandidateView.incrementCurrentIndex() + } + break + case Qt.Key_Return: + if (!keyboard.navigationModeActive) + break + if (languagePopupListActive) { + if (!isAutoRepeat) { + languagePopupList.model.selectItem(languagePopupList.currentIndex) + keyboardInputArea.reset() + keyboardInputArea.navigateToNextKey(0, 0, false) + } + } else if (keyboardInputArea.initialKey) { + if (!isAutoRepeat) { + pressAndHoldTimer.restart() + keyboardInputArea.setActiveKey(keyboardInputArea.initialKey) + keyboardInputArea.press(keyboardInputArea.initialKey, true) + } + } else if (!wordCandidateContextMenu.active && wordCandidateView.count > 0) { + if (!isAutoRepeat) { + pressAndHoldTimer.restart() + } + } + break + default: + break + } + } + function onNavigationKeyReleased(key, isAutoRepeat) { + switch (key) { + case Qt.Key_Return: + if (!keyboard.navigationModeActive) { + if (languagePopupListActive) + languagePopupList.model.selectItem(languagePopupList.currentIndex) + break + } + if (isAutoRepeat) + break + if (!languagePopupListActive && !alternativeKeys.active && !functionPopupList.active && !wordCandidateContextMenu.active && keyboard.activeKey) { + keyboardInputArea.release(keyboard.activeKey) + pressAndHoldTimer.stop() + alternativeKeys.close() + functionPopupList.close() + keyboardInputArea.setActiveKey(null) + if (!languagePopupListActive && keyboardInputArea.navigationCursor !== Qt.point(-1, -1)) + keyboardInputArea.navigateToNextKey(0, 0, false) + } else if (wordCandidateContextMenu.active) { + if (!wordCandidateContextMenu.openedByNavigationKeyLongPress) { + wordCandidateContextMenu.selectCurrentItem() + keyboardInputArea.navigateToNextKey(0, 0, false) + } else { + wordCandidateContextMenu.openedByNavigationKeyLongPress = false + } + } else if (alternativeKeys.active) { + if (!alternativeKeys.openedByNavigationKeyLongPress) { + alternativeKeys.clicked() + alternativeKeys.close() + keyboardInputArea.navigateToNextKey(0, 0, false) + keyboardInputArea.reset() + } else { + alternativeKeys.openedByNavigationKeyLongPress = false + } + } else if (functionPopupList.active) { + if (!functionPopupList.openedByNavigationKeyLongPress) { + functionPopupList.clicked() + functionPopupList.close() + keyboardInputArea.navigateToNextKey(0, 0, false) + keyboardInputArea.reset() + } else { + functionPopupList.openedByNavigationKeyLongPress = false + } + } else if (!wordCandidateContextMenu.active && wordCandidateView.count > 0) { + wordCandidateView.model.selectItem(wordCandidateView.currentIndex) + if (!InputContext.preeditText.length) + keyboardInputArea.navigateToNextKey(0, 1, true) + } + break + default: + break + } + } + } + Connections { + target: InputContext.inputEngine + function onVirtualKeyClicked(key, text, modifiers, isAutoRepeat) { + if (isAutoRepeat && keyboard.activeKey) + soundEffect.play(keyboard.activeKey.soundEffect) + if (key !== Qt.Key_unknown && keyboardInputArea.dragSymbolMode) { + keyboardInputArea.dragSymbolMode = false + keyboard.symbolMode = false + } else if (key === Qt.Key_Space) { + var surroundingText = InputContext.surroundingText.trim() + if (InputContext.priv.shiftHandler.sentenceEndingCharacters.indexOf(surroundingText.charAt(surroundingText.length-1)) >= 0) + keyboard.symbolMode = false + } + } + } + FolderListModel { + id: layoutsModel + nameFilters: ["$"] + folder: VirtualKeyboardSettings.layoutPath + } + Connections { + target: layoutsModel + function onCountChanged() { + updateDefaultLocale() + localeIndex = defaultLocaleIndex + } + } + AlternativeKeys { + id: alternativeKeys + objectName: "alternativeKeys" + // Add some extra margin for decoration + property real horizontalMargin: style.alternateKeysListItemWidth + property real verticalMargin: style.alternateKeysListItemHeight + property rect previewRect: Qt.rect(keyboard.x + alternativeKeys.listView.x - horizontalMargin, + keyboard.y + alternativeKeys.listView.y - verticalMargin, + alternativeKeys.listView.width + horizontalMargin * 2, + alternativeKeys.listView.height + verticalMargin * 2) + property bool openedByNavigationKeyLongPress + onVisibleChanged: { + if (visible) + InputContext.priv.previewRectangle = Qt.binding(function() {return previewRect}) + else + openedByNavigationKeyLongPress = false + InputContext.priv.previewVisible = visible + } + } + FunctionPopupList { + id: functionPopupList + property bool openedByNavigationKeyLongPress + } + Timer { + id: pressAndHoldTimer + interval: keyboard.pressAndHoldDelay + onTriggered: { + if (keyboard.activeKey && keyboard.activeKey === keyboardInputArea.initialKey) { + var origin = keyboard.mapFromItem(activeKey, activeKey.width / 2, 0) + if (keyboard.activeKey.smallText === "\u2699" && + functionPopupList.open(keyboard.activeKey, origin.x, origin.y)) { + InputContext.inputEngine.virtualKeyCancel() + keyboardInputArea.initialKey = null + functionPopupList.openedByNavigationKeyLongPress = keyboard.navigationModeActive + } else if (alternativeKeys.open(keyboard.activeKey, origin.x, origin.y)) { + InputContext.inputEngine.virtualKeyCancel() + keyboardInputArea.initialKey = null + alternativeKeys.openedByNavigationKeyLongPress = keyboard.navigationModeActive + } else if (keyboard.activeKey.key === Qt.Key_Context1 && !keyboard.symbolMode) { + InputContext.inputEngine.virtualKeyCancel() + keyboardInputArea.dragSymbolMode = true + keyboard.symbolMode = true + keyboardInputArea.initialKey = null + if (keyboardInputArea.navigationCursor !== Qt.point(-1, -1)) + keyboardInputArea.navigateToNextKey(0, 0, false) + } + } else if (keyboardInputArea.dragSymbolMode && + keyboard.activeKey && + keyboard.activeKey.functionKey && + !keyboard.activeKey.repeat) { + InputContext.inputEngine.virtualKeyCancel() + keyboardInputArea.click(keyboard.activeKey) + keyboardInputArea.initialKey = null + if (keyboardInputArea.navigationCursor !== Qt.point(-1, -1)) + keyboardInputArea.navigateToNextKey(0, 0, false) + } else if (!wordCandidateContextMenu.active && keyboard.navigationModeActive) { + wordCandidateContextMenu.show(wordCandidateView.currentIndex) + wordCandidateContextMenu.openedByNavigationKeyLongPress = keyboard.navigationModeActive + } + } + } + Timer { + id: releaseInaccuracyTimer + interval: 500 + onTriggered: { + if (keyboardInputArea.pressed && activeTouchPoint && !alternativeKeys.active && !keyboardInputArea.dragSymbolMode && !functionPopupList.active) { + var key = keyboardInputArea.keyOnPoint(activeTouchPoint.x, activeTouchPoint.y) + if (key !== keyboard.activeKey) { + InputContext.inputEngine.virtualKeyCancel() + keyboardInputArea.setActiveKey(key) + keyboardInputArea.press(key, false) + } + } + } + } + CharacterPreviewBubble { + id: characterPreview + objectName: "characterPreviewBubble" + active: keyboardInputArea.pressed && !alternativeKeys.active && !functionPopupList.active + property rect previewRect: Qt.rect(keyboard.x + characterPreview.x, + keyboard.y + characterPreview.y, + characterPreview.width, + characterPreview.height) + } + Binding { + target: InputContext.priv + property: "previewRectangle" + value: characterPreview.previewRect + when: characterPreview.visible + restoreMode: Binding.RestoreBinding + } + Binding { + target: InputContext.priv + property: "previewRectangle" + value: languagePopupList.previewRect + when: languagePopupListActive + restoreMode: Binding.RestoreBinding + } + Binding { + target: InputContext.priv + property: "previewVisible" + value: characterPreview.visible || languagePopupListActive + restoreMode: Binding.RestoreBinding + } + Loader { + id: styleLoader + source: VirtualKeyboardSettings.style + Binding { + target: styleLoader.item + property: "keyboardHeight" + value: keyboardInnerContainer.height + restoreMode: Binding.RestoreBinding + } + } + Loader { + id: naviationHighlight + objectName: "naviationHighlight" + property var highlightItem: { + if (keyboard.navigationModeActive) { + if (languagePopupListActive) { + return languagePopupList.highlightItem + } else if (keyboardInputArea.initialKey) { + return keyboardInputArea.initialKey + } else if (alternativeKeys.listView.count > 0) { + return alternativeKeys.listView.highlightItem + } else if (functionPopupList.listView.count > 0) { + return functionPopupList.listView.highlightItem + } else if (wordCandidateContextMenu.active) { + return wordCandidateContextMenuList.highlightItem + } else if (wordCandidateView.count > 0) { + return wordCandidateView.highlightItem + } + } + return keyboard + } + // Note: without "highlightItem.x - highlightItem.x" the binding does not work for alternativeKeys + property var highlightItemOffset: highlightItem ? keyboard.mapFromItem(highlightItem, highlightItem.x - highlightItem.x, highlightItem.y - highlightItem.y) : ({x:0, y:0}) + property int moveDuration: !keyboard.noAnimations ? 200 : 0 + property int resizeDuration: !keyboard.noAnimations ? 200 : 0 + z: 2 + x: highlightItemOffset.x + y: highlightItemOffset.y + width: highlightItem ? highlightItem.width : 0 + height: highlightItem ? highlightItem.height : 0 + visible: keyboard.navigationModeActive && highlightItem !== null && highlightItem !== keyboard + sourceComponent: keyboard.style.navigationHighlight + Behavior on x { + NumberAnimation { id: xAnimation; duration: naviationHighlight.moveDuration; easing.type: Easing.OutCubic } + } + Behavior on y { + NumberAnimation { id: yAnimation; duration: naviationHighlight.moveDuration; easing.type: Easing.OutCubic } + } + Behavior on width { + NumberAnimation { id: widthAnimation; duration: naviationHighlight.resizeDuration; easing.type: Easing.OutCubic } + } + Behavior on height { + NumberAnimation { id: heightAnimation; duration: naviationHighlight.resizeDuration; easing.type: Easing.OutCubic } + } + } + + ShadowInputControl { + id: shadowInputControl + objectName: "shadowInputControl" + z: -3 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: wordCandidateView.top + height: keyboard.screenHeight - + keyboard.height - + wordCandidateView.height + visible: fullScreenMode && (shadowInputControlVisibleTimer.running || InputContext.animating) + + Connections { + target: keyboard + function onActiveChanged() { + if (keyboard.active) + shadowInputControlVisibleTimer.start() + else + shadowInputControlVisibleTimer.stop() + } + } + + Timer { + id: shadowInputControlVisibleTimer + interval: 2147483647 + repeat: true + } + + MouseArea { + onPressed: keyboard.hideLanguagePopup() + anchors.fill: parent + enabled: languagePopupList.enabled + } + } + + SelectionControl { + id: fullScreenModeSelectionControl + objectName: "fullScreenModeSelectionControl" + inputContext: InputContext.priv.shadow + anchors.top: shadowInputControl.top + anchors.left: shadowInputControl.left + enabled: keyboard.enabled && fullScreenMode + } + + ListView { + id: wordCandidateView + objectName: "wordCandidateView" + clip: true + z: -2 + property bool empty: true + readonly property bool visibleCondition: (((!wordCandidateView.empty || wordCandidateViewAutoHideTimer.running) && + InputContext.inputEngine.wordCandidateListVisibleHint) || VirtualKeyboardSettings.wordCandidateList.alwaysVisible) && + keyboard.active + readonly property real visibleYOffset: -height + readonly property real currentYOffset: visibleCondition ? visibleYOffset : 0 + height: style ? style.selectionListHeight : 0 + anchors.left: parent.left + anchors.right: parent.right + spacing: 0 + orientation: ListView.Horizontal + snapMode: ListView.SnapToItem + delegate: style.selectionListDelegate + highlight: style.selectionListHighlight ? style.selectionListHighlight : defaultHighlight + highlightMoveDuration: 0 + highlightResizeDuration: 0 + add: !keyboard.noAnimations ? style.selectionListAdd : null + remove: !keyboard.noAnimations ? style.selectionListRemove : null + keyNavigationWraps: true + model: InputContext.inputEngine.wordCandidateListModel + onCurrentItemChanged: if (currentItem) soundEffect.register(currentItem.soundEffect) + Connections { + target: wordCandidateView.model ? wordCandidateView.model : null + function onActiveItemChanged(index) { wordCandidateView.currentIndex = index } + function onItemSelected() { if (wordCandidateView.currentItem) soundEffect.play(wordCandidateView.currentItem.soundEffect) } + function onCountChanged() { + var empty = wordCandidateView.model.count === 0 + if (empty) + wordCandidateViewAutoHideTimer.restart() + else + wordCandidateViewAutoHideTimer.stop() + wordCandidateView.empty = empty + keyboard.hideWordCandidateContextMenu() + } + } + Connections { + target: InputContext.priv + function onInputItemChanged() { wordCandidateViewAutoHideTimer.stop() } + } + Connections { + target: InputContext.inputEngine + function onWordCandidateListVisibleHintChanged() { wordCandidateViewAutoHideTimer.stop() } + } + Timer { + id: wordCandidateViewAutoHideTimer + interval: VirtualKeyboardSettings.wordCandidateList.autoHideDelay + } + Loader { + sourceComponent: style.selectionListBackground + anchors.fill: parent + z: -1 + } + Component { + id: defaultHighlight + Item {} + } + states: [ + State { + name: "visible" + when: wordCandidateView.visibleCondition + PropertyChanges { + target: wordCandidateView + y: wordCandidateView.visibleYOffset + } + }, + State { + name: "alwaysVisible" + when: keyboard.fullScreenMode || VirtualKeyboardSettings.wordCandidateList.alwaysVisible + PropertyChanges { + target: wordCandidateView + y: wordCandidateView.visibleYOffset + } + } + ] + transitions: Transition { + id: wordCandidateViewTransition + from: "" + to: "visible" + enabled: !InputContext.animating && !keyboard.noAnimations + reversible: true + ParallelAnimation { + NumberAnimation { + properties: "y" + duration: 250 + easing.type: Easing.InOutQuad + } + } + } + + function longPressItem(index) { + return keyboard.showWordCandidateContextMenu(index) + } + } + + Item { + id: soundEffect + property var __sounds: ({}) + property bool available: false + + signal playingChanged(url source, bool playing) + + Connections { + target: VirtualKeyboardSettings + function onStyleNameChanged() { + soundEffect.__sounds = {} + soundEffect.available = false + } + } + + function play(sound) { + if (enabled && sound != Qt.resolvedUrl("")) { + var soundId = Qt.md5(sound) + var multiSoundEffect = __sounds[soundId] + if (!multiSoundEffect) + multiSoundEffect = register(sound) + if (multiSoundEffect) + multiSoundEffect.play() + } + } + + function register(sound) { + var multiSoundEffect = null + if (enabled && sound != Qt.resolvedUrl("")) { + var soundId = Qt.md5(sound) + multiSoundEffect = __sounds[soundId] + if (!multiSoundEffect) { + multiSoundEffect = Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard; MultiSoundEffect {}', soundEffect) + if (multiSoundEffect) { + multiSoundEffect.playingChanged.connect(soundEffect.playingChanged) + multiSoundEffect.source = sound + __sounds[soundId] = multiSoundEffect + available = true + } + } + } + return multiSoundEffect + } + } + + Loader { + id: keyboardBackground + z: -1 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: keyboardInnerContainer.height + sourceComponent: style.keyboardBackground + + Item { + id: keyboardInnerContainer + z: 1 + width: Math.round(keyboardBackground.width) + height: style ? Math.round(style.keyboardDesignHeight * width / style.keyboardDesignWidth) : 0 + anchors.horizontalCenter: parent.horizontalCenter + LayoutMirroring.enabled: false + LayoutMirroring.childrenInherit: true + + KeyboardObserver { + id: keyboardObserver + + function scanLayout() { + if (keyboardLayoutLoader.item == null) + return null + + return keyboardLayoutLoader.item.scanLayout() + } + } + + Component.onCompleted: InputContext.priv.setKeyboardObserver(keyboardObserver) + + onWidthChanged: notifyLayoutChanged() + onHeightChanged: notifyLayoutChanged() + + Loader { + id: keyboardLayoutLoader + objectName: "keyboardLayoutLoader" + + anchors.fill: parent + anchors.leftMargin: Math.round(style.keyboardRelativeLeftMargin * parent.width) + anchors.rightMargin: Math.round(style.keyboardRelativeRightMargin * parent.width) + anchors.topMargin: Math.round(style.keyboardRelativeTopMargin * parent.height) + anchors.bottomMargin: Math.round(style.keyboardRelativeBottomMargin * parent.height) + + Binding { + target: keyboardLayoutLoader + property: "source" + value: keyboard.layout + when: keyboard.width > 0 && keyboard.layout.length > 0 + restoreMode: Binding.RestoreNone + } + + onItemChanged: { + if (!item) + return + + // Reset input mode if the new layout wants to override it + if (item.inputMode !== -1) + inputModeNeedsReset = true + + if (!InputContext.inputEngine.inputMethod) + updateInputMethod() + + notifyLayoutChanged() + } + + MultiPointTouchArea { + id: keyboardInputArea + objectName: "keyboardInputArea" + + property Item initialKey: null + property bool dragSymbolMode + property real releaseMargin: initialKey !== null ? Math.min(initialKey.width / 3, initialKey.height / 3) : 0 + property point navigationCursor: Qt.point(-1, -1) + + anchors.fill: keyboardLayoutLoader + + Connections { + target: keyboardLayoutLoader + function onLoaded() { + if (keyboard.navigationModeActive && + keyboardInputArea.navigationCursor !== Qt.point(-1, -1)) + keyboard.navigationModeActive = keyboardInputArea.navigateToNextKey(0, 0, false) + } + } + Connections { + target: keyboard + function onNavigationModeActiveChanged() { + if (!keyboard.navigationModeActive) { + keyboardInputArea.navigationCursor = Qt.point(-1, -1) + keyboardInputArea.reset() + } + } + } + + function press(key, isRealPress) { + if (key && key.enabled) { + if (!key.noKeyEvent) + InputContext.inputEngine.virtualKeyPress(key.key, key.uppercased ? key.text.toUpperCase() : key.text, key.uppercased ? Qt.ShiftModifier : 0, key.repeat && !dragSymbolMode) + if (isRealPress) + soundEffect.play(key.soundEffect) + } + } + function release(key) { + if (key && key.enabled) { + if (!key.noKeyEvent) + InputContext.inputEngine.virtualKeyRelease(key.key, key.uppercased ? key.text.toUpperCase() : key.text, key.uppercased ? Qt.ShiftModifier : 0) + key.clicked() + } + } + function click(key) { + if (key && key.enabled) { + if (!key.noKeyEvent) + InputContext.inputEngine.virtualKeyClick(key.key, InputContext.uppercase ? key.text.toUpperCase() : key.text, InputContext.uppercase ? Qt.ShiftModifier : 0) + key.clicked() + } + } + function setActiveKey(activeKey) { + if (keyboard.activeKey === activeKey) + return + if (keyboard.activeKey) { + if (keyboard.activeKey.keyType === QtVirtualKeyboard.KeyType.FlickKey) + keyboard.activeKey.onKeyChanged.disconnect(onFlickKeyKeyChanged) + keyboard.activeKey.active = false + } + keyboard.activeKey = activeKey + if (keyboard.activeKey) { + keyboard.activeKey.active = true + } + } + function keyOnPoint(px, py) { + var parentItem = keyboardLayoutLoader + var child = parentItem.childAt(px, py) + while (child !== null) { + var position = parentItem.mapToItem(child, px, py) + px = position.x; py = position.y + parentItem = child + child = parentItem.childAt(px, py) + if (child && child.key !== undefined) + return child + } + return null + } + function hitInitialKey(x, y, margin) { + if (!initialKey) + return false + var position = initialKey.mapFromItem(keyboardInputArea, x, y) + return (position.x > -margin + && position.y > -margin + && position.x < initialKey.width + margin + && position.y < initialKey.height + margin) + } + function containsPoint(touchPoints, point) { + if (!point) + return false + for (var i in touchPoints) + if (touchPoints[i].pointId == point.pointId) + return true + return false + } + function releaseActiveKey() { + if (alternativeKeys.active) { + alternativeKeys.clicked() + } else if (functionPopupList.active) { + functionPopupList.clicked() + } else if (keyboard.activeKey) { + release(keyboard.activeKey) + } + reset() + } + function reset() { + releaseInaccuracyTimer.stop() + pressAndHoldTimer.stop() + setActiveKey(null) + activeTouchPoint = null + alternativeKeys.close() + functionPopupList.close() + if (dragSymbolMode) { + keyboard.symbolMode = false + dragSymbolMode = false + } + } + function nextKeyInNavigation(dX, dY, wrapEnabled) { + var nextKey = null, x, y, itemOffset + if (dX !== 0 || dY !== 0) { + var offsetX, offsetY + for (offsetX = dX, offsetY = dY; + Math.abs(offsetX) < width && Math.abs(offsetY) < height; + offsetX += dX, offsetY += dY) { + x = navigationCursor.x + offsetX + if (x < 0) { + if (!wrapEnabled) + break + x += width + } else if (x >= width) { + if (!wrapEnabled) + break + x -= width + } + y = navigationCursor.y + offsetY + if (y < 0) { + if (!wrapEnabled) + break + y += height + } else if (y >= height) { + if (!wrapEnabled) + break + y -= height + } + nextKey = keyOnPoint(x, y) + if (nextKey) { + // Check if key is visible. Only the visible keys have keyPanelDelegate set. + if (nextKey != initialKey && nextKey.hasOwnProperty("keyPanelDelegate") && nextKey.keyPanelDelegate) + break + // Jump over the item to reduce the number of iterations in this loop + itemOffset = mapToItem(nextKey, x, y) + if (dX > 0) + offsetX += nextKey.width - itemOffset.x + else if (dX < 0) + offsetX -= itemOffset.x + else if (dY > 0) + offsetY += nextKey.height - itemOffset.y + else if (dY < 0) + offsetY -= itemOffset.y + } + nextKey = null + } + } else { + nextKey = keyOnPoint(navigationCursor.x, navigationCursor.y) + } + if (nextKey) { + itemOffset = mapFromItem(nextKey, nextKey.width / 2, nextKey.height / 2) + if (dX) { + x = itemOffset.x + } else if (dY) { + y = itemOffset.y + } else { + x = itemOffset.x + y = itemOffset.y + } + navigationCursor = Qt.point(x, y) + } + return nextKey + } + function navigateToNextKey(dX, dY, wrapEnabled) { + // Resolve initial landing point of the navigation cursor + if (!keyboard.navigationModeActive || keyboard.navigationCursor === Qt.point(-1, -1)) { + if (dX > 0) + navigationCursor = Qt.point(0, height / 2) + else if (dX < 0) + navigationCursor = Qt.point(width, height / 2) + else if (dY > 0) + navigationCursor = Qt.point(width / 2, 0) + else if (dY < 0) + navigationCursor = Qt.point(width / 2, height) + else + navigationCursor = Qt.point(width / 2, height / 2) + keyboard.navigationModeActive = true + } + if (dX && dY) { + initialKey = nextKeyInNavigation(dX, 0, wrapEnabled) + if (initialKey || wrapEnabled) + initialKey = nextKeyInNavigation(0, dY, wrapEnabled) + } else { + initialKey = nextKeyInNavigation(dX, dY, wrapEnabled) + } + return initialKey !== null + } + + function onFlickKeyKeyChanged() { + InputContext.inputEngine.virtualKeyCancel() + press(activeKey, false) + } + + onPressed: (touchPoints) => { + keyboard.navigationModeActive = false + + // Immediately release any pending key that the user might be + // holding (and about to release) when a second key is pressed. + if (activeTouchPoint) + releaseActiveKey(); + + for (var i in touchPoints) { + // Release any key pressed by a previous iteration of the loop. + if (containsPoint(touchPoints, activeTouchPoint)) + releaseActiveKey(); + + initialKey = keyOnPoint(touchPoints[i].x, touchPoints[i].y) + if (!initialKey) + continue + activeTouchPoint = touchPoints[i] + if (initialKey.keyType === QtVirtualKeyboard.KeyType.FlickKey) { + initialKey.press(activeTouchPoint.x, activeTouchPoint.y) + initialKey.onKeyChanged.connect(onFlickKeyKeyChanged) + } else { + releaseInaccuracyTimer.start() + pressAndHoldTimer.start() + } + setActiveKey(initialKey) + press(initialKey, true) + } + } + onUpdated: (touchPoints) => { + if (!containsPoint(touchPoints, activeTouchPoint)) + return + + if (alternativeKeys.active) { + alternativeKeys.move(mapToItem(alternativeKeys, activeTouchPoint.x, 0).x) + } else if (functionPopupList.active) { + functionPopupList.move(mapToItem(functionPopupList, activeTouchPoint.x, activeTouchPoint.y)) + } else if (activeKey && activeKey.keyType === QtVirtualKeyboard.KeyType.FlickKey) { + activeKey.update(activeTouchPoint.x, activeTouchPoint.y) + } else { + var key = null + if (releaseInaccuracyTimer.running) { + if (hitInitialKey(activeTouchPoint.x, activeTouchPoint.y, releaseMargin)) { + key = initialKey + } else if (initialKey) { + releaseInaccuracyTimer.stop() + initialKey = null + } + } + if (key === null) { + key = keyOnPoint(activeTouchPoint.x, activeTouchPoint.y) + } + if (key !== keyboard.activeKey) { + InputContext.inputEngine.virtualKeyCancel() + setActiveKey(key) + press(key, false) + if (dragSymbolMode) { + if (key && key.functionKey && key.key !== Qt.Key_Context1) + pressAndHoldTimer.restart() + else + pressAndHoldTimer.stop() + } + } + } + } + onReleased: (touchPoints) => { + if (containsPoint(touchPoints, activeTouchPoint)) { + if (dragSymbolMode) { + var key = keyOnPoint(activeTouchPoint.x, activeTouchPoint.y) + if (key && key.key === Qt.Key_Context1) { + dragSymbolMode = false + InputContext.inputEngine.virtualKeyCancel() + reset() + return + } + } + releaseActiveKey(); + } + } + onCanceled: (touchPoints) => { + if (containsPoint(touchPoints, activeTouchPoint)) + reset() + } + } + } + } + } + + Item { + id: languagePopup + z: 1 + anchors.fill: parent + LayoutMirroring.enabled: false + LayoutMirroring.childrenInherit: true + + MouseArea { + onPressed: keyboard.hideLanguagePopup() + anchors.fill: parent + enabled: languagePopupList.enabled + } + + PopupList { + id: languagePopupList + objectName: "languagePopupList" + z: 2 + anchors.left: parent.left + anchors.top: parent.top + enabled: false + model: languageListModel + delegate: keyboard.style ? keyboard.style.languageListDelegate : null + highlight: keyboard.style ? keyboard.style.languageListHighlight : defaultHighlight + add: keyboard.style && !keyboard.noAnimations ? keyboard.style.languageListAdd : null + remove: keyboard.style && !keyboard.noAnimations ? keyboard.style.languageListRemove : null + property rect previewRect: Qt.rect(keyboard.x + languagePopupList.x, + keyboard.y + languagePopupList.y, + languagePopupList.width, + languagePopupList.height) + } + + Loader { + sourceComponent: keyboard.style.languageListBackground + anchors.fill: languagePopupList + z: -1 + visible: languagePopupList.visible + } + + ListModel { + id: languageListModel + + function selectItem(index) { + languagePopupList.currentIndex = index + keyboard.soundEffect.play(languagePopupList.currentItem.soundEffect) + changeLanguageTimer.newLocaleIndex = languageListModel.get(index).localeIndex + changeLanguageTimer.start() + } + } + + Timer { + id: changeLanguageTimer + interval: 1 + property int newLocaleIndex + onTriggered: { + if (languagePopupListActive) { + hideLanguagePopup() + start() + } else { + localeIndex = newLocaleIndex + } + } + } + + function show(locales, parentItem, customLayoutsOnly) { + if (!languagePopupList.enabled) { + languageListModel.clear() + for (var i = 0; i < locales.length; i++) { + languageListModel.append({localeName: locales[i].name, displayName: locales[i].locale.nativeLanguageName, localeIndex: locales[i].index}) + if (locales[i].index === keyboard.localeIndex) + languagePopupList.currentIndex = i + } + languagePopupList.positionViewAtIndex(languagePopupList.currentIndex, ListView.Center) + if (parentItem) { + languagePopupList.anchors.leftMargin = Qt.binding(function() { + const newLeftMargin = Math.round(keyboard.mapFromItem(parentItem, (parentItem.width - languagePopupList.width) / 2, 0).x) + return Math.min(Math.max(0, newLeftMargin), keyboard.width - languagePopupList.width) + }) + languagePopupList.anchors.topMargin = Qt.binding(function() {return Math.round(keyboard.mapFromItem(parentItem, 0, -languagePopupList.height).y)}) + } else { + languagePopupList.anchors.leftMargin = Qt.binding(function() {return Math.round((keyboard.width - languagePopupList.width) / 2)}) + languagePopupList.anchors.topMargin = Qt.binding(function() {return Math.round((keyboard.height - languagePopupList.height) / 2)}) + } + } + languagePopupList.enabled = true + } + + function hide() { + if (languagePopupList.enabled) { + languagePopupList.enabled = false + languagePopupList.anchors.leftMargin = undefined + languagePopupList.anchors.topMargin = undefined + languageListModel.clear() + } + } + } + + function showLanguagePopup(parentItem, customLayoutsOnly) { + var locales = keyboard.listLocales(customLayoutsOnly, parent.externalLanguageSwitchEnabled) + if (parent.externalLanguageSwitchEnabled) { + var currentIndex = 0 + for (var i = 0; i < locales.length; i++) { + if (locales[i] === keyboard.locale) { + currentIndex = i + break + } + } + parent.externalLanguageSwitch(locales, currentIndex) + return + } + languagePopup.show(locales, parentItem, customLayoutsOnly) + } + + function hideLanguagePopup() { + languagePopup.hide() + } + + MouseArea { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: keyboard.screenHeight + onPressed: keyboard.hideWordCandidateContextMenu() + enabled: wordCandidateContextMenuList.enabled + } + + Item { + id: wordCandidateContextMenu + objectName: "wordCandidateContextMenu" + z: 1 + anchors.fill: parent + LayoutMirroring.enabled: false + LayoutMirroring.childrenInherit: true + property int previousWordCandidateIndex: -1 + readonly property bool active: wordCandidateContextMenuList.visible + property bool openedByNavigationKeyLongPress + + PopupList { + id: wordCandidateContextMenuList + objectName: "wordCandidateContextMenuList" + z: 2 + anchors.left: parent.left + anchors.top: parent.top + enabled: false + model: wordCandidateContextMenuListModel + property rect previewRect: Qt.rect(keyboard.x + wordCandidateContextMenuList.x, + keyboard.y + wordCandidateContextMenuList.y, + wordCandidateContextMenuList.width, + wordCandidateContextMenuList.height) + } + + ListModel { + id: wordCandidateContextMenuListModel + + function selectItem(index) { + wordCandidateContextMenu.previousWordCandidateIndex = -1 + wordCandidateContextMenuList.currentIndex = index + keyboard.soundEffect.play(wordCandidateContextMenuList.currentItem.soundEffect) + switch (get(index).action) { + case "remove": + wordCandidateView.model.removeItem(wordCandidateView.currentIndex) + break + } + keyboard.hideWordCandidateContextMenu() + } + } + + function show(wordCandidateIndex) { + if (wordCandidateContextMenu.enabled) + wordCandidateContextMenu.hide() + + wordCandidateContextMenuListModel.clear() + + var canRemoveSuggestion = wordCandidateView.model.dataAt(wordCandidateIndex, SelectionListModel.Role.CanRemoveSuggestion) + if (canRemoveSuggestion) { + var dictionaryType = wordCandidateView.model.dataAt(wordCandidateIndex, SelectionListModel.Role.Dictionary) + var removeItemText; + switch (dictionaryType) { + case SelectionListModel.DictionaryType.User: + //~ VirtualKeyboard Context menu for word suggestion if it can be removed from the user dictionary. + removeItemText = qsTr("Remove from dictionary") + break + case SelectionListModel.DictionaryType.Default: + // Fallthrough + default: + //~ VirtualKeyboard Context menu for word suggestion if it can be removed from the default dictionary. + removeItemText = qsTr("Block word") + break + } + wordCandidateContextMenuListModel.append({action: "remove", display: removeItemText, wordCompletionLength: 0}) + } + + if (wordCandidateContextMenuListModel.count === 0) + return + + previousWordCandidateIndex = wordCandidateView.currentIndex + wordCandidateView.currentIndex = wordCandidateIndex + + wordCandidateContextMenuList.anchors.leftMargin = Qt.binding(function() { + if (!wordCandidateView.currentItem) + return 0 + var leftBorder = Math.round(wordCandidateView.mapFromItem(wordCandidateView.currentItem, (wordCandidateView.currentItem.width - wordCandidateContextMenuList.width) / 2, 0).x) + var rightBorder = Math.round(wordCandidateContextMenuList.parent.width - wordCandidateContextMenuList.width) + return Math.max(0, Math.min(leftBorder, rightBorder)) + }) + + wordCandidateContextMenuList.enabled = true + } + + function hide() { + if (wordCandidateContextMenuList.enabled) { + if (previousWordCandidateIndex !== -1) { + wordCandidateView.currentIndex = previousWordCandidateIndex + previousWordCandidateIndex = -1 + } + wordCandidateContextMenuList.enabled = false + wordCandidateContextMenuList.anchors.leftMargin = undefined + wordCandidateContextMenuListModel.clear() + } + openedByNavigationKeyLongPress = false + } + + function selectCurrentItem() { + if (active && wordCandidateContextMenuList.currentIndex !== -1) + wordCandidateContextMenuListModel.selectItem(wordCandidateContextMenuList.currentIndex) + } + } + + function showWordCandidateContextMenu(wordCandidateIndex) { + wordCandidateContextMenu.show(wordCandidateIndex) + } + + function hideWordCandidateContextMenu() { + wordCandidateContextMenu.hide() + } + + function updateInputMethod() { + if (!keyboardLayoutLoader.item) + return + if (!InputContext.priv.focus) + return + + // Reset the custom input method if it is not included in the list of shared layouts + if (customInputMethod && !inputMethodNeedsReset && customInputMethodSharedLayouts.indexOf(layoutType) === -1) + inputMethodNeedsReset = true + + var customInputMethodToDestroy = null + if (inputMethodNeedsReset) { + if (customInputMethod) { + // Postpones the destruction of the custom input method after creating a new one + // and after assigning it to the input engine. This allows the input method to clear + // its state before destroying. + customInputMethodToDestroy = customInputMethod + customInputMethod = null + } + customInputMethodSharedLayouts = [] + inputMethodNeedsReset = false + } + + var inputMethod = null + var inputMode = InputContext.inputEngine.inputMode + + // Use input method from keyboard layout + if (keyboardLayoutLoader.item.inputMethod) { + inputMethod = keyboardLayoutLoader.item.inputMethod + } else if (!customInputMethod) { + try { + customInputMethod = keyboardLayoutLoader.item.createInputMethod() + if (customInputMethod) { + // Pull the list of shared layouts from the keyboard layout + if (keyboardLayoutLoader.item.sharedLayouts) + customInputMethodSharedLayouts = customInputMethodSharedLayouts.concat(keyboardLayoutLoader.item.sharedLayouts) + + // Make sure the current layout is included in the list + if (customInputMethodSharedLayouts.indexOf(layoutType) === -1) + customInputMethodSharedLayouts.push(layoutType) + + // Reset input mode, since inputEngine.inputModes is updated + inputModeNeedsReset = true + } + } catch (e) { + console.error(e.message) + } + } + if (!inputMethod) { + if (customInputMethod) { + inputMethod = customInputMethod + } else if (!VirtualKeyboardSettings.defaultInputMethodDisabled) { + inputMethod = defaultInputMethod + } else { + inputMethod = plainInputMethod + } + } + + var inputMethodChanged = InputContext.inputEngine.inputMethod !== inputMethod + if (inputMethodChanged) { + InputContext.inputEngine.inputMethod = inputMethod + } + + if (InputContext.inputEngine.inputMethod) { + var inputModes = InputContext.inputEngine.inputModes + if (inputModes.length > 0) { + // Reset to default input mode if the input locale has changed + if (inputModeNeedsReset) { + inputMode = inputModes[0] + + // Check the current layout for input mode override + if (keyboardLayoutLoader.item.inputMode !== -1) + inputMode = keyboardLayoutLoader.item.inputMode + + // Update input mode automatically in handwriting mode + if (keyboard.handwritingMode) { + if (keyboard.dialableCharactersOnly && inputModes.indexOf(InputEngine.InputMode.Dialable) !== -1) + inputMode = InputEngine.InputMode.Dialable + else if ((keyboard.formattedNumbersOnly || keyboard.digitsOnly) && inputModes.indexOf(InputEngine.InputMode.Numeric) !== -1) + inputMode = InputEngine.InputMode.Numeric + else if (keyboardLayoutLoader.item.inputMode === -1) + inputMode = inputModes[0] + } + + // Check the input method hints for input mode overrides + if (latinOnly) + inputMode = InputEngine.InputMode.Latin + if (preferNumbers) + inputMode = InputEngine.InputMode.Numeric + } + + // Make sure the input mode is supported by the current input method + if (inputModes.indexOf(inputMode) === -1) + inputMode = inputModes[0] + + if (InputContext.inputEngine.inputMode !== inputMode || inputMethodChanged || inputModeNeedsReset) { + InputContext.priv.setKeyboardObserver(keyboardObserver) + InputContext.inputEngine.inputMode = inputMode + } + + inputModeNeedsReset = false + } + } + + if (customInputMethodToDestroy !== null) + customInputMethodToDestroy.destroy() + + // Clear the toggle shift timer + InputContext.priv.shiftHandler.clearToggleShiftTimer() + } + + function updateLayout() { + var newLayout + newLayout = findLayout(locale, layoutType) + if (!newLayout.length) { + newLayout = findLayout(locale, "main") + } + layout = newLayout + inputLocale = locale + updateInputMethod() + } + + function updateDefaultLocale() { + updateAvailableLocaleIndices() + if (layoutsModel.count > 0) { + var defaultLocales = [] + if (isValidLocale(VirtualKeyboardSettings.locale)) + defaultLocales.push(VirtualKeyboardSettings.locale) + if (isValidLocale(InputContext.locale)) + defaultLocales.push(InputContext.locale) + if (VirtualKeyboardSettings.activeLocales.length > 0 && isValidLocale(VirtualKeyboardSettings.activeLocales[0])) + defaultLocales.push(VirtualKeyboardSettings.activeLocales[0]) + if (VirtualKeyboardSettings.availableLocales.indexOf("en_GB") !== -1) + defaultLocales.push("en_GB") + if (availableLocaleIndices.length > 0) + defaultLocales.push(layoutsModel.get(availableLocaleIndices[0], "fileName")) + var newDefaultLocaleIndex = -1 + for (var i = 0; i < defaultLocales.length; i++) { + newDefaultLocaleIndex = findLocale(defaultLocales[i], -1) + if (availableLocaleIndices.indexOf(newDefaultLocaleIndex) !== -1) + break; + newDefaultLocaleIndex = -1 + } + defaultLocaleIndex = newDefaultLocaleIndex + } else { + defaultLocaleIndex = -1 + } + } + + function filterLocaleIndices(filterCb) { + var localeIndices = [] + for (var i = 0; i < layoutsModel.count; i++) { + if (localeIndices.indexOf(i) === -1) { + var localeName = layoutsModel.get(i, "fileName") + if (filterCb(localeName) && findLayout(localeName, "main")) + localeIndices.push(i) + } + } + return localeIndices + } + + function updateAvailableLocaleIndices() { + // Update list of all available locales + var fallbackIndex = findFallbackIndex() + var newIndices = filterLocaleIndices(function(localeName) { + return isValidLocale(localeName) + }) + + // Handle case where the VirtualKeyboardSettings.activeLocales contains no valid entries + // Fetch all locales by ignoring active locales setting + var ignoreActiveLocales = newIndices.length === 0 + if (ignoreActiveLocales) { + newIndices = filterLocaleIndices(function(localeName) { + return isValidLocale(localeName, ignoreActiveLocales) + }) + } + + // Fetch matching locale names + var newAvailableLocales = [] + for (var i = 0; i < newIndices.length; i++) { + newAvailableLocales.push(layoutsModel.get(newIndices[i], "fileName")) + } + + newAvailableLocales.sort() + + var sortOrder = !ignoreActiveLocales && VirtualKeyboardSettings.activeLocales.length > 0 ? + VirtualKeyboardSettings.activeLocales : + newAvailableLocales + + newIndices.sort(function(localeIndexA, localeIndexB) { + var localeNameA = layoutsModel.get(localeIndexA, "fileName") + var localeNameB = layoutsModel.get(localeIndexB, "fileName") + var sortIndexA = sortOrder.indexOf(localeNameA) + var sortIndexB = sortOrder.indexOf(localeNameB) + return sortIndexA - sortIndexB + }) + + availableLocaleIndices = newIndices + InputContext.priv.updateAvailableLocales(newAvailableLocales) + + // Update list of custom locale indices + newIndices = [] + for (i = 0; i < availableLocaleIndices.length; i++) { + if (availableLocaleIndices[i] === localeIndex || + layoutExists(layoutsModel.get(availableLocaleIndices[i], "fileName"), layoutType)) + newIndices.push(availableLocaleIndices[i]) + } + availableCustomLocaleIndices = newIndices + } + + function listLocales(customLayoutsOnly, localeNameOnly) { + var locales = [] + var localeIndices = customLayoutsOnly ? availableCustomLocaleIndices : availableLocaleIndices + for (var i = 0; i < localeIndices.length; i++) { + var layoutFolder = layoutsModel.get(localeIndices[i], "fileName") + if (localeNameOnly) + locales.push(layoutFolder) + else + locales.push({locale:Qt.locale(layoutFolder), index:localeIndices[i], name:layoutFolder}) + } + return locales + } + + function nextLocaleIndex(customLayoutsOnly) { + var newLocaleIndex = localeIndex + var localeIndices = customLayoutsOnly ? availableCustomLocaleIndices : availableLocaleIndices + var i = localeIndices.indexOf(localeIndex) + if (i !== -1) { + i = (i + 1) % localeIndices.length + newLocaleIndex = localeIndices[i] + } + return newLocaleIndex + } + + function changeInputLanguage(customLayoutsOnly) { + var newLocaleIndex = nextLocaleIndex(customLayoutsOnly) + if (newLocaleIndex !== -1 && newLocaleIndex !== localeIndex) + localeIndex = newLocaleIndex + } + + function canChangeInputLanguage(customLayoutsOnly) { + if (customLayoutsOnly) + return availableCustomLocaleIndices.length > 1 + return availableLocaleIndices.length > 1 + } + + function findLocale(localeName, defaultValue) { + var languageCode = localeName.substring(0, 3) // Including the '_' delimiter + var languageMatch = -1 + for (var i = 0; i < layoutsModel.count; i++) { + if (!layoutsModel.isFolder(i)) + continue + var layoutFolder = layoutsModel.get(i, "fileName") + if (layoutFolder === localeName) + return i + if (languageMatch == -1 && layoutFolder.substring(0, 3) === languageCode) + languageMatch = i + } + return (languageMatch != -1) ? languageMatch : defaultValue + } + + function findFallbackIndex() { + for (var i = 0; i < layoutsModel.count; i++) { + var layoutFolder = layoutsModel.get(i, "fileName") + if (layoutFolder === "fallback") + return i + } + return -1 + } + + function isValidLocale(localeNameOrIndex, ignoreActiveLocales) { + var localeName + if (typeof localeNameOrIndex == "number") { + if (localeNameOrIndex < 0 || localeNameOrIndex >= layoutsModel.count) + return false + localeName = layoutsModel.get(localeNameOrIndex, "fileName") + } else { + localeName = localeNameOrIndex + } + + if (!localeName) + return false + + if (localeName === "fallback") + return false + + if (Qt.locale(localeName).name === "C") + return false + + if (ignoreActiveLocales !== true && + VirtualKeyboardSettings.activeLocales.length > 0 && + VirtualKeyboardSettings.activeLocales.indexOf(localeName) === -1) + return false + + return true + } + + function getLayoutFile(localeName, layoutType) { + if (localeName === "" || layoutType === "") + return "" + return layoutsModel.folder + "/" + localeName + "/" + layoutType + ".qml" + } + + function getFallbackFile(localeName, layoutType) { + if (localeName === "" || layoutType === "") + return "" + return layoutsModel.folder + "/" + localeName + "/" + layoutType + ".fallback" + } + + function layoutExists(localeName, layoutType) { + var result = InputContext.priv.fileExists(getLayoutFile(localeName, layoutType)) + if (!result && layoutType === "handwriting") + result = InputContext.priv.fileExists(getFallbackFile(localeName, layoutType)) + return result + } + + function findLayout(localeName, layoutType) { + var layoutFile = getLayoutFile(localeName, layoutType) + if (InputContext.priv.fileExists(layoutFile)) + return layoutFile + var fallbackFile = getFallbackFile(localeName, layoutType) + if (InputContext.priv.fileExists(fallbackFile)) { + layoutFile = getLayoutFile("fallback", layoutType) + if (InputContext.priv.fileExists(layoutFile)) + return layoutFile + } + return "" + } + + function isHandwritingAvailable() { + if (VirtualKeyboardSettings.handwritingModeDisabled) + return false + return VirtualKeyboardFeatures.Handwriting && layoutExists(locale, "handwriting") + } + + function setHandwritingMode(enabled, resetInputMode) { + if (VirtualKeyboardSettings.handwritingModeDisabled) + return + if (enabled && resetInputMode) + inputModeNeedsReset = true + handwritingMode = enabled + } + + function notifyLayoutChanged() { + Qt.callLater(function() { + if (keyboardLayoutLoader.item != null) keyboardObserver.layoutChanged() + }) + } + + function doKeyboardFunction(keyboardFunction) { + if (!isKeyboardFunctionAvailable(keyboardFunction)) + return + switch (keyboardFunction) { + case QtVirtualKeyboard.KeyboardFunction.HideInputPanel: + InputContext.priv.hideInputPanel() + break + case QtVirtualKeyboard.KeyboardFunction.ChangeLanguage: + if (style.languagePopupListEnabled) { + if (!languagePopupListActive) { + showLanguagePopup(activeKey, false) + } else { + hideLanguagePopup() + } + } else { + const customLayoutsOnly = arguments.length == 2 && arguments[1] + changeInputLanguage(customLayoutsOnly) + } + break + case QtVirtualKeyboard.KeyboardFunction.ToggleHandwritingMode: + setHandwritingMode(!handwritingMode) + break + default: + console.warn("Unknown keyboard function '%1'".arg(keyboardFunction)) + break + } + } + + function isKeyboardFunctionAvailable(keyboardFunction) { + switch (keyboardFunction) { + case QtVirtualKeyboard.KeyboardFunction.HideInputPanel: + return true + case QtVirtualKeyboard.KeyboardFunction.ChangeLanguage: + const customLayoutsOnly = arguments.length == 2 && arguments[1] + return canChangeInputLanguage(customLayoutsOnly) + case QtVirtualKeyboard.KeyboardFunction.ToggleHandwritingMode: + return isHandwritingAvailable() + default: + return false + } + } + + function isFunctionPopupListAvailable() { + const allFunctionKeys = QtVirtualKeyboard.KeyboardFunctionKeys.Hide | + QtVirtualKeyboard.KeyboardFunctionKeys.Language + return (VirtualKeyboardSettings.visibleFunctionKeys & allFunctionKeys) !== allFunctionKeys || + isHandwritingAvailable() + } +} |