From 0423970ae077d3e48b62376ba9b5a85a5fda6dd9 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Thu, 9 Mar 2023 15:08:39 +0100 Subject: [PATCH] qml: add word picker to SeedTextArea --- .../qml/components/controls/SeedTextArea.qml | 108 +++++++++++++++--- .../qml/components/wizard/WCConfirmSeed.qml | 1 + .../gui/qml/components/wizard/WCHaveSeed.qml | 9 +- electrum/gui/qml/qebitcoin.py | 13 ++- 4 files changed, 111 insertions(+), 20 deletions(-) diff --git a/electrum/gui/qml/components/controls/SeedTextArea.qml b/electrum/gui/qml/components/controls/SeedTextArea.qml index 34bd866f8..3ab6e95d9 100644 --- a/electrum/gui/qml/components/controls/SeedTextArea.qml +++ b/electrum/gui/qml/components/controls/SeedTextArea.qml @@ -1,20 +1,102 @@ -import QtQuick 2.6 +import QtQuick 2.15 import QtQuick.Layouts 1.0 -import QtQuick.Controls 2.1 +import QtQuick.Controls 2.15 import QtQuick.Controls.Material 2.0 -TextArea { - id: seedtext - Layout.fillWidth: true - Layout.minimumHeight: 80 - rightPadding: constants.paddingLarge - leftPadding: constants.paddingLarge - wrapMode: TextInput.WordWrap - font.bold: true - font.pixelSize: constants.fontSizeLarge - inputMethodHints: Qt.ImhSensitiveData | Qt.ImhPreferLowercase | Qt.ImhNoPredictiveText +import org.electrum 1.0 + +Pane { + id: root + implicitHeight: rootLayout.height + padding: 0 + + property alias readOnly: seedtextarea.readOnly + property alias text: seedtextarea.text + property alias placeholderText: seedtextarea.placeholderText + + property var _suggestions: [] + background: Rectangle { color: "transparent" - border.color: Material.accentColor + } + + ColumnLayout { + id: rootLayout + width: parent.width + spacing: 0 + Flickable { + Layout.preferredWidth: parent.width + Layout.minimumHeight: fontMetrics.lineSpacing + 2*constants.paddingXXSmall + 2*constants.paddingXSmall + 2 + implicitHeight: wordsLayout.height + + visible: !readOnly + flickableDirection: Flickable.HorizontalFlick + contentWidth: wordsLayout.width + interactive: wordsLayout.width > width + + RowLayout { + id: wordsLayout + Repeater { + model: _suggestions + Rectangle { + Layout.margins: constants.paddingXXSmall + width: suggestionLabel.width + height: suggestionLabel.height + color: constants.lighterBackground + radius: constants.paddingXXSmall + Label { + id: suggestionLabel + text: modelData + padding: constants.paddingXSmall + leftPadding: constants.paddingSmall + rightPadding: constants.paddingSmall + } + MouseArea { + anchors.fill: parent + onClicked: { + var words = seedtextarea.text.split(' ') + words.pop() + words.push(modelData) + seedtextarea.text = words.join(' ') + ' ' + } + } + } + } + } + } + + 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.ImhPreferLowercase | Qt.ImhNoPredictiveText + + background: Rectangle { + color: constants.darkerBackground + } + + onTextChanged: { + _suggestions = bitcoin.mnemonicsFor(seedtextarea.text.split(' ').pop()) + // TODO: cursorPosition only on suggestion apply + cursorPosition = text.length + } + } + } + + FontMetrics { + id: fontMetrics + font: seedtextarea.font + } + + Bitcoin { + id: bitcoin } } diff --git a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml index c81727f1e..ac7a0c8ff 100644 --- a/electrum/gui/qml/components/wizard/WCConfirmSeed.qml +++ b/electrum/gui/qml/components/wizard/WCConfirmSeed.qml @@ -41,6 +41,7 @@ WizardComponent { SeedTextArea { id: confirm Layout.fillWidth: true + placeholderText: qsTr('Enter your seed') onTextChanged: checkValid() } diff --git a/electrum/gui/qml/components/wizard/WCHaveSeed.qml b/electrum/gui/qml/components/wizard/WCHaveSeed.qml index 08246665d..6fe39f1e8 100644 --- a/electrum/gui/qml/components/wizard/WCHaveSeed.qml +++ b/electrum/gui/qml/components/wizard/WCHaveSeed.qml @@ -165,16 +165,13 @@ WizardComponent { Layout.columnSpan: 2 } - Label { - Layout.topMargin: constants.paddingMedium - Layout.columnSpan: 2 - text: cosigner ? qsTr('Enter cosigner seed') : qsTr('Enter your seed') - } - SeedTextArea { id: seedtext Layout.fillWidth: true Layout.columnSpan: 2 + + placeholderText: cosigner ? qsTr('Enter cosigner seed') : qsTr('Enter your seed') + onTextChanged: { validationTimer.restart() } diff --git a/electrum/gui/qml/qebitcoin.py b/electrum/gui/qml/qebitcoin.py index 4ca0f4a88..d0c7a0763 100644 --- a/electrum/gui/qml/qebitcoin.py +++ b/electrum/gui/qml/qebitcoin.py @@ -10,7 +10,8 @@ from electrum.logging import get_logger from electrum.slip39 import decode_mnemonic, Slip39Error from electrum.util import parse_URI, create_bip21_uri, InvalidBitcoinURI, get_asyncio_loop from electrum.transaction import tx_from_any -from electrum.mnemonic import is_any_2fa_seed_type +from electrum.mnemonic import Mnemonic, is_any_2fa_seed_type +from electrum.old_mnemonic import wordlist as old_wordlist from .qetypes import QEAmount @@ -26,6 +27,8 @@ class QEBitcoin(QObject): validationMessageChanged = pyqtSignal() _validationMessage = '' + _words = None + def __init__(self, config, parent=None): super().__init__(parent) self.config = config @@ -165,3 +168,11 @@ class QEBitcoin(QObject): @pyqtSlot(str, result=bool) def isPrivateKeyList(self, csv: str): return keystore.is_private_key_list(csv) + + @pyqtSlot(str, result='QVariantList') + def mnemonicsFor(self, fragment): + if not fragment: + return [] + if not self._words: + self._words = set(Mnemonic('en').wordlist).union(set(old_wordlist)) + return sorted(filter(lambda x: x.startswith(fragment), self._words))