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/controls/SeedKeyboard.qml b/electrum/gui/qml/components/controls/SeedKeyboard.qml new file mode 100644 index 000000000..750e9ceb8 --- /dev/null +++ b/electrum/gui/qml/components/controls/SeedKeyboard.qml @@ -0,0 +1,93 @@ +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 hpadding: 0 + property int vpadding: 15 + + 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 + + function emitKeyEvent(key, keycode) { + keyEvent(keycode, key) + } + + ColumnLayout { + id: rootLayout + 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'] + 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: ' ' + 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 new file mode 100644 index 000000000..16c39b8e2 --- /dev/null +++ b/electrum/gui/qml/components/controls/SeedKeyboardKey.qml @@ -0,0 +1,43 @@ +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 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 + + focusPolicy: Qt.NoFocus + autoRepeat: true + autoRepeatDelay: 750 + + text: key + + padding: 0 + font.pixelSize: Math.max(root.height * 1/3, constants.fontSizeSmall) + + onClicked: { + emitKeyEvent() + } + + // send keyevent again, otherwise it is ignored + onDoubleClicked: { + emitKeyEvent() + } + } +} diff --git a/electrum/gui/qml/components/controls/SeedTextArea.qml b/electrum/gui/qml/components/controls/SeedTextArea.qml index 0047869be..4115096bd 100644 --- a/electrum/gui/qml/components/controls/SeedTextArea.qml +++ b/electrum/gui/qml/components/controls/SeedTextArea.qml @@ -11,8 +11,10 @@ Pane { padding: 0 property string text - property alias readOnly: seedtextarea.readOnly + property bool readOnly: false property alias placeholderText: seedtextarea.placeholderText + property string indicatorText + property bool indicatorValid property var _suggestions: [] @@ -29,6 +31,56 @@ 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 + } + + 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 { Layout.preferredWidth: parent.width Layout.minimumHeight: fontMetrics.lineSpacing + 2*constants.paddingXXSmall + 2*constants.paddingXSmall + 2 @@ -70,34 +122,18 @@ Pane { } } - TextArea { - id: seedtextarea + SeedKeyboard { + id: kbd 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 - - 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 + 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 + } } } } 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 diff --git a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml index b5979b290..ab04a44ae 100644 --- a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml +++ b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml @@ -30,13 +30,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 6b76bd5b1..35069f37c 100644 --- a/electrum/gui/qml/components/wizard/WCHaveSeed.qml +++ b/electrum/gui/qml/components/wizard/WCHaveSeed.qml @@ -164,6 +164,7 @@ WizardComponent { id: infotext Layout.fillWidth: true Layout.columnSpan: 2 + Layout.bottomMargin: constants.paddingLarge } SeedTextArea { @@ -173,25 +174,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 @@ -223,13 +210,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() }