Files
purple-electrumwallet/electrum/gui/kivy/uix/dialogs/installwizard.py
T

1166 lines
34 KiB
Python
Raw Normal View History

2016-06-16 19:25:44 +02:00
from functools import partial
import threading
import os
from kivy.app import App
2016-06-16 19:25:44 +02:00
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import ObjectProperty, StringProperty, OptionProperty
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.uix.togglebutton import ToggleButton
2016-06-16 19:25:44 +02:00
from kivy.utils import platform
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.clock import Clock
2016-06-25 19:42:41 +02:00
from kivy.utils import platform
2016-06-25 19:42:41 +02:00
from electrum.base_wizard import BaseWizard
2018-05-18 18:07:52 +02:00
from electrum.util import is_valid_email
2016-01-16 15:05:49 +01:00
2017-02-16 10:54:24 +01:00
from . import EventsDialog
from ...i18n import _
from .password_dialog import PasswordDialog
2017-02-16 10:54:24 +01:00
# global Variables
2016-06-25 19:42:41 +02:00
is_test = (platform == "linux")
2018-05-18 18:07:52 +02:00
test_seed = "grape impose jazz bind spatial mind jelly tourist tank today holiday stomach"
test_seed = "time taxi field recycle tiny license olive virus report rare steel portion achieve"
2016-06-20 16:25:11 +02:00
test_xpub = "xpub661MyMwAqRbcEbvVtRRSjqxVnaWVUMewVzMiURAKyYratih4TtBpMypzzefmv8zUNebmNVzB3PojdC5sV2P9bDgMoo9B3SARw1MXUUfU1GL"
2016-06-16 19:25:44 +02:00
Builder.load_string('''
#:import Window kivy.core.window.Window
2018-07-11 17:38:47 +02:00
#:import _ electrum.gui.kivy.i18n._
2016-06-16 19:25:44 +02:00
<WizardTextInput@TextInput>
border: 4, 4, 4, 4
font_size: '15sp'
padding: '15dp', '15dp'
background_color: (1, 1, 1, 1) if self.focus else (0.454, 0.698, 0.909, 1)
foreground_color: (0.31, 0.31, 0.31, 1) if self.focus else (0.835, 0.909, 0.972, 1)
hint_text_color: self.foreground_color
2018-07-11 17:38:47 +02:00
background_active: 'atlas://electrum/gui/kivy/theming/light/create_act_text_active'
background_normal: 'atlas://electrum/gui/kivy/theming/light/create_act_text_active'
2016-06-16 19:25:44 +02:00
size_hint_y: None
height: '48sp'
<WizardButton@Button>:
root: None
size_hint: 1, None
height: '48sp'
on_press: if self.root: self.root.dispatch('on_press', self)
on_release: if self.root: self.root.dispatch('on_release', self)
<BigLabel@Label>
color: .854, .925, .984, 1
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
bold: True
<-WizardDialog>
text_color: .854, .925, .984, 1
value: ''
#auto_dismiss: False
size_hint: None, None
canvas.before:
Color:
rgba: .239, .588, .882, 1
Rectangle:
size: Window.size
crcontent: crcontent
# add electrum icon
BoxLayout:
orientation: 'vertical' if self.width < self.height else 'horizontal'
padding:
min(dp(27), self.width/32), min(dp(27), self.height/32),\
min(dp(27), self.width/32), min(dp(27), self.height/32)
spacing: '10dp'
GridLayout:
id: grid_logo
cols: 1
pos_hint: {'center_y': .5}
size_hint: 1, None
height: self.minimum_height
Label:
color: root.text_color
text: 'ELECTRUM'
size_hint: 1, None
height: self.texture_size[1] if self.opacity else 0
font_size: '33sp'
2018-07-11 17:38:47 +02:00
font_name: 'electrum/gui/kivy/data/fonts/tron/Tr2n.ttf'
2016-06-16 19:25:44 +02:00
GridLayout:
cols: 1
id: crcontent
spacing: '1dp'
Widget:
size_hint: 1, 0.3
GridLayout:
rows: 1
spacing: '12dp'
size_hint: 1, None
height: self.minimum_height
WizardButton:
id: back
text: _('Back')
root: root
WizardButton:
id: next
text: _('Next')
root: root
disabled: root.value == ''
<WizardMultisigDialog>
value: 'next'
Widget
size_hint: 1, 1
Label:
color: root.text_color
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
text: _("Choose the number of signatures needed to unlock funds in your wallet")
Widget
size_hint: 1, 1
GridLayout:
orientation: 'vertical'
cols: 2
spacing: '14dp'
size_hint: 1, 1
height: self.minimum_height
Label:
color: root.text_color
text: _('From {} cosigners').format(n.value)
2016-06-16 19:25:44 +02:00
Slider:
id: n
range: 2, 5
step: 1
value: 2
Label:
color: root.text_color
text: _('Require {} signatures').format(m.value)
2016-06-16 19:25:44 +02:00
Slider:
id: m
range: 1, n.value
step: 1
value: 2
2019-08-20 09:02:33 +02:00
<WizardChoiceDialog>
2016-06-20 16:25:11 +02:00
message : ''
2016-06-16 19:25:44 +02:00
Widget:
size_hint: 1, 1
Label:
color: root.text_color
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
2016-06-20 16:25:11 +02:00
text: root.message
2016-06-16 19:25:44 +02:00
Widget
size_hint: 1, 1
GridLayout:
row_default_height: '48dp'
orientation: 'vertical'
id: choices
cols: 1
spacing: '14dp'
size_hint: 1, None
2018-05-18 18:07:52 +02:00
<WizardConfirmDialog>
message : ''
Widget:
size_hint: 1, 1
Label:
color: root.text_color
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
text: root.message
Widget
size_hint: 1, 1
<WizardTOSDialog>
message : ''
size_hint: 1, 1
2018-05-18 18:07:52 +02:00
ScrollView:
size_hint: 1, 1
TextInput:
2018-05-18 18:07:52 +02:00
color: root.text_color
size_hint: 1, None
text_size: self.width, None
height: self.minimum_height
2018-05-18 18:07:52 +02:00
text: root.message
disabled: True
2018-05-18 18:07:52 +02:00
<WizardEmailDialog>
Label:
color: root.text_color
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
text: 'Please enter your email address'
WizardTextInput:
id: email
on_text: Clock.schedule_once(root.on_text)
multiline: False
on_text_validate: Clock.schedule_once(root.on_enter)
2018-05-18 18:07:52 +02:00
<WizardKnownOTPDialog>
message : ''
message2: ''
Widget:
size_hint: 1, 1
Label:
color: root.text_color
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
text: root.message
Widget
size_hint: 1, 1
WizardTextInput:
id: otp
on_text: Clock.schedule_once(root.on_text)
multiline: False
on_text_validate: Clock.schedule_once(root.on_enter)
2018-05-18 18:07:52 +02:00
Widget
size_hint: 1, 1
Label:
color: root.text_color
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
text: root.message2
Widget
size_hint: 1, 1
height: '48sp'
BoxLayout:
orientation: 'horizontal'
2018-06-11 17:46:17 +02:00
WizardButton:
id: cb
text: _('Request new secret')
on_release: root.request_new_secret()
size_hint: 1, None
WizardButton:
id: abort
text: _('Abort creation')
on_release: root.abort_wallet_creation()
size_hint: 1, None
2018-06-11 17:46:17 +02:00
2018-05-18 18:07:52 +02:00
<WizardNewOTPDialog>
message : ''
message2 : ''
Label:
color: root.text_color
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
text: root.message
QRCodeWidget:
id: qr
size_hint: 1, 1
Label:
color: root.text_color
size_hint: 1, None
text_size: self.width, None
height: self.texture_size[1]
text: root.message2
WizardTextInput:
id: otp
on_text: Clock.schedule_once(root.on_text)
multiline: False
on_text_validate: Clock.schedule_once(root.on_enter)
2018-05-18 18:07:52 +02:00
2016-06-16 19:25:44 +02:00
<MButton@Button>:
size_hint: 1, None
height: '33dp'
on_release:
self.parent.update_amount(self.text)
<WordButton@Button>:
size_hint: None, None
padding: '5dp', '5dp'
text_size: None, self.height
width: self.texture_size[0]
height: '30dp'
on_release:
self.parent.new_word(self.text)
<SeedButton@Button>:
height: dp(100)
border: 4, 4, 4, 4
halign: 'justify'
valign: 'top'
font_size: '18dp'
text_size: self.width - dp(24), self.height - dp(12)
color: .1, .1, .1, 1
2018-07-11 17:38:47 +02:00
background_normal: 'atlas://electrum/gui/kivy/theming/light/white_bg_round_top'
2016-06-16 19:25:44 +02:00
background_down: self.background_normal
size_hint_y: None
<SeedLabel@Label>:
font_size: '12sp'
text_size: self.width, None
size_hint: 1, None
height: self.texture_size[1]
halign: 'justify'
valign: 'middle'
border: 4, 4, 4, 4
<SeedDialogHeader@GridLayout>
text: ''
options_dialog: None
rows: 1
orientation: 'horizontal'
size_hint: 1, None
height: self.minimum_height
BigLabel:
size_hint: 9, None
text: root.text
IconButton:
id: options_button
height: '30dp'
width: '30dp'
size_hint: 1, None
icon: 'atlas://electrum/gui/kivy/theming/light/gear'
on_release:
root.options_dialog() if root.options_dialog else None
2016-06-16 19:25:44 +02:00
<RestoreSeedDialog>
2016-07-01 11:44:26 +02:00
message: ''
2016-06-16 19:25:44 +02:00
word: ''
SeedDialogHeader:
id: seed_dialog_header
text: 'ENTER YOUR SEED PHRASE'
options_dialog: root.options_dialog
GridLayout:
2016-06-16 19:25:44 +02:00
cols: 1
padding: 0, '12dp'
orientation: 'vertical'
spacing: '12dp'
size_hint: 1, None
height: self.minimum_height
SeedButton:
id: text_input_seed
text: ''
on_text: Clock.schedule_once(root.on_text)
SeedLabel:
text: root.message
BoxLayout:
id: suggestions
height: '35dp'
size_hint: 1, None
new_word: root.on_word
BoxLayout:
id: line1
update_amount: root.update_text
size_hint: 1, None
height: '30dp'
MButton:
text: 'Q'
MButton:
text: 'W'
MButton:
text: 'E'
MButton:
text: 'R'
MButton:
text: 'T'
MButton:
text: 'Y'
MButton:
text: 'U'
MButton:
text: 'I'
MButton:
text: 'O'
MButton:
text: 'P'
BoxLayout:
id: line2
update_amount: root.update_text
size_hint: 1, None
height: '30dp'
Widget:
size_hint: 0.5, None
height: '33dp'
MButton:
text: 'A'
MButton:
text: 'S'
MButton:
text: 'D'
MButton:
text: 'F'
MButton:
text: 'G'
MButton:
text: 'H'
MButton:
text: 'J'
MButton:
text: 'K'
MButton:
text: 'L'
Widget:
size_hint: 0.5, None
height: '33dp'
BoxLayout:
id: line3
update_amount: root.update_text
size_hint: 1, None
height: '30dp'
Widget:
size_hint: 1, None
MButton:
text: 'Z'
MButton:
text: 'X'
MButton:
text: 'C'
MButton:
text: 'V'
MButton:
text: 'B'
MButton:
text: 'N'
MButton:
text: 'M'
MButton:
text: ' '
MButton:
text: '<'
<AddXpubDialog>
title: ''
message: ''
BigLabel:
text: root.title
GridLayout
cols: 1
padding: 0, '12dp'
orientation: 'vertical'
spacing: '12dp'
size_hint: 1, None
height: self.minimum_height
SeedButton:
id: text_input
text: ''
on_text: Clock.schedule_once(root.check_text)
SeedLabel:
text: root.message
GridLayout
rows: 1
spacing: '12dp'
size_hint: 1, None
height: self.minimum_height
IconButton:
id: scan
height: '48sp'
on_release: root.scan_xpub()
2018-07-11 17:38:47 +02:00
icon: 'atlas://electrum/gui/kivy/theming/light/camera'
2016-06-16 19:25:44 +02:00
size_hint: 1, None
WizardButton:
text: _('Paste')
on_release: root.do_paste()
WizardButton:
text: _('Clear')
on_release: root.do_clear()
<ShowXpubDialog>
xpub: ''
message: _('Here is your master public key. Share it with your cosigners.')
BigLabel:
text: "MASTER PUBLIC KEY"
GridLayout
cols: 1
padding: 0, '12dp'
orientation: 'vertical'
spacing: '12dp'
size_hint: 1, None
height: self.minimum_height
SeedButton:
id: text_input
text: root.xpub
SeedLabel:
text: root.message
GridLayout
rows: 1
spacing: '12dp'
size_hint: 1, None
height: self.minimum_height
WizardButton:
text: _('QR code')
on_release: root.do_qr()
WizardButton:
text: _('Copy')
on_release: root.do_copy()
WizardButton:
text: _('Share')
on_release: root.do_share()
<ShowSeedDialog>
spacing: '12dp'
value: 'next'
SeedDialogHeader:
2016-06-16 19:25:44 +02:00
text: "PLEASE WRITE DOWN YOUR SEED PHRASE"
options_dialog: root.options_dialog
2016-06-16 19:25:44 +02:00
GridLayout:
id: grid
cols: 1
pos_hint: {'center_y': .5}
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: '12dp'
SeedButton:
text: root.seed_text
2016-10-11 14:59:29 +02:00
SeedLabel:
text: root.message
<LineDialog>
BigLabel:
text: root.title
SeedLabel:
text: root.message
TextInput:
id: passphrase_input
multiline: False
size_hint: 1, None
2019-08-13 15:16:49 +02:00
height: '48dp'
SeedLabel:
text: root.warning
2019-08-13 15:16:49 +02:00
<ChoiceLineDialog>
BigLabel:
text: root.title
SeedLabel:
text: root.message1
GridLayout:
row_default_height: '48dp'
orientation: 'vertical'
id: choices
cols: 1
spacing: '14dp'
size_hint: 1, None
SeedLabel:
text: root.message2
TextInput:
id: text_input
2019-08-13 15:16:49 +02:00
multiline: False
size_hint: 1, None
height: '48dp'
2016-06-16 19:25:44 +02:00
''')
class WizardDialog(EventsDialog):
''' Abstract dialog to be used as the base for all Create Account Dialogs
'''
crcontent = ObjectProperty(None)
2016-06-20 16:25:11 +02:00
def __init__(self, wizard, **kwargs):
self.auto_dismiss = False
2017-02-16 10:54:24 +01:00
super(WizardDialog, self).__init__()
2016-06-20 16:25:11 +02:00
self.wizard = wizard
self.ids.back.disabled = not wizard.can_go_back()
self.app = App.get_running_app()
2016-06-16 19:25:44 +02:00
self.run_next = kwargs['run_next']
2019-08-13 17:12:42 +02:00
self._trigger_size_dialog = Clock.create_trigger(self._size_dialog)
# note: everything bound here needs to be unbound as otherwise the
# objects will be kept around and keep receiving the callbacks
Window.bind(size=self._trigger_size_dialog,
rotation=self._trigger_size_dialog,
on_keyboard=self.on_keyboard)
2019-08-13 17:12:42 +02:00
self._trigger_size_dialog()
2016-06-16 19:25:44 +02:00
self._on_release = False
def _size_dialog(self, dt):
app = App.get_running_app()
if app.ui_mode[0] == 'p':
self.size = Window.size
else:
#tablet
if app.orientation[0] == 'p':
#portrait
self.size = Window.size[0]/1.67, Window.size[1]/1.4
else:
self.size = Window.size[0]/2.5, Window.size[1]
def add_widget(self, widget, index=0):
if not self.crcontent:
super(WizardDialog, self).add_widget(widget)
else:
self.crcontent.add_widget(widget, index=index)
def on_keyboard(self, instance, key, keycode, codepoint, modifier):
if key == 27:
if self.wizard.can_go_back():
self.wizard.go_back()
else:
app = App.get_running_app()
if not app.is_exit:
app.is_exit = True
app.show_info(_('Press again to exit'))
else:
self._on_release = False
self.dismiss()
return True
2016-06-16 19:25:44 +02:00
def on_dismiss(self):
2019-08-13 17:12:42 +02:00
Window.unbind(size=self._trigger_size_dialog,
rotation=self._trigger_size_dialog,
on_keyboard=self.on_keyboard)
2016-06-16 19:25:44 +02:00
app = App.get_running_app()
if app.wallet is None and not self._on_release:
app.stop()
def get_params(self, button):
2016-06-20 16:25:11 +02:00
return (None,)
2016-06-16 19:25:44 +02:00
def on_release(self, button):
self._on_release = True
self.close()
if not button:
self.parent.dispatch('on_wizard_complete', None)
return
if button is self.ids.back:
2016-06-20 16:25:11 +02:00
self.wizard.go_back()
2016-06-16 19:25:44 +02:00
return
params = self.get_params(button)
self.run_next(*params)
class WizardMultisigDialog(WizardDialog):
def get_params(self, button):
m = self.ids.m.value
n = self.ids.n.value
return m, n
2018-05-18 18:07:52 +02:00
class WizardOTPDialogBase(WizardDialog):
2018-05-18 18:07:52 +02:00
def get_otp(self):
otp = self.ids.otp.text
if len(otp) != 6:
return
try:
return int(otp)
except:
return
def on_text(self, dt):
self.ids.next.disabled = self.get_otp() is None
def on_enter(self, dt):
# press next
next = self.ids.next
if not next.disabled:
next.dispatch('on_release')
class WizardKnownOTPDialog(WizardOTPDialogBase):
def __init__(self, wizard, **kwargs):
WizardOTPDialogBase.__init__(self, wizard, **kwargs)
self.message = _("This wallet is already registered with TrustedCoin. To finalize wallet creation, please enter your Google Authenticator Code.")
2018-06-11 17:46:17 +02:00
self.message2 =_("If you have lost your Google Authenticator account, you can request a new secret. You will need to retype your seed.")
self.request_new = False
2018-05-18 18:07:52 +02:00
def get_params(self, button):
2018-06-11 17:46:17 +02:00
return (self.get_otp(), self.request_new)
2018-05-18 18:07:52 +02:00
2018-06-11 17:46:17 +02:00
def request_new_secret(self):
self.request_new = True
self.on_release(True)
2018-05-18 18:07:52 +02:00
def abort_wallet_creation(self):
self._on_release = True
2019-05-11 19:36:57 +02:00
self.wizard.terminate(aborted=True)
self.dismiss()
2018-05-18 18:07:52 +02:00
class WizardNewOTPDialog(WizardOTPDialogBase):
2018-05-18 18:07:52 +02:00
def __init__(self, wizard, **kwargs):
WizardOTPDialogBase.__init__(self, wizard, **kwargs)
2018-05-18 18:07:52 +02:00
otp_secret = kwargs['otp_secret']
uri = "otpauth://totp/%s?secret=%s"%('trustedcoin.com', otp_secret)
self.message = "Please scan the following QR code in Google Authenticator. You may also use the secret key: %s"%otp_secret
self.message2 = _('Then, enter your Google Authenticator code:')
self.ids.qr.set_data(uri)
def get_params(self, button):
return (self.get_otp(), False)
class WizardTOSDialog(WizardDialog):
def __init__(self, wizard, **kwargs):
WizardDialog.__init__(self, wizard, **kwargs)
self.ids.next.text = 'Accept'
self.ids.next.disabled = False
self.message = kwargs['tos']
self.message2 = _('Enter your email address:')
class WizardEmailDialog(WizardDialog):
2018-05-18 18:07:52 +02:00
def get_params(self, button):
return (self.ids.email.text,)
2018-05-18 18:07:52 +02:00
def on_text(self, dt):
self.ids.next.disabled = not is_valid_email(self.ids.email.text)
def on_enter(self, dt):
# press next
next = self.ids.next
if not next.disabled:
next.dispatch('on_release')
2018-05-18 18:07:52 +02:00
class WizardConfirmDialog(WizardDialog):
def __init__(self, wizard, **kwargs):
super(WizardConfirmDialog, self).__init__(wizard, **kwargs)
self.message = kwargs.get('message', '')
self.value = 'ok'
def on_parent(self, instance, value):
if value:
app = App.get_running_app()
self._back = _back = partial(app.dispatch, 'on_back')
def get_params(self, button):
return (True,)
2019-08-13 15:16:49 +02:00
2019-08-20 09:02:33 +02:00
class WizardChoiceDialog(WizardDialog):
2016-06-16 19:25:44 +02:00
2016-06-20 16:25:11 +02:00
def __init__(self, wizard, **kwargs):
2019-08-20 09:02:33 +02:00
super(WizardChoiceDialog, self).__init__(wizard, **kwargs)
2019-08-13 15:16:49 +02:00
self.title = kwargs.get('message', '')
2016-06-20 16:25:11 +02:00
self.message = kwargs.get('message', '')
2016-06-16 19:25:44 +02:00
choices = kwargs.get('choices', [])
2019-08-13 15:16:49 +02:00
self.init_choices(choices)
def init_choices(self, choices):
2016-06-16 19:25:44 +02:00
layout = self.ids.choices
layout.bind(minimum_height=layout.setter('height'))
2016-06-20 16:25:11 +02:00
for action, text in choices:
2016-06-16 19:25:44 +02:00
l = WizardButton(text=text)
l.action = action
l.height = '48dp'
l.root = self
layout.add_widget(l)
def on_parent(self, instance, value):
if value:
app = App.get_running_app()
self._back = _back = partial(app.dispatch, 'on_back')
def get_params(self, button):
return (button.action,)
class LineDialog(WizardDialog):
title = StringProperty('')
message = StringProperty('')
warning = StringProperty('')
def __init__(self, wizard, **kwargs):
WizardDialog.__init__(self, wizard, **kwargs)
2019-08-13 15:16:49 +02:00
self.title = kwargs.get('title', '')
self.message = kwargs.get('message', '')
self.ids.next.disabled = False
def get_params(self, b):
return (self.ids.passphrase_input.text,)
class CLButton(ToggleButton):
2019-08-13 15:16:49 +02:00
def on_release(self):
self.root.script_type = self.script_type
self.root.set_text(self.value)
2019-08-20 09:02:33 +02:00
class ChoiceLineDialog(WizardChoiceDialog):
2019-08-13 15:16:49 +02:00
title = StringProperty('')
message1 = StringProperty('')
message2 = StringProperty('')
def __init__(self, wizard, **kwargs):
WizardDialog.__init__(self, wizard, **kwargs)
self.title = kwargs.get('title', '')
self.message1 = kwargs.get('message1', '')
self.message2 = kwargs.get('message2', '')
self.choices = kwargs.get('choices', [])
default_choice_idx = kwargs.get('default_choice_idx', 0)
2019-08-13 15:16:49 +02:00
self.ids.next.disabled = False
layout = self.ids.choices
layout.bind(minimum_height=layout.setter('height'))
for idx, (script_type, title, text) in enumerate(self.choices):
b = CLButton(text=title, height='30dp', group=self.title, allow_no_selection=False)
2019-08-13 15:16:49 +02:00
b.script_type = script_type
b.root = self
b.value = text
layout.add_widget(b)
if idx == default_choice_idx:
b.trigger_action(duration=0)
2019-08-13 15:16:49 +02:00
def set_text(self, value):
self.ids.text_input.text = value
2019-08-13 15:16:49 +02:00
def get_params(self, b):
return (self.ids.text_input.text, self.script_type)
2019-08-13 15:16:49 +02:00
class ShowSeedDialog(WizardDialog):
2016-06-16 19:25:44 +02:00
seed_text = StringProperty('')
message = _("If you forget your PIN or lose your device, your seed phrase will be the only way to recover your funds.")
2016-10-11 14:59:29 +02:00
ext = False
2016-06-16 19:25:44 +02:00
2017-08-24 16:19:47 +02:00
def __init__(self, wizard, **kwargs):
super(ShowSeedDialog, self).__init__(wizard, **kwargs)
self.seed_text = kwargs['seed_text']
2016-06-16 19:25:44 +02:00
def on_parent(self, instance, value):
if value:
app = App.get_running_app()
self._back = _back = partial(self.ids.back.dispatch, 'on_release')
2016-10-11 14:59:29 +02:00
def options_dialog(self):
2017-08-24 16:19:47 +02:00
from .seed_options import SeedOptionsDialog
2019-08-13 15:16:49 +02:00
def callback(ext, _):
self.ext = ext
d = SeedOptionsDialog(self.ext, None, callback)
2016-10-11 14:59:29 +02:00
d.open()
2016-06-16 19:25:44 +02:00
def get_params(self, b):
2016-10-11 14:59:29 +02:00
return (self.ext,)
2016-06-16 19:25:44 +02:00
class WordButton(Button):
pass
class WizardButton(Button):
pass
2016-06-20 16:25:11 +02:00
2016-06-16 19:25:44 +02:00
class RestoreSeedDialog(WizardDialog):
2016-06-20 16:25:11 +02:00
def __init__(self, wizard, **kwargs):
super(RestoreSeedDialog, self).__init__(wizard, **kwargs)
self._test = kwargs['test']
2016-06-16 19:25:44 +02:00
from electrum.mnemonic import Mnemonic
from electrum.old_mnemonic import words as old_wordlist
self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
2016-06-20 16:25:11 +02:00
self.ids.text_input_seed.text = test_seed if is_test else ''
2016-07-01 11:44:26 +02:00
self.message = _('Please type your seed phrase using the virtual keyboard.')
self.title = _('Enter Seed')
2016-10-11 14:59:29 +02:00
self.ext = False
2019-08-13 15:16:49 +02:00
self.bip39 = False
2016-10-11 14:59:29 +02:00
def options_dialog(self):
2017-08-24 16:19:47 +02:00
from .seed_options import SeedOptionsDialog
2019-08-13 15:16:49 +02:00
def callback(ext, bip39):
self.ext = ext
self.bip39 = bip39
self.update_next_button()
d = SeedOptionsDialog(self.ext, self.bip39, callback)
2016-10-11 14:59:29 +02:00
d.open()
2016-06-16 19:25:44 +02:00
def get_suggestions(self, prefix):
for w in self.words:
if w.startswith(prefix):
yield w
2019-08-13 15:16:49 +02:00
def update_next_button(self):
self.ids.next.disabled = False if self.bip39 else not bool(self._test(self.get_text()))
2016-06-16 19:25:44 +02:00
def on_text(self, dt):
2019-08-13 15:16:49 +02:00
self.update_next_button()
2016-06-16 19:25:44 +02:00
text = self.ids.text_input_seed.text
if not text:
last_word = ''
elif text[-1] == ' ':
last_word = ''
else:
last_word = text.split(' ')[-1]
enable_space = False
self.ids.suggestions.clear_widgets()
suggestions = [x for x in self.get_suggestions(last_word)]
if last_word in suggestions:
b = WordButton(text=last_word)
self.ids.suggestions.add_widget(b)
enable_space = True
for w in suggestions:
if w != last_word and len(suggestions) < 10:
b = WordButton(text=w)
self.ids.suggestions.add_widget(b)
2016-06-16 19:25:44 +02:00
i = len(last_word)
p = set()
for x in suggestions:
if len(x)>i: p.add(x[i])
for line in [self.ids.line1, self.ids.line2, self.ids.line3]:
for c in line.children:
if isinstance(c, Button):
if c.text in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
c.disabled = (c.text.lower() not in p) and bool(last_word)
2016-06-16 19:25:44 +02:00
elif c.text == ' ':
c.disabled = not enable_space
def on_word(self, w):
text = self.get_text()
words = text.split(' ')
words[-1] = w
text = ' '.join(words)
self.ids.text_input_seed.text = text + ' '
self.ids.suggestions.clear_widgets()
def get_text(self):
ti = self.ids.text_input_seed
2017-02-16 10:54:24 +01:00
return ' '.join(ti.text.strip().split())
2016-06-16 19:25:44 +02:00
def update_text(self, c):
c = c.lower()
text = self.ids.text_input_seed.text
if c == '<':
text = text[:-1]
else:
text += c
self.ids.text_input_seed.text = text
def on_parent(self, instance, value):
if value:
tis = self.ids.text_input_seed
tis.focus = True
#tis._keyboard.bind(on_key_down=self.on_key_down)
self._back = _back = partial(self.ids.back.dispatch,
'on_release')
app = App.get_running_app()
def on_key_down(self, keyboard, keycode, key, modifiers):
if keycode[0] in (13, 271):
self.on_enter()
return True
def on_enter(self):
#self._remove_keyboard()
# press next
next = self.ids.next
if not next.disabled:
next.dispatch('on_release')
def _remove_keyboard(self):
tis = self.ids.text_input_seed
if tis._keyboard:
tis._keyboard.unbind(on_key_down=self.on_key_down)
tis.focus = False
def get_params(self, b):
2019-08-13 15:16:49 +02:00
return (self.get_text(), self.bip39, self.ext)
class ConfirmSeedDialog(RestoreSeedDialog):
def __init__(self, *args, **kwargs):
RestoreSeedDialog.__init__(self, *args, **kwargs)
self.ids.seed_dialog_header.ids.options_button.disabled = True
def get_params(self, b):
return (self.get_text(),)
2016-10-11 14:59:29 +02:00
def options_dialog(self):
pass
2016-06-16 19:25:44 +02:00
class ShowXpubDialog(WizardDialog):
2016-06-20 16:25:11 +02:00
def __init__(self, wizard, **kwargs):
WizardDialog.__init__(self, wizard, **kwargs)
2016-06-16 19:25:44 +02:00
self.xpub = kwargs['xpub']
self.ids.next.disabled = False
def do_copy(self):
self.app._clipboard.copy(self.xpub)
def do_share(self):
self.app.do_share(self.xpub, _("Master Public Key"))
def do_qr(self):
2017-10-22 07:33:03 +02:00
from .qr_dialog import QRDialog
2016-06-16 19:25:44 +02:00
popup = QRDialog(_("Master Public Key"), self.xpub, True)
popup.open()
class AddXpubDialog(WizardDialog):
2016-06-20 16:25:11 +02:00
def __init__(self, wizard, **kwargs):
WizardDialog.__init__(self, wizard, **kwargs)
def is_valid(x):
try:
return kwargs['is_valid'](x)
except:
return False
self.is_valid = is_valid
2016-06-16 19:25:44 +02:00
self.title = kwargs['title']
self.message = kwargs['message']
self.allow_multi = kwargs.get('allow_multi', False)
2016-06-16 19:25:44 +02:00
def check_text(self, dt):
2016-06-20 16:25:11 +02:00
self.ids.next.disabled = not bool(self.is_valid(self.get_text()))
2016-06-16 19:25:44 +02:00
def get_text(self):
ti = self.ids.text_input
2017-08-08 11:12:44 +02:00
return ti.text.strip()
2016-06-16 19:25:44 +02:00
def get_params(self, button):
return (self.get_text(),)
def scan_xpub(self):
def on_complete(text):
if self.allow_multi:
self.ids.text_input.text += text + '\n'
else:
self.ids.text_input.text = text
2016-06-16 19:25:44 +02:00
self.app.scan_qr(on_complete)
def do_paste(self):
2017-08-08 11:12:44 +02:00
self.ids.text_input.text = test_xpub if is_test else self.app._clipboard.paste()
2016-06-16 19:25:44 +02:00
def do_clear(self):
self.ids.text_input.text = ''
class InstallWizard(BaseWizard, Widget):
'''
events::
`on_wizard_complete` Fired when the wizard is done creating/ restoring
wallet/s.
'''
__events__ = ('on_wizard_complete', )
2016-06-16 19:25:44 +02:00
def on_wizard_complete(self, wallet):
"""overriden by main_window"""
pass
def waiting_dialog(self, task, msg, on_finished=None):
'''Perform a blocking task in the background by running the passed
method in a thread.
'''
def target():
# run your threaded function
try:
task()
except Exception as err:
2016-09-28 12:48:46 +02:00
self.show_error(str(err))
# on completion hide message
Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1)
if on_finished:
2018-07-24 18:57:49 +02:00
def protected_on_finished():
try:
on_finished()
except Exception as e:
self.show_error(str(e))
Clock.schedule_once(lambda dt: protected_on_finished(), -1)
2016-01-16 12:01:37 +01:00
2017-02-16 10:54:24 +01:00
app = App.get_running_app()
app.show_info_bubble(
2018-07-11 17:38:47 +02:00
text=msg, icon='atlas://electrum/gui/kivy/theming/light/important',
pos=Window.center, width='200sp', arrow_pos=None, modal=True)
t = threading.Thread(target = target)
t.start()
2019-05-11 19:36:57 +02:00
def terminate(self, *, storage=None, aborted=False):
if storage is None and not aborted:
storage = self.create_storage(self.path)
self.dispatch('on_wizard_complete', storage)
2016-06-20 16:25:11 +02:00
def choice_dialog(self, **kwargs):
choices = kwargs['choices']
if len(choices) > 1:
2019-08-20 09:02:33 +02:00
WizardChoiceDialog(self, **kwargs).open()
else:
f = kwargs['run_next']
2017-08-01 05:22:18 +02:00
f(choices[0][0])
2016-06-20 16:25:11 +02:00
def multisig_dialog(self, **kwargs): WizardMultisigDialog(self, **kwargs).open()
def show_seed_dialog(self, **kwargs): ShowSeedDialog(self, **kwargs).open()
def line_dialog(self, **kwargs): LineDialog(self, **kwargs).open()
2019-08-13 15:16:49 +02:00
def choice_and_line_dialog(self, **kwargs): ChoiceLineDialog(self, **kwargs).open()
2016-07-01 11:44:26 +02:00
def confirm_seed_dialog(self, **kwargs):
kwargs['title'] = _('Confirm Seed')
kwargs['message'] = _('Please retype your seed phrase, to confirm that you properly saved it')
ConfirmSeedDialog(self, **kwargs).open()
2016-07-01 11:44:26 +02:00
def restore_seed_dialog(self, **kwargs):
RestoreSeedDialog(self, **kwargs).open()
2018-05-18 18:07:52 +02:00
def confirm_dialog(self, **kwargs):
WizardConfirmDialog(self, **kwargs).open()
def tos_dialog(self, **kwargs):
WizardTOSDialog(self, **kwargs).open()
def email_dialog(self, **kwargs):
WizardEmailDialog(self, **kwargs).open()
def otp_dialog(self, **kwargs):
if kwargs['otp_secret']:
WizardNewOTPDialog(self, **kwargs).open()
else:
WizardKnownOTPDialog(self, **kwargs).open()
def add_xpub_dialog(self, **kwargs):
2016-07-01 11:44:26 +02:00
kwargs['message'] += ' ' + _('Use the camera button to scan a QR code.')
AddXpubDialog(self, **kwargs).open()
def add_cosigner_dialog(self, **kwargs):
kwargs['title'] = _("Add Cosigner") + " %d"%kwargs['index']
kwargs['message'] = _('Please paste your cosigners master public key, or scan it using the camera button.')
AddXpubDialog(self, **kwargs).open()
2016-06-20 16:25:11 +02:00
def show_xpub_dialog(self, **kwargs): ShowXpubDialog(self, **kwargs).open()
2018-05-18 18:07:52 +02:00
def show_message(self, msg): self.show_error(msg)
2016-06-20 16:25:11 +02:00
def show_error(self, msg):
2017-02-16 10:54:24 +01:00
app = App.get_running_app()
2016-09-28 12:48:46 +02:00
Clock.schedule_once(lambda dt: app.show_error(msg))
2016-01-14 16:54:37 +01:00
2018-03-22 16:39:01 +01:00
def request_password(self, run_next, force_disable_encrypt_cb=False):
if force_disable_encrypt_cb:
# do not request PIN for watching-only wallets
run_next(None, False)
return
2018-03-22 16:39:01 +01:00
def on_success(old_pin, pin):
assert old_pin is None
run_next(pin, False)
def on_failure():
self.show_error(_('PIN mismatch'))
self.run('request_password', run_next)
2016-03-06 13:23:31 +01:00
popup = PasswordDialog()
2018-03-22 16:39:01 +01:00
app = App.get_running_app()
popup.init(app, None, _('Choose PIN code'), on_success, on_failure, is_change=2)
2016-01-14 16:54:37 +01:00
popup.open()
2016-06-20 16:25:11 +02:00
def action_dialog(self, action, run_next):
f = getattr(self, action)
f()