URI: 
       tinstallwizard.py - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
       tinstallwizard.py (34286B)
       ---
            1 
            2 from functools import partial
            3 import threading
            4 import os
            5 from typing import TYPE_CHECKING
            6 
            7 from kivy.app import App
            8 from kivy.clock import Clock
            9 from kivy.lang import Builder
           10 from kivy.properties import ObjectProperty, StringProperty, OptionProperty
           11 from kivy.core.window import Window
           12 from kivy.uix.button import Button
           13 from kivy.uix.togglebutton import ToggleButton
           14 from kivy.utils import platform
           15 from kivy.uix.widget import Widget
           16 from kivy.core.window import Window
           17 from kivy.clock import Clock
           18 from kivy.utils import platform
           19 
           20 from electrum.base_wizard import BaseWizard
           21 from electrum.util import is_valid_email
           22 
           23 
           24 from . import EventsDialog
           25 from ...i18n import _
           26 from .password_dialog import PasswordDialog
           27 
           28 if TYPE_CHECKING:
           29     from electrum.gui.kivy.main_window import ElectrumWindow
           30 
           31 
           32 # global Variables
           33 
           34 Builder.load_string('''
           35 #:import Window kivy.core.window.Window
           36 #:import _ electrum.gui.kivy.i18n._
           37 #:import KIVY_GUI_PATH electrum.gui.kivy.KIVY_GUI_PATH
           38 
           39 
           40 <WizardTextInput@TextInput>
           41     border: 4, 4, 4, 4
           42     font_size: '15sp'
           43     padding: '15dp', '15dp'
           44     background_color: (1, 1, 1, 1) if self.focus else (0.454, 0.698, 0.909, 1)
           45     foreground_color: (0.31, 0.31, 0.31, 1) if self.focus else (0.835, 0.909, 0.972, 1)
           46     hint_text_color: self.foreground_color
           47     background_active: f'atlas://{KIVY_GUI_PATH}/theming/light/create_act_text_active'
           48     background_normal: f'atlas://{KIVY_GUI_PATH}/theming/light/create_act_text_active'
           49     size_hint_y: None
           50     height: '48sp'
           51 
           52 <WizardButton@Button>:
           53     root: None
           54     size_hint: 1, None
           55     height: '48sp'
           56     on_press: if self.root: self.root.dispatch('on_press', self)
           57     on_release: if self.root: self.root.dispatch('on_release', self)
           58 
           59 <BigLabel@Label>
           60     color: .854, .925, .984, 1
           61     size_hint: 1, None
           62     text_size: self.width, None
           63     height: self.texture_size[1]
           64     bold: True
           65 
           66 <-WizardDialog>
           67     text_color: .854, .925, .984, 1
           68     value: ''
           69     #auto_dismiss: False
           70     size_hint: None, None
           71     canvas.before:
           72         Color:
           73             rgba: .239, .588, .882, 1
           74         Rectangle:
           75             size: Window.size
           76 
           77     crcontent: crcontent
           78     # add electrum icon
           79     BoxLayout:
           80         orientation: 'vertical' if self.width < self.height else 'horizontal'
           81         padding:
           82             min(dp(27), self.width/32), min(dp(27), self.height/32),\
           83             min(dp(27), self.width/32), min(dp(27), self.height/32)
           84         spacing: '10dp'
           85         GridLayout:
           86             id: grid_logo
           87             cols: 1
           88             pos_hint: {'center_y': .5}
           89             size_hint: 1, None
           90             height: self.minimum_height
           91             Label:
           92                 color: root.text_color
           93                 text: 'ELECTRUM'
           94                 size_hint: 1, None
           95                 height: self.texture_size[1] if self.opacity else 0
           96                 font_size: '33sp'
           97                 font_name: f'{KIVY_GUI_PATH}/data/fonts/tron/Tr2n.ttf'
           98         GridLayout:
           99             cols: 1
          100             id: crcontent
          101             spacing: '1dp'
          102         Widget:
          103             size_hint: 1, 0.3
          104         GridLayout:
          105             rows: 1
          106             spacing: '12dp'
          107             size_hint: 1, None
          108             height: self.minimum_height
          109             WizardButton:
          110                 id: back
          111                 text: _('Back')
          112                 root: root
          113             WizardButton:
          114                 id: next
          115                 text: _('Next')
          116                 root: root
          117                 disabled: root.value == ''
          118 
          119 
          120 <WizardMultisigDialog>
          121     value: 'next'
          122     Widget
          123         size_hint: 1, 1
          124     Label:
          125         color: root.text_color
          126         size_hint: 1, None
          127         text_size: self.width, None
          128         height: self.texture_size[1]
          129         text: _("Choose the number of signatures needed to unlock funds in your wallet")
          130     Widget
          131         size_hint: 1, 1
          132     GridLayout:
          133         cols: 2
          134         spacing: '14dp'
          135         size_hint: 1, 1
          136         height: self.minimum_height
          137         Label:
          138             color: root.text_color
          139             text: _('From {} cosigners').format(n.value)
          140         Slider:
          141             id: n
          142             range: 2, 5
          143             step: 1
          144             value: 2
          145         Label:
          146             color: root.text_color
          147             text: _('Require {} signatures').format(m.value)
          148         Slider:
          149             id: m
          150             range: 1, n.value
          151             step: 1
          152             value: 2
          153     Widget
          154         size_hint: 1, 1
          155     Label:
          156         id: backup_warning_label
          157         color: root.text_color
          158         size_hint: 1, None
          159         text_size: self.width, None
          160         height: self.texture_size[1]
          161         opacity: int(m.value != n.value)
          162         text: _("Warning: to be able to restore a multisig wallet, " \
          163                 "you should include the master public key for each cosigner " \
          164                 "in all of your backups.")
          165 
          166 
          167 <WizardChoiceDialog>
          168     message : ''
          169     Widget:
          170         size_hint: 1, 1
          171     Label:
          172         color: root.text_color
          173         size_hint: 1, None
          174         text_size: self.width, None
          175         height: self.texture_size[1]
          176         text: root.message
          177     Widget
          178         size_hint: 1, 1
          179     GridLayout:
          180         row_default_height: '48dp'
          181         id: choices
          182         cols: 1
          183         spacing: '14dp'
          184         size_hint: 1, None
          185 
          186 <WizardConfirmDialog>
          187     message : ''
          188     Widget:
          189         size_hint: 1, 1
          190     Label:
          191         color: root.text_color
          192         size_hint: 1, None
          193         text_size: self.width, None
          194         height: self.texture_size[1]
          195         text: root.message
          196     Widget
          197         size_hint: 1, 1
          198 
          199 <WizardTOSDialog>
          200     message : ''
          201     size_hint: 1, 1
          202     ScrollView:
          203         size_hint: 1, 1
          204         TextInput:
          205             color: root.text_color
          206             size_hint: 1, None
          207             text_size: self.width, None
          208             height: self.minimum_height
          209             text: root.message
          210             disabled: True
          211 
          212 <WizardEmailDialog>
          213     Label:
          214         color: root.text_color
          215         size_hint: 1, None
          216         text_size: self.width, None
          217         height: self.texture_size[1]
          218         text: 'Please enter your email address'
          219     WizardTextInput:
          220         id: email
          221         on_text: Clock.schedule_once(root.on_text)
          222         multiline: False
          223         on_text_validate: Clock.schedule_once(root.on_enter)
          224 
          225 <WizardKnownOTPDialog>
          226     message : ''
          227     message2: ''
          228     Widget:
          229         size_hint: 1, 1
          230     Label:
          231         color: root.text_color
          232         size_hint: 1, None
          233         text_size: self.width, None
          234         height: self.texture_size[1]
          235         text: root.message
          236     Widget
          237         size_hint: 1, 1
          238     WizardTextInput:
          239         id: otp
          240         on_text: Clock.schedule_once(root.on_text)
          241         multiline: False
          242         on_text_validate: Clock.schedule_once(root.on_enter)
          243     Widget
          244         size_hint: 1, 1
          245     Label:
          246         color: root.text_color
          247         size_hint: 1, None
          248         text_size: self.width, None
          249         height: self.texture_size[1]
          250         text: root.message2
          251     Widget
          252         size_hint: 1, 1
          253         height: '48sp'
          254     BoxLayout:
          255         orientation: 'horizontal'
          256         WizardButton:
          257             id: cb
          258             text: _('Request new secret')
          259             on_release: root.request_new_secret()
          260             size_hint: 1, None
          261         WizardButton:
          262             id: abort
          263             text: _('Abort creation')
          264             on_release: root.abort_wallet_creation()
          265             size_hint: 1, None
          266 
          267 
          268 <WizardNewOTPDialog>
          269     message : ''
          270     message2 : ''
          271     Label:
          272         color: root.text_color
          273         size_hint: 1, None
          274         text_size: self.width, None
          275         height: self.texture_size[1]
          276         text: root.message
          277     QRCodeWidget:
          278         id: qr
          279         size_hint: 1, 1
          280     Label:
          281         color: root.text_color
          282         size_hint: 1, None
          283         text_size: self.width, None
          284         height: self.texture_size[1]
          285         text: root.message2
          286     WizardTextInput:
          287         id: otp
          288         on_text: Clock.schedule_once(root.on_text)
          289         multiline: False
          290         on_text_validate: Clock.schedule_once(root.on_enter)
          291 
          292 <MButton@Button>:
          293     size_hint: 1, None
          294     height: '33dp'
          295     on_release:
          296         self.parent.update_amount(self.text)
          297 
          298 <WordButton@Button>:
          299     size_hint: None, None
          300     padding: '5dp', '5dp'
          301     text_size: None, self.height
          302     width: self.texture_size[0]
          303     height: '30dp'
          304     on_release:
          305         if self.parent: self.parent.new_word(self.text)
          306 
          307 
          308 <SeedButton@Button>:
          309     height: dp(100)
          310     border: 4, 4, 4, 4
          311     halign: 'justify'
          312     valign: 'top'
          313     font_size: '18dp'
          314     text_size: self.width - dp(24), self.height - dp(12)
          315     color: .1, .1, .1, 1
          316     background_normal: f'atlas://{KIVY_GUI_PATH}/theming/light/white_bg_round_top'
          317     background_down: self.background_normal
          318     size_hint_y: None
          319 
          320 
          321 <SeedLabel@Label>:
          322     font_size: '12sp'
          323     text_size: self.width, None
          324     size_hint: 1, None
          325     height: self.texture_size[1]
          326     halign: 'justify'
          327     valign: 'middle'
          328     border: 4, 4, 4, 4
          329 
          330 <SeedDialogHeader@GridLayout>
          331     text: ''
          332     options_dialog: None
          333     rows: 1
          334     size_hint: 1, None
          335     height: self.minimum_height
          336     BigLabel:
          337         size_hint: 9, None
          338         text: root.text
          339     IconButton:
          340         id: options_button
          341         height: '30dp'
          342         width: '30dp'
          343         size_hint: 1, None
          344         icon: f'atlas://{KIVY_GUI_PATH}/theming/light/gear'
          345         on_release:
          346             root.options_dialog() if root.options_dialog else None
          347 
          348 <RestoreSeedDialog>
          349     message: ''
          350     word: ''
          351     SeedDialogHeader:
          352         id: seed_dialog_header
          353         text: 'ENTER YOUR SEED PHRASE'
          354         options_dialog: root.options_dialog
          355     GridLayout:
          356         cols: 1
          357         padding: 0, '12dp'
          358         spacing: '12dp'
          359         size_hint: 1, None
          360         height: self.minimum_height
          361         SeedButton:
          362             id: text_input_seed
          363             text: ''
          364             on_text: Clock.schedule_once(root.on_text)
          365         SeedLabel:
          366             text: root.message
          367         BoxLayout:
          368             id: suggestions
          369             height: '35dp'
          370             size_hint: 1, None
          371             new_word: root.on_word
          372         BoxLayout:
          373             id: line1
          374             update_amount: root.update_text
          375             size_hint: 1, None
          376             height: '30dp'
          377             MButton:
          378                 text: 'Q'
          379             MButton:
          380                 text: 'W'
          381             MButton:
          382                 text: 'E'
          383             MButton:
          384                 text: 'R'
          385             MButton:
          386                 text: 'T'
          387             MButton:
          388                 text: 'Y'
          389             MButton:
          390                 text: 'U'
          391             MButton:
          392                 text: 'I'
          393             MButton:
          394                 text: 'O'
          395             MButton:
          396                 text: 'P'
          397         BoxLayout:
          398             id: line2
          399             update_amount: root.update_text
          400             size_hint: 1, None
          401             height: '30dp'
          402             Widget:
          403                 size_hint: 0.5, None
          404                 height: '33dp'
          405             MButton:
          406                 text: 'A'
          407             MButton:
          408                 text: 'S'
          409             MButton:
          410                 text: 'D'
          411             MButton:
          412                 text: 'F'
          413             MButton:
          414                 text: 'G'
          415             MButton:
          416                 text: 'H'
          417             MButton:
          418                 text: 'J'
          419             MButton:
          420                 text: 'K'
          421             MButton:
          422                 text: 'L'
          423             Widget:
          424                 size_hint: 0.5, None
          425                 height: '33dp'
          426         BoxLayout:
          427             id: line3
          428             update_amount: root.update_text
          429             size_hint: 1, None
          430             height: '30dp'
          431             Widget:
          432                 size_hint: 1, None
          433             MButton:
          434                 text: 'Z'
          435             MButton:
          436                 text: 'X'
          437             MButton:
          438                 text: 'C'
          439             MButton:
          440                 text: 'V'
          441             MButton:
          442                 text: 'B'
          443             MButton:
          444                 text: 'N'
          445             MButton:
          446                 text: 'M'
          447             MButton:
          448                 text: ' '
          449             MButton:
          450                 text: '<'
          451 
          452 <AddXpubDialog>
          453     title: ''
          454     message: ''
          455     BigLabel:
          456         text: root.title
          457     GridLayout
          458         cols: 1
          459         padding: 0, '12dp'
          460         spacing: '12dp'
          461         size_hint: 1, None
          462         height: self.minimum_height
          463         SeedButton:
          464             id: text_input
          465             text: ''
          466             on_text: Clock.schedule_once(root.check_text)
          467         SeedLabel:
          468             text: root.message
          469     GridLayout
          470         rows: 1
          471         spacing: '12dp'
          472         size_hint: 1, None
          473         height: self.minimum_height
          474         IconButton:
          475             id: scan
          476             height: '48sp'
          477             on_release: root.scan_xpub()
          478             icon: f'atlas://{KIVY_GUI_PATH}/theming/light/camera'
          479             size_hint: 1, None
          480         WizardButton:
          481             text: _('Paste')
          482             on_release: root.do_paste()
          483         WizardButton:
          484             text: _('Clear')
          485             on_release: root.do_clear()
          486 
          487 
          488 <ShowXpubDialog>
          489     xpub: ''
          490     message: _('Here is your master public key. Share it with your cosigners.')
          491     BigLabel:
          492         text: "MASTER PUBLIC KEY"
          493     GridLayout
          494         cols: 1
          495         padding: 0, '12dp'
          496         spacing: '12dp'
          497         size_hint: 1, None
          498         height: self.minimum_height
          499         SeedButton:
          500             id: text_input
          501             text: root.xpub
          502         SeedLabel:
          503             text: root.message
          504     GridLayout
          505         rows: 1
          506         spacing: '12dp'
          507         size_hint: 1, None
          508         height: self.minimum_height
          509         WizardButton:
          510             text: _('QR code')
          511             on_release: root.do_qr()
          512         WizardButton:
          513             text: _('Copy')
          514             on_release: root.do_copy()
          515         WizardButton:
          516             text: _('Share')
          517             on_release: root.do_share()
          518 
          519 <ShowSeedDialog>
          520     spacing: '12dp'
          521     value: 'next'
          522     SeedDialogHeader:
          523         text: "PLEASE WRITE DOWN YOUR SEED PHRASE"
          524         options_dialog: root.options_dialog
          525     GridLayout:
          526         id: grid
          527         cols: 1
          528         pos_hint: {'center_y': .5}
          529         size_hint_y: None
          530         height: self.minimum_height
          531         spacing: '12dp'
          532         SeedButton:
          533             text: root.seed_text
          534         SeedLabel:
          535             text: root.message
          536 
          537 <LineDialog>
          538     BigLabel:
          539         text: root.title
          540     SeedLabel:
          541         text: root.message
          542     TextInput:
          543         id: passphrase_input
          544         multiline: False
          545         size_hint: 1, None
          546         height: '48dp'
          547         on_text: Clock.schedule_once(root.on_text)
          548     SeedLabel:
          549         text: root.warning
          550 
          551 <ChoiceLineDialog>
          552     BigLabel:
          553         text: root.title
          554     SeedLabel:
          555         text: root.message1
          556     GridLayout:
          557         row_default_height: '48dp'
          558         id: choices
          559         cols: 1
          560         spacing: '14dp'
          561         size_hint: 1, None
          562     SeedLabel:
          563         text: root.message2
          564     TextInput:
          565         id: text_input
          566         multiline: False
          567         size_hint: 1, None
          568         height: '48dp'
          569 
          570 ''')
          571 
          572 
          573 
          574 class WizardDialog(EventsDialog):
          575     ''' Abstract dialog to be used as the base for all Create Account Dialogs
          576     '''
          577     crcontent = ObjectProperty(None)
          578 
          579     def __init__(self, wizard, **kwargs):
          580         self.auto_dismiss = False
          581         super(WizardDialog, self).__init__()
          582         self.wizard = wizard
          583         self.ids.back.disabled = not wizard.can_go_back()
          584         self.app = App.get_running_app()
          585         self.run_next = kwargs['run_next']
          586 
          587         self._trigger_size_dialog = Clock.create_trigger(self._size_dialog, -1)
          588         # note: everything bound here needs to be unbound as otherwise the
          589         # objects will be kept around and keep receiving the callbacks
          590         Window.bind(size=self._trigger_size_dialog,
          591                     rotation=self._trigger_size_dialog,
          592                     on_keyboard=self.on_keyboard)
          593         self._trigger_size_dialog()
          594         self._on_release = False
          595 
          596     def _size_dialog(self, dt):
          597         if self.app.ui_mode[0] == 'p':
          598             self.size = Window.size
          599         else:
          600             #tablet
          601             if self.app.orientation[0] == 'p':
          602                 #portrait
          603                 self.size = Window.size[0]/1.67, Window.size[1]/1.4
          604             else:
          605                 self.size = Window.size[0]/2.5, Window.size[1]
          606 
          607     def add_widget(self, widget, index=0):
          608         if not self.crcontent:
          609             super(WizardDialog, self).add_widget(widget)
          610         else:
          611             self.crcontent.add_widget(widget, index=index)
          612 
          613     def on_keyboard(self, instance, key, keycode, codepoint, modifier):
          614         if key == 27:
          615             if self.wizard.can_go_back():
          616                 self.dismiss()
          617                 self.wizard.go_back()
          618             else:
          619                 if not self.app.is_exit:
          620                     self.app.is_exit = True
          621                     self.app.show_info(_('Press again to exit'))
          622                 else:
          623                     self._on_release = False
          624                     self.dismiss()
          625             return True
          626 
          627     def on_dismiss(self):
          628         Window.unbind(size=self._trigger_size_dialog,
          629                       rotation=self._trigger_size_dialog,
          630                       on_keyboard=self.on_keyboard)
          631         if self.app.wallet is None and not self._on_release:
          632             self.app.stop()
          633 
          634     def get_params(self, button):
          635         return (None,)
          636 
          637     def on_release(self, button):
          638         if self._on_release is True:
          639             return
          640         self._on_release = True
          641         self.dismiss()
          642         if not button:
          643             self.wizard.terminate(aborted=True)
          644             return
          645         if button is self.ids.back:
          646             self.wizard.go_back()
          647             return
          648         params = self.get_params(button)
          649         self.run_next(*params)
          650 
          651 
          652 class WizardMultisigDialog(WizardDialog):
          653 
          654     def get_params(self, button):
          655         m = self.ids.m.value
          656         n = self.ids.n.value
          657         return m, n
          658 
          659 
          660 class WizardOTPDialogBase(WizardDialog):
          661 
          662     def get_otp(self):
          663         otp = self.ids.otp.text
          664         if len(otp) != 6:
          665             return
          666         try:
          667             return int(otp)
          668         except:
          669             return
          670 
          671     def on_text(self, dt):
          672         self.ids.next.disabled = self.get_otp() is None
          673 
          674     def on_enter(self, dt):
          675         # press next
          676         next = self.ids.next
          677         if not next.disabled:
          678             next.dispatch('on_release')
          679 
          680 
          681 class WizardKnownOTPDialog(WizardOTPDialogBase):
          682 
          683     def __init__(self, wizard, **kwargs):
          684         WizardOTPDialogBase.__init__(self, wizard, **kwargs)
          685         self.message = _("This wallet is already registered with TrustedCoin. To finalize wallet creation, please enter your Google Authenticator Code.")
          686         self.message2 =_("If you have lost your Google Authenticator account, you can request a new secret. You will need to retype your seed.")
          687         self.request_new = False
          688 
          689     def get_params(self, button):
          690         return (self.get_otp(), self.request_new)
          691 
          692     def request_new_secret(self):
          693         self.request_new = True
          694         self.on_release(True)
          695 
          696     def abort_wallet_creation(self):
          697         self._on_release = True
          698         self.wizard.terminate(aborted=True)
          699         self.dismiss()
          700 
          701 
          702 class WizardNewOTPDialog(WizardOTPDialogBase):
          703 
          704     def __init__(self, wizard, **kwargs):
          705         WizardOTPDialogBase.__init__(self, wizard, **kwargs)
          706         otp_secret = kwargs['otp_secret']
          707         uri = "otpauth://totp/%s?secret=%s"%('trustedcoin.com', otp_secret)
          708         self.message = "Please scan the following QR code in Google Authenticator. You may also use the secret key: %s"%otp_secret
          709         self.message2 = _('Then, enter your Google Authenticator code:')
          710         self.ids.qr.set_data(uri)
          711 
          712     def get_params(self, button):
          713         return (self.get_otp(), False)
          714 
          715 class WizardTOSDialog(WizardDialog):
          716 
          717     def __init__(self, wizard, **kwargs):
          718         WizardDialog.__init__(self, wizard, **kwargs)
          719         self.ids.next.text = 'Accept'
          720         self.ids.next.disabled = False
          721         self.message = kwargs['tos']
          722 
          723 class WizardEmailDialog(WizardDialog):
          724 
          725     def get_params(self, button):
          726         return (self.ids.email.text,)
          727 
          728     def on_text(self, dt):
          729         self.ids.next.disabled = not is_valid_email(self.ids.email.text)
          730 
          731     def on_enter(self, dt):
          732         # press next
          733         next = self.ids.next
          734         if not next.disabled:
          735             next.dispatch('on_release')
          736 
          737 class WizardConfirmDialog(WizardDialog):
          738 
          739     def __init__(self, wizard, **kwargs):
          740         super(WizardConfirmDialog, self).__init__(wizard, **kwargs)
          741         self.message = kwargs.get('message', '')
          742         self.value = 'ok'
          743 
          744     def on_parent(self, instance, value):
          745         if value:
          746             self._back = _back = partial(self.app.dispatch, 'on_back')
          747 
          748     def get_params(self, button):
          749         return (True,)
          750 
          751 
          752 class WizardChoiceDialog(WizardDialog):
          753 
          754     def __init__(self, wizard, **kwargs):
          755         super(WizardChoiceDialog, self).__init__(wizard, **kwargs)
          756         self.title = kwargs.get('message', '')
          757         self.message = kwargs.get('message', '')
          758         choices = kwargs.get('choices', [])
          759         self.init_choices(choices)
          760 
          761     def init_choices(self, choices):
          762         layout = self.ids.choices
          763         layout.bind(minimum_height=layout.setter('height'))
          764         for action, text in choices:
          765             l = WizardButton(text=text)
          766             l.action = action
          767             l.height = '48dp'
          768             l.root = self
          769             layout.add_widget(l)
          770 
          771     def on_parent(self, instance, value):
          772         if value:
          773             self._back = _back = partial(self.app.dispatch, 'on_back')
          774 
          775     def get_params(self, button):
          776         return (button.action,)
          777 
          778 
          779 class LineDialog(WizardDialog):
          780     title = StringProperty('')
          781     message = StringProperty('')
          782     warning = StringProperty('')
          783 
          784     def __init__(self, wizard, **kwargs):
          785         WizardDialog.__init__(self, wizard, **kwargs)
          786         self.title = kwargs.get('title', '')
          787         self.message = kwargs.get('message', '')
          788         self.ids.next.disabled = True
          789         self.test = kwargs['test']
          790 
          791     def get_text(self):
          792         return self.ids.passphrase_input.text
          793 
          794     def on_text(self, dt):
          795         self.ids.next.disabled = not self.test(self.get_text())
          796 
          797     def get_params(self, b):
          798         return (self.get_text(),)
          799 
          800 class CLButton(ToggleButton):
          801     def on_release(self):
          802         self.root.script_type = self.script_type
          803         self.root.set_text(self.value)
          804 
          805 class ChoiceLineDialog(WizardChoiceDialog):
          806     title = StringProperty('')
          807     message1 = StringProperty('')
          808     message2 = StringProperty('')
          809 
          810     def __init__(self, wizard, **kwargs):
          811         WizardDialog.__init__(self, wizard, **kwargs)
          812         self.title = kwargs.get('title', '')
          813         self.message1 = kwargs.get('message1', '')
          814         self.message2 = kwargs.get('message2', '')
          815         self.choices = kwargs.get('choices', [])
          816         default_choice_idx = kwargs.get('default_choice_idx', 0)
          817         self.ids.next.disabled = False
          818         layout = self.ids.choices
          819         layout.bind(minimum_height=layout.setter('height'))
          820         for idx, (script_type, title, text) in enumerate(self.choices):
          821             b = CLButton(text=title, height='30dp', group=self.title, allow_no_selection=False)
          822             b.script_type = script_type
          823             b.root = self
          824             b.value = text
          825             layout.add_widget(b)
          826             if idx == default_choice_idx:
          827                 b.trigger_action(duration=0)
          828 
          829     def set_text(self, value):
          830         self.ids.text_input.text = value
          831 
          832     def get_params(self, b):
          833         return (self.ids.text_input.text, self.script_type)
          834 
          835 class ShowSeedDialog(WizardDialog):
          836     seed_text = StringProperty('')
          837     message = _("If you forget your PIN or lose your device, your seed phrase will be the only way to recover your funds.")
          838 
          839     def __init__(self, wizard, **kwargs):
          840         super(ShowSeedDialog, self).__init__(wizard, **kwargs)
          841         self.seed_text = kwargs['seed_text']
          842         self.opt_ext = True
          843         self.is_ext = False
          844 
          845     def on_parent(self, instance, value):
          846         if value:
          847             self._back = _back = partial(self.ids.back.dispatch, 'on_release')
          848 
          849     def options_dialog(self):
          850         from .seed_options import SeedOptionsDialog
          851         def callback(ext, _):
          852             self.is_ext = ext
          853         d = SeedOptionsDialog(self.opt_ext, False, self.is_ext, False, callback)
          854         d.open()
          855 
          856     def get_params(self, b):
          857         return (self.is_ext,)
          858 
          859 
          860 class WordButton(Button):
          861     pass
          862 
          863 class WizardButton(Button):
          864     pass
          865 
          866 
          867 class RestoreSeedDialog(WizardDialog):
          868 
          869     def __init__(self, wizard, **kwargs):
          870         super(RestoreSeedDialog, self).__init__(wizard, **kwargs)
          871         self._test = kwargs['test']
          872         from electrum.mnemonic import Mnemonic
          873         from electrum.old_mnemonic import wordlist as old_wordlist
          874         self.words = set(Mnemonic('en').wordlist).union(set(old_wordlist))
          875         self.ids.text_input_seed.text = ''
          876         self.message = _('Please type your seed phrase using the virtual keyboard.')
          877         self.title = _('Enter Seed')
          878         self.opt_ext = kwargs['opt_ext']
          879         self.opt_bip39 = kwargs['opt_bip39']
          880         self.is_ext = False
          881         self.is_bip39 = False
          882 
          883     def options_dialog(self):
          884         from .seed_options import SeedOptionsDialog
          885         def callback(ext, bip39):
          886             self.is_ext = ext
          887             self.is_bip39 = bip39
          888             self.update_next_button()
          889         d = SeedOptionsDialog(self.opt_ext, self.opt_bip39, self.is_ext, self.is_bip39, callback)
          890         d.open()
          891 
          892     def get_suggestions(self, prefix):
          893         for w in self.words:
          894             if w.startswith(prefix):
          895                 yield w
          896 
          897     def update_next_button(self):
          898         from electrum.keystore import bip39_is_checksum_valid
          899         text = self.get_text()
          900         if self.is_bip39:
          901             is_seed, is_wordlist = bip39_is_checksum_valid(text)
          902         else:
          903             is_seed = bool(self._test(text))
          904         self.ids.next.disabled = not is_seed
          905 
          906     def on_text(self, dt):
          907         self.update_next_button()
          908 
          909         text = self.ids.text_input_seed.text
          910         if not text:
          911             last_word = ''
          912         elif text[-1] == ' ':
          913             last_word = ''
          914         else:
          915             last_word = text.split(' ')[-1]
          916 
          917         enable_space = False
          918         self.ids.suggestions.clear_widgets()
          919         suggestions = [x for x in self.get_suggestions(last_word)]
          920 
          921         if last_word in suggestions:
          922             b = WordButton(text=last_word)
          923             self.ids.suggestions.add_widget(b)
          924             enable_space = True
          925 
          926         for w in suggestions:
          927             if w != last_word and len(suggestions) < 10:
          928                 b = WordButton(text=w)
          929                 self.ids.suggestions.add_widget(b)
          930 
          931         i = len(last_word)
          932         p = set()
          933         for x in suggestions:
          934             if len(x)>i: p.add(x[i])
          935 
          936         for line in [self.ids.line1, self.ids.line2, self.ids.line3]:
          937             for c in line.children:
          938                 if isinstance(c, Button):
          939                     if c.text in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
          940                         c.disabled = (c.text.lower() not in p) and bool(last_word)
          941                     elif c.text == ' ':
          942                         c.disabled = not enable_space
          943 
          944     def on_word(self, w):
          945         text = self.get_text()
          946         words = text.split(' ')
          947         words[-1] = w
          948         text = ' '.join(words)
          949         self.ids.text_input_seed.text = text + ' '
          950         self.ids.suggestions.clear_widgets()
          951 
          952     def get_text(self):
          953         ti = self.ids.text_input_seed
          954         return ' '.join(ti.text.strip().split())
          955 
          956     def update_text(self, c):
          957         c = c.lower()
          958         text = self.ids.text_input_seed.text
          959         if c == '<':
          960             text = text[:-1]
          961         else:
          962             text += c
          963         self.ids.text_input_seed.text = text
          964 
          965     def on_parent(self, instance, value):
          966         if value:
          967             tis = self.ids.text_input_seed
          968             tis.focus = True
          969             #tis._keyboard.bind(on_key_down=self.on_key_down)
          970             self._back = _back = partial(self.ids.back.dispatch,
          971                                          'on_release')
          972 
          973     def on_key_down(self, keyboard, keycode, key, modifiers):
          974         if keycode[0] in (13, 271):
          975             self.on_enter()
          976             return True
          977 
          978     def on_enter(self):
          979         #self._remove_keyboard()
          980         # press next
          981         next = self.ids.next
          982         if not next.disabled:
          983             next.dispatch('on_release')
          984 
          985     def _remove_keyboard(self):
          986         tis = self.ids.text_input_seed
          987         if tis._keyboard:
          988             tis._keyboard.unbind(on_key_down=self.on_key_down)
          989             tis.focus = False
          990 
          991     def get_params(self, b):
          992         return (self.get_text(), self.is_bip39, self.is_ext)
          993 
          994 
          995 class ConfirmSeedDialog(RestoreSeedDialog):
          996 
          997     def __init__(self, *args, **kwargs):
          998         RestoreSeedDialog.__init__(self, *args, **kwargs)
          999         self.ids.seed_dialog_header.ids.options_button.disabled = True
         1000         self.ids.text_input_seed.text = kwargs['seed']
         1001 
         1002     def get_params(self, b):
         1003         return (self.get_text(),)
         1004     def options_dialog(self):
         1005         pass
         1006 
         1007 
         1008 class ShowXpubDialog(WizardDialog):
         1009 
         1010     def __init__(self, wizard, **kwargs):
         1011         WizardDialog.__init__(self, wizard, **kwargs)
         1012         self.xpub = kwargs['xpub']
         1013         self.ids.next.disabled = False
         1014 
         1015     def do_copy(self):
         1016         self.app._clipboard.copy(self.xpub)
         1017 
         1018     def do_share(self):
         1019         self.app.do_share(self.xpub, _("Master Public Key"))
         1020 
         1021     def do_qr(self):
         1022         from .qr_dialog import QRDialog
         1023         popup = QRDialog(_("Master Public Key"), self.xpub, True)
         1024         popup.open()
         1025 
         1026 
         1027 class AddXpubDialog(WizardDialog):
         1028 
         1029     def __init__(self, wizard, **kwargs):
         1030         WizardDialog.__init__(self, wizard, **kwargs)
         1031         def is_valid(x):
         1032             try:
         1033                 return kwargs['is_valid'](x)
         1034             except:
         1035                 return False
         1036         self.is_valid = is_valid
         1037         self.title = kwargs['title']
         1038         self.message = kwargs['message']
         1039         self.allow_multi = kwargs.get('allow_multi', False)
         1040 
         1041     def check_text(self, dt):
         1042         self.ids.next.disabled = not bool(self.is_valid(self.get_text()))
         1043 
         1044     def get_text(self):
         1045         ti = self.ids.text_input
         1046         return ti.text.strip()
         1047 
         1048     def get_params(self, button):
         1049         return (self.get_text(),)
         1050 
         1051     def scan_xpub(self):
         1052         def on_complete(text):
         1053             if self.allow_multi:
         1054                 self.ids.text_input.text += text + '\n'
         1055             else:
         1056                 self.ids.text_input.text = text
         1057         self.app.scan_qr(on_complete)
         1058 
         1059     def do_paste(self):
         1060         self.ids.text_input.text = self.app._clipboard.paste()
         1061 
         1062     def do_clear(self):
         1063         self.ids.text_input.text = ''
         1064 
         1065 
         1066 
         1067 
         1068 class InstallWizard(BaseWizard, Widget):
         1069 
         1070     def __init__(self, *args, **kwargs):
         1071         BaseWizard.__init__(self, *args, **kwargs)
         1072         self.app = App.get_running_app()
         1073 
         1074     def terminate(self, *, storage=None, db=None, aborted=False):
         1075         # storage must be None because manual upgrades are disabled on Kivy
         1076         assert storage is None
         1077         if not aborted:
         1078             password = self.pw_args.password
         1079             storage, db = self.create_storage(self.path)
         1080             self.app.on_wizard_success(storage, db, password)
         1081         else:
         1082             try: os.unlink(self.path)
         1083             except FileNotFoundError: pass
         1084             self.reset_stack()
         1085             self.confirm_dialog(message=_('Wallet creation failed'), run_next=lambda x: self.app.on_wizard_aborted())
         1086 
         1087     def choice_dialog(self, **kwargs):
         1088         choices = kwargs['choices']
         1089         if len(choices) > 1:
         1090             WizardChoiceDialog(self, **kwargs).open()
         1091         else:
         1092             f = kwargs['run_next']
         1093             f(choices[0][0])
         1094 
         1095     def multisig_dialog(self, **kwargs): WizardMultisigDialog(self, **kwargs).open()
         1096     def show_seed_dialog(self, **kwargs): ShowSeedDialog(self, **kwargs).open()
         1097     def line_dialog(self, **kwargs): LineDialog(self, **kwargs).open()
         1098     def derivation_and_script_type_gui_specific_dialog(self, **kwargs): ChoiceLineDialog(self, **kwargs).open()
         1099 
         1100     def confirm_seed_dialog(self, **kwargs):
         1101         kwargs['title'] = _('Confirm Seed')
         1102         kwargs['message'] = _('Please retype your seed phrase, to confirm that you properly saved it')
         1103         kwargs['opt_bip39'] = self.opt_bip39
         1104         kwargs['opt_ext'] = self.opt_ext
         1105         ConfirmSeedDialog(self, **kwargs).open()
         1106 
         1107     def restore_seed_dialog(self, **kwargs):
         1108         kwargs['opt_bip39'] = self.opt_bip39
         1109         kwargs['opt_ext'] = self.opt_ext
         1110         RestoreSeedDialog(self, **kwargs).open()
         1111 
         1112     def confirm_dialog(self, **kwargs):
         1113         WizardConfirmDialog(self, **kwargs).open()
         1114 
         1115     def tos_dialog(self, **kwargs):
         1116         WizardTOSDialog(self, **kwargs).open()
         1117 
         1118     def email_dialog(self, **kwargs):
         1119         WizardEmailDialog(self, **kwargs).open()
         1120 
         1121     def otp_dialog(self, **kwargs):
         1122         if kwargs['otp_secret']:
         1123             WizardNewOTPDialog(self, **kwargs).open()
         1124         else:
         1125             WizardKnownOTPDialog(self, **kwargs).open()
         1126 
         1127     def add_xpub_dialog(self, **kwargs):
         1128         kwargs['message'] += ' ' + _('Use the camera button to scan a QR code.')
         1129         AddXpubDialog(self, **kwargs).open()
         1130 
         1131     def add_cosigner_dialog(self, **kwargs):
         1132         kwargs['title'] = _("Add Cosigner") + " %d"%kwargs['index']
         1133         kwargs['message'] = _('Please paste your cosigners master public key, or scan it using the camera button.')
         1134         AddXpubDialog(self, **kwargs).open()
         1135 
         1136     def show_xpub_dialog(self, **kwargs): ShowXpubDialog(self, **kwargs).open()
         1137 
         1138     def show_message(self, msg): self.show_error(msg)
         1139 
         1140     def show_error(self, msg):
         1141         Clock.schedule_once(lambda dt: self.app.show_error(msg))
         1142 
         1143     def request_password(self, run_next, force_disable_encrypt_cb=False):
         1144         if self.app.password is not None:
         1145             run_next(self.app.password, True)
         1146             return
         1147         def on_success(old_pw, pw):
         1148             assert old_pw is None
         1149             run_next(pw, True)
         1150         def on_failure():
         1151             self.show_error(_('Password mismatch'))
         1152             self.request_password(run_next)
         1153         popup = PasswordDialog(
         1154             self.app,
         1155             check_password=lambda x:True,
         1156             on_success=on_success,
         1157             on_failure=on_failure,
         1158             is_change=True,
         1159             is_password=True,
         1160             message=_('Choose a password'))
         1161         popup.open()
         1162 
         1163     def action_dialog(self, action, run_next):
         1164         f = getattr(self, action)
         1165         f()