aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/AlternativeKeys.qml133
-rw-r--r--src/components/BackspaceKey.qml52
-rw-r--r--src/components/BaseKey.qml281
-rw-r--r--src/components/CMakeLists.txt55
-rw-r--r--src/components/ChangeLanguageKey.qml75
-rw-r--r--src/components/CharacterPreviewBubble.qml81
-rw-r--r--src/components/EnterKey.qml58
-rw-r--r--src/components/FillerKey.qml47
-rw-r--r--src/components/FlickKey.qml120
-rw-r--r--src/components/FunctionPopupList.qml133
-rw-r--r--src/components/HandwritingModeKey.qml57
-rw-r--r--src/components/HideKeyboardKey.qml50
-rw-r--r--src/components/InputModeKey.qml127
-rw-r--r--src/components/Key.qml49
-rw-r--r--src/components/Keyboard.qml1828
-rw-r--r--src/components/KeyboardColumn.qml63
-rw-r--r--src/components/KeyboardLayout.qml188
-rw-r--r--src/components/KeyboardLayoutLoader.qml140
-rw-r--r--src/components/KeyboardRow.qml62
-rw-r--r--src/components/ModeKey.qml66
-rw-r--r--src/components/MultiSoundEffect.qml73
-rw-r--r--src/components/MultitapInputMethod.qml132
-rw-r--r--src/components/NumberKey.qml49
-rw-r--r--src/components/PopupList.qml59
-rw-r--r--src/components/SelectionControl.qml100
-rw-r--r--src/components/ShadowInputControl.qml141
-rw-r--r--src/components/ShiftKey.qml53
-rw-r--r--src/components/SpaceKey.qml53
-rw-r--r--src/components/SymbolModeKey.qml52
-rw-r--r--src/components/TraceInputArea.qml223
-rw-r--r--src/components/TraceInputKey.qml116
-rw-r--r--src/components/WordCandidatePopupList.qml85
32 files changed, 4801 insertions, 0 deletions
diff --git a/src/components/AlternativeKeys.qml b/src/components/AlternativeKeys.qml
new file mode 100644
index 00000000..951b3c8d
--- /dev/null
+++ b/src/components/AlternativeKeys.qml
@@ -0,0 +1,133 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+Item {
+ property bool active: listView.currentIndex != -1
+ property int highlightIndex: -1
+ property alias listView: listView
+ property int keyCode
+ property point origin
+ signal clicked
+ LayoutMirroring.enabled: false
+ LayoutMirroring.childrenInherit: true
+
+ z: 1
+ visible: active
+ anchors.fill: parent
+
+ ListModel {
+ id: listModel
+ }
+
+ ListView {
+ id: listView
+ spacing: 0
+ model: listModel
+ delegate: keyboard.style.alternateKeysListDelegate
+ highlight: keyboard.style.alternateKeysListHighlight ? keyboard.style.alternateKeysListHighlight : defaultHighlight
+ highlightMoveDuration: 0
+ highlightResizeDuration: 0
+ keyNavigationWraps: true
+ orientation: ListView.Horizontal
+ height: keyboard.style ? keyboard.style.alternateKeysListItemHeight : 0
+ x: origin.x
+ y: keyboard.style ? origin.y - height - keyboard.style.alternateKeysListBottomMargin : 0
+ Component {
+ id: defaultHighlight
+ Item {}
+ }
+ }
+
+ Loader {
+ id: backgroundLoader
+ sourceComponent: keyboard.style.alternateKeysListBackground
+ anchors.fill: listView
+ z: -1
+ states: State {
+ name: "highlighted"
+ when: highlightIndex !== -1 && highlightIndex === listView.currentIndex &&
+ backgroundLoader.item !== null && backgroundLoader.item.hasOwnProperty("currentItemHighlight")
+ PropertyChanges {
+ target: backgroundLoader.item
+ currentItemHighlight: true
+ }
+ }
+ }
+
+ onClicked: {
+ if (active && listView.currentIndex >= 0 && listView.currentIndex < listView.model.count) {
+ var activeKey = listView.model.get(listView.currentIndex)
+ InputContext.inputEngine.virtualKeyClick(keyCode, activeKey.data,
+ InputContext.uppercase ? Qt.ShiftModifier : 0)
+ }
+ }
+
+ function open(key, originX, originY) {
+ keyCode = key.key
+ var alternativeKeys = key.effectiveAlternativeKeys
+ var displayAlternativeKeys = key.displayAlternativeKeys
+ if (alternativeKeys.length > 0 && displayAlternativeKeys.length === alternativeKeys.length) {
+ for (var i = 0; i < alternativeKeys.length; i++) {
+ listModel.append({
+ "text": InputContext.uppercase ? displayAlternativeKeys[i].toUpperCase() : displayAlternativeKeys[i],
+ "data": InputContext.uppercase ? alternativeKeys[i].toUpperCase() : alternativeKeys[i]
+ })
+ }
+ listView.width = keyboard.style.alternateKeysListItemWidth * listModel.count
+ listView.forceLayout()
+ highlightIndex = key.effectiveAlternativeKeysHighlightIndex
+ if (highlightIndex === -1) {
+ console.log("AlternativeKeys: active key \"" + key.text + "\" not found in alternativeKeys \"" + alternativeKeys + ".\"")
+ highlightIndex = 0
+ }
+ listView.currentIndex = highlightIndex
+ var currentItemOffset = (listView.currentIndex + 0.5) * keyboard.style.alternateKeysListItemWidth
+ origin = Qt.point(Math.min(Math.max(keyboard.style.alternateKeysListLeftMargin, originX - currentItemOffset), width - listView.width - keyboard.style.alternateKeysListRightMargin), originY)
+ if (backgroundLoader.item && backgroundLoader.item.hasOwnProperty("currentItemOffset")) {
+ backgroundLoader.item.currentItemOffset = currentItemOffset
+ }
+ }
+ return active
+ }
+
+ function move(mouseX) {
+ var newIndex = listView.indexAt(Math.max(1, Math.min(listView.width - 1, mapToItem(listView, mouseX, 0).x)), 1)
+ if (newIndex !== listView.currentIndex) {
+ listView.currentIndex = newIndex
+ }
+ }
+
+ function close() {
+ listView.currentIndex = -1
+ listModel.clear()
+ }
+}
diff --git a/src/components/BackspaceKey.qml b/src/components/BackspaceKey.qml
new file mode 100644
index 00000000..998e9181
--- /dev/null
+++ b/src/components/BackspaceKey.qml
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype BackspaceKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits BaseKey
+
+ \brief Backspace key for keyboard layouts.
+
+ Sends a backspace key for input method processing.
+ This key is repeatable.
+*/
+
+BaseKey {
+ key: Qt.Key_Backspace
+ keyType: QtVirtualKeyboard.BackspaceKey
+ repeat: true
+ functionKey: true
+ highlighted: true
+ keyPanelDelegate: keyboard.style ? keyboard.style.backspaceKeyPanel : undefined
+}
diff --git a/src/components/BaseKey.qml b/src/components/BaseKey.qml
new file mode 100644
index 00000000..945e8a70
--- /dev/null
+++ b/src/components/BaseKey.qml
@@ -0,0 +1,281 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.Layouts
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype BaseKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits Item
+
+ \brief Common parent for all key types.
+
+ BaseKey is a common type for all keys in keyboard layout.
+
+ This type should not be used directly in the layouts. The specialized
+ key types, such as Key or EnterKey should be used instead.
+*/
+
+Item {
+ id: keyItem
+
+ /*! \since QtQuick.VirtualKeyboard 6.1
+
+ Type of the key.
+
+ \list
+ \li \c QtVirtualKeyboard.BaseKey
+ \li \c QtVirtualKeyboard.BackspaceKey
+ \li \c QtVirtualKeyboard.ChangeLanguageKey
+ \li \c QtVirtualKeyboard.EnterKey
+ \li \c QtVirtualKeyboard.FillerKey
+ \li \c QtVirtualKeyboard.HandwritingModeKey
+ \li \c QtVirtualKeyboard.HideKeyboardKey
+ \li \c QtVirtualKeyboard.InputModeKey
+ \li \c QtVirtualKeyboard.Key
+ \li \c QtVirtualKeyboard.ModeKey
+ \li \c QtVirtualKeyboard.NumberKey
+ \li \c QtVirtualKeyboard.ShiftKey
+ \li \c QtVirtualKeyboard.SpaceKey
+ \li \c QtVirtualKeyboard.SymbolModeKey
+ \li \c QtVirtualKeyboard.FlickKey
+ \endlist
+ */
+ property int keyType: QtVirtualKeyboard.BaseKey
+
+ /*! Sets the key weight value which determines the relative size of the key.
+
+ Use this property to change the key size in the layout.
+
+ The default value is inherited from the parent element
+ of the key in the layout hierarchy.
+ */
+ property real weight: parent.keyWeight
+
+ /*! Sets the key text for input method processing.
+
+ In most cases, this is the Unicode representation of the key code.
+
+ The default value is an empty string.
+ */
+ property string text: ""
+
+ /*! Sets the display text.
+
+ This string is rendered in the keyboard layout.
+
+ The default value is the key text.
+ */
+ property string displayText: text
+
+ /*! \since QtQuick.VirtualKeyboard 2.0
+
+ Sets the small text rendered in the corner of the key.
+
+ The default value based on the default item in the effective alternative keys.
+ */
+ property string smallText: effectiveAlternativeKeys && effectiveAlternativeKeysHighlightIndex !== -1 ? effectiveAlternativeKeys[effectiveAlternativeKeysHighlightIndex] : ""
+
+ /*! \since QtQuick.VirtualKeyboard 2.0
+
+ Sets the visibility of small text.
+
+ The default value is inherited from the parent.
+ */
+ property bool smallTextVisible: parent.smallTextVisible
+
+ /*! Sets the list of alternative keys.
+
+ This property can be set to a string, or a list of strings. If the value is
+ a string, the alternative keys are presented as individual characters of
+ that string. If the value is a list of strings, the list is used instead.
+
+ The alternative keys are presented to the user by pressing and holding a key
+ with this property set.
+
+ \note If the alternative keys contains the key \c text, it will be filtered from
+ the \c effectiveAlternativeKeys and its position will be used as an indicator
+ for the highlighted item instead.
+
+ The default is empty list.
+ */
+ property var alternativeKeys: []
+
+ /*! \since QtQuick.VirtualKeyboard 2.0
+
+ This property contains the effective alternative keys presented to user.
+
+ The list is contains the items in the \c alternativeKeys excluding the \c text
+ item.
+ */
+ readonly property var effectiveAlternativeKeys: {
+ var textIndex = alternativeKeys.indexOf(text)
+ if (textIndex == -1)
+ return alternativeKeys
+ return alternativeKeys.slice(0, textIndex).concat(alternativeKeys.slice(textIndex + 1))
+ }
+
+ /*! \since QtQuick.VirtualKeyboard 2.0
+
+ This property contains the index of highlighted item in the \c effectiveAlternativeKeys.
+
+ The index is calculated from the index of the key \c text in the \c alternativeKeys.
+
+ For example, if the alternative keys contains "çcċčć" and the key \c text is "c",
+ this index will become 1 and the effective alternative keys presented to user will
+ be "ç[ċ]čć".
+ */
+ readonly property int effectiveAlternativeKeysHighlightIndex: {
+ var index = alternativeKeys.indexOf(text)
+ return index > 0 && (index + 1) == alternativeKeys.length ? index - 1 : index
+ }
+
+ /*! \since QtQuick.VirtualKeyboard 6.2
+
+ This property allows overriding the list of key strings presented to the user in the
+ alternative keys view.
+ */
+ property var displayAlternativeKeys: effectiveAlternativeKeys
+
+ /*! Sets the key code for input method processing.
+
+ The default is Qt.Key_unknown.
+ */
+ property int key: Qt.Key_unknown
+
+ /*! \since QtQuick.VirtualKeyboard 1.3
+
+ This property controls whether the key emits key events for input
+ method processing. When true, the key events are disabled.
+
+ By default, the key event is emitted if the \e key is not unknown
+ or the \e text is not empty.
+ */
+ property bool noKeyEvent: key === Qt.Key_unknown && text.length === 0
+
+ /*! This property holds the active status of the key.
+
+ This property is automatically set to true when the key is pressed.
+ */
+ property bool active: false
+
+ /*! \since QtQuick.VirtualKeyboard 1.3
+
+ Disables key modifiers on the emitted key.
+
+ The default is false.
+ */
+ property bool noModifier: false
+
+ /*! Sets the key repeat attribute.
+
+ If the repeat is enabled, the key will repeat the input events while held down.
+ The repeat should not be used if alternativeKeys is also set.
+
+ The default is false.
+ */
+ property bool repeat: false
+
+ /*! Sets the highlighted status of the key.
+
+ The default is false.
+ */
+ property bool highlighted: false
+
+ /*! Sets the function key attribute.
+
+ The default is false.
+ */
+ property bool functionKey: false
+
+ /*! Sets the show preview attribute.
+
+ By default, the character preview popup is not shown for function keys.
+ */
+ property bool showPreview: enabled && !functionKey && !keyboard.navigationModeActive
+
+ /*! This property holds the pressed status of the key.
+
+ The pressed status can only be true if the key is both enabled and active.
+ When the key state becomes pressed, it triggers a key down event for the
+ input engine. A key up event is triggered when the key is released.
+ */
+ property bool pressed: enabled && active
+
+ /*! This property holds the uppercase status of the key.
+
+ By default, this property reflects the uppercase status of the keyboard.
+ */
+ property bool uppercased: InputContext.uppercase && !noModifier
+
+ /*! Sets the key panel delegate for the key.
+
+ This property is essential for key decoration. Without a key panel delegate,
+ the key is invisible. This property should be assigned in the inherited key type.
+ */
+ property alias keyPanelDelegate: keyPanel.sourceComponent
+
+ /*!
+ \since QtQuick.VirtualKeyboard 1.1
+
+ This property holds the sound effect to be played on key press.
+
+ This property is read-only since the sound effects are defined in the keyboard style.
+ */
+ readonly property url soundEffect: keyPanel.item ? keyPanel.item.soundEffect : ""
+
+ onSoundEffectChanged: keyboard.soundEffect.register(soundEffect)
+
+ // QTBUG-54953, QTBUG-55773
+ // Avoid a row that was hidden taking up the entire height of the
+ // keyboard when it is made visible after the application has started.
+ // This value is low because keys can scale vertically, and setting e.g. 40
+ // pixels might be too high for a keyboard that doesn't have a lot of space.
+ implicitHeight: 1
+
+ Layout.minimumWidth: keyPanel.implicitWidth
+ Layout.minimumHeight: keyPanel.implicitHeight
+ Layout.preferredWidth: weight
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+
+ Loader {
+ id: keyPanel
+ anchors.fill: parent
+ onLoaded: keyPanel.item.control = keyItem
+ }
+
+ /*! This signal is triggered when the key is pressed, allowing custom processing
+ of key.
+ */
+ signal clicked
+}
diff --git a/src/components/CMakeLists.txt b/src/components/CMakeLists.txt
new file mode 100644
index 00000000..746dce26
--- /dev/null
+++ b/src/components/CMakeLists.txt
@@ -0,0 +1,55 @@
+#####################################################################
+## VirtualKeyboard.Components Module:
+#####################################################################
+
+set(qml_files
+ AlternativeKeys.qml
+ BackspaceKey.qml
+ BaseKey.qml
+ ChangeLanguageKey.qml
+ CharacterPreviewBubble.qml
+ EnterKey.qml
+ FillerKey.qml
+ FlickKey.qml
+ FunctionPopupList.qml
+ HandwritingModeKey.qml
+ HideKeyboardKey.qml
+ InputModeKey.qml
+ Key.qml
+ Keyboard.qml
+ KeyboardColumn.qml
+ KeyboardLayout.qml
+ KeyboardLayoutLoader.qml
+ KeyboardRow.qml
+ ModeKey.qml
+ MultiSoundEffect.qml
+ MultitapInputMethod.qml
+ NumberKey.qml
+ PopupList.qml
+ SelectionControl.qml
+ ShadowInputControl.qml
+ ShiftKey.qml
+ SpaceKey.qml
+ SymbolModeKey.qml
+ TraceInputArea.qml
+ TraceInputKey.qml
+ WordCandidatePopupList.qml
+)
+
+qt_internal_add_qml_module(qtvkbcomponentsplugin
+ URI "QtQuick.VirtualKeyboard.Components"
+ VERSION "${PROJECT_VERSION}"
+ PAST_MAJOR_VERSIONS 2 1
+ PLUGIN_TARGET qtvkbcomponentsplugin
+ DEPENDENCIES
+ QtQuick/auto
+ QtQuick.Layouts/auto
+ QtQuick.VirtualKeyboard.Settings/auto
+ QML_FILES
+ ${qml_files}
+ LIBRARIES
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+)
diff --git a/src/components/ChangeLanguageKey.qml b/src/components/ChangeLanguageKey.qml
new file mode 100644
index 00000000..0d3ec969
--- /dev/null
+++ b/src/components/ChangeLanguageKey.qml
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype ChangeLanguageKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits BaseKey
+
+ \brief Change language key for keyboard layouts.
+
+ This key changes the current input language in the list of supported
+ languages. The key has two function modes:
+
+ \list
+ \li Popup mode
+ \li Toggle mode
+ \endlist
+
+ The popup mode is enabled by the \l {KeyboardStyle::languagePopupListEnabled} property.
+ If enabled, a key press will open a popup list with available languages. Otherwise
+ it will cycle to the next available input language.
+*/
+
+BaseKey {
+ /*! If this property is true, the input language is only
+ changed between the languages providing custom layout.
+
+ For example, if only the English and Arabic languages
+ provide digits layout, then other locales using the
+ shared default layout are ignored.
+
+ The default is false.
+ */
+ property bool customLayoutsOnly: false
+
+ id: changeLanguageKey
+ keyType: QtVirtualKeyboard.ChangeLanguageKey
+ objectName: "changeLanguageKey"
+ functionKey: true
+ highlighted: true
+ displayText: keyboard.locale.split("_")[0]
+ keyPanelDelegate: keyboard.style ? keyboard.style.languageKeyPanel : undefined
+ onClicked: keyboard.doKeyboardFunction(QtVirtualKeyboard.ChangeLanguage, customLayoutsOnly)
+ enabled: keyboard.isKeyboardFunctionAvailable(QtVirtualKeyboard.ChangeLanguage, customLayoutsOnly)
+}
diff --git a/src/components/CharacterPreviewBubble.qml b/src/components/CharacterPreviewBubble.qml
new file mode 100644
index 00000000..ad0cef5a
--- /dev/null
+++ b/src/components/CharacterPreviewBubble.qml
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+Item {
+ property bool active
+ property Item activeKey: keyboard.activeKey
+
+ visible: active && activeKey !== undefined && activeKey !== null && activeKey.showPreview
+ z: 1
+
+ Loader {
+ id: characterPreview
+ anchors.fill: parent
+ sourceComponent: keyboard.style.characterPreviewDelegate
+ }
+
+ onActiveKeyChanged: {
+ if (characterPreview.item !== null) {
+ if (!activeKey) {
+ characterPreview.item.text = ""
+ return
+ }
+
+ characterPreview.item.text = Qt.binding(function() {
+ if (!activeKey)
+ return ""
+ var displayText = (activeKey.keyType === QtVirtualKeyboard.FlickKey) ? activeKey.text : activeKey.displayText
+ return InputContext.uppercase ? displayText.toUpperCase() : displayText
+ })
+ if (activeKey.keyType === QtVirtualKeyboard.FlickKey) {
+ if (characterPreview.item.hasOwnProperty("flickLeft")) {
+ characterPreview.item.flickLeft = activeKey.flickLeft
+ characterPreview.item.flickRight = activeKey.flickRight
+ characterPreview.item.flickTop = activeKey.flickTop
+ characterPreview.item.flickBottom = activeKey.flickBottom
+ }
+ } else {
+ if (characterPreview.item.hasOwnProperty("flickLeft")) {
+ characterPreview.item.flickLeft = ""
+ characterPreview.item.flickRight = ""
+ characterPreview.item.flickTop = ""
+ characterPreview.item.flickBottom = ""
+ }
+ }
+ width = activeKey.width
+ height = activeKey.height
+ var position = keyboard.mapFromItem(activeKey, 0, 0)
+ x = position.x
+ y = position.y - height - keyboard.style.characterPreviewMargin
+ }
+ }
+}
diff --git a/src/components/EnterKey.qml b/src/components/EnterKey.qml
new file mode 100644
index 00000000..f3ade36e
--- /dev/null
+++ b/src/components/EnterKey.qml
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype EnterKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits BaseKey
+
+ \brief Enter key for keyboard layouts.
+
+ Sends an enter key for input method processing.
+*/
+
+BaseKey {
+ /*! This property holds the action id for the enter key.
+
+ */
+ readonly property int actionId: InputContext.priv.hasEnterKeyAction(InputContext.priv.inputItem) ? InputContext.priv.inputItem.EnterKeyAction.actionId : EnterKeyAction.None
+
+ keyType: QtVirtualKeyboard.EnterKey
+ text: "\n"
+ displayText: InputContext.priv.hasEnterKeyAction(InputContext.priv.inputItem) ? InputContext.priv.inputItem.EnterKeyAction.label : ""
+ key: Qt.Key_Return
+ showPreview: false
+ highlighted: true
+ enabled: InputContext.priv.hasEnterKeyAction(InputContext.priv.inputItem) ? InputContext.priv.inputItem.EnterKeyAction.enabled : true
+ keyPanelDelegate: keyboard.style ? keyboard.style.enterKeyPanel : undefined
+}
diff --git a/src/components/FillerKey.qml b/src/components/FillerKey.qml
new file mode 100644
index 00000000..a6c2a402
--- /dev/null
+++ b/src/components/FillerKey.qml
@@ -0,0 +1,47 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype FillerKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits BaseKey
+
+ \brief Filler key for keyboard layouts.
+
+ This key can be used as a filler in the keyboard layout.
+*/
+
+BaseKey {
+ keyType: QtVirtualKeyboard.FillerKey
+ showPreview: false
+}
diff --git a/src/components/FlickKey.qml b/src/components/FlickKey.qml
new file mode 100644
index 00000000..48108f00
--- /dev/null
+++ b/src/components/FlickKey.qml
@@ -0,0 +1,120 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype FlickKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits Key
+ \since QtQuick.VirtualKeyboard 6.1
+
+ \brief Flick key for keyboard layouts.
+
+ Allows to enter an alternative character in a four-way gesture.
+ Characters are taken from the alternate keys starting with the
+ key at index \c 0 (excluding the main key text) and the positions
+ are filled in the following order: left, top, bottom, right.
+*/
+
+Key {
+
+ property int __key
+ property string __text
+ property point pt1
+ readonly property real __centerRadius: width * 0.4
+ readonly property var flickKeys: {
+ var keys = InputContext.uppercase ? alternativeKeys.toUpperCase() : alternativeKeys.toLowerCase()
+ var textIndex = keys.indexOf(InputContext.uppercase ? __text.toUpperCase() : __text.toLowerCase())
+ if (textIndex === -1)
+ return keys
+ return keys.slice(0, textIndex).concat(keys.slice(textIndex + 1))
+ }
+ property string flickLeft: flickKeys.length > 0 ? flickKeys[0] : ""
+ property string flickTop: flickKeys.length > 2 ? flickKeys[1] : ""
+ property string flickBottom: flickKeys.length > 3 ? flickKeys[3] : (flickKeys.length > 2 ? flickKeys[2] : "")
+ property string flickRight: flickKeys.length > 3 ? flickKeys[2] : (flickKeys.length === 2 ? flickKeys[1] : "")
+
+ keyType: QtVirtualKeyboard.FlickKey
+
+ Component.onCompleted: {
+ __key = key
+ __text = text
+ }
+
+ onActiveChanged: {
+ key = __key
+ text = __text
+ }
+
+ function __angle(pt2) {
+ var dx = pt2.x - pt1.x
+ var dy = pt2.y - pt1.y
+ var theta = Math.atan2(-dy, dx) * 360 / (2 * Math.PI)
+ var theta_normalized = theta < 0 ? theta + 360 : theta
+ return theta_normalized >= 360 ? 0 : theta_normalized
+ }
+
+ function __distance(pt2) {
+ var dx = pt2.x - pt1.x
+ dx = dx * dx
+ var dy = pt2.y - pt1.y
+ dy = dy * dy
+ return Math.sqrt(dx + dy)
+ }
+
+ function press(x, y) {
+ pt1 = Qt.point(x, y)
+ }
+
+ function update(x, y) {
+ var pt = Qt.point(x, y)
+ var distance = __distance(pt)
+ if (distance < __centerRadius) {
+ return
+ }
+ var currentText
+ var angle = __angle(pt)
+ if (angle < 45 || angle > 315) {
+ currentText = flickRight
+ } else if (angle < 135) {
+ currentText = flickTop
+ } else if (angle < 225) {
+ currentText = flickLeft
+ } else {
+ currentText = flickBottom
+ }
+ if (currentText.length === 1 && text !== currentText) {
+ key = currentText.toUpperCase().charCodeAt(0)
+ text = currentText
+ }
+ }
+}
diff --git a/src/components/FunctionPopupList.qml b/src/components/FunctionPopupList.qml
new file mode 100644
index 00000000..c3799f02
--- /dev/null
+++ b/src/components/FunctionPopupList.qml
@@ -0,0 +1,133 @@
+/****************************************************************************
+**
+** Copyright (C) 2021 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
+import QtQuick.VirtualKeyboard
+
+Item {
+ property bool active
+ property alias listView: listView
+ property point origin
+ signal clicked
+ LayoutMirroring.enabled: false
+ LayoutMirroring.childrenInherit: true
+
+ z: 1
+ visible: active
+ anchors.fill: parent
+
+ ListModel {
+ id: listModel
+ }
+
+ ListView {
+ id: listView
+ spacing: 0
+ model: listModel
+ currentIndex: -1
+ delegate: keyboard.style.functionPopupListDelegate
+ highlight: keyboard.style.functionPopupListHighlight ? keyboard.style.functionPopupListHighlight : defaultHighlight
+ highlightMoveDuration: 0
+ highlightResizeDuration: 0
+ keyNavigationWraps: true
+ orientation: ListView.Horizontal
+ width: contentItem.childrenRect.width
+ height: contentItem.childrenRect.height
+ x: {
+ var result = origin.x
+ if (count > 0) {
+ const item = itemAtIndex(0)
+ if (item) {
+ result -= Math.round(item.width / 2)
+ }
+ }
+ return result
+ }
+ y: origin.y - height
+ Component {
+ id: defaultHighlight
+ Item {}
+ }
+ }
+
+ Loader {
+ id: backgroundLoader
+ sourceComponent: keyboard.style.functionPopupListBackground
+ anchors.fill: listView
+ z: -1
+ Binding {
+ target: backgroundLoader.item
+ property: "view"
+ value: listView
+ when: backgroundLoader.item !== null && backgroundLoader.item.hasOwnProperty("view")
+ }
+ }
+
+ onClicked: {
+ if (active && listView.currentIndex >= 0 && listView.currentIndex < listView.model.count) {
+ const listElement = listView.model.get(listView.currentIndex)
+ keyboard.doKeyboardFunction(listElement.keyboardFunction)
+ }
+ }
+
+ function open(key, originX, originY) {
+ listModel.clear()
+ for (const keyboardFunction of [
+ QtVirtualKeyboard.HideInputPanel,
+ QtVirtualKeyboard.ChangeLanguage,
+ QtVirtualKeyboard.ToggleHandwritingMode,
+ ]) {
+ if (keyboard.isKeyboardFunctionAvailable(keyboardFunction)) {
+ const listElement = {
+ keyboardFunction: keyboardFunction
+ }
+ listModel.append(listElement)
+ }
+ }
+ origin = Qt.binding(function() {
+ return Qt.point(Math.min(Math.max(0, originX), width - listView.width), originY)
+ })
+ listView.currentIndex = (listModel.count > 0) ? 0 : -1
+ active = listView.currentIndex !== -1
+ return active
+ }
+
+ function move(pt) {
+ var listPt = mapToItem(listView, pt.x, pt.y)
+ var newIndex = listView.indexAt(listPt.x, Math.max(1, Math.min(listView.height - 1, listPt.y)))
+ if (newIndex !== listView.currentIndex) {
+ listView.currentIndex = newIndex
+ }
+ }
+
+ function close() {
+ listView.currentIndex = -1
+ active = false
+ }
+}
diff --git a/src/components/HandwritingModeKey.qml b/src/components/HandwritingModeKey.qml
new file mode 100644
index 00000000..946b9502
--- /dev/null
+++ b/src/components/HandwritingModeKey.qml
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype HandwritingModeKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits Key
+ \since QtQuick.VirtualKeyboard 2.0
+
+ \brief Hand writing mode key for keyboard layouts.
+
+ This key toggles between the handwriting mode layout and the main layout.
+
+ The key is automatically hidden from the keyboard layout if handwriting support
+ is not enabled for the virtual keyboard.
+*/
+
+Key {
+ keyType: QtVirtualKeyboard.HandwritingModeKey
+ key: Qt.Key_Context2
+ displayText: "HWR"
+ functionKey: true
+ highlighted: true
+ visible: keyboard.isKeyboardFunctionAvailable(QtVirtualKeyboard.ToggleHandwritingMode)
+ onClicked: keyboard.doKeyboardFunction(QtVirtualKeyboard.ToggleHandwritingMode)
+ keyPanelDelegate: keyboard.style ? keyboard.style.handwritingKeyPanel : undefined
+}
diff --git a/src/components/HideKeyboardKey.qml b/src/components/HideKeyboardKey.qml
new file mode 100644
index 00000000..504fbc3a
--- /dev/null
+++ b/src/components/HideKeyboardKey.qml
@@ -0,0 +1,50 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype HideKeyboardKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits BaseKey
+
+ \brief Hide keyboard key for keyboard layouts.
+
+ This key hides the keyboard from the user when pressed.
+*/
+
+BaseKey {
+ keyType: QtVirtualKeyboard.HideKeyboardKey
+ functionKey: true
+ highlighted: true
+ onClicked: keyboard.doKeyboardFunction(QtVirtualKeyboard.HideInputPanel)
+ keyPanelDelegate: keyboard.style ? keyboard.style.hideKeyPanel : undefined
+}
diff --git a/src/components/InputModeKey.qml b/src/components/InputModeKey.qml
new file mode 100644
index 00000000..142c8866
--- /dev/null
+++ b/src/components/InputModeKey.qml
@@ -0,0 +1,127 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype InputModeKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits Key
+ \since QtQuick.VirtualKeyboard 2.3
+
+ \brief Input mode key for keyboard layouts.
+
+ This key toggles between available \l {QVirtualKeyboardInputEngine::inputModes} {InputEngine.inputModes}.
+*/
+
+Key {
+ keyType: QtVirtualKeyboard.InputModeKey
+ key: Qt.Key_Mode_switch
+ noKeyEvent: true
+ functionKey: true
+ highlighted: true
+ text: InputContext.inputEngine.inputMode < inputModeNameList.length ?
+ inputModeNameList[InputContext.inputEngine.inputMode] : "ABC"
+ onClicked: InputContext.inputEngine.inputMode = __nextInputMode(InputContext.inputEngine.inputMode)
+ keyPanelDelegate: keyboard.style ? keyboard.style.symbolKeyPanel : undefined
+ enabled: inputModeCount > 1
+
+ /*!
+ List of input mode names.
+
+ The default list contains all known input modes for \l {QVirtualKeyboardInputEngine::inputMode} {InputEngine.inputMode}.
+ */
+ property var inputModeNameList: [
+ "ABC", // InputEngine.InputMode.Latin
+ "123", // InputEngine.InputMode.Numeric
+ "123", // InputEngine.InputMode.Dialable
+ "拼音", // InputEngine.InputMode.Pinyin
+ "倉頡", // InputEngine.InputMode.Cangjie
+ "注音", // InputEngine.InputMode.Zhuyin
+ "한글", // InputEngine.InputMode.Hangul
+ "かな", // InputEngine.InputMode.Hiragana
+ "カナ", // InputEngine.InputMode.Katakana
+ "全角", // InputEngine.InputMode.FullwidthLatin
+ "ΑΒΓ", // InputEngine.InputMode.Greek
+ "АБВ", // InputEngine.InputMode.Cyrillic
+ "\u0623\u200C\u0628\u200C\u062C", // InputEngine.InputMode.Arabic
+ "\u05D0\u05D1\u05D2", // InputEngine.InputMode.Hebrew
+ "中文", // InputEngine.InputMode.ChineseHandwriting
+ "日本語", // InputEngine.InputMode.JapaneseHandwriting
+ "한국어", // InputEngine.InputMode.KoreanHandwriting
+ "กขค", // InputEngine.InputMode.Thai
+ "笔画", // InputEngine.InputMode.Stroke
+ "ABC", // InputEngine.InputMode.Romaji
+ ]
+
+ /*!
+ List of input modes to toggle.
+
+ This property allows to define a custom list of input modes to
+ toggle.
+
+ The default list contains all the available input modes.
+ */
+ property var inputModes: InputContext.inputEngine.inputModes
+
+ /*!
+ This read-only property reflects the actual number of input modes
+ the user can cycle through this key.
+ */
+ readonly property int inputModeCount: __inputModes !== undefined ? __inputModes.length : 0
+
+ property var __inputModes: __filterInputModes([].concat(InputContext.inputEngine.inputModes), inputModes)
+
+ onInputModesChanged: {
+ // Check that the current input mode is included in our list
+ if (keyboard.active && InputContext.inputEngine.inputMode !== -1 &&
+ __inputModes !== undefined && __inputModes.length > 0 &&
+ __inputModes.indexOf(InputContext.inputEngine.inputMode) === -1)
+ InputContext.inputEngine.inputMode = __inputModes[0]
+ }
+
+ function __nextInputMode(inputMode) {
+ if (!enabled)
+ return inputMode
+ var inputModeIndex = __inputModes.indexOf(inputMode) + 1
+ if (inputModeIndex >= __inputModes.length)
+ inputModeIndex = 0
+ return __inputModes[inputModeIndex]
+ }
+
+ function __filterInputModes(inputModes, filter) {
+ for (var i = 0; i < inputModes.length; i++) {
+ if (filter.indexOf(inputModes[i]) === -1)
+ inputModes.splice(i, 1)
+ }
+ return inputModes
+ }
+}
diff --git a/src/components/Key.qml b/src/components/Key.qml
new file mode 100644
index 00000000..a1666f3a
--- /dev/null
+++ b/src/components/Key.qml
@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype Key
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits BaseKey
+
+ \brief Regular character key for keyboard layouts.
+
+ This key emits the key code and key text for input method processing.
+*/
+
+BaseKey {
+ id: keyItem
+ keyType: QtVirtualKeyboard.Key
+ key: !functionKey && text.length > 0 ? text.toUpperCase().charCodeAt(0) : Qt.Key_unknown
+ keyPanelDelegate: keyboard.style ? keyboard.style.keyPanel : undefined
+}
diff --git a/src/components/Keyboard.qml b/src/components/Keyboard.qml
new file mode 100644
index 00000000..2c03f28e
--- /dev/null
+++ b/src/components/Keyboard.qml
@@ -0,0 +1,1828 @@
+/****************************************************************************
+**
+** 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
+// 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 Item activeKey: null
+ property TouchPoint activeTouchPoint
+ property int localeIndex: -1
+ property var availableLocaleIndices: []
+ property var availableCustomLocaleIndices: []
+ property string locale: localeIndex >= 0 && localeIndex < layoutsModel.count ? layoutsModel.get(localeIndex, "fileName") : ""
+ property string inputLocale
+ property int defaultLocaleIndex: -1
+ readonly property bool latinOnly: InputContext.inputMethodHints & (Qt.ImhLatinOnly | Qt.ImhEmailCharactersOnly | Qt.ImhUrlCharactersOnly)
+ readonly property bool preferNumbers: InputContext.inputMethodHints & Qt.ImhPreferNumbers
+ readonly property bool dialableCharactersOnly: InputContext.inputMethodHints & Qt.ImhDialableCharactersOnly
+ readonly property bool formattedNumbersOnly: InputContext.inputMethodHints & Qt.ImhFormattedNumbersOnly
+ readonly property bool digitsOnly: InputContext.inputMethodHints & Qt.ImhDigitsOnly
+ property string layout
+ property string layoutType: {
+ if (keyboard.handwritingMode) return "handwriting"
+ if (keyboard.dialableCharactersOnly) return "dialpad"
+ if (keyboard.formattedNumbersOnly) return "numbers"
+ if (keyboard.digitsOnly) return "digits"
+ if (keyboard.symbolMode) return "symbols"
+ return "main"
+ }
+ property bool active: Qt.inputMethod.visible
+ property bool handwritingMode
+ property bool fullScreenHandwritingMode
+ property bool symbolMode
+ property bool fullScreenMode: VirtualKeyboardSettings.fullScreenMode
+ property var defaultInputMethod: initDefaultInputMethod()
+ property var plainInputMethod: PlainInputMethod {}
+ property var customInputMethod: null
+ property var customInputMethodSharedLayouts: []
+ property int defaultInputMode: InputEngine.InputMode.Latin
+ property bool inputMethodNeedsReset: true
+ property bool inputModeNeedsReset: true
+ property bool navigationModeActive: false
+ readonly property bool languagePopupListActive: languagePopupList.enabled
+ property alias soundEffect: soundEffect
+ property alias keyboardLayoutLoader: keyboardLayoutLoader
+
+ 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 + (VirtualKeyboardSettings.wordCandidateList.alwaysVisible ? wordCandidateView.height : 0)
+ onActiveChanged: {
+ hideLanguagePopup()
+ if (active && symbolMode && !preferNumbers)
+ symbolMode = false
+ keyboardInputArea.reset()
+ wordCandidateViewAutoHideTimer.stop()
+ }
+ onActiveKeyChanged: {
+ if (InputContext.inputEngine.activeKey !== Qt.Key_unknown)
+ InputContext.inputEngine.virtualKeyCancel()
+ }
+ Connections {
+ target: VirtualKeyboardSettings
+ function onLocaleChanged() {
+ updateDefaultLocale()
+ localeIndex = defaultLocaleIndex
+ }
+ function onActiveLocalesChanged() {
+ updateDefaultLocale()
+ if (!isValidLocale(localeIndex) || VirtualKeyboardSettings.locale)
+ localeIndex = defaultLocaleIndex
+ }
+ function onFullScreenModeChanged() {
+ wordCandidateView.disableAnimation = VirtualKeyboardSettings.fullScreenMode
+ keyboard.fullScreenMode = VirtualKeyboardSettings.fullScreenMode
+ }
+ function onDefaultInputMethodDisabledChanged() {
+ updateInputMethod()
+ }
+ }
+ onAvailableLocaleIndicesChanged: hideLanguagePopup()
+ onAvailableCustomLocaleIndicesChanged: hideLanguagePopup()
+ onLocaleChanged: {
+ hideLanguagePopup()
+ inputMethodNeedsReset = true
+ inputModeNeedsReset = true
+ updateLayout()
+ }
+ onInputLocaleChanged: {
+ if (Qt.locale(inputLocale).name !== "C")
+ InputContext.priv.locale = inputLocale
+ }
+ onLayoutChanged: hideLanguagePopup()
+ onLayoutTypeChanged: {
+ updateAvailableLocaleIndices()
+ updateLayout()
+ }
+ onLatinOnlyChanged: inputModeNeedsReset = true
+ onPreferNumbersChanged: {
+ keyboard.symbolMode = !keyboard.handwritingMode && preferNumbers
+ inputModeNeedsReset = true
+ }
+ onDialableCharactersOnlyChanged: inputModeNeedsReset = true
+ onFormattedNumbersOnlyChanged: inputModeNeedsReset = true
+ onDigitsOnlyChanged: inputModeNeedsReset = true
+ onHandwritingModeChanged: if (!keyboard.handwritingMode) keyboard.fullScreenHandwritingMode = false
+ onFullScreenHandwritingModeChanged: if (keyboard.fullScreenHandwritingMode) keyboard.handwritingMode = true
+ onLanguagePopupListActiveChanged: {
+ if (languagePopupListActive && navigationModeActive)
+ keyboardInputArea.initialKey = null
+ }
+
+ Connections {
+ target: InputContext
+ function onInputMethodHintsChanged() {
+ if (InputContext.priv.focus)
+ updateInputMethod()
+ }
+ }
+ Connections {
+ target: InputContext.priv
+ function onInputItemChanged() {
+ keyboard.hideLanguagePopup()
+ if (active && symbolMode && !preferNumbers)
+ symbolMode = false
+ }
+ function onFocusChanged() {
+ if (InputContext.priv.focus)
+ updateInputMethod()
+ }
+ function onNavigationKeyPressed(key, isAutoRepeat) {
+ var initialKey
+ var direction = wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight ? 1 : -1
+ switch (key) {
+ case Qt.Key_Left:
+ if (keyboard.navigationModeActive && !keyboardInputArea.initialKey) {
+ if (languagePopupListActive) {
+ hideLanguagePopup()
+ keyboardInputArea.setActiveKey(null)
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ break
+ }
+ if (alternativeKeys.active) {
+ if (alternativeKeys.listView.currentIndex > 0) {
+ alternativeKeys.listView.decrementCurrentIndex()
+ } else {
+ alternativeKeys.close()
+ keyboardInputArea.setActiveKey(null)
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ }
+ break
+ }
+ if (functionPopupList.active) {
+ if (functionPopupList.listView.currentIndex > 0) {
+ functionPopupList.listView.decrementCurrentIndex()
+ } else {
+ functionPopupList.close()
+ keyboardInputArea.setActiveKey(null)
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ }
+ break
+ }
+ if (wordCandidateContextMenu.active) {
+ hideWordCandidateContextMenu()
+ break
+ }
+ if (wordCandidateView.count) {
+ if (wordCandidateView.effectiveLayoutDirection == Qt.LeftToRight &&
+ wordCandidateView.currentIndex > 0) {
+ wordCandidateView.decrementCurrentIndex()
+ } else if (wordCandidateView.effectiveLayoutDirection == Qt.RightToLeft &&
+ wordCandidateView.currentIndex + 1 < wordCandidateView.count) {
+ wordCandidateView.incrementCurrentIndex()
+ } else {
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ initialKey = keyboardInputArea.initialKey
+ while (keyboardInputArea.navigateToNextKey(0, 1 * direction, false))
+ initialKey = keyboardInputArea.initialKey
+ while (keyboardInputArea.navigateToNextKey(1, 0, false))
+ initialKey = keyboardInputArea.initialKey
+ keyboardInputArea.initialKey = initialKey
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ }
+ break
+ }
+ }
+ initialKey = keyboardInputArea.initialKey
+ if (!keyboardInputArea.navigateToNextKey(-1, 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 (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, 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 (functionPopupList.active) {
+ functionPopupList.close()
+ keyboardInputArea.setActiveKey(null)
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ } else if (wordCandidateContextMenu.active) {
+ if (wordCandidateContextMenuList.currentIndex + 1 < wordCandidateContextMenuList.count) {
+ wordCandidateContextMenuList.incrementCurrentIndex()
+ } else if (wordCandidateContextMenuList.keyNavigationWraps && wordCandidateContextMenuList.count > 1) {
+ wordCandidateContextMenuList.currentIndex = 0
+ } else {
+ hideWordCandidateContextMenu()
+ keyboardInputArea.setActiveKey(null)
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ }
+ } else if (keyboard.navigationModeActive && !keyboardInputArea.initialKey && wordCandidateView.count) {
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ initialKey = keyboardInputArea.initialKey
+ if (!keyboardInputArea.navigateToNextKey(0, 1, false)) {
+ keyboardInputArea.initialKey = initialKey
+ keyboardInputArea.navigateToNextKey(0, 1, true)
+ } else {
+ keyboardInputArea.navigateToNextKey(0, -1, false)
+ }
+ } else if (!keyboardInputArea.navigateToNextKey(0, 1, !keyboard.navigationModeActive || !keyboardInputArea.initialKey || wordCandidateView.count == 0)) {
+ if (wordCandidateView.currentIndex === -1)
+ wordCandidateView.incrementCurrentIndex()
+ }
+ break
+ case Qt.Key_Return:
+ if (!keyboard.navigationModeActive)
+ break
+ if (languagePopupListActive) {
+ if (!isAutoRepeat) {
+ languagePopupList.model.selectItem(languagePopupList.currentIndex)
+ keyboardInputArea.reset()
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ }
+ } else if (keyboardInputArea.initialKey) {
+ if (!isAutoRepeat) {
+ pressAndHoldTimer.restart()
+ keyboardInputArea.setActiveKey(keyboardInputArea.initialKey)
+ keyboardInputArea.press(keyboardInputArea.initialKey, true)
+ }
+ } else if (!wordCandidateContextMenu.active && wordCandidateView.count > 0) {
+ if (!isAutoRepeat) {
+ pressAndHoldTimer.restart()
+ }
+ }
+ break
+ default:
+ break
+ }
+ }
+ function onNavigationKeyReleased(key, isAutoRepeat) {
+ switch (key) {
+ case Qt.Key_Return:
+ if (!keyboard.navigationModeActive) {
+ if (languagePopupListActive)
+ languagePopupList.model.selectItem(languagePopupList.currentIndex)
+ break
+ }
+ if (isAutoRepeat)
+ break
+ if (!languagePopupListActive && !alternativeKeys.active && !functionPopupList.active && !wordCandidateContextMenu.active && keyboard.activeKey) {
+ keyboardInputArea.release(keyboard.activeKey)
+ pressAndHoldTimer.stop()
+ alternativeKeys.close()
+ functionPopupList.close()
+ keyboardInputArea.setActiveKey(null)
+ if (!languagePopupListActive && keyboardInputArea.navigationCursor !== Qt.point(-1, -1))
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ } else if (wordCandidateContextMenu.active) {
+ if (!wordCandidateContextMenu.openedByNavigationKeyLongPress) {
+ wordCandidateContextMenu.selectCurrentItem()
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ } else {
+ wordCandidateContextMenu.openedByNavigationKeyLongPress = false
+ }
+ } else if (alternativeKeys.active) {
+ if (!alternativeKeys.openedByNavigationKeyLongPress) {
+ alternativeKeys.clicked()
+ alternativeKeys.close()
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ keyboardInputArea.reset()
+ } else {
+ alternativeKeys.openedByNavigationKeyLongPress = false
+ }
+ } else if (functionPopupList.active) {
+ if (!functionPopupList.openedByNavigationKeyLongPress) {
+ functionPopupList.clicked()
+ functionPopupList.close()
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ keyboardInputArea.reset()
+ } else {
+ functionPopupList.openedByNavigationKeyLongPress = false
+ }
+ } else if (!wordCandidateContextMenu.active && wordCandidateView.count > 0) {
+ wordCandidateView.model.selectItem(wordCandidateView.currentIndex)
+ if (!InputContext.preeditText.length)
+ keyboardInputArea.navigateToNextKey(0, 1, true)
+ }
+ break
+ default:
+ break
+ }
+ }
+ }
+ Connections {
+ target: InputContext.inputEngine
+ function onVirtualKeyClicked(key, text, modifiers, isAutoRepeat) {
+ if (isAutoRepeat && keyboard.activeKey)
+ soundEffect.play(keyboard.activeKey.soundEffect)
+ if (key !== Qt.Key_unknown && keyboardInputArea.dragSymbolMode) {
+ keyboardInputArea.dragSymbolMode = false
+ keyboard.symbolMode = false
+ } else if (key === Qt.Key_Space) {
+ var surroundingText = InputContext.surroundingText.trim()
+ if (InputContext.priv.shiftHandler.sentenceEndingCharacters.indexOf(surroundingText.charAt(surroundingText.length-1)) >= 0)
+ keyboard.symbolMode = false
+ }
+ }
+ }
+ FolderListModel {
+ id: layoutsModel
+ nameFilters: ["$"]
+ folder: VirtualKeyboardSettings.layoutPath
+ }
+ Connections {
+ target: layoutsModel
+ function onCountChanged() {
+ updateDefaultLocale()
+ localeIndex = defaultLocaleIndex
+ }
+ }
+ AlternativeKeys {
+ id: alternativeKeys
+ objectName: "alternativeKeys"
+ // Add some extra margin for decoration
+ property real horizontalMargin: style.alternateKeysListItemWidth
+ property real verticalMargin: style.alternateKeysListItemHeight
+ property rect previewRect: Qt.rect(keyboard.x + alternativeKeys.listView.x - horizontalMargin,
+ keyboard.y + alternativeKeys.listView.y - verticalMargin,
+ alternativeKeys.listView.width + horizontalMargin * 2,
+ alternativeKeys.listView.height + verticalMargin * 2)
+ property bool openedByNavigationKeyLongPress
+ onVisibleChanged: {
+ if (visible)
+ InputContext.priv.previewRectangle = Qt.binding(function() {return previewRect})
+ else
+ openedByNavigationKeyLongPress = false
+ InputContext.priv.previewVisible = visible
+ }
+ }
+ FunctionPopupList {
+ id: functionPopupList
+ property bool openedByNavigationKeyLongPress
+ }
+ Timer {
+ id: pressAndHoldTimer
+ interval: 500
+ onTriggered: {
+ if (keyboard.activeKey && keyboard.activeKey === keyboardInputArea.initialKey) {
+ var origin = keyboard.mapFromItem(activeKey, activeKey.width / 2, 0)
+ if (keyboard.activeKey.smallText === "\u2699" &&
+ functionPopupList.open(keyboard.activeKey, origin.x, origin.y)) {
+ InputContext.inputEngine.virtualKeyCancel()
+ keyboardInputArea.initialKey = null
+ functionPopupList.openedByNavigationKeyLongPress = keyboard.navigationModeActive
+ } else if (alternativeKeys.open(keyboard.activeKey, origin.x, origin.y)) {
+ InputContext.inputEngine.virtualKeyCancel()
+ keyboardInputArea.initialKey = null
+ alternativeKeys.openedByNavigationKeyLongPress = keyboard.navigationModeActive
+ } else if (keyboard.activeKey.key === Qt.Key_Context1 && !keyboard.symbolMode) {
+ InputContext.inputEngine.virtualKeyCancel()
+ keyboardInputArea.dragSymbolMode = true
+ keyboard.symbolMode = true
+ keyboardInputArea.initialKey = null
+ if (keyboardInputArea.navigationCursor !== Qt.point(-1, -1))
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ }
+ } else if (keyboardInputArea.dragSymbolMode &&
+ keyboard.activeKey &&
+ keyboard.activeKey.functionKey &&
+ !keyboard.activeKey.repeat) {
+ InputContext.inputEngine.virtualKeyCancel()
+ keyboardInputArea.click(keyboard.activeKey)
+ keyboardInputArea.initialKey = null
+ if (keyboardInputArea.navigationCursor !== Qt.point(-1, -1))
+ keyboardInputArea.navigateToNextKey(0, 0, false)
+ } else if (!wordCandidateContextMenu.active && keyboard.navigationModeActive) {
+ wordCandidateContextMenu.show(wordCandidateView.currentIndex)
+ wordCandidateContextMenu.openedByNavigationKeyLongPress = keyboard.navigationModeActive
+ }
+ }
+ }
+ Timer {
+ id: releaseInaccuracyTimer
+ interval: 500
+ onTriggered: {
+ if (keyboardInputArea.pressed && activeTouchPoint && !alternativeKeys.active && !keyboardInputArea.dragSymbolMode && !functionPopupList.active) {
+ var key = keyboardInputArea.keyOnPoint(activeTouchPoint.x, activeTouchPoint.y)
+ if (key !== keyboard.activeKey) {
+ InputContext.inputEngine.virtualKeyCancel()
+ keyboardInputArea.setActiveKey(key)
+ keyboardInputArea.press(key, false)
+ }
+ }
+ }
+ }
+ CharacterPreviewBubble {
+ id: characterPreview
+ objectName: "characterPreviewBubble"
+ active: keyboardInputArea.pressed && !alternativeKeys.active && !functionPopupList.active
+ property rect previewRect: Qt.rect(keyboard.x + characterPreview.x,
+ keyboard.y + characterPreview.y,
+ characterPreview.width,
+ characterPreview.height)
+ }
+ Binding {
+ target: InputContext.priv
+ property: "previewRectangle"
+ value: characterPreview.previewRect
+ when: characterPreview.visible
+ restoreMode: Binding.RestoreBinding
+ }
+ Binding {
+ target: InputContext.priv
+ property: "previewRectangle"
+ value: languagePopupList.previewRect
+ when: languagePopupListActive
+ restoreMode: Binding.RestoreBinding
+ }
+ Binding {
+ target: InputContext.priv
+ property: "previewVisible"
+ value: characterPreview.visible || languagePopupListActive
+ restoreMode: Binding.RestoreBinding
+ }
+ Loader {
+ id: styleLoader
+ source: VirtualKeyboardSettings.style
+ Binding {
+ target: styleLoader.item
+ property: "keyboardHeight"
+ value: keyboardInnerContainer.height
+ restoreMode: Binding.RestoreBinding
+ }
+ }
+ Loader {
+ id: naviationHighlight
+ objectName: "naviationHighlight"
+ property var highlightItem: {
+ if (keyboard.navigationModeActive) {
+ if (languagePopupListActive) {
+ return languagePopupList.highlightItem
+ } else if (keyboardInputArea.initialKey) {
+ return keyboardInputArea.initialKey
+ } else if (alternativeKeys.listView.count > 0) {
+ return alternativeKeys.listView.highlightItem
+ } else if (functionPopupList.listView.count > 0) {
+ return functionPopupList.listView.highlightItem
+ } else if (wordCandidateContextMenu.active) {
+ return wordCandidateContextMenuList.highlightItem
+ } else if (wordCandidateView.count > 0) {
+ return wordCandidateView.highlightItem
+ }
+ }
+ return keyboard
+ }
+ // Note: without "highlightItem.x - highlightItem.x" the binding does not work for alternativeKeys
+ property var highlightItemOffset: highlightItem ? keyboard.mapFromItem(highlightItem, highlightItem.x - highlightItem.x, highlightItem.y - highlightItem.y) : ({x:0, y:0})
+ property int moveDuration: 200
+ property int resizeDuration: 200
+ property alias xAnimation: xAnimation
+ property alias yAnimation: yAnimation
+ property alias widthAnimation: widthAnimation
+ property alias heightAnimation: heightAnimation
+ z: 2
+ x: highlightItemOffset.x
+ y: highlightItemOffset.y
+ width: highlightItem ? highlightItem.width : 0
+ height: highlightItem ? highlightItem.height : 0
+ visible: keyboard.navigationModeActive && highlightItem !== null && highlightItem !== keyboard
+ sourceComponent: keyboard.style.navigationHighlight
+ Behavior on x {
+ NumberAnimation { id: xAnimation; duration: naviationHighlight.moveDuration; easing.type: Easing.OutCubic }
+ }
+ Behavior on y {
+ NumberAnimation { id: yAnimation; duration: naviationHighlight.moveDuration; easing.type: Easing.OutCubic }
+ }
+ Behavior on width {
+ NumberAnimation { id: widthAnimation; duration: naviationHighlight.resizeDuration; easing.type: Easing.OutCubic }
+ }
+ Behavior on height {
+ NumberAnimation { id: heightAnimation; duration: naviationHighlight.resizeDuration; easing.type: Easing.OutCubic }
+ }
+ }
+
+ ShadowInputControl {
+ id: shadowInputControl
+ objectName: "shadowInputControl"
+ z: -3
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: wordCandidateView.top
+ height: (keyboard.parent.parent ? keyboard.parent.parent.height : Screen.height) -
+ keyboard.height - (wordCandidateView.visibleCondition && !VirtualKeyboardSettings.wordCandidateList.alwaysVisible ? wordCandidateView.height : 0)
+ visible: fullScreenMode && (shadowInputControlVisibleTimer.running || InputContext.animating)
+
+ Connections {
+ target: keyboard
+ function onActiveChanged() {
+ if (keyboard.active)
+ shadowInputControlVisibleTimer.start()
+ else
+ shadowInputControlVisibleTimer.stop()
+ }
+ }
+
+ Timer {
+ id: shadowInputControlVisibleTimer
+ interval: 2147483647
+ repeat: true
+ }
+
+ MouseArea {
+ onPressed: keyboard.hideLanguagePopup()
+ anchors.fill: parent
+ enabled: languagePopupList.enabled
+ }
+ }
+
+ SelectionControl {
+ objectName: "fullScreenModeSelectionControl"
+ inputContext: InputContext.priv.shadow
+ anchors.top: shadowInputControl.top
+ anchors.left: shadowInputControl.left
+ enabled: keyboard.enabled && fullScreenMode
+ }
+
+ ListView {
+ id: wordCandidateView
+ objectName: "wordCandidateView"
+ clip: true
+ z: -2
+ property bool disableAnimation: VirtualKeyboardSettings.fullScreenMode
+ property bool empty: true
+ readonly property bool visibleCondition: (((!wordCandidateView.empty || wordCandidateViewAutoHideTimer.running || shadowInputControl.visible) &&
+ InputContext.inputEngine.wordCandidateListVisibleHint) || VirtualKeyboardSettings.wordCandidateList.alwaysVisible) &&
+ (keyboard.active || shadowInputControl.visible)
+ readonly property real visibleYOffset: VirtualKeyboardSettings.wordCandidateList.alwaysVisible ? 0 : -height
+ readonly property real currentYOffset: visibleCondition || wordCandidateViewTransition.running ? visibleYOffset : 0
+ height: 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: style.selectionListAdd
+ remove: style.selectionListRemove
+ keyNavigationWraps: true
+ model: InputContext.inputEngine.wordCandidateListModel
+ onCurrentItemChanged: if (currentItem) soundEffect.register(currentItem.soundEffect)
+ Connections {
+ target: wordCandidateView.model ? wordCandidateView.model : null
+ function onActiveItemChanged(index) { wordCandidateView.currentIndex = index }
+ function onItemSelected() { if (wordCandidateView.currentItem) soundEffect.play(wordCandidateView.currentItem.soundEffect) }
+ function onCountChanged() {
+ var empty = wordCandidateView.model.count === 0
+ if (empty)
+ wordCandidateViewAutoHideTimer.restart()
+ else
+ wordCandidateViewAutoHideTimer.stop()
+ wordCandidateView.empty = empty
+ keyboard.hideWordCandidateContextMenu()
+ }
+ }
+ Connections {
+ target: InputContext.priv
+ function onInputItemChanged() { wordCandidateViewAutoHideTimer.stop() }
+ }
+ Connections {
+ target: InputContext.inputEngine
+ function onWordCandidateListVisibleHintChanged() { wordCandidateViewAutoHideTimer.stop() }
+ }
+ Timer {
+ id: wordCandidateViewAutoHideTimer
+ interval: VirtualKeyboardSettings.wordCandidateList.autoHideDelay
+ }
+ Loader {
+ sourceComponent: style.selectionListBackground
+ anchors.fill: parent
+ z: -1
+ }
+ Component {
+ id: defaultHighlight
+ Item {}
+ }
+ states: State {
+ name: "visible"
+ when: wordCandidateView.visibleCondition
+ PropertyChanges {
+ target: wordCandidateView
+ y: wordCandidateView.visibleYOffset
+ }
+ }
+ transitions: Transition {
+ id: wordCandidateViewTransition
+ to: "visible"
+ enabled: !InputContext.animating && !VirtualKeyboardSettings.wordCandidateList.alwaysVisible && !wordCandidateView.disableAnimation
+ reversible: true
+ ParallelAnimation {
+ NumberAnimation {
+ properties: "y"
+ duration: 250
+ easing.type: Easing.InOutQuad
+ }
+ }
+ }
+
+ function longPressItem(index) {
+ return keyboard.showWordCandidateContextMenu(index)
+ }
+ }
+
+ Item {
+ id: soundEffect
+ property var __sounds: ({})
+ property bool available: false
+
+ signal playingChanged(url source, bool playing)
+
+ Connections {
+ target: VirtualKeyboardSettings
+ function onStyleNameChanged() {
+ soundEffect.__sounds = {}
+ soundEffect.available = false
+ }
+ }
+
+ function play(sound) {
+ if (enabled && sound != Qt.resolvedUrl("")) {
+ var soundId = Qt.md5(sound)
+ var multiSoundEffect = __sounds[soundId]
+ if (!multiSoundEffect)
+ multiSoundEffect = register(sound)
+ if (multiSoundEffect)
+ multiSoundEffect.play()
+ }
+ }
+
+ function register(sound) {
+ var multiSoundEffect = null
+ if (enabled && sound != Qt.resolvedUrl("")) {
+ var soundId = Qt.md5(sound)
+ multiSoundEffect = __sounds[soundId]
+ if (!multiSoundEffect) {
+ multiSoundEffect = Qt.createQmlObject('import QtQuick; import QtQuick.VirtualKeyboard; MultiSoundEffect {}', soundEffect)
+ if (multiSoundEffect) {
+ multiSoundEffect.playingChanged.connect(soundEffect.playingChanged)
+ multiSoundEffect.source = sound
+ __sounds[soundId] = multiSoundEffect
+ available = true
+ }
+ }
+ }
+ return multiSoundEffect
+ }
+ }
+
+ Loader {
+ id: keyboardBackground
+ z: -1
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ height: keyboardInnerContainer.height
+ sourceComponent: style.keyboardBackground
+
+ Item {
+ id: keyboardInnerContainer
+ z: 1
+ width: Math.round(keyboardBackground.width)
+ height: style ? Math.round(style.keyboardDesignHeight * width / style.keyboardDesignWidth) : 0
+ anchors.horizontalCenter: parent.horizontalCenter
+ LayoutMirroring.enabled: false
+ LayoutMirroring.childrenInherit: true
+
+ KeyboardObserver {
+ id: keyboardObserver
+
+ function scanLayout() {
+ if (keyboardLayoutLoader.item == null)
+ return null
+
+ return keyboardLayoutLoader.item.scanLayout()
+ }
+ }
+
+ Component.onCompleted: InputContext.priv.setKeyboardObserver(keyboardObserver)
+
+ onWidthChanged: notifyLayoutChanged()
+ onHeightChanged: notifyLayoutChanged()
+
+ Loader {
+ id: keyboardLayoutLoader
+ objectName: "keyboardLayoutLoader"
+
+ anchors.fill: parent
+ anchors.leftMargin: Math.round(style.keyboardRelativeLeftMargin * parent.width)
+ anchors.rightMargin: Math.round(style.keyboardRelativeRightMargin * parent.width)
+ anchors.topMargin: Math.round(style.keyboardRelativeTopMargin * parent.height)
+ anchors.bottomMargin: Math.round(style.keyboardRelativeBottomMargin * parent.height)
+
+ Binding {
+ target: keyboardLayoutLoader
+ property: "source"
+ value: keyboard.layout
+ when: keyboard.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
+ if (item)
+ 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.FlickKey)
+ keyboard.activeKey.onKeyChanged.disconnect(onFlickKeyKeyChanged)
+ keyboard.activeKey.active = false
+ }
+ keyboard.activeKey = activeKey
+ if (keyboard.activeKey) {
+ keyboard.activeKey.active = true
+ }
+ }
+ function keyOnPoint(px, py) {
+ var parentItem = keyboardLayoutLoader
+ var child = parentItem.childAt(px, py)
+ while (child !== null) {
+ var position = parentItem.mapToItem(child, px, py)
+ px = position.x; py = position.y
+ parentItem = child
+ child = parentItem.childAt(px, py)
+ if (child && child.key !== undefined)
+ return child
+ }
+ return null
+ }
+ function hitInitialKey(x, y, margin) {
+ if (!initialKey)
+ return false
+ var position = initialKey.mapFromItem(keyboardInputArea, x, y)
+ return (position.x > -margin
+ && position.y > -margin
+ && position.x < initialKey.width + margin
+ && position.y < initialKey.height + margin)
+ }
+ function containsPoint(touchPoints, point) {
+ if (!point)
+ return false
+ for (var i in touchPoints)
+ if (touchPoints[i].pointId == point.pointId)
+ return true
+ return false
+ }
+ function releaseActiveKey() {
+ if (alternativeKeys.active) {
+ alternativeKeys.clicked()
+ } else if (functionPopupList.active) {
+ functionPopupList.clicked()
+ } else if (keyboard.activeKey) {
+ release(keyboard.activeKey)
+ }
+ reset()
+ }
+ function reset() {
+ releaseInaccuracyTimer.stop()
+ pressAndHoldTimer.stop()
+ setActiveKey(null)
+ activeTouchPoint = null
+ alternativeKeys.close()
+ functionPopupList.close()
+ if (dragSymbolMode) {
+ keyboard.symbolMode = false
+ dragSymbolMode = false
+ }
+ }
+ function nextKeyInNavigation(dX, dY, wrapEnabled) {
+ var nextKey = null, x, y, itemOffset
+ if (dX !== 0 || dY !== 0) {
+ var offsetX, offsetY
+ for (offsetX = dX, offsetY = dY;
+ Math.abs(offsetX) < width && Math.abs(offsetY) < height;
+ offsetX += dX, offsetY += dY) {
+ x = navigationCursor.x + offsetX
+ if (x < 0) {
+ if (!wrapEnabled)
+ break
+ x += width
+ } else if (x >= width) {
+ if (!wrapEnabled)
+ break
+ x -= width
+ }
+ y = navigationCursor.y + offsetY
+ if (y < 0) {
+ if (!wrapEnabled)
+ break
+ y += height
+ } else if (y >= height) {
+ if (!wrapEnabled)
+ break
+ y -= height
+ }
+ nextKey = keyOnPoint(x, y)
+ if (nextKey) {
+ // Check if key is visible. Only the visible keys have keyPanelDelegate set.
+ if (nextKey != initialKey && nextKey.hasOwnProperty("keyPanelDelegate") && nextKey.keyPanelDelegate)
+ break
+ // Jump over the item to reduce the number of iterations in this loop
+ itemOffset = mapToItem(nextKey, x, y)
+ if (dX > 0)
+ offsetX += nextKey.width - itemOffset.x
+ else if (dX < 0)
+ offsetX -= itemOffset.x
+ else if (dY > 0)
+ offsetY += nextKey.height - itemOffset.y
+ else if (dY < 0)
+ offsetY -= itemOffset.y
+ }
+ nextKey = null
+ }
+ } else {
+ nextKey = keyOnPoint(navigationCursor.x, navigationCursor.y)
+ }
+ if (nextKey) {
+ itemOffset = mapFromItem(nextKey, nextKey.width / 2, nextKey.height / 2)
+ if (dX) {
+ x = itemOffset.x
+ } else if (dY) {
+ y = itemOffset.y
+ } else {
+ x = itemOffset.x
+ y = itemOffset.y
+ }
+ navigationCursor = Qt.point(x, y)
+ }
+ return nextKey
+ }
+ function navigateToNextKey(dX, dY, wrapEnabled) {
+ // Resolve initial landing point of the navigation cursor
+ if (!keyboard.navigationModeActive || keyboard.navigationCursor === Qt.point(-1, -1)) {
+ if (dX > 0)
+ navigationCursor = Qt.point(0, height / 2)
+ else if (dX < 0)
+ navigationCursor = Qt.point(width, height / 2)
+ else if (dY > 0)
+ navigationCursor = Qt.point(width / 2, 0)
+ else if (dY < 0)
+ navigationCursor = Qt.point(width / 2, height)
+ else
+ navigationCursor = Qt.point(width / 2, height / 2)
+ keyboard.navigationModeActive = true
+ }
+ if (dX && dY) {
+ initialKey = nextKeyInNavigation(dX, 0, wrapEnabled)
+ if (initialKey || wrapEnabled)
+ initialKey = nextKeyInNavigation(0, dY, wrapEnabled)
+ } else {
+ initialKey = nextKeyInNavigation(dX, dY, wrapEnabled)
+ }
+ return initialKey !== null
+ }
+
+ function onFlickKeyKeyChanged() {
+ InputContext.inputEngine.virtualKeyCancel()
+ press(activeKey, false)
+ }
+
+ onPressed: (touchPoints) => {
+ keyboard.navigationModeActive = false
+
+ // Immediately release any pending key that the user might be
+ // holding (and about to release) when a second key is pressed.
+ if (activeTouchPoint)
+ releaseActiveKey();
+
+ for (var i in touchPoints) {
+ // Release any key pressed by a previous iteration of the loop.
+ if (containsPoint(touchPoints, activeTouchPoint))
+ releaseActiveKey();
+
+ initialKey = keyOnPoint(touchPoints[i].x, touchPoints[i].y)
+ if (!initialKey)
+ continue
+ activeTouchPoint = touchPoints[i]
+ if (initialKey.keyType === QtVirtualKeyboard.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.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.style.languageListAdd : null
+ remove: keyboard.style ? 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) {
+ 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)
+ if (parentItem) {
+ 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)})
+ } 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
+ }
+
+ 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() {
+ 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
+ }
+
+ 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.HideInputPanel:
+ InputContext.priv.hideInputPanel()
+ break
+ case QtVirtualKeyboard.ChangeLanguage:
+ if (style.languagePopupListEnabled) {
+ if (!languagePopupListActive) {
+ showLanguagePopup(activeKey, false)
+ } else {
+ hideLanguagePopup()
+ }
+ } else {
+ const customLayoutsOnly = arguments.length == 2 && arguments[1]
+ changeInputLanguage(customLayoutsOnly)
+ }
+ break
+ case QtVirtualKeyboard.ToggleHandwritingMode:
+ setHandwritingMode(!handwritingMode)
+ break
+ default:
+ console.warn("Unknown keyboard function '%1'".arg(keyboardFunction))
+ break
+ }
+ }
+
+ function isKeyboardFunctionAvailable(keyboardFunction) {
+ switch (keyboardFunction) {
+ case QtVirtualKeyboard.HideInputPanel:
+ return true
+ case QtVirtualKeyboard.ChangeLanguage:
+ const customLayoutsOnly = arguments.length == 2 && arguments[1]
+ return canChangeInputLanguage(customLayoutsOnly)
+ case QtVirtualKeyboard.ToggleHandwritingMode:
+ return isHandwritingAvailable()
+ default:
+ return false
+ }
+ }
+}
diff --git a/src/components/KeyboardColumn.qml b/src/components/KeyboardColumn.qml
new file mode 100644
index 00000000..15c2e315
--- /dev/null
+++ b/src/components/KeyboardColumn.qml
@@ -0,0 +1,63 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.Layouts
+
+/*!
+ \qmltype KeyboardColumn
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits ColumnLayout
+
+ \brief Keyboard column for keyboard layouts.
+
+ This type can be used in special cases where multiple columns
+ are added to a single keyboard layout.
+*/
+
+ColumnLayout {
+ /*! Sets the key weight for all children keys.
+
+ The default value is inherited from the parent element
+ in the layout hierarchy.
+ */
+ property real keyWeight: parent ? parent.keyWeight : undefined
+
+ /*! \since QtQuick.VirtualKeyboard 2.0
+
+ Sets the \c smallTextVisible for all children keys.
+
+ The default value is inherited from the parent element
+ in the layout hierarchy.
+ */
+ property bool smallTextVisible: parent ? parent.smallTextVisible : false
+
+ spacing: 0
+}
diff --git a/src/components/KeyboardLayout.qml b/src/components/KeyboardLayout.qml
new file mode 100644
index 00000000..14e5e8eb
--- /dev/null
+++ b/src/components/KeyboardLayout.qml
@@ -0,0 +1,188 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.Layouts
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype KeyboardLayout
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits ColumnLayout
+
+ \brief Keyboard layout.
+
+ This type is the root element of the keyboard layout.
+ Use this element to build a new keyboard layout.
+
+ Example:
+
+ \code
+ import QtQuick
+ import QtQuick.Layouts
+ import QtQuick.VirtualKeyboard
+
+ // file: layouts/en_GB/main.qml
+
+ KeyboardLayout {
+ KeyboardRow {
+ Key {
+ key: Qt.Key_Q
+ text: "q"
+ }
+ Key {
+ key: Qt.Key_W
+ text: "w"
+ }
+ Key {
+ key: Qt.Key_E
+ text: "e"
+ }
+ Key {
+ key: Qt.Key_R
+ text: "r"
+ }
+ Key {
+ key: Qt.Key_T
+ text: "t"
+ }
+ Key {
+ key: Qt.Key_Y
+ text: "y"
+ }
+ }
+ }
+ \endcode
+*/
+
+ColumnLayout {
+ id: root
+
+ /*! Sets the input method to be used in this layout.
+
+ This property allows a custom input method to be
+ used in this layout.
+ */
+ property var inputMethod
+
+ /*! This function may be overridden by the keyboard layout
+ to create the input method object dynamically. The default
+ implementation returns \c null.
+
+ The input method object created by this function can outlive
+ keyboard layout transitions in certain cases. In particular,
+ this applies to the transitions between the layouts listed in
+ the sharedLayouts property.
+ */
+ function createInputMethod() {
+ return null
+ }
+
+ /*! List of layout names which share the input method created
+ by the createInputMethod() function.
+
+ If the list is empty (the default) the input method is not
+ shared with any other layout and will be destroyed when the
+ layout changes.
+
+ The list should contain only the name of the layout type,
+ e.g., ['symbols']. The current layout does not have to be
+ included in the list.
+ */
+ property var sharedLayouts
+
+ /*! Sets the input mode to be used in this layout.
+
+ By default, the virtual keyboard attempts to preserve
+ the current input mode when switching to a different
+ keyboard layout.
+
+ If the current input mode is not valid in the current
+ context, the default input mode is specified by the
+ input method.
+ */
+ property int inputMode: -1
+
+ /*! Sets the key weight for all children keys.
+
+ The default value is inherited from the parent element
+ in the layout hierarchy.
+ */
+ property real keyWeight
+
+ /*! \since QtQuick.VirtualKeyboard 2.0
+
+ Sets the \c smallTextVisible for all children keys.
+
+ The default value is inherited from the parent element
+ in the layout hierarchy.
+ */
+ property bool smallTextVisible
+
+ spacing: 0
+
+ function scanLayout() {
+ var layout = {
+ width: root.width,
+ height: root.height,
+ keys: []
+ }
+ __scanLayoutRecursive(this, layout)
+ return layout
+ }
+
+ function __scanLayoutRecursive(parent, layout) {
+ for (var i in parent.children) {
+ var child = parent.children[i]
+ if (child.keyType !== undefined) {
+ var pos = mapFromItem(child, 0, 0)
+ var key = {
+ left: pos.x,
+ top: pos.y,
+ width: child.width,
+ height: child.height,
+ keyType: child.keyType,
+ key: child.key,
+ text: child.text,
+ altKeys: child.effectiveAlternativeKeys,
+ isFunctionKey: child.functionKey,
+ noKeyEvent: child.noKeyEvent
+ }
+ if (key.left + key.width > layout.width)
+ layout.width = key.left + key.width
+ if (key.top + key.height > layout.height)
+ layout.height = key.top + key.height
+ layout.keys.push(key)
+ } else {
+ __scanLayoutRecursive(child, layout)
+ }
+ }
+ }
+}
diff --git a/src/components/KeyboardLayoutLoader.qml b/src/components/KeyboardLayoutLoader.qml
new file mode 100644
index 00000000..f67a72e8
--- /dev/null
+++ b/src/components/KeyboardLayoutLoader.qml
@@ -0,0 +1,140 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype KeyboardLayoutLoader
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits Loader
+ \since QtQuick.VirtualKeyboard 1.1
+
+ \brief Allows dynamic loading of keyboard layout.
+
+ This type is useful for keyboard layouts consisting of multiple pages of keys.
+
+ A single keyboard layout (a page) is defined by using the Component
+ as a container. The active keyboard layout can then be changed by
+ setting the sourceComponent property to a different value.
+
+ Example:
+
+ \code
+ import QtQuick
+ import QtQuick.Layouts
+ import QtQuick.VirtualKeyboard
+
+ // file: layouts/en_GB/symbols.qml
+
+ KeyboardLayoutLoader {
+ property bool secondPage
+ onVisibleChanged: if (!visible) secondPage = false
+ sourceComponent: secondPage ? page2 : page1
+ Component {
+ id: page1
+ KeyboardLayout {
+ // Keyboard layout definition for page 1
+ }
+ }
+ Component {
+ id: page2
+ KeyboardLayout {
+ // Keyboard layout definition for page 2
+ }
+ }
+ }
+ \endcode
+*/
+
+Loader {
+ /*! Sets the input method for all the keyboard layouts loaded
+ in this context.
+
+ The input method can either be set separately for each keyboard
+ layout, or commonly at this context. If set separately, then this
+ property should not be modified.
+ */
+ property var inputMethod: item ? item.inputMethod : null
+
+ /*! This function may be overridden by the keyboard layout
+ to create the input method object dynamically. The default
+ implementation forwards the call to the child keyboard
+ layout.
+
+ The input method object created by this function can outlive
+ keyboard layout transitions in certain cases. In particular,
+ this applies to the transitions between the layouts listed in
+ the sharedLayouts property.
+ */
+ function createInputMethod() {
+ return item ? item.createInputMethod() : null
+ }
+
+ /*! List of layout names which share the input method created
+ by the createInputMethod() function.
+
+ If the list is empty (the default) the input method is not
+ shared with any other layout and will be destroyed when the
+ layout changes.
+
+ The list should contain only the name of the layout type,
+ e.g., ['symbols']. The current layout does not have to be
+ included in the list.
+ */
+ property var sharedLayouts: item ? item.sharedLayouts : null
+
+ /*! Sets the input mode for all the keyboard layouts loaded
+ in this context.
+
+ The input mode can either be set separately for each keyboard
+ layout, or commonly at this context. If set separately, then this
+ property should not be modified.
+ */
+ property int inputMode: item ? item.inputMode : -1
+
+ property int __updateCount
+
+ active: parent !== null
+
+ onItemChanged: {
+ if (parent && item && __updateCount++ > 0) {
+ if (!keyboard.inputMethodNeedsReset)
+ keyboard.updateInputMethod()
+ keyboard.notifyLayoutChanged()
+ }
+ }
+
+ function scanLayout() {
+ if (item === null)
+ return null
+ return item.scanLayout()
+ }
+}
diff --git a/src/components/KeyboardRow.qml b/src/components/KeyboardRow.qml
new file mode 100644
index 00000000..d08f8411
--- /dev/null
+++ b/src/components/KeyboardRow.qml
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.Layouts
+
+/*!
+ \qmltype KeyboardRow
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits RowLayout
+
+ \brief Keyboard row for keyboard layouts.
+
+ Specifies a row of keys in the keyboard layout.
+*/
+
+RowLayout {
+ /*! Sets the key weight for all children keys.
+
+ The default value is inherited from the parent element
+ in the layout hierarchy.
+ */
+ property real keyWeight: parent ? parent.keyWeight : undefined
+
+ /*! \since QtQuick.VirtualKeyboard 2.0
+
+ Sets the \c smallTextVisible for all children keys.
+
+ The default value is inherited from the parent element
+ in the layout hierarchy.
+ */
+ property bool smallTextVisible: parent ? parent.smallTextVisible : false
+
+ spacing: 0
+}
diff --git a/src/components/ModeKey.qml b/src/components/ModeKey.qml
new file mode 100644
index 00000000..165d0f95
--- /dev/null
+++ b/src/components/ModeKey.qml
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype ModeKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits Key
+ \since QtQuick.VirtualKeyboard 2.0
+
+ \brief Generic mode key for keyboard layouts.
+
+ This key provides generic mode button functionality.
+
+ A key press toggles the current mode without emitting key event
+ for input method processing.
+
+ ModeKey can be used in situations where a particular mode is switched
+ "ON / OFF", and where the mode change does not require changing the
+ keyboard layout. When this component is used, the \l { BaseKey::displayText } { displayText } should
+ remain the same regardless of the mode, because the keyboard style
+ visualizes the status.
+*/
+
+Key {
+ /*! This property provides the current mode.
+
+ The default is false.
+ */
+ property bool mode
+ keyType: QtVirtualKeyboard.ModeKey
+ noKeyEvent: true
+ functionKey: true
+ highlighted: true
+ onClicked: mode = !mode
+ keyPanelDelegate: keyboard.style ? keyboard.style.modeKeyPanel : undefined
+}
diff --git a/src/components/MultiSoundEffect.qml b/src/components/MultiSoundEffect.qml
new file mode 100644
index 00000000..80080682
--- /dev/null
+++ b/src/components/MultiSoundEffect.qml
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** 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
+import QtMultimedia
+
+Item {
+ id: multiSoundEffect
+ property url source
+ property int maxInstances: 2
+ property var __cachedInstances
+ property int __currentIndex: 0
+
+ signal playingChanged(url source, bool playing)
+
+ Component {
+ id: soundEffectComp
+ SoundEffect {
+ source: multiSoundEffect.source
+ onPlayingChanged: multiSoundEffect.playingChanged(source, playing)
+ }
+ }
+
+ onSourceChanged: {
+ __cachedInstances = []
+ __currentIndex = 0
+ if (source != Qt.resolvedUrl("")) {
+ var i
+ for (i = 0; i < maxInstances; i++) {
+ var soundEffect = soundEffectComp.createObject(multiSoundEffect)
+ if (soundEffect === null)
+ return
+ __cachedInstances.push(soundEffect)
+ }
+ }
+ }
+
+ function play() {
+ if (__cachedInstances === undefined || __cachedInstances.length === 0)
+ return
+ if (__cachedInstances[__currentIndex].playing) {
+ __cachedInstances[__currentIndex].stop()
+ __currentIndex = (__currentIndex + 1) % __cachedInstances.length
+ }
+ __cachedInstances[__currentIndex].play()
+ }
+}
diff --git a/src/components/MultitapInputMethod.qml b/src/components/MultitapInputMethod.qml
new file mode 100644
index 00000000..ee664e86
--- /dev/null
+++ b/src/components/MultitapInputMethod.qml
@@ -0,0 +1,132 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+InputMethod {
+ property string multitapSequence
+ property int multitapIndex: -1
+
+ onMultitapSequenceChanged: selectionListChanged(SelectionListModel.Type.WordCandidateList)
+ onMultitapIndexChanged: selectionListActiveItemChanged(SelectionListModel.Type.WordCandidateList, multitapIndex)
+
+ property variant multiTapTimer: Timer {
+ interval: 1200
+ onTriggered: {
+ update()
+ }
+ }
+
+ function inputModes(locale) {
+ return [InputEngine.InputMode.Latin, InputEngine.InputMode.Numeric, InputEngine.InputMode.Dialable];
+ }
+
+ function setInputMode(locale, inputMode) {
+ return true
+ }
+
+ function setTextCase(textCase) {
+ return true
+ }
+
+ function reset() {
+ multiTapTimer.stop()
+ multitapIndex = -1
+ multitapSequence = ""
+ }
+
+ function update() {
+ multiTapTimer.stop()
+ multitapIndex = -1
+ multitapSequence = ""
+ if (inputContext !== null && inputContext.preeditText.length > 0) {
+ inputContext.commit()
+ }
+ }
+
+ function keyEvent(key, text, modifiers) {
+ var accept = false
+ switch (key) {
+ case Qt.Key_Enter:
+ case Qt.Key_Return:
+ case Qt.Key_Tab:
+ update()
+ break
+ case Qt.Key_Backspace:
+ if (inputContext.preeditText.length > 0) {
+ inputContext.clear()
+ update()
+ accept = true
+ }
+ break
+ default:
+ if (key !== inputEngine.previousKey) {
+ update()
+ }
+ multitapSequence = text
+ if (multitapSequence.length > 1) {
+ multitapIndex = multiTapTimer.running ? (multitapIndex + 1) % multitapSequence.length : 0
+ inputContext.preeditText = multitapSequence.charAt(multitapIndex)
+ multiTapTimer.restart()
+ } else {
+ inputContext.commit(text)
+ }
+ accept = true
+ break
+ }
+ return accept;
+ }
+
+ function selectionLists() {
+ return [SelectionListModel.Type.WordCandidateList];
+ }
+
+ function selectionListItemCount(type) {
+ return multitapSequence.length > 1 ? multitapSequence.length : 0
+ }
+
+ function selectionListData(type, index, role) {
+ var result = null
+ switch (role) {
+ case SelectionListModel.Role.Display:
+ result = multitapSequence.charAt(index)
+ break
+ default:
+ break
+ }
+ return result
+ }
+
+ function selectionListItemSelected(type, index) {
+ multitapIndex = index
+ inputContext.preeditText = multitapSequence.charAt(multitapIndex)
+ update()
+ }
+}
diff --git a/src/components/NumberKey.qml b/src/components/NumberKey.qml
new file mode 100644
index 00000000..61df72aa
--- /dev/null
+++ b/src/components/NumberKey.qml
@@ -0,0 +1,49 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype NumberKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits Key
+
+ \brief Specialized number key for keyboard layouts.
+
+ This key emits the key code and key text for input method processing.
+ A NumberKey differs from a normal \l Key in that it does not show a
+ character preview.
+*/
+
+Key {
+ showPreview: false
+ keyType: QtVirtualKeyboard.NumberKey
+}
diff --git a/src/components/PopupList.qml b/src/components/PopupList.qml
new file mode 100644
index 00000000..9deb79de
--- /dev/null
+++ b/src/components/PopupList.qml
@@ -0,0 +1,59 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+ListView {
+ property int maxVisibleItems: 5
+ readonly property int preferredVisibleItems: count < maxVisibleItems ? count : maxVisibleItems
+ readonly property real contentWidth: contentItem.childrenRect.width
+ property alias defaultHighlight: defaultHighlight
+
+ clip: true
+ visible: enabled && count > 0
+ width: contentWidth
+ height: currentItem ? currentItem.height * preferredVisibleItems + (spacing * preferredVisibleItems - 1) : 0
+ orientation: ListView.Vertical
+ snapMode: ListView.SnapToItem
+ delegate: keyboard.style.popupListDelegate
+ highlight: keyboard.style.popupListHighlight ? keyboard.style.popupListHighlight : defaultHighlight
+ highlightMoveDuration: 0
+ highlightResizeDuration: 0
+ add: keyboard.style.popupListAdd
+ remove: keyboard.style.popupListRemove
+ keyNavigationWraps: true
+
+ onCurrentItemChanged: if (currentItem) keyboard.soundEffect.register(currentItem.soundEffect)
+
+ Component {
+ id: defaultHighlight
+ Item {}
+ }
+}
diff --git a/src/components/SelectionControl.qml b/src/components/SelectionControl.qml
new file mode 100644
index 00000000..f571d292
--- /dev/null
+++ b/src/components/SelectionControl.qml
@@ -0,0 +1,100 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+Item {
+ id: root
+ property bool handleIsMoving: false
+ property var inputContext: InputContext
+ visible: enabled && (inputContext.selectionControlVisible || handleIsMoving) && !InputContext.animating
+
+ Loader {
+ id: anchorHandle
+ sourceComponent: keyboard.style.selectionHandle
+ x: visible ? inputContext.anchorRectangle.x - width/2 : 0
+ y: visible ? inputContext.anchorRectangle.y + inputContext.anchorRectangle.height : 0
+
+ Behavior on opacity {
+ NumberAnimation { duration: 200 }
+ }
+ opacity: inputContext !== null && inputContext.anchorRectIntersectsClipRect ? 1.0 : 0.0
+
+ MouseArea {
+ width: parent.width * 2
+ height: width * 1.12
+ anchors.centerIn: parent
+ onPositionChanged: {
+ // we don't move the handles, the handles will move as the selection changes.
+ // The middle of a handle is mapped to the middle of the line above it
+ root.handleIsMoving = true
+ var xx = x + anchorHandle.x + mouse.x
+ var yy = y + anchorHandle.y + mouse.y - (anchorHandle.height + inputContext.anchorRectangle.height)/2
+ var x2 = cursorHandle.x + cursorHandle.width/2
+ var y2 = cursorHandle.y - inputContext.cursorRectangle.height/2
+ inputContext.setSelectionOnFocusObject(Qt.point(xx,yy), Qt.point(x2,y2))
+ }
+ onReleased: {
+ root.handleIsMoving = false
+ }
+ }
+ }
+
+ // selection cursor handle
+ Loader {
+ id: cursorHandle
+ sourceComponent: keyboard.style.selectionHandle
+ x: visible ? inputContext.cursorRectangle.x - width/2 : 0
+ y: visible ? inputContext.cursorRectangle.y + inputContext.cursorRectangle.height : 0
+
+ Behavior on opacity {
+ NumberAnimation { duration: 200 }
+ }
+ opacity: inputContext !== null && inputContext.cursorRectIntersectsClipRect ? 1.0 : 0.0
+
+ MouseArea {
+ width: parent.width * 2
+ height: width * 1.12
+ anchors.centerIn: parent
+ onPositionChanged: {
+ // we don't move the handles, the handles will move as the selection changes.
+ root.handleIsMoving = true
+ var xx = anchorHandle.x + anchorHandle.width/2
+ var yy = anchorHandle.y - inputContext.anchorRectangle.height/2
+ var x2 = x + cursorHandle.x + mouse.x
+ var y2 = y + cursorHandle.y + mouse.y - (cursorHandle.height + inputContext.cursorRectangle.height)/2
+ inputContext.setSelectionOnFocusObject(Qt.point(xx, yy), Qt.point(x2, y2))
+ }
+ onReleased: {
+ root.handleIsMoving = false
+ }
+ }
+ }
+}
diff --git a/src/components/ShadowInputControl.qml b/src/components/ShadowInputControl.qml
new file mode 100644
index 00000000..1667f24a
--- /dev/null
+++ b/src/components/ShadowInputControl.qml
@@ -0,0 +1,141 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 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
+// Deliberately imported after QtQuick to avoid missing restoreMode property in Binding. Fix in Qt 6.
+import QtQml
+import QtQuick.VirtualKeyboard
+import QtQuick.VirtualKeyboard.Settings
+
+Item {
+ id: control
+
+ enabled: keyboard.active && VirtualKeyboardSettings.fullScreenMode
+
+ MouseArea {
+ anchors.fill: parent
+ }
+
+ onXChanged: InputContext.priv.shadow.updateSelectionProperties()
+ onYChanged: InputContext.priv.shadow.updateSelectionProperties()
+
+ Loader {
+ sourceComponent: keyboard.style.fullScreenInputContainerBackground
+ anchors.fill: parent
+ Loader {
+ id: fullScreenInputBackground
+ sourceComponent: keyboard.style.fullScreenInputBackground
+ anchors.fill: parent
+ anchors.margins: keyboard.style.fullScreenInputMargins
+ z: 1
+ Flickable {
+ id: flickable
+ clip: true
+ z: 2
+ width: parent.width
+ height: parent.height
+ flickableDirection: Flickable.HorizontalFlick
+ interactive: contentWidth > width
+ contentWidth: shadowInput.width
+ onContentXChanged: InputContext.priv.shadow.updateSelectionProperties()
+
+ function ensureVisible(rectangle) {
+ if (contentX >= rectangle.x)
+ contentX = rectangle.x
+ else if (contentX + width <= rectangle.x + rectangle.width)
+ contentX = rectangle.x + rectangle.width - width;
+ }
+
+ TextInput {
+ id: shadowInput
+ objectName: "shadowInput"
+ property bool blinkStatus: true
+ width: Math.max(flickable.width, implicitWidth)
+ height: implicitHeight
+ anchors.verticalCenter: parent.verticalCenter
+ leftPadding: keyboard.style.fullScreenInputPadding
+ rightPadding: keyboard.style.fullScreenInputPadding
+ activeFocusOnPress: false
+ font: keyboard.style.fullScreenInputFont
+ inputMethodHints: InputContext.inputMethodHints
+ cursorDelegate: keyboard.style.fullScreenInputCursor
+ passwordCharacter: keyboard.style.fullScreenInputPasswordCharacter
+ color: keyboard.style.fullScreenInputColor
+ selectionColor: keyboard.style.fullScreenInputSelectionColor
+ selectedTextColor: keyboard.style.fullScreenInputSelectedTextColor
+ echoMode: (InputContext.inputMethodHints & Qt.ImhHiddenText) ? TextInput.Password : TextInput.Normal
+ selectByMouse: !!InputContext.inputItem && !!InputContext.inputItem.selectByMouse
+ onCursorPositionChanged: {
+ cursorSyncTimer.restart()
+ blinkStatus = true
+ cursorTimer.restart()
+ }
+ onSelectionStartChanged: cursorSyncTimer.restart()
+ onSelectionEndChanged: cursorSyncTimer.restart()
+ onCursorRectangleChanged: flickable.ensureVisible(cursorRectangle)
+
+ function getAnchorPosition() {
+ if (selectionStart == selectionEnd)
+ return cursorPosition
+ else if (selectionStart == cursorPosition)
+ return selectionEnd
+ else
+ return selectionStart
+ }
+
+ Timer {
+ id: cursorSyncTimer
+ interval: 0
+ onTriggered: {
+ var anchorPosition = shadowInput.getAnchorPosition()
+ if (anchorPosition !== InputContext.anchorPosition || shadowInput.cursorPosition !== InputContext.cursorPosition)
+ InputContext.priv.forceCursorPosition(anchorPosition, shadowInput.cursorPosition)
+ }
+ }
+
+ Timer {
+ id: cursorTimer
+ interval: Qt.styleHints.cursorFlashTime / 2
+ repeat: true
+ running: true
+ onTriggered: shadowInput.blinkStatus = !shadowInput.blinkStatus
+ }
+ }
+ }
+ }
+ }
+
+ Binding {
+ target: InputContext.priv.shadow
+ property: "inputItem"
+ value: shadowInput
+ when: VirtualKeyboardSettings.fullScreenMode
+ restoreMode: Binding.RestoreBinding
+ }
+}
diff --git a/src/components/ShiftKey.qml b/src/components/ShiftKey.qml
new file mode 100644
index 00000000..1b256c66
--- /dev/null
+++ b/src/components/ShiftKey.qml
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype ShiftKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits BaseKey
+
+ \brief Shift key for keyboard layouts.
+
+ This key changes the shift state of the keyboard.
+*/
+
+BaseKey {
+ id: shiftKey
+ keyType: QtVirtualKeyboard.ShiftKey
+ key: Qt.Key_Shift
+ enabled: InputContext.priv.shiftHandler.toggleShiftEnabled
+ highlighted: true
+ functionKey: true
+ keyPanelDelegate: keyboard.style ? keyboard.style.shiftKeyPanel : undefined
+ onClicked: InputContext.priv.shiftHandler.toggleShift()
+}
diff --git a/src/components/SpaceKey.qml b/src/components/SpaceKey.qml
new file mode 100644
index 00000000..0a06ad29
--- /dev/null
+++ b/src/components/SpaceKey.qml
@@ -0,0 +1,53 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype SpaceKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits Key
+
+ \brief Space key for keyboard layouts.
+
+ This key emits a space for input method processing.
+*/
+
+Key {
+ keyType: QtVirtualKeyboard.SpaceKey
+ text: " "
+ displayText: ""
+ repeat: true
+ showPreview: false
+ highlighted: true
+ key: Qt.Key_Space
+ keyPanelDelegate: keyboard.style ? keyboard.style.spaceKeyPanel : undefined
+}
diff --git a/src/components/SymbolModeKey.qml b/src/components/SymbolModeKey.qml
new file mode 100644
index 00000000..d58402cb
--- /dev/null
+++ b/src/components/SymbolModeKey.qml
@@ -0,0 +1,52 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype SymbolModeKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits Key
+
+ \brief Symbol mode key for keyboard layouts.
+
+ This key toggles between the symbol mode layout and the main layout.
+*/
+
+Key {
+ keyType: QtVirtualKeyboard.SymbolModeKey
+ key: Qt.Key_Context1
+ displayText: "&123"
+ functionKey: true
+ highlighted: true
+ onClicked: keyboard.symbolMode = !keyboard.symbolMode
+ keyPanelDelegate: keyboard.style ? keyboard.style.symbolKeyPanel : undefined
+}
diff --git a/src/components/TraceInputArea.qml b/src/components/TraceInputArea.qml
new file mode 100644
index 00000000..cbbfc598
--- /dev/null
+++ b/src/components/TraceInputArea.qml
@@ -0,0 +1,223 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.Window
+import QtQuick.VirtualKeyboard
+
+/*!
+ \qmltype TraceInputArea
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits MultiPointTouchArea
+ \since QtQuick.VirtualKeyboard 2.0
+
+ \brief A specialized MultiPointTouchArea for collecting touch input data.
+
+ This type handles the trace interaction between the touch screen and the input engine.
+
+ The traces are rendered using the delegate from the
+ \l {KeyboardStyle::}{traceCanvasDelegate} property of the current
+ \l KeyboardStyle.
+*/
+
+MultiPointTouchArea {
+ id: traceInputArea
+
+ /*! Pattern recognition mode of this input area.
+
+ The default value is \l {InputEngine::patternRecognitionModes} {InputEngine.PatternRecognitionMode.None}.
+ */
+ property int patternRecognitionMode: InputEngine.PatternRecognitionMode.None
+
+ /*! List of horizontal rulers in the input area.
+
+ The rulers are defined as a number of pixels from the top edge of the boundingBox.
+
+ Here is an example that demonstrates how to define rulers:
+
+ \code
+ horizontalRulers: [boundingBox.height / 3, boundingBox.height / 3 * 2]
+ verticalRulers: [boundingBox.width / 3, boundingBox.width / 3 * 2]
+ \endcode
+ */
+ property var horizontalRulers
+
+ /*! List of vertical rulers in the input area.
+
+ The rulers are defined as a number of pixels from the left edge of the boundingBox.
+ */
+ property var verticalRulers
+
+ /*! Bounding box for the trace input.
+
+ This property is readonly and is automatically updated based on the item size
+ and margins.
+ */
+ readonly property rect boundingBox: (width > 0 && height > 0) ?
+ Qt.rect(traceInputArea.x + traceInputArea.anchors.leftMargin,
+ traceInputArea.y + traceInputArea.anchors.topMargin,
+ traceInputArea.width,
+ traceInputArea.height) :
+ Qt.rect(0, 0, 0, 0)
+
+ /*! Canvas type of this trace input area.
+
+ This property can be used to distinguish between different types of canvases.
+ For example, in full screen handwriting mode this property is set to \c "fullscreen", and
+ in keyboard handwriting mode this property is set to \c "keyboard".
+ */
+ property string canvasType
+
+ property var __activeTraceCanvases: ({})
+ property var __traceCanvasList: ([])
+ property var __recycledTraceCanvasList: ([])
+
+ Component.onCompleted: {
+ for (var i = 0; i < 6; i++) {
+ __recycledTraceCanvasList.push(__createTraceCanvas())
+ }
+ }
+
+ function __getTraceCanvas() {
+ while (__recycledTraceCanvasList.length == 0 &&
+ __traceCanvasList.length >= 15 &&
+ !__traceCanvasList.shift().recycle()) {}
+
+ return __recycledTraceCanvasList.length > 0 ?
+ __recycledTraceCanvasList.pop() :
+ __createTraceCanvas()
+ }
+
+ function __createTraceCanvas() {
+ var traceCanvas = keyboard.style.traceCanvasDelegate.createObject(traceInputArea)
+ traceCanvas.onRecycle.connect(__onTraceCanvasRecycled)
+ traceCanvas.anchors.fill = traceCanvas.parent
+ return traceCanvas
+ }
+
+ function __onTraceCanvasRecycled(traceCanvas) {
+ var index = __traceCanvasList.findIndex(function(otherCanvas) {
+ return traceCanvas === otherCanvas
+ })
+ if (index !== -1) {
+ __traceCanvasList.splice(index, index + 1)
+ }
+ __recycledTraceCanvasList.push(traceCanvas)
+ }
+
+ property var __traceCaptureDeviceInfo:
+ ({
+ channels: ['t'],
+ sampleRate: 60,
+ uniform: false,
+ latency: 0.0,
+ dpi: Screen.pixelDensity * 25.4
+ })
+ property var __traceScreenInfo:
+ ({
+ boundingBox: traceInputArea.boundingBox,
+ horizontalRulers: traceInputArea.horizontalRulers,
+ verticalRulers: traceInputArea.verticalRulers,
+ canvasType: traceInputArea.canvasType
+ })
+
+ enabled: patternRecognitionMode !== InputEngine.PatternRecognitionMode.None && InputContext.inputEngine.patternRecognitionModes.indexOf(patternRecognitionMode) !== -1
+
+ onPressed: (touchPoints) => {
+ if (!keyboard.style.traceCanvasDelegate)
+ return
+ for (var i = 0; i < touchPoints.length; i++) {
+ var traceId = touchPoints[i].pointId
+ var trace = InputContext.inputEngine.traceBegin(traceId, patternRecognitionMode, __traceCaptureDeviceInfo, __traceScreenInfo)
+ if (trace) {
+ var traceCanvas = __getTraceCanvas()
+ if (traceCanvas) {
+ traceCanvas.trace = trace
+ var index = trace.addPoint(Qt.point(touchPoints[i].x, touchPoints[i].y))
+ if (trace.channels.indexOf('t') !== -1) {
+ var dt = new Date()
+ trace.setChannelData('t', index, dt.getTime())
+ }
+ __activeTraceCanvases[traceId] = traceCanvas
+ } else {
+ __activeTraceCanvases[traceId] = null
+ }
+ } else {
+ __activeTraceCanvases[traceId] = null
+ }
+ }
+ }
+
+ onUpdated: (touchPoints) => {
+ for (var i = 0; i < touchPoints.length; i++) {
+ var traceId = touchPoints[i].pointId
+ var traceCanvas = __activeTraceCanvases[traceId]
+ if (traceCanvas) {
+ var trace = traceCanvas.trace
+ var index = trace.addPoint(Qt.point(touchPoints[i].x, touchPoints[i].y))
+ if (trace.channels.indexOf('t') !== -1) {
+ var dt = new Date()
+ trace.setChannelData('t', index, dt.getTime())
+ }
+ }
+ }
+ }
+
+ onReleased: (touchPoints) => {
+ for (var i = 0; i < touchPoints.length; i++) {
+ var traceId = touchPoints[i].pointId
+ var traceCanvas = __activeTraceCanvases[traceId]
+ if (traceCanvas) {
+ if (traceCanvas.trace) {
+ traceCanvas.trace.final = true
+ InputContext.inputEngine.traceEnd(traceCanvas.trace)
+ }
+ __traceCanvasList.push(traceCanvas)
+ __activeTraceCanvases[traceId] = null
+ }
+ }
+ }
+
+ onCanceled: (touchPoints) => {
+ for (var i = 0; i < touchPoints.length; i++) {
+ var traceId = touchPoints[i].pointId
+ var traceCanvas = __activeTraceCanvases[traceId]
+ if (traceCanvas) {
+ if (traceCanvas.trace) {
+ traceCanvas.trace.final = true
+ traceCanvas.trace.canceled = true
+ InputContext.inputEngine.traceEnd(traceCanvas.trace)
+ }
+ __traceCanvasList.push(traceCanvas)
+ __activeTraceCanvases[traceId] = null
+ }
+ }
+ }
+}
diff --git a/src/components/TraceInputKey.qml b/src/components/TraceInputKey.qml
new file mode 100644
index 00000000..a12a0a20
--- /dev/null
+++ b/src/components/TraceInputKey.qml
@@ -0,0 +1,116 @@
+/****************************************************************************
+**
+** 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
+import QtQuick.Layouts
+
+/*!
+ \qmltype TraceInputKey
+ \inqmlmodule QtQuick.VirtualKeyboard
+ \ingroup qtvirtualkeyboard-qml
+ \inherits Item
+ \since QtQuick.VirtualKeyboard 2.0
+
+ \brief A specialized key for collecting touch input data.
+
+ This type can be placed in the keyboard layout. It collects
+ and renders touch input data (trace) from the key area.
+*/
+
+Item {
+ id: traceInputKey
+
+ /*! Sets the key weight value which determines the relative size of the key.
+
+ Use this property to change the key size in the layout.
+
+ The default value is inherited from the parent element
+ of the key in the layout hierarchy.
+ */
+ property real weight: parent.keyWeight
+
+ /*! Pattern recognition mode of this input area.
+
+ The default value is \l {InputEngine::patternRecognitionModes} {InputEngine.PatternRecognitionMode.None}.
+ */
+ property alias patternRecognitionMode: traceInputArea.patternRecognitionMode
+
+ /*! List of horizontal rulers in the input area.
+
+ The rulers are defined as a number of pixels from the top edge of the bounding box.
+
+ Here is an example that demonstrates how to define rulers:
+
+ \code
+ horizontalRulers: [boundingBox.height / 3, boundingBox.height / 3 * 2]
+ verticalRulers: [boundingBox.width / 3, boundingBox.width / 3 * 2]
+ \endcode
+ */
+ property alias horizontalRulers: traceInputArea.horizontalRulers
+
+ /*! List of vertical rulers in the input area.
+
+ The rulers are defined as a number of pixels from the left edge of the bounding box.
+ */
+ property alias verticalRulers: traceInputArea.verticalRulers
+
+ /*! Bounding box for the trace input.
+
+ This property is readonly and is automatically updated based on the item size
+ and margins.
+ */
+ readonly property alias boundingBox: traceInputArea.boundingBox
+
+ /*! Canvas type of this trace input area.
+
+ This property can be used to distinguish between different types of canvases.
+ The default value is \c "keyboard".
+ */
+ property alias canvasType: traceInputArea.canvasType
+
+ Layout.minimumWidth: traceInputKeyPanel.implicitWidth
+ Layout.minimumHeight: traceInputKeyPanel.implicitHeight
+ Layout.preferredWidth: weight
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ canvasType: "keyboard"
+
+ Loader {
+ id: traceInputKeyPanel
+ sourceComponent: keyboard.style.traceInputKeyPanelDelegate
+ anchors.fill: parent
+ onLoaded: traceInputKeyPanel.item.control = traceInputKey
+ }
+
+ TraceInputArea {
+ id: traceInputArea
+ anchors.fill: traceInputKeyPanel
+ anchors.margins: traceInputKeyPanel.item ? traceInputKeyPanel.item.traceMargins : 0
+ }
+}
diff --git a/src/components/WordCandidatePopupList.qml b/src/components/WordCandidatePopupList.qml
new file mode 100644
index 00000000..df47b639
--- /dev/null
+++ b/src/components/WordCandidatePopupList.qml
@@ -0,0 +1,85 @@
+/****************************************************************************
+**
+** 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
+// Deliberately imported after QtQuick to avoid missing restoreMode property in Binding. Fix in Qt 6.
+import QtQml
+import QtQuick.VirtualKeyboard
+
+PopupList {
+ id: wordCandidatePopupList
+
+ readonly property int preferredVisibleItems: {
+ if (!currentItem)
+ return 0
+ var maxHeight = flipVertical ? Qt.inputMethod.cursorRectangle.y : parent.height - Qt.inputMethod.cursorRectangle.height - Qt.inputMethod.cursorRectangle.y
+ var result = Math.min(count, maxVisibleItems)
+ while (result > 2 && result * currentItem.height > maxHeight)
+ --result
+ return result
+ }
+ readonly property bool flipVertical: currentItem &&
+ Qt.inputMethod.cursorRectangle.y + (Qt.inputMethod.cursorRectangle.height / 2) > (parent.height / 2) &&
+ Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height + (currentItem.height * 2) > parent.height
+
+ height: currentItem ? currentItem.height * preferredVisibleItems + (spacing * preferredVisibleItems - 1) : 0
+ Binding {
+ target: wordCandidatePopupList
+ property: "x"
+ value: Math.round(Qt.inputMethod.cursorRectangle.x -
+ (wordCandidatePopupList.currentItem ?
+ (wordCandidatePopupList.currentItem.hasOwnProperty("cursorAnchor") ?
+ wordCandidatePopupList.currentItem.cursorAnchor : wordCandidatePopupList.currentItem.width) : 0))
+ when: wordCandidatePopupList.visible
+ restoreMode: Binding.RestoreBinding
+ }
+ Binding {
+ target: wordCandidatePopupList
+ property: "y"
+ value: Math.round(wordCandidatePopupList.flipVertical ? Qt.inputMethod.cursorRectangle.y - wordCandidatePopupList.height : Qt.inputMethod.cursorRectangle.y + Qt.inputMethod.cursorRectangle.height)
+ when: wordCandidatePopupList.visible
+ restoreMode: Binding.RestoreBinding
+ }
+ model: enabled ? InputContext.inputEngine.wordCandidateListModel : null
+
+ onContentWidthChanged: viewResizeTimer.restart()
+
+ Timer {
+ id: viewResizeTimer
+ interval: 0
+ repeat: false
+ onTriggered: wordCandidatePopupList.width = wordCandidatePopupList.contentWidth
+ }
+
+ Connections {
+ target: wordCandidatePopupList.model ? wordCandidatePopupList.model : null
+ function onActiveItemChanged(index) { wordCandidatePopupList.currentIndex = index }
+ function onItemSelected() { if (wordCandidatePopupList.currentItem) keyboard.soundEffect.play(wordCandidatePopupList.currentItem.soundEffect) }
+ }
+}