// 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 navigationHighlight: navigationHighlight 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: navigationHighlight objectName: "navigationHighlight" 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: navigationHighlight.moveDuration; easing.type: Easing.OutCubic } } Behavior on y { NumberAnimation { id: yAnimation; duration: navigationHighlight.moveDuration; easing.type: Easing.OutCubic } } Behavior on width { NumberAnimation { id: widthAnimation; duration: navigationHighlight.resizeDuration; easing.type: Easing.OutCubic } } Behavior on height { NumberAnimation { id: heightAnimation; duration: navigationHighlight.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.y 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: keyboard.active && InputContext.inputEngine.wordCandidateListVisibleHint && (!wordCandidateView.empty || wordCandidateViewAutoHideTimer.running) readonly property bool alwaysVisibleCondition: InputContext.inputEngine.wordCandidateListVisibleHint && (keyboard.fullScreenMode || VirtualKeyboardSettings.wordCandidateList.alwaysVisible) readonly property real visibleYOffset: -height 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: wordCandidateView.alwaysVisibleCondition 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.soundVolume = VirtualKeyboardSettings.convertVolume(VirtualKeyboardSettings.keySoundVolume) 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) { let currentIndex = -1 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) currentIndex = i } 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 if (currentIndex !== -1) { languagePopupList.currentIndex = currentIndex languagePopupList.forceLayout() languagePopupList.positionViewAtIndex(currentIndex, ListView.Center) } } 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) } Loader { sourceComponent: keyboard.style.popupListBackground anchors.fill: wordCandidateContextMenuList z: -1 visible: wordCandidateContextMenuList.visible } 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 wordCandidateContextMenuList.currentIndex = 0 } 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() } }