blob: 1c6f174f7b2459868af6ac079c3eccf714ea7e4b [file] [log] [blame]
/****************************************************************************
**
** 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
}
}