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, *dt): ''' 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() 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[1] == 'enter': 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 self.ids.ti_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")