Revision control

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