tkivy wizard: use own soft keyboard - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 656069070a9ba8c68c066226bdd4c02883f7c7ae DIR parent 950f3ae633ae2d774dbbd0f60dd23cd8be06c624 HTML Author: ThomasV <thomasv@electrum.org> Date: Sat, 13 Feb 2016 15:10:17 +0100 kivy wizard: use own soft keyboard Diffstat: M gui/kivy/main_window.py | 4 ++-- M gui/kivy/uix/dialogs/create_restor… | 215 +++++++++++++++++++++++-------- M lib/mnemonic.py | 5 +++++ 3 files changed, 168 insertions(+), 56 deletions(-) --- DIR diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py t@@ -230,11 +230,11 @@ class ElectrumWindow(App): def set_URI(self, url): try: - url = electrum.util.parse_URI(url, self.on_pr) + d = electrum.util.parse_URI(url, self.on_pr) except: self.show_info(_("Not a Bitcoin URI") + ':\n', url) return - self.send_screen.set_URI(url) + self.send_screen.set_URI(d) def on_qr(self, data): if data.startswith('bitcoin:'): DIR diff --git a/gui/kivy/uix/dialogs/create_restore.py b/gui/kivy/uix/dialogs/create_restore.py t@@ -11,6 +11,7 @@ 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 electrum_gui.kivy.uix.dialogs import EventsDialog from electrum_gui.kivy.i18n import _ t@@ -60,62 +61,49 @@ Builder.load_string(''' BoxLayout: orientation: 'vertical' if self.width < self.height else 'horizontal' padding: - min(dp(42), self.width/8), min(dp(60), self.height/9.7),\ - min(dp(42), self.width/8), min(dp(72), self.height/8) + min(dp(42), self.width/16), min(dp(60), self.height/16),\ + min(dp(42), self.width/16), min(dp(72), self.height/16) spacing: '27dp' GridLayout: id: grid_logo cols: 1 pos_hint: {'center_y': .5} - size_hint: 1, .42 + size_hint: 1, None #height: self.minimum_height - Image: - id: logo_img - mipmap: True - allow_stretch: True - size_hint: 1, None - height: '110dp' - source: 'atlas://gui/kivy/theming/light/electrum_icon640' - Widget: - size_hint: 1, None - height: 0 if stepper.opacity else dp(15) Label: color: root.text_color - opacity: 0 if stepper.opacity else 1 text: 'ELECTRUM' size_hint: 1, None height: self.texture_size[1] if self.opacity else 0 font_size: '33sp' font_name: 'gui/kivy/data/fonts/tron/Tr2n.ttf' - Image: - id: stepper - allow_stretch: True - opacity: 0 - source: 'atlas://gui/kivy/theming/light/stepper_left' - size_hint: 1, None - height: grid_logo.height/2.5 if self.opacity else 0 - Widget: - size_hint: None, None - size: '5dp', '5dp' GridLayout: cols: 1 id: crcontent - spacing: '13dp' + spacing: '1dp' <CreateRestoreDialog> + Image: + id: logo_img + mipmap: True + allow_stretch: True + size_hint: 1, None + height: '110dp' + source: 'atlas://gui/kivy/theming/light/electrum_icon640' + 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: - _("Wallet file not found!!")+"\\n\\n" +\ + _("Wallet file not found")+"\\n\\n" +\ _("Do you want to create a new wallet ")+\ _("or restore an existing one?") Widget - size_hint: 1, None - height: dp(15) + size_hint: 1, 1 GridLayout: id: grid orientation: 'vertical' t@@ -133,7 +121,23 @@ Builder.load_string(''' root: root +<MButton@Button>: + size_hint: 1, None + height: '33dp' + on_release: + self.parent.update_amount(self.text) + +<WordButton@Button>: + size_hint: None, None + text_size: None, self.height + width: self.texture_size[0] + height: '30dp' + on_release: + self.parent.new_word(self.text) + + <RestoreSeedDialog> + word: '' Label: color: root.text_color size_hint: 1, None t@@ -147,13 +151,20 @@ Builder.load_string(''' spacing: '12dp' size_hint: 1, None height: self.minimum_height - WizardTextInput: + Button: + border: 4, 4, 4, 4 + halign: 'justify' + valign: 'middle' + font_size: self.width/15 + text_size: self.width - dp(24), self.height - dp(12) + color: .1, .1, .1, 1 + background_normal: 'atlas://gui/kivy/theming/light/white_bg_round_top' + background_down: self.background_normal id: text_input_seed - size_hint: 1, None - height: '110dp' - hint_text: - _('Enter your seedphrase') - on_text: root._trigger_check_seed() + size_hint_y: None + height: dp(100) + text: '' + on_text: Clock.schedule_once(root.on_text) Label: font_size: '12sp' text_size: self.width, None t@@ -165,6 +176,80 @@ Builder.load_string(''' on_ref_press: import webbrowser webbrowser.open('https://electrum.org/faq.html#seed') + + BoxLayout: + id: suggestions + height: '35dp' + size_hint: 1, None + new_word: root.on_word + + BoxLayout: + 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: + update_amount: root.update_text + size_hint: 1, None + height: '30dp' + 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' + BoxLayout: + update_amount: root.update_text + size_hint: 1, None + height: '30dp' + MButton: + text: 'Z' + MButton: + text: 'X' + MButton: + text: 'C' + MButton: + text: 'V' + MButton: + text: 'B' + MButton: + text: 'N' + MButton: + text: 'M' + MButton: + text: '<' + GridLayout: rows: 1 spacing: '12dp' t@@ -182,6 +267,7 @@ Builder.load_string(''' id: next text: _('Next') root: root + disabled: True <ShowSeedDialog> t@@ -206,8 +292,6 @@ Builder.load_string(''' valign: 'middle' font_size: self.width/15 text_size: self.width - dp(24), self.height - dp(12) - #size_hint: 1, None - #height: self.texture_size[1] + dp(24) color: .1, .1, .1, 1 background_normal: 'atlas://gui/kivy/theming/light/white_bg_round_top' background_down: self.background_normal t@@ -215,7 +299,6 @@ Builder.load_string(''' Label: rows: 1 size_hint: 1, .7 - id: but_seed border: 4, 4, 4, 4 halign: 'justify' valign: 'middle' t@@ -250,7 +333,6 @@ class WizardDialog(EventsDialog): Window.bind(size=_trigger_size_dialog, rotation=_trigger_size_dialog) _trigger_size_dialog() - Window.softinput_mode = 'pan' def _size_dialog(self, dt): app = App.get_running_app() t@@ -273,10 +355,7 @@ class WizardDialog(EventsDialog): def on_dismiss(self): app = App.get_running_app() if app.wallet is None and self._on_release is not None: - print "on dismiss: stopping app" app.stop() - else: - Window.softinput_mode = 'below_target' class CreateRestoreDialog(WizardDialog): t@@ -296,12 +375,12 @@ class ShowSeedDialog(WizardDialog): def on_parent(self, instance, value): if value: app = App.get_running_app() - stepper = self.ids.stepper - stepper.opacity = 1 - stepper.source = 'atlas://gui/kivy/theming/light/stepper_full' self._back = _back = partial(self.ids.back.dispatch, 'on_release') +class WordButton(Button): + pass + class RestoreSeedDialog(WizardDialog): message = StringProperty('') t@@ -309,10 +388,34 @@ class RestoreSeedDialog(WizardDialog): def __init__(self, **kwargs): super(RestoreSeedDialog, self).__init__(**kwargs) self._test = kwargs['test'] - self._trigger_check_seed = Clock.create_trigger(self.check_seed) + from electrum.mnemonic import Mnemonic + self.mnemonic = Mnemonic('en') + + def on_text(self, dt): + text = self.get_seed_text() + self.ids.next.disabled = not bool(self._test(text)) - def check_seed(self, dt): - self.ids.next.disabled = not bool(self._test(self.get_seed_text())) + if not text: + last_word = '' + elif text[-1] == ' ': + last_word = '' + else: + last_word = text.split(' ')[-1] + + self.ids.suggestions.clear_widgets() + suggestions = [x for x in self.mnemonic.get_suggestions(last_word)] + if suggestions and len(suggestions) < 10: + for w in suggestions: + b = WordButton(text=w) + self.ids.suggestions.add_widget(b) + + def on_word(self, w): + text = self.get_seed_text() + words = text.split(' ') + words[-1] = w + text = ' '.join(words) + self.ids.text_input_seed.text = text + ' ' + self.ids.suggestions.clear_widgets() def get_seed_text(self): ti = self.ids.text_input_seed t@@ -320,6 +423,15 @@ class RestoreSeedDialog(WizardDialog): text = ' '.join(text.split()) return text + 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 scan_seed(self): def on_complete(text): self.ids.text_input_seed.text = text t@@ -330,15 +442,10 @@ class RestoreSeedDialog(WizardDialog): if value: tis = self.ids.text_input_seed tis.focus = True - tis._keyboard.bind(on_key_down=self.on_key_down) - stepper = self.ids.stepper - stepper.opacity = 1 - stepper.source = ('atlas://gui/kivy/theming' - '/light/stepper_restore_seed') + #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() - #app.navigation_higherarchy.append(_back) def on_key_down(self, keyboard, keycode, key, modifiers): if keycode[0] in (13, 271): t@@ -359,7 +466,7 @@ class RestoreSeedDialog(WizardDialog): tis.focus = False def close(self): - self._remove_keyboard() + #self._remove_keyboard() app = App.get_running_app() #if self._back in app.navigation_higherarchy: # app.navigation_higherarchy.pop() DIR diff --git a/lib/mnemonic.py b/lib/mnemonic.py t@@ -132,6 +132,11 @@ class Mnemonic(object): words.append(self.wordlist[x]) return ' '.join(words) + def get_suggestions(self, prefix): + for w in self.wordlist: + if w.startswith(prefix) and w!=prefix: + yield w + def mnemonic_decode(self, seed): n = len(self.wordlist) words = seed.split()