Merge branch 'seed_keyboard'
This commit is contained in:
@@ -61,10 +61,6 @@ class ElectrumGui(BaseElectrumGui, Logger):
|
|||||||
# os.environ['QML_IMPORT_TRACE'] = '1'
|
# os.environ['QML_IMPORT_TRACE'] = '1'
|
||||||
# os.environ['QT_DEBUG_PLUGINS'] = '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'
|
os.environ['QT_ANDROID_DISABLE_ACCESSIBILITY'] = '1'
|
||||||
|
|
||||||
# set default locale to en_GB. This is for l10n (e.g. number formatting, number input etc),
|
# set default locale to en_GB. This is for l10n (e.g. number formatting, number input etc),
|
||||||
|
|||||||
93
electrum/gui/qml/components/controls/SeedKeyboard.qml
Normal file
93
electrum/gui/qml/components/controls/SeedKeyboard.qml
Normal file
@@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
43
electrum/gui/qml/components/controls/SeedKeyboardKey.qml
Normal file
43
electrum/gui/qml/components/controls/SeedKeyboardKey.qml
Normal file
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,10 @@ Pane {
|
|||||||
padding: 0
|
padding: 0
|
||||||
|
|
||||||
property string text
|
property string text
|
||||||
property alias readOnly: seedtextarea.readOnly
|
property bool readOnly: false
|
||||||
property alias placeholderText: seedtextarea.placeholderText
|
property alias placeholderText: seedtextarea.placeholderText
|
||||||
|
property string indicatorText
|
||||||
|
property bool indicatorValid
|
||||||
|
|
||||||
property var _suggestions: []
|
property var _suggestions: []
|
||||||
|
|
||||||
@@ -29,6 +31,56 @@ Pane {
|
|||||||
id: rootLayout
|
id: rootLayout
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: 0
|
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 {
|
Flickable {
|
||||||
Layout.preferredWidth: parent.width
|
Layout.preferredWidth: parent.width
|
||||||
Layout.minimumHeight: fontMetrics.lineSpacing + 2*constants.paddingXXSmall + 2*constants.paddingXSmall + 2
|
Layout.minimumHeight: fontMetrics.lineSpacing + 2*constants.paddingXXSmall + 2*constants.paddingXSmall + 2
|
||||||
@@ -70,34 +122,18 @@ Pane {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextArea {
|
SeedKeyboard {
|
||||||
id: seedtextarea
|
id: kbd
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.minimumHeight: fontMetrics.height * 3 + topPadding + bottomPadding
|
Layout.preferredHeight: kbd.width / 2
|
||||||
|
visible: !root.readOnly
|
||||||
rightPadding: constants.paddingLarge
|
onKeyEvent: {
|
||||||
leftPadding: constants.paddingLarge
|
if (keycode == Qt.Key_Backspace) {
|
||||||
|
if (seedtextarea.text.length > 0)
|
||||||
wrapMode: TextInput.WordWrap
|
seedtextarea.text = seedtextarea.text.substring(0, seedtextarea.text.length-1)
|
||||||
font.bold: true
|
} else {
|
||||||
font.pixelSize: constants.fontSizeLarge
|
seedtextarea.text = seedtextarea.text + text
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import QtQuick.Layouts 1.0
|
|||||||
import QtQuick.Controls 2.3
|
import QtQuick.Controls 2.3
|
||||||
import QtQuick.Controls.Material 2.0
|
import QtQuick.Controls.Material 2.0
|
||||||
import QtQuick.Controls.Material.impl 2.12
|
import QtQuick.Controls.Material.impl 2.12
|
||||||
|
import QtQuick.Window 2.15
|
||||||
|
|
||||||
import QtQml 2.6
|
import QtQml 2.6
|
||||||
import QtMultimedia 5.6
|
import QtMultimedia 5.6
|
||||||
@@ -31,7 +32,6 @@ ApplicationWindow
|
|||||||
Constants { id: appconstants }
|
Constants { id: appconstants }
|
||||||
|
|
||||||
property alias stack: mainStackView
|
property alias stack: mainStackView
|
||||||
property alias inputPanel: inputPanel
|
|
||||||
|
|
||||||
property variant activeDialogs: []
|
property variant activeDialogs: []
|
||||||
|
|
||||||
@@ -224,7 +224,7 @@ ApplicationWindow
|
|||||||
StackView {
|
StackView {
|
||||||
id: mainStackView
|
id: mainStackView
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: inputPanel.y - header.height
|
height: keyboardFreeZone.height - header.height
|
||||||
initialItem: Qt.resolvedUrl('WalletMainView.qml')
|
initialItem: Qt.resolvedUrl('WalletMainView.qml')
|
||||||
|
|
||||||
function getRoot() {
|
function getRoot() {
|
||||||
@@ -266,39 +266,47 @@ ApplicationWindow
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
id: keyboardFreeZone
|
||||||
// Item as first child in Overlay that adjusts its size to the available
|
// 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)
|
// screen space minus the virtual keyboard (e.g. to center dialogs in)
|
||||||
// see ElDialog.resizeWithKeyboard property
|
// see also ElDialog.resizeWithKeyboard property
|
||||||
parent: Overlay.overlay
|
parent: Overlay.overlay
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: inputPanel.y
|
height: parent.height
|
||||||
}
|
|
||||||
|
|
||||||
InputPanel {
|
|
||||||
id: inputPanel
|
|
||||||
width: parent.width
|
|
||||||
y: parent.height
|
|
||||||
|
|
||||||
states: State {
|
states: State {
|
||||||
name: "visible"
|
name: "visible"
|
||||||
when: inputPanel.active
|
when: Qt.inputMethod.visible
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: inputPanel
|
target: keyboardFreeZone
|
||||||
y: parent.height - height
|
height: keyboardFreeZone.parent.height - Qt.inputMethod.keyboardRectangle.height / Screen.devicePixelRatio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transitions: Transition {
|
transitions: [
|
||||||
from: ''
|
Transition {
|
||||||
to: 'visible'
|
from: ''
|
||||||
reversible: true
|
to: 'visible'
|
||||||
ParallelAnimation {
|
ParallelAnimation {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
properties: "y"
|
properties: "height"
|
||||||
duration: 250
|
duration: 250
|
||||||
easing.type: Easing.OutQuad
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Transition {
|
||||||
|
from: 'visible'
|
||||||
|
to: ''
|
||||||
|
ParallelAnimation {
|
||||||
|
NumberAnimation {
|
||||||
|
properties: "height"
|
||||||
|
duration: 50
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property alias newWalletWizard: _newWalletWizard
|
property alias newWalletWizard: _newWalletWizard
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ WizardComponent {
|
|||||||
|
|
||||||
InfoTextArea {
|
InfoTextArea {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.bottomMargin: constants.paddingLarge
|
||||||
text: qsTr('Your seed is important!') + ' ' +
|
text: qsTr('Your seed is important!') + ' ' +
|
||||||
qsTr('If you lose your seed, your money will be permanently lost.') + ' ' +
|
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.')
|
qsTr('To make sure that you have properly saved your seed, please retype it here.')
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.topMargin: constants.paddingMedium
|
|
||||||
text: qsTr('Confirm your seed (re-enter)')
|
text: qsTr('Confirm your seed (re-enter)')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ WizardComponent {
|
|||||||
id: infotext
|
id: infotext
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.columnSpan: 2
|
Layout.columnSpan: 2
|
||||||
|
Layout.bottomMargin: constants.paddingLarge
|
||||||
}
|
}
|
||||||
|
|
||||||
SeedTextArea {
|
SeedTextArea {
|
||||||
@@ -173,25 +174,11 @@ WizardComponent {
|
|||||||
|
|
||||||
placeholderText: cosigner ? qsTr('Enter cosigner seed') : qsTr('Enter your seed')
|
placeholderText: cosigner ? qsTr('Enter cosigner seed') : qsTr('Enter your seed')
|
||||||
|
|
||||||
|
indicatorValid: root.valid
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
startValidationTimer()
|
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 {
|
TextArea {
|
||||||
id: validationtext
|
id: validationtext
|
||||||
@@ -223,13 +210,13 @@ WizardComponent {
|
|||||||
|
|
||||||
Bitcoin {
|
Bitcoin {
|
||||||
id: bitcoin
|
id: bitcoin
|
||||||
onSeedTypeChanged: contentText.text = bitcoin.seedType
|
onSeedTypeChanged: seedtext.indicatorText = bitcoin.seedType
|
||||||
onValidationMessageChanged: validationtext.text = validationMessage
|
onValidationMessageChanged: validationtext.text = validationMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
function startValidationTimer() {
|
function startValidationTimer() {
|
||||||
valid = false
|
valid = false
|
||||||
contentText.text = ''
|
seedtext.indicatorText = ''
|
||||||
validationTimer.restart()
|
validationTimer.restart()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user