From 9ac83f6d9f31199322a2de4c10f28cbd55d197bb Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 1 May 2023 16:59:40 +0200 Subject: [PATCH 1/6] qml: add SeedKeyboard for seed entry without using system virtual keyboard --- .../qml/components/controls/SeedKeyboard.qml | 90 +++++++++++++++++++ .../components/controls/SeedKeyboardKey.qml | 34 +++++++ .../qml/components/controls/SeedTextArea.qml | 18 +++- 3 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 electrum/gui/qml/components/controls/SeedKeyboard.qml create mode 100644 electrum/gui/qml/components/controls/SeedKeyboardKey.qml diff --git a/electrum/gui/qml/components/controls/SeedKeyboard.qml b/electrum/gui/qml/components/controls/SeedKeyboard.qml new file mode 100644 index 000000000..73efc2fba --- /dev/null +++ b/electrum/gui/qml/components/controls/SeedKeyboard.qml @@ -0,0 +1,90 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 2.15 +import QtQuick.Controls.Material 2.0 + +Item { + id: root + + signal keyEvent(keycode: int, text: string) + + property int padding: 15 + + property int keywidth: (root.width - 2 * padding) / 11 - keyhspacing + property int keyheight: (root.height - 2 * padding) / 4 - keyvspacing + property int keyhspacing: 4 + property int keyvspacing: 5 + + function emitKeyEvent(key) { + var keycode + if (key == '<=') { + keycode = Qt.Key_Backspace + } else { + keycode = parseInt(key, 36) - 9 + 0x40 // map char to key code + } + keyEvent(keycode, key) + } + + ColumnLayout { + id: rootLayout + x: padding + y: padding + width: parent.width - 2*padding + spacing: keyvspacing + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: keyhspacing + Repeater { + model: ['q','w','e','r','t','y','u','i','o','p','<='] + delegate: SeedKeyboardKey { + key: modelData + kbd: root + implicitWidth: keywidth + implicitHeight: keyheight + } + } + } + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: keyhspacing + Repeater { + model: ['a','s','d','f','g','h','j','k','l'] + delegate: SeedKeyboardKey { + key: modelData + kbd: root + implicitWidth: keywidth + implicitHeight: keyheight + } + } + // spacer + Item { Layout.preferredHeight: 1; Layout.preferredWidth: keywidth / 2 } + } + RowLayout { + Layout.alignment: Qt.AlignHCenter + spacing: keyhspacing + Repeater { + model: ['z','x','c','v','b','n','m'] + delegate: SeedKeyboardKey { + key: modelData + kbd: root + implicitWidth: keywidth + implicitHeight: keyheight + } + } + // spacer + Item { Layout.preferredHeight: 1; Layout.preferredWidth: keywidth } + } + RowLayout { + Layout.alignment: Qt.AlignHCenter + SeedKeyboardKey { + key: ' ' + kbd: root + implicitWidth: keywidth * 5 + implicitHeight: keyheight + } + // spacer + Item { Layout.preferredHeight: 1; Layout.preferredWidth: keywidth / 2 } + } + } + +} diff --git a/electrum/gui/qml/components/controls/SeedKeyboardKey.qml b/electrum/gui/qml/components/controls/SeedKeyboardKey.qml new file mode 100644 index 000000000..edf878df4 --- /dev/null +++ b/electrum/gui/qml/components/controls/SeedKeyboardKey.qml @@ -0,0 +1,34 @@ +import QtQuick 2.15 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 2.15 +import QtQuick.Controls.Material 2.0 + +Pane { + id: root + + property string key + property QtObject kbd + padding: 1 + + FlatButton { + anchors.fill: parent + + focusPolicy: Qt.NoFocus + autoRepeat: true + autoRepeatDelay: 750 + + text: key + + padding: 0 + font.pixelSize: Math.max(root.height * 1/3, constants.fontSizeSmall) + + onClicked: { + kbd.emitKeyEvent(key) + } + + // send keyevent again, otherwise it is ignored + onDoubleClicked: { + kbd.emitKeyEvent(key) + } + } +} diff --git a/electrum/gui/qml/components/controls/SeedTextArea.qml b/electrum/gui/qml/components/controls/SeedTextArea.qml index 0047869be..096c7bb52 100644 --- a/electrum/gui/qml/components/controls/SeedTextArea.qml +++ b/electrum/gui/qml/components/controls/SeedTextArea.qml @@ -11,7 +11,7 @@ Pane { padding: 0 property string text - property alias readOnly: seedtextarea.readOnly + property bool readOnly: false property alias placeholderText: seedtextarea.placeholderText property var _suggestions: [] @@ -83,6 +83,7 @@ Pane { font.pixelSize: constants.fontSizeLarge font.family: FixedFont inputMethodHints: Qt.ImhSensitiveData | Qt.ImhLowercaseOnly | Qt.ImhNoPredictiveText + readOnly: true background: Rectangle { color: constants.darkerBackground @@ -100,6 +101,21 @@ Pane { cursorPosition = text.length } } + + SeedKeyboard { + id: kbd + Layout.fillWidth: true + Layout.preferredHeight: kbd.width / 2 + visible: !root.readOnly + onKeyEvent: { + if (keycode == Qt.Key_Backspace) { + if (seedtextarea.text.length > 0) + seedtextarea.text = seedtextarea.text.substring(0, seedtextarea.text.length-1) + } else { + seedtextarea.text = seedtextarea.text + text + } + } + } } FontMetrics { From 6394cdcd3b25885d8bf0e31331b45f8dd4e1529b Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Mon, 1 May 2023 18:39:16 +0200 Subject: [PATCH 2/6] qml: disable Qt Virtual Keyboard and refactor keyboardFreeZone item to take android system keyboard into account --- electrum/gui/qml/__init__.py | 4 --- electrum/gui/qml/components/main.qml | 54 ++++++++++++++++------------ 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/electrum/gui/qml/__init__.py b/electrum/gui/qml/__init__.py index 6a0a70037..5d5bf5045 100644 --- a/electrum/gui/qml/__init__.py +++ b/electrum/gui/qml/__init__.py @@ -61,10 +61,6 @@ class ElectrumGui(BaseElectrumGui, Logger): # os.environ['QML_IMPORT_TRACE'] = '1' # os.environ['QT_DEBUG_PLUGINS'] = '1' - os.environ['QT_IM_MODULE'] = 'qtvirtualkeyboard' - os.environ['QT_VIRTUALKEYBOARD_STYLE'] = 'Electrum' - os.environ['QML2_IMPORT_PATH'] = 'electrum/gui/qml' - os.environ['QT_ANDROID_DISABLE_ACCESSIBILITY'] = '1' # set default locale to en_GB. This is for l10n (e.g. number formatting, number input etc), diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index e6b770ad9..1dd04544a 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -3,6 +3,7 @@ import QtQuick.Layouts 1.0 import QtQuick.Controls 2.3 import QtQuick.Controls.Material 2.0 import QtQuick.Controls.Material.impl 2.12 +import QtQuick.Window 2.15 import QtQml 2.6 import QtMultimedia 5.6 @@ -31,7 +32,6 @@ ApplicationWindow Constants { id: appconstants } property alias stack: mainStackView - property alias inputPanel: inputPanel property variant activeDialogs: [] @@ -224,7 +224,7 @@ ApplicationWindow StackView { id: mainStackView width: parent.width - height: inputPanel.y - header.height + height: keyboardFreeZone.height - header.height initialItem: Qt.resolvedUrl('WalletMainView.qml') function getRoot() { @@ -266,39 +266,47 @@ ApplicationWindow } Item { + id: keyboardFreeZone // Item as first child in Overlay that adjusts its size to the available // screen space minus the virtual keyboard (e.g. to center dialogs in) - // see ElDialog.resizeWithKeyboard property + // see also ElDialog.resizeWithKeyboard property parent: Overlay.overlay width: parent.width - height: inputPanel.y - } - - InputPanel { - id: inputPanel - width: parent.width - y: parent.height + height: parent.height states: State { name: "visible" - when: inputPanel.active + when: Qt.inputMethod.visible PropertyChanges { - target: inputPanel - y: parent.height - height + target: keyboardFreeZone + height: keyboardFreeZone.parent.height - Qt.inputMethod.keyboardRectangle.height / Screen.devicePixelRatio } } - transitions: Transition { - from: '' - to: 'visible' - reversible: true - ParallelAnimation { - NumberAnimation { - properties: "y" - duration: 250 - easing.type: Easing.OutQuad + transitions: [ + Transition { + from: '' + to: 'visible' + ParallelAnimation { + NumberAnimation { + properties: "height" + duration: 250 + easing.type: Easing.OutQuad + } + } + }, + Transition { + from: 'visible' + to: '' + ParallelAnimation { + NumberAnimation { + properties: "height" + duration: 50 + easing.type: Easing.OutQuad + } } } - } + ] + } property alias newWalletWizard: _newWalletWizard From b290fbd4b5fa315dfba2e716a247b52aead94942 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 2 May 2023 13:03:13 +0200 Subject: [PATCH 3/6] qml: seedkeyboard padding, explicit keycode, backspace next to spacebar --- .../qml/components/controls/SeedKeyboard.qml | 29 ++++++++++--------- .../components/controls/SeedKeyboardKey.qml | 13 +++++++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/electrum/gui/qml/components/controls/SeedKeyboard.qml b/electrum/gui/qml/components/controls/SeedKeyboard.qml index 73efc2fba..15b87ab28 100644 --- a/electrum/gui/qml/components/controls/SeedKeyboard.qml +++ b/electrum/gui/qml/components/controls/SeedKeyboard.qml @@ -8,34 +8,29 @@ Item { signal keyEvent(keycode: int, text: string) - property int padding: 15 + property int hpadding: 0 + property int vpadding: 15 - property int keywidth: (root.width - 2 * padding) / 11 - keyhspacing + property int keywidth: (root.width - 2 * padding) / 10 - keyhspacing property int keyheight: (root.height - 2 * padding) / 4 - keyvspacing property int keyhspacing: 4 property int keyvspacing: 5 - function emitKeyEvent(key) { - var keycode - if (key == '<=') { - keycode = Qt.Key_Backspace - } else { - keycode = parseInt(key, 36) - 9 + 0x40 // map char to key code - } + function emitKeyEvent(key, keycode) { keyEvent(keycode, key) } ColumnLayout { id: rootLayout - x: padding - y: padding - width: parent.width - 2*padding + x: hpadding + y: vpadding + width: parent.width - 2*hpadding spacing: keyvspacing RowLayout { Layout.alignment: Qt.AlignHCenter spacing: keyhspacing Repeater { - model: ['q','w','e','r','t','y','u','i','o','p','<='] + model: ['q','w','e','r','t','y','u','i','o','p'] delegate: SeedKeyboardKey { key: modelData kbd: root @@ -78,10 +73,18 @@ Item { Layout.alignment: Qt.AlignHCenter SeedKeyboardKey { key: ' ' + keycode: Qt.Key_Space kbd: root implicitWidth: keywidth * 5 implicitHeight: keyheight } + SeedKeyboardKey { + key: '<' + keycode: Qt.Key_Backspace + kbd: root + implicitWidth: keywidth + implicitHeight: keyheight + } // spacer Item { Layout.preferredHeight: 1; Layout.preferredWidth: keywidth / 2 } } diff --git a/electrum/gui/qml/components/controls/SeedKeyboardKey.qml b/electrum/gui/qml/components/controls/SeedKeyboardKey.qml index edf878df4..16c39b8e2 100644 --- a/electrum/gui/qml/components/controls/SeedKeyboardKey.qml +++ b/electrum/gui/qml/components/controls/SeedKeyboardKey.qml @@ -7,9 +7,18 @@ Pane { id: root property string key + property int keycode: -1 + property QtObject kbd padding: 1 + function emitKeyEvent() { + if (keycode == -1) { + keycode = parseInt(key, 36) - 9 + 0x40 // map a-z char to key code + } + kbd.keyEvent(keycode, key) + } + FlatButton { anchors.fill: parent @@ -23,12 +32,12 @@ Pane { font.pixelSize: Math.max(root.height * 1/3, constants.fontSizeSmall) onClicked: { - kbd.emitKeyEvent(key) + emitKeyEvent() } // send keyevent again, otherwise it is ignored onDoubleClicked: { - kbd.emitKeyEvent(key) + emitKeyEvent() } } } From 95f960bb677cd6ddfe4adbf0a027044e2f7d1150 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 2 May 2023 13:04:33 +0200 Subject: [PATCH 4/6] qml: seedtext word suggestions below text area --- .../qml/components/controls/SeedTextArea.qml | 65 ++++++++++--------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/electrum/gui/qml/components/controls/SeedTextArea.qml b/electrum/gui/qml/components/controls/SeedTextArea.qml index 096c7bb52..1681cad5d 100644 --- a/electrum/gui/qml/components/controls/SeedTextArea.qml +++ b/electrum/gui/qml/components/controls/SeedTextArea.qml @@ -29,6 +29,39 @@ Pane { id: rootLayout width: parent.width spacing: 0 + + TextArea { + id: seedtextarea + Layout.fillWidth: true + Layout.minimumHeight: fontMetrics.height * 3 + topPadding + bottomPadding + + rightPadding: constants.paddingLarge + leftPadding: constants.paddingLarge + + wrapMode: TextInput.WordWrap + font.bold: true + font.pixelSize: constants.fontSizeLarge + font.family: FixedFont + inputMethodHints: Qt.ImhSensitiveData | Qt.ImhLowercaseOnly | Qt.ImhNoPredictiveText + readOnly: true + + background: Rectangle { + color: constants.darkerBackground + } + + onTextChanged: { + // work around Qt issue, TextArea fires spurious textChanged events + // NOTE: might be Qt virtual keyboard, or Qt upgrade from 5.15.2 to 5.15.7 + if (root.text != text) + root.text = text + + // update suggestions + _suggestions = bitcoin.mnemonicsFor(seedtextarea.text.split(' ').pop()) + // TODO: cursorPosition only on suggestion apply + cursorPosition = text.length + } + } + Flickable { Layout.preferredWidth: parent.width Layout.minimumHeight: fontMetrics.lineSpacing + 2*constants.paddingXXSmall + 2*constants.paddingXSmall + 2 @@ -70,38 +103,6 @@ Pane { } } - TextArea { - id: seedtextarea - Layout.fillWidth: true - Layout.minimumHeight: fontMetrics.height * 3 + topPadding + bottomPadding - - rightPadding: constants.paddingLarge - leftPadding: constants.paddingLarge - - wrapMode: TextInput.WordWrap - font.bold: true - font.pixelSize: constants.fontSizeLarge - font.family: FixedFont - inputMethodHints: Qt.ImhSensitiveData | Qt.ImhLowercaseOnly | Qt.ImhNoPredictiveText - readOnly: true - - background: Rectangle { - color: constants.darkerBackground - } - - onTextChanged: { - // work around Qt issue, TextArea fires spurious textChanged events - // NOTE: might be Qt virtual keyboard, or Qt upgrade from 5.15.2 to 5.15.7 - if (root.text != text) - root.text = text - - // update suggestions - _suggestions = bitcoin.mnemonicsFor(seedtextarea.text.split(' ').pop()) - // TODO: cursorPosition only on suggestion apply - cursorPosition = text.length - } - } - SeedKeyboard { id: kbd Layout.fillWidth: true From 8cd26820bf8733cee7181778d3d8b173227de7fc Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 2 May 2023 13:56:55 +0200 Subject: [PATCH 5/6] qml: styling seedtextarea --- .../qml/components/controls/SeedTextArea.qml | 19 +++++++++++++++ .../qml/components/wizard/WCConfirmSeed.qml | 2 +- .../gui/qml/components/wizard/WCHaveSeed.qml | 23 ++++--------------- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/electrum/gui/qml/components/controls/SeedTextArea.qml b/electrum/gui/qml/components/controls/SeedTextArea.qml index 1681cad5d..4115096bd 100644 --- a/electrum/gui/qml/components/controls/SeedTextArea.qml +++ b/electrum/gui/qml/components/controls/SeedTextArea.qml @@ -13,6 +13,8 @@ Pane { property string text property bool readOnly: false property alias placeholderText: seedtextarea.placeholderText + property string indicatorText + property bool indicatorValid property var _suggestions: [] @@ -60,6 +62,23 @@ Pane { // TODO: cursorPosition only on suggestion apply cursorPosition = text.length } + + Rectangle { + anchors.fill: contentText + color: root.indicatorValid ? 'green' : 'red' + border.color: Material.accentColor + radius: 2 + } + Label { + id: contentText + text: root.indicatorText + anchors.right: parent.right + anchors.bottom: parent.bottom + leftPadding: root.indicatorText != '' ? constants.paddingLarge : 0 + rightPadding: root.indicatorText != '' ? constants.paddingLarge : 0 + font.bold: false + font.pixelSize: constants.fontSizeSmall + } } Flickable { diff --git a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml index ac7a0c8ff..f67739733 100644 --- a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml +++ b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml @@ -28,13 +28,13 @@ WizardComponent { InfoTextArea { Layout.fillWidth: true + Layout.bottomMargin: constants.paddingLarge text: qsTr('Your seed is important!') + ' ' + qsTr('If you lose your seed, your money will be permanently lost.') + ' ' + qsTr('To make sure that you have properly saved your seed, please retype it here.') } Label { - Layout.topMargin: constants.paddingMedium text: qsTr('Confirm your seed (re-enter)') } diff --git a/electrum/gui/qml/components/wizard/WCHaveSeed.qml b/electrum/gui/qml/components/wizard/WCHaveSeed.qml index f8bd211e6..59559bde0 100644 --- a/electrum/gui/qml/components/wizard/WCHaveSeed.qml +++ b/electrum/gui/qml/components/wizard/WCHaveSeed.qml @@ -163,6 +163,7 @@ WizardComponent { id: infotext Layout.fillWidth: true Layout.columnSpan: 2 + Layout.bottomMargin: constants.paddingLarge } SeedTextArea { @@ -172,25 +173,11 @@ WizardComponent { placeholderText: cosigner ? qsTr('Enter cosigner seed') : qsTr('Enter your seed') + indicatorValid: root.valid + onTextChanged: { startValidationTimer() } - - Rectangle { - anchors.fill: contentText - color: root.valid ? 'green' : 'red' - border.color: Material.accentColor - radius: 2 - } - Label { - id: contentText - anchors.right: parent.right - anchors.bottom: parent.bottom - leftPadding: text != '' ? constants.paddingLarge : 0 - rightPadding: text != '' ? constants.paddingLarge : 0 - font.bold: false - font.pixelSize: constants.fontSizeSmall - } } TextArea { id: validationtext @@ -222,13 +209,13 @@ WizardComponent { Bitcoin { id: bitcoin - onSeedTypeChanged: contentText.text = bitcoin.seedType + onSeedTypeChanged: seedtext.indicatorText = bitcoin.seedType onValidationMessageChanged: validationtext.text = validationMessage } function startValidationTimer() { valid = false - contentText.text = '' + seedtext.indicatorText = '' validationTimer.restart() } From c8c76a8d6f6fd42105c571b43b4cd46cc6eb4eee Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Tue, 2 May 2023 20:16:36 +0200 Subject: [PATCH 6/6] qml: fix var ref --- electrum/gui/qml/components/controls/SeedKeyboard.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/electrum/gui/qml/components/controls/SeedKeyboard.qml b/electrum/gui/qml/components/controls/SeedKeyboard.qml index 15b87ab28..750e9ceb8 100644 --- a/electrum/gui/qml/components/controls/SeedKeyboard.qml +++ b/electrum/gui/qml/components/controls/SeedKeyboard.qml @@ -11,8 +11,8 @@ Item { property int hpadding: 0 property int vpadding: 15 - property int keywidth: (root.width - 2 * padding) / 10 - keyhspacing - property int keyheight: (root.height - 2 * padding) / 4 - keyvspacing + property int keywidth: (root.width - 2 * hpadding) / 10 - keyhspacing + property int keyheight: (root.height - 2 * vpadding) / 4 - keyvspacing property int keyhspacing: 4 property int keyvspacing: 5