/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Virtual Keyboard module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ import QtQuick 2.0 // Deliberately imported after QtQuick to avoid missing restoreMode property in Binding. Fix in Qt 6. import QtQml 2.14 import QtQuick.Layouts 1.0 import QtQuick.Window 2.2 import QtQuick.VirtualKeyboard 2.3 import QtQuick.VirtualKeyboard.Styles 2.1 import QtQuick.VirtualKeyboard.Settings 2.2 import QtQuick.VirtualKeyboard.Plugins 2.3 import Qt.labs.folderlistmodel 2.0 Item { id: keyboard objectName: "keyboard" property alias style: styleLoader.item property alias wordCandidateView: wordCandidateView property alias shadowInputControl: shadowInputControl property var 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 function initDefaultInputMethod() { try { return Qt.createQmlObject('import QtQuick 2.0; import QtQuick.VirtualKeyboard.Plugins 2.3; DefaultInputMethod {}', keyboard, "defaultInputMethod") } catch (e) { } return plainInputMethod } width: keyboardBackground.width height: keyboardBackground.height + (VirtualKeyboardSettings.wordCandidateList.alwaysVisible ? wordCandidateView.height : 0) 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 onFullScreenModeChanged() { wordCandidateView.disableAnimation = VirtualKeyboardSettings.fullScreenMode keyboard.fullScreenMode = VirtualKeyboardSettings.fullScreenMode } } 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 (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, 0, false)) { keyboardInputArea.initialKey = initialKey if (!keyboardInputArea.navigateToNextKey(0, -1 * direction, false)) { if (wordCandidateView.count) { if (wordCandidateView.count) { wordCandidateView.currentIndex = wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight ? (wordCandidateView.count - 1) : 0 break } break } keyboardInputArea.initialKey = initialKey keyboardInputArea.navigateToNextKey(0, -1 * direction, true) } keyboardInputArea.navigateToNextKey(-1, 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 (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 (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, 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, 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 (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 && !wordCandidateContextMenu.active && keyboard.activeKey) { keyboardInputArea.release(keyboard.activeKey) pressAndHoldTimer.stop() alternativeKeys.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 (!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 } } Timer { id: pressAndHoldTimer interval: 800 onTriggered: { if (keyboard.activeKey && keyboard.activeKey === keyboardInputArea.initialKey) { var origin = keyboard.mapFromItem(activeKey, activeKey.width / 2, 0) 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) { 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) { wordCandidateContextMenu.show(wordCandidateView.currentIndex) wordCandidateContextMenu.openedByNavigationKeyLongPress = keyboard.navigationModeActive } } } Timer { id: releaseInaccuracyTimer interval: 500 onTriggered: { if (keyboardInputArea.pressed && activeTouchPoint && !alternativeKeys.active && !keyboardInputArea.dragSymbolMode) { 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 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 (keyboardInputArea.initialKey) { return keyboardInputArea.initialKey } else if (languagePopupListActive) { return languagePopupList.highlightItem } else if (alternativeKeys.listView.count > 0) { return alternativeKeys.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: 200 property int resizeDuration: 200 property alias xAnimation: xAnimation property alias yAnimation: yAnimation property alias widthAnimation: widthAnimation property alias heightAnimation: heightAnimation 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.parent.parent ? keyboard.parent.parent.height : Screen.height) - keyboard.height - (wordCandidateView.visibleCondition && !VirtualKeyboardSettings.wordCandidateList.alwaysVisible ? wordCandidateView.height : 0) 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 { 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 disableAnimation: VirtualKeyboardSettings.fullScreenMode property bool empty: true readonly property bool visibleCondition: (((!wordCandidateView.empty || wordCandidateViewAutoHideTimer.running || shadowInputControl.visible) && InputContext.inputEngine.wordCandidateListVisibleHint) || VirtualKeyboardSettings.wordCandidateList.alwaysVisible) && (keyboard.active || shadowInputControl.visible) readonly property real visibleYOffset: VirtualKeyboardSettings.wordCandidateList.alwaysVisible ? 0 : -height readonly property real currentYOffset: visibleCondition || wordCandidateViewTransition.running ? visibleYOffset : 0 height: Math.round(style.selectionListHeight) 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: style.selectionListAdd remove: style.selectionListRemove 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 } } transitions: Transition { id: wordCandidateViewTransition to: "visible" enabled: !InputContext.animating && !VirtualKeyboardSettings.wordCandidateList.alwaysVisible && !wordCandidateView.disableAnimation 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 2.0; import QtQuick.VirtualKeyboard 2.1; 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: Math.round(style.keyboardDesignHeight * width / style.keyboardDesignWidth) anchors.horizontalCenter: parent.horizontalCenter LayoutMirroring.enabled: false LayoutMirroring.childrenInherit: true 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.layout.length > 0 restoreMode: Binding.RestoreBinding } onItemChanged: { // Reset input mode if the new layout wants to override it if (item && item.inputMode !== -1) inputModeNeedsReset = true } MultiPointTouchArea { id: keyboardInputArea objectName: "keyboardInputArea" property var 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 onStatusChanged() { if (keyboardLayoutLoader.status == Loader.Ready && 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) { 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 (keyboard.activeKey) { release(keyboard.activeKey) } reset() } function reset() { releaseInaccuracyTimer.stop() pressAndHoldTimer.stop() setActiveKey(null) activeTouchPoint = null alternativeKeys.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 } onPressed: { 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(); releaseInaccuracyTimer.start() pressAndHoldTimer.start() initialKey = keyOnPoint(touchPoints[i].x, touchPoints[i].y) activeTouchPoint = touchPoints[i] setActiveKey(initialKey) press(initialKey, true) } } onUpdated: { if (!containsPoint(touchPoints, activeTouchPoint)) return if (alternativeKeys.active) { alternativeKeys.move(mapToItem(alternativeKeys, activeTouchPoint.x, 0).x) } 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) pressAndHoldTimer.restart() } } } onReleased: { if (containsPoint(touchPoints, activeTouchPoint)) releaseActiveKey(); } onCanceled: { 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.style.languageListAdd : null remove: keyboard.style ? keyboard.style.languageListRemove : null background: keyboard.style ? keyboard.style.languageListBackground : null property rect previewRect: Qt.rect(keyboard.x + languagePopupList.x, keyboard.y + languagePopupList.y, languagePopupList.width, languagePopupList.height) } 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) languagePopupList.anchors.leftMargin = Qt.binding(function() {return Math.round(keyboard.mapFromItem(parentItem, (parentItem.width - languagePopupList.width) / 2, 0).x)}) languagePopupList.anchors.topMargin = Qt.binding(function() {return Math.round(keyboard.mapFromItem(parentItem, 0, -languagePopupList.height).y)}) } 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.parent.parent ? keyboard.parent.parent.height : Screen.height 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() { 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.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 if (inputMethodNeedsReset) { if (customInputMethod) { customInputMethod.destroy() 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) inputMethod = customInputMethod ? customInputMethod : defaultInputMethod 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.inputEngine.inputMode = inputMode inputModeNeedsReset = false } } // 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 if (newIndices.length === 0) { newIndices = filterLocaleIndices(function(localeName) { return isValidLocale(localeName, true) }) } // Fetch matching locale names var newAvailableLocales = [] for (var i = 0; i < newIndices.length; i++) { newAvailableLocales.push(layoutsModel.get(newIndices[i], "fileName")) } newIndices.sort(function(a, b) { return a - b }) availableLocaleIndices = newIndices newAvailableLocales.sort() 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() { return InputContext.priv.inputMethods.indexOf("HandwritingInputMethod") !== -1 && layoutExists(locale, "handwriting") } function setHandwritingMode(enabled, resetInputMode) { if (enabled && resetInputMode) inputModeNeedsReset = true handwritingMode = enabled } }