diff options
Diffstat (limited to 'src/components')
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) } + } +} |