Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
class ScreenManagement(ScreenManager):
    main_screen = ObjectProperty(None)
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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()
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
class LoadDialog(FloatLayout):
    load = ObjectProperty(None)
    cancel = ObjectProperty(None)
    fileChooser = ObjectProperty(None)
Ejemplo n.º 8
0
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))
Ejemplo n.º 9
0
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")
Ejemplo n.º 10
0
class ChangeTableSizeDialog(GridLayout):
    accept = ObjectProperty(None)
    cancel = ObjectProperty(None)
Ejemplo n.º 11
0
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)
Ejemplo n.º 12
0
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, ))
Ejemplo n.º 13
0
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)),
                ]
            )
Ejemplo n.º 14
0
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']
            ]
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
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
Ejemplo n.º 17
0
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
Ejemplo n.º 18
0
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)
Ejemplo n.º 19
0
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
Ejemplo n.º 20
0
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)
Ejemplo n.º 21
0
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)
Ejemplo n.º 22
0
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()
Ejemplo n.º 23
0
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()
Ejemplo n.º 24
0
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)
Ejemplo n.º 25
0
class WidgetTreeElement(TreeViewLabel):
    '''WidgetTreeElement represents each node in WidgetsTree
    '''
    node = ObjectProperty(None)
Ejemplo n.º 26
0
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
Ejemplo n.º 27
0
class SettingsManager(BoxLayout):
    # Instance of language select dropdown menu
    lang_select = ObjectProperty(None)
Ejemplo n.º 28
0
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()
Ejemplo n.º 29
0
class Header(BoxLayout):
    head = ObjectProperty(None)
Ejemplo n.º 30
0
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))