| /**************************************************************************** |
| ** |
| ** 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 |
| onLocaleChanged: { |
| updateDefaultLocale() |
| localeIndex = defaultLocaleIndex |
| } |
| onActiveLocalesChanged: { |
| updateDefaultLocale() |
| if (!isValidLocale(localeIndex) || VirtualKeyboardSettings.locale) |
| localeIndex = defaultLocaleIndex |
| } |
| 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 |
| onInputMethodHintsChanged: { |
| if (InputContext.priv.focus) |
| updateInputMethod() |
| } |
| } |
| Connections { |
| target: InputContext.priv |
| onInputItemChanged: { |
| keyboard.hideLanguagePopup() |
| if (active && symbolMode && !preferNumbers) |
| symbolMode = false |
| } |
| onFocusChanged: { |
| if (InputContext.priv.focus) |
| updateInputMethod() |
| } |
| onNavigationKeyPressed: { |
| 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 |
| } |
| } |
| onNavigationKeyReleased: { |
| 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 |
| onVirtualKeyClicked: { |
| 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 |
| 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 |
| 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 |
| onActiveItemChanged: wordCandidateView.currentIndex = index |
| onItemSelected: if (wordCandidateView.currentItem) soundEffect.play(wordCandidateView.currentItem.soundEffect) |
| onCountChanged: { |
| var empty = wordCandidateView.model.count === 0 |
| if (empty) |
| wordCandidateViewAutoHideTimer.restart() |
| else |
| wordCandidateViewAutoHideTimer.stop() |
| wordCandidateView.empty = empty |
| keyboard.hideWordCandidateContextMenu() |
| } |
| } |
| Connections { |
| target: InputContext.priv |
| onInputItemChanged: wordCandidateViewAutoHideTimer.stop() |
| } |
| Connections { |
| target: InputContext.inputEngine |
| 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 |
| 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 |
| onStatusChanged: { |
| if (keyboardLayoutLoader.status == Loader.Ready && |
| keyboard.navigationModeActive && |
| keyboardInputArea.navigationCursor !== Qt.point(-1, -1)) |
| keyboard.navigationModeActive = keyboardInputArea.navigateToNextKey(0, 0, false) |
| } |
| } |
| Connections { |
| target: keyboard |
| 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 |
| } |
| } |