class TabbedCarousel(Factory.TabbedPanel): '''Custom TabbedPanel using a carousel used in the Main Screen ''' carousel = ObjectProperty(None) def animate_tab_to_center(self, value): scrlv = self._tab_strip.parent if not scrlv: return idx = self.tab_list.index(value) n = len(self.tab_list) if idx in [0, 1]: scroll_x = 1 elif idx in [n - 1, n - 2]: scroll_x = 0 else: scroll_x = 1. * (n - idx - 1) / (n - 1) mation = Factory.Animation(scroll_x=scroll_x, d=.25) mation.cancel_all(scrlv) mation.start(scrlv) def on_current_tab(self, instance, value): self.animate_tab_to_center(value) def on_index(self, instance, value): current_slide = instance.current_slide if not hasattr(current_slide, 'tab'): return tab = current_slide.tab ct = self.current_tab try: if ct.text != tab.text: carousel = self.carousel carousel.slides[ct.slide].dispatch('on_leave') self.switch_to(tab) carousel.slides[tab.slide].dispatch('on_enter') except AttributeError: current_slide.dispatch('on_enter') def switch_to(self, header): # we have to replace the functionality of the original switch_to if not header: return if not hasattr(header, 'slide'): header.content = self.carousel super(TabbedCarousel, self).switch_to(header) try: tab = self.tab_list[-1] except IndexError: return self._current_tab = tab tab.state = 'down' return carousel = self.carousel self.current_tab.state = "normal" header.state = 'down' self._current_tab = header # set the carousel to load the appropriate slide # saved in the screen attribute of the tab head slide = carousel.slides[header.slide] if carousel.current_slide != slide: carousel.current_slide.dispatch('on_leave') carousel.load_slide(slide) slide.dispatch('on_enter') def add_widget(self, widget, index=0): if isinstance(widget, Factory.CScreen): self.carousel.add_widget(widget) return super(TabbedCarousel, self).add_widget(widget, index=index)
class ScreenManagement(ScreenManager): main_screen = ObjectProperty(None)
class ICDialog(ThemableBehavior, RectangularElevationBehavior, ModalView): """ Dialog box with the ability to add action buttons """ title = StringProperty('KivyIC Dialog Box') ''' Title of the Dialog Box. :attr:`title` is an :class:`~kivy.properties.StringProperty` and defaults to 'KivyIC Dialog Box'. .. versionadded:: 0.1 ''' content = ObjectProperty(None) ''' Container widget for what will be displayed in the Dialog Box. :attr:`content` is an :class:`~kivy.properties.ObjectProperty` and defaults to None. .. versionadded:: 0.1 ''' content_fit = OptionProperty('items', options=['window', 'items']) md_bg_color = ListProperty([0, 0, 0, .2]) _container = ObjectProperty() _action_buttons = ListProperty([]) _action_area = ObjectProperty() action_area_width = NumericProperty() def __init__(self, **kwargs): super().__init__(**kwargs) self.bind(_action_buttons=self._update_action_buttons, auto_dismiss=lambda *x: setattr( self.shadow, 'on_release', self.shadow.dismiss if self.auto_dismiss else None)) def add_action_button(self, text, action=None): """Add an :class:`FlatButton` to the right of the action area. :param icon: Unicode character for the icon :type icon: str or None :param action: Function set to trigger when on_release fires :type action: function or None """ button = MDFlatButton(text=text, size_hint=(None, None), height=dp(36)) if action: button.bind(on_release=action) # FIX - fix color button.text_color = self.theme_cls.primary_color button.md_bg_color = self.theme_cls.bg_light self._action_buttons.append(button) def add_widget(self, widget): if self._container: if self.content: raise PopupException( 'Popup can have only one widget as content') self.content = widget else: super(ICDialog, self).add_widget(widget) def open(self, *largs): '''Show the view window from the :attr:`attach_to` widget. If set, it will attach to the nearest window. If the widget is not attached to any window, the view will attach to the global :class:`~kivy.core.window.Window`. ''' if self._window is not None: Logger.warning('ModalView: you can only open once.') return self # search window self._window = self._search_window() if not self._window: Logger.warning('ModalView: cannot open view, no window found.') return self self._window.add_widget(self) self._window.bind(on_resize=self._align_center, on_keyboard=self._handle_keyboard) self.center = self._window.center self.bind(size=self._align_center) a = Animation(_anim_alpha=1., d=self._anim_duration) a.bind(on_complete=lambda *x: self.dispatch('on_open')) a.start(self) return self def dismiss(self, *largs, **kwargs): '''Close the view if it is open. If you really want to close the view, whatever the on_dismiss event returns, you can use the *force* argument: :: view = ModalView(...) view.dismiss(force=True) When the view is dismissed, it will be faded out before being removed from the parent. If you don't want animation, use:: view.dismiss(animation=False) ''' if self._window is None: return self if self.dispatch('on_dismiss') is True: if kwargs.get('force', False) is not True: return self if kwargs.get('animation', True): Animation(_anim_alpha=0., d=self._anim_duration).start(self) else: self._anim_alpha = 0 self._real_remove_widget() return self def on_content(self, instance, value): if self._container: self._container.clear_widgets() self._container.add_widget(value) def on__container(self, instance, value): if value is None or self.content is None: return self._container.clear_widgets() self._container.add_widget(self.content) def on_touch_down(self, touch): if self.disabled and self.collide_point(*touch.pos): return True return super(ICDialog, self).on_touch_down(touch) def _update_action_buttons(self, *args): self._action_area.clear_widgets() self.action_area_width = 0 for btn in self._action_buttons: btn.content.texture_update() btn.width = btn.content.texture_size[0] + dp(16) self.action_area_width += btn.width self._action_area.add_widget(btn) spacing = sum(self._action_area.spacing) - self._action_area.spacing[0] self.action_area_width += spacing
class FileChooser(FileChooserController): '''Implementation of a :class:`FileChooserController` which supports switching between multiple, synced layout views. The FileChooser can be used as follows: .. code-block:: kv BoxLayout: orientation: 'vertical' BoxLayout: size_hint_y: None height: sp(52) Button: text: 'Icon View' on_press: fc.view_mode = 'icon' Button: text: 'List View' on_press: fc.view_mode = 'list' FileChooser: id: fc FileChooserIconLayout FileChooserListLayout .. versionadded:: 1.9.0 ''' manager = ObjectProperty() ''' Reference to the :class:`~kivy.uix.screenmanager.ScreenManager` instance. manager is an :class:`~kivy.properties.ObjectProperty`. ''' _view_list = ListProperty() def get_view_list(self): return self._view_list view_list = AliasProperty(get_view_list, bind=('_view_list', )) ''' List of views added to this FileChooser. view_list is an :class:`~kivy.properties.AliasProperty` of type :class:`list`. ''' _view_mode = StringProperty() def get_view_mode(self): return self._view_mode def set_view_mode(self, mode): if mode not in self._view_list: raise ValueError('unknown view mode %r' % mode) self._view_mode = mode view_mode = AliasProperty(get_view_mode, set_view_mode, bind=('_view_mode', )) ''' Current layout view mode. view_mode is an :class:`~kivy.properties.AliasProperty` of type :class:`str`. ''' @property def _views(self): return [screen.children[0] for screen in self.manager.screens] def __init__(self, **kwargs): super(FileChooser, self).__init__(**kwargs) self.manager = ScreenManager() super(FileChooser, self).add_widget(self.manager) self.trigger_update_view = Clock.create_trigger(self.update_view) self.fbind('view_mode', self.trigger_update_view) def add_widget(self, widget, **kwargs): if widget is self._progress: super(FileChooser, self).add_widget(widget, **kwargs) elif hasattr(widget, 'VIEWNAME'): name = widget.VIEWNAME + 'view' screen = Screen(name=name) widget.controller = self screen.add_widget(widget) self.manager.add_widget(screen) self.trigger_update_view() else: raise ValueError('widget must be a FileChooserLayout,' ' not %s' % type(widget).__name__) def rebuild_views(self): views = [view.VIEWNAME for view in self._views] if views != self._view_list: self._view_list = views if self._view_mode not in self._view_list: self._view_mode = self._view_list[0] self._trigger_update() def update_view(self, *args): self.rebuild_views() sm = self.manager viewlist = self._view_list view = self.view_mode current = sm.current[:-4] viewindex = viewlist.index(view) if view in viewlist else 0 currentindex = viewlist.index(current) if current in viewlist else 0 direction = 'left' if currentindex < viewindex else 'right' sm.transition.direction = direction sm.current = view + 'view' def _create_entry_widget(self, ctx): return [ Builder.template(view._ENTRY_TEMPLATE, **ctx) for view in self._views ] def _get_file_paths(self, items): if self._views: return [file[0].path for file in items] return [] def _update_item_selection(self, *args): for viewitem in self._items: selected = viewitem[0].path in self.selection for item in viewitem: item.selected = selected def on_entry_added(self, node, parent=None): for index, view in enumerate(self._views): view.dispatch('on_entry_added', node[index], parent[index] if parent else None) def on_entries_cleared(self): for view in self._views: view.dispatch('on_entries_cleared') def on_subentry_to_entry(self, subentry, entry): for index, view in enumerate(self._views): view.dispatch('on_subentry_to_entry', subentry[index], entry) def on_remove_subentry(self, subentry, entry): for index, view in enumerate(self._views): view.dispatch('on_remove_subentry', subentry[index], entry) def on_submit(self, selected, touch=None): view_mode = self.view_mode for view in self._views: if view_mode == view.VIEWNAME: view.dispatch('on_submit', selected, touch) return
class WidgetsTree(ScrollView): '''WidgetsTree class is used to display the Root Widget's Tree in a Tree hierarchy. ''' playground = ObjectProperty(None) '''This property is an instance of :class:`~designer.playground.Playground` :data:`playground` is a :class:`~kivy.properties.ObjectProperty` ''' tree = ObjectProperty(None) '''This property is an instance of :class:`~kivy.uix.treeview.TreeView`. This TreeView is responsible for showing Root Widget's Tree. :data:`tree` is a :class:`~kivy.properties.ObjectProperty` ''' dragging = BooleanProperty(False) '''Specifies whether a node is dragged or not. :data:`dragging` is a :class:`~kivy.properties.BooleanProperty` ''' selected_widget = ObjectProperty(allownone=True) '''Current selected widget. :data:`dragging` is a :class:`~kivy.properties.ObjectProperty` ''' def __init__(self, **kwargs): super(WidgetsTree, self).__init__(**kwargs) self.refresh = Clock.create_trigger(self._refresh) self._widget_cache = {} def recursive_insert(self, node, treenode): '''This function will add a node to TreeView, by recursively travelling through the Root Widget's Tree. ''' if node is None: return b = self._get_widget(node) self.tree.add_node(b, treenode) class_rules = get_current_project().app_widgets root_widget = self.playground.root is_child_custom = False for rule_name in class_rules: if rule_name == type(node).__name__: is_child_custom = True break is_child_complex = False for widget in widgets: if widget[0] == type(node).__name__ and widget[1] == 'complex': is_child_complex = True break if root_widget == node or (not is_child_custom and not is_child_complex): if isinstance(node, TabbedPanel): self.insert_for_tabbed_panel(node, b) else: for child in node.children: self.recursive_insert(child, b) def insert_for_tabbed_panel(self, node, treenode): '''This function will insert nodes in tree specially for TabbedPanel. ''' for tab in node.tab_list: b = self._get_widget(tab) self.tree.add_node(b, treenode) self.recursive_insert(tab.content, b) def _get_widget(self, node): try: wid = self._widget_cache[node] if not wid: raise KeyError() except KeyError: wid = WidgetTreeElement(node=node) self._widget_cache[node] = wid.proxy_ref if wid.parent_node: self.tree.remove_node(wid) return wid def _clear_tree(self, tree, node): remove_node = tree.remove_node for n in node.nodes[:]: self._clear_tree(tree, n) remove_node(n) def _refresh(self, *l): '''This function will refresh the tree. It will first remove all nodes and then insert them using recursive_insert ''' self._clear_tree(self.tree, self.tree.root) self.recursive_insert(self.playground.root, self.tree.root) self._clean_cache() def _clean_cache(self): for node, wid in list(self._widget_cache.items()): try: if node and node.parent and wid and wid.parent_node: continue except ReferenceError: pass del self._widget_cache[node] def on_touch_up(self, touch): '''Default event handler for 'on_touch_up' event. ''' self.dragging = False Clock.unschedule(self._start_dragging) return super(WidgetsTree, self).on_touch_up(touch) def on_touch_down(self, touch): '''Default event handler for 'on_touch_down' event. ''' if self.collide_point(*touch.pos) and not self.dragging: self.dragging = True self.touch = touch Clock.schedule_once(self._start_dragging, 2) node = self.tree.get_node_at_pos((self.touch.x, self.touch.y)) if node: self.selected_widget = node.node self.playground.selected_widget = self.selected_widget else: self.selected_widget = None self.playground.selected_widget = None return super(WidgetsTree, self).on_touch_down(touch) def _start_dragging(self, *args): '''This function will start dragging the widget. ''' if self.dragging and self.selected_widget: self.playground.selected_widget = self.selected_widget self.playground.dragging = False self.playground.touch = self.touch self.playground.start_widget_dragging()
class CalibrationWidget(BoxLayout): screen_manager = ObjectProperty(None) scribe_widget = ObjectProperty(None) callibration_screen = ObjectProperty(None) loading_image = StringProperty(LOADING_IMAGE) left_port = StringProperty() right_port = StringProperty() target_screen = ObjectProperty('capture_screen') target_extra = ObjectProperty(allownone=True) use_tooltips = BooleanProperty(False) """Pass this value to `use_tooltips` of every TooltipControl child widget. """ def __init__(self, scribe_widget=scribe_widget, **kwargs): super(CalibrationWidget, self).__init__(**kwargs) self.scribe_widget = scribe_widget self.left_image = None self.left_thumb = None self.right_image = None self.right_thumb = None self.foldout_image = None self.foldout_thumb = None self.foldout_widget = None self.left_port = scribe_widget.cameras.get_camera_port('left') self.right_port = scribe_widget.cameras.get_camera_port('right') if scribe_widget.cameras.get_camera_port('foldout') is not None: self.add_foldout_widget() self.capture_images() Clock.schedule_once(self._bind_image_menus) def _bind_image_menus(self, *args): left = self.ids.left_image_menu_bar left.bind(on_option_select=self.on_left_image_menu_option) right = self.ids.right_image_menu_bar right.bind(on_option_select=self.on_right_image_menu_option) if self.foldout_widget: menu = self.foldout_widget.ids.image_menu_bar menu.bind(on_option_select=self.on_foldout_image_menu_option) def on_left_image_menu_option(self, menu, option): if option == 'view_source': self.show_image(self.left_image) elif option == 'export': self.start_export_filechooser(self.left_image) def on_right_image_menu_option(self, menu, option): if option == 'view_source': self.show_image(self.right_image) elif option == 'export': self.start_export_filechooser(self.right_image) def on_foldout_image_menu_option(self, menu, option): if option == 'view_source': self.show_image(self.foldout_image) elif option == 'export': self.start_export_filechooser(self.foldout_image) def start_export_filechooser(self, source_path): filename = os.path.basename(source_path) default_path = join(os.path.expanduser('~'), filename) root, ext = os.path.splitext(source_path) filters = [[ '{} image file'.format(ext), '*{}'.format(ext.lower()), '*{}'.format(ext.upper()) ]] filechooser = FileChooser() callback = partial(self.on_file_chooser_selection, source_path) filechooser.bind(on_selection=callback) filechooser.save_file(title='Export image', icon='./images/window_icon.png', filters=filters, path=default_path) def on_file_chooser_selection(self, source_path, chooser, selection): if selection: destination_path = selection[0] root, ext = os.path.splitext(source_path) if not destination_path.endswith(ext): destination_path += ext self.export_image(source_path, destination_path) def show_image(self, path): try: firefox = webbrowser.get('firefox') firefox.open(path) except Exception: Logger.exception( 'Calibration: Unable to open image "{}"'.format(path)) def export_image(self, source, destination): try: shutil.copyfile(source, destination) Logger.info('Calibration: Image exported from "{}" to "{}"'.format( source, destination)) except shutil.Error: Logger.exception('Calibration: Image source path are the same. ' 'Source "{}", destionation "{}"'.format( source, destination)) except IOError: Logger.exception( 'Calibration: Destination "{}" is not writable'.format( destination)) except Exception: Logger.exception('Calibration: Unable to export image from "{}" ' 'to "{}"'.format(source, destination)) def add_foldout_widget(self): self.foldout_widget = CalibrationWidgetFoldout() self.foldout_widget.scribe_widget = self.scribe_widget self.foldout_widget.calibration_widget = self self.foldout_widget.ids['_foldout_spinner'].values = [ self.scribe_widget.cameras.get_camera_port('left'), self.scribe_widget.cameras.get_camera_port('right'), self.scribe_widget.cameras.get_camera_port('foldout') ] self.foldout_widget.ids[ '_foldout_spinner'].text = self.scribe_widget.cameras.get_camera_port( 'foldout') self.add_widget(self.foldout_widget) def capture_images(self): page_left = self.ids['_left_image'] page_left.source = self.loading_image self.clean_temp_images() f = tempfile.NamedTemporaryFile(prefix='calibration', suffix='.jpg', delete=False) self.left_image = f.name f.close() f = tempfile.NamedTemporaryFile(prefix='calibration_thumb', suffix='.jpg', delete=False) self.left_thumb = f.name f.close() camera_kwargs = { camera_system.KEY_SIDE: 'left', camera_system.KEY_PATH: self.left_image, camera_system.KEY_THUMB_PATH: self.left_thumb, camera_system.KEY_IMAGE_WIDGET: page_left, camera_system.KEY_CALLBACK: self.show_image_callback } self.scribe_widget.left_queue.put(camera_kwargs) f = tempfile.NamedTemporaryFile(prefix='calibration', suffix='.jpg', delete=False) self.right_image = f.name f.close() f = tempfile.NamedTemporaryFile(prefix='calibration_thumb', suffix='.jpg', delete=False) self.right_thumb = f.name f.close() page_right = self.ids['_right_image'] page_right.source = self.loading_image camera_kwargs = { camera_system.KEY_SIDE: 'right', camera_system.KEY_PATH: self.right_image, camera_system.KEY_THUMB_PATH: self.right_thumb, camera_system.KEY_IMAGE_WIDGET: page_right, camera_system.KEY_CALLBACK: self.show_image_callback } self.scribe_widget.right_queue.put(camera_kwargs) if self.scribe_widget.cameras.get_camera_port('foldout') is not None: if self.foldout_widget is None: self.add_foldout_widget() foldout = self.foldout_widget.ids['_foldout_image'] foldout.source = self.loading_image f = tempfile.NamedTemporaryFile(prefix='calibration', suffix='.jpg', delete=False) self.foldout_image = f.name f.close() f = tempfile.NamedTemporaryFile(prefix='calibration_thumb', suffix='.jpg', delete=False) self.foldout_thumb = f.name f.close() camera_kwargs = { camera_system.KEY_SIDE: 'foldout', camera_system.KEY_PATH: self.foldout_image, camera_system.KEY_THUMB_PATH: self.foldout_thumb, camera_system.KEY_IMAGE_WIDGET: foldout, camera_system.KEY_CALLBACK: self.show_image_callback } self.scribe_widget.foldout_queue.put(camera_kwargs) def clean_temp_images(self): for image in (self.left_image, self.left_thumb, self.right_image, self.right_thumb, self.foldout_image, self.foldout_thumb): if image is not None and os.path.exists(image): os.unlink(image) def show_image_callback(self, report, *args): """This function modifies UI elements and needs to be scheduled on the main thread """ thumb_path = report[camera_system.KEY_THUMB_PATH] img_obj = report[camera_system.KEY_IMAGE_WIDGET] img_obj.source = thumb_path self.ids.box_bottons.disabled = False # self.check_agreement() def parse_gphoto_output(self, output): value = '' for line in output.split('\n'): if line.startswith('Current:'): value = line[8:].strip() break return value def show_config_error(self, setting, value, current_val, side): popup = InfoPopup(title='Camera Configuration Error', auto_dismiss=False, text_ok='Retry') popup.message = '\n\n'.join([ 'The {}-page camera is not configured correctly.'.format(side), 'The setting {} should be set to {},\n' 'but it is set to {}.'.format(setting, value, current_val), 'Note that the {}-page camera is mounted ' 'OPPOSITE the page it is capturing'.format(side) ]) popup.bind(on_submit=self._on_config_error_popup_submit) popup.open() def _on_config_error_popup_submit(self, popup, *args): popup.dismiss() self.callibration_screen.query_cameras() def swap_button(self): print("swapping!") self.scribe_widget.cameras.swap() #if self.scribe_widget.cameras.get_num_cameras() == 3: # self.scribe_widget.cameras.set_camera('foldout', # camera_ports['foldout']) self.left_port = self.scribe_widget.cameras.get_camera_port('left') self.right_port = self.scribe_widget.cameras.get_camera_port('right') print("CAPTURING AFTER SWAP WITH L = {0}, R= {1}".format( self.left_port, self.right_port)) self.ids.box_bottons.disabled = True self.clean_temp_images() self.capture_images() def reshoot_button(self): self.ids.box_bottons.disabled = True self.clean_temp_images() self.capture_images() def show_help_popup(self): popup = ScribeLearnMorePopup() popup.open() def done_button(self): # self.check_camera_config() self.ids.box_bottons.disabled = True self.clean_temp_images() self.scribe_widget.cameras.set_calibrated() self.goto_target_screen() def goto_target_screen(self): screen = self.screen_manager.get_screen(self.target_screen) screen.target_extra = self.target_extra self.screen_manager.transition.direction = 'left' self.screen_manager.current = self.target_screen def switch_foldout(self, spinner): text = spinner.text camera_ports = self.scribe_widget.cameras.camera_ports if text == self.scribe_widget.cameras.get_camera_port('foldout'): return if text == self.scribe_widget.cameras.get_camera_port('right'): #camera_ports['right'], camera_ports['foldout'] = camera_ports['foldout'], camera_ports['right'] pass else: #camera_ports['left'], camera_ports['foldout'] = camera_ports['foldout'], camera_ports['left'] pass self.left_port = self.scribe_widget.cameras.get_camera_port('left') self.right_port = self.scribe_widget.cameras.get_camera_port('right') self.clean_temp_images() self.capture_images() def exif_tag(self, tag, side): return "Unavailable" def on_use_tooltips(self, widget, use_tooltips): stack = self.children[:] while stack: widget = stack.pop() if isinstance(widget, TooltipControl): widget.use_tooltips = use_tooltips stack.extend(widget.children)
class LoadDialog(FloatLayout): load = ObjectProperty(None) cancel = ObjectProperty(None) fileChooser = ObjectProperty(None)
class ElectrumWindow(App): electrum_config = ObjectProperty(None) language = StringProperty('en') # properties might be updated by the network num_blocks = NumericProperty(0) num_nodes = NumericProperty(0) server_host = StringProperty('') server_port = StringProperty('') num_chains = NumericProperty(0) blockchain_name = StringProperty('') fee_status = StringProperty('Fee') balance = StringProperty('') fiat_balance = StringProperty('') is_fiat = BooleanProperty(False) blockchain_forkpoint = NumericProperty(0) auto_connect = BooleanProperty(False) def on_auto_connect(self, instance, x): net_params = self.network.get_parameters() net_params = net_params._replace(auto_connect=self.auto_connect) self.network.run_from_another_thread(self.network.set_parameters(net_params)) def toggle_auto_connect(self, x): self.auto_connect = not self.auto_connect oneserver = BooleanProperty(False) def on_oneserver(self, instance, x): net_params = self.network.get_parameters() net_params = net_params._replace(oneserver=self.oneserver) self.network.run_from_another_thread(self.network.set_parameters(net_params)) def toggle_oneserver(self, x): self.oneserver = not self.oneserver proxy_str = StringProperty('') def update_proxy_str(self, proxy: dict): mode = proxy.get('mode') host = proxy.get('host') port = proxy.get('port') self.proxy_str = (host + ':' + port) if mode else _('None') def choose_server_dialog(self, popup): from .uix.dialogs.choice_dialog import ChoiceDialog protocol = 's' def cb2(host): from electrum_exos import constants pp = servers.get(host, constants.net.DEFAULT_PORTS) port = pp.get(protocol, '') popup.ids.host.text = host popup.ids.port.text = port servers = self.network.get_servers() ChoiceDialog(_('Choose a server'), sorted(servers), popup.ids.host.text, cb2).open() def choose_blockchain_dialog(self, dt): from .uix.dialogs.choice_dialog import ChoiceDialog chains = self.network.get_blockchains() def cb(name): with blockchain.blockchains_lock: blockchain_items = list(blockchain.blockchains.items()) for chain_id, b in blockchain_items: if name == b.get_name(): self.network.run_from_another_thread(self.network.follow_chain_given_id(chain_id)) chain_objects = [blockchain.blockchains.get(chain_id) for chain_id in chains] chain_objects = filter(lambda b: b is not None, chain_objects) names = [b.get_name() for b in chain_objects] if len(names) > 1: cur_chain = self.network.blockchain().get_name() ChoiceDialog(_('Choose your chain'), names, cur_chain, cb).open() use_rbf = BooleanProperty(False) def on_use_rbf(self, instance, x): self.electrum_config.set_key('use_rbf', self.use_rbf, True) use_change = BooleanProperty(False) def on_use_change(self, instance, x): self.electrum_config.set_key('use_change', self.use_change, True) use_unconfirmed = BooleanProperty(False) def on_use_unconfirmed(self, instance, x): self.electrum_config.set_key('confirmed_only', not self.use_unconfirmed, True) def set_URI(self, uri): self.switch_to('send') self.send_screen.set_URI(uri) def on_new_intent(self, intent): if intent.getScheme() != 'exos': return uri = intent.getDataString() self.set_URI(uri) def on_language(self, instance, language): Logger.info('language: {}'.format(language)) _.switch_lang(language) def update_history(self, *dt): if self.history_screen: self.history_screen.update() def on_quotes(self, d): Logger.info("on_quotes") self._trigger_update_status() self._trigger_update_history() def on_history(self, d): Logger.info("on_history") self.wallet.clear_coin_price_cache() self._trigger_update_history() def on_fee_histogram(self, *args): self._trigger_update_history() def _get_bu(self): decimal_point = self.electrum_config.get('decimal_point', DECIMAL_POINT_DEFAULT) try: return decimal_point_to_base_unit_name(decimal_point) except UnknownBaseUnit: return decimal_point_to_base_unit_name(DECIMAL_POINT_DEFAULT) def _set_bu(self, value): assert value in base_units.keys() decimal_point = base_unit_name_to_decimal_point(value) self.electrum_config.set_key('decimal_point', decimal_point, True) self._trigger_update_status() self._trigger_update_history() wallet_name = StringProperty(_('No Wallet')) base_unit = AliasProperty(_get_bu, _set_bu) fiat_unit = StringProperty('') def on_fiat_unit(self, a, b): self._trigger_update_history() def decimal_point(self): return base_units[self.base_unit] def btc_to_fiat(self, amount_str): if not amount_str: return '' if not self.fx.is_enabled(): return '' rate = self.fx.exchange_rate() if rate.is_nan(): return '' fiat_amount = self.get_amount(amount_str + ' ' + self.base_unit) * rate / pow(10, 8) return "{:.2f}".format(fiat_amount).rstrip('0').rstrip('.') def fiat_to_btc(self, fiat_amount): if not fiat_amount: return '' rate = self.fx.exchange_rate() if rate.is_nan(): return '' satoshis = int(pow(10,8) * Decimal(fiat_amount) / Decimal(rate)) return format_satoshis_plain(satoshis, self.decimal_point()) def get_amount(self, amount_str): a, u = amount_str.split() assert u == self.base_unit try: x = Decimal(a) except: return None p = pow(10, self.decimal_point()) return int(p * x) _orientation = OptionProperty('landscape', options=('landscape', 'portrait')) def _get_orientation(self): return self._orientation orientation = AliasProperty(_get_orientation, None, bind=('_orientation',)) '''Tries to ascertain the kind of device the app is running on. Cane be one of `tablet` or `phone`. :data:`orientation` is a read only `AliasProperty` Defaults to 'landscape' ''' _ui_mode = OptionProperty('phone', options=('tablet', 'phone')) def _get_ui_mode(self): return self._ui_mode ui_mode = AliasProperty(_get_ui_mode, None, bind=('_ui_mode',)) '''Defines tries to ascertain the kind of device the app is running on. Cane be one of `tablet` or `phone`. :data:`ui_mode` is a read only `AliasProperty` Defaults to 'phone' ''' def __init__(self, **kwargs): # initialize variables self._clipboard = Clipboard self.info_bubble = None self.nfcscanner = None self.tabs = None self.is_exit = False self.wallet = None self.pause_time = 0 App.__init__(self)#, **kwargs) title = _('EXOS-Electrum App') self.electrum_config = config = kwargs.get('config', None) self.language = config.get('language', 'en') self.network = network = kwargs.get('network', None) # type: Network if self.network: self.num_blocks = self.network.get_local_height() self.num_nodes = len(self.network.get_interfaces()) net_params = self.network.get_parameters() self.server_host = net_params.host self.server_port = net_params.port self.auto_connect = net_params.auto_connect self.oneserver = net_params.oneserver self.proxy_config = net_params.proxy if net_params.proxy else {} self.update_proxy_str(self.proxy_config) self.plugins = kwargs.get('plugins', []) self.gui_object = kwargs.get('gui_object', None) self.daemon = self.gui_object.daemon self.fx = self.daemon.fx self.use_rbf = config.get('use_rbf', True) self.use_change = config.get('use_change', True) self.use_unconfirmed = not config.get('confirmed_only', False) # create triggers so as to minimize updating a max of 2 times a sec self._trigger_update_wallet = Clock.create_trigger(self.update_wallet, .5) self._trigger_update_status = Clock.create_trigger(self.update_status, .5) self._trigger_update_history = Clock.create_trigger(self.update_history, .5) self._trigger_update_interfaces = Clock.create_trigger(self.update_interfaces, .5) # cached dialogs self._settings_dialog = None self._password_dialog = None self.fee_status = self.electrum_config.get_fee_status() def on_pr(self, pr): if not self.wallet: self.show_error(_('No wallet loaded.')) return if pr.verify(self.wallet.contacts): key = self.wallet.invoices.add(pr) if self.invoices_screen: self.invoices_screen.update() status = self.wallet.invoices.get_status(key) if status == PR_PAID: self.show_error("invoice already paid") self.send_screen.do_clear() else: if pr.has_expired(): self.show_error(_('Payment request has expired')) else: self.switch_to('send') self.send_screen.set_request(pr) else: self.show_error("invoice error:" + pr.error) self.send_screen.do_clear() def on_qr(self, data): from electrum_exos.bitcoin import base_decode, is_address data = data.strip() if is_address(data): self.set_URI(data) return if data.startswith('exos:'): self.set_URI(data) return # try to decode transaction from electrum_exos.transaction import Transaction from electrum_exos.util import bh2u try: text = bh2u(base_decode(data, None, base=43)) tx = Transaction(text) tx.deserialize() except: tx = None if tx: self.tx_dialog(tx) return # show error self.show_error("Unable to decode QR data") def update_tab(self, name): s = getattr(self, name + '_screen', None) if s: s.update() @profiler def update_tabs(self): for tab in ['invoices', 'send', 'history', 'receive', 'address']: self.update_tab(tab) def switch_to(self, name): s = getattr(self, name + '_screen', None) if s is None: s = self.tabs.ids[name + '_screen'] s.load_screen() panel = self.tabs.ids.panel tab = self.tabs.ids[name + '_tab'] panel.switch_to(tab) def show_request(self, addr): self.switch_to('receive') self.receive_screen.screen.address = addr def show_pr_details(self, req, status, is_invoice): from electrum_exos.util import format_time requestor = req.get('requestor') exp = req.get('exp') memo = req.get('memo') amount = req.get('amount') fund = req.get('fund') popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/invoice.kv') popup.is_invoice = is_invoice popup.amount = amount popup.requestor = requestor if is_invoice else req.get('address') popup.exp = format_time(exp) if exp else '' popup.description = memo if memo else '' popup.signature = req.get('signature', '') popup.status = status popup.fund = fund if fund else 0 txid = req.get('txid') popup.tx_hash = txid or '' popup.on_open = lambda: popup.ids.output_list.update(req.get('outputs', [])) popup.export = self.export_private_keys popup.open() def show_addr_details(self, req, status): from electrum_exos.util import format_time fund = req.get('fund') isaddr = 'y' popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/invoice.kv') popup.isaddr = isaddr popup.is_invoice = False popup.status = status popup.requestor = req.get('address') popup.fund = fund if fund else 0 popup.export = self.export_private_keys popup.open() def qr_dialog(self, title, data, show_text=False, text_for_clipboard=None): from .uix.dialogs.qr_dialog import QRDialog def on_qr_failure(): popup.dismiss() msg = _('Failed to display QR code.') if text_for_clipboard: msg += '\n' + _('Text copied to clipboard.') self._clipboard.copy(text_for_clipboard) Clock.schedule_once(lambda dt: self.show_info(msg)) popup = QRDialog(title, data, show_text, on_qr_failure) popup.open() def scan_qr(self, on_complete): if platform != 'android': return from jnius import autoclass, cast from android import activity PythonActivity = autoclass('org.kivy.android.PythonActivity') SimpleScannerActivity = autoclass("org.electrum.qr.SimpleScannerActivity") Intent = autoclass('android.content.Intent') intent = Intent(PythonActivity.mActivity, SimpleScannerActivity) def on_qr_result(requestCode, resultCode, intent): try: if resultCode == -1: # RESULT_OK: # this doesn't work due to some bug in jnius: # contents = intent.getStringExtra("text") String = autoclass("java.lang.String") contents = intent.getStringExtra(String("text")) on_complete(contents) finally: activity.unbind(on_activity_result=on_qr_result) activity.bind(on_activity_result=on_qr_result) PythonActivity.mActivity.startActivityForResult(intent, 0) def do_share(self, data, title): if platform != 'android': return from jnius import autoclass, cast JS = autoclass('java.lang.String') Intent = autoclass('android.content.Intent') sendIntent = Intent() sendIntent.setAction(Intent.ACTION_SEND) sendIntent.setType("text/plain") sendIntent.putExtra(Intent.EXTRA_TEXT, JS(data)) PythonActivity = autoclass('org.kivy.android.PythonActivity') currentActivity = cast('android.app.Activity', PythonActivity.mActivity) it = Intent.createChooser(sendIntent, cast('java.lang.CharSequence', JS(title))) currentActivity.startActivity(it) def build(self): return Builder.load_file('electrum/gui/kivy/main.kv') def _pause(self): if platform == 'android': # move activity to back from jnius import autoclass python_act = autoclass('org.kivy.android.PythonActivity') mActivity = python_act.mActivity mActivity.moveTaskToBack(True) def on_start(self): ''' This is the start point of the kivy ui ''' import time Logger.info('Time to on_start: {} <<<<<<<<'.format(time.clock())) win = Window win.bind(size=self.on_size, on_keyboard=self.on_keyboard) win.bind(on_key_down=self.on_key_down) #win.softinput_mode = 'below_target' self.on_size(win, win.size) self.init_ui() crash_reporter.ExceptionHook(self) # init plugins run_hook('init_kivy', self) # fiat currency self.fiat_unit = self.fx.ccy if self.fx.is_enabled() else '' # default tab self.switch_to('history') # bind intent for exos: URI scheme if platform == 'android': from android import activity from jnius import autoclass PythonActivity = autoclass('org.kivy.android.PythonActivity') mactivity = PythonActivity.mActivity self.on_new_intent(mactivity.getIntent()) activity.bind(on_new_intent=self.on_new_intent) # connect callbacks if self.network: interests = ['wallet_updated', 'network_updated', 'blockchain_updated', 'status', 'new_transaction', 'verified'] self.network.register_callback(self.on_network_event, interests) self.network.register_callback(self.on_fee, ['fee']) self.network.register_callback(self.on_fee_histogram, ['fee_histogram']) self.network.register_callback(self.on_quotes, ['on_quotes']) self.network.register_callback(self.on_history, ['on_history']) # load wallet self.load_wallet_by_name(self.electrum_config.get_wallet_path()) # URI passed in config uri = self.electrum_config.get('url') if uri: self.set_URI(uri) def get_wallet_path(self): if self.wallet: return self.wallet.storage.path else: return '' def on_wizard_complete(self, wizard, wallet): if wallet: # wizard returned a wallet wallet.start_network(self.daemon.network) self.daemon.add_wallet(wallet) self.load_wallet(wallet) elif not self.wallet: # wizard did not return a wallet; and there is no wallet open atm # try to open last saved wallet (potentially start wizard again) self.load_wallet_by_name(self.electrum_config.get_wallet_path(), ask_if_wizard=True) def load_wallet_by_name(self, path, ask_if_wizard=False): if not path: return if self.wallet and self.wallet.storage.path == path: return wallet = self.daemon.load_wallet(path, None) if wallet: if wallet.has_password(): self.password_dialog(wallet, _('Enter PIN code'), lambda x: self.load_wallet(wallet), self.stop) else: self.load_wallet(wallet) else: def launch_wizard(): storage = WalletStorage(path, manual_upgrades=True) wizard = Factory.InstallWizard(self.electrum_config, self.plugins, storage) wizard.bind(on_wizard_complete=self.on_wizard_complete) action = wizard.storage.get_action() wizard.run(action) if not ask_if_wizard: launch_wizard() else: from .uix.dialogs.question import Question def handle_answer(b: bool): if b: launch_wizard() else: try: os.unlink(path) except FileNotFoundError: pass self.stop() d = Question(_('Do you want to launch the wizard again?'), handle_answer) d.open() def on_stop(self): Logger.info('on_stop') if self.wallet: self.electrum_config.save_last_wallet(self.wallet) self.stop_wallet() def stop_wallet(self): if self.wallet: self.daemon.stop_wallet(self.wallet.storage.path) self.wallet = None def on_key_down(self, instance, key, keycode, codepoint, modifiers): if 'ctrl' in modifiers: # q=24 w=25 if keycode in (24, 25): self.stop() elif keycode == 27: # r=27 # force update wallet self.update_wallet() elif keycode == 112: # pageup #TODO move to next tab pass elif keycode == 117: # pagedown #TODO move to prev tab pass #TODO: alt+tab_number to activate the particular tab def on_keyboard(self, instance, key, keycode, codepoint, modifiers): if key == 27 and self.is_exit is False: self.is_exit = True self.show_info(_('Press again to exit')) return True # override settings button if key in (319, 282): #f1/settings button on android #self.gui.main_gui.toggle_settings(self) return True def settings_dialog(self): from .uix.dialogs.settings import SettingsDialog if self._settings_dialog is None: self._settings_dialog = SettingsDialog(self) self._settings_dialog.update() self._settings_dialog.open() def popup_dialog(self, name): if name == 'settings': self.settings_dialog() elif name == 'wallets': from .uix.dialogs.wallets import WalletDialog d = WalletDialog() d.open() elif name == 'status': popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/'+name+'.kv') master_public_keys_layout = popup.ids.master_public_keys for xpub in self.wallet.get_master_public_keys()[1:]: master_public_keys_layout.add_widget(TopLabel(text=_('Master Public Key'))) ref = RefLabel() ref.name = _('Master Public Key') ref.data = xpub master_public_keys_layout.add_widget(ref) popup.open() else: popup = Builder.load_file('electrum/gui/kivy/uix/ui_screens/'+name+'.kv') popup.open() @profiler def init_ui(self): ''' Initialize The Ux part of exos-electrum. This function performs the basic tasks of setting up the ui. ''' #from weakref import ref self.funds_error = False # setup UX self.screens = {} #setup lazy imports for mainscreen Factory.register('AnimatedPopup', module='electrum_exos.gui.kivy.uix.dialogs') Factory.register('QRCodeWidget', module='electrum_exos.gui.kivy.uix.qrcodewidget') # preload widgets. Remove this if you want to load the widgets on demand #Cache.append('electrum_widgets', 'AnimatedPopup', Factory.AnimatedPopup()) #Cache.append('electrum_widgets', 'QRCodeWidget', Factory.QRCodeWidget()) # load and focus the ui self.root.manager = self.root.ids['manager'] self.history_screen = None self.contacts_screen = None self.send_screen = None self.invoices_screen = None self.receive_screen = None self.requests_screen = None self.address_screen = None self.icon = "electrum/gui/icons/electrum.png" self.tabs = self.root.ids['tabs'] def update_interfaces(self, dt): net_params = self.network.get_parameters() self.num_nodes = len(self.network.get_interfaces()) self.num_chains = len(self.network.get_blockchains()) chain = self.network.blockchain() self.blockchain_forkpoint = chain.get_max_forkpoint() self.blockchain_name = chain.get_name() interface = self.network.interface if interface: self.server_host = interface.host else: self.server_host = str(net_params.host) + ' (connecting...)' self.proxy_config = net_params.proxy or {} self.update_proxy_str(self.proxy_config) def on_network_event(self, event, *args): Logger.info('network event: '+ event) if event == 'network_updated': self._trigger_update_interfaces() self._trigger_update_status() elif event == 'wallet_updated': self._trigger_update_wallet() self._trigger_update_status() elif event == 'blockchain_updated': # to update number of confirmations in history self._trigger_update_wallet() elif event == 'status': self._trigger_update_status() elif event == 'new_transaction': self._trigger_update_wallet() elif event == 'verified': self._trigger_update_wallet() @profiler def load_wallet(self, wallet): if self.wallet: self.stop_wallet() self.wallet = wallet self.wallet_name = wallet.basename() self.update_wallet() # Once GUI has been initialized check if we want to announce something # since the callback has been called before the GUI was initialized if self.receive_screen: self.receive_screen.clear() self.update_tabs() run_hook('load_wallet', wallet, self) try: wallet.try_detecting_internal_addresses_corruption() except InternalAddressCorruption as e: self.show_error(str(e)) send_exception_to_crash_reporter(e) def update_status(self, *dt): if not self.wallet: return if self.network is None or not self.network.is_connected(): status = _("Offline") elif self.network.is_connected(): self.num_blocks = self.network.get_local_height() server_height = self.network.get_server_height() server_lag = self.num_blocks - server_height if not self.wallet.up_to_date or server_height == 0: status = _("Synchronizing...") elif server_lag > 1: status = _("Server lagging") else: status = '' else: status = _("Disconnected") if status: self.balance = status self.fiat_balance = status else: c, u, x = self.wallet.get_balance() text = self.format_amount(c+x+u) self.balance = str(text.strip()) + ' [size=22dp]%s[/size]'% self.base_unit self.fiat_balance = self.fx.format_amount(c+u+x) + ' [size=22dp]%s[/size]'% self.fx.ccy def get_max_amount(self): from electrum_exos.transaction import TxOutput if run_hook('abort_send', self): return '' inputs = self.wallet.get_spendable_coins(None, self.electrum_config) if not inputs: return '' addr = str(self.send_screen.screen.address) or self.wallet.dummy_address() outputs = [TxOutput(TYPE_ADDRESS, addr, '!')] try: tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config) except NoDynamicFeeEstimates as e: Clock.schedule_once(lambda dt, bound_e=e: self.show_error(str(bound_e))) return '' except NotEnoughFunds: return '' except InternalAddressCorruption as e: self.show_error(str(e)) send_exception_to_crash_reporter(e) return '' amount = tx.output_value() __, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0) amount_after_all_fees = amount - x_fee_amount return format_satoshis_plain(amount_after_all_fees, self.decimal_point()) def format_amount(self, x, is_diff=False, whitespaces=False): return format_satoshis(x, 0, self.decimal_point(), is_diff=is_diff, whitespaces=whitespaces) def format_amount_and_units(self, x): return format_satoshis_plain(x, self.decimal_point()) + ' ' + self.base_unit #@profiler def update_wallet(self, *dt): self._trigger_update_status() if self.wallet and (self.wallet.up_to_date or not self.network or not self.network.is_connected()): self.update_tabs() def notify(self, message): try: global notification, os if not notification: from plyer import notification icon = (os.path.dirname(os.path.realpath(__file__)) + '/../../' + self.icon) notification.notify('EXOS-Electrum', message, app_icon=icon, app_name='EXOS-Electrum') except ImportError: Logger.Error('Notification: needs plyer; `sudo python3 -m pip install plyer`') def on_pause(self): self.pause_time = time.time() # pause nfc if self.nfcscanner: self.nfcscanner.nfc_disable() return True def on_resume(self): now = time.time() if self.wallet and self.wallet.has_password() and now - self.pause_time > 60: self.password_dialog(self.wallet, _('Enter PIN'), None, self.stop) if self.nfcscanner: self.nfcscanner.nfc_enable() def on_size(self, instance, value): width, height = value self._orientation = 'landscape' if width > height else 'portrait' self._ui_mode = 'tablet' if min(width, height) > inch(3.51) else 'phone' def on_ref_label(self, label, touch): if label.touched: label.touched = False self.qr_dialog(label.name, label.data, True) else: label.touched = True self._clipboard.copy(label.data) Clock.schedule_once(lambda dt: self.show_info(_('Text copied to clipboard.\nTap again to display it as QR code.'))) def show_error(self, error, width='200dp', pos=None, arrow_pos=None, exit=False, icon='atlas://electrum/gui/kivy/theming/light/error', duration=0, modal=False): ''' Show an error Message Bubble. ''' self.show_info_bubble( text=error, icon=icon, width=width, pos=pos or Window.center, arrow_pos=arrow_pos, exit=exit, duration=duration, modal=modal) def show_info(self, error, width='200dp', pos=None, arrow_pos=None, exit=False, duration=0, modal=False): ''' Show an Info Message Bubble. ''' self.show_error(error, icon='atlas://electrum/gui/kivy/theming/light/important', duration=duration, modal=modal, exit=exit, pos=pos, arrow_pos=arrow_pos) def show_info_bubble(self, text=_('Hello World'), pos=None, duration=0, arrow_pos='bottom_mid', width=None, icon='', modal=False, exit=False): '''Method to show an Information Bubble .. parameters:: text: Message to be displayed pos: position for the bubble duration: duration the bubble remains on screen. 0 = click to hide width: width of the Bubble arrow_pos: arrow position for the bubble ''' info_bubble = self.info_bubble if not info_bubble: info_bubble = self.info_bubble = Factory.InfoBubble() win = Window if info_bubble.parent: win.remove_widget(info_bubble if not info_bubble.modal else info_bubble._modal_view) if not arrow_pos: info_bubble.show_arrow = False else: info_bubble.show_arrow = True info_bubble.arrow_pos = arrow_pos img = info_bubble.ids.img if text == 'texture': # icon holds a texture not a source image # display the texture in full screen text = '' img.texture = icon info_bubble.fs = True info_bubble.show_arrow = False img.allow_stretch = True info_bubble.dim_background = True info_bubble.background_image = 'atlas://electrum/gui/kivy/theming/light/card' else: info_bubble.fs = False info_bubble.icon = icon #if img.texture and img._coreimage: # img.reload() img.allow_stretch = False info_bubble.dim_background = False info_bubble.background_image = 'atlas://data/images/defaulttheme/bubble' info_bubble.message = text if not pos: pos = (win.center[0], win.center[1] - (info_bubble.height/2)) info_bubble.show(pos, duration, width, modal=modal, exit=exit) def tx_dialog(self, tx): from .uix.dialogs.tx_dialog import TxDialog d = TxDialog(self, tx) d.open() def sign_tx(self, *args): threading.Thread(target=self._sign_tx, args=args).start() def _sign_tx(self, tx, password, on_success, on_failure): try: self.wallet.sign_transaction(tx, password) except InvalidPassword: Clock.schedule_once(lambda dt: on_failure(_("Invalid PIN"))) return on_success = run_hook('tc_sign_wrapper', self.wallet, tx, on_success, on_failure) or on_success Clock.schedule_once(lambda dt: on_success(tx)) def _broadcast_thread(self, tx, on_complete): status = False try: self.network.run_from_another_thread(self.network.broadcast_transaction(tx)) except TxBroadcastError as e: msg = e.get_message_for_gui() except BestEffortRequestFailed as e: msg = repr(e) else: status, msg = True, tx.txid() Clock.schedule_once(lambda dt: on_complete(status, msg)) def broadcast(self, tx, pr=None): def on_complete(ok, msg): if ok: self.show_info(_('Payment sent.')) if self.send_screen: self.send_screen.do_clear() if pr: self.wallet.invoices.set_paid(pr, tx.txid()) self.wallet.invoices.save() self.update_tab('invoices') else: msg = msg or '' self.show_error(msg) if self.network and self.network.is_connected(): self.show_info(_('Sending')) threading.Thread(target=self._broadcast_thread, args=(tx, on_complete)).start() else: self.show_info(_('Cannot broadcast transaction') + ':\n' + _('Not connected')) def description_dialog(self, screen): from .uix.dialogs.label_dialog import LabelDialog text = screen.message def callback(text): screen.message = text d = LabelDialog(_('Enter description'), text, callback) d.open() def amount_dialog(self, screen, show_max): from .uix.dialogs.amount_dialog import AmountDialog amount = screen.amount if amount: amount, u = str(amount).split() assert u == self.base_unit def cb(amount): screen.amount = amount popup = AmountDialog(show_max, amount, cb) popup.open() def invoices_dialog(self, screen): from .uix.dialogs.invoices import InvoicesDialog if len(self.wallet.invoices.sorted_list()) == 0: self.show_info(' '.join([ _('No saved invoices.'), _('Signed invoices are saved automatically when you scan them.'), _('You may also save unsigned requests or contact addresses using the save button.') ])) return popup = InvoicesDialog(self, screen, None) popup.update() popup.open() def requests_dialog(self, screen): from .uix.dialogs.requests import RequestsDialog if len(self.wallet.get_sorted_requests(self.electrum_config)) == 0: self.show_info(_('No saved requests.')) return popup = RequestsDialog(self, screen, None) popup.update() popup.open() def addresses_dialog(self, screen): from .uix.dialogs.addresses import AddressesDialog popup = AddressesDialog(self, screen, None) popup.update() popup.open() def fee_dialog(self, label, dt): from .uix.dialogs.fee_dialog import FeeDialog def cb(): self.fee_status = self.electrum_config.get_fee_status() fee_dialog = FeeDialog(self, self.electrum_config, cb) fee_dialog.open() def on_fee(self, event, *arg): self.fee_status = self.electrum_config.get_fee_status() def protected(self, msg, f, args): if self.wallet.has_password(): on_success = lambda pw: f(*(args + (pw,))) self.password_dialog(self.wallet, msg, on_success, lambda: None) else: f(*(args + (None,))) def delete_wallet(self): from .uix.dialogs.question import Question basename = os.path.basename(self.wallet.storage.path) d = Question(_('Delete wallet?') + '\n' + basename, self._delete_wallet) d.open() def _delete_wallet(self, b): if b: basename = self.wallet.basename() self.protected(_("Enter your PIN code to confirm deletion of {}").format(basename), self.__delete_wallet, ()) def __delete_wallet(self, pw): wallet_path = self.get_wallet_path() dirname = os.path.dirname(wallet_path) basename = os.path.basename(wallet_path) if self.wallet.has_password(): try: self.wallet.check_password(pw) except: self.show_error("Invalid PIN") return self.stop_wallet() os.unlink(wallet_path) self.show_error(_("Wallet removed: {}").format(basename)) new_path = self.electrum_config.get_wallet_path() self.load_wallet_by_name(new_path) def show_seed(self, label): self.protected(_("Enter your PIN code in order to decrypt your seed"), self._show_seed, (label,)) def _show_seed(self, label, password): if self.wallet.has_password() and password is None: return keystore = self.wallet.keystore try: seed = keystore.get_seed(password) passphrase = keystore.get_passphrase(password) except: self.show_error("Invalid PIN") return label.text = _('Seed') + ':\n' + seed if passphrase: label.text += '\n\n' + _('Passphrase') + ': ' + passphrase def password_dialog(self, wallet, msg, on_success, on_failure): from .uix.dialogs.password_dialog import PasswordDialog if self._password_dialog is None: self._password_dialog = PasswordDialog() self._password_dialog.init(self, wallet, msg, on_success, on_failure) self._password_dialog.open() def change_password(self, cb): from .uix.dialogs.password_dialog import PasswordDialog if self._password_dialog is None: self._password_dialog = PasswordDialog() message = _("Changing PIN code.") + '\n' + _("Enter your current PIN:") def on_success(old_password, new_password): self.wallet.update_password(old_password, new_password) self.show_info(_("Your PIN code was updated")) on_failure = lambda: self.show_error(_("PIN codes do not match")) self._password_dialog.init(self, self.wallet, message, on_success, on_failure, is_change=1) self._password_dialog.open() def export_private_keys(self, pk_label, addr): if self.wallet.is_watching_only(): self.show_info(_('This is a watching-only wallet. It does not contain private keys.')) return def show_private_key(addr, pk_label, password): if self.wallet.has_password() and password is None: return if not self.wallet.can_export(): return try: key = str(self.wallet.export_private_key(addr, password)[0]) pk_label.data = key except InvalidPassword: self.show_error("Invalid PIN") return self.protected(_("Enter your PIN code in order to decrypt your private key"), show_private_key, (addr, pk_label))
class Game(Widget): car = ObjectProperty(None) ballg = ObjectProperty(None) #Setup start_timesteps = 1e3 # Number of iterations/timesteps before which the model randomly chooses an action, and after which it starts to use the policy network eval_freq = 5e2 # How often the evaluation step is performed (after how many timesteps) max_timesteps = 5e4 # Total number of iterations/timesteps save_models = True # Boolean checker whether or not to save the pre-trained model expl_noise = 0.1 # Exploration noise - STD value of exploration Gaussian noise batch_size = 100 # Size of the batch discount = 0.99 # Discount factor gamma, used in the calculation of the total discounted reward tau = 0.005 # Target network update rate policy_noise = 0.2 # STD of Gaussian noise added to the actions for the exploration purposes noise_clip = 0.5 # Maximum value of the Gaussian noise added to the actions (policy) policy_freq = 2 # Number of iterations to wait before the policy network (Actor model) is updated goal_x = 120 goal_y = 700 img = cv2.imread("./images/plain.png") img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) state_dim = 12 action_dim = 1 max_action = 1.0 policy = TD3(state_dim, action_dim, max_action) env_state = DNN() replay_buffer = ReplayBuffer() max_episode_steps = 1000 total_timesteps = 0 episode_reward = 0 timesteps_since_eval = 0 episode_num = 0 done = True t0 = time.time() last_reward = 0 living_penalty = 0 last_distance = 0 swap = 0 obs = [] action = 5 file_name = "T3D_car_0" if save_models and not os.path.exists("./pytorch_models"): os.makedirs("./pytorch_models") def serve_car(self): # print(self.center) self.car.center = [865, 640] self.car.velocity = Vector(1, 0) def update(self, dt): # global last_reward # global last_x # global last_y # global last_distance # global goal_x # global goal_y # global swap # global living_penalty if self.total_timesteps > self.max_timesteps: CarApp().stop() print(self.total_timesteps) if self.done: if self.total_timesteps != 0: print("Total Timesteps: {} Episode Num: {} Reward: {}".format( self.total_timesteps, self.episode_num, self.episode_reward)) self.policy.train(self.replay_buffer, self.episode_timesteps, self.batch_size, self.discount, self.tau, self.policy_noise, self.noise_clip, self.policy_freq) if self.timesteps_since_eval >= self.eval_freq: self.timesteps_since_eval %= self.eval_freq self.policy.save(self.file_name, directory="./pytorch_models") self.car.x = random.randint(int(self.width * 0.2), int(self.width * 0.8)) self.car.y = random.randint(int(self.height * 0.2), int(self.height * 0.8)) self.obs = get_obs(self.img, self.car.x, self.car.y, self.goal_x, self.goal_y, self.env_state, self.car) self.done = False self.episode_reward = 0 self.episode_timesteps = 0 self.episode_num += 1 self.last_reward = 0 if self.total_timesteps < self.start_timesteps: self.action = np.array([random.uniform(-1, 1)]) else: # After 10000 timesteps, we switch to the model self.action = self.policy.select_action(np.array(self.obs)) if self.expl_noise != 0: self.action = (self.action + np.random.normal( 0, self.expl_noise, size=1)).clip(-10, 10) * 10 self.car.move(int(self.action[0] * 10)) new_obs = get_obs(self.img, self.car.x, self.car.y, self.goal_x, self.goal_y, self.env_state, self.car) # print("obs", new_obs.shape, type(new_obs)) distance = np.sqrt((self.car.x - self.goal_x)**2 + (self.car.y - self.goal_y)**2) if self.img[int(self.car.x), int(self.car.y)] > 0.8: self.car.velocity = Vector(0.4, 0).rotate(self.car.angle) # print(1, goal_x, goal_y, distance, int(car.x),int(car.y), im.read_pixel(int(car.x),int(car.y))) if distance > self.last_distance: self.last_reward = -1.8 else: self.last_reward = -1.2 elif self.img[int(self.car.x), int(self.car.y)] > 0.1 and self.img[ int(self.car.x), int(self.car.y)] < 0.7: self.car.velocity = Vector(0.8, 0).rotate(self.car.angle) # print(1, goal_x, goal_y, distance, int(car.x),int(car.y), im.read_pixel(int(car.x),int(car.y))) if distance > self.last_distance: self.last_reward = -0.9 else: self.last_reward = -0.6 else: # otherwise self.car.velocity = Vector(2, 0).rotate(self.car.angle) # print(0, goal_x, goal_y, distance, int(car.x),int(car.y), im.read_pixel(int(car.x),int(car.y))) if distance < self.last_distance: self.last_reward = 0.3 # living_penalty -= 0.002 else: self.last_reward = -0.2 # living_penalty += 0.002 if self.car.x <= 25: self.car.x = 25 self.last_reward = -2.7 if self.car.x >= self.width - 25: self.car.x = self.width - 25 self.last_reward = -2.7 if self.car.y <= 25: self.car.y = 25 self.last_reward = -2.7 if self.car.y >= self.height - 25: self.car.y = self.height - 25 self.last_reward = -2.7 self.living_penalty += 0.02 self.last_reward -= self.living_penalty print(self.last_reward) if distance < 15: if self.swap == 1: self.goal_x = 865 self.goal_y = 660 self.swap = 2 self.living_penalty = 0 self.last_reward = 0 self.done = True elif self.swap == 2: self.goal_x = 120 self.goal_y = 700 self.swap = 0 self.living_penalty = 0 self.last_reward = 0 self.done = True else: self.goal_x = 660 self.goal_y = 60 self.swap = 1 self.living_penalty = 0 self.last_reward = 0 self.done = True self.last_distance = distance if self.last_reward < -12: self.done = True self.last_reward -= 10 self.ballg.pos = (self.goal_x, self.goal_y) # new_obs, reward, done, _ = env.step(action) done_bool = 0 if self.episode_timesteps + 1 == 1000 else float( self.done) self.episode_reward += self.last_reward # print("action", self.action.shape, type(self.action)) self.replay_buffer.add( (self.obs, new_obs, self.action, self.last_reward, done_bool)) print(self.goal_x, self.goal_y, self.car.x, self.car.y, done_bool) self.obs = new_obs self.episode_timesteps += 1 self.total_timesteps += 1 self.timesteps_since_eval += 1 if self.save_models: self.policy.save("%s" % (self.file_name), directory="./pytorch_models")
class ChangeTableSizeDialog(GridLayout): accept = ObjectProperty(None) cancel = ObjectProperty(None)
class TableSizeManager(FloatLayout): scroll_view = ObjectProperty(None) def dismiss_popup(self): self._popup.dismiss() def show_add_rows_dialog(self): content = ChangeTableSizeDialog(accept=self.insert_rows, cancel=self.dismiss_popup) self._popup = Popup(title='Add rows', content=content, size_hint=(.5, .5)) self._popup.open() def show_add_cols_dialog(self): content = ChangeTableSizeDialog(accept=self.insert_cols, cancel=self.dismiss_popup) self._popup = Popup(title='Add cols', content=content, size_hint=(.5, .5)) self._popup.open() def show_remove_rows_dialog(self): content = ChangeTableSizeDialog(accept=self.remove_rows, cancel=self.dismiss_popup) self._popup = Popup(title='Remove rows', content=content, size_hint=(.5, .5)) self._popup.open() def show_remove_cols_dialog(self): content = ChangeTableSizeDialog(accept=self.remove_cols, cancel=self.dismiss_popup) self._popup = Popup(title='Remove cols', content=content, size_hint=(.5, .5)) self._popup.open() def insert_rows(self, str_n, str_begin_index): n, begin_index = self.validate_input(str_n, str_begin_index) self.scroll_view.insert_empty_rows(int(n), int(begin_index)) self._popup.dismiss() def insert_cols(self, str_n, str_begin_index): n, begin_index = self.validate_input(str_n, str_begin_index) self.scroll_view.insert_empty_cols(n, begin_index) self._popup.dismiss() def remove_rows(self, str_n, str_begin_index): n, begin_index = self.validate_input(str_n, str_begin_index) self.scroll_view.remove_rows(n, begin_index) self._popup.dismiss() def remove_cols(self, str_n, str_begin_index): n, begin_index = self.validate_input(str_n, str_begin_index) self.scroll_view.remove_cols(n, begin_index) self._popup.dismiss() @staticmethod def validate_input(str_n, str_begin_index): if str_n == '': str_n = 0 if str_begin_index == '': str_begin_index = 0 return int(str_n), int(str_begin_index)
class ElectrumWindow(App): electrum_config = ObjectProperty(None) def _get_bu(self): return self.electrum_config.get('base_unit', 'mBTC') def _set_bu(self, value): assert value in base_units.keys() self.electrum_config.set_key('base_unit', value, True) self.update_status() if self.history_screen: self.history_screen.update() base_unit = AliasProperty(_get_bu, _set_bu) def _rotate_bu(self): keys = sorted(base_units.keys()) self.base_unit = keys[(keys.index(self.base_unit) + 1) % len(keys)] status = StringProperty(_('Not Connected')) def decimal_point(self): return base_units[self.base_unit] def _get_num_zeros(self): try: return self.electrum_config.get('num_zeros', 0) except AttributeError: return 0 def _set_num_zeros(self): try: self.electrum_config.set_key('num_zeros', value, True) except AttributeError: Logger.error('Electrum: Config not available ' 'While trying to save value to config') num_zeros = AliasProperty(_get_num_zeros, _set_num_zeros) '''Number of zeros used while representing the value in base_unit. ''' def get_amount(self, amount_str): a, u = amount_str.split() assert u == self.base_unit try: x = Decimal(a) except: return None p = pow(10, self.decimal_point()) return int(p * x) hierarchy = ListProperty([]) '''used to navigate with the back button. ''' _orientation = OptionProperty('landscape', options=('landscape', 'portrait')) def _get_orientation(self): return self._orientation orientation = AliasProperty(_get_orientation, None, bind=('_orientation', )) '''Tries to ascertain the kind of device the app is running on. Cane be one of `tablet` or `phone`. :data:`orientation` is a read only `AliasProperty` Defaults to 'landscape' ''' _ui_mode = OptionProperty('phone', options=('tablet', 'phone')) def _get_ui_mode(self): return self._ui_mode ui_mode = AliasProperty(_get_ui_mode, None, bind=('_ui_mode', )) '''Defines tries to ascertain the kind of device the app is running on. Cane be one of `tablet` or `phone`. :data:`ui_mode` is a read only `AliasProperty` Defaults to 'phone' ''' url = StringProperty('', allownone=True) ''' ''' wallet = ObjectProperty(None) '''Holds the electrum wallet :attr:`wallet` is a `ObjectProperty` defaults to None. ''' def __init__(self, **kwargs): # initialize variables self._clipboard = None self.info_bubble = None self.qrscanner = None self.nfcscanner = None self.tabs = None super(ElectrumWindow, self).__init__(**kwargs) title = _('Electrum App') self.electrum_config = config = kwargs.get('config', None) self.network = network = kwargs.get('network', None) self.plugins = kwargs.get('plugins', []) self.gui_object = kwargs.get('gui_object', None) #self.config = self.gui_object.config self.contacts = Contacts(self.electrum_config) self.bind(url=self.set_url) # were we sent a url? url = self.electrum_config.get('url', None) if url: self.set_url(url) # create triggers so as to minimize updation a max of 2 times a sec self._trigger_update_wallet =\ Clock.create_trigger(self.update_wallet, .5) self._trigger_update_status =\ Clock.create_trigger(self.update_status, .5) self._trigger_notify_transactions = \ Clock.create_trigger(self.notify_transactions, 5) def set_url(self, url): print "set url", url url = electrum.util.parse_URI(url) self.send_screen.set_qr_data(url) def scan_qr(self, on_complete): from jnius import autoclass from android import activity PythonActivity = autoclass('org.renpy.android.PythonActivity') Intent = autoclass('android.content.Intent') intent = Intent("com.google.zxing.client.android.SCAN") intent.putExtra("SCAN_MODE", "QR_CODE_MODE") def on_qr_result(requestCode, resultCode, intent): if requestCode == 0: if resultCode == -1: # RESULT_OK: contents = intent.getStringExtra("SCAN_RESULT") if intent.getStringExtra( "SCAN_RESULT_FORMAT") == 'QR_CODE': uri = electrum.util.parse_URI(contents) on_complete(uri) activity.bind(on_activity_result=on_qr_result) PythonActivity.mActivity.startActivityForResult(intent, 0) def show_plugins(self, plugins_list): def on_checkbox_active(cb, value): self.plugins.toggle_enabled(self.electrum_config, cb.name) for item in self.plugins.descriptions: if 'kivy' not in item.get('available_for', []): continue name = item.get('__name__') label = Label(text=item.get('fullname'), height='48db', size_hint=(1, None)) plugins_list.add_widget(label) cb = CheckBox() cb.name = name p = self.plugins.get(name) cb.active = (p is not None) and p.is_enabled() cb.bind(active=on_checkbox_active) plugins_list.add_widget(cb) def build(self): return Builder.load_file('gui/kivy/main.kv') def _pause(self): if platform == 'android': # move activity to back from jnius import autoclass python_act = autoclass('org.renpy.android.PythonActivity') mActivity = python_act.mActivity mActivity.moveTaskToBack(True) def on_start(self): ''' This is the start point of the kivy ui ''' Logger.info("dpi: {} {}".format(metrics.dpi, metrics.dpi_rounded)) win = Window win.bind(size=self.on_size, on_keyboard=self.on_keyboard) win.bind(on_key_down=self.on_key_down) # Register fonts without this you won't be able to use bold/italic... # inside markup. from kivy.core.text import Label Label.register('Roboto', 'data/fonts/Roboto.ttf', 'data/fonts/Roboto.ttf', 'data/fonts/Roboto-Bold.ttf', 'data/fonts/Roboto-Bold.ttf') if platform == 'android': # bind to keyboard height so we can get the window contents to # behave the way we want when the keyboard appears. win.bind(keyboard_height=self.on_keyboard_height) self.on_size(win, win.size) config = self.electrum_config storage = WalletStorage(config.get_wallet_path()) Logger.info('Electrum: Check for existing wallet') if storage.file_exists: wallet = Wallet(storage) action = wallet.get_action() else: action = 'new' if action is not None: # start installation wizard Logger.debug( 'Electrum: Wallet not found. Launching install wizard') wizard = Factory.InstallWizard(config, self.network, storage) wizard.bind(on_wizard_complete=self.on_wizard_complete) wizard.run(action) else: wallet.start_threads(self.network) self.on_wizard_complete(None, wallet) self.on_resume() def on_stop(self): if self.wallet: self.wallet.stop_threads() def on_back(self): try: self.hierarchy.pop()() except IndexError: # capture back button and pause app. self._pause() def on_keyboard_height(self, window, height): win = window active_widg = win.children[0] if not issubclass(active_widg.__class__, Factory.Popup): try: active_widg = self.root.children[0] except IndexError: return try: fw = self._focused_widget except AttributeError: return if height > 0 and fw.to_window(*fw.pos)[1] > height: return Factory.Animation(y=win.keyboard_height, d=.1).start(active_widg) def on_key_down(self, instance, key, keycode, codepoint, modifiers): if 'ctrl' in modifiers: # q=24 w=25 if keycode in (24, 25): self.stop() elif keycode == 27: # r=27 # force update wallet self.update_wallet() elif keycode == 112: # pageup #TODO move to next tab pass elif keycode == 117: # pagedown #TODO move to prev tab pass #TODO: alt+tab_number to activate the particular tab def on_keyboard(self, instance, key, keycode, codepoint, modifiers): # override settings button if key in (319, 282): #f1/settings button on android self.gui.main_gui.toggle_settings(self) return True def on_wizard_complete(self, instance, wallet): if not wallet: Logger.debug('Electrum: No Wallet set/found. Exiting...') app = App.get_running_app() app.show_error('Electrum: No Wallet set/found. Exiting...', exit=True) self.init_ui() self.load_wallet(wallet) def popup_dialog(self, name): popup = Builder.load_file('gui/kivy/uix/ui_screens/' + name + '.kv') popup.open() @profiler def init_ui(self): ''' Initialize The Ux part of electrum. This function performs the basic tasks of setting up the ui. ''' from weakref import ref set_language(self.electrum_config.get('language')) self.funds_error = False # setup UX self.screens = {} #setup lazy imports for mainscreen Factory.register('AnimatedPopup', module='electrum_gui.kivy.uix.dialogs') Factory.register('QRCodeWidget', module='electrum_gui.kivy.uix.qrcodewidget') # preload widgets. Remove this if you want to load the widgets on demand #Cache.append('electrum_widgets', 'AnimatedPopup', Factory.AnimatedPopup()) #Cache.append('electrum_widgets', 'QRCodeWidget', Factory.QRCodeWidget()) # load and focus the ui self.root.manager = self.root.ids['manager'] self.recent_activity_card = None self.history_screen = None self.contacts_screen = None self.icon = "icons/electrum.png" # connect callbacks if self.network: interests = ['updated', 'status', 'new_transaction'] self.network.register_callback(self.on_network, interests) self.wallet = None def on_network(self, event, *args): if event == 'updated': self._trigger_update_wallet() elif event == 'status': self._trigger_update_status() elif event == 'new_transaction': self._trigger_notify_transactions(*args) @profiler def load_wallet(self, wallet): self.wallet = wallet self.current_account = self.wallet.storage.get('current_account', None) self.update_wallet() # Once GUI has been initialized check if we want to announce something # since the callback has been called before the GUI was initialized self.update_history_tab() self.notify_transactions() run_hook('load_wallet', wallet, self) def update_status(self, *dt): if not self.wallet: return unconfirmed = '' quote_text = '' if self.network is None or not self.network.is_running(): text = _("Offline") elif self.network.is_connected(): server_height = self.network.get_server_height() server_lag = self.network.get_local_height() - server_height if not self.wallet.up_to_date or server_height == 0: self.status = _("Synchronizing...") elif server_lag > 1: self.status = _("Server lagging (%d blocks)" % server_lag) else: c, u, x = self.wallet.get_account_balance(self.current_account) text = self.format_amount(c) if u: unconfirmed = " [%s unconfirmed]" % (self.format_amount( u, True).strip()) if x: unmatured = " [%s unmatured]" % (self.format_amount( x, True).strip()) #quote_text = self.create_quote_text(Decimal(c+u+x)/100000000, mode='symbol') or '' self.status = text.strip() + ' ' + self.base_unit else: self.status = _("Not connected") return print self.root.manager.ids #try: status_card = self.root.main_screen.ids.tabs.ids.\ screen_dashboard.ids.status_card #except AttributeError: # return status_card.quote_text = quote_text.strip() status_card.uncomfirmed = unconfirmed.strip() def get_max_amount(self): from electrum.util import format_satoshis_plain inputs = self.wallet.get_spendable_coins(None) amount, fee = self.wallet.get_max_amount(self.electrum_config, inputs, None) return format_satoshis_plain(amount, self.decimal_point()) def update_amount(self, amount, c): if c == '<': return amount[:-1] if c == '.' and amount == '': return '0.' if c == '0' and amount == '0': return '0' try: Decimal(amount + c) amount += c except: pass return amount def format_amount(self, x, is_diff=False, whitespaces=False): from electrum.util import format_satoshis return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point(), whitespaces) @profiler def update_wallet(self, *dt): self._trigger_update_status() if self.wallet.up_to_date or not self.network or not self.network.is_connected( ): self.update_history_tab() self.update_contacts_tab() @profiler def update_history_tab(self, see_all=False): if self.history_screen: self.history_screen.update(see_all) def update_contacts_tab(self): if self.contacts_screen: self.contacts_screen.update() @profiler def notify_transactions(self, *dt): ''' ''' if not self.network or not self.network.is_connected(): return # temporarily disabled for merge return iface = self.network ptfn = iface.pending_transactions_for_notifications if len(ptfn) > 0: # Combine the transactions if there are more then three tx_amount = len(ptfn) if (tx_amount >= 3): total_amount = 0 for tx in ptfn: is_relevant, is_mine, v, fee = self.wallet.get_tx_value(tx) if (v > 0): total_amount += v self.notify( _("{txs}s new transactions received. Total amount" "received in the new transactions {amount}s" "{unit}s").format( txs=tx_amount, amount=self.format_amount(total_amount), unit=self.base_unit())) iface.pending_transactions_for_notifications = [] else: for tx in iface.pending_transactions_for_notifications: if tx: iface.pending_transactions_for_notifications.remove(tx) is_relevant, is_mine, v, fee = self.wallet.get_tx_value( tx) if (v > 0): self.notify( _("{txs} new transaction received. {amount} {unit}" ).format(txs=tx_amount, amount=self.format_amount(v), unit=self.base_unit)) def copy(self, text): ''' Copy provided text to clipboard ''' if not self._clipboard: from kivy.core.clipboard import Clipboard self._clipboard = Clipboard self._clipboard.put(text, 'text/plain') def notify(self, message): try: global notification, os if not notification: from plyer import notification import os icon = (os.path.dirname(os.path.realpath(__file__)) + '/../../' + self.icon) notification.notify('Electrum', message, app_icon=icon, app_name='Electrum') except ImportError: Logger.Error('Notification: needs plyer; `sudo pip install plyer`') def on_pause(self): ''' ''' # pause nfc if self.qrscanner: self.qrscanner.stop() if self.nfcscanner: self.nfcscanner.nfc_disable() return True def on_resume(self): ''' ''' if self.qrscanner and qrscanner.get_parent_window(): self.qrscanner.start() if self.nfcscanner: self.nfcscanner.nfc_enable() def on_size(self, instance, value): width, height = value self._orientation = 'landscape' if width > height else 'portrait' self._ui_mode = 'tablet' if min(width, height) > inch(3.51) else 'phone' #Logger.info("size: {} {}".format(width, height)) #Logger.info('orientation: {}'.format(self._orientation)) #Logger.info('ui_mode: {}'.format(self._ui_mode)) def save_new_contact(self, address, label): address = unicode(address) label = unicode(label) global is_valid if not is_valid: from electrum.bitcoin import is_valid if is_valid(address): if label: self.set_label(address, text=label) self.wallet.add_contact(address) self.update_contacts_tab() self.update_history_tab() else: self.show_error(_('Invalid Address')) def send_payment(self, address, amount=0, label='', message=''): tabs = self.tabs screen_send = tabs.ids.screen_send if label and self.wallet.labels.get(address) != label: #if self.question('Give label "%s" to address %s ?'%(label,address)): if address not in self.wallet.addressbook and not self.wallet.is_mine( address): self.wallet.addressbook.append(address) self.wallet.set_label(address, label) # switch_to the send screen tabs.ids.panel.switch_to(tabs.ids.tab_send) label = self.wallet.labels.get(address) m_addr = label + ' <' + address + '>' if label else address # populate def set_address(*l): content = screen_send.ids content.payto_e.text = m_addr content.message_e.text = message if amount: content.amount_e.text = amount # wait for screen to load Clock.schedule_once(set_address, .5) def set_send(self, address, amount, label, message): self.send_payment(address, amount=amount, label=label, message=message) def prepare_for_payment_request(self): tabs = self.tabs screen_send = tabs.ids.screen_send # switch_to the send screen tabs.ids.panel.switch_to(tabs.ids.tab_send) content = screen_send.ids if content: self.set_frozen(content, False) screen_send.screen_label.text = _("please wait...") return True def payment_request_ok(self): tabs = self.tabs screen_send = tabs.ids.screen_send # switch_to the send screen tabs.ids.panel.switch_to(tabs.ids.tab_send) self.set_frozen(content, True) screen_send.ids.payto_e.text = self.gui_object.payment_request.domain screen_send.ids.amount_e.text = self.format_amount( self.gui_object.payment_request.get_amount()) screen_send.ids.message_e.text = self.gui_object.payment_request.memo # wait for screen to load Clock.schedule_once(set_address, .5) def set_frozen(self, entry, frozen): if frozen: entry.disabled = True Factory.Animation(opacity=0).start(content) else: entry.disabled = False Factory.Animation(opacity=1).start(content) def payment_request_error(self): tabs = self.tabs screen_send = tabs.ids.screen_send # switch_to the send screen tabs.ids.panel.switch_to(tabs.ids.tab_send) self.do_clear() self.show_info(self.gui_object.payment_request.error) def show_error(self, error, width='200dp', pos=None, arrow_pos=None, exit=False, icon='atlas://gui/kivy/theming/light/error', duration=0, modal=False): ''' Show a error Message Bubble. ''' self.show_info_bubble(text=error, icon=icon, width=width, pos=pos or Window.center, arrow_pos=arrow_pos, exit=exit, duration=duration, modal=modal) def show_info(self, error, width='200dp', pos=None, arrow_pos=None, exit=False, duration=0, modal=False): ''' Show a Info Message Bubble. ''' self.show_error(error, icon='atlas://gui/kivy/theming/light/error', duration=duration, modal=modal, exit=exit, pos=pos, arrow_pos=arrow_pos) def show_info_bubble(self, text=_('Hello World'), pos=None, duration=0, arrow_pos='bottom_mid', width=None, icon='', modal=False, exit=False): '''Method to show a Information Bubble .. parameters:: text: Message to be displayed pos: position for the bubble duration: duration the bubble remains on screen. 0 = click to hide width: width of the Bubble arrow_pos: arrow position for the bubble ''' info_bubble = self.info_bubble if not info_bubble: info_bubble = self.info_bubble = Factory.InfoBubble() win = Window if info_bubble.parent: win.remove_widget(info_bubble if not info_bubble.modal else info_bubble._modal_view) if not arrow_pos: info_bubble.show_arrow = False else: info_bubble.show_arrow = True info_bubble.arrow_pos = arrow_pos img = info_bubble.ids.img if text == 'texture': # icon holds a texture not a source image # display the texture in full screen text = '' img.texture = icon info_bubble.fs = True info_bubble.show_arrow = False img.allow_stretch = True info_bubble.dim_background = True info_bubble.background_image = 'atlas://gui/kivy/theming/light/card' else: info_bubble.fs = False info_bubble.icon = icon #if img.texture and img._coreimage: # img.reload() img.allow_stretch = False info_bubble.dim_background = False info_bubble.background_image = 'atlas://data/images/defaulttheme/bubble' info_bubble.message = text if not pos: pos = (win.center[0], win.center[1] - (info_bubble.height / 2)) info_bubble.show(pos, duration, width, modal=modal, exit=exit) def tx_dialog(self, tx_hash): popup = Builder.load_file('gui/kivy/uix/ui_screens/transaction.kv') popup.tx_hash = tx_hash popup.open() def amount_dialog(self, label, callback, show_max): popup = Builder.load_file('gui/kivy/uix/ui_screens/amount.kv') but_max = popup.ids.but_max if not show_max: but_max.disabled = True but_max.opacity = 0 else: but_max.disabled = False but_max.opacity = 1 if label.text != label.default_text: a, u = label.text.split() assert u == self.base_unit popup.ids.a.amount = a def cb(): o = popup.ids.a.text label.text = o if o else label.default_text if callback: callback() popup.on_dismiss = cb popup.open() def password_dialog(self, f, args): if self.wallet.use_encryption: popup = Builder.load_file('gui/kivy/uix/ui_screens/password.kv') def callback(): pw = popup.ids.text_input.text Clock.schedule_once(lambda x: apply(f, args + (pw, )), 0.5) popup.on_dismiss = callback popup.open() else: apply(f, args + (None, ))
class MainScreen(Screen): number = ObjectProperty(None) def __init__(self, **kwargs): super().__init__(**kwargs) self.dialog_error = MDDialog( text="Oops! Something seems to have gone wrong with your enrollment number or our server is not responding! Please try again after sometime :/", radius=[20, 7, 20, 7], ) self.dialog_network = MDDialog( text="Oops! No internet available! Please try again after sometime :/", radius=[20, 7, 20, 7], ) self.semester_get = '' self.batch_get = '' self.menu_items_batch = [{"text": "17"}, {"text": "18"}, {"text": "19"}] self.menu_batch = MDDropdownMenu( caller=self.ids.drop_batch, items=self.menu_items_batch, pos_hint={'center_x': .36, 'center_y': .4}, callback=self.set_batch, width_mult=4, ) self.menu_items_semester = [{"text": f"{i}"} for i in range(1, 9)] self.menu_semester = MDDropdownMenu( caller=self.ids.drop_semester, items=self.menu_items_semester, pos_hint={'center_x': .6, 'center_y': .4}, callback=self.set_semester, width_mult=4, ) self.data = '' self.datatables = '' def openweb(instance, link): webbrowser.open(link) def set_semester(self, instance): self.ids.drop_semester.text = instance.text self.semester_get = instance.text #print(instance.text) self.menu_semester.dismiss() def set_batch(self, instance): self.ids.drop_batch.text = instance.text self.batch_get = instance.text #print(instance.text) self.menu_batch.dismiss() def btn(self): label = self.ids.loading label.text = "Wait! Your result is being prepared :)" baseurl = "https://ipuresultskg.herokuapp.com/" url = baseurl + 'api?rollNo={}&batch={}&semester={}&token={}'.format(self.number.text, self.batch_get, self.semester_get, token) self.request = UrlRequest(url=url, on_success=self.res) def network(self, *args): return self.dialog_network def res(self, *args): self.data = self.request.result sm = self.ids['screen_manager'] ans = self.data if ans is None: self.dialog_error.open() self.ids.loading.text = '' else: sm.current = "result-screen" self.ids.loading.text = '' marks = [tuple(i) for i in [[j.strip() for j in i.split(' ') if j != ''] for i in ans[0].split('\n')[1:]]] info = self.ids['other_info'] to_send = "[i]Semester-{}".format(self.semester_get) + str('\n') + "Name: " + str( ans[4]) + str( '\n') + "Enrollment Number: " + str(ans[5]) + str('\n') + "College: " + str(ans[1]) + str('\n') + str( "Branch: ") + str(ans[2]) + str('\n') + "Percentage: " + str(ans[10]) + str( '%\n') + "College Rank :{}/{}".format(ans[6], ans[7]) + str('\n') + "University Rank :{}/{}\n".format( ans[8], ans[9]) + str('[/i]') info.text = to_send self.datatables = MDDataTable( size_hint=(0.9, 0.6), row_data=marks, rows_num=20, column_data=[ ("Subject", dp(35)), ("Internal", dp(20)), ("External", dp(20)), ("Total", dp(20)), ] )
class MARCResultsPanel(BoxLayout): raw_results = DictProperty() results = ListProperty() unfiltered_results = ListProperty() search_backend = ObjectProperty() change_title_callback = ObjectProperty() filter_text = StringProperty() EVENT_GOT_SEARCH_RESULTS = 'on_got_search_results' EVENT_GO_PREVIOUS = 'on_go_previous' EVENT_GO_NEXT = 'on_go_next' EVENT_RESULT_SELECT = 'on_result_select' EVENT_LOAD_MORE_START = 'on_load_more_started' __events__ = (EVENT_RESULT_SELECT, EVENT_GO_PREVIOUS, EVENT_GO_NEXT, EVENT_LOAD_MORE_START, EVENT_GOT_SEARCH_RESULTS) def __init__(self, *args, **kwargs): super(MARCResultsPanel, self).__init__(**kwargs) self.results = [] def postponed_init(self): self.search_backend.fbind(self.search_backend.EVENT_SEARCH_RESULTS, self.on_search_results) self.ids.rv.bind(on_entry_selected=self.receive_upstream_selection) def on_search_results(self, *args): if self.search_backend.results: self.raw_results = self.search_backend.results self.results = self.unfiltered_results = [ value['dc_meta']['metadata'] for entry, value in self.raw_results['recs'].items() ] # this is what causes the screen to change to the results page self.dispatch(self.EVENT_GOT_SEARCH_RESULTS) def on_got_search_results(self): self.change_title_callback( '{total} results for query [b]{query}[/b] in {catalog}' ' [{page}/{total}]'.format( total=self.search_backend.results['query']['hits'], query=self.search_backend.query, catalog=self.search_backend.results['query']['catalog'], page=self.search_backend.results['query']['offset'])) def on_results(self, *args): self._update_rv_data() def _update_rv_data(self, *args): rv = self.ids.rv rv.data = self.results[:] rv.scroll_y = 1.0 def receive_upstream_selection(self, widget, data): query = self.raw_results['query'] full_record = [ y for x, y in self.raw_results.recs.items() if y['dc_meta']['metadata'] == dict(data) ][0] self.dispatch(self.EVENT_RESULT_SELECT, query, full_record) def on_result_select(self, *args): pass def on_go_previous(self, *args): if self.search_backend.go_previous(): self.dispatch(self.EVENT_LOAD_MORE_START) def on_go_next(self, *args): if self.search_backend.go_next(): self.dispatch(self.EVENT_LOAD_MORE_START) def on_load_more_started(self, *args): pass def on_filter_text(self, filter_input, text): self.results = self.filter(text) def filter(self, text): if text == '': return self.unfiltered_results else: return [ x for x in self.unfiltered_results if text in x['title'] or text in ['creator'] ]
class ProjectCard(MDCard): """ Card that represents each project """ project_filename = ObjectProperty() home_page = ObjectProperty() project = StringProperty() project_card = ObjectProperty() project_image = ObjectProperty() image_edit = ObjectProperty() home_project_title = ObjectProperty() home_progress_label = ObjectProperty() home_progress_bar = ObjectProperty() home_project_desc = ObjectProperty() delete_project = ObjectProperty() open_project = ObjectProperty() cancel_button = ObjectProperty() def __init__(self, **kwargs): super(MDCard, self).__init__(**kwargs) self.app = App.get_running_app() self.delete_dialog = None def update_colors(self): """Update title color in the delete dialog and the color of the CANCEL button""" self.delete_dialog.title = f"[color=%s]Are you sure?[/color]" % \ get_hex_from_color(self.app.theme_cls.text_color) self.delete_dialog.buttons[ 0].text_color = self.app.theme_cls.text_color
class PoliceNew(Widget): label_severely_injured_value = ObjectProperty(None) label_casualties_value = ObjectProperty(None) text_accident_description_value = ObjectProperty(None) text_accident_location_value = ObjectProperty(None) text_accident_km_value = ObjectProperty(None) text_accident_concelho_value = ObjectProperty(None) text_accident_timestamp_value = ObjectProperty(None) severely_number = "0" casualty_number = "0" def decrease_severely_injured(self): if(self.severely_number=="0"): return self.severely_number = str(int(self.severely_number)-1) self.label_severely_injured_value.text = self.severely_number def increase_severely_injured(self): self.severely_number = str(int(self.severely_number)+1) self.label_severely_injured_value.text = self.severely_number def decrease_casualties(self): if(self.casualty_number=="0"): return self.casualty_number = str(int(self.casualty_number)-1) self.label_casualties_value.text = self.casualty_number def increase_casualties(self): self.casualty_number = str(int(self.casualty_number)+1) self.label_casualties_value.text = self.casualty_number def save_accident(self): if(self.text_accident_description_value.text==""): process = Popen(['python3', 'police_input_warning.py'], stdout=PIPE, stderr=PIPE) elif(self.text_accident_location_value.text==""): process = Popen(['python3', 'police_input_warning.py'], stdout=PIPE, stderr=PIPE) elif(self.text_accident_km_value.text==""): process = Popen(['python3', 'police_input_warning.py'], stdout=PIPE, stderr=PIPE) elif(self.text_accident_concelho_value.text==""): process = Popen(['python3', 'police_input_warning.py'], stdout=PIPE, stderr=PIPE) elif(len(self.text_accident_timestamp_value.text)<16): process = Popen(['python3', 'police_input_warning.py'], stdout=PIPE, stderr=PIPE) else: new_id = get_query_result("SELECT MAX(IdAcidente) FROM Acidentes;")[0][0] + 1 new_road_id = get_query_result("SELECT IdVia FROM Vias Where Nome = '" + self.text_accident_location_value.text + "';") new_concelho_name = get_query_result("SELECT IdConcelho FROM Concelhos Where Nome = '" + self.text_accident_concelho_value.text + "';") if(len(new_concelho_name)==0): new_concelho_id = get_query_result("SELECT MAX(IdConcelho) FROM Concelhos;")[0][0] + 1 get_query_result("INSERT INTO Concelhos(IdConcelho, Nome, IdDistrito) VALUES (" + str(new_concelho_id) + ", '" + self.text_accident_concelho_value.text + "',1); COMMIT;") else: new_concelho_id = new_concelho_name[0][0] if(len(new_road_id)==0): # it's a new road new_new_road_id = get_query_result("SELECT MAX(IdVia) FROM Vias;")[0][0] + 1 get_query_result("INSERT INTO Vias(IdVia, Nome, IdConcelho, Tipo) VALUES (" + str(new_new_road_id) + ", '" + self.text_accident_location_value.text + "'," + str(new_concelho_id) + ", 'None'); COMMIT;") get_query_result("INSERT INTO Acidentes(IdAcidente, DataHora, IdVia, Natureza, Km, Mortos, FeridosGraves) VALUES (" + str(new_id) + ", '" + self.text_accident_timestamp_value.text + "', " + str(new_new_road_id) + ", '" + self.text_accident_description_value.text + "', " + self.text_accident_km_value.text + ", " + self.casualty_number + ", " + self.severely_number + "); COMMIT;") else: get_query_result("INSERT INTO Vias(IdVia, Nome, IdConcelho, Tipo) VALUES (" + str(new_road_id[0][0]) + ", '" + self.text_accident_location_value.text + "'," + str(new_concelho_id) + ", 'None'); COMMIT;") get_query_result("INSERT INTO Acidentes(IdAcidente, DataHora, IdVia, Natureza, Km, Mortos, FeridosGraves) VALUES (" + str(new_id) + ", '" + self.text_accident_timestamp_value.text + "', " + str(new_road_id[0][0]) + ", '" + self.text_accident_description_value.text + "', " + self.text_accident_km_value.text + ", " + self.casualty_number + ", " + self.severely_number + "); COMMIT;") process = Popen(['python3', 'police_successful.py'], stdout=PIPE, stderr=PIPE) def update_padding(self, text_input, *args): # align text within a textinput field text_width = text_input._get_text_width( text_input.text, text_input.tab_width, text_input._label_cached) text_input.padding_x = (text_input.width - text_width)/2 def update_padding_and_format(self, text_input, *args): # routine to take care of the date and time formats, as well as thei alignment # AUTOMATICALLY ADD "/", ":" OR " " #------------------------------------------------------------- if(len(text_input.text)==2 and "/" in text_input.text): text_input.text = text_input.text.replace('/','') if(len(text_input.text)==2 and "/" not in text_input.text): aux = text_input.text text_input.text = "" text_input.text = aux + "/" if(len(text_input.text)<=5 and text_input.text.count('/')>1): text_input.text = text_input.text[:-1] if(len(text_input.text)==5 and text_input.text.count('/')==1): aux = text_input.text text_input.text = "" text_input.text = aux + "/" # take care of the time format if(len(text_input.text)==13 and ":" not in text_input.text): aux = text_input.text text_input.text = "" text_input.text = aux + ":" if(len(text_input.text)==13 and ":" in text_input.text): text_input.text = text_input.text.replace(':','') #------------------------------------------------------------- # ELIMINATE INVALID CHARACTERS #-------------------------------------------------------------------- aux = "" for i in range(len(text_input.text)): if(text_input.text[i]==" "): if(i==10): aux += text_input.text[i] else: continue elif(text_input.text[i]=="/"): if(i==2 or i==5): aux += text_input.text[i] else: continue elif(text_input.text[i]==":"): if(i==13): aux += text_input.text[i] else: continue elif(not (text_input.text[i]).isdigit()): # a forbidden character continue else: aux += text_input.text[i] text_input.text = aux #-------------------------------------------------------------------- # TOO MANY NUMBERS #---------------------------------------------------------------------------------------- # take care of the date if(text_input.text.count('/')==1 and len(text_input.text.split("/")[0])>2): text_input.text = text_input.text[:2] + "/" if(text_input.text.count('/')==2 and len(text_input.text.split("/")[1])>2): text_input.text = text_input.text[:5] + "" if(len(text_input.text)>10 and text_input.text[10]!=' '): text_input.text = text_input.text[:10] # take care of the time if(text_input.text.count(':')==1 and len(text_input.text.split(" ")[1].split(":")[0])>2): text_input.text = text_input.text[:13] + ":" if(len(text_input.text)>16): text_input.text = text_input.text[:16] #---------------------------------------------------------------------------------------- # RANGES EXCEEDED #-------------------------------------------------------------------------------------------------------------------------------------------- # day outside of range if(len(text_input.text)==3 and (int(text_input.text.split("/")[0])>31 or int(text_input.text.split("/")[0])<0)): text_input.text = "" # month outside of range if(len(text_input.text)==6 and (int(text_input.text.split("/")[1])>12 or int(text_input.text.split("/")[1])<0)): text_input.text = text_input.text[:3] # year outside of range if(len(text_input.text)==10 and (int(text_input.text.split("/")[2])>2019 or int(text_input.text.split("/")[2])<1500)): text_input.text = text_input.text[:6] # hour outside of range if(len(text_input.text)==14 and (int(text_input.text.split(" ")[1].split(":")[0])>23 or int(text_input.text.split(" ")[1].split(":")[0])<0)): text_input.text = text_input.text[:11] # minutes outside of range if(len(text_input.text)==16 and (int(text_input.text.split(" ")[1].split(":")[1])>59 or int(text_input.text.split(" ")[1].split(":")[1])<0)): text_input.text = text_input.text[:14] #-------------------------------------------------------------------------------------------------------------------------------------------- text_width = text_input._get_text_width( text_input.text, text_input.tab_width, text_input._label_cached) text_input.padding_x = (text_input.width - text_width)/2
class HomeSelectPage(MDBoxLayout): """ Homepage with cards for each project, only created once and has a card for each project """ home_title = ObjectProperty() home_subtitle = ObjectProperty() card_layout = ObjectProperty() delete_dialog = None def __init__(self, manager, **kwargs): """Init page, adds a card for each project in utils.keys""" super(MDBoxLayout, self).__init__(**kwargs) self.manager = manager self.app = App.get_running_app() for project in utils.keys: self.add_card(project) def add_card(self, project): """Adds a new ProjectCard""" card = ProjectCard() card.home_page = self card.project = project card.delete_dialog = None # creates the card dialog in advance, TODO might want to change this self.delete_project(card, project, open_dialog=False) card.project_image.source = self.get_image( project, utils.data[project]['proj_image']) card.project_image.reload() card.home_project_title.text = self.shorten_title( utils.data[project]["title"]) card.home_project_desc.text = self.get_project_desc(project) card.home_progress_label.text = f"{utils.data[project]['progress']}%" card.home_progress_bar.value = utils.data[project]["progress"] card.open_project.bind(on_release=partial(self.open_project, project)) card.delete_project.bind( on_release=partial(self.delete_project, card, project)) card.project_filename.text = f"Filename: {project}" utils.project_cards.append(card) self.card_layout.add_widget(card) def open_project(self, project, *args): """changes screen to selected project based on which ProjectCard is clicked""" self.manager.transition = SwapTransition() self.manager.current = project def delete_project(self, card, project, open_dialog=True, *args): """ Deletes selected project when delete project button is pressed :param card: ProjectCard() :param project: str :param open_dialog: bool :param args: button args :return: None """ # create the delete dialog if not created if not card.delete_dialog: card.delete_dialog = MDDialog( title=f"[color=%s]Are you sure?[/color]" % get_hex_from_color(self.app.theme_cls.text_color), type="custom", content_cls=DeleteDialogContent( new_text=utils.data[project]['title']), buttons=[ MDFlatButton(text="CANCEL", text_color=self.app.theme_cls.text_color, on_release=partial(self.close_delete_dialog, card)), MDRaisedButton(text="DELETE", text_color=self.app.theme_cls.primary_color, on_release=partial(self.confirm_delete, card, project)) ]) if open_dialog: card.delete_dialog.set_normal_height() card.delete_dialog.open() @staticmethod def close_delete_dialog(card, *args): """Closes the delete dialog for the given card""" card.delete_dialog.dismiss() def confirm_delete(self, card, project, *args): """ Confirms the delete for the selected project and deletes it :param card: ProjectCard() :param project: str :param args: button args :return: None """ # dismisses the delete dialog card.delete_dialog.dismiss() # deletes the project from the projects path del_dir = os.path.abspath(utils.data[project]['proj_path'].replace( "/", "\\")) shutil.rmtree(del_dir, True) # removes project references to this project in required places # utils.update_keys() key_index = utils.keys.index(project) ''' print("\nkey index:", key_index) print("windows", utils.windows) print("keys", utils.keys) print("data", utils.data) ''' remove_window = utils.windows.pop(key_index) utils.keys.remove(project) utils.project_cards.remove(card) # removes the project from the navigation drawer menu self.app.remove_menu_entry(utils.menu_projects.pop(key_index)) # remove the window from the window manager so only 1 instance of the window exists if the user imports a # project with the same name self.manager.remove_widget(remove_window) # when introducing the import feature, this was added, seems like without it, utils.update_data() # is called each clock cycle and the app hangs/stalls this is needed to be removed # raises error if the project was added after initialization or not from import, no project_data_path is # created if not loaded when initialized, all projects created after app startup will error # THIS IS NO LONGER AN ERROR, here in case something is missed try: utils.project_data_paths.pop(key_index) except IndexError: pass # removes the card from the home page card.home_page.card_layout.remove_widget(card) # if the project id is the ending project, decrement the ending project number # TODO: rework id system and move towards hash id (utils.generate_hash_id()) if utils.data[project]["id"] == utils.get_num_projects(): utils.num_projects -= 1 utils.save_settings({"num_projects": utils.num_projects}) # remove the project data utils.data.pop(project) # update the remaining windows to make sure their navigation buttons and name are correct utils.update_changes(False, False) @staticmethod def get_image(project, image_name): """ Sets the image to the path of the image in the project data, gets the image_icon.png for the project, if none returns default image :param project: str :param image_name: str :return: str """ path = utils.data[project]["proj_path"] + f"\\{image_name}" if os.path.isfile(path): return path else: return "default_image.png" @staticmethod def edit_image(project, image_widget): """ asks user to select an image of .png or .jpg and copies selection to the project folder and renames it to "project_image".extension :param image_widget: object the image widget that contains the project image :param project: str :return: str """ root = tkinter.Tk() root.withdraw() selected_image = tkinter.filedialog.askopenfilename( initialdir="C:/", title="Select an image", filetypes=(("png files", "png"), ("jpg files", "jpg"))).replace("/", "\\") image_name = selected_image.split("\\")[-1] image_extension = image_name.split(".")[-1] copied_file = utils.data[project][ "proj_path"] + f"\\project_image.{image_extension}" try: shutil.copyfile(selected_image, copied_file) utils.update_data() utils.data[project][ "proj_image"] = f"project_image.{image_extension}" new_data = {'data': utils.data[project]} with open(f"{utils.data[project]['proj_path']}\\project_data.json", "w") as file: json.dump(new_data, file) utils.update_data() except FileNotFoundError: pass source = os.path.abspath(utils.data[project]["proj_path"] + f"/project_image.{image_extension}").replace( "\\", "/") image_widget.source = source image_widget.reload() return source @staticmethod def shorten_title(title): """ shortens the title of the project to fit on one line inside the card, 32 characters :param title: str :return: str """ if len(title) >= 32: return title[0:32] + "..." else: return title @staticmethod def get_project_desc(project): """ sets the description field, if greater than 100 characters shorten it :param project: str :return: str """ desc_path = utils.data[project]["proj_path"] + f"/DESCRIPTION.txt" with open(desc_path, "r") as file: desc = file.read() if len(desc) > 100: return desc[0:100] + "..." else: return desc @staticmethod def get_lighter(color, percentage): """returns a lighter alpha value for a given color""" color[3] = percentage return color
class MDTabsBar(ThemableBehavior, RectangularElevationBehavior, MDBoxLayout): """ This class is just a boxlayout that contains the scroll view for tabs. He is also responsible for resizing the tab shortcut when necessary. """ target = ObjectProperty(None, allownone=True) """ Is the carousel reference of the next tab / slide. When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the target tab / slide of the carousel. :attr:`target` is an :class:`~kivy.properties.ObjectProperty` and default to `None`. """ def get_rect_instruction(self): for i in self.layout.canvas.after.children: if isinstance(i, Rectangle): return i indicator = AliasProperty(get_rect_instruction, cache=True) """ Is the Rectangle instruction reference of the tab indicator. :attr:`indicator` is an :class:`~kivy.properties.AliasProperty`. """ def get_last_scroll_x(self): return self.scrollview.scroll_x last_scroll_x = AliasProperty(get_last_scroll_x, bind=("target", ), cache=True) """ Is the carousel reference of the next tab/slide. When you go from `'Tab A'` to `'Tab B'`, `'Tab B'` will be the target tab/slide of the carousel. :attr:`last_scroll_x` is an :class:`~kivy.properties.AliasProperty`. """ def __init__(self, **kwargs): self._trigger_update_tab_bar = Clock.schedule_once( self._update_tab_bar, 0) super().__init__(**kwargs) def _update_tab_bar(self, *args): if self.parent.allow_stretch: # update width of the labels when it is needed width, tabs = self.scrollview.width, self.layout.children tabs_widths = [t.min_space for t in tabs if t.min_space] tabs_space = float(sum(tabs_widths)) if not tabs_space: return ratio = width / tabs_space use_ratio = True in (width / len(tabs) < w for w in tabs_widths) for t in tabs: t.width = (t.min_space if tabs_space > width else t.min_space * ratio if use_ratio is True else width / len(tabs)) def update_indicator(self, x, w): # update position and size of the indicator self.indicator.pos = (x, 0) self.indicator.size = (w, self.indicator.size[1]) def tab_bar_autoscroll(self, target, step): # automatic scroll animation of the tab bar. bound_left = self.center_x bound_right = self.layout.width - bound_left dt = target.center_x - bound_left sx, sy = self.scrollview.convert_distance_to_scroll(dt, 0) # last scroll x of the tab bar lsx = self.last_scroll_x # determine scroll direction scroll_is_late = lsx < sx # distance to run dst = abs(lsx - sx) * step if not dst: return if scroll_is_late and target.center_x > bound_left: x = lsx + dst elif not scroll_is_late and target.center_x < bound_right: x = lsx - dst x = boundary(x, 0.0, 1.0) self.scrollview.goto(x, None) def android_animation(self, carousel, offset): # try to reproduce the android animation effect. if offset != 0 and abs(offset) < carousel.width: forward = offset < 0 offset = abs(offset) step = offset / float(carousel.width) distance = abs(offset - carousel.width) threshold = self.parent.anim_threshold breakpoint = carousel.width - (carousel.width * threshold) traveled = distance / breakpoint if breakpoint else 0 break_step = 1.0 - traveled indicator_animation = self.parent.tab_indicator_anim skip_slide = (carousel.slides[carousel._skip_slide] if carousel._skip_slide is not None else None) next_slide = (carousel.next_slide if forward else carousel.previous_slide) self.target = skip_slide if skip_slide else next_slide if not self.target: return a = carousel.current_slide.tab_label b = self.target.tab_label self.tab_bar_autoscroll(b, step) if not indicator_animation: return if step <= threshold: if forward: gap_w = abs((a.x + a.width) - (b.x + b.width)) w_step = a.width + (gap_w * step) x_step = a.x else: gap = abs((a.x - b.x)) x_step = a.x - gap * step w_step = a.width + gap * step else: if forward: x_step = a.x + abs((a.x - b.x)) * break_step gap_w = abs((a.x + a.width) - (b.x + b.width)) ind_width = a.width + gap_w * threshold gap_w = ind_width - b.width w_step = ind_width - (gap_w * break_step) else: x_step = a.x - abs((a.x - b.x)) * threshold x_step = x_step - abs(x_step - b.x) * break_step ind_width = ((a.x + a.width) - x_step if threshold else a.width) gap_w = ind_width - b.width w_step = ind_width - (gap_w * break_step) w_step = (w_step if w_step + x_step <= a.x + a.width else ind_width) self.update_indicator(x_step, w_step)
class CryptoPricerGUI(BoxLayout): requestInput = ObjectProperty() requestList = ObjectProperty() resultOutput = ObjectProperty() statusBar = ObjectProperty() showRequestList = False def __init__(self, **kwargs): global fromAppBuilt if not fromAppBuilt: return super(CryptoPricerGUI, self).__init__(**kwargs) self.dropDownMenu = CustomDropDown(owner=self) if os.name == 'posix': configPath = '/sdcard/cryptopricer.ini' else: configPath = 'c:\\temp\\cryptopricer.ini' self.toggleAppSizeButton.text = 'Half' # correct on Windows version ! self.configMgr = ConfigurationManager(configPath) self.controller = Controller(GuiOutputFormater(self.configMgr, activateClipboard=True), self.configMgr) self.dataPath = self.configMgr.dataPath self.histoListItemHeight = int(self.configMgr.histoListItemHeight) self.histoListMaxVisibleItems = int(self.configMgr.histoListVisibleSize) self.maxHistoListHeight = self.histoListMaxVisibleItems * self.histoListItemHeight self.appSize = self.configMgr.appSize self.defaultAppPosAndSize = self.configMgr.appSize self.appSizeHalfProportion = float(self.configMgr.appSizeHalfProportion) self.applyAppPosAndSize() # loading the load at start history file if defined pathFilename = self.configMgr.loadAtStartPathFilename if pathFilename != '': self.loadPathFilename(pathFilename) def toggleAppPosAndSize(self): if self.appSize == self.configMgr.APP_SIZE_HALF: self.appSize = self.configMgr.APP_SIZE_FULL if self.defaultAppPosAndSize == self.configMgr.APP_SIZE_FULL: # on the smartphone, we do not want to reposition the cursor ob # the input field since this would display the keyboard ! self.refocusOnRequestInput() else: self.appSize = self.configMgr.APP_SIZE_HALF # the case on the smartphone. Here, positioning the cursor on # the input field after having pressed the 'half' button # automatically displays the keyboard self.refocusOnRequestInput() self.applyAppPosAndSize() def applyAppPosAndSize(self): if self.appSize == self.configMgr.APP_SIZE_HALF: sizeHintY = float(self.appSizeHalfProportion) self.size_hint_y = sizeHintY self.pos_hint = {'x': 0, 'y': 1 - sizeHintY} self.toggleAppSizeButton.text = 'Full' else: self.size_hint_y = 1 self.pos_hint = {'x': 0, 'y': 0} self.toggleAppSizeButton.text = 'Half' def toggleRequestList(self): ''' called by 'History' toggle button to toggle the display of the history request list. ''' if self.showRequestList: self.requestList.size_hint_y = None self.requestList.height = '0dp' self.disableRequestListItemButtons() self.showRequestList = False else: listItemNumber = len(self.requestList.adapter.data) self.requestList.height = min(listItemNumber * self.histoListItemHeight, self.maxHistoListHeight) self.showRequestList = True self.resetListViewScrollToEnd() self.refocusOnRequestInput() def submitRequest(self): ''' Submit the request, output the result and add the request to the request list :return: ''' # Get the request from the TextInput requestStr = self.requestInput.text self.updateStatusBar(requestStr) # purpose of the informations obtained from the business layer: # outputResultStr - for the output text zone # fullRequestStr - for the request history list # fullRequestStrWithOptions - for the status bar # fullRequestStrWithSaveModeOptions - for the request history list outputResultStr, fullRequestStr, fullRequestStrWithOptions, fullRequestStrWithSaveModeOptions = self.controller.getPrintableResultForInput( requestStr) self.outputResult(outputResultStr) if fullRequestStrWithSaveModeOptions != None: if fullRequestStr in self.requestList.adapter.data: # if the full request string corresponding to the full request string with options is already # in the history list, it is removed before the full request string with options is added # to the list. Otherwise, this would engender a duplicate ! self.requestList.adapter.data.remove(fullRequestStr) if not fullRequestStrWithSaveModeOptions in self.requestList.adapter.data: self.requestList.adapter.data.extend([fullRequestStrWithSaveModeOptions]) # Reset the ListView self.resetListViewScrollToEnd() elif fullRequestStr != '' and not fullRequestStr in self.requestList.adapter.data: # Add the full request to the ListView if not already in self.requestList.adapter.data.extend([fullRequestStr]) # if an identical full request string with options is in the history, it is not # removed automatically. If the user wants to get rid of it, he must do it exolicitely # using the delete button ! # Reset the ListView self.resetListViewScrollToEnd() self.manageStateOfRequestListButtons() self.requestInput.text = '' # displaying request and result in status bar if 'ERROR' in outputResultStr: if requestStr == '': self.updateStatusBar('REPLAY --> ' + outputResultStr) elif requestStr: self.updateStatusBar(requestStr + ' --> ' + outputResultStr) else: if fullRequestStrWithSaveModeOptions: if requestStr == '': self.updateStatusBar('REPLAY --> ' + fullRequestStrWithSaveModeOptions) elif requestStr: self.updateStatusBar(requestStr + ' --> ' + fullRequestStrWithSaveModeOptions) else: if not fullRequestStrWithOptions: fullRequestStrWithOptions = fullRequestStr if requestStr == '': self.updateStatusBar('REPLAY --> ' + fullRequestStrWithOptions) elif requestStr: self.updateStatusBar(requestStr + ' --> ' + fullRequestStrWithOptions) self.refocusOnRequestInput() def clearOutput(self): self.resultOutput.text = '' self.statusBar.text = '' self.clearResultOutputButton.disabled = True self.refocusOnRequestInput() def resetListViewScrollToEnd(self): listView = self.requestList maxVisibleItemNumber = self.histoListMaxVisibleItems listLength = len(listView.adapter.data) if listLength > maxVisibleItemNumber: listView.scroll_to(listLength - maxVisibleItemNumber) else: listView.scroll_to(0) listView._trigger_reset_populate() def manageStateOfRequestListButtons(self): ''' Enable or disable history request list related controls according to the status of the list: filled with items or empty. :return: ''' if len(self.requestList.adapter.data) == 0: # request list is empty self.toggleHistoButton.state = 'normal' self.toggleHistoButton.disabled = True self.replayAllButton.disabled = True self.requestList.height = '0dp' self.dropDownMenu.saveButton.disabled = True else: self.toggleHistoButton.disabled = False self.replayAllButton.disabled = False self.dropDownMenu.saveButton.disabled = False def outputResult(self, resultStr): if len(self.resultOutput.text) == 0: self.resultOutput.text = resultStr else: self.resultOutput.text = self.resultOutput.text + '\n' + resultStr # self.outputResultScrollView.scroll_to(100000) # self.resultOutput.cursor = (10000,0) self.clearResultOutputButton.disabled = False def refocusOnRequestInput(self): # defining a delay of 0.1 sec ensure the # refocus works in all situations. Leaving # it empty (== next frame) does not work # when pressing a button ! Clock.schedule_once(self._refocusTextInput, 0.1) def _refocusTextInput(self, *args): ''' This method is here to be used as callback by Clock and must not be called directly :param args: :return: ''' self.requestInput.focus = True def deleteRequest(self, *args): # If a list item is selected if self.requestList.adapter.selection: # Get the text from the item selected selection = self.requestList.adapter.selection[0].text # Remove the matching item self.requestList.adapter.data.remove(selection) # Reset the ListView self.resetListViewScrollToEnd() self.requestInput.text = '' self.disableRequestListItemButtons() self.manageStateOfRequestListButtons() self.refocusOnRequestInput() def replaceRequest(self, *args): # If a list item is selected if self.requestList.adapter.selection: # Get the text from the item selected selection = self.requestList.adapter.selection[0].text # Remove the matching item self.requestList.adapter.data.remove(selection) # Get the request from the TextInputs requestStr = self.requestInput.text # Add the updated data to the list if not already in if not requestStr in self.requestList.adapter.data: self.requestList.adapter.data.extend([requestStr]) # Reset the ListView self.requestList._trigger_reset_populate() self.requestInput.text = '' self.disableRequestListItemButtons() self.refocusOnRequestInput() def historyItemSelected(self, instance): requestStr = str(instance.text) # counter-intuitive, but test must be defined that way ! if instance.is_selected: # disabling the 2 history request list item related buttons self.disableRequestListItemButtons() else: self.enableRequestListItemButtons() self.requestInput.text = requestStr self.refocusOnRequestInput() self.dropDownMenu.saveButton.disabled = False def enableRequestListItemButtons(self): self.deleteButton.disabled = False self.replaceButton.disabled = False def disableRequestListItemButtons(self): self.deleteButton.disabled = True self.replaceButton.disabled = True def replayAllRequests(self): # output blank line self.outputResult('') for request in self.requestList.adapter.data: outputResultStr, fullRequestStr, fullRequestStrWithOptions, fullRequestStrWithSaveModeOptions = self.controller.getPrintableResultForInput( request) self.outputResult(outputResultStr) # self.resultOutput.do_cursor_movement('cursor_pgdown') self.refocusOnRequestInput() def openDropDownMenu(self, widget): self.dropDownMenu.open(widget) def displayHelp(self): self.dropDownMenu.dismiss() popup = Popup(title='CryptoPricer', content=Label(text='Version 2.1'), size_hint=(None, None), size=(400, 400)) popup.open() def updateStatusBar(self, messageStr): self.statusBar.text = messageStr # --- file chooser code --- def getStartPath(self): return "D:\\Users\\Jean-Pierre" def dismissPopup(self): ''' Act as a call back function for the cancel button of the load and save dialog :return: nothing ''' self.updateStatusBar('') self.popup.dismiss() def openLoadHistoryFileChooser(self): fileChooserDialog = LoadDialog(load=self.load, cancel=self.dismissPopup) fileChooserDialog.fileChooser.rootpath = self.dataPath self.popup = Popup(title="Load file", content=fileChooserDialog, size_hint=(0.9, 0.6), pos_hint={'center': 1, 'top': 1}) self.popup.open() self.dropDownMenu.dismiss() def openSaveHistoryFileChooser(self): fileChooserDialog = SaveDialog(save=self.save, cancel=self.dismissPopup) fileChooserDialog.owner = self fileChooserDialog.fileChooser.rootpath = self.dataPath self.popup = Popup(title="Save file", content=fileChooserDialog, size_hint=(0.9, 0.6), pos_hint={'center': 1, 'top': 1}) self.popup.open() self.dropDownMenu.dismiss() def load(self, path, filename): if not filename: # no file selected. Load dialog remains open .. return pathFilename = os.path.join(path, filename[0]) self.loadPathFilename(pathFilename) self.dismissPopup() def loadPathFilename(self, pathFilename): # emptying the list self.requestList.adapter.data[:] = [] with open(pathFilename) as stream: lines = stream.readlines() lines = list(map(lambda line: line.strip('\n'), lines)) self.requestList.adapter.data.extend(lines) # Reset the ListView self.resetListViewScrollToEnd() self.manageStateOfRequestListButtons() self.refocusOnRequestInput() def save(self, path, filename, isLoadAtStart): if not filename: # no file selected. Save dialog remains open .. return pathFileName = os.path.join(path, filename) with open(pathFileName, 'w') as stream: for line in self.requestList.adapter.data: line = line + '\n' stream.write(line) # saving in config file if the saved file # is to be loaded at application start if isLoadAtStart: self.configMgr.loadAtStartPathFilename = pathFileName else: if self.configMgr.loadAtStartPathFilename == pathFileName: self.configMgr.loadAtStartPathFilename = '' self.configMgr.storeConfig() self.dismissPopup() self.refocusOnRequestInput() # --- end file chooser code --- def isLoadAtStart(self, filePathName): return self.configMgr.loadAtStartPathFilename == filePathName
class MDTabs(ThemableBehavior, AnchorLayout): """ You can use this class to create your own tabbed panel.. :Events: `on_tab_switch` Called when switching tabs. """ default_tab = NumericProperty(0) """ Index of the default tab. :attr:`default_tab` is an :class:`~kivy.properties.NumericProperty` and defaults to `0`. """ tab_bar_height = NumericProperty("48dp") """ Height of the tab bar. :attr:`tab_bar_height` is an :class:`~kivy.properties.NumericProperty` and defaults to `'48dp'`. """ tab_indicator_anim = BooleanProperty(False) """ Tab indicator animation. If you want use animation set it to ``True``. :attr:`tab_indicator_anim` is an :class:`~kivy.properties.BooleanProperty` and defaults to `False`. """ tab_indicator_height = NumericProperty("2dp") """ Height of the tab indicator. :attr:`tab_indicator_height` is an :class:`~kivy.properties.NumericProperty` and defaults to `'2dp'`. """ anim_duration = NumericProperty(0.2) """ Duration of the slide animation. :attr:`anim_duration` is an :class:`~kivy.properties.NumericProperty` and defaults to `0.2`. """ anim_threshold = BoundedNumericProperty(0.8, min=0.0, max=1.0, errorhandler=lambda x: 0.0 if x < 0.0 else 1.0) """ Animation threshold allow you to change the tab indicator animation effect. :attr:`anim_threshold` is an :class:`~kivy.properties.BoundedNumericProperty` and defaults to `0.8`. """ allow_stretch = BooleanProperty(True) """ If False - tabs will not stretch to full screen. :attr:`allow_stretch` is an :class:`~kivy.properties.BooleanProperty` and defaults to `True`. """ background_color = ListProperty() """ Background color of tabs in ``rgba`` format. :attr:`background_color` is an :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ text_color_normal = ListProperty() """ Text color of the label when it is not selected. :attr:`text_color_normal` is an :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ text_color_active = ListProperty() """ Text color of the label when it is selected. :attr:`text_color_active` is an :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ elevation = NumericProperty(0) """ Tab value elevation. .. seealso:: `Behaviors/Elevation <https://kivymd.readthedocs.io/en/latest/behaviors/elevation/index.html>`_ :attr:`elevation` is an :class:`~kivy.properties.NumericProperty` and defaults to `0`. """ color_indicator = ListProperty() """ Color indicator in ``rgba`` format. :attr:`color_indicator` is an :class:`~kivy.properties.ListProperty` and defaults to `[]`. """ callback = ObjectProperty() """ User callback. The method will be called when the ``on_ref_press`` event occurs in the :class:`~MDTabsLabel` class. :attr:`callback` is an :class:`~kivy.properties.ObjectProperty` and defaults to `None`. """ def __init__(self, **kwargs): super().__init__(**kwargs) self.register_event_type("on_tab_switch") def on_tab_switch(self, *args): """Called when switching tabs.""" def on_carousel_index(self, carousel, index): # when the index of the carousel change, update # tab indicator, select the current tab and reset threshold data. current_tab_label = carousel.current_slide.tab_label if current_tab_label.state == "normal": current_tab_label._do_press() self.tab_bar.update_indicator(current_tab_label.x, current_tab_label.width) def add_widget(self, widget, index=0, canvas=None): # You can add only subclass of MDTabsBase. if len(self.children) >= 2: try: self.background_color = (self.background_color if self.background_color else self.theme_cls.primary_color) self.text_color_normal = (self.text_color_normal if self.text_color_normal else self.theme_cls.text_color) self.text_color_active = (self.text_color_active if self.text_color_active else self.theme_cls.bg_dark) widget.tab_label.callback = self.callback widget.tab_label.tab_bar = self.tab_bar widget.tab_label.text_color_normal = self.text_color_normal widget.tab_label.text_color_active = self.text_color_active self.tab_bar.layout.add_widget(widget.tab_label) self.carousel.add_widget(widget) return except AttributeError: pass return super().add_widget(widget) def remove_widget(self, widget): # You can remove only subclass of MDTabsBase. if not issubclass(widget.__class__, MDTabsBase): raise MDTabsException( "MDTabs can remove only subclass of MDTabBase") if widget.parent.parent == self.carousel: self.tab_bar.layout.remove_widget(widget.tab_label) self.carousel.remove_widget(widget)
class FileChooserController(RelativeLayout): '''Base for implementing a FileChooser. Don't use this class directly, but prefer using an implementation such as the :class:`FileChooser`, :class:`FileChooserListView` or :class:`FileChooserIconView`. :Events: `on_entry_added`: entry, parent Fired when a root-level entry is added to the file list. If you return True from this event, the entry is not added to FileChooser. `on_entries_cleared` Fired when the the entries list is cleared, usually when the root is refreshed. `on_subentry_to_entry`: entry, parent Fired when a sub-entry is added to an existing entry or when entries are removed from an entry e.g. when a node is closed. `on_submit`: selection, touch Fired when a file has been selected with a double-tap. ''' _ENTRY_TEMPLATE = None layout = ObjectProperty(baseclass=FileChooserLayout) ''' Reference to the layout widget instance. layout is an :class:`~kivy.properties.ObjectProperty`. .. versionadded:: 1.9.0 ''' path = StringProperty(u'/') ''' path is a :class:`~kivy.properties.StringProperty` and defaults to the current working directory as a unicode string. It specifies the path on the filesystem that this controller should refer to. .. warning:: If a unicode path is specified, all the files returned will be in unicode, allowing the display of unicode files and paths. If a bytes path is specified, only files and paths with ascii names will be displayed properly: non-ascii filenames will be displayed and listed with questions marks (?) instead of their unicode characters. ''' filters = ListProperty([]) ''' filters specifies the filters to be applied to the files in the directory. filters is a :class:`~kivy.properties.ListProperty` and defaults to []. This is equivalent to '\*' i.e. nothing is filtered. The filters are not reset when the path changes. You need to do that yourself if desired. There are two kinds of filters: patterns and callbacks. #. Patterns e.g. ['\*.png']. You can use the following patterns: ========== ================================= Pattern Meaning ========== ================================= \* matches everything ? matches any single character [seq] matches any character in seq [!seq] matches any character not in seq ========== ================================= #. Callbacks You can specify a function that will be called for each file. The callback will be passed the folder and file name as the first and second parameters respectively. It should return True to indicate a match and False otherwise. .. versionchanged:: 1.4.0 Added the option to specify the filter as a callback. ''' filter_dirs = BooleanProperty(False) ''' Indicates whether filters should also apply to directories. filter_dirs is a :class:`~kivy.properties.BooleanProperty` and defaults to False. ''' sort_func = ObjectProperty(alphanumeric_folders_first) ''' Provides a function to be called with a list of filenames as the first argument and the filesystem implementation as the second argument. It returns a list of filenames sorted for display in the view. sort_func is an :class:`~kivy.properties.ObjectProperty` and defaults to a function returning alphanumerically named folders first. .. versionchanged:: 1.8.0 The signature needs now 2 arguments: first the list of files, second the filesystem class to use. ''' files = ListProperty([]) ''' The list of files in the directory specified by path after applying the filters. files is a read-only :class:`~kivy.properties.ListProperty`. ''' show_hidden = BooleanProperty(False) ''' Determines whether hidden files and folders should be shown. show_hidden is a :class:`~kivy.properties.BooleanProperty` and defaults to False. ''' selection = ListProperty([]) ''' Contains the list of files that are currently selected. selection is a read-only :class:`~kivy.properties.ListProperty` and defaults to []. ''' multiselect = BooleanProperty(False) ''' Determines whether the user is able to select multiple files or not. multiselect is a :class:`~kivy.properties.BooleanProperty` and defaults to False. ''' dirselect = BooleanProperty(False) ''' Determines whether directories are valid selections or not. dirselect is a :class:`~kivy.properties.BooleanProperty` and defaults to False. .. versionadded:: 1.1.0 ''' rootpath = StringProperty(None, allownone=True) ''' Root path to use instead of the system root path. If set, it will not show a ".." directory to go up to the root path. For example, if you set rootpath to /users/foo, the user will be unable to go to /users or to any other directory not starting with /users/foo. rootpath is a :class:`~kivy.properties.StringProperty` and defaults to None. .. versionadded:: 1.2.0 .. note:: Similarly to :attr:`path`, whether `rootpath` is specified as bytes or a unicode string determines the type of the filenames and paths read. ''' progress_cls = ObjectProperty(FileChooserProgress) '''Class to use for displaying a progress indicator for filechooser loading. progress_cls is an :class:`~kivy.properties.ObjectProperty` and defaults to :class:`FileChooserProgress`. .. versionadded:: 1.2.0 .. versionchanged:: 1.8.0 If set to a string, the :class:`~kivy.factory.Factory` will be used to resolve the class name. ''' file_encodings = ListProperty(['utf-8', 'latin1', 'cp1252']) '''Possible encodings for decoding a filename to unicode. In the case that the user has a non-ascii filename, undecodable without knowing it's initial encoding, we have no other choice than to guess it. Please note that if you encounter an issue because of a missing encoding here, we'll be glad to add it to this list. file_encodings is a :class:`~kivy.properties.ListProperty` and defaults to ['utf-8', 'latin1', 'cp1252']. .. versionadded:: 1.3.0 .. deprecated:: 1.8.0 This property is no longer used as the filechooser no longer decodes the file names. ''' file_system = ObjectProperty(FileSystemLocal(), baseclass=FileSystemAbstract) '''The file system object used to access the file system. This should be a subclass of :class:`FileSystemAbstract`. file_system is an :class:`~kivy.properties.ObjectProperty` and defaults to :class:`FileSystemLocal()` .. versionadded:: 1.8.0 ''' _update_files_ev = None _create_files_entries_ev = None __events__ = ('on_entry_added', 'on_entries_cleared', 'on_subentry_to_entry', 'on_remove_subentry', 'on_submit') def __init__(self, **kwargs): self._progress = None super(FileChooserController, self).__init__(**kwargs) self._items = [] fbind = self.fbind fbind('selection', self._update_item_selection) self._previous_path = [self.path] fbind('path', self._save_previous_path) update = self._trigger_update fbind('path', update) fbind('filters', update) fbind('rootpath', update) update() def on_touch_down(self, touch): # don't respond to touchs outside self if not self.collide_point(*touch.pos): return if self.disabled: return True return super(FileChooserController, self).on_touch_down(touch) def on_touch_up(self, touch): # don't respond to touchs outside self if not self.collide_point(*touch.pos): return if self.disabled: return True return super(FileChooserController, self).on_touch_up(touch) def _update_item_selection(self, *args): for item in self._items: item.selected = item.path in self.selection def _save_previous_path(self, instance, value): self._previous_path.append(value) self._previous_path = self._previous_path[-2:] def _trigger_update(self, *args): ev = self._update_files_ev if ev is None: ev = self._update_files_ev = Clock.create_trigger( self._update_files) ev() def on_entry_added(self, node, parent=None): if self.layout: self.layout.dispatch('on_entry_added', node, parent) def on_entries_cleared(self): if self.layout: self.layout.dispatch('on_entries_cleared') def on_subentry_to_entry(self, subentry, entry): if self.layout: self.layout.dispatch('on_subentry_to_entry', subentry, entry) def on_remove_subentry(self, subentry, entry): if self.layout: self.layout.dispatch('on_remove_subentry', subentry, entry) def on_submit(self, selected, touch=None): if self.layout: self.layout.dispatch('on_submit', selected, touch) def entry_touched(self, entry, touch): '''(internal) This method must be called by the template when an entry is touched by the user. ''' if ('button' in touch.profile and touch.button in ('scrollup', 'scrolldown', 'scrollleft', 'scrollright')): return False _dir = self.file_system.is_dir(entry.path) dirselect = self.dirselect if _dir and dirselect and touch.is_double_tap: self.open_entry(entry) return if self.multiselect: if entry.path in self.selection: self.selection.remove(entry.path) else: if _dir and not self.dirselect: self.open_entry(entry) return self.selection.append(entry.path) else: if _dir and not self.dirselect: return self.selection = [ abspath(join(self.path, entry.path)), ] def entry_released(self, entry, touch): '''(internal) This method must be called by the template when an entry is touched by the user. .. versionadded:: 1.1.0 ''' if ('button' in touch.profile and touch.button in ('scrollup', 'scrolldown', 'scrollleft', 'scrollright')): return False if not self.multiselect: if self.file_system.is_dir(entry.path) and not self.dirselect: self.open_entry(entry) elif touch.is_double_tap: if self.dirselect and self.file_system.is_dir(entry.path): return else: self.dispatch('on_submit', self.selection, touch) def open_entry(self, entry): try: # Just check if we can list the directory. This is also what # _add_file does, so if it fails here, it would also fail later # on. Do the check here to prevent setting path to an invalid # directory that we cannot list. self.file_system.listdir(entry.path) except OSError: entry.locked = True else: # If entry.path is to jump to previous directory, update path with # parent directory self.path = abspath(join(self.path, entry.path)) self.selection = [ self.path, ] if self.dirselect else [] def _apply_filters(self, files): if not self.filters: return files filtered = [] for filt in self.filters: if isinstance(filt, collections.Callable): filtered.extend([fn for fn in files if filt(self.path, fn)]) else: filtered.extend([fn for fn in files if fnmatch(fn, filt)]) if not self.filter_dirs: dirs = [fn for fn in files if self.file_system.is_dir(fn)] filtered.extend(dirs) return list(set(filtered)) def get_nice_size(self, fn): '''Pass the filepath. Returns the size in the best human readable format or '' if it is a directory (Don't recursively calculate size). ''' if self.file_system.is_dir(fn): return '' try: size = self.file_system.getsize(fn) except OSError: return '--' for unit in filesize_units: if size < 1024.0: return '%1.0f %s' % (size, unit) size /= 1024.0 def _update_files(self, *args, **kwargs): # trigger to start gathering the files in the new directory # we'll start a timer that will do the job, 10 times per frames # (default) self._gitems = [] self._gitems_parent = kwargs.get('parent', None) self._gitems_gen = self._generate_file_entries( path=kwargs.get('path', self.path), parent=self._gitems_parent) # cancel any previous clock if exist ev = self._create_files_entries_ev if ev is not None: ev.cancel() # show the progression screen self._hide_progress() if self._create_files_entries(): # not enough for creating all the entries, all a clock to continue # start a timer for the next 100 ms if ev is None: ev = self._create_files_entries_ev = Clock.schedule_interval( self._create_files_entries, .1) ev() def _get_file_paths(self, items): return [file.path for file in items] def _create_files_entries(self, *args): # create maximum entries during 50ms max, or 10 minimum (slow system) # (on a "fast system" (core i7 2700K), we can create up to 40 entries # in 50 ms. So 10 is fine for low system. start = time() finished = False index = total = count = 1 while time() - start < 0.05 or count < 10: try: index, total, item = next(self._gitems_gen) self._gitems.append(item) count += 1 except StopIteration: finished = True break except TypeError: # in case _gitems_gen is None finished = True break # if this wasn't enough for creating all the entries, show a progress # bar, and report the activity to the user. if not finished: self._show_progress() self._progress.total = total self._progress.index = index return True # we created all the files, now push them on the view self._items = items = self._gitems parent = self._gitems_parent if parent is None: self.dispatch('on_entries_cleared') for entry in items: self.dispatch('on_entry_added', entry, parent) else: parent.entries[:] = items for entry in items: self.dispatch('on_subentry_to_entry', entry, parent) self.files[:] = self._get_file_paths(items) # stop the progression / creation self._hide_progress() self._gitems = None self._gitems_gen = None ev = self._create_files_entries_ev if ev is not None: ev.cancel() return False def cancel(self, *largs): '''Cancel any background action started by filechooser, such as loading a new directory. .. versionadded:: 1.2.0 ''' ev = self._create_files_entries_ev if ev is not None: ev.cancel() self._hide_progress() if len(self._previous_path) > 1: # if we cancel any action, the path will be set same as the # previous one, so we can safely cancel the update of the previous # path. self.path = self._previous_path[-2] ev = self._update_files_ev if ev is not None: ev.cancel() def _show_progress(self): if self._progress: return cls = self.progress_cls if isinstance(cls, string_types): cls = Factory.get(cls) self._progress = cls(path=self.path) self._progress.value = 0 self.add_widget(self._progress) def _hide_progress(self): if self._progress: self.remove_widget(self._progress) self._progress = None def _generate_file_entries(self, *args, **kwargs): # Generator that will create all the files entries. # the generator is used via _update_files() and _create_files_entries() # don't use it directly. is_root = False path = kwargs.get('path', self.path) have_parent = kwargs.get('parent', None) is not None # Add the components that are always needed if self.rootpath: rootpath = realpath(self.rootpath) path = realpath(path) if not path.startswith(rootpath): self.path = rootpath return elif path == rootpath: is_root = True else: if platform == 'win': is_root = splitdrive(path)[1] in (sep, altsep) elif platform in ('macosx', 'linux', 'android', 'ios'): is_root = normpath(expanduser(path)) == sep else: # Unknown fs, just always add the .. entry but also log Logger.warning('Filechooser: Unsupported OS: %r' % platform) # generate an entries to go back to previous if not is_root and not have_parent: back = '..' + sep if platform == 'win': new_path = path[:path.rfind(sep)] if sep not in new_path: new_path += sep pardir = self._create_entry_widget( dict(name=back, size='', path=new_path, controller=ref(self), isdir=True, parent=None, sep=sep, get_nice_size=lambda: '')) else: pardir = self._create_entry_widget( dict(name=back, size='', path=back, controller=ref(self), isdir=True, parent=None, sep=sep, get_nice_size=lambda: '')) yield 0, 1, pardir # generate all the entries for files try: for index, total, item in self._add_files(path): yield index, total, item except OSError: Logger.exception('Unable to open directory <%s>' % self.path) self.files[:] = [] def _create_entry_widget(self, ctx): template = self.layout._ENTRY_TEMPLATE\ if self.layout else self._ENTRY_TEMPLATE return Builder.template(template, **ctx) def _add_files(self, path, parent=None): path = expanduser(path) if isfile(path): path = dirname(path) files = [] fappend = files.append for f in self.file_system.listdir(path): try: # In the following, use fully qualified filenames fappend(normpath(join(path, f))) except UnicodeDecodeError: Logger.exception('unable to decode <{}>'.format(f)) except UnicodeEncodeError: Logger.exception('unable to encode <{}>'.format(f)) # Apply filename filters files = self._apply_filters(files) # Sort the list of files files = self.sort_func(files, self.file_system) is_hidden = self.file_system.is_hidden if not self.show_hidden: files = [x for x in files if not is_hidden(x)] self.files[:] = files total = len(files) wself = ref(self) for index, fn in enumerate(files): def get_nice_size(): # Use a closure for lazy-loading here return self.get_nice_size(fn) ctx = { 'name': basename(fn), 'get_nice_size': get_nice_size, 'path': fn, 'controller': wself, 'isdir': self.file_system.is_dir(fn), 'parent': parent, 'sep': sep } entry = self._create_entry_widget(ctx) yield index, total, entry def entry_subselect(self, entry): if not self.file_system.is_dir(entry.path): return self._update_files(path=entry.path, parent=entry) def close_subselection(self, entry): for subentry in entry.entries: self.dispatch('on_remove_subentry', subentry, entry)
class PythonConsole(BoxLayout): text_input = ObjectProperty(None) '''Instance of :class:`~designer.uix.py_console.InteractiveShellInput` :data:`text_input` is an :class:`~kivy.properties.ObjectProperty` ''' sh = ObjectProperty(None) '''Instance of :class:`~designer.uix.py_console.Shell` :data:`sh` is an :class:`~kivy.properties.ObjectProperty` ''' scroll_view = ObjectProperty(None) '''Instance of :class:`~kivy.uix.scrollview.ScrollView` :data:`scroll_view` is an :class:`~kivy.properties.ObjectProperty` ''' foreground_color = ListProperty((.5, .5, .5, .93)) '''This defines the color of the text in the console :data:`foreground_color` is an :class:`~kivy.properties.ListProperty`, Default to '(.5, .5, .5, .93)' ''' background_color = ListProperty((0, 0, 0, 1)) '''This defines the color of the text in the console :data:`foreground_color` is an :class:`~kivy.properties.ListProperty`, Default to '(0, 0, 0, 1)''' font_name = StringProperty('data/fonts/DroidSansMono.ttf') '''Indicates the font Style used in the console :data:`font` is a :class:`~kivy.properties.StringProperty`, Default to 'DroidSansMono' ''' font_size = NumericProperty(14) '''Indicates the size of the font used for the console :data:`font_size` is a :class:`~kivy.properties.NumericProperty`, Default to '9' ''' def __init__(self, **kwargs): super(PythonConsole, self).__init__() self.sh = Shell(self) self._thread = InteractiveThread(self.sh) Clock.schedule_once(self.run_sh, 0) self._ready_to_input = False self._exit = False def ready_to_input(self, *args): '''Specifies that PythonConsole is ready to take input from user. ''' self._ready_to_input = True def run_sh(self, *args): '''Start Python Shell. ''' self._thread.start() def show_output(self, data, dt): '''Show output to user. ''' self.text_input.show_output(data) def _show_prompt(self, *args): '''Show prompt to user and asks for input. ''' self.text_input.show_output(self.prompt) def get_input(self, prompt): '''Get input from user. ''' import time self.prompt = prompt Clock.schedule_once(self._show_prompt, 0.1) while not self._ready_to_input and not self._exit: time.sleep(0.05) self._ready_to_input = False return self.text_input.last_line def exit(self): '''Exit PythonConsole ''' self._exit = True self.sh.exit()
class ImageLayout1(Widget): im1 = ObjectProperty(None) im2 = ObjectProperty(None) global filename filename = "1.jpg" def SaveFile(self): toast('Saved successfully in gallery as out.jpg') def tint(self): try: if os.path.exists("out.jpg"): os.remove("out.jpg") img = Image.open(self.filename) imgG = img.convert("L") img1 = ImageOps.colorize(imgG, black=self.ids.spin.text, white="white") img2 = img1.save('out.jpg') except: pop = Popup(title='Some error occurred', content=Label(text='Try again'), size_hint=(None, None), size=(350, 100)) pop.open() def grey(self): try: if os.path.exists("out.jpg"): os.remove("out.jpg") img = Image.open(self.filename) imgG = img.convert("L") img2 = imgG.save('out.jpg') except: pop = Popup(title='Some error occurred', content=Label(text='Try again'), size_hint=(None, None), size=(350, 100)) pop.open() def flip(self, dir): try: if os.path.exists("out.jpg"): os.remove("out.jpg") img = Image.open(self.filename) img = img.convert('RGB') if dir == 'v': img1 = ImageOps.flip(img) elif dir == 'h': img1 = ImageOps.mirror(img) elif dir == 'neg': img1 = ImageOps.invert(img) img2 = img1.save('out.jpg') except: pop = Popup(title='Some error occurred', content=Label(text='Try again'), size_hint=(None, None), size=(350, 100)) pop.open() def Convert(self): try: self.im2.source = 'old.jpg' for i in range(0, 5): pass self.im2.source = 'out.jpg' except: pop = Popup(title='Some error occurred', content=Label(text='Try again'), size_hint=(None, None), size=(350, 100)) pop.open() def workOn(self): try: image = Image.open('out.jpg') image.save('workagain.jpg') self.filename = 'workagain.jpg' self.im1.source = 'old.jpg' print('test') self.im1.source = self.filename except: pop = Popup(title='Some error occurred', content=Label(text='Try again'), size_hint=(None, None), size=(350, 100)) pop.open() def browse(self): global filename global flag createTkinter() tk.mainloop() #filename= filedialog.askopenfilename(initialdir="/",title="Select A file",filetype=(("jpeg files","*.jpg"),("all files","*.*"))) #self.label.configure(text=self.filename) if flag == 1: self.im1.source = filename def resetSlider(self): self.ids.slid1.value = 1 def rot90(self): try: if os.path.exists("out.jpg"): os.remove("out.jpg") img = Image.open(self.filename) imgG = img.rotate(90) for i in range(0, 5): pass img2 = imgG.save('out.jpg') except: pop = Popup(title='Some error occurred', content=Label(text='Try again'), size_hint=(None, None), size=(350, 100)) pop.open() def crop(self): try: if os.path.exists("out.jpg"): os.remove("out.jpg") img = Image.open(self.filename) SZ = img.size l = SZ[0] * self.ids.left.value r = SZ[0] * self.ids.rgt.value t = SZ[1] * self.ids.top.value b = SZ[1] * self.ids.btm.value box = (l, t, r, b) if l > r or t > b: pop = Popup(title='Some error occurred', content=Label(text='Try again'), size_hint=(None, None), size=(350, 100)) pop.open() else: img1 = img.crop(box) img1.save('out.jpg') except: pop = Popup(title='Some error occurred', content=Label(text='Try again'), size_hint=(None, None), size=(350, 100)) pop.open()
class RecycleDataAdapter(EventDispatcher): '''The class that converts data to a view. --- Internal details --- A view can have 3 states. * It can be completely in sync with the data, which occurs when the view is displayed. These are stored in :attr:`views`. * It can be dirty, which occurs when the view is in sync with the data, except for the size/pos parameters which is controlled by the layout. This occurs when the view is not currently displayed but the data has not changed. These views are stored in :attr:`dirty_views`. * Finally the view can be dead which occurs when the data changes and the view was not updated or when a view is just created. Such views are typically added to the internal cache. Typically what happens is that the layout manager lays out the data and then asks for views, using :meth:`set_visible_views,` for some specific data items that it displays. These views are gotten from the current views, dirty or global cache. Then depending on the view state :meth:`refresh_view_attrs` is called to bring the view up to date with the data (except for sizing parameters). Finally, the layout manager gets these views, updates their size and displays them. ''' recycleview = ObjectProperty(None, allownone=True) '''The :class:`~kivy.uix.recycleview.RecycleViewBehavior` associated with this instance. ''' # internals views = {} # current displayed items # items whose attrs, except for pos/size is still accurate dirty_views = defaultdict(dict) _sizing_attrs = {'size', 'width', 'height', 'size_hint', 'size_hint_x', 'size_hint_y', 'pos', 'x', 'y', 'center', 'center_x', 'center_y', 'pos_hint'} def attach_recycleview(self, rv): '''Associates a :class:`~kivy.uix.recycleview.RecycleViewBehavior` with this instance. It is stored in :attr:`recycleview`. ''' self.recycleview = rv def detach_recycleview(self): '''Removes the :class:`~kivy.uix.recycleview.RecycleViewBehavior` associated with this instance and clears :attr:`recycleview`. ''' self.recycleview = None def create_view(self, index, data_item, viewclass): '''(internal) Creates and initializes the view for the data at `index`. The returned view is synced with the data, except for the pos/size information. ''' if viewclass is None: return view = viewclass() self.refresh_view_attrs(index, data_item, view) return view def get_view(self, index, data_item, viewclass): '''(internal) Returns a view instance for the data at `index` It looks through the various caches and finally creates a view if it doesn't exist. The returned view is synced with the data, except for the pos/size information. If found in the cache it's removed from the source before returning. It doesn't check the current views. ''' # is it in the dirtied views? dirty_views = self.dirty_views if viewclass is None: return stale = False view = None if viewclass in dirty_views: # get it first from dirty list dirty_class = dirty_views[viewclass] if index in dirty_class: # we found ourself in the dirty list, no need to update data! view = dirty_class.pop(index) elif _cached_views[viewclass]: # global cache has this class, update data view, stale = _cached_views[viewclass].pop(), True elif dirty_class: # random any dirty view element - update data view, stale = dirty_class.popitem()[1], True elif _cached_views[viewclass]: # otherwise go directly to cache # global cache has this class, update data view, stale = _cached_views[viewclass].pop(), True if view is None: view = self.create_view(index, data_item, viewclass) if view is None: return if stale: self.refresh_view_attrs(index, data_item, view) return view def refresh_view_attrs(self, index, data_item, view): '''(internal) Syncs the view and brings it up to date with the data. This method calls :meth:`RecycleDataViewBehavior.refresh_view_attrs` if the view inherits from :class:`RecycleDataViewBehavior`. See that method for more details. .. note:: Any sizing and position info is skipped when syncing with the data. ''' viewclass = view.__class__ if viewclass not in _view_base_cache: _view_base_cache[viewclass] = isinstance(view, RecycleDataViewBehavior) if _view_base_cache[viewclass]: view.refresh_view_attrs(self.recycleview, index, data_item) else: sizing_attrs = RecycleDataAdapter._sizing_attrs for key, value in data_item.items(): if key not in sizing_attrs: setattr(view, key, value) def refresh_view_layout(self, index, pos, pos_hint, size, size_hint, view, viewport): '''Updates the sizing information of the view. viewport` is in coordinates of the layout manager. This method calls :meth:`RecycleDataViewBehavior.refresh_view_attrs` if the view inherits from :class:`RecycleDataViewBehavior`. See that method for more details. .. note:: Any sizing and position info is skipped when syncing with the data. ''' if view.__class__ not in _view_base_cache: _view_base_cache[view.__class__] = isinstance( view, RecycleDataViewBehavior) if _view_base_cache[view.__class__]: view.refresh_view_layout( self.recycleview, index, pos, pos_hint, size, size_hint, viewport) else: view.size_hint = size_hint view.pos_hint = pos_hint w, h = size if w is None: if h is not None: view.height = h else: if h is None: view.width = w else: view.size = size view.pos = pos def make_view_dirty(self, view, index): '''(internal) Used to flag this view as dirty, ready to be used for others. See :meth:`make_views_dirty`. ''' del self.views[index] self.dirty_views[view.__class__][index] = view def make_views_dirty(self): '''Makes all the current views dirty. Dirty views are still in sync with the corresponding data. However, the size information may go out of sync. Therefore a dirty view can be reused by the same index by just updating the sizing information. Once the underlying data of this index changes, the view should be removed from the dirty views and moved to the global cache with :meth:`invalidate`. This is typically called when the layout manager needs to re-layout all the data. ''' views = self.views if not views: return dirty_views = self.dirty_views for index, view in views.items(): dirty_views[view.__class__][index] = view self.views = {} def invalidate(self): '''Moves all the current views into the global cache. As opposed to making a view dirty where the view is in sync with the data except for sizing information, this will completely disconnect the view from the data, as it is assumed the data has gone out of sync with the view. This is typically called when the data changes. ''' global _cache_count for view in self.views.values(): _cached_views[view.__class__].append(view) _cache_count += 1 for cls, views in self.dirty_views.items(): _cached_views[cls].extend(views.values()) _cache_count += len(views) if _cache_count >= _max_cache_size: _clean_cache() self.views = {} self.dirty_views.clear() def set_visible_views(self, indices, data, viewclasses): '''Gets a 3-tuple of the new, remaining, and old views for the current viewport. The new views are synced to the data except for the size/pos properties. The old views need to be removed from the layout, and the new views added. The new views are not necessarily *new*, but are all the currently visible views. ''' visible_views = {} previous_views = self.views ret_new = [] ret_remain = [] get_view = self.get_view # iterate though the visible view # add them into the container if not already done for index in indices: view = previous_views.pop(index, None) if view is not None: # was current view visible_views[index] = view ret_remain.append((index, view)) else: view = get_view(index, data[index], viewclasses[index]['viewclass']) if view is None: continue visible_views[index] = view ret_new.append((index, view)) old_views = previous_views.items() self.make_views_dirty() self.views = visible_views return ret_new, ret_remain, old_views def get_visible_view(self, index): '''Returns the currently visible view associated with ``index``. If no view is currently displayed for ``index`` it returns ``None``. ''' return self.views.get(index)
class WidgetTreeElement(TreeViewLabel): '''WidgetTreeElement represents each node in WidgetsTree ''' node = ObjectProperty(None)
class Game(Widget): car = ObjectProperty(None) ball1 = ObjectProperty(None) ball2 = ObjectProperty(None) ball3 = ObjectProperty(None) def serve_car(self): self.car.center = self.center self.car.velocity = Vector(6, 0) def update(self, dt): global brain global last_reward global scores global last_distance global goal_x global goal_y global longueur global largeur longueur = self.width largeur = self.height if first_update: init() xx = goal_x - self.car.x yy = goal_y - self.car.y orientation = Vector(*self.car.velocity).angle((xx, yy)) / 180. last_signal = [ self.car.signal1, self.car.signal2, self.car.signal3, orientation, -orientation ] action = brain.update(last_reward, last_signal) scores.append(brain.score()) rotation = action2rotation[action] self.car.move(rotation) distance = np.sqrt((self.car.x - goal_x)**2 + (self.car.y - goal_y)**2) self.ball1.pos = self.car.sensor1 self.ball2.pos = self.car.sensor2 self.ball3.pos = self.car.sensor3 if sand[int(self.car.x), int(self.car.y)] > 0: self.car.velocity = Vector(1, 0).rotate(self.car.angle) last_reward = -1 else: # otherwise self.car.velocity = Vector(6, 0).rotate(self.car.angle) last_reward = -0.2 if distance < last_distance: last_reward = 0.1 if self.car.x < 10: self.car.x = 10 last_reward = -1 if self.car.x > self.width - 10: self.car.x = self.width - 10 last_reward = -1 if self.car.y < 10: self.car.y = 10 last_reward = -1 if self.car.y > self.height - 10: self.car.y = self.height - 10 last_reward = -1 if distance < 100: goal_x = self.width - goal_x goal_y = self.height - goal_y last_distance = distance
class SettingsManager(BoxLayout): # Instance of language select dropdown menu lang_select = ObjectProperty(None)
class Carousel(StencilView): '''Carousel class. See module documentation for more information. ''' slides = ListProperty([]) '''List of slides inside the Carousel. The slides are the widgets added to the Carousel using the :attr:`add_widget` method. :attr:`slides` is a :class:`~kivy.properties.ListProperty` and is read-only. ''' def _get_slides_container(self): return [x.parent for x in self.slides] slides_container = AliasProperty(_get_slides_container, bind=('slides', )) direction = OptionProperty('right', options=('right', 'left', 'top', 'bottom')) '''Specifies the direction in which the slides are ordered. This corresponds to the direction from which the user swipes to go from one slide to the next. It can be `right`, `left`, `top`, or `bottom`. For example, with the default value of `right`, the second slide is to the right of the first and the user would swipe from the right towards the left to get to the second slide. :attr:`direction` is an :class:`~kivy.properties.OptionProperty` and defaults to 'right'. ''' min_move = NumericProperty(0.2) '''Defines the minimum distance to be covered before the touch is considered a swipe gesture and the Carousel content changed. This is a expressed as a fraction of the Carousel's width. If the movement doesn't reach this minimum value, the movement is cancelled and the content is restored to its original position. :attr:`min_move` is a :class:`~kivy.properties.NumericProperty` and defaults to 0.2. ''' anim_move_duration = NumericProperty(0.5) '''Defines the duration of the Carousel animation between pages. :attr:`anim_move_duration` is a :class:`~kivy.properties.NumericProperty` and defaults to 0.5. ''' anim_cancel_duration = NumericProperty(0.3) '''Defines the duration of the animation when a swipe movement is not accepted. This is generally when the user does not make a large enough swipe. See :attr:`min_move`. :attr:`anim_cancel_duration` is a :class:`~kivy.properties.NumericProperty` and defaults to 0.3. ''' loop = BooleanProperty(False) '''Allow the Carousel to loop infinitely. If True, when the user tries to swipe beyond last page, it will return to the first. If False, it will remain on the last page. :attr:`loop` is a :class:`~kivy.properties.BooleanProperty` and defaults to False. ''' def _get_index(self): if self.slides: return self._index % len(self.slides) return None def _set_index(self, value): if self.slides: self._index = value % len(self.slides) else: self._index = None index = AliasProperty(_get_index, _set_index, bind=('_index', 'slides'), cache=True) '''Get/Set the current slide based on the index. :attr:`index` is an :class:`~kivy.properties.AliasProperty` and defaults to 0 (the first item). ''' def _prev_slide(self): slides = self.slides len_slides = len(slides) index = self.index if len_slides < 2: # None, or 1 slide return None if self.loop and index == 0: return slides[-1] if index > 0: return slides[index - 1] previous_slide = AliasProperty(_prev_slide, bind=('slides', 'index', 'loop'), cache=True) '''The previous slide in the Carousel. It is None if the current slide is the first slide in the Carousel. This ordering reflects the order in which the slides are added: their presentation varies according to the :attr:`direction` property. :attr:`previous_slide` is an :class:`~kivy.properties.AliasProperty`. .. versionchanged:: 1.5.0 This property no longer exposes the slides container. It returns the widget you have added. ''' def _curr_slide(self): if len(self.slides): return self.slides[self.index or 0] current_slide = AliasProperty(_curr_slide, bind=('slides', 'index'), cache=True) '''The currently shown slide. :attr:`current_slide` is an :class:`~kivy.properties.AliasProperty`. .. versionchanged:: 1.5.0 The property no longer exposes the slides container. It returns the widget you have added. ''' def _next_slide(self): if len(self.slides) < 2: # None, or 1 slide return None if self.loop and self.index == len(self.slides) - 1: return self.slides[0] if self.index < len(self.slides) - 1: return self.slides[self.index + 1] next_slide = AliasProperty(_next_slide, bind=('slides', 'index', 'loop'), cache=True) '''The next slide in the Carousel. It is None if the current slide is the last slide in the Carousel. This ordering reflects the order in which the slides are added: their presentation varies according to the :attr:`direction` property. :attr:`next_slide` is an :class:`~kivy.properties.AliasProperty`. .. versionchanged:: 1.5.0 The property no longer exposes the slides container. It returns the widget you have added. ''' scroll_timeout = NumericProperty(200) '''Timeout allowed to trigger the :attr:`scroll_distance`, in milliseconds. If the user has not moved :attr:`scroll_distance` within the timeout, no scrolling will occur and the touch event will go to the children. :attr:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty` and defaults to 200 (milliseconds) .. versionadded:: 1.5.0 ''' scroll_distance = NumericProperty('20dp') '''Distance to move before scrolling the :class:`Carousel` in pixels. As soon as the distance has been traveled, the :class:`Carousel` 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. :attr:`scroll_distance` is a :class:`~kivy.properties.NumericProperty` and defaults to 20dp. .. versionadded:: 1.5.0 ''' anim_type = StringProperty('out_quad') '''Type of animation to use while animating to the next/previous slide. This should be the name of an :class:`~kivy.animation.AnimationTransition` function. :attr:`anim_type` is a :class:`~kivy.properties.StringProperty` and defaults to 'out_quad'. .. versionadded:: 1.8.0 ''' ignore_perpendicular_swipes = BooleanProperty(False) '''Ignore swipes on axis perpendicular to direction. :attr:`ignore_perpendicular_swipes` is a :class:`~kivy.properties.BooleanProperty` and defaults to False. .. versionadded:: 1.10.0 ''' # private properties, for internal use only ### _index = NumericProperty(0, allownone=True) _prev = ObjectProperty(None, allownone=True) _current = ObjectProperty(None, allownone=True) _next = ObjectProperty(None, allownone=True) _offset = NumericProperty(0) _touch = ObjectProperty(None, allownone=True) _change_touch_mode_ev = None def __init__(self, **kwargs): self._trigger_position_visible_slides = Clock.create_trigger( self._position_visible_slides, -1) super(Carousel, self).__init__(**kwargs) self._skip_slide = None self.touch_mode_change = False self._prioritize_next = False self.fbind('loop', lambda *args: self._insert_visible_slides()) def load_slide(self, slide): '''Animate to the slide that is passed as the argument. .. versionchanged:: 1.8.0 ''' slides = self.slides start, stop = slides.index(self.current_slide), slides.index(slide) if start == stop: return self._skip_slide = stop if stop > start: self._prioritize_next = True self._insert_visible_slides(_next_slide=slide) self.load_next() else: self._prioritize_next = False self._insert_visible_slides(_prev_slide=slide) self.load_previous() def load_previous(self): '''Animate to the previous slide. .. versionadded:: 1.7.0 ''' self.load_next(mode='prev') def load_next(self, mode='next'): '''Animate to the next slide. .. versionadded:: 1.7.0 ''' if self.index is not None: w, h = self.size _direction = { 'top': -h / 2, 'bottom': h / 2, 'left': w / 2, 'right': -w / 2 } _offset = _direction[self.direction] if mode == 'prev': _offset = -_offset self._start_animation(min_move=0, offset=_offset) def get_slide_container(self, slide): return slide.parent @property def _prev_equals_next(self): return self.loop and len(self.slides) == 2 def _insert_visible_slides(self, _next_slide=None, _prev_slide=None): get_slide_container = self.get_slide_container previous_slide = _prev_slide if _prev_slide else self.previous_slide if previous_slide: self._prev = get_slide_container(previous_slide) else: self._prev = None current_slide = self.current_slide if current_slide: self._current = get_slide_container(current_slide) else: self._current = None next_slide = _next_slide if _next_slide else self.next_slide if next_slide: self._next = get_slide_container(next_slide) else: self._next = None if self._prev_equals_next: setattr(self, '_prev' if self._prioritize_next else '_next', None) super_remove = super(Carousel, self).remove_widget for container in self.slides_container: super_remove(container) if self._prev and self._prev.parent is not self: super(Carousel, self).add_widget(self._prev) if self._next and self._next.parent is not self: super(Carousel, self).add_widget(self._next) if self._current: super(Carousel, self).add_widget(self._current) def _position_visible_slides(self, *args): slides, index = self.slides, self.index no_of_slides = len(slides) - 1 if not slides: return x, y, width, height = self.x, self.y, self.width, self.height _offset, direction = self._offset, self.direction[0] _prev, _next, _current = self._prev, self._next, self._current get_slide_container = self.get_slide_container last_slide = get_slide_container(slides[-1]) first_slide = get_slide_container(slides[0]) skip_next = False _loop = self.loop if direction in 'rl': xoff = x + _offset x_prev = {'l': xoff + width, 'r': xoff - width} x_next = {'l': xoff - width, 'r': xoff + width} if _prev: _prev.pos = (x_prev[direction], y) elif _loop and _next and index == 0: # if first slide is moving to right with direction set to right # or toward left with direction set to left if ((_offset > 0 and direction == 'r') or (_offset < 0 and direction == 'l')): # put last_slide before first slide last_slide.pos = (x_prev[direction], y) skip_next = True if _current: _current.pos = (xoff, y) if skip_next: return if _next: _next.pos = (x_next[direction], y) elif _loop and _prev and index == no_of_slides: if ((_offset < 0 and direction == 'r') or (_offset > 0 and direction == 'l')): first_slide.pos = (x_next[direction], y) if direction in 'tb': yoff = y + _offset y_prev = {'t': yoff - height, 'b': yoff + height} y_next = {'t': yoff + height, 'b': yoff - height} if _prev: _prev.pos = (x, y_prev[direction]) elif _loop and _next and index == 0: if ((_offset > 0 and direction == 't') or (_offset < 0 and direction == 'b')): last_slide.pos = (x, y_prev[direction]) skip_next = True if _current: _current.pos = (x, yoff) if skip_next: return if _next: _next.pos = (x, y_next[direction]) elif _loop and _prev and index == no_of_slides: if ((_offset < 0 and direction == 't') or (_offset > 0 and direction == 'b')): first_slide.pos = (x, y_next[direction]) def on_size(self, *args): size = self.size for slide in self.slides_container: slide.size = size self._trigger_position_visible_slides() def on_pos(self, *args): self._trigger_position_visible_slides() def on_index(self, *args): self._insert_visible_slides() self._trigger_position_visible_slides() self._offset = 0 def on_slides(self, *args): if self.slides: self.index = self.index % len(self.slides) self._insert_visible_slides() self._trigger_position_visible_slides() def on__offset(self, *args): self._trigger_position_visible_slides() # if reached full offset, switch index to next or prev direction = self.direction[0] _offset = self._offset width = self.width height = self.height index = self.index if self._skip_slide is not None or index is None: return # Move to next slide? if (direction == 'r' and _offset <= -width) or \ (direction == 'l' and _offset >= width) or \ (direction == 't' and _offset <= - height) or \ (direction == 'b' and _offset >= height): if self.next_slide: self.index += 1 # Move to previous slide? elif (direction == 'r' and _offset >= width) or \ (direction == 'l' and _offset <= -width) or \ (direction == 't' and _offset >= height) or \ (direction == 'b' and _offset <= -height): if self.previous_slide: self.index -= 1 elif self._prev_equals_next: new_value = (_offset < 0) is (direction in 'rt') if self._prioritize_next is not new_value: self._prioritize_next = new_value if new_value is (self._next is None): self._prev, self._next = self._next, self._prev def _start_animation(self, *args, **kwargs): # compute target offset for ease back, next or prev new_offset = 0 direction = kwargs.get('direction', self.direction)[0] is_horizontal = direction in 'rl' extent = self.width if is_horizontal else self.height min_move = kwargs.get('min_move', self.min_move) _offset = kwargs.get('offset', self._offset) if _offset < min_move * -extent: new_offset = -extent elif _offset > min_move * extent: new_offset = extent # if new_offset is 0, it wasnt enough to go next/prev dur = self.anim_move_duration if new_offset == 0: dur = self.anim_cancel_duration # detect edge cases if not looping len_slides = len(self.slides) index = self.index if not self.loop or len_slides == 1: is_first = (index == 0) is_last = (index == len_slides - 1) if direction in 'rt': towards_prev = (new_offset > 0) towards_next = (new_offset < 0) else: towards_prev = (new_offset < 0) towards_next = (new_offset > 0) if (is_first and towards_prev) or (is_last and towards_next): new_offset = 0 anim = Animation(_offset=new_offset, d=dur, t=self.anim_type) anim.cancel_all(self) def _cmp(*l): if self._skip_slide is not None: self.index = self._skip_slide self._skip_slide = None anim.bind(on_complete=_cmp) anim.start(self) def _get_uid(self, prefix='sv'): return '{0}.{1}'.format(prefix, self.uid) def on_touch_down(self, touch): if not self.collide_point(*touch.pos): touch.ud[self._get_uid('cavoid')] = True return if self.disabled: return True if self._touch: return super(Carousel, self).on_touch_down(touch) Animation.cancel_all(self) self._touch = touch uid = self._get_uid() touch.grab(self) touch.ud[uid] = {'mode': 'unknown', 'time': touch.time_start} self._change_touch_mode_ev = Clock.schedule_once( self._change_touch_mode, self.scroll_timeout / 1000.) self.touch_mode_change = False return True def on_touch_move(self, touch): if not self.touch_mode_change: if self.ignore_perpendicular_swipes and \ self.direction in ('top', 'bottom'): if abs(touch.oy - touch.y) < self.scroll_distance: if abs(touch.ox - touch.x) > self.scroll_distance: self._change_touch_mode() self.touch_mode_change = True elif self.ignore_perpendicular_swipes and \ self.direction in ('right', 'left'): if abs(touch.ox - touch.x) < self.scroll_distance: if abs(touch.oy - touch.y) > self.scroll_distance: self._change_touch_mode() self.touch_mode_change = True 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[0] if ud['mode'] == 'unknown': if direction in 'rl': distance = abs(touch.ox - touch.x) else: distance = abs(touch.oy - touch.y) if distance > self.scroll_distance: ev = self._change_touch_mode_ev if ev is not None: ev.cancel() ud['mode'] = 'scroll' else: if direction in 'rl': self._offset += touch.dx if direction in 'tb': self._offset += touch.dy return True def on_touch_up(self, touch): if self._get_uid('cavoid') in touch.ud: return if self in [x() for x in touch.grab_list]: touch.ungrab(self) self._touch = None ud = touch.ud[self._get_uid()] if ud['mode'] == 'unknown': ev = self._change_touch_mode_ev if ev is not None: ev.cancel() super(Carousel, self).on_touch_down(touch) Clock.schedule_once(partial(self._do_touch_up, touch), .1) else: self._start_animation() else: if self._touch is not touch and self.uid not in touch.ud: super(Carousel, self).on_touch_up(touch) return self._get_uid() in touch.ud def _do_touch_up(self, touch, *largs): super(Carousel, self).on_touch_up(touch) # don't forget about grab event! for x in touch.grab_list[:]: touch.grab_list.remove(x) x = x() if not x: continue touch.grab_current = x super(Carousel, self).on_touch_up(touch) touch.grab_current = None def _change_touch_mode(self, *largs): if not self._touch: return self._start_animation() uid = self._get_uid() touch = self._touch ud = touch.ud[uid] if ud['mode'] == 'unknown': touch.ungrab(self) self._touch = None super(Carousel, self).on_touch_down(touch) return def add_widget(self, widget, index=0, *args, **kwargs): container = RelativeLayout(size=self.size, x=self.x - self.width, y=self.y) container.add_widget(widget) super(Carousel, self).add_widget(container, index, *args, **kwargs) if index != 0: self.slides.insert(index - len(self.slides), widget) else: self.slides.append(widget) def remove_widget(self, widget, *args, **kwargs): # XXX be careful, the widget.parent refer to the RelativeLayout # added in add_widget(). But it will break if RelativeLayout # implementation change. # if we passed the real widget slides = self.slides if widget in slides: if self.index >= slides.index(widget): self.index = max(0, self.index - 1) container = widget.parent slides.remove(widget) super(Carousel, self).remove_widget(container, *args, **kwargs) container.remove_widget(widget) return super(Carousel, self).remove_widget(widget, *args, **kwargs) def clear_widgets(self, children=None, *args, **kwargs): # `children` must be a list of slides or None if children is None: children = self.slides[:] remove_widget = self.remove_widget for widget in children: remove_widget(widget) super(Carousel, self).clear_widgets()
class Header(BoxLayout): head = ObjectProperty(None)
class HistoryScreen(CScreen): tab = ObjectProperty(None) kvname = 'history' cards = {} def __init__(self, **kwargs): self.ra_dialog = None super(HistoryScreen, self).__init__(**kwargs) self.menu_actions = [('Label', self.label_dialog), ('Details', self.show_tx)] def show_tx(self, obj): tx_hash = obj.tx_hash tx = self.app.wallet.transactions.get(tx_hash) if not tx: return self.app.tx_dialog(tx) def label_dialog(self, obj): from dialogs.label_dialog import LabelDialog key = obj.tx_hash text = self.app.wallet.get_label(key) def callback(text): self.app.wallet.set_label(key, text) self.update() d = LabelDialog(_('Enter Transaction Label'), text, callback) d.open() def get_card(self, tx_hash, height, conf, timestamp, value, balance): status, status_str = self.app.wallet.get_tx_status( tx_hash, height, conf, timestamp) icon = "atlas://gui/kivy/theming/light/" + TX_ICONS[status] label = self.app.wallet.get_label(tx_hash) if tx_hash else _( 'Pruned transaction outputs') date = timestamp_to_datetime(timestamp) ri = self.cards.get(tx_hash) if ri is None: ri = Factory.HistoryItem() ri.screen = self ri.tx_hash = tx_hash self.cards[tx_hash] = ri ri.icon = icon ri.date = status_str ri.message = label ri.value = value or 0 ri.amount = self.app.format_amount(value, True) if value is not None else '--' ri.confirmations = conf if self.app.fiat_unit and date: rate = self.app.fx.history_rate(date) if rate: s = self.app.fx.value_str(value, rate) ri.quote_text = '' if s is None else s + ' ' + self.app.fiat_unit return ri def update(self, see_all=False): if self.app.wallet is None: return history = reversed(self.app.wallet.get_history()) history_card = self.screen.ids.history_container history_card.clear_widgets() count = 0 for item in history: ri = self.get_card(*item) count += 1 history_card.add_widget(ri) if count == 0: msg = _( 'This screen shows your list of transactions. It is currently empty.' ) history_card.add_widget(EmptyLabel(text=msg))