diff --git a/data/fonts/Roboto-Bold.ttf b/data/fonts/Roboto-Bold.ttf index e5d828d8b..87d3af3e1 100644 Binary files a/data/fonts/Roboto-Bold.ttf and b/data/fonts/Roboto-Bold.ttf differ diff --git a/data/fonts/Roboto-Condensed.ttf b/data/fonts/Roboto-Condensed.ttf new file mode 100644 index 000000000..c38f7c881 Binary files /dev/null and b/data/fonts/Roboto-Condensed.ttf differ diff --git a/data/fonts/Roboto-Medium.ttf b/data/fonts/Roboto-Medium.ttf new file mode 100644 index 000000000..879834198 Binary files /dev/null and b/data/fonts/Roboto-Medium.ttf differ diff --git a/gui/kivy/Makefile b/gui/kivy/Makefile index 0bbb1bff0..97c7890fd 100644 --- a/gui/kivy/Makefile +++ b/gui/kivy/Makefile @@ -9,7 +9,7 @@ apk: # running pre build setup @cp tools/buildozer.spec ../../buildozer.spec # get aes.py - @cd ../..; wget -4 https://raw.github.com/devrandom/slowaes/master/python/aes.py + @cd ../..; curl -O https://raw.github.com/devrandom/slowaes/master/python/aes.py # rename electrum to main.py @mv ../../electrum ../../main.py @-if [ ! -d "../../.buildozer" ];then \ diff --git a/gui/kivy/__init__.py b/gui/kivy/__init__.py index cdaf3bcc3..9ffe888ad 100644 --- a/gui/kivy/__init__.py +++ b/gui/kivy/__init__.py @@ -21,7 +21,7 @@ import sys #, time, datetime, re, threading #from electrum.i18n import _, set_language -#from electrum.util import print_error, print_msg, parse_url +from electrum.util import print_error, print_msg, parse_url #:TODO: replace this with kivy's own plugin managment #from electrum.plugins import run_hook @@ -42,9 +42,8 @@ from kivy.logger import Logger from electrum.bitcoin import MIN_RELAY_TX_FEE -#:TODO main window from main_window import ElectrumWindow -from electrum.plugins import init_plugins +#from electrum.plugins import init_plugins #:TODO find a equivalent method to register to `bitcoin:` uri #: ref: http://stackoverflow.com/questions/30931/register-file-extensions-mime-types-in-linux @@ -60,7 +59,6 @@ from electrum.plugins import init_plugins # return True # return False - class ElectrumGui: def __init__(self, config, network, app=None): @@ -74,6 +72,47 @@ class ElectrumGui: # base #init_plugins(self) + def set_url(self, url): + from electrum import util + from decimal import Decimal + + try: + address, amount, label, message,\ + request_url, url = util.parse_url(url) + except Exception: + self.main_window.show_error(_('Invalid bitcoin URL')) + return + + if amount: + try: + if main_window.base_unit == 'mBTC': + amount = str( 1000* Decimal(amount)) + else: + amount = str(Decimal(amount)) + except Exception: + amount = "0.0" + self.main_window.show_error(_('Invalid Amount')) + + if request_url: + try: + from electrum import paymentrequest + except: + self.main_window.show_error("cannot import payment request") + request_url = None + + if not request_url: + self.main_window.set_send(address, amount, label, message) + return + + def payment_request(): + self.payment_request = paymentrequest.PaymentRequest(request_url) + if self.payment_request.verify(): + Clock.schedule_once(self.main_window.payment_request_ok) + else: + Clock.schedule_once(self.main_window.payment_request_error) + + threading.Thread(target=payment_request).start() + self.main_window.prepare_for_payment_request() def main(self, url): ''' The main entry point of the kivy ux @@ -83,5 +122,7 @@ class ElectrumGui: ''' self.main_window = w = ElectrumWindow(config=self.config, - network=self.network) - w.run() + network=self.network, + url=url, + gui_object=self) + w.run() \ No newline at end of file diff --git a/gui/kivy/carousel.py b/gui/kivy/carousel.py deleted file mode 100644 index 239a28cdd..000000000 --- a/gui/kivy/carousel.py +++ /dev/null @@ -1,32 +0,0 @@ -from kivy.uix.carousel import Carousel -from kivy.clock import Clock - -class Carousel(Carousel): - - def on_touch_move(self, touch): - if self._get_uid('cavoid') in touch.ud: - return - if self._touch is not touch: - super(Carousel, self).on_touch_move(touch) - return self._get_uid() in touch.ud - if touch.grab_current is not self: - return True - ud = touch.ud[self._get_uid()] - direction = self.direction - if ud['mode'] == 'unknown': - if direction[0] in ('r', 'l'): - distance = abs(touch.ox - touch.x) - else: - distance = abs(touch.oy - touch.y) - if distance > self.scroll_distance: - Clock.unschedule(self._change_touch_mode) - ud['mode'] = 'scroll' - else: - diff = 0 - if direction[0] in ('r', 'l'): - diff = touch.dx - if direction[0] in ('t', 'b'): - diff = touch.dy - - self._offset += diff * 1.27 - return True \ No newline at end of file diff --git a/gui/kivy/dialog.py b/gui/kivy/dialog.py deleted file mode 100644 index 502c6a92f..000000000 --- a/gui/kivy/dialog.py +++ /dev/null @@ -1,686 +0,0 @@ -from functools import partial - -from kivy.app import App -from kivy.factory import Factory -from kivy.uix.button import Button -from kivy.uix.bubble import Bubble -from kivy.uix.popup import Popup -from kivy.uix.widget import Widget -from kivy.uix.carousel import Carousel -from kivy.uix.tabbedpanel import TabbedPanelHeader -from kivy.properties import (NumericProperty, StringProperty, ListProperty, - ObjectProperty, AliasProperty, OptionProperty, - BooleanProperty) - -from kivy.animation import Animation -from kivy.core.window import Window -from kivy.clock import Clock -from kivy.lang import Builder -from kivy.metrics import dp, inch - -#from electrum.bitcoin import is_valid -from electrum.i18n import _ - -# Delayed inits -QRScanner = None -NFCSCanner = None -ScreenAddress = None -decode_uri = None - -DEFAULT_PATH = '/tmp/' -app = App.get_running_app() - -class CarouselHeader(TabbedPanelHeader): - - slide = NumericProperty(0) - ''' indicates the link to carousels slide''' - -class AnimatedPopup(Popup): - - def open(self): - self.opacity = 0 - super(AnimatedPopup, self).open() - anim = Animation(opacity=1, d=.5).start(self) - - def dismiss(self): - def on_complete(*l): - super(AnimatedPopup, self).dismiss() - anim = Animation(opacity=0, d=.5) - anim.bind(on_complete=on_complete) - anim.start(self) - - -class CarouselDialog(AnimatedPopup): - ''' A Popup dialog with a CarouselIndicator used as the content. - ''' - - carousel_content = ObjectProperty(None) - - def open(self): - self.opacity = 0 - super(CarouselDialog, self).open() - anim = Animation(opacity=1, d=.5).start(self) - - def dismiss(self): - def on_complete(*l): - super(CarouselDialog, self).dismiss() - anim = Animation(opacity=0, d=.5) - anim.bind(on_complete=on_complete) - anim.start(self) - - def add_widget(self, widget, index=0): - if isinstance(widget, Carousel): - super(CarouselDialog, self).add_widget(widget, index) - return - if 'carousel_content' not in self.ids.keys(): - super(CarouselDialog, self).add_widget(widget) - return - self.carousel_content.add_widget(widget, index) - - - -class NFCTransactionDialog(AnimatedPopup): - - mode = OptionProperty('send', options=('send','receive')) - - scanner = ObjectProperty(None) - - def __init__(self, **kwargs): - # Delayed Init - global NFCSCanner - if NFCSCanner is None: - from electrum_gui.kivy.nfc_scanner import NFCScanner - self.scanner = NFCSCanner - - super(NFCTransactionDialog, self).__init__(**kwargs) - self.scanner.nfc_init() - self.scanner.bind() - - def on_parent(self, instance, value): - sctr = self.ids.sctr - if value: - def _cmp(*l): - anim = Animation(rotation=2, scale=1, opacity=1) - anim.start(sctr) - anim.bind(on_complete=_start) - - def _start(*l): - anim = Animation(rotation=350, scale=2, opacity=0) - anim.start(sctr) - anim.bind(on_complete=_cmp) - _start() - return - Animation.cancel_all(sctr) - - -class InfoBubble(Bubble): - '''Bubble to be used to display short Help Information''' - - message = StringProperty(_('Nothing set !')) - '''Message to be displayed; defaults to "nothing set"''' - - icon = StringProperty('') - ''' Icon to be displayed along with the message defaults to '' - - :attr:`icon` is a `StringProperty` defaults to `''` - ''' - - fs = BooleanProperty(False) - ''' Show Bubble in half screen mode - - :attr:`fs` is a `BooleanProperty` defaults to `False` - ''' - - modal = BooleanProperty(False) - ''' Allow bubble to be hidden on touch. - - :attr:`modal` is a `BooleanProperty` defauult to `False`. - ''' - - exit = BooleanProperty(False) - '''Indicates whether to exit app after bubble is closed. - - :attr:`exit` is a `BooleanProperty` defaults to False. - ''' - - dim_background = BooleanProperty(False) - ''' Indicates Whether to draw a background on the windows behind the bubble. - - :attr:`dim` is a `BooleanProperty` defaults to `False`. - ''' - - def on_touch_down(self, touch): - if self.modal: - return True - self.hide() - if self.collide_point(*touch.pos): - return True - - def show(self, pos, duration, width=None, modal=False, exit=False): - '''Animate the bubble into position''' - self.modal, self.exit = modal, exit - if width: - self.width = width - if self.modal: - from kivy.uix.modalview import ModalView - self._modal_view = m = ModalView() - Window.add_widget(m) - m.add_widget(self) - else: - Window.add_widget(self) - # wait for the bubble to adjust it's size according to text then animate - Clock.schedule_once(lambda dt: self._show(pos, duration)) - - def _show(self, pos, duration): - - def on_stop(*l): - if duration: - Clock.schedule_once(self.hide, duration + .5) - - self.opacity = 0 - arrow_pos = self.arrow_pos - if arrow_pos[0] in ('l', 'r'): - pos = pos[0], pos[1] - (self.height/2) - else: - pos = pos[0] - (self.width/2), pos[1] - - self.limit_to = Window - - anim = Animation(opacity=1, pos=pos, d=.32) - anim.bind(on_complete=on_stop) - anim.cancel_all(self) - anim.start(self) - - - def hide(self, now=False): - ''' Auto fade out the Bubble - ''' - def on_stop(*l): - if self.modal: - m = self._modal_view - m.remove_widget(self) - Window.remove_widget(m) - Window.remove_widget(self) - if self.exit: - App.get_running_app().stop() - import sys - sys.exit() - if now: - return on_stop() - - anim = Animation(opacity=0, d=.25) - anim.bind(on_complete=on_stop) - anim.cancel_all(self) - anim.start(self) - - -class InfoContent(Widget): - '''Abstract class to be used to add to content to InfoDialog''' - pass - - -class InfoButton(Button): - '''Button that is auto added to the dialog when setting `buttons:` - property. - ''' - pass - - -class EventsDialog(AnimatedPopup): - ''' Abstract Popup that provides the following events - .. events:: - `on_release` - `on_press` - ''' - - __events__ = ('on_release', 'on_press') - - def __init__(self, **kwargs): - super(EventsDialog, self).__init__(**kwargs) - self._on_release = kwargs.get('on_release') - Window.bind(size=self.on_size, - rotation=self.on_size) - self.on_size(Window, Window.size) - - def on_size(self, instance, value): - 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 on_release(self, instance): - pass - - def on_press(self, instance): - pass - - def close(self): - self._on_release = None - self.dismiss() - - -class InfoDialog(EventsDialog): - ''' A dialog box meant to display info along with buttons at the bottom - - ''' - - buttons = ListProperty([_('ok'), _('cancel')]) - '''List of Buttons to be displayed at the bottom''' - - def __init__(self, **kwargs): - self._old_buttons = self.buttons - super(InfoDialog, self).__init__(**kwargs) - self.on_buttons(self, self.buttons) - - def on_buttons(self, instance, value): - if 'buttons_layout' not in self.ids.keys(): - return - if value == self._old_buttons: - return - blayout = self.ids.buttons_layout - blayout.clear_widgets() - for btn in value: - ib = InfoButton(text=btn) - ib.bind(on_press=partial(self.dispatch, 'on_press')) - ib.bind(on_release=partial(self.dispatch, 'on_release')) - blayout.add_widget(ib) - self._old_buttons = value - pass - - def add_widget(self, widget, index=0): - if isinstance(widget, InfoContent): - self.ids.info_content.add_widget(widget, index=index) - else: - super(InfoDialog, self).add_widget(widget) - - -class TakeInputDialog(InfoDialog): - ''' A simple Dialog for displaying a message and taking a input - using a Textinput - ''' - - text = StringProperty('Nothing set yet') - - readonly = BooleanProperty(False) - - -class EditLabelDialog(TakeInputDialog): - pass - - - -class ImportPrivateKeysDialog(TakeInputDialog): - pass - - - -class ShowMasterPublicKeyDialog(TakeInputDialog): - pass - - -class EditDescriptionDialog(TakeInputDialog): - - pass - - -class PrivateKeyDialog(InfoDialog): - - private_key = StringProperty('') - ''' private key to be displayed in the TextInput - ''' - - address = StringProperty('') - ''' address to be displayed in the dialog - ''' - - -class SignVerifyDialog(InfoDialog): - - address = StringProperty('') - '''current address being verified''' - - - -class MessageBox(InfoDialog): - - image = StringProperty('atlas://gui/kivy/theming/light/info') - '''path to image to be displayed on the left''' - - message = StringProperty('Empty Message') - '''Message to be displayed on the dialog''' - - def __init__(self, **kwargs): - super(MessageBox, self).__init__(**kwargs) - self.title = kwargs.get('title', _('Message')) - - -class MessageBoxExit(MessageBox): - - def __init__(self, **kwargs): - super(MessageBox, self).__init__(**kwargs) - self.title = kwargs.get('title', _('Exiting')) - -class MessageBoxError(MessageBox): - - def __init__(self, **kwargs): - super(MessageBox, self).__init__(**kwargs) - self.title = kwargs.get('title', _('Error')) - - -class WalletAddressesDialog(CarouselDialog): - - def __init__(self, **kwargs): - super(WalletAddressesDialog, self).__init__(**kwargs) - CarouselHeader = Factory.CarouselHeader - ch = CarouselHeader() - ch.slide = 0 # idx - - # delayed init - global ScreenAddress - if not ScreenAddress: - from electrum_gui.kivy.screens import ScreenAddress - slide = ScreenAddress() - - slide.tab=ch - - labels = app.wallet.labels - addresses = app.wallet.addresses() - _labels = {} - for address in addresses: - _labels[labels.get(address, address)] = address - - slide.labels = _labels - - self.add_widget(slide) - self.add_widget(ch) - Clock.schedule_once(lambda dt: self.delayed_init(slide)) - - def delayed_init(self, slide): - # add a tab for each wallet - # for wallet in wallets - slide.ids.btn_address.values = values = slide.labels.keys() - slide.ids.btn_address.text = values[0] - - - -class RecentActivityDialog(CarouselDialog): - - def send_payment(self, address): - tabs = app.root.main_screen.ids.tabs - screen_send = tabs.ids.screen_send - # remove self - self.dismiss() - # switch_to the send screen - tabs.ids.panel.switch_to(tabs.ids.tab_send) - # populate - screen_send.ids.payto_e.text = address - - def populate_inputs_outputs(self, app, tx_hash): - if tx_hash: - tx = app.wallet.transactions.get(tx_hash) - self.ids.list_outputs.content_adapter.data = \ - [(address, app.gui.main_gui.format_amount(value))\ - for address, value in tx.outputs] - self.ids.list_inputs.content_adapter.data = \ - [(input['address'], input['prevout_hash'])\ - for input in tx.inputs] - - -class CreateAccountDialog(EventsDialog): - ''' Abstract dialog to be used as the base for all Create Account Dialogs - ''' - crcontent = ObjectProperty(None) - - def add_widget(self, widget, index=0): - if not self.crcontent: - super(CreateAccountDialog, self).add_widget(widget) - else: - self.crcontent.add_widget(widget, index=index) - - -class CreateRestoreDialog(CreateAccountDialog): - ''' Initial Dialog for creating or restoring seed''' - - def on_parent(self, instance, value): - if value: - self.ids.but_close.disabled = True - self.ids.but_close.opacity = 0 - self._back = _back = partial(app.dispatch, 'on_back') - app.navigation_higherarchy.append(_back) - - def close(self): - if self._back in app.navigation_higherarchy: - app.navigation_higherarchy.pop() - self._back = None - super(CreateRestoreDialog, self).close() - - -class InitSeedDialog(CreateAccountDialog): - - seed_msg = StringProperty('') - '''Text to be displayed in the TextInput''' - - message = StringProperty('') - '''Message to be displayed under seed''' - - seed = ObjectProperty(None) - - def on_parent(self, instance, value): - if value: - 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') - app.navigation_higherarchy.append(_back) - - def close(self): - if self._back in app.navigation_higherarchy: - app.navigation_higherarchy.pop() - self._back = None - super(InitSeedDialog, self).close() - -class VerifySeedDialog(CreateAccountDialog): - - pass - -class RestoreSeedDialog(CreateAccountDialog): - - 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) - stepper = self.ids.stepper - stepper.opacity = 1 - stepper.source = ('atlas://gui/kivy/theming' - '/light/stepper_restore_seed') - self._back = _back = partial(self.ids.back.dispatch, 'on_release') - app.navigation_higherarchy.append(_back) - - def on_key_down(self, keyboard, keycode, key, modifiers): - if keycode[0] in (13, 271): - self.on_enter() - return True - #super - - def on_enter(self): - #self._remove_keyboard() - # press next - self.ids.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 close(self): - self._remove_keyboard() - if self._back in app.navigation_higherarchy: - app.navigation_higherarchy.pop() - self._back = None - super(RestoreSeedDialog, self).close() - -class NewContactDialog(Popup): - - qrscr = ObjectProperty(None) - _decoder = None - - def load_qr_scanner(self): - global QRScanner - if not QRScanner: - from electrum_gui.kivy.qr_scanner import QRScanner - qrscr = self.qrscr - if not qrscr: - self.qrscr = qrscr = QRScanner(opacity=0) - #pos=self.pos, size=self.size) - #self.bind(pos=qrscr.setter('pos'), - # size=qrscr.setter('size') - qrscr.bind(symbols=self.on_symbols) - bl = self.ids.bl - bl.clear_widgets() - bl.add_widget(qrscr) - qrscr.opacity = 1 - Animation(height=dp(280)).start(self) - Animation(opacity=1).start(self) - qrscr.start() - - def on_symbols(self, instance, value): - instance.stop() - self.remove_widget(instance) - self.ids.but_contact.dispatch('on_release') - global decode_uri - if not decode_uri: - from electrum_gui.kivy.qr_scanner import decode_uri - uri = decode_uri(value[0].data) - self.ids.ti.text = uri.get('address', 'empty') - self.ids.ti_lbl.text = uri.get('label', 'empty') - self.ids.ti_lbl.focus = True - - -class PasswordRequiredDialog(InfoDialog): - - pass - - -class ChangePasswordDialog(CreateAccountDialog): - - message = StringProperty(_('Empty Message')) - '''Message to be displayed.''' - - mode = OptionProperty('new', - options=('new', 'confirm', 'create', 'restore')) - ''' Defines the mode of the password dialog.''' - - def validate_new_password(self): - self.ids.next.dispatch('on_release') - - def on_parent(self, instance, value): - if value: - stepper = self.ids.stepper - stepper.opacity = 1 - t_wallet_name = self.ids.ti_wallet_name - if self.mode in ('create', 'restore'): - t_wallet_name.text = 'Default Wallet' - t_wallet_name.readonly = True - self.ids.ti_new_password.focus = True - else: - t_wallet_name.text = '' - t_wallet_name.readonly = False - t_wallet_name.focus = True - stepper.source = 'atlas://gui/kivy/theming/light/stepper_left' - self._back = _back = partial(self.ids.back.dispatch, 'on_release') - app.navigation_higherarchy.append(_back) - - def close(self): - ids = self.ids - ids.ti_wallet_name.text = "" - ids.ti_wallet_name.focus = False - ids.ti_password.text = "" - ids.ti_password.focus = False - ids.ti_new_password.text = "" - ids.ti_new_password.focus = False - ids.ti_confirm_password.text = "" - ids.ti_confirm_password.focus = False - if self._back in app.navigation_higherarchy: - app.navigation_higherarchy.pop() - self._back = None - super(ChangePasswordDialog, self).close() - - - -class Dialog(Popup): - - content_padding = NumericProperty('2dp') - '''Padding for the content area of the dialog defaults to 2dp - ''' - - buttons_padding = NumericProperty('2dp') - '''Padding for the bottns area of the dialog defaults to 2dp - ''' - - buttons_height = NumericProperty('40dp') - '''Height to be used for the Buttons at the bottom - ''' - - def close(self): - self.dismiss() - - def add_content(self, widget, index=0): - self.ids.layout_content.add_widget(widget, index) - - def add_button(self, widget, index=0): - self.ids.layout_buttons.add_widget(widget, index) - - -class SaveDialog(Popup): - - filename = StringProperty('') - '''The default file name provided - ''' - - filters = ListProperty([]) - ''' list of files to be filtered and displayed defaults to allow all - ''' - - path = StringProperty(DEFAULT_PATH) - '''path to be loaded by default in this dialog - ''' - - file_chooser = ObjectProperty(None) - '''link to the file chooser object inside the dialog - ''' - - text_input = ObjectProperty(None) - ''' - ''' - - cancel_button = ObjectProperty(None) - ''' - ''' - - save_button = ObjectProperty(None) - ''' - ''' - - def close(self): - self.dismiss() - - -class LoadDialog(SaveDialog): - - def _get_load_btn(self): - return self.save_button - - load_button = AliasProperty(_get_load_btn, None, bind=('save_button', )) - '''Alias to the Save Button to be used as LoadButton - ''' - - def __init__(self, **kwargs): - super(LoadDialog, self).__init__(**kwargs) - self.load_button.text=_("Load") diff --git a/gui/kivy/drawer.py b/gui/kivy/drawer.py deleted file mode 100644 index 5a649a096..000000000 --- a/gui/kivy/drawer.py +++ /dev/null @@ -1,187 +0,0 @@ - -from kivy.uix.stencilview import StencilView -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.image import Image - -from kivy.animation import Animation -from kivy.clock import Clock -from kivy.properties import OptionProperty, NumericProperty, ObjectProperty - -# delayed import -app = None - - -class Drawer(StencilView): - - state = OptionProperty('closed', - options=('closed', 'open', 'opening', 'closing')) - '''This indicates the current state the drawer is in. - - :attr:`state` is a `OptionProperty` defaults to `closed`. Can be one of - `closed`, `open`, `opening`, `closing`. - ''' - - scroll_timeout = NumericProperty(200) - '''Timeout allowed to trigger the :data:`scroll_distance`, - in milliseconds. If the user has not moved :data:`scroll_distance` - within the timeout, the scrolling will be disabled and the touch event - will go to the children. - - :data:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty` - and defaults to 200 (milliseconds) - ''' - - scroll_distance = NumericProperty('9dp') - '''Distance to move before scrolling the :class:`Drawer` in pixels. - As soon as the distance has been traveled, the :class:`Drawer` will - start to scroll, and no touch event will go to children. - It is advisable that you base this value on the dpi of your target - device's screen. - - :data:`scroll_distance` is a :class:`~kivy.properties.NumericProperty` - and defaults to 20dp. - ''' - - drag_area = NumericProperty(.1) - '''The percentage of area on the left edge that triggers the opening of - the drawer. from 0-1 - - :attr:`drag_area` is a `NumericProperty` defaults to 2 - ''' - - _hidden_widget = ObjectProperty(None) - _overlay_widget = ObjectProperty(None) - - def __init__(self, **kwargs): - super(Drawer, self).__init__(**kwargs) - self.bind(pos=self._do_layout, - size=self._do_layout, - children=self._do_layout) - - def _do_layout(self, instance, value): - if not self._hidden_widget or not self._overlay_widget: - return - self._overlay_widget.height = self._hidden_widget.height =\ - self.height - - def on_touch_down(self, touch): - if self.disabled: - return - - if not self.collide_point(*touch.pos): - return - - touch.grab(self) - - global app - if not app: - from kivy.app import App - app = App.get_running_app() - - # skip on tablet mode - if app.ui_mode[0] == 't': - return super(Drawer, self).on_touch_down(touch) - - state = self.state - touch.ud['send_touch_down'] = False - start = 0 if state[0] == 'c' else self._hidden_widget.right - drag_area = ((self.width * self.drag_area) - if self.state[0] == 'c' else - self.width) - if touch.x not in range(int(start), int(drag_area)): - return super(Drawer, self).on_touch_down(touch) - self._touch = touch - Clock.schedule_once(self._change_touch_mode, - self.scroll_timeout/1000.) - touch.ud['in_drag_area'] = True - touch.ud['send_touch_down'] = True - return - - def on_touch_move(self, touch): - if not touch.grab_current: - return - - # skip on tablet mode - if app.ui_mode[0] == 't': - return super(Drawer, self).on_touch_move(touch) - - if not touch.ud.get('in_drag_area', None): - return super(Drawer, self).on_touch_move(touch) - - ov = self._overlay_widget - ov.x=min(self._hidden_widget.width, - max(ov.x + touch.dx*2, 0)) - #_anim = Animation(x=x, duration=1/2, t='in_out_quart') - #_anim.cancel_all(ov) - #_anim.start(ov) - - if abs(touch.x - touch.ox) < self.scroll_distance: - return - touch.ud['send_touch_down'] = False - Clock.unschedule(self._change_touch_mode) - self._touch = None - self.state = 'opening' if touch.dx > 0 else 'closing' - touch.ox = touch.x - return - - def _change_touch_mode(self, *args): - if not self._touch: - return - touch = self._touch - touch.ud['in_drag_area'] = False - touch.ud['send_touch_down'] = False - self._touch = None - super(Drawer, self).on_touch_down(touch) - return - - def on_touch_up(self, touch): - if not touch.grab_current: - return - - # skip on tablet mode - if app.ui_mode[0] == 't': - return super(Drawer, self).on_touch_down(touch) - - if touch.ud.get('send_touch_down', None): - Clock.unschedule(self._change_touch_mode) - Clock.schedule_once( - lambda dt: super(Drawer, self).on_touch_down(touch), -1) - if touch.ud.get('in_drag_area', None): - touch.ud['in_drag_area'] = False - Animation.cancel_all(self._overlay_widget) - anim = Animation(x=self._hidden_widget.width - if self.state[0] == 'o' else 0, - d=.1, t='linear') - anim.bind(on_complete = self._complete_drawer_animation) - anim.start(self._overlay_widget) - Clock.schedule_once( - lambda dt: super(Drawer, self).on_touch_up(touch), 0) - - def _complete_drawer_animation(self, *args): - self.state = 'open' if self.state[0] == 'o' else 'closed' - - def add_widget(self, widget, index=1): - if not widget: - return - children = self.children - len_children = len(children) - if len_children == 2: - Logger.debug('Drawer: No more than two widgets allowed') - return - - super(Drawer, self).add_widget(widget) - if len_children == 0: - # first widget add it to the hidden/drawer section - self._hidden_widget = widget - return - # Second Widget - self._overlay_widget = widget - - def remove_widget(self, widget): - super(Drawer, self).remove_widget(self) - if widget == self._hidden_widget: - self._hidden_widget = None - return - if widget == self._overlay_widget: - self._overlay_widget = None - return \ No newline at end of file diff --git a/gui/kivy/installwizard.py b/gui/kivy/installwizard.py deleted file mode 100644 index 41809a8e1..000000000 --- a/gui/kivy/installwizard.py +++ /dev/null @@ -1,328 +0,0 @@ -from electrum import Wallet -from electrum.i18n import _ - -from kivy.app import App -from kivy.uix.widget import Widget -from kivy.core.window import Window -from kivy.clock import Clock - -from electrum_gui.kivy.dialog import CreateRestoreDialog -#from network_dialog import NetworkDialog -#from util import * -#from amountedit import AmountEdit - -import sys -import threading -from functools import partial - -# global Variables -app = App.get_running_app() - - -class InstallWizard(Widget): - '''Installation Wizard. Responsible for instantiating the - creation/restoration of wallets. - - events:: - `on_wizard_complete` Fired when the wizard is done creating/ restoring - wallet/s. - ''' - - __events__ = ('on_wizard_complete', ) - - def __init__(self, config, network, storage): - super(InstallWizard, self).__init__() - self.config = config - self.network = network - self.storage = storage - - def waiting_dialog(self, task, - msg= _("Electrum is generating your addresses," - " please wait."), - on_complete=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: - Clock.schedule_once(lambda dt: app.show_error(str(err))) - - # on completion hide message - Clock.schedule_once(lambda dt: app.info_bubble.hide(now=True), -1) - - # call completion routine - if on_complete: - Clock.schedule_once(lambda dt: on_complete()) - - app.show_info_bubble( - text=msg, icon='atlas://gui/kivy/theming/light/important', - pos=Window.center, width='200sp', arrow_pos=None, modal=True) - t = threading.Thread(target = target) - t.start() - - def run(self): - '''Entry point of our Installation wizard - ''' - CreateRestoreDialog(on_release=self.on_creatrestore_complete).open() - - def on_creatrestore_complete(self, dialog, button): - if not button: - return self.dispatch('on_wizard_complete', None) - - #gap = self.config.get('gap_limit', 5) - #if gap !=5: - # wallet.gap_limit = gap_limit - # wallet.storage.put('gap_limit', gap, True) - - dialog.close() - if button == dialog.ids.create: - # create - wallet = Wallet(self.storage) - self.change_password_dialog(wallet=wallet) - elif button == dialog.ids.restore: - # restore - wallet = None - self.restore_seed_dialog(wallet) - #if button == dialog.ids.watching: - #TODO: not available in the new design - # self.action = 'watching' - else: - self.dispatch('on_wizard_complete', None) - - def restore_seed_dialog(self, wallet): - from electrum_gui.kivy.dialog import RestoreSeedDialog - RestoreSeedDialog( - on_release=partial(self.on_verify_restore_ok, wallet)).open() - - def on_verify_restore_ok(self, wallet, _dlg, btn, restore=False): - - if _dlg.ids.back == btn: - _dlg.close() - CreateRestoreDialog( - on_release=self.on_creatrestore_complete).open() - return - - seed = unicode(_dlg.ids.text_input_seed.text) - if not seed: - app.show_error(_("No seed!"), duration=.5) - return - - try: - wallet = Wallet.from_seed(seed, self.storage) - except Exception as err: - _dlg.close() - return app.show_error(str(err) + '\n App will now exit', - exit=True, modal=True, duration=.5) - _dlg.close() - return self.change_password_dialog(wallet=wallet, mode='restore') - - - def init_seed_dialog(self, wallet=None, instance=None, password=None, - wallet_name=None, mode='create'): - # renamed from show_seed() - '''Can be called directly (password is None) - or from a password-protected callback (password is not None)''' - - if not wallet or not wallet.seed: - if instance == None: - wallet.init_seed(None) - else: - return app.show_error(_('No seed')) - - if password is None or not instance: - seed = wallet.get_mnemonic(None) - else: - try: - seed = self.wallet.get_seed(password) - except Exception: - return app.show_error(_('Incorrect Password')) - - brainwallet = seed - - msg2 = _("[color=#414141]"+\ - "[b]PLEASE WRITE DOWN YOUR SEED PASS[/b][/color]"+\ - "[size=9]\n\n[/size]" +\ - "[color=#929292]If you ever forget your pincode, your seed" +\ - " phrase will be the [color=#EB984E]"+\ - "[b]only way to recover[/b][/color] your wallet. Your " +\ - " [color=#EB984E][b]Bitcoins[/b][/color] will otherwise be" +\ - " [color=#EB984E][b]lost forever![/b][/color]") - - if wallet.imported_keys: - msg2 += "[b][color=#ff0000ff]" + _("WARNING") + "[/color]:[/b] " +\ - _("Your wallet contains imported keys. These keys cannot" +\ - " be recovered from seed.") - - def on_ok_press(_dlg, _btn): - _dlg.close() - if _btn != _dlg.ids.confirm: - if not instance: - self.change_password_dialog(wallet) - return - # confirm - if instance is None: - # in initial phase - def create(password): - try: - password = None if not password else password - wallet.save_seed(password) - except Exception as err: - Logger.Info('Wallet: {}'.format(err)) - Clock.schedule_once(lambda dt: - app.show_error(err)) - wallet.synchronize() # generate first addresses offline - self.waiting_dialog( - partial(create, password), - on_complete=partial(self.load_network, wallet, mode=mode)) - - from electrum_gui.kivy.dialog import InitSeedDialog - InitSeedDialog(message=msg2, - seed_msg=brainwallet, seed=seed, on_release=on_ok_press).open() - - def change_password_dialog(self, wallet=None, instance=None, mode='create'): - """Can be called directly (instance is None) - or from a callback (instance is not None)""" - - if instance and not wallet.seed: - return ShowError(_('No seed !!'), exit=True, modal=True) - - if instance is not None: - if wallet.use_encryption: - msg = ( - _('Your wallet is encrypted. Use this dialog to change" + \ - " your password.') + '\n' + _('To disable wallet" + \ - " encryption, enter an empty new password.')) - mode = 'confirm' - else: - msg = _('Your wallet keys are not encrypted') - mode = 'new' - else: - msg = _("Please choose a password to encrypt your wallet keys.") +\ - '\n' + _("Leave these fields empty if you want to disable" + \ - " encryption.") - - def on_release(_dlg, _btn): - ti_password = _dlg.ids.ti_password - ti_new_password = _dlg.ids.ti_new_password - ti_confirm_password = _dlg.ids.ti_confirm_password - if _btn != _dlg.ids.next: - if mode == 'restore': - # back is disabled cause seed is already set - return - _dlg.close() - if not instance: - # back on create - CreateRestoreDialog( - on_release=self.on_creatrestore_complete).open() - return - - # Confirm - wallet_name = _dlg.ids.ti_wallet_name.text - password = (unicode(ti_password.text) - if wallet.use_encryption else - None) - new_password = unicode(ti_new_password.text) - new_password2 = unicode(ti_confirm_password.text) - - if new_password != new_password2: - ti_password.text = "" - ti_new_password.text = "" - ti_confirm_password.text = "" - if ti_password.disabled: - ti_new_password.focus = True - else: - ti_password.focus = True - return app.show_error(_('Passwords do not match'), duration=.5) - - if mode == 'restore': - def on_complete(*l): - _dlg.close() - self.load_network(wallet, mode='restore') - - self.waiting_dialog(lambda: wallet.save_seed(new_password), - msg=_("saving seed"), - on_complete=on_complete) - return - if not instance: - # create - _dlg.close() - #self.load_network(wallet, mode='create') - return self.init_seed_dialog(password=new_password, - wallet=wallet, wallet_name=wallet_name, mode=mode) - - try: - seed = wallet.decode_seed(password) - except BaseException: - return app.show_error(_('Incorrect Password'), duration=.5) - - # test carefully - try: - wallet.update_password(seed, password, new_password) - except BaseException: - return app.show_error(_('Failed to update password'), exit=True) - else: - app.show_info_bubble( - text=_('Password successfully updated'), duration=1, - pos=_btn.pos) - _dlg.close() - - - if instance is None: # in initial phase - self.load_wallet() - self.app.update_wallet() - - from electrum_gui.kivy.dialog import ChangePasswordDialog - cpd = ChangePasswordDialog( - message=msg, - mode=mode, - on_release=on_release).open() - - def load_network(self, wallet, mode='create'): - #if not self.config.get('server'): - if self.network: - if self.network.interfaces: - if mode not in ('restore', 'create'): - self.network_dialog() - else: - app.show_error(_('You are offline')) - self.network.stop() - self.network = None - - if mode in ('restore', 'create'): - # auto cycle - self.config.set_key('auto_cycle', True, True) - - # start wallet threads - wallet.start_threads(self.network) - - if not mode == 'restore': - return self.dispatch('on_wizard_complete', wallet) - - def get_text(text): - def set_text(*l): app.info_bubble.ids.lbl.text=text - Clock.schedule_once(set_text) - - def on_complete(*l): - if not self.network: - app.show_info( - _("This wallet was restored offline. It may contain more" - " addresses than displayed."), duration=.5) - return self.dispatch('on_wizard_complete', wallet) - - if wallet.is_found(): - app.show_info(_("Recovery successful"), duration=.5) - else: - app.show_info(_("No transactions found for this seed"), - duration=.5) - return self.dispatch('on_wizard_complete', wallet) - - self.waiting_dialog(lambda: wallet.restore(get_text), - on_complete=on_complete) - - def on_wizard_complete(self, wallet): - pass diff --git a/gui/kivy/main.kv b/gui/kivy/main.kv index d9c59e7e6..b0d077227 100644 --- a/gui/kivy/main.kv +++ b/gui/kivy/main.kv @@ -1,7 +1,4 @@ #:import Window kivy.core.window.Window -#:import _ electrum.i18n._ -#:import partial functools.partial - # Custom Global Widgets @@ -22,37 +19,36 @@ if root.state == 'normal' else 'icon_border') size: root.size pos: root.pos -########################### -## Gloabal Defaults -########################### - -