Exemple #1
0
class MDSpinner(ThemableBehavior, Widget):
    """:class:`MDSpinner` is an implementation of the circular progress
    indicator in Google's Material Design.

    It can be used either as an indeterminate indicator that loops while
    the user waits for something to happen, or as a determinate indicator.

    Set :attr:`determinate` to **True** to activate determinate mode, and
    :attr:`determinate_time` to set the duration of the animation.
    """

    determinate = BooleanProperty(False)
    """:attr:`determinate` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to False
    """

    determinate_time = NumericProperty(2)
    """:attr:`determinate_time` is a :class:`~kivy.properties.NumericProperty`
    and defaults to 2
    """

    active = BooleanProperty(True)
    """Use :attr:`active` to start or stop the spinner.

    :attr:`active` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to True
    """

    color = ListProperty([])
    """:attr:`color` is a :class:`~kivy.properties.ListProperty` and
    defaults to 'self.theme_cls.primary_color'
    """

    _alpha = NumericProperty(0)
    _rotation_angle = NumericProperty(360)
    _angle_start = NumericProperty(0)
    _angle_end = NumericProperty(8)

    def __init__(self, **kwargs):
        super(MDSpinner, self).__init__(**kwargs)
        self.color = self.theme_cls.primary_color
        self._alpha_anim_in = Animation(_alpha=1, duration=.8, t='out_quad')
        self._alpha_anim_out = Animation(_alpha=0, duration=.3, t='out_quad')
        self._alpha_anim_out.bind(on_complete=self._reset)
        self.theme_cls.bind(primary_color=self._update_color)

        if self.determinate:
            self._start_determinate()
        else:
            self._start_loop()

    def _update_color(self, *args):
        self.color = self.theme_cls.primary_color

    def _start_determinate(self, *args):
        self._alpha_anim_in.start(self)

        _rot_anim = Animation(_rotation_angle=0,
                              duration=self.determinate_time * .7,
                              t='out_quad')
        _rot_anim.start(self)

        _angle_start_anim = Animation(_angle_end=360,
                                      duration=self.determinate_time,
                                      t='in_out_quad')
        _angle_start_anim.bind(on_complete=lambda *x: \
            self._alpha_anim_out.start(self))

        _angle_start_anim.start(self)

    def _start_loop(self, *args):
        if self._alpha == 0:
            _rot_anim = Animation(_rotation_angle=0, duration=2, t='linear')
            _rot_anim.start(self)

        self._alpha = 1
        self._alpha_anim_in.start(self)
        _angle_start_anim = Animation(_angle_end=self._angle_end + 270,
                                      duration=.6,
                                      t='in_out_cubic')
        _angle_start_anim.bind(on_complete=self._anim_back)
        _angle_start_anim.start(self)

    def _anim_back(self, *args):
        _angle_back_anim = Animation(_angle_start=self._angle_end - 8,
                                     duration=.6,
                                     t='in_out_cubic')
        _angle_back_anim.bind(on_complete=self._start_loop)

        _angle_back_anim.start(self)

    def on__rotation_angle(self, *args):
        if self._rotation_angle == 0:
            self._rotation_angle = 360
            if not self.determinate:
                _rot_anim = Animation(_rotation_angle=0, duration=2)
                _rot_anim.start(self)

    def _reset(self, *args):
        Animation.cancel_all(self, '_angle_start', '_rotation_angle',
                             '_angle_end', '_alpha')
        self._angle_start = 0
        self._angle_end = 8
        self._rotation_angle = 360
        self._alpha = 0
        self.active = False

    def on_active(self, *args):
        if not self.active:
            self._reset()
        else:
            if self.determinate:
                self._start_determinate()
            else:
                self._start_loop()
Exemple #2
0
class IRCController(EventDispatcher):
    server = "irc.quakenet.org"
    port = 6667
    is_connected = BooleanProperty(False)

    def on_is_connected(self, instance, value):
        maingui = App.get_running_app().root
        irc_ids = [id for id in maingui.ids if id.startswith("irc")]
        if value:
            # enable button
            for id in irc_ids:
                maingui.ids[id].on_connected()
            maingui.ids.btn_connectIRC.text = "Disconnect from IRC"
        else:
            # disable button and input
            for id in irc_ids:
                maingui.ids[id].on_disconnected()
            maingui.ids.btn_connectIRC.text = "Connect to IRC"

    def connect(self):
        if self.is_connected:
            return
        self.ircfactory = IRCFactory(self)
        self.connector = reactor.connectTCP(IRCController.server,
                                            IRCController.port,
                                            self.ircfactory)

    def disconnect(self):
        if not self.is_connected:
            return
        self.ircfactory.client.quit()
        self.connector.disconnect()
        self.is_connected = False
        for channel in self.ircfactory.client.joined_channels:
            self.get_irc_widget(channel).ids.btn_join_part.text = "Join"
        self.ircfactory = None

    def toggle_connection(self):
        """
        Toggle the connection to the Quakenet IRC servers
        """
        if not self.is_connected:
            self.connect()
            app = App.get_running_app()
            app.root.ids.btn_connectIRC.text = "Connecting to IRC"
        else:
            self.disconnect()

    def join_or_part(self, channel):
        if not self.is_connected:
            return
        if channel in self.ircfactory.client.joined_channels:
            self.ircfactory.client.leave(channel)
        else:
            self.ircfactory.client.join(channel)

    def get_irc_widget(self, channel):
        maingui = App.get_running_app().root
        irc_ids = [id for id in maingui.ids if id.startswith("irc")]
        for id in irc_ids:
            if maingui.ids[id].channel == channel:
                return maingui.ids[id]
Exemple #3
0
class UserSelector(Button):
    """
    Show and select connected users
    """
    is_active = BooleanProperty(False)
    users_dropdown = ObjectProperty(None)
    usernames = ListProperty()
    selected_username = StringProperty()
    _connection = ObjectProperty(None)
    _update_event = ObjectProperty(None)
    _updating_users = BooleanProperty(False)
    _new_user_selected = BooleanProperty(False)
    _selecting_user = BooleanProperty(False)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._app = App.get_running_app()
        self.users_dropdown = DropDown(on_select=self._select_user)
        print("constructor called")

    def _select_user(self, _, username):
        """
        Select a user
        :param username: The username to select
        """
        self.selected_username = username
        self._new_user_selected = True
        Logger.info(f"User selector:Selected partner: {username}")

    def _check_for_usernames_response(self):
        return self._connection.socket.recv(block=False)

    @staticmethod
    def _handle_usernames_response(response):
        """
        parse the usernames from the server response
        :param response: The response the server sent
        :return: A list of the usernames the server sent
        """
        usernames = response.get_content_as_text().split(", ")
        Logger.debug(f"User selector:Received usernames: {usernames}")
        return usernames

    def _update_dropdown(self, usernames):
        """
        Update the list of the usernames the dropdown displays
        :param usernames: The usernames to display
        """
        self.users_dropdown.clear_widgets()
        for username in usernames:
            self.users_dropdown.add_widget(
                Button(size_hint_y=None,
                       size=self.size,
                       text=username,
                       on_release=lambda button: self.users_dropdown.select(
                           button.text)))

    def _check_for_select_response(self):
        return self._connection.socket.recv(block=False)

    def _handle_select_response(self, response):
        pass

    def _finnish_selecting(self):
        self._app.partner = self.selected_username

    def _send_usernames_request(self):
        Logger.debug("User selector:Requesting usernames")
        self._connection.socket.send(
            Message(MESSAGE_TYPES["server interaction"],
                    "get all connected usernames"))

    def _send_select_request(self):
        Logger.debug(
            f"User selector:Sending set partner: {self.selected_username}")
        self._connection.socket.send(
            Message(MESSAGE_TYPES["server interaction"],
                    f"set partner\n{self.selected_username}"))

    def _update_users(self, _):
        try:
            if self._updating_users:
                usernames_response = self._check_for_usernames_response()
                if usernames_response is not None:
                    usernames = UserSelector._handle_usernames_response(
                        usernames_response)
                    self._update_dropdown(usernames)
                    self._updating_users = False
            elif self._selecting_user:
                select_response = self._check_for_select_response()
                if select_response is not None:
                    self._handle_select_response(select_response)
                    self._finnish_selecting()
                    self._selecting_user = False
            elif self._new_user_selected:
                self._send_select_request()
                self._selecting_user = True
                self._new_user_selected = False
            else:
                self._send_usernames_request()
                self._updating_users = True
        except ConnectionClosed:  # Unexpected close
            Logger.error("User selector:Unexpected close, closing!")
            self.close()
        except Exception as e:  # TODO: be more specific
            print(e)
            Logger.error("User selector:Unexpected error, closing!",
                         exc_info=True)
            self.close()

    def on_release(self):
        """
        Open the dropdown
        """
        Logger.debug("User selector:Attempting to open dropdown")
        if self.is_active:
            Logger.debug("User selector:Opening dropdown")
            self.users_dropdown.open(self)
        else:
            Logger.debug("User selector:Did not open dropdown "
                         "since it is closed or had not started")

    def start(self, connection):
        """
        Start receiving users and displaying them
        :param connection: The connection to get the users with
        """
        Logger.info("User selector:Starting")
        self.is_active = True
        self._connection = connection
        self._update_event = Clock.schedule_interval(
            self._update_users, UPDATE_USERS_REFRESH_RATE)

    def close(self):
        """
        Close the users getter
        Only call this after opening
        """
        Logger.info("User selector:Closing")
        self.is_active = False
        if self._update_event is not None:
            self._update_event.cancel()
Exemple #4
0
class MDFileManager(ThemableBehavior, MDRelativeLayout):
    icon = StringProperty("check")
    """
    The icon that will be used on the directory selection button.

    :attr:`icon` is an :class:`~kivy.properties.StringProperty`
    and defaults to `check`.
    """

    icon_folder = StringProperty(f"{images_path}folder.png")
    """
    The icon that will be used for folder icons when using ``preview = True``.

    :attr:`icon` is an :class:`~kivy.properties.StringProperty`
    and defaults to `check`.
    """

    exit_manager = ObjectProperty(lambda x: None)
    """
    Function called when the user reaches directory tree root.

    :attr:`exit_manager` is an :class:`~kivy.properties.ObjectProperty`
    and defaults to `lambda x: None`.
    """

    select_path = ObjectProperty(lambda x: None)
    """
    Function, called when selecting a file/directory.

    :attr:`select_path` is an :class:`~kivy.properties.ObjectProperty`
    and defaults to `lambda x: None`.
    """

    ext = ListProperty()
    """
    List of file extensions to be displayed in the manager.
    For example, `['.py', '.kv']` - will filter out all files,
    except python scripts and Kv Language.

    :attr:`ext` is an :class:`~kivy.properties.ListProperty`
    and defaults to `[]`.
    """

    search = OptionProperty("all", options=["all", "dirs", "files"])
    """
    It can take the values 'all' 'dirs' 'files' - display only directories
    or only files or both them. By default, it displays folders, and files.
    Available options are: `'all'`, `'dirs'`, `'files'`.

    :attr:`search` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `all`.
    """

    current_path = StringProperty(os.getcwd())
    """
    Current directory.

    :attr:`current_path` is an :class:`~kivy.properties.StringProperty`
    and defaults to `/`.
    """

    use_access = BooleanProperty(True)
    """
    Show access to files and directories.

    :attr:`use_access` is an :class:`~kivy.properties.BooleanProperty`
    and defaults to `True`.
    """

    preview = BooleanProperty(False)
    """
    Shows only image previews.

    :attr:`preview` is an :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    show_hidden_files = BooleanProperty(False)
    """
    Shows hidden files.

    :attr:`show_hidden_files` is an :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    sort_by = OptionProperty(
        "name", options=["nothing", "name", "date", "size", "type"])
    """
    It can take the values 'nothing' 'name' 'date' 'size' 'type' - sorts files by option
    By default, sort by name.
    Available options are: `'nothing'`, `'name'`, `'date'`, `'size'`, `'type'`.

    :attr:`sort_by` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `name`.
    """

    sort_by_desc = BooleanProperty(False)
    """
    Sort by descending.

    :attr:`sort_by_desc` is an :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    selector = OptionProperty("any",
                              options=["any", "file", "folder", "multi"])
    """
    It can take the values 'any' 'file' 'folder' 'multi'
    By default, any.
    Available options are: `'any'`, `'file'`, `'folder'`, `'multi'`.

    :attr:`selector` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `any`.
    """

    selection = ListProperty()
    """
    Contains the list of files that are currently selected.

    :attr:`selection` is a read-only :class:`~kivy.properties.ListProperty` and
    defaults to `[]`.
    """

    _window_manager = None
    _window_manager_open = False

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        toolbar_label = self.ids.toolbar.children[1].children[0]
        toolbar_label.font_style = "Subtitle1"
        if (self.selector == "any" or self.selector == "multi"
                or self.selector == "folder"):
            self.add_widget(
                FloatButton(
                    callback=self.select_directory_on_press_button,
                    md_bg_color=self.theme_cls.primary_color,
                    icon=self.icon,
                ))

        if self.preview:
            self.ext = [".png", ".jpg", ".jpeg"]
        self.disks = []

    def show_disks(self) -> None:
        if platform == "win":
            self.disks = sorted(
                re.findall(
                    r"[A-Z]+:.*$",
                    os.popen("mountvol /").read(),
                    re.MULTILINE,
                ))
        elif platform in ["linux", "android"]:
            self.disks = sorted(
                re.findall(
                    r"on\s(/.*)\stype",
                    os.popen("mount").read(),
                ))
        elif platform == "macosx":
            self.disks = sorted(
                re.findall(
                    r"on\s(/.*)\s\(",
                    os.popen("mount").read(),
                ))
        else:
            return

        self.current_path = ""
        manager_list = []

        for disk in self.disks:
            access_string = self.get_access_string(disk)
            if "r" not in access_string:
                icon = "harddisk-remove"
            else:
                icon = "harddisk"

            manager_list.append({
                "viewclass": "BodyManager",
                "path": disk,
                "icon": icon,
                "dir_or_file_name": disk,
                "events_callback": self.select_dir_or_file,
                "_selected": False,
            })
        self.ids.rv.data = manager_list

        if not self._window_manager:
            self._window_manager = ModalView(size_hint=self.size_hint,
                                             auto_dismiss=False)
            self._window_manager.add_widget(self)
        if not self._window_manager_open:
            self._window_manager.open()
            self._window_manager_open = True

    def show(self, path: str) -> None:
        """
        Forms the body of a directory tree.

        :param path:
            The path to the directory that will be opened in the file manager.
        """

        self.current_path = path
        self.selection = []
        dirs, files = self.get_content()
        manager_list = []

        if dirs == [] and files == []:  # selected directory
            pass
        elif not dirs and not files:  # directory is unavailable
            return

        if self.preview:
            for name_dir in self.__sort_files(dirs):
                manager_list.append({
                    "viewclass": "BodyManagerWithPreview",
                    "path": self.icon_folder,
                    "realpath": os.path.join(path),
                    "type": "folder",
                    "name": name_dir,
                    "events_callback": self.select_dir_or_file,
                    "height": dp(150),
                    "_selected": False,
                })
            for name_file in self.__sort_files(files):
                if (os.path.splitext(os.path.join(path, name_file))[1]
                        in self.ext):
                    manager_list.append({
                        "viewclass": "BodyManagerWithPreview",
                        "path": os.path.join(path, name_file),
                        "name": name_file,
                        "type": "files",
                        "events_callback": self.select_dir_or_file,
                        "height": dp(150),
                        "_selected": False,
                    })
        else:
            for name in self.__sort_files(dirs):
                _path = os.path.join(path, name)
                access_string = self.get_access_string(_path)
                if "r" not in access_string:
                    icon = "folder-lock"
                else:
                    icon = "folder"

                manager_list.append({
                    "viewclass": "BodyManager",
                    "path": _path,
                    "icon": icon,
                    "dir_or_file_name": name,
                    "events_callback": self.select_dir_or_file,
                    "_selected": False,
                })
            for name in self.__sort_files(files):
                if self.ext and os.path.splitext(name)[1] not in self.ext:
                    continue

                manager_list.append({
                    "viewclass": "BodyManager",
                    "path": name,
                    "icon": "file-outline",
                    "dir_or_file_name": os.path.split(name)[1],
                    "events_callback": self.select_dir_or_file,
                    "_selected": False,
                })
        self.ids.rv.data = manager_list

        if not self._window_manager:
            self._window_manager = ModalView(size_hint=self.size_hint,
                                             auto_dismiss=False)
            self._window_manager.add_widget(self)
        if not self._window_manager_open:
            self._window_manager.open()
            self._window_manager_open = True

    def get_access_string(self, path: str) -> str:
        access_string = ""
        if self.use_access:
            access_data = {"r": os.R_OK, "w": os.W_OK, "x": os.X_OK}
            for access in access_data.keys():
                access_string += (access if os.access(
                    path, access_data[access]) else "-")
        return access_string

    def get_content(
        self, ) -> Union[Tuple[List[str], List[str]], Tuple[None, None]]:
        """Returns a list of the type [[Folder List], [file list]]."""

        try:
            files = []
            dirs = []

            for content in os.listdir(self.current_path):
                if os.path.isdir(os.path.join(self.current_path, content)):
                    if self.search == "all" or self.search == "dirs":
                        if (not self.show_hidden_files) and (
                                content.startswith(".")):
                            continue
                        else:
                            dirs.append(content)

                else:
                    if self.search == "all" or self.search == "files":
                        if len(self.ext) != 0:
                            try:
                                files.append(
                                    os.path.join(self.current_path, content))
                            except IndexError:
                                pass
                        else:
                            if (not self.show_hidden_files
                                    and content.startswith(".")):
                                continue
                            else:
                                files.append(content)

            return dirs, files

        except OSError:
            return None, None

    def close(self) -> None:
        """Closes the file manager window."""

        self._window_manager.dismiss()
        self._window_manager_open = False

    def select_dir_or_file(
        self,
        path: str,
        widget: Union[BodyManagerWithPreview, Factory.BodyManager],
    ):
        """Called by tap on the name of the directory or file."""

        if os.path.isfile(os.path.join(self.current_path, path)):
            if self.selector == "multi":
                file_path = os.path.join(self.current_path, path)
                if file_path in self.selection:
                    widget._selected = False
                    self.selection.remove(file_path)
                else:
                    widget._selected = True
                    self.selection.append(file_path)
            elif self.selector == "folder":
                return
            else:
                self.select_path(os.path.join(self.current_path, path))

        else:
            self.current_path = path
            self.show(path)

    def back(self) -> None:
        """Returning to the branch down in the directory tree."""

        path, end = os.path.split(self.current_path)

        if self.current_path and path == self.current_path:
            self.show_disks()
        else:
            if not end:
                self.close()
                self.exit_manager(1)
            else:
                self.show(path)

    def select_directory_on_press_button(self, *args) -> None:
        """Called when a click on a floating button."""

        if self.selector == "multi":
            if len(self.selection) > 0:
                self.select_path(self.selection)
        else:
            if self.selector == "folder" or self.selector == "any":
                self.select_path(self.current_path)

    def __sort_files(self, files):
        def sort_by_name(files):
            files.sort(key=locale.strxfrm)
            files.sort(key=str.casefold)
            return files

        if self.sort_by == "name":
            sorted_files = sort_by_name(files)
        elif self.sort_by == "date":
            _files = sort_by_name(files)
            _sorted_files = [
                os.path.join(self.current_path, f) for f in _files
            ]
            _sorted_files.sort(key=os.path.getmtime, reverse=True)
            sorted_files = [os.path.basename(f) for f in _sorted_files]
        elif self.sort_by == "size":
            _files = sort_by_name(files)
            _sorted_files = [
                os.path.join(self.current_path, f) for f in _files
            ]
            _sorted_files.sort(key=os.path.getsize, reverse=True)
            sorted_files = [os.path.basename(f) for f in _sorted_files]
        elif self.sort_by == "type":
            _files = sort_by_name(files)
            sorted_files = sorted(
                _files,
                key=lambda f: (os.path.splitext(f)[1], os.path.splitext(f)[0]),
            )
        else:
            sorted_files = files

        if self.sort_by_desc:
            sorted_files.reverse()

        return sorted_files
class Ponerine(BoxLayout):
    version = StringProperty(__version__)
    JsonData = {}
    token = NumericProperty()
    Jsoncounter = NumericProperty(0)
    Jsonflip = NumericProperty(0)
    connected = BooleanProperty()
    camconfig = {}

    def CamConnect(self):
        #try:
        self.camaddr = "192.168.42.1"
        self.camport = 7878
        self.camdataport = 8787
        self.camwebport = 80
        self.token = 0
        print self.camaddr, self.camport, self.camdataport, self.camwebport
        socket.setdefaulttimeout(5)
        self.srv = socket.socket(socket.AF_INET,
                                 socket.SOCK_STREAM)  #create socket
        self.srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.srv.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
        self.srv.connect((self.camaddr, self.camport))  #open socket
        self.thread_read = threading.Thread(target=self.JsonReader)
        self.thread_read.setDaemon(True)
        self.thread_read.setName('JsonReader')
        self.thread_read.start()
        waiter = 0
        #self.token = ""
        while 1:
            if self.connected:
                print "self.token before empty", self.token
                if self.token == 0:
                    print "self.token empty"
                    break
                self.UpdateUsage()
                self.UpdateBattery()
                self.ReadConfig()
                self.SDOK = True
                if self.camconfig[
                        "sd_card_status"] == "insert" and self.totalspace > 0:
                    if self.camconfig["sdcard_need_format"] != "no-need":
                        if not self.ActionForceFormat():
                            self.SDOK = False
                            print "SD memory card not formatted!"
                else:
                    self.SDOK = False
                    print "No SD memory card inserted in camera!"

                if self.SDOK == True:
                    print "SD Usage: "
                    print "Battery: "
                break
            else:
                if waiter <= 5:
                    time.sleep(1)
                    waiter += 1
                else:
                    raise Exception('Connection',
                                    'failed')  #throw an exception

    #except Exception as e:
    #self.connected = False
    #print "Connect Error:", "Cannot connect to the address specified"
    #self.srv.close()

    def JsonReader(self):
        self.JsonData = {}
        self.Jsoncounter = 0
        self.Jsonflip = 0
        initcounter = 0
        self.srv.send('{"msg_id":257,"token":0}')  #auth to the camera
        while initcounter < 300:
            self.JsonLoop()
            initcounter += 1
            if len(self.JsonData) > 0:
                break
        if len(self.JsonData) > 0:
            self.srv.setblocking(0)
            self.connected = True
            while self.connected:
                self.JsonLoop()

    def JsonLoop(self):
        #try:
        ready = select.select([self.srv], [], [])
        if ready[0]:
            byte = self.srv.recv(1)
            if byte == "{":
                self.Jsoncounter += 1
                self.Jsonflip = 1
            elif byte == "}":
                self.Jsoncounter -= 1
            self.JsonData += byte

            if self.Jsonflip == 1 and self.Jsoncounter == 0:
                #     try:
                data_dec = json.loads(self.JsonData)
                print "one piece json:", data_dec
                self.JsonData = {}
                self.Jsonflip = 0
                if "msg_id" in data_dec.keys():
                    print "msg_id:", data_dec["msg_id"]
                    if data_dec["msg_id"] == 257:
                        print "param:", data_dec["param"]
                        self.token = data_dec["param"]
                        print "Loop token:", self.token
                    elif data_dec["msg_id"] == 7:
                        if "type" in data_dec.keys(
                        ) and "param" in data_dec.keys():
                            if data_dec["type"] == "battery":
                                self.thread_Battery = threading.Thread(
                                    target=self.UpdateBattery)
                                self.thread_Battery.setName('UpdateBattery')
                                self.thread_Battery.start()
                            elif data_dec["type"] == "start_photo_capture":
                                if self.camconfig[
                                        "capture_mode"] == "precise quality cont.":
                                    self.bphoto.config(text="Stop\nTIMELAPSE",
                                                       bg="#ff6666")
                                    self.brecord.config(state=DISABLED)
                                    self.brecord.update_idletasks()
                                    self.bphoto.update_idletasks()
                                    self.thread_ReadConfig = threading.Thread(
                                        target=self.ReadConfig)
                                    self.thread_ReadConfig.setDaemon(True)
                                    self.thread_ReadConfig.setName(
                                        'ReadConfig')
                                    self.thread_ReadConfig.start()
                            elif data_dec["type"] == "precise_cont_complete":
                                if self.camconfig[
                                        "capture_mode"] == "precise quality cont.":
                                    self.bphoto.config(text="Start\nTIMELAPSE",
                                                       bg="#66ff66")
                                    self.brecord.config(state="normal")
                                    self.brecord.update_idletasks()
                                    self.bphoto.update_idletasks()
                                    self.thread_ReadConfig = threading.Thread(
                                        target=self.ReadConfig)
                                    self.thread_ReadConfig.setDaemon(True)
                                    self.thread_ReadConfig.setName(
                                        'ReadConfig')
                                    self.thread_ReadConfig.start()
                    self.JsonData[data_dec["msg_id"]] = data_dec
                else:
                    raise Exception('Unknown', 'data')
        #    except Exception as e:
        #     print data, e

    #except Exception:
    # self.connected = False

    def ReadConfig(self):
        tosend = '{"msg_id":3,"token":%s}' % self.token
        resp = self.Comm(tosend)
        self.camconfig = {}
        for each in resp["param"]:
            self.camconfig.update(each)

    def Comm(self, tosend):
        Jtosend = json.loads(tosend)
        msgid = Jtosend["msg_id"]
        self.JsonData[msgid] = ""
        self.srv.send(tosend)
        while self.JsonData[msgid] == "":
            continue
        #wrong token, ackquire new one & resend - "workaround" for camera insisting on tokens
        if self.JsonData[msgid]["rval"] == -4:
            self.token = ""
            self.srv.send('{"msg_id":257,"token":0}')
            while self.token == "":
                continue
            Jtosend["token"] = self.token
            tosend = json.dumps(Jtosend)
            self.JsonData[msgid] = ""
            self.srv.send(tosend)
            while self.JsonData[msgid] == "":
                continue
        return self.JsonData[msgid]
Exemple #6
0
class ShinobiMonitor(BoxLayout):
    serverURL = StringProperty()
    monitorPath = StringProperty()
    loading = BooleanProperty(False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._data = {}
        self._state = 'stop'
        self._update_event = None
        self.image = Image(allow_stretch=True, keep_ratio=False)
        self.loading_image = Image(source='data/images/image-loading.gif',
                                   allow_stretch=False,
                                   keep_ratio=True)

    def on_loading(self, instnace, value):
        if value:
            self.remove_widget(self.image)
            self.add_widget(self.loading_image)
        else:
            self.add_widget(self.image)
            self.remove_widget(self.loading_image)

    @property
    def snapshot_url(self):
        return '%s%s' % (self.serverURL.rstrip('/'), self._data['snapshot'])

    @property
    def state(self):
        return self._state

    def start(self):
        self.loading = True
        self._state = 'play'
        self._image_lock = threading.Lock()
        self._image_buffer = None
        self.image_thread = threading.Thread(target=self._fetch_image,
                                             daemon=True)
        self.meta_thread = threading.Thread(target=self._fetch_metadata,
                                            daemon=True)
        self.meta_thread.start()

    def stop(self):
        self._state = 'stop'
        self.loading = False
        Clock.unschedule(self._update_event)

    def _fetch_metadata(self):
        while self.state == 'play':
            try:
                response = requests.get(
                    '%s/%s' %
                    (self.serverURL.rstrip('/'), self.monitorPath.strip('/')),
                    timeout=10)
            except:
                time.sleep(1)
                continue
            if response.status_code == 200:
                self._data = response.json()
                self.image_thread.start()
                self._update_event = Clock.schedule_interval(
                    self._update_image, 1)
                break

    def _fetch_image(self):
        while self.state == 'play':
            try:
                response = requests.get(self.snapshot_url, timeout=0.5)
            except:
                time.sleep(0.5)
                self.loading = True
                continue
            if response.status_code == 200:
                data = BytesIO(response.content)
                im = CoreImage(data, ext="jpeg", nocache=True)
                with self._image_lock:
                    self._image_buffer = im
                if self.loading:
                    self.loading = False
            time.sleep(1)

    def _update_image(self, *args):
        im = None
        with self._image_lock:
            im = self._image_buffer
            self._image_buffer = None
        if im is not None:
            self.image.texture = im.texture
            self.image.texture_size = im.texture.size
Exemple #7
0
class ReceiveScreen(Screen):
    connect_button_disabled = BooleanProperty(False)
    connect_button_text = StringProperty('connect')

    accept_button_disabled = BooleanProperty(True)
    accept_button_func = ObjectProperty(None)
    accept_button_text = StringProperty('waiting for offer')

    file_name = StringProperty('…')
    file_size = StringProperty('…')

    def on_pre_enter(self):
        """
        Called just before the user enters this screen.
        """
        self.downloads_dir = get_downloads_dir()

        self.connect_button_disabled = False
        self.connect_button_text = 'connect'

        self.accept_button_disabled = True
        self.accept_button_func = self.accept_offer
        self.accept_button_text = 'waiting for offer'

        self.file_name = '…'
        self.file_size = '…'

        self.ids.code_input.text = ''

    def open_wormhole(self):
        """
        Called when the user releases the connect button.
        """
        code = self.ids.code_input.text.strip()
        code = '-'.join(code.split())
        if not code:
            return ErrorPopup.show('Please enter a code.')

        def connect():
            self.connect_button_disabled = True
            self.connect_button_text = 'connecting'

            self.wormhole = Wormhole()

            deferred = self.wormhole.connect(code)
            deferred.addCallbacks(exchange_keys, ErrorPopup.show)

        def exchange_keys(code):
            self.connect_button_disabled = True
            self.connect_button_text = 'exchanging keys'

            deferred = self.wormhole.exchange_keys()
            deferred.addCallbacks(await_offer, ErrorPopup.show)

        def await_offer(verifier):
            self.connect_button_disabled = True
            self.connect_button_text = 'connected'

            deferred = self.wormhole.await_offer()
            deferred.addCallbacks(show_offer, ErrorPopup.show)

        def show_offer(offer):
            self.file_name = str(offer['filename'])
            self.file_size = humanize.naturalsize(offer['filesize'])

            self.accept_button_disabled = False
            self.accept_button_text = 'accept'

        connect()

    def accept_offer(self):
        """
        Called when the user releases the accept button.
        """
        file_path = os.path.join(self.downloads_dir, self.file_name)

        def show_error():
            ErrorPopup.show(
                ('You cannot receive a file if the app cannot write it.'))

        @ensure_storage_perms(show_error)
        def accept_offer():
            self.accept_button_disabled = True
            self.accept_button_text = 'receiving'

            deferred = self.wormhole.accept_offer(file_path)
            deferred.addCallbacks(show_done, ErrorPopup.show)

        def show_done(hex_digest):
            self.accept_button_disabled = False
            self.accept_button_func = self.open_file
            self.accept_button_text = 'open file'

        accept_offer()

    def open_file(self):
        """
        Called when the user presses the accept button after the file transfer
        has been completed.
        """
        file_path = os.path.join(self.downloads_dir, self.file_name)

        def show_error():
            ErrorPopup.show('Cannot open {}'.format(file_path))

        @ensure_storage_perms(show_error)
        def do():
            open_file(file_path)

        if os.path.exists(file_path):
            do()
        else:
            show_error()

    def on_leave(self):
        """
        Called when the user leaves this screen.
        """
        try:
            self.wormhole.close()
        except AttributeError:
            pass
Exemple #8
0
class ActionGroup(ActionItem, Spinner):
    '''ActionGroup class, see module documentation for more information.
    '''

    use_separator = BooleanProperty(False)
    '''Specifies whether to use a separator after/before this group or not.

       :attr:`use_separator` is a :class:`~kivy.properties.BooleanProperty` and
       defaults to False.
    '''

    separator_image = StringProperty(
        'atlas://data/images/defaulttheme/separator')
    '''Background Image for an ActionSeparator in an ActionView.

       :attr:`separator_image` is a :class:`~kivy.properties.StringProperty`
       and defaults to 'atlas://data/images/defaulttheme/separator'.
    '''

    separator_width = NumericProperty(0)
    '''Width of the ActionSeparator in an ActionView.

       :attr:`separator_width` is a :class:`~kivy.properties.NumericProperty`
       and defaults to 0.
    '''

    mode = OptionProperty('normal', options=('normal', 'spinner'))
    '''Sets the current mode of an ActionGroup. If mode is 'normal', the
       ActionGroups children will be displayed normally if there is enough
       space, otherwise they will be displayed in a spinner. If mode is
       'spinner', then the children will always be displayed in a spinner.

       :attr:`mode` is a :class:`~kivy.properties.OptionProperty` and
       defaults to 'normal'.
    '''

    dropdown_width = NumericProperty(0)
    '''If non zero, provides the width for the associated DropDown. This is
    useful when some items in the ActionGroup's DropDown are wider than usual
    and you don't want to make the ActionGroup widget itself wider.

    :attr:`dropdown_width` is an :class:`~kivy.properties.NumericProperty`
    and defaults to 0.

    .. versionadded:: 1.9.2
    '''
    def __init__(self, **kwargs):
        self.list_action_item = []
        self._list_overflow_items = []
        super(ActionGroup, self).__init__(**kwargs)
        self.dropdown_cls = ActionDropDown

    def add_widget(self, item):
        if isinstance(item, ActionSeparator):
            super(ActionGroup, self).add_widget(item)
            return

        if not isinstance(item, ActionItem):
            raise ActionBarException('ActionGroup only accepts ActionItem')

        self.list_action_item.append(item)

    def show_group(self):
        self.clear_widgets()
        for item in self._list_overflow_items + self.list_action_item:
            item.inside_group = True
            self._dropdown.add_widget(item)

    def _build_dropdown(self, *largs):
        if self._dropdown:
            self._dropdown.unbind(on_dismiss=self._toggle_dropdown)
            self._dropdown.dismiss()
            self._dropdown = None
        self._dropdown = self.dropdown_cls()
        self._dropdown.bind(on_dismiss=self._toggle_dropdown)

    def _update_dropdown(self, *largs):
        pass

    def _toggle_dropdown(self, *largs):
        self.is_open = not self.is_open
        ddn = self._dropdown
        ddn.size_hint_x = None
        if not ddn.container:
            return
        children = ddn.container.children

        if children:
            ddn.width = self.dropdown_width or max(
                self.width, max(c.pack_width for c in children))
        else:
            ddn.width = self.width

        for item in children:
            item.size_hint_y = None
            item.height = max([self.height, sp(48)])

    def clear_widgets(self):
        self._dropdown.clear_widgets()
Exemple #9
0
class ActionView(BoxLayout):
    '''ActionView class, see module documentation for more information.
    '''

    action_previous = ObjectProperty(None)
    '''Previous button for an ActionView.

       :attr:`action_previous` is an :class:`~kivy.properties.ObjectProperty`
       and defaults to None.
    '''

    background_color = ListProperty([1, 1, 1, 1])
    '''Background color in the format (r, g, b, a).

       :attr:`background_color` is a :class:`~kivy.properties.ListProperty` and
       defaults to [1, 1, 1, 1].
    '''

    background_image = StringProperty(
        'atlas://data/images/defaulttheme/action_view')
    '''Background image of an ActionViews default graphical representation.

       :attr:`background_image` is an :class:`~kivy.properties.StringProperty`
       and defaults to 'atlas://data/images/defaulttheme/action_view'.
    '''

    use_separator = BooleanProperty(False)
    '''Specify whether to use a separator before every ActionGroup or not.

       :attr:`use_separator` is a :class:`~kivy.properties.BooleanProperty` and
       defaults to False.
    '''

    overflow_group = ObjectProperty(None)
    '''Widget to be used for the overflow.

       :attr:`overflow_group` is an :class:`~kivy.properties.ObjectProperty`
       and defaults to an instance of :class:`ActionOverflow`.
    '''
    def __init__(self, **kwargs):
        self._list_action_items = []
        self._list_action_group = []
        super(ActionView, self).__init__(**kwargs)
        self._state = ''
        if not self.overflow_group:
            self.overflow_group = ActionOverflow(
                use_separator=self.use_separator)

    def on_action_previous(self, instance, value):
        self._list_action_items.insert(0, value)

    def add_widget(self, action_item, index=0):
        if action_item is None:
            return

        if not isinstance(action_item, ActionItem):
            raise ActionBarException('ActionView only accepts ActionItem'
                                     ' (got {!r}'.format(action_item))

        elif isinstance(action_item, ActionOverflow):
            self.overflow_group = action_item
            action_item.use_separator = self.use_separator

        elif isinstance(action_item, ActionGroup):
            self._list_action_group.append(action_item)
            action_item.use_separator = self.use_separator

        elif isinstance(action_item, ActionPrevious):
            self.action_previous = action_item

        else:
            super(ActionView, self).add_widget(action_item, index)
            if index == 0:
                index = len(self._list_action_items)
            self._list_action_items.insert(index, action_item)

    def on_use_separator(self, instance, value):
        for group in self._list_action_group:
            group.use_separator = value
        self.overflow_group.use_separator = value

    def remove_widget(self, widget):
        super(ActionView, self).remove_widget(widget)
        if isinstance(widget, ActionOverflow):
            for item in widget.list_action_item:
                self._list_action_items.remove(item)

        if widget in self._list_action_items:
            self._list_action_items.remove(widget)

    def _clear_all(self):
        lst = self._list_action_items[:]
        self.clear_widgets()
        for group in self._list_action_group:
            group.clear_widgets()

        self.overflow_group.clear_widgets()
        self.overflow_group.list_action_item = []
        self._list_action_items = lst

    def _layout_all(self):
        # all the items can fit to the view, so expand everything
        super_add = super(ActionView, self).add_widget
        self._state = 'all'
        self._clear_all()
        if not self.action_previous.parent:
            super_add(self.action_previous)
        if len(self._list_action_items) > 1:
            for child in self._list_action_items[1:]:
                child.inside_group = False
                super_add(child)

        for group in self._list_action_group:
            if group.mode == 'spinner':
                super_add(group)
                group.show_group()
            else:
                if group.list_action_item != []:
                    super_add(ActionSeparator())
                for child in group.list_action_item:
                    child.inside_group = False
                    super_add(child)

        self.overflow_group.show_default_items(self)

    def _layout_group(self):
        # layout all the items in order to pack them per group
        super_add = super(ActionView, self).add_widget
        self._state = 'group'
        self._clear_all()
        if not self.action_previous.parent:
            super_add(self.action_previous)
        if len(self._list_action_items) > 1:
            for child in self._list_action_items[1:]:
                super_add(child)
                child.inside_group = False

        for group in self._list_action_group:
            super_add(group)
            group.show_group()

        self.overflow_group.show_default_items(self)

    def _layout_random(self):
        # layout the items in order to pack all of them grouped, and display
        # only the action items having 'important'
        super_add = super(ActionView, self).add_widget
        self._state = 'random'
        self._clear_all()
        hidden_items = []
        hidden_groups = []
        total_width = 0
        if not self.action_previous.parent:
            super_add(self.action_previous)

        width = (self.width - self.overflow_group.pack_width -
                 self.action_previous.minimum_width)

        if len(self._list_action_items):
            for child in self._list_action_items[1:]:
                if child.important:
                    if child.pack_width + total_width < width:
                        super_add(child)
                        child.inside_group = False
                        total_width += child.pack_width
                    else:
                        hidden_items.append(child)
                else:
                    hidden_items.append(child)

        # if space is left then display ActionItem inside their
        # ActionGroup
        if total_width < self.width:
            for group in self._list_action_group:
                if group.pack_width + total_width +\
                        group.separator_width < width:
                    super_add(group)
                    group.show_group()
                    total_width += (group.pack_width + group.separator_width)

                else:
                    hidden_groups.append(group)
        group_index = len(self.children) - 1
        # if space is left then display other ActionItems
        if total_width < self.width:
            for child in hidden_items[:]:
                if child.pack_width + total_width < width:
                    super_add(child, group_index)
                    total_width += child.pack_width
                    child.inside_group = False
                    hidden_items.remove(child)

        # for all the remaining ActionItems and ActionItems with in
        # ActionGroups, Display them inside overflow_group
        extend_hidden = hidden_items.extend
        for group in hidden_groups:
            extend_hidden(group.list_action_item)

        overflow_group = self.overflow_group

        if hidden_items != []:
            over_add = super(overflow_group.__class__,
                             overflow_group).add_widget
            for child in hidden_items:
                over_add(child)

            overflow_group.show_group()
            if not self.overflow_group.parent:
                super_add(overflow_group)

    def on_width(self, width, *args):
        # determine the layout to use

        # can we display all of them?
        total_width = 0
        for child in self._list_action_items:
            total_width += child.pack_width
        for group in self._list_action_group:
            for child in group.list_action_item:
                total_width += child.pack_width
        if total_width <= self.width:
            if self._state != 'all':
                self._layout_all()
            return

        # can we display them per group?
        total_width = 0
        for child in self._list_action_items:
            total_width += child.pack_width
        for group in self._list_action_group:
            total_width += group.pack_width
        if total_width < self.width:
            # ok, we can display all the items grouped
            if self._state != 'group':
                self._layout_group()
            return

        # none of the solutions worked, display them in pack mode
        self._layout_random()
Exemple #10
0
class BaseButton(
    ThemableBehavior,
    ButtonBehavior,
    SpecificBackgroundColorBehavior,
    AnchorLayout,
    Widget,
):
    """
    Abstract base class for all MD buttons. This class handles the button's
    colors (disabled/down colors handled in children classes as those depend on
    type of button) as well as the disabled state.
    """

    theme_text_color = OptionProperty(
        "Primary",
        options=[
            "Primary",
            "Secondary",
            "Hint",
            "Error",
            "Custom",
            "ContrastParentBackground",
        ],
    )
    """
    Button text type. Available options are: (`"Primary"`, `"Secondary"`,
    `"Hint"`, `"Error"`, `"Custom"`, `"ContrastParentBackground"`).

    :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'Primary'`.
    """

    text_color = ListProperty()
    """
    Text color in ``rgba`` format.

    :attr:`text_color` is an :class:`~kivy.properties.ListProperty`
    and defaults to `''`.
    """

    font_name = StringProperty()
    """
    Font name.

    :attr:`font_name` is an :class:`~kivy.properties.StringProperty`
    and defaults to `''`.
    """

    font_size = NumericProperty(14)
    """
    Font size.

    :attr:`font_size` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `14`.
    """

    user_font_size = NumericProperty()
    """Custom font size for :class:`~MDIconButton`.

    :attr:`user_font_size` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `0`.
    """

    md_bg_color_disabled = ListProperty()
    """Color disabled.

    :attr:`md_bg_color_disabled` is an :class:`~kivy.properties.ListProperty`
    and defaults to `[]`.
    """

    line_width = NumericProperty(1)

    opposite_colors = BooleanProperty(False)

    _current_button_color = ListProperty([0.0, 0.0, 0.0, 0.0])
    _current_text_color = ListProperty([1.0, 1.0, 1.0, 1])
    _md_bg_color_down = ListProperty([0.0, 0.0, 0.0, 0.1])
    _md_bg_color_disabled = ListProperty([0.0, 0.0, 0.0, 0.0])

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.theme_cls.bind(primary_palette=self.update_md_bg_color)
        Clock.schedule_once(self.check_current_button_color)

    def update_md_bg_color(self, instance, value):
        """Called when the application color palette changes."""

    def check_current_button_color(self, interval):
        if self.md_bg_color_disabled:
            self._md_bg_color_disabled = self.md_bg_color_disabled
        else:
            self._md_bg_color_disabled = self.theme_cls.disabled_hint_text_color
        self.on_disabled(self, self.disabled)
        if self._current_button_color == [0.0, 0.0, 0.0, 0.0]:
            self._current_button_color = self.md_bg_color

    def on_text_color(self, instance, value):
        if value not in ([0, 0, 0, 0.87], [1.0, 1.0, 1.0, 1]):
            self._current_text_color = value

    def on_md_bg_color(self, instance, value):
        if value != self.theme_cls.primary_color:
            self._current_button_color = value

    def on_disabled(self, instance, value):
        if self.disabled:
            self._current_button_color = self._md_bg_color_disabled
        else:
            self._current_button_color = self.md_bg_color

    def on_font_size(self, instance, value):
        def _on_font_size(interval):
            if "lbl_ic" in instance.ids:
                instance.ids.lbl_ic.font_size = sp(value)

        Clock.schedule_once(_on_font_size)
Exemple #11
0
class ActionPrevious(BoxLayout, ActionItem):
    '''ActionPrevious class, see module documentation for more information.
    '''

    with_previous = BooleanProperty(True)
    '''Specifies whether clicking on ActionPrevious will load the previous
       screen or not. If True, the previous_icon will be shown otherwise it
       will not.

       :attr:`with_previous` is a :class:`~kivy.properties.BooleanProperty` and
       defaults to True.
    '''

    app_icon = StringProperty(window_icon)
    '''Application icon for the ActionView.

       :attr:`app_icon` is a :class:`~kivy.properties.StringProperty`
       and defaults to the window icon if set, otherwise
       'data/logo/kivy-icon-32.png'.
    '''

    app_icon_width = NumericProperty(0)
    '''Width of app_icon image.
    '''

    app_icon_height = NumericProperty(0)
    '''Height of app_icon image.
    '''

    color = ListProperty([1, 1, 1, 1])
    '''Text color, in the format (r, g, b, a)

       :attr:`color` is a :class:`~kivy.properties.ListProperty` and defaults
       to [1, 1, 1, 1].
    '''

    previous_image = StringProperty(
        'atlas://data/images/defaulttheme/previous_normal')
    '''Image for the 'previous' ActionButtons default graphical representation.

       :attr:`previous_image` is a :class:`~kivy.properties.StringProperty` and
       defaults to 'atlas://data/images/defaulttheme/previous_normal'.
    '''

    previous_image_width = NumericProperty(0)
    '''Width of previous_image image.
    '''

    previous_image_height = NumericProperty(0)
    '''Height of previous_image image.
    '''

    title = StringProperty('')
    '''Title for ActionView.

       :attr:`title` is a :class:`~kivy.properties.StringProperty` and
       defaults to ''.
    '''
    def __init__(self, **kwargs):
        self.register_event_type('on_press')
        self.register_event_type('on_release')
        super(ActionPrevious, self).__init__(**kwargs)
        if not self.app_icon:
            self.app_icon = 'data/logo/kivy-icon-32.png'

    def on_press(self):
        pass

    def on_release(self):
        pass
Exemple #12
0
class MDFloatingActionButtonSpeedDial(ThemableBehavior, FloatLayout):
    """
    :Events:
        :attr:`on_open`
            Called when a stack is opened.
        :attr:`on_close`
            Called when a stack is closed.
    """

    icon = StringProperty("plus")
    """
    Root button icon name.

    :attr:`icon` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'plus'`.
    """

    anchor = OptionProperty("right", option=["right"])
    """
    Stack anchor. Available options are: `'right'`.

    :attr:`anchor` is a :class:`~kivy.properties.OptionProperty`
    and defaults to `'right'`.
    """

    callback = ObjectProperty(lambda x: None)
    """
    Custom callback.

    .. code-block:: kv

        MDFloatingActionButtonSpeedDial:
            callback: app.callback

    .. code-block:: python

        def callback(self, instance):
            print(instance.icon)


    :attr:`callback` is a :class:`~kivy.properties.ObjectProperty`
    and defaults to `None`.
    """

    label_text_color = ListProperty([0, 0, 0, 1])
    """
    Floating text color in ``rgba`` format.

    :attr:`label_text_color` is a :class:`~kivy.properties.ListProperty`
    and defaults to `[0, 0, 0, 1]`.
    """

    data = DictProperty()
    """
    Must be a dictionary

    .. code-block:: python

        {
            'name-icon': 'Text label',
            ...,
            ...,
        }
    """

    right_pad = BooleanProperty(True)
    """
    If `True`, the button will increase on the right side by 2.5 piesels
    if the :attr:`~hint_animation` parameter equal to `True`.

    .. rubric:: False

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad.gif
        :align: center

    .. rubric:: True

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/MDFloatingActionButtonSpeedDial-right-pad-true.gif
        :align: center

    :attr:`right_pad` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    rotation_root_button = BooleanProperty(False)
    """
    If ``True`` then the root button will rotate 45 degrees when the stack
    is opened.

    :attr:`rotation_root_button` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    opening_transition = StringProperty("out_cubic")
    """
    The name of the stack opening animation type.

    :attr:`opening_transition` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'out_cubic'`.
    """

    closing_transition = StringProperty("out_cubic")
    """
    The name of the stack closing animation type.

    :attr:`closing_transition` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'out_cubic'`.
    """

    opening_transition_button_rotation = StringProperty("out_cubic")
    """
    The name of the animation type to rotate the root button when opening the
    stack.

    :attr:`opening_transition_button_rotation` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'out_cubic'`.
    """

    closing_transition_button_rotation = StringProperty("out_cubic")
    """
    The name of the animation type to rotate the root button when closing the
    stack.

    :attr:`closing_transition_button_rotation` is a :class:`~kivy.properties.StringProperty`
    and defaults to `'out_cubic'`.
    """

    opening_time = NumericProperty(0.5)
    """
    Time required for the stack to go to: attr:`state` `'open'`.

    :attr:`opening_time` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    closing_time = NumericProperty(0.2)
    """
    Time required for the stack to go to: attr:`state` `'close'`.

    :attr:`closing_time` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    opening_time_button_rotation = NumericProperty(0.2)
    """
    Time required to rotate the root button 45 degrees during the stack
    opening animation.

    :attr:`opening_time_button_rotation` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    closing_time_button_rotation = NumericProperty(0.2)
    """
    Time required to rotate the root button 0 degrees during the stack
    closing animation.

    :attr:`closing_time_button_rotation` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.2`.
    """

    state = OptionProperty("close", options=("close", "open"))
    """
    Indicates whether the stack is closed or open.
    Available options are: `'close'`, `'open'`.

    :attr:`state` is a :class:`~kivy.properties.OptionProperty`
    and defaults to `'close'`.
    """

    bg_color_root_button = ListProperty()
    """
    Root button color in ``rgba`` format.

    :attr:`bg_color_root_button` is a :class:`~kivy.properties.ListProperty`
    and defaults to `[]`.
    """

    bg_color_stack_button = ListProperty()
    """
    The color of the buttons in the stack ``rgba`` format.

    :attr:`bg_color_stack_button` is a :class:`~kivy.properties.ListProperty`
    and defaults to `[]`.
    """

    color_icon_stack_button = ListProperty()
    """
    The color icon of the buttons in the stack ``rgba`` format.

    :attr:`color_icon_stack_button` is a :class:`~kivy.properties.ListProperty`
    and defaults to `[]`.
    """

    color_icon_root_button = ListProperty()
    """
    The color icon of the root button ``rgba`` format.

    :attr:`color_icon_root_button` is a :class:`~kivy.properties.ListProperty`
    and defaults to `[]`.
    """

    bg_hint_color = ListProperty()
    """
    Background color for the text of the buttons in the stack ``rgba`` format.

    :attr:`bg_hint_color` is a :class:`~kivy.properties.ListProperty`
    and defaults to `[]`.
    """

    hint_animation = BooleanProperty(False)
    """
    Whether to use button extension animation to display text labels.

    :attr:`hint_animation` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to `False`.
    """

    _label_pos_y_set = False
    _anim_buttons_data = {}
    _anim_labels_data = {}

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.register_event_type("on_open")
        self.register_event_type("on_close")
        Window.bind(on_resize=self._update_pos_buttons)

    def on_open(self, *args):
        """Called when a stack is opened."""

    def on_close(self, *args):
        """Called when a stack is closed."""

    def on_leave(self, instance):
        """Called when the mouse cursor goes outside the button of stack."""

        if self.state == "open":
            for widget in self.children:
                if isinstance(widget, MDFloatingLabel) and self.hint_animation:
                    Animation.cancel_all(widget)
                    if self.data[instance.icon] == widget.text:
                        Animation(
                            _canvas_width=0,
                            _padding_right=0,
                            d=self.opening_time,
                            t=self.opening_transition,
                        ).start(instance)
                        if self.hint_animation:
                            Animation(
                                opacity=0, d=0.1, t=self.opening_transition
                            ).start(widget)
                        break

    def on_enter(self, instance):
        """Called when the mouse cursor is over a button from the stack."""

        if self.state == "open":
            for widget in self.children:
                if isinstance(widget, MDFloatingLabel) and self.hint_animation:
                    widget.elevation = 0
                    if self.data[instance.icon] == widget.text:
                        Animation(
                            _canvas_width=widget.width + dp(24),
                            _padding_right=dp(5) if self.right_pad else 0,
                            d=self.opening_time,
                            t=self.opening_transition,
                        ).start(instance)
                        if self.hint_animation:
                            Animation(
                                opacity=1,
                                d=self.opening_time,
                                t=self.opening_transition,
                            ).start(widget)
                        break

    def on_data(self, instance, value):
        """Creates a stack of buttons."""

        # Bottom buttons.
        for name_icon in self.data.keys():
            bottom_button = MDFloatingBottomButton(
                icon=name_icon,
                on_enter=self.on_enter,
                on_leave=self.on_leave,
                opacity=0,
            )
            bottom_button.bind(
                on_release=lambda x=bottom_button: self.callback(x)
            )
            self.set_pos_bottom_buttons(bottom_button)
            self.add_widget(bottom_button)
            # Labels.
            floating_text = value[name_icon]
            if floating_text:
                label = MDFloatingLabel(text=floating_text, opacity=0)
                label.text_color = self.label_text_color
                self.add_widget(label)
        # Top root button.
        root_button = MDFloatingRootButton(on_release=self.open_stack)
        root_button.icon = self.icon
        self.set_pos_root_button(root_button)
        self.add_widget(root_button)

    def on_icon(self, instance, value):
        self._get_count_widget(MDFloatingRootButton).icon = value

    def on_label_text_color(self, instance, value):
        for widget in self.children:
            if isinstance(widget, MDFloatingLabel):
                widget.text_color = value

    def on_color_icon_stack_button(self, instance, value):
        for widget in self.children:
            if isinstance(widget, MDFloatingBottomButton):
                widget.text_color = value

    def on_hint_animation(self, instance, value):
        for widget in self.children:
            if isinstance(widget, MDFloatingLabel):
                widget.bg_color = (0, 0, 0, 0)

    def on_bg_hint_color(self, instance, value):
        for widget in self.children:
            if isinstance(widget, MDFloatingBottomButton):
                widget._bg_color = value

    def on_color_icon_root_button(self, instance, value):
        self._get_count_widget(MDFloatingRootButton).text_color = value

    def on_bg_color_stack_button(self, instance, value):
        for widget in self.children:
            if isinstance(widget, MDFloatingBottomButton):
                widget.md_bg_color = value

    def on_bg_color_root_button(self, instance, value):
        self._get_count_widget(MDFloatingRootButton).md_bg_color = value

    def set_pos_labels(self, widget):
        """Sets the position of the floating labels."""

        if self.anchor == "right":
            widget.x = Window.width - widget.width - dp(86)

    def set_pos_root_button(self, instance):
        """Sets the position of the root button."""

        if self.anchor == "right":
            instance.y = dp(20)
            instance.x = Window.width - (dp(56) + dp(20))

    def set_pos_bottom_buttons(self, instance):
        """Sets the position of the bottom buttons in a stack."""

        if self.anchor == "right":
            if self.state != "open":
                instance.y = instance.height / 2
            instance.x = Window.width - (instance.height + instance.width / 2)

    def open_stack(self, instance):
        """Opens a button stack."""

        for widget in self.children:
            if isinstance(widget, MDFloatingLabel):
                Animation.cancel_all(widget)

        if self.state != "open":
            y = 0
            label_position = dp(56)
            anim_buttons_data = {}
            anim_labels_data = {}

            for widget in self.children:
                if isinstance(widget, MDFloatingBottomButton):
                    # Sets new button positions.
                    y += dp(56)
                    widget.y = widget.y * 2 + y
                    if not self._anim_buttons_data:
                        anim_buttons_data[widget] = Animation(
                            opacity=1,
                            d=self.opening_time,
                            t=self.opening_transition,
                        )
                elif isinstance(widget, MDFloatingLabel):
                    # Sets new labels positions.
                    label_position += dp(56)
                    # Sets the position of signatures only once.
                    if not self._label_pos_y_set:
                        widget.y = widget.y * 2 + label_position
                        widget.x = Window.width - widget.width - dp(86)
                    if not self._anim_labels_data:
                        anim_labels_data[widget] = Animation(
                            opacity=1, d=self.opening_time
                        )
                elif (
                    isinstance(widget, MDFloatingRootButton)
                    and self.rotation_root_button
                ):
                    # Rotates the root button 45 degrees.
                    Animation(
                        _angle=-45,
                        d=self.opening_time_button_rotation,
                        t=self.opening_transition_button_rotation,
                    ).start(widget)

            if anim_buttons_data:
                self._anim_buttons_data = anim_buttons_data
            if anim_labels_data and not self.hint_animation:
                self._anim_labels_data = anim_labels_data

            self.state = "open"
            self.dispatch("on_open")
            self.do_animation_open_stack(self._anim_buttons_data)
            self.do_animation_open_stack(self._anim_labels_data)
            if not self._label_pos_y_set:
                self._label_pos_y_set = True
        else:
            self.close_stack()

    def do_animation_open_stack(self, anim_data):
        def on_progress(animation, widget, value):
            if value >= 0.1:
                animation_open_stack()

        def animation_open_stack(*args):
            try:
                widget = next(widgets_list)
                animation = anim_data[widget]
                animation.bind(on_progress=on_progress)
                animation.start(widget)
            except StopIteration:
                pass

        widgets_list = iter(list(anim_data.keys()))
        animation_open_stack()

    def close_stack(self):
        """Closes the button stack."""

        for widget in self.children:
            if isinstance(widget, MDFloatingBottomButton):
                Animation(
                    y=widget.height / 2,
                    d=self.closing_time,
                    t=self.closing_transition,
                    opacity=0,
                ).start(widget)
            elif isinstance(widget, MDFloatingLabel):
                Animation(opacity=0, d=0.1).start(widget)
            elif (
                isinstance(widget, MDFloatingRootButton)
                and self.rotation_root_button
            ):
                Animation(
                    _angle=0,
                    d=self.closing_time_button_rotation,
                    t=self.closing_transition_button_rotation,
                ).start(widget)
        self.state = "close"
        self.dispatch("on_close")

    def _update_pos_buttons(self, instance, width, height):
        # Updates button positions when resizing screen.
        for widget in self.children:
            if isinstance(widget, MDFloatingBottomButton):
                self.set_pos_bottom_buttons(widget)
            elif isinstance(widget, MDFloatingRootButton):
                self.set_pos_root_button(widget)
            elif isinstance(widget, MDFloatingLabel):
                self.set_pos_labels(widget)

    def _get_count_widget(self, instance):
        widget = None
        for widget in self.children:
            if isinstance(widget, instance):
                break
        return widget
Exemple #13
0
class MDTextFieldRound(ThemableBehavior, BoxLayout):

    __events__ = ("on_text_validate", "on_text", "on_focus")

    write_tab = BooleanProperty(False)
    """write_tab property of TextInput"""

    input_filter = ObjectProperty(None)
    """input_filter from TextInput"""

    readonly = BooleanProperty(False)
    """readonly property from TextInput"""

    tab_width = NumericProperty(4)
    """tab_width property from TextInput"""

    text_language = StringProperty()
    """text_language property from TextInput"""
    """font related properties from TextInput"""
    font_context = StringProperty()

    font_family = StringProperty()

    font_name = StringProperty("Roboto")

    font_size = NumericProperty(15)

    allow_copy = BooleanProperty(True)
    """Whether copying text from the field is allowed or not"""

    width = NumericProperty(Window.width - dp(100))
    """Text field width."""

    icon_left = StringProperty("email-outline")
    """Left icon."""

    icon_right = StringProperty("email-outline")
    """Right icon."""

    icon_type = OptionProperty("none",
                               options=["right", "left", "all", "without"])
    """Use one (left) or two (left and right) icons in the text field."""

    hint_text = StringProperty()
    """Hint text in the text field."""

    icon_color = ListProperty([1, 1, 1, 1])
    """Color of icons."""

    active_color = ListProperty([1, 1, 1, 0.2])
    """The color of the text field when it is in focus."""

    normal_color = ListProperty([1, 1, 1, 0.5])
    """The color of the text field when it not in focus."""

    foreground_color = ListProperty([1, 1, 1, 1])
    """Text color."""

    hint_text_color = ListProperty([0.5, 0.5, 0.5, 1.0])
    """Text field hint color."""

    cursor_color = ListProperty()
    """Color of cursor"""

    selection_color = ListProperty()
    """Text selection color."""

    icon_callback = ObjectProperty()
    """The function that is called when you click on the icon 
    in the text field."""

    text = StringProperty()
    """Text of field."""

    icon_left_disabled = BooleanProperty(True)
    """Disable the left icon."""

    icon_right_disabled = BooleanProperty(False)
    """Disable the right icon."""

    password = BooleanProperty(False)
    """Hide text or notю"""

    password_mask = StringProperty("*")
    """Characters on which the text will be replaced
    if the `password` is True."""

    require_text_error = StringProperty()
    """Error text if the text field requires mandatory text."""

    require_error_callback = ObjectProperty()
    """The function that will be called when the unfocus.
    if `require_text_error` != ''
    """

    event_focus = ObjectProperty()
    """The function is called at the moment of focus/unfocus of the text field.
    """

    focus = BooleanProperty()
    """Whether or not the widget is focused"""

    error_color = ListProperty(
        [0.7607843137254902, 0.2235294117647059, 0.2549019607843137, 1])

    _current_color = ListProperty()

    _outline_color = ListProperty([0, 0, 0, 0])

    _instance_icon_left = ObjectProperty()

    _instance_icon_right = ObjectProperty()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        if not (len(self.cursor_color)):
            self.cursor_color = self.theme_cls.primary_color
        if not (len(self.selection_color)):
            self.selection_color = self.theme_cls.primary_color
            self.selection_color[3] = 0.75
        self._current_color = self.normal_color

    def on_icon_type(self, instance, value):
        def remove_icon_right():
            self.ids.box.remove_widget(self.ids.icon_right)
            self.add_widget(Widget(size_hint_x=None, width=dp(48)))

        if value == "left":
            remove_icon_right()
        elif value == "right":
            self.ids.box.remove_widget(self.ids.icon_left)
        elif value == "without":
            remove_icon_right()
            self.ids.box.remove_widget(self.ids.icon_left)
            self.add_widget(Widget(size_hint_x=None, width=dp(48)), index=0)

    def on_normal_color(self, instance, value):
        self._current_color = value

    def get_color_line(self, field_inastance, field_text, field_focus):
        if not field_focus:
            if self.require_text_error and field_text == "":
                self._outline_color = self.error_color
                self._instance_icon_left.text_color = self._outline_color
                if self.require_text_error != "":
                    self.show_require_error()
            else:
                self._outline_color = [0, 0, 0, 0]
        else:
            self._outline_color = self.theme_cls.primary_color

    def show_require_error(self):
        self.ids.label_error_require.text = self.require_text_error
        self.ids.spacer.height = "10dp"
        if self.require_error_callback:
            self.require_error_callback(self)

    def hide_require_error(self, focus):
        if focus:
            self.ids.label_error_require.text = ""
            self.ids.spacer.height = 0

    """ TextInput Events"""

    def on_text_validate(self):
        pass

    def on_text(self, *args):
        pass

    def on_focus(self, *args):
        pass
Exemple #14
0
class MDTextField(ThemableBehavior, FixedHintTextInput):
    helper_text = StringProperty("This field is required")
    helper_text_mode = OptionProperty(
        "none", options=["none", "on_error", "persistent", "on_focus"])

    max_text_length = NumericProperty(None)
    required = BooleanProperty(False)

    color_mode = OptionProperty("primary",
                                options=["primary", "accent", "custom"])
    line_color_normal = ListProperty()
    line_color_focus = ListProperty()
    error_color = ListProperty()

    error = BooleanProperty(False)
    _text_len_error = BooleanProperty(False)

    _hint_lbl_font_size = NumericProperty(sp(16))
    _hint_y = NumericProperty(dp(38))
    _line_width = NumericProperty(0)
    _current_line_color = ListProperty([0.0, 0.0, 0.0, 0.0])
    _current_error_color = ListProperty([0.0, 0.0, 0.0, 0.0])
    _current_hint_text_color = ListProperty([0.0, 0.0, 0.0, 0.0])
    _current_right_lbl_color = ListProperty([0.0, 0.0, 0.0, 0.0])

    def __init__(self, **kwargs):
        self._msg_lbl = TextfieldLabel(
            font_style="Caption",
            halign="left",
            valign="middle",
            text=self.helper_text,
        )

        self._right_msg_lbl = TextfieldLabel(font_style="Caption",
                                             halign="right",
                                             valign="middle",
                                             text="")

        self._hint_lbl = TextfieldLabel(font_style="Subtitle1",
                                        halign="left",
                                        valign="middle")
        super().__init__(**kwargs)
        self.line_color_normal = self.theme_cls.divider_color
        self.line_color_focus = self.theme_cls.primary_color
        self.error_color = self.theme_cls.error_color

        self._current_hint_text_color = self.theme_cls.disabled_hint_text_color
        self._current_line_color = self.theme_cls.primary_color

        self.bind(
            helper_text=self._set_msg,
            hint_text=self._set_hint,
            _hint_lbl_font_size=self._hint_lbl.setter("font_size"),
            helper_text_mode=self._set_message_mode,
            max_text_length=self._set_max_text_length,
            text=self.on_text,
        )
        self.theme_cls.bind(
            primary_color=self._update_primary_color,
            theme_style=self._update_theme_style,
            accent_color=self._update_accent_color,
        )
        self.has_had_text = False

    def _update_colors(self, color):
        self.line_color_focus = color
        if not self.error and not self._text_len_error:
            self._current_line_color = color
            if self.focus:
                self._current_line_color = color

    def _update_accent_color(self, *args):
        if self.color_mode == "accent":
            self._update_colors(self.theme_cls.accent_color)

    def _update_primary_color(self, *args):
        if self.color_mode == "primary":
            self._update_colors(self.theme_cls.primary_color)

    def _update_theme_style(self, *args):
        self.line_color_normal = self.theme_cls.divider_color
        if not any([self.error, self._text_len_error]):
            if not self.focus:
                self._current_hint_text_color = (
                    self.theme_cls.disabled_hint_text_color)
                self._current_right_lbl_color = (
                    self.theme_cls.disabled_hint_text_color)
                if self.helper_text_mode == "persistent":
                    self._current_error_color = (
                        self.theme_cls.disabled_hint_text_color)

    def on_width(self, instance, width):
        if (any([self.focus, self.error, self._text_len_error])
                and instance is not None):
            self._line_width = width
        self._msg_lbl.width = self.width
        self._right_msg_lbl.width = self.width
        self._hint_lbl.width = self.width

    def on_focus(self, *args):
        disabled_hint_text_color = self.theme_cls.disabled_hint_text_color
        Animation.cancel_all(self, "_line_width", "_hint_y",
                             "_hint_lbl_font_size")
        if self.max_text_length is None:
            max_text_length = sys.maxsize
        else:
            max_text_length = self.max_text_length
        if len(self.text) > max_text_length or all(
            [self.required,
             len(self.text) == 0, self.has_had_text]):
            self._text_len_error = True
        if self.error or all([
                self.max_text_length is not None
                and len(self.text) > self.max_text_length
        ]):
            has_error = True
        else:
            if all([self.required, len(self.text) == 0, self.has_had_text]):
                has_error = True
            else:
                has_error = False

        if self.focus:
            self.has_had_text = True
            Animation.cancel_all(self, "_line_width", "_hint_y",
                                 "_hint_lbl_font_size")
            if not self.text:
                Animation(
                    _hint_y=dp(14),
                    _hint_lbl_font_size=sp(12),
                    duration=0.2,
                    t="out_quad",
                ).start(self)
            Animation(_line_width=self.width, duration=0.2,
                      t="out_quad").start(self)
            if has_error:
                Animation(
                    duration=0.2,
                    _current_hint_text_color=self.error_color,
                    _current_right_lbl_color=self.error_color,
                    _current_line_color=self.error_color,
                ).start(self)
                if self.helper_text_mode == "on_error" and (
                        self.error or self._text_len_error):
                    Animation(
                        duration=0.2,
                        _current_error_color=self.error_color).start(self)
                elif (self.helper_text_mode == "on_error" and not self.error
                      and not self._text_len_error):
                    Animation(duration=0.2,
                              _current_error_color=(0, 0, 0, 0)).start(self)
                elif self.helper_text_mode == "persistent":
                    Animation(
                        duration=0.2,
                        _current_error_color=disabled_hint_text_color,
                    ).start(self)
                elif self.helper_text_mode == "on_focus":
                    Animation(
                        duration=0.2,
                        _current_error_color=disabled_hint_text_color,
                    ).start(self)
            else:
                Animation(
                    duration=0.2,
                    _current_hint_text_color=self.line_color_focus,
                    _current_right_lbl_color=disabled_hint_text_color,
                ).start(self)
                if self.helper_text_mode == "on_error":
                    Animation(duration=0.2,
                              _current_error_color=(0, 0, 0, 0)).start(self)
                if self.helper_text_mode == "persistent":
                    Animation(
                        duration=0.2,
                        _current_error_color=disabled_hint_text_color,
                    ).start(self)
                elif self.helper_text_mode == "on_focus":
                    Animation(
                        duration=0.2,
                        _current_error_color=disabled_hint_text_color,
                    ).start(self)
        else:
            if not self.text:
                Animation(
                    _hint_y=dp(38),
                    _hint_lbl_font_size=sp(16),
                    duration=0.2,
                    t="out_quad",
                ).start(self)
            if has_error:
                Animation(
                    duration=0.2,
                    _current_line_color=self.error_color,
                    _current_hint_text_color=self.error_color,
                    _current_right_lbl_color=self.error_color,
                ).start(self)
                if self.helper_text_mode == "on_error" and (
                        self.error or self._text_len_error):
                    Animation(
                        duration=0.2,
                        _current_error_color=self.error_color).start(self)
                elif (self.helper_text_mode == "on_error" and not self.error
                      and not self._text_len_error):
                    Animation(duration=0.2,
                              _current_error_color=(0, 0, 0, 0)).start(self)
                elif self.helper_text_mode == "persistent":
                    Animation(
                        duration=0.2,
                        _current_error_color=disabled_hint_text_color,
                    ).start(self)
                elif self.helper_text_mode == "on_focus":
                    Animation(duration=0.2,
                              _current_error_color=(0, 0, 0, 0)).start(self)
            else:
                Animation(
                    duration=0.2,
                    _current_line_color=self.line_color_focus,
                    _current_hint_text_color=disabled_hint_text_color,
                    _current_right_lbl_color=(0, 0, 0, 0),
                ).start(self)
                if self.helper_text_mode == "on_error":
                    Animation(duration=0.2,
                              _current_error_color=(0, 0, 0, 0)).start(self)
                elif self.helper_text_mode == "persistent":
                    Animation(
                        duration=0.2,
                        _current_error_color=disabled_hint_text_color,
                    ).start(self)
                elif self.helper_text_mode == "on_focus":
                    Animation(duration=0.2,
                              _current_error_color=(0, 0, 0, 0)).start(self)
                Animation(_line_width=0, duration=0.2,
                          t="out_quad").start(self)

    def on_text(self, instance, text):
        if len(text) > 0:
            self.has_had_text = True
        if self.max_text_length is not None:
            self._right_msg_lbl.text = f"{len(text)}/{self.max_text_length}"
            max_text_length = self.max_text_length
        else:
            max_text_length = sys.maxsize
        if len(text) > max_text_length or all(
            [self.required,
             len(self.text) == 0, self.has_had_text]):
            self._text_len_error = True
        else:
            self._text_len_error = False
        if self.error or self._text_len_error:
            if self.focus:
                Animation(
                    duration=0.2,
                    _current_hint_text_color=self.error_color,
                    _current_line_color=self.error_color,
                ).start(self)
                if self.helper_text_mode == "on_error" and (
                        self.error or self._text_len_error):
                    Animation(
                        duration=0.2,
                        _current_error_color=self.error_color).start(self)
                if self._text_len_error:
                    Animation(
                        duration=0.2,
                        _current_right_lbl_color=self.error_color).start(self)
        else:
            if self.focus:
                disabled_hint_text_color = (
                    self.theme_cls.disabled_hint_text_color)
                Animation(
                    duration=0.2,
                    _current_right_lbl_color=disabled_hint_text_color,
                ).start(self)
                Animation(
                    duration=0.2,
                    _current_hint_text_color=self.line_color_focus,
                    _current_line_color=self.line_color_focus,
                ).start(self)
                if self.helper_text_mode == "on_error":
                    Animation(duration=0.2,
                              _current_error_color=(0, 0, 0, 0)).start(self)
        if len(self.text) != 0 and not self.focus:
            self._hint_y = dp(14)
            self._hint_lbl_font_size = sp(12)

    def on_text_validate(self):
        self.has_had_text = True
        if self.max_text_length is None:
            max_text_length = sys.maxsize
        else:
            max_text_length = self.max_text_length
        if len(self.text) > max_text_length or all(
            [self.required,
             len(self.text) == 0, self.has_had_text]):
            self._text_len_error = True

    def _set_hint(self, instance, text):
        self._hint_lbl.text = text

    def _set_msg(self, instance, text):
        self._msg_lbl.text = text
        self.helper_text = text

    def _set_message_mode(self, instance, text):
        self.helper_text_mode = text
        if self.helper_text_mode == "persistent":
            disabled_hint_text_color = self.theme_cls.disabled_hint_text_color
            Animation(
                duration=0.1,
                _current_error_color=disabled_hint_text_color).start(self)

    def _set_max_text_length(self, instance, length):
        self.max_text_length = length
        self._right_msg_lbl.text = f"{len(self.text)}/{length}"

    def on_color_mode(self, instance, mode):
        if mode == "primary":
            self._update_primary_color()
        elif mode == "accent":
            self._update_accent_color()
        elif mode == "custom":
            self._update_colors(self.line_color_focus)

    def on_line_color_focus(self, *args):
        if self.color_mode == "custom":
            self._update_colors(self.line_color_focus)
Exemple #15
0
class MDLabel(ThemableBehavior, Label):
    font_style = OptionProperty("Body1", options=theme_font_styles)
    """
    Label font style.
    
    Available options are: `'H1'`, `'H2'`, `'H3'`, `'H4'`, `'H5'`, `'H6'`,
    `'Subtitle1'`, `'Subtitle2'`, `'Body1'`, `'Body2'`, `'Button'`,
    `'Caption'`, `'Overline'`, `'Icon'`.

    :attr:`font_style` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'Body1'`.
    """

    _capitalizing = BooleanProperty(False)

    def _get_text(self):
        if self._capitalizing:
            return self._text.upper()
        return self._text

    def _set_text(self, value):
        self._text = value

    _text = StringProperty()

    text = AliasProperty(_get_text, _set_text, bind=["_text", "_capitalizing"])
    """Text of the label."""

    theme_text_color = OptionProperty(
        "Primary",
        allownone=True,
        options=[
            "Primary",
            "Secondary",
            "Hint",
            "Error",
            "Custom",
            "ContrastParentBackground",
        ],
    )
    """
    Label color scheme name.

    Available options are: `'Primary'`, `'Secondary'`, `'Hint'`, `'Error'`,
    `'Custom'`, `'ContrastParentBackground'`.

    :attr:`theme_text_color` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `None`.
    """

    text_color = ListProperty(None, allownone=True)
    """Label text color in ``rgba`` format.

    :attr:`text_color` is an :class:`~kivy.properties.ListProperty`
    and defaults to `None`.
    """

    parent_background = ListProperty(None, allownone=True)

    _currently_bound_property = {}

    can_capitalize = BooleanProperty(True)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.bind(
            font_style=self.update_font_style,
            can_capitalize=self.update_font_style,
        )
        self.on_theme_text_color(None, self.theme_text_color)
        self.update_font_style()
        self.on_opposite_colors(None, self.opposite_colors)

    def update_font_style(self, *args):
        font_info = self.theme_cls.font_styles[self.font_style]
        self.font_name = font_info[0]
        self.font_size = sp(font_info[1])
        if font_info[2] and self.can_capitalize:
            self._capitalizing = True
        else:
            self._capitalizing = False
        # TODO: Add letter spacing change
        # self.letter_spacing = font_info[3]

    def on_theme_text_color(self, instance, value):
        t = self.theme_cls
        op = self.opposite_colors
        setter = self.setter("color")
        t.unbind(**self._currently_bound_property)
        attr_name = {
            "Primary": "text_color" if not op else "opposite_text_color",
            "Secondary": "secondary_text_color"
            if not op else "opposite_secondary_text_color",
            "Hint": "disabled_hint_text_color"
            if not op else "opposite_disabled_hint_text_color",
            "Error": "error_color",
        }.get(value, None)
        if attr_name:
            c = {attr_name: setter}
            t.bind(**c)
            self._currently_bound_property = c
            self.color = getattr(t, attr_name)
        else:
            # 'Custom' and 'ContrastParentBackground' lead here, as well as the
            # generic None value it's not yet been set
            if value == "Custom" and self.text_color:
                self.color = self.text_color
            elif value == "ContrastParentBackground" and self.parent_background:
                self.color = get_contrast_text_color(self.parent_background)
            else:
                self.color = [0, 0, 0, 1]

    def on_text_color(self, *args):
        if self.theme_text_color == "Custom":
            self.color = self.text_color

    def on_opposite_colors(self, instance, value):
        self.on_theme_text_color(self, self.theme_text_color)
Exemple #16
0
class WindowBase(EventDispatcher):
    '''WindowBase is an abstract window widget for any window implementation.

    :Parameters:
        `borderless`: str, one of ('0', '1')
            Set the window border state. Check the
            :mod:`~kivy.config` documentation for a
            more detailed explanation on the values.
        `fullscreen`: str, one of ('0', '1', 'auto', 'fake')
            Make the window fullscreen. Check the
            :mod:`~kivy.config` documentation for a
            more detailed explanation on the values.
        `width`: int
            Width of the window.
        `height`: int
            Height of the window.

    :Events:
        `on_motion`: etype, motionevent
            Fired when a new :class:`~kivy.input.motionevent.MotionEvent` is
            dispatched
        `on_touch_down`:
            Fired when a new touch event is initiated.
        `on_touch_move`:
            Fired when an existing touch event changes location.
        `on_touch_up`:
            Fired when an existing touch event is terminated.
        `on_draw`:
            Fired when the :class:`Window` is being drawn.
        `on_flip`:
            Fired when the :class:`Window` GL surface is being flipped.
        `on_rotate`: rotation
            Fired when the :class:`Window` is being rotated.
        `on_close`:
            Fired when the :class:`Window` is closed.
        `on_request_close`:
            Fired when the event loop wants to close the window, or if the
            escape key is pressed and `exit_on_escape` is `True`. If a function
            bound to this event returns `True`, the window will not be closed.
            If the the event is triggered because of the keyboard escape key,
            the keyword argument `source` is dispatched along with a value of
            `keyboard` to the bound functions.

            .. versionadded:: 1.9.0

        `on_keyboard`: key, scancode, codepoint, modifier
            Fired when the keyboard is used for input.

            .. versionchanged:: 1.3.0
                The *unicode* parameter has been deprecated in favor of
                codepoint, and will be removed completely in future versions.

        `on_key_down`: key, scancode, codepoint
            Fired when a key pressed.

            .. versionchanged:: 1.3.0
                The *unicode* parameter has been deprecated in favor of
                codepoint, and will be removed completely in future versions.

        `on_key_up`: key, scancode, codepoint
            Fired when a key is released.

            .. versionchanged:: 1.3.0
                The *unicode* parameter has be deprecated in favor of
                codepoint, and will be removed completely in future versions.

        `on_dropfile`: str
            Fired when a file is dropped on the application.

    '''

    __instance = None
    __initialized = False
    _fake_fullscreen = False

    # private properties
    _size = ListProperty([0, 0])
    _modifiers = ListProperty([])
    _rotation = NumericProperty(0)
    _clearcolor = ObjectProperty([0, 0, 0, 1])

    children = ListProperty([])
    '''List of the children of this window.

    :attr:`children` is a :class:`~kivy.properties.ListProperty` instance and
    defaults to an empty list.

    Use :meth:`add_widget` and :meth:`remove_widget` to manipulate the list of
    children. Don't manipulate the list directly unless you know what you are
    doing.
    '''

    parent = ObjectProperty(None, allownone=True)
    '''Parent of this window.

    :attr:`parent` is a :class:`~kivy.properties.ObjectProperty` instance and
    defaults to None. When created, the parent is set to the window itself.
    You must take care of it if you are doing a recursive check.
    '''

    icon = StringProperty()

    def _get_modifiers(self):
        return self._modifiers

    modifiers = AliasProperty(_get_modifiers, None)
    '''List of keyboard modifiers currently active.
    '''

    def _get_size(self):
        r = self._rotation
        w, h = self._size
        if self.softinput_mode == 'resize':
            h -= self.keyboard_height
        if r in (0, 180):
            return w, h
        return h, w

    def _set_size(self, size):
        if self._size != size:
            r = self._rotation
            if r in (0, 180):
                self._size = size
            else:
                self._size = size[1], size[0]

            self.dispatch('on_resize', *size)
            return True
        else:
            return False

    size = AliasProperty(_get_size, _set_size, bind=('_size', ))
    '''Get the rotated size of the window. If :attr:`rotation` is set, then the
    size will change to reflect the rotation.
    '''

    def _get_clearcolor(self):
        return self._clearcolor

    def _set_clearcolor(self, value):
        if value is not None:
            if type(value) not in (list, tuple):
                raise Exception('Clearcolor must be a list or tuple')
            if len(value) != 4:
                raise Exception('Clearcolor must contain 4 values')
        self._clearcolor = value

    clearcolor = AliasProperty(_get_clearcolor,
                               _set_clearcolor,
                               bind=('_clearcolor', ))
    '''Color used to clear the window.

    ::

        from kivy.core.window import Window

        # red background color
        Window.clearcolor = (1, 0, 0, 1)

        # don't clear background at all
        Window.clearcolor = None

    .. versionchanged:: 1.7.2
        The clearcolor default value is now: (0, 0, 0, 1).

    '''

    # make some property read-only
    def _get_width(self):
        r = self._rotation
        if r == 0 or r == 180:
            return self._size[0]
        return self._size[1]

    width = AliasProperty(_get_width, None, bind=('_rotation', '_size'))
    '''Rotated window width.

    :attr:`width` is a read-only :class:`~kivy.properties.AliasProperty`.
    '''

    def _get_height(self):
        '''Rotated window height'''
        r = self._rotation
        kb = self.keyboard_height if self.softinput_mode == 'resize' else 0
        if r == 0 or r == 180:
            return self._size[1] - kb
        return self._size[0] - kb

    height = AliasProperty(_get_height, None, bind=('_rotation', '_size'))
    '''Rotated window height.

    :attr:`height` is a read-only :class:`~kivy.properties.AliasProperty`.
    '''

    def _get_center(self):
        return self.width / 2., self.height / 2.

    center = AliasProperty(_get_center, None, bind=('width', 'height'))
    '''Center of the rotated window.

    :attr:`center` is a :class:`~kivy.properties.AliasProperty`.
    '''

    def _get_rotation(self):
        return self._rotation

    def _set_rotation(self, x):
        x = int(x % 360)
        if x == self._rotation:
            return
        if x not in (0, 90, 180, 270):
            raise ValueError('can rotate only 0, 90, 180, 270 degrees')
        self._rotation = x
        if self.initialized is False:
            return
        self.dispatch('on_resize', *self.size)
        self.dispatch('on_rotate', x)

    rotation = AliasProperty(_get_rotation,
                             _set_rotation,
                             bind=('_rotation', ))
    '''Get/set the window content rotation. Can be one of 0, 90, 180, 270
    degrees.
    '''

    softinput_mode = OptionProperty('', options=('', 'pan', 'scale', 'resize'))
    '''This specifies the behavior of window contents on display of soft
    keyboard on mobile platform. Can be one of '', 'pan', 'scale', 'resize'.

    When '' The main window is left as it is allowing the user to use
    :attr:`keyboard_height` to manage the window contents the way they want.

    when 'pan' The main window pans moving the bottom part of the window to be
    always on top of the keyboard.

    when 'resize' The window is resized and the contents scaled to fit the
    remaining space.

    ..versionadded::1.9.0

    :attr:`softinput_mode` is a :class:`OptionProperty` defaults to None.

    '''

    _keyboard_changed = BooleanProperty(False)

    def _upd_kbd_height(self, *kargs):
        self._keyboard_changed = not self._keyboard_changed

    def _get_ios_kheight(self):
        return 0

    def _get_android_kheight(self):
        global android
        if not android:
            import android
        return android.get_keyboard_height()

    def _get_kheight(self):
        if platform == 'android':
            return self._get_android_kheight()
        if platform == 'ios':
            return self._get_ios_kheight()
        return 0

    keyboard_height = AliasProperty(_get_kheight,
                                    None,
                                    bind=('_keyboard_changed', ))
    '''Rerturns the height of the softkeyboard/IME on mobile platforms.
    Will return 0 if not on mobile platform or if IME is not active.

    ..versionadded:: 1.9.0

    :attr:`keyboard_height` is a read-only :class:`AliasProperty` defaults to 0.
    '''

    def _set_system_size(self, size):
        self._size = size

    def _get_system_size(self):
        if self.softinput_mode == 'resize':
            return self._size[0], self._size[1] - self.keyboard_height
        return self._size

    system_size = AliasProperty(_get_system_size,
                                _set_system_size,
                                bind=('_size', ))
    '''Real size of the window ignoring rotation.
    '''

    borderless = BooleanProperty(False)
    '''When set to True, this property removes the window border/decoration.

    .. versionadded:: 1.9.0

    :attr:`borderless` is a :class:`BooleanProperty`, defaults to False.
    '''

    fullscreen = OptionProperty(False, options=(True, False, 'auto', 'fake'))
    '''This property sets the fullscreen mode of the window. Available options
    are: True, False, 'auto', 'fake'. Check the :mod:`~kivy.config`
    documentation for a more detailed explanation on the values.

    .. versionadded:: 1.2.0

    .. note::
        The 'fake' option has been deprecated, use the :attr:`borderless`
        property instead.
    '''

    mouse_pos = ObjectProperty([0, 0])
    '''2d position of the mouse within the window.

    .. versionadded:: 1.2.0
    '''

    @property
    def __self__(self):
        return self

    top = NumericProperty(None, allownone=True)
    left = NumericProperty(None, allownone=True)
    position = OptionProperty('auto', options=['auto', 'custom'])
    render_context = ObjectProperty(None)
    canvas = ObjectProperty(None)
    title = StringProperty('Kivy')

    __events__ = ('on_draw', 'on_flip', 'on_rotate', 'on_resize', 'on_close',
                  'on_motion', 'on_touch_down', 'on_touch_move', 'on_touch_up',
                  'on_mouse_down', 'on_mouse_move', 'on_mouse_up',
                  'on_keyboard', 'on_key_down', 'on_key_up', 'on_textinput',
                  'on_dropfile', 'on_request_close', 'on_joy_axis',
                  'on_joy_hat', 'on_joy_ball', 'on_joy_button_down',
                  "on_joy_button_up")

    def __new__(cls, **kwargs):
        if cls.__instance is None:
            cls.__instance = EventDispatcher.__new__(cls)
        return cls.__instance

    def __init__(self, **kwargs):

        force = kwargs.pop('force', False)

        # don't init window 2 times,
        # except if force is specified
        if WindowBase.__instance is not None and not force:
            return

        self.initialized = False
        self._is_desktop = Config.getboolean('kivy', 'desktop')

        # create a trigger for update/create the window when one of window
        # property changes
        self.trigger_create_window = Clock.create_trigger(
            self.create_window, -1)

        # Create a trigger for updating the keyboard height
        self.trigger_keyboard_height = Clock.create_trigger(
            self._upd_kbd_height, .5)

        # set the default window parameter according to the configuration
        if 'borderless' not in kwargs:
            kwargs['borderless'] = Config.getboolean('graphics', 'borderless')
        if 'fullscreen' not in kwargs:
            fullscreen = Config.get('graphics', 'fullscreen')
            if fullscreen not in ('auto', 'fake'):
                fullscreen = fullscreen.lower() in ('true', '1', 'yes', 'yup')
            kwargs['fullscreen'] = fullscreen
        if 'width' not in kwargs:
            kwargs['width'] = Config.getint('graphics', 'width')
        if 'height' not in kwargs:
            kwargs['height'] = Config.getint('graphics', 'height')
        if 'rotation' not in kwargs:
            kwargs['rotation'] = Config.getint('graphics', 'rotation')
        if 'position' not in kwargs:
            kwargs['position'] = Config.getdefault('graphics', 'position',
                                                   'auto')
        if 'top' in kwargs:
            kwargs['position'] = 'custom'
            kwargs['top'] = kwargs['top']
        else:
            kwargs['top'] = Config.getint('graphics', 'top')
        if 'left' in kwargs:
            kwargs['position'] = 'custom'
            kwargs['left'] = kwargs['left']
        else:
            kwargs['left'] = Config.getint('graphics', 'left')
        kwargs['_size'] = (kwargs.pop('width'), kwargs.pop('height'))

        super(WindowBase, self).__init__(**kwargs)

        # bind all the properties that need to recreate the window
        self._bind_create_window()
        self.bind(size=self.trigger_keyboard_height,
                  rotation=self.trigger_keyboard_height)

        self.bind(softinput_mode=lambda *dt: self.update_viewport(),
                  keyboard_height=lambda *dt: self.update_viewport())

        # init privates
        self._system_keyboard = Keyboard(window=self)
        self._keyboards = {'system': self._system_keyboard}
        self._vkeyboard_cls = None

        self.children = []
        self.parent = self

        # before creating the window
        import kivy.core.gl  # NOQA

        # configure the window
        self.create_window()

        # attach modules + listener event
        EventLoop.set_window(self)
        Modules.register_window(self)
        EventLoop.add_event_listener(self)

        # manage keyboard(s)
        self.configure_keyboards()

        # assign the default context of the widget creation
        if not hasattr(self, '_context'):
            self._context = get_current_context()

        # mark as initialized
        self.initialized = True

    def _bind_create_window(self):
        for prop in ('fullscreen', 'borderless', 'position', 'top', 'left',
                     '_size', 'system_size'):
            self.bind(**{prop: self.trigger_create_window})

    def _unbind_create_window(self):
        for prop in ('fullscreen', 'borderless', 'position', 'top', 'left',
                     '_size', 'system_size'):
            self.unbind(**{prop: self.trigger_create_window})

    def toggle_fullscreen(self):
        '''Toggle between fullscreen and windowed mode.

        .. deprecated:: 1.9.0
            Use :attr:`fullscreen` instead.
        '''
        pass

    def maximize(self):
        '''Maximizes the window. This method should be used on desktop
        platforms only.

        .. versionadded:: 1.9.0

        .. note::
            This feature works with the SDL2 window provider only.

        .. warning::
            This code is still experimental, and its API may be subject to
            change in a future version.
        '''
        Logger.warning('Window: maximize() is not implemented in the current '
                       'window provider.')

    def minimize(self):
        '''Minimizes the window. This method should be used on desktop
        platforms only.

        .. versionadded:: 1.9.0

        .. note::
            This feature works with the SDL2 window provider only.

        .. warning::
            This code is still experimental, and its API may be subject to
            change in a future version.
        '''
        Logger.warning('Window: minimize() is not implemented in the current '
                       'window provider.')

    def restore(self):
        '''Restores the size and position of a maximized or minimized window.
        This method should be used on desktop platforms only.

        .. versionadded:: 1.9.0

        .. note::
            This feature works with the SDL2 window provider only.

        .. warning::
            This code is still experimental, and its API may be subject to
            change in a future version.
        '''
        Logger.warning('Window: restore() is not implemented in the current '
                       'window provider.')

    def hide(self):
        '''Hides the window. This method should be used on desktop
        platforms only.

        .. versionadded:: 1.9.0

        .. note::
            This feature works with the SDL2 window provider only.

        .. warning::
            This code is still experimental, and its API may be subject to
            change in a future version.
        '''
        Logger.warning('Window: hide() is not implemented in the current '
                       'window provider.')

    def show(self):
        '''Shows the window. This method should be used on desktop
        platforms only.

        .. versionadded:: 1.9.0

        .. note::
            This feature works with the SDL2 window provider only.

        .. warning::
            This code is still experimental, and its API may be subject to
            change in a future version.
        '''
        Logger.warning('Window: show() is not implemented in the current '
                       'window provider.')

    def close(self):
        '''Close the window'''
        pass

    def create_window(self, *largs):
        '''Will create the main window and configure it.

        .. warning::
            This method is called automatically at runtime. If you call it, it
            will recreate a RenderContext and Canvas. This means you'll have a
            new graphics tree, and the old one will be unusable.

            This method exist to permit the creation of a new OpenGL context
            AFTER closing the first one. (Like using runTouchApp() and
            stopTouchApp()).

            This method has only been tested in a unittest environment and
            is not suitable for Applications.

            Again, don't use this method unless you know exactly what you are
            doing!
        '''
        # just to be sure, if the trigger is set, and if this method is
        # manually called, unset the trigger
        Clock.unschedule(self.create_window)

        # ensure the window creation will not be called twice
        if platform in ('android', 'ios'):
            self._unbind_create_window()

        if not self.initialized:
            from kivy.core.gl import init_gl
            init_gl()

            # create the render context and canvas, only the first time.
            from kivy.graphics import RenderContext, Canvas
            self.render_context = RenderContext()
            self.canvas = Canvas()
            self.render_context.add(self.canvas)

        else:
            # if we get initialized more than once, then reload opengl state
            # after the second time.
            # XXX check how it's working on embed platform.
            if platform == 'linux' or Window.__class__.__name__ == 'WindowSDL':
                # on linux, it's safe for just sending a resize.
                self.dispatch('on_resize', *self.system_size)

            else:
                # on other platform, window are recreated, we need to reload.
                from kivy.graphics.context import get_context
                get_context().reload()
                Clock.schedule_once(lambda x: self.canvas.ask_update(), 0)
                self.dispatch('on_resize', *self.system_size)

        # ensure the gl viewport is correct
        self.update_viewport()

    def on_flip(self):
        '''Flip between buffers (event)'''
        self.flip()

    def flip(self):
        '''Flip between buffers'''
        pass

    def _update_childsize(self, instance, value):
        self.update_childsize([instance])

    def add_widget(self, widget, canvas=None):
        '''Add a widget to a window'''
        widget.parent = self
        self.children.insert(0, widget)
        canvas = self.canvas.before if canvas == 'before' else \
            self.canvas.after if canvas == 'after' else self.canvas
        canvas.add(widget.canvas)
        self.update_childsize([widget])
        widget.bind(pos_hint=self._update_childsize,
                    size_hint=self._update_childsize,
                    size=self._update_childsize,
                    pos=self._update_childsize)

    def remove_widget(self, widget):
        '''Remove a widget from a window
        '''
        if not widget in self.children:
            return
        self.children.remove(widget)
        if widget.canvas in self.canvas.children:
            self.canvas.remove(widget.canvas)
        elif widget.canvas in self.canvas.after.children:
            self.canvas.after.remove(widget.canvas)
        elif widget.canvas in self.canvas.before.children:
            self.canvas.before.remove(widget.canvas)
        widget.parent = None
        widget.unbind(pos_hint=self._update_childsize,
                      size_hint=self._update_childsize,
                      size=self._update_childsize,
                      pos=self._update_childsize)

    def clear(self):
        '''Clear the window with the background color'''
        # XXX FIXME use late binding
        from kivy.graphics.opengl import glClearColor, glClear, \
            GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT
        cc = self._clearcolor
        if cc is not None:
            glClearColor(*cc)
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT
                    | GL_STENCIL_BUFFER_BIT)

    def set_title(self, title):
        '''Set the window title.

        .. versionadded:: 1.0.5
        '''
        self.title = title

    def set_icon(self, filename):
        '''Set the icon of the window.

        .. versionadded:: 1.0.5
        '''
        self.icon = filename

    def to_widget(self, x, y, initial=True, relative=False):
        return (x, y)

    def to_window(self, x, y, initial=True, relative=False):
        return (x, y)

    def _apply_transform(self, m):
        return m

    def get_window_matrix(self, x=0, y=0):
        m = Matrix()
        m.translate(x, y, 0)
        return m

    def get_root_window(self):
        return self

    def get_parent_window(self):
        return self

    def get_parent_layout(self):
        return None

    def on_draw(self):
        self.clear()
        self.render_context.draw()

    def on_motion(self, etype, me):
        '''Event called when a Motion Event is received.

        :Parameters:
            `etype`: str
                One of 'begin', 'update', 'end'
            `me`: :class:`~kivy.input.motionevent.MotionEvent`
                The Motion Event currently dispatched.
        '''
        if me.is_touch:
            w, h = self.system_size
            me.scale_for_screen(w,
                                h,
                                rotation=self._rotation,
                                smode=self.softinput_mode,
                                kheight=self.keyboard_height)
            if etype == 'begin':
                self.dispatch('on_touch_down', me)
            elif etype == 'update':
                self.dispatch('on_touch_move', me)
            elif etype == 'end':
                self.dispatch('on_touch_up', me)
                FocusBehavior._handle_post_on_touch_up(me)

    def on_touch_down(self, touch):
        '''Event called when a touch down event is initiated.

        .. versionchanged:: 1.9.0
            The touch `pos` is now transformed to window coordinates before
            this method is called. Before, the touch `pos` coordinate would be
            `(0, 0)` when this method was called.
        '''
        for w in self.children[:]:
            if w.dispatch('on_touch_down', touch):
                return True

    def on_touch_move(self, touch):
        '''Event called when a touch event moves (changes location).

        .. versionchanged:: 1.9.0
            The touch `pos` is now transformed to window coordinates before
            this method is called. Before, the touch `pos` coordinate would be
            `(0, 0)` when this method was called.
        '''
        for w in self.children[:]:
            if w.dispatch('on_touch_move', touch):
                return True

    def on_touch_up(self, touch):
        '''Event called when a touch event is released (terminated).

        .. versionchanged:: 1.9.0
            The touch `pos` is now transformed to window coordinates before
            this method is called. Before, the touch `pos` coordinate would be
            `(0, 0)` when this method was called.
        '''
        for w in self.children[:]:
            if w.dispatch('on_touch_up', touch):
                return True

    def on_resize(self, width, height):
        '''Event called when the window is resized.'''
        self.update_viewport()

    def update_viewport(self):
        from kivy.graphics.opengl import glViewport
        from kivy.graphics.transformation import Matrix
        from math import radians

        w, h = self.system_size

        smode = self.softinput_mode
        kheight = self.keyboard_height

        w2, h2 = w / 2., h / 2.
        r = radians(self.rotation)

        x, y = 0, 0
        _h = h
        if smode:
            y = kheight
        if smode == 'scale':
            _h -= kheight

        # prepare the viewport
        glViewport(x, y, w, _h)

        # do projection matrix
        projection_mat = Matrix()
        projection_mat.view_clip(0.0, w, 0.0, h, -1.0, 1.0, 0)
        self.render_context['projection_mat'] = projection_mat

        # do modelview matrix
        modelview_mat = Matrix().translate(w2, h2, 0)
        modelview_mat = modelview_mat.multiply(Matrix().rotate(r, 0, 0, 1))

        w, h = self.size
        w2, h2 = w / 2., h / 2.
        modelview_mat = modelview_mat.multiply(Matrix().translate(-w2, -h2, 0))
        self.render_context['modelview_mat'] = modelview_mat

        # redraw canvas
        self.canvas.ask_update()

        # and update childs
        self.update_childsize()

    def update_childsize(self, childs=None):
        width, height = self.size
        if childs is None:
            childs = self.children
        for w in childs:
            shw, shh = w.size_hint
            if shw and shh:
                w.size = shw * width, shh * height
            elif shw:
                w.width = shw * width
            elif shh:
                w.height = shh * height
            for key, value in w.pos_hint.items():
                if key == 'x':
                    w.x = value * width
                elif key == 'right':
                    w.right = value * width
                elif key == 'y':
                    w.y = value * height
                elif key == 'top':
                    w.top = value * height
                elif key == 'center_x':
                    w.center_x = value * width
                elif key == 'center_y':
                    w.center_y = value * height

    def screenshot(self, name='screenshot{:04d}.png'):
        '''Save the actual displayed image in a file
        '''
        i = 0
        path = None
        if name != 'screenshot{:04d}.png':
            _ext = name.split('.')[-1]
            name = ''.join((name[:-(len(_ext) + 1)], '{:04d}.', _ext))
        while True:
            i += 1
            path = join(getcwd(), name.format(i))
            if not exists(path):
                break
        return path

    def on_rotate(self, rotation):
        '''Event called when the screen has been rotated.
        '''
        pass

    def on_close(self, *largs):
        '''Event called when the window is closed'''
        Modules.unregister_window(self)
        EventLoop.remove_event_listener(self)

    def on_request_close(self, *largs, **kwargs):
        '''Event called before we close the window. If a bound function returns
        `True`, the window will not be closed. If the the event is triggered
        because of the keyboard escape key, the keyword argument `source` is
        dispatched along with a value of `keyboard` to the bound functions.

        .. warning::
            When the bound function returns True the window will not be closed,
            so use with care because the user would not be able to close the
            program, even if the red X is clicked.
        '''
        pass

    def on_mouse_down(self, x, y, button, modifiers):
        '''Event called when the mouse is used (pressed/released)'''
        pass

    def on_mouse_move(self, x, y, modifiers):
        '''Event called when the mouse is moved with buttons pressed'''
        pass

    def on_mouse_up(self, x, y, button, modifiers):
        '''Event called when the mouse is moved with buttons pressed'''
        pass

    def on_joy_axis(self, stickid, axisid, value):
        '''Event called when a joystick has a stick or other axis moved

        .. versionadded:: 1.9.0'''
        pass

    def on_joy_hat(self, stickid, hatid, value):
        '''Event called when a joystick has a hat/dpad moved

        .. versionadded:: 1.9.0'''
        pass

    def on_joy_ball(self, stickid, ballid, value):
        '''Event called when a joystick has a ball moved

        .. versionadded:: 1.9.0'''
        pass

    def on_joy_button_down(self, stickid, buttonid):
        '''Event called when a joystick has a button pressed

        .. versionadded:: 1.9.0'''
        pass

    def on_joy_button_up(self, stickid, buttonid):
        '''Event called when a joystick has a button released

        .. versionadded:: 1.9.0'''
        pass

    def on_keyboard(self,
                    key,
                    scancode=None,
                    codepoint=None,
                    modifier=None,
                    **kwargs):
        '''Event called when keyboard is used.

        .. warning::
            Some providers may omit `scancode`, `codepoint` and/or `modifier`!
        '''
        if 'unicode' in kwargs:
            Logger.warning("The use of the unicode parameter is deprecated, "
                           "and will be removed in future versions. Use "
                           "codepoint instead, which has identical "
                           "semantics.")

        # Quit if user presses ESC or the typical OSX shortcuts CMD+q or CMD+w
        # TODO If just CMD+w is pressed, only the window should be closed.
        is_osx = platform == 'darwin'
        if WindowBase.on_keyboard.exit_on_escape:
            if key == 27 or all([is_osx, key in [113, 119], modifier == 1024]):
                if not self.dispatch('on_request_close', source='keyboard'):
                    stopTouchApp()
                    self.close()
                    return True

    if Config:
        on_keyboard.exit_on_escape = Config.getboolean('kivy',
                                                       'exit_on_escape')

        def __exit(section, name, value):
            WindowBase.__dict__['on_keyboard'].exit_on_escape = \
                Config.getboolean('kivy', 'exit_on_escape')

        Config.add_callback(__exit, 'kivy', 'exit_on_escape')

    def on_key_down(self,
                    key,
                    scancode=None,
                    codepoint=None,
                    modifier=None,
                    **kwargs):
        '''Event called when a key is down (same arguments as on_keyboard)'''
        if 'unicode' in kwargs:
            Logger.warning("The use of the unicode parameter is deprecated, "
                           "and will be removed in future versions. Use "
                           "codepoint instead, which has identical "
                           "semantics.")

    def on_key_up(self,
                  key,
                  scancode=None,
                  codepoint=None,
                  modifier=None,
                  **kwargs):
        '''Event called when a key is released (same arguments as on_keyboard)
        '''
        if 'unicode' in kwargs:
            Logger.warning("The use of the unicode parameter is deprecated, "
                           "and will be removed in future versions. Use "
                           "codepoint instead, which has identical "
                           "semantics.")

    def on_textinput(self, text):
        '''Event called whem text: i.e. alpha numeric non control keys or set
        of keys is entered. As it is not gaurenteed whether we get one
        character or multiple ones, this event supports handling multiple
        characters.

        ..versionadded:: 1.9.0
        '''
        pass

    def on_dropfile(self, filename):
        '''Event called when a file is dropped on the application.

        .. warning::

            This event currently works with sdl2 window provider, on pygame
            window provider and MacOSX with a patched version of pygame.
            This event is left in place for further evolution
            (ios, android etc.)

        .. versionadded:: 1.2.0
        '''
        pass

    @reify
    def dpi(self):
        '''Return the DPI of the screen. If the implementation doesn't support
        any DPI lookup, it will just return 96.

        .. warning::

            This value is not cross-platform. Use
            :attr:`kivy.base.EventLoop.dpi` instead.
        '''
        return 96.

    def configure_keyboards(self):
        # Configure how to provide keyboards (virtual or not)

        # register system keyboard to listening keys from window
        sk = self._system_keyboard
        self.bind(on_key_down=sk._on_window_key_down,
                  on_key_up=sk._on_window_key_up,
                  on_textinput=sk._on_window_textinput)

        # use the device's real keyboard
        self.use_syskeyboard = True

        # use the device's real keyboard
        self.allow_vkeyboard = False

        # one single vkeyboard shared between all widgets
        self.single_vkeyboard = True

        # the single vkeyboard is always sitting at the same position
        self.docked_vkeyboard = False

        # now read the configuration
        mode = Config.get('kivy', 'keyboard_mode')
        if mode not in ('', 'system', 'dock', 'multi', 'systemanddock',
                        'systemandmulti'):
            Logger.critical('Window: unknown keyboard mode %r' % mode)

        # adapt mode according to the configuration
        if mode == 'system':
            self.use_syskeyboard = True
            self.allow_vkeyboard = False
            self.single_vkeyboard = True
            self.docked_vkeyboard = False
        elif mode == 'dock':
            self.use_syskeyboard = False
            self.allow_vkeyboard = True
            self.single_vkeyboard = True
            self.docked_vkeyboard = True
        elif mode == 'multi':
            self.use_syskeyboard = False
            self.allow_vkeyboard = True
            self.single_vkeyboard = False
            self.docked_vkeyboard = False
        elif mode == 'systemanddock':
            self.use_syskeyboard = True
            self.allow_vkeyboard = True
            self.single_vkeyboard = True
            self.docked_vkeyboard = True
        elif mode == 'systemandmulti':
            self.use_syskeyboard = True
            self.allow_vkeyboard = True
            self.single_vkeyboard = False
            self.docked_vkeyboard = False

        Logger.info(
            'Window: virtual keyboard %sallowed, %s, %s' %
            ('' if self.allow_vkeyboard else 'not ',
             'single mode' if self.single_vkeyboard else 'multiuser mode',
             'docked' if self.docked_vkeyboard else 'not docked'))

    def set_vkeyboard_class(self, cls):
        '''.. versionadded:: 1.0.8

        Set the VKeyboard class to use. If set to None, it will use the
        :class:`kivy.uix.vkeyboard.VKeyboard`.
        '''
        self._vkeyboard_cls = cls

    def release_all_keyboards(self):
        '''.. versionadded:: 1.0.8

        This will ensure that no virtual keyboard / system keyboard is
        requested. All instances will be closed.
        '''
        for key in list(self._keyboards.keys())[:]:
            keyboard = self._keyboards[key]
            if keyboard:
                keyboard.release()

    def request_keyboard(self, callback, target, input_type='text'):
        '''.. versionadded:: 1.0.4

        Internal widget method to request the keyboard. This method is rarely
        required by the end-user as it is handled automatically by the
        :class:`~kivy.uix.textinput.TextInput`. We expose it in case you want
        to handle the keyboard manually for unique input scenarios.

        A widget can request the keyboard, indicating a callback to call
        when the keyboard is released (or taken by another widget).

        :Parameters:
            `callback`: func
                Callback that will be called when the keyboard is
                closed. This can be because somebody else requested the
                keyboard or the user closed it.
            `target`: Widget
                Attach the keyboard to the specified `target`. This should be
                the widget that requested the keyboard. Ensure you have a
                different target attached to each keyboard if you're working in
                a multi user mode.

                .. versionadded:: 1.0.8

            `input_type`: string
                Choose the type of soft keyboard to request. Can be one of
                'text', 'number', 'url', 'mail', 'datetime', 'tel', 'address'.

                .. note::

                    `input_type` is currently only honored on mobile devices.

                .. versionadded:: 1.8.0

        :Return:
            An instance of :class:`Keyboard` containing the callback, target,
            and if the configuration allows it, a
            :class:`~kivy.uix.vkeyboard.VKeyboard` instance attached as a
            *.widget* property.

        .. note::

            The behavior of this function is heavily influenced by the current
            `keyboard_mode`. Please see the Config's
            :ref:`configuration tokens <configuration-tokens>` section for
            more information.

        '''

        # release any previous keyboard attached.
        self.release_keyboard(target)

        # if we can use virtual vkeyboard, activate it.
        if self.allow_vkeyboard:
            keyboard = None

            # late import
            global VKeyboard
            if VKeyboard is None and self._vkeyboard_cls is None:
                from kivy.uix.vkeyboard import VKeyboard
                self._vkeyboard_cls = VKeyboard

            # if the keyboard doesn't exist, create it.
            key = 'single' if self.single_vkeyboard else target
            if key not in self._keyboards:
                vkeyboard = self._vkeyboard_cls()
                keyboard = Keyboard(widget=vkeyboard, window=self)
                vkeyboard.bind(on_key_down=keyboard._on_vkeyboard_key_down,
                               on_key_up=keyboard._on_vkeyboard_key_up,
                               on_textinput=keyboard._on_vkeyboard_textinput)
                self._keyboards[key] = keyboard
            else:
                keyboard = self._keyboards[key]

            # configure vkeyboard
            keyboard.target = keyboard.widget.target = target
            keyboard.callback = keyboard.widget.callback = callback

            # add to the window
            self.add_widget(keyboard.widget)

            # only after add, do dock mode
            keyboard.widget.docked = self.docked_vkeyboard
            keyboard.widget.setup_mode()

        else:
            # system keyboard, just register the callback.
            keyboard = self._system_keyboard
            keyboard.callback = callback
            keyboard.target = target

        # use system (hardware) keyboard according to flag
        if self.allow_vkeyboard and self.use_syskeyboard:
            self.unbind(on_key_down=keyboard._on_window_key_down,
                        on_key_up=keyboard._on_window_key_up,
                        on_textinput=keyboard._on_window_textinput)
            self.bind(on_key_down=keyboard._on_window_key_down,
                      on_key_up=keyboard._on_window_key_up,
                      on_textinput=keyboard._on_window_textinput)

        return keyboard

    def release_keyboard(self, target=None):
        '''.. versionadded:: 1.0.4

        Internal method for the widget to release the real-keyboard. Check
        :meth:`request_keyboard` to understand how it works.
        '''
        if self.allow_vkeyboard:
            key = 'single' if self.single_vkeyboard else target
            if key not in self._keyboards:
                return
            keyboard = self._keyboards[key]
            callback = keyboard.callback
            if callback:
                keyboard.callback = None
                callback()
            keyboard.target = None
            self.remove_widget(keyboard.widget)
            if key != 'single' and key in self._keyboards:
                del self._keyboards[key]
        elif self._system_keyboard.callback:
            # this way will prevent possible recursion.
            callback = self._system_keyboard.callback
            self._system_keyboard.callback = None
            callback()
            return True
Exemple #17
0
class ShinobiViewer(App):
    fullscreen = BooleanProperty(False)

    def on_fullscreen(self, instance, value):
        if value:
            Window.fullscreen = 'auto'
        else:
            Window.fullscreen = False

    def build(self):
        self.settings_cls = SettingsWithSidebar
        self.available_layouts = [
            os.path.splitext(os.path.basename(l))[0]
            for l in glob("layouts/*.kv")
        ]
        Window.bind(on_keyboard=self.on_keyboard)  # bind our handler
        monitors = self.config['monitors']
        self.root = BoxLayout()
        self.add_monitors()
        return self.root

    def add_monitors(self):
        monitors = self.config['monitors'].values()
        if not monitors:
            self.root.add_widget(Label(text='No monitors'))
            return self.root
        config = self.config['shinobi']
        monitor_urls = [
            '/%s/monitor/%s/%s' % (config['apiKey'], config['groupKey'], mid)
            for mid in monitors
        ]
        server_url = config['server'].rstrip('/')
        if config['layout'] == 'auto':
            layout = self.auto_layout(len(monitors))
        else:
            layout = Builder.load_file('layouts/%s.kv' % config['layout'])

        self.monitor_widgets = [
            widget for widget in layout.walk(restrict=True)
            if isinstance(widget, ShinobiMonitor)
        ]
        for url, monitor in zip(monitor_urls, self.monitor_widgets):
            monitor.serverURL = server_url
            monitor.monitorPath = url
            monitor.start()
        self.root.add_widget(layout)
        return self.root

    def fetch_monitors(self):
        shinobi_monitors_url = '/'.join([
            self.config['shinobi']['server'].rstrip('/'),
            self.config['shinobi']['apikey'],
            'monitor',
            self.config['shinobi']['groupkey'],
        ])
        try:
            response = requests.get(shinobi_monitors_url, timeout=60)
        except:
            return []
        finally:
            for monitor in response.json():
                details = json.loads(monitor['details'])
                yield {
                    'mid': monitor['mid'],
                    'name': monitor['name'],
                    'groups': details['groups']
                }

    def build_settings(self, settings):
        settings.add_json_panel('Connection',
                                self.config,
                                data=json.dumps([
                                    {
                                        "type": "string",
                                        "title": "Server URL",
                                        "desc": "URL of shinobi server",
                                        "section": "shinobi",
                                        "key": "server"
                                    },
                                    {
                                        "type": "string",
                                        "title": "API Key",
                                        "desc": "Server API key",
                                        "section": "shinobi",
                                        "key": "apikey"
                                    },
                                    {
                                        "type": "string",
                                        "title": "Group Key",
                                        "desc": "User API key",
                                        "section": "shinobi",
                                        "key": "groupkey"
                                    },
                                    {
                                        "type": "title",
                                        "title": "Monitors"
                                    },
                                    {
                                        "type":
                                        "options",
                                        "title":
                                        "Layout",
                                        "desc":
                                        "Monitors layout on screen",
                                        "section":
                                        "shinobi",
                                        "key":
                                        "layout",
                                        "options":
                                        ["auto"] + self.available_layouts
                                    },
                                ]))
        monitors_panel = MonitorSettingsPanel(
            title="Monitors",
            settings=settings,
            config=self.config,
            fetch_func=self.fetch_monitors,
        )
        settings.interface.add_panel(monitors_panel, 'Monitors',
                                     monitors_panel.uid)

    def build_config(self, config):
        config.setdefaults('shinobi', {
            'layout': 'auto',
            'server': '',
            'apiKey': '',
            'groupKey': ''
        })
        config.adddefaultsection('monitors')

    def on_keyboard(self, window, key, scancode, codepoint, modifier):
        if scancode == 68:  # F11
            self.fullscreen = not self.fullscreen
        if scancode == 69:  # F12
            self.open_settings()

    def auto_layout(self, qtd):
        cols = math.ceil(math.sqrt(qtd))
        layout = GridLayout(cols=cols)
        for i in range(qtd):
            layout.add_widget(ShinobiMonitor())
        return layout

    def start_stop_monitors(self, start):
        for monitor in getattr(self, 'monitor_widgets', []):
            if start:
                monitor.start()
            else:
                monitor.stop()

    def open_settings(self, *args, **kwargs):
        self.start_stop_monitors(False)
        self.root.clear_widgets()
        return super().open_settings(*args, **kwargs)

    def close_settings(self, *args, **kwargs):
        self.start_stop_monitors(True)
        self.add_monitors()
        return super().close_settings(*args, **kwargs)

    def on_pause(self):
        self.start_stop_monitors(False)
        return True

    def on_resume(self):
        self.start_stop_monitors(True)
Exemple #18
0
class DataTable(GridLayout):
    """This is a compound widget designed to display
    a dictionary of data as a nice table. The dictionary
    should have the column headers as keys, and then
    the associated value is a list of data for that
    column.
    
    You may have lists of different lengths, but the columns
    will fill from the top down; therefore, include blank
    strings as placeholders for any empty cells.
    
    Note that since the column headers are dict keys, you
    must have unique column names. Sorry..."""
    data = DictProperty({})
    ncol = NumericProperty(0)
    nrow = NumericProperty(0)
    editable = BooleanProperty(False)
    header_col = StringProperty('')

    def __init__(self,
                 data={},
                 editable=False,
                 header_column='',
                 header_row=[],
                 **kw):
        super(DataTable, self).__init__(**kw)

        self.bind(height=self.setter('minimum_height'))

        self.data = data
        self.ncol = len(data)
        self.editable = editable
        self.header_col = header_column
        self.header_row = header_row
        celltype = EditableCell if self.editable else StaticCell
        self.nrow = max([len(data[x]) for x in data])
        self.cells = {}
        for key in self.header_row:
            cell_id = str(key) + '_head'
            cell = ColHeader(text=str(key),
                             data_table=self,
                             id=cell_id,
                             size_hint_y=None,
                             height=100)
            self.cells[cell_id] = cell
            self.add_widget(cell)
        for i in xrange(self.nrow):
            get = itemgetter(i)
            for key in self.header_row:
                cell_id = str(key) + '_' + str(i)
                if i <= len(self.data[key]):
                    text = get(self.data[key])
                else:
                    text = ''
                    self.data[key].append('')
                if key == self.header_col:
                    self.cells[cell_id] = RowHeader(text=str(text),
                                                    data_table=self,
                                                    id=cell_id,
                                                    initial_type=type(text))
                else:
                    self.cells[cell_id] = celltype(text=str(text),
                                                   data_table=self,
                                                   id=cell_id,
                                                   initial_type=type(text))
                self.add_widget(self.cells[cell_id])

    def data_update(self, cell_id, value):
        """This will try to convert the value
        to the initial type of the data. If that fails,
        it'll just be a string. The initial type won't
        change, however."""
        key, idx = cell_id.split('_')
        try:
            val = self.cells[cell_id].initial_type(value)
        except ValueError:
            val = value
        self.data[key][int(idx)] = val

    def sort_by(self, colname):
        column_to_order = enumerate(self.data[colname])
        sort_order = map(itemgetter(0),
                         sorted(column_to_order, key=itemgetter(1)))
        for key in self.data:
            col = self.data[key]
            self.data[key] = [col[x] for x in sort_order]
            self.cells[str(key) + '_head'].background_color = (1, 1, 1, 1)
            for i in xrange(self.nrow):
                self.cells[str(key) + '_' + str(i)].text = str(
                    self.data[key][i])
        self.cells[colname + '_head'].background_color = (0, 1, 0, 1)
Exemple #19
0
class BaseButton(ThemableBehavior, ButtonBehavior,
                 SpecificBackgroundColorBehavior, AnchorLayout):
    '''
    Abstract base class for all MD buttons. This class handles the button's
    colors (disabled/down colors handled in children classes as those depend on
    type of button) as well as the disabled state.
    '''
    _md_bg_color_down = ListProperty(None, allownone=True)
    _md_bg_color_disabled = ListProperty(None, allownone=True)
    _current_button_color = ListProperty([0., 0., 0., 0.])
    theme_text_color = OptionProperty(None,
                                      allownone=True,
                                      options=[
                                          'Primary', 'Secondary', 'Hint',
                                          'Error', 'Custom',
                                          'ContrastParentBackground'
                                      ])
    text_color = ListProperty(None, allownone=True)
    opposite_colors = BooleanProperty(False)

    def __init__(self, **kwargs):
        super(BaseButton, self).__init__(**kwargs)
        Clock.schedule_once(self._finish_init)

    def _finish_init(self, dt):
        self._update_color()

    def on_md_bg_color(self, instance, value):
        self._update_color()

    def _update_color(self):
        if not self.disabled:
            self._current_button_color = self.md_bg_color
        else:
            self._current_button_color = self.md_bg_color_disabled

    def _call_get_bg_color_down(self):
        return self._get_md_bg_color_down()

    def _get_md_bg_color_down(self):
        if self._md_bg_color_down:
            return self._md_bg_color_down
        else:
            raise NotImplementedError

    def _set_md_bg_color_down(self, value):
        self._md_bg_color_down = value

    md_bg_color_down = AliasProperty(_call_get_bg_color_down,
                                     _set_md_bg_color_down)

    def _call_get_bg_color_disabled(self):
        return self._get_md_bg_color_disabled()

    def _get_md_bg_color_disabled(self):
        if self._md_bg_color_disabled:
            return self._md_bg_color_disabled
        else:
            raise NotImplementedError

    def _set_md_bg_color_disabled(self, value):
        self._md_bg_color_disabled = value

    md_bg_color_disabled = AliasProperty(_call_get_bg_color_disabled,
                                         _set_md_bg_color_disabled)

    def on_disabled(self, instance, value):
        if value:
            self._current_button_color = self.md_bg_color_disabled
        else:
            self._current_button_color = self.md_bg_color
        super(BaseButton, self).on_disabled(instance, value)
Exemple #20
0
class VideoPlayer(GridLayout):
    '''VideoPlayer class. See module documentation for more information.
    '''

    source = StringProperty('')
    '''Source of the video to read.

    :attr:`source` is a :class:`~kivy.properties.StringProperty` and
    defaults to ''.

    .. versionchanged:: 1.4.0
    '''

    thumbnail = StringProperty('')
    '''Thumbnail of the video to show. If None, VideoPlayer will try to find
    the thumbnail from the :attr:`source` + '.png'.

    :attr:`thumbnail` a :class:`~kivy.properties.StringProperty` and defaults
    to ''.

    .. versionchanged:: 1.4.0
    '''

    duration = NumericProperty(-1)
    '''Duration of the video. The duration defaults to -1 and is set to the
    real duration when the video is loaded.

    :attr:`duration` is a :class:`~kivy.properties.NumericProperty` and
    defaults to -1.
    '''

    position = NumericProperty(0)
    '''Position of the video between 0 and :attr:`duration`. The position
    defaults to -1 and is set to the real position when the video is loaded.

    :attr:`position` is a :class:`~kivy.properties.NumericProperty` and
    defaults to -1.
    '''

    volume = NumericProperty(1.0)
    '''Volume of the video in the range 0-1. 1 means full volume and 0 means
    mute.

    :attr:`volume` is a :class:`~kivy.properties.NumericProperty` and defaults
    to 1.
    '''

    state = OptionProperty('stop', options=('play', 'pause', 'stop'))
    '''String, indicates whether to play, pause, or stop the video::

        # start playing the video at creation
        video = VideoPlayer(source='movie.mkv', state='play')

        # create the video, and start later
        video = VideoPlayer(source='movie.mkv')
        # and later
        video.state = 'play'

    :attr:`state` is an :class:`~kivy.properties.OptionProperty` and defaults
    to 'stop'.
    '''

    play = BooleanProperty(False, deprecated=True)
    '''
    .. deprecated:: 1.4.0
        Use :attr:`state` instead.

    Boolean, indicates whether the video is playing or not. You can start/stop
    the video by setting this property::

        # start playing the video at creation
        video = VideoPlayer(source='movie.mkv', play=True)

        # create the video, and start later
        video = VideoPlayer(source='movie.mkv')
        # and later
        video.play = True

    :attr:`play` is a :class:`~kivy.properties.BooleanProperty` and defaults
    to False.
    '''

    image_overlay_play = StringProperty(
        'atlas://data/images/defaulttheme/player-play-overlay')
    '''Image filename used to show a "play" overlay when the video has not yet
    started.

    :attr:`image_overlay_play` is a
    :class:`~kivy.properties.StringProperty` and
    defaults to 'atlas://data/images/defaulttheme/player-play-overlay'.

    '''

    image_loading = StringProperty('data/images/image-loading.gif')
    '''Image filename used when the video is loading.

    :attr:`image_loading` is a :class:`~kivy.properties.StringProperty` and
    defaults to 'data/images/image-loading.gif'.
    '''

    image_play = StringProperty(
        'atlas://data/images/defaulttheme/media-playback-start')
    '''Image filename used for the "Play" button.

    :attr:`image_play` is a :class:`~kivy.properties.StringProperty` and
    defaults to 'atlas://data/images/defaulttheme/media-playback-start'.
    '''

    image_stop = StringProperty(
        'atlas://data/images/defaulttheme/media-playback-stop')
    '''Image filename used for the "Stop" button.

    :attr:`image_stop` is a :class:`~kivy.properties.StringProperty` and
    defaults to 'atlas://data/images/defaulttheme/media-playback-stop'.
    '''

    image_pause = StringProperty(
        'atlas://data/images/defaulttheme/media-playback-pause')
    '''Image filename used for the "Pause" button.

    :attr:`image_pause` is a :class:`~kivy.properties.StringProperty` and
    defaults to 'atlas://data/images/defaulttheme/media-playback-pause'.
    '''

    image_volumehigh = StringProperty(
        'atlas://data/images/defaulttheme/audio-volume-high')
    '''Image filename used for the volume icon when the volume is high.

    :attr:`image_volumehigh` is a :class:`~kivy.properties.StringProperty` and
    defaults to 'atlas://data/images/defaulttheme/audio-volume-high'.
    '''

    image_volumemedium = StringProperty(
        'atlas://data/images/defaulttheme/audio-volume-medium')
    '''Image filename used for the volume icon when the volume is medium.

    :attr:`image_volumemedium` is a :class:`~kivy.properties.StringProperty`
    and defaults to 'atlas://data/images/defaulttheme/audio-volume-medium'.
    '''

    image_volumelow = StringProperty(
        'atlas://data/images/defaulttheme/audio-volume-low')
    '''Image filename used for the volume icon when the volume is low.

    :attr:`image_volumelow` is a :class:`~kivy.properties.StringProperty`
    and defaults to 'atlas://data/images/defaulttheme/audio-volume-low'.
    '''

    image_volumemuted = StringProperty(
        'atlas://data/images/defaulttheme/audio-volume-muted')
    '''Image filename used for the volume icon when the volume is muted.

    :attr:`image_volumemuted` is a :class:`~kivy.properties.StringProperty`
    and defaults to 'atlas://data/images/defaulttheme/audio-volume-muted'.
    '''

    annotations = StringProperty('')
    '''If set, it will be used for reading annotations box.

    :attr:`annotations` is a :class:`~kivy.properties.StringProperty`
    and defaults to ''.
    '''

    fullscreen = BooleanProperty(False)
    '''Switch to fullscreen view. This should be used with care. When
    activated, the widget will remove itself from its parent, remove all
    children from the window and will add itself to it. When fullscreen is
    unset, all the previous children are restored and the widget is restored to
    its previous parent.

    .. warning::

        The re-add operation doesn't care about the index position of its
        children within the parent.

    :attr:`fullscreen` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to False.
    '''

    allow_fullscreen = BooleanProperty(True)
    '''By default, you can double-tap on the video to make it fullscreen. Set
    this property to False to prevent this behavior.

    :attr:`allow_fullscreen` is a :class:`~kivy.properties.BooleanProperty`
    defaults to True.
    '''

    options = DictProperty({})
    '''Optional parameters can be passed to a :class:`~kivy.uix.video.Video`
    instance with this property.

    :attr:`options` a :class:`~kivy.properties.DictProperty` and
    defaults to {}.
    '''

    # internals
    container = ObjectProperty(None)

    _video_load_ev = None

    def __init__(self, **kwargs):
        self._video = None
        self._image = None
        self._annotations = ''
        self._annotations_labels = []
        super(VideoPlayer, self).__init__(**kwargs)
        self._load_thumbnail()
        self._load_annotations()

        if self.source:
            self._trigger_video_load()

    def _trigger_video_load(self, *largs):
        ev = self._video_load_ev
        if ev is None:
            ev = self._video_load_ev = Clock.schedule_once(self._do_video_load,
                                                           -1)
        ev()

    def on_source(self, instance, value):
        # we got a value, try to see if we have an image for it
        self._load_thumbnail()
        self._load_annotations()
        if self._video is not None:
            self._video.unload()
            self._video = None
        if value:
            self._trigger_video_load()

    def on_image_overlay_play(self, instance, value):
        self._image.image_overlay_play = value

    def on_image_loading(self, instance, value):
        self._image.image_loading = value

    def _load_thumbnail(self):
        if not self.container:
            return
        self.container.clear_widgets()
        # get the source, remove extension, and use png
        thumbnail = self.thumbnail
        if not thumbnail:
            filename = self.source.rsplit('.', 1)
            thumbnail = filename[0] + '.png'
            if not exists(thumbnail):
                thumbnail = ''
        self._image = VideoPlayerPreview(source=thumbnail, video=self)
        self.container.add_widget(self._image)

    def _load_annotations(self):
        if not self.container:
            return
        self._annotations_labels = []
        annotations = self.annotations
        if not annotations:
            filename = self.source.rsplit('.', 1)
            annotations = filename[0] + '.jsa'
        if exists(annotations):
            with open(annotations, 'r') as fd:
                self._annotations = load(fd)
        if self._annotations:
            for ann in self._annotations:
                self._annotations_labels.append(
                    VideoPlayerAnnotation(annotation=ann))

    def on_state(self, instance, value):
        if self._video is not None:
            self._video.state = value

    def _set_state(self, instance, value):
        self.state = value

    def _do_video_load(self, *largs):
        self._video = Video(source=self.source, state=self.state,
                            volume=self.volume, pos_hint={'x': 0, 'y': 0},
                            **self.options)
        self._video.bind(texture=self._play_started,
                         duration=self.setter('duration'),
                         position=self.setter('position'),
                         volume=self.setter('volume'),
                         state=self._set_state)

    def on_play(self, instance, value):
        value = 'play' if value else 'stop'
        return self.on_state(instance, value)

    def on_volume(self, instance, value):
        if not self._video:
            return
        self._video.volume = value

    def on_position(self, instance, value):
        labels = self._annotations_labels
        if not labels:
            return
        for label in labels:
            start = label.start
            duration = label.duration
            if start > value or (start + duration) < value:
                if label.parent:
                    label.parent.remove_widget(label)
            elif label.parent is None:
                self.container.add_widget(label)

    def seek(self, percent, precise=True):
        '''Change the position to a percentage of duration.

        :Parameters:
            `percent`: float or int
                Position to seek, must be between 0-1.
            `precise`: bool, defaults to True
                Precise seeking is slower, but seeks to exact requested
                percent.

        .. warning::
            Calling seek() before the video is loaded has no effect.

        .. versionadded:: 1.2.0

        .. versionchanged:: 1.10.1
            The `precise` keyword argument has been added.
        '''
        if not self._video:
            return
        self._video.seek(percent, precise=precise)

    def _play_started(self, instance, value):
        self.container.clear_widgets()
        self.container.add_widget(self._video)

    def on_touch_down(self, touch):
        if not self.collide_point(*touch.pos):
            return False
        if touch.is_double_tap and self.allow_fullscreen:
            self.fullscreen = not self.fullscreen
            return True
        return super(VideoPlayer, self).on_touch_down(touch)

    def on_fullscreen(self, instance, value):
        window = self.get_parent_window()
        if not window:
            Logger.warning('VideoPlayer: Cannot switch to fullscreen, '
                           'window not found.')
            if value:
                self.fullscreen = False
            return
        if not self.parent:
            Logger.warning('VideoPlayer: Cannot switch to fullscreen, '
                           'no parent.')
            if value:
                self.fullscreen = False
            return

        if value:
            self._fullscreen_state = state = {
                'parent': self.parent,
                'pos': self.pos,
                'size': self.size,
                'pos_hint': self.pos_hint,
                'size_hint': self.size_hint,
                'window_children': window.children[:]}

            # remove all window children
            for child in window.children[:]:
                window.remove_widget(child)

            # put the video in fullscreen
            if state['parent'] is not window:
                state['parent'].remove_widget(self)
            window.add_widget(self)

            # ensure the video widget is in 0, 0, and the size will be
            # readjusted
            self.pos = (0, 0)
            self.size = (100, 100)
            self.pos_hint = {}
            self.size_hint = (1, 1)
        else:
            state = self._fullscreen_state
            window.remove_widget(self)
            for child in state['window_children']:
                window.add_widget(child)
            self.pos_hint = state['pos_hint']
            self.size_hint = state['size_hint']
            self.pos = state['pos']
            self.size = state['size']
            if state['parent'] is not window:
                state['parent'].add_widget(self)
Exemple #21
0
class SendScreen(Screen):
    send_button_text = StringProperty('send')
    send_button_disabled = BooleanProperty(False)

    has_code = BooleanProperty(False)
    code = StringProperty('…')

    file_name = StringProperty('…')
    file_size = StringProperty('…')

    def on_pre_enter(self):
        """
        Reset the labels and buttons and init a magic wormhole instance.

        This method is called just before the user enters this screen.
        """
        self.send_button_disabled = True
        self.send_button_text = 'waiting for code'

        self.has_code = False
        self.code = '…'

        self.file_path = None
        self.file_name = '…'
        self.file_size = '…'

        self.wormhole = Wormhole()

        def update_code(code):
            self.has_code = True
            self.code = code
            self.send_button_disabled = False
            self.send_button_text = 'send'
            Clipboard.copy(code)

        deferred = self.wormhole.generate_code()
        deferred.addCallbacks(update_code, ErrorPopup.show)

    def set_file(self, path):
        """
        Set the file to be sent down the wormhole.

        This method is called when the the user selects the file to send using
        the file chooser. It is also called directly by the App instance when
        the app has been started or resumed via a send file intent on Android.
        """
        try:
            path = os.path.normpath(path)
            assert os.path.exists(path) and os.path.isfile(path)
        except:
            ErrorPopup.show(
                ('There is something wrong about the file you chose. '
                 'One possible reason is an issue with some Androids '
                 'where a file cannot be directly selected from the '
                 '"Downloads" section and instead you have to reach it '
                 'some other way, e.g. "Phone" -> "Downloads".'))
            self.file_path = None
            self.file_name = '…'
            self.file_size = '…'
        else:
            self.file_path = path
            self.file_name = os.path.basename(self.file_path)
            self.file_size = humanize.naturalsize(
                os.stat(self.file_path).st_size)

    def open_file_chooser(self):
        """
        Open a file chooser so that the user can select a file to send down the
        wormhole. On Android, this could be preceded by asking for permissions.

        This method is called when the user releases the "choose file" button.
        """
        def handle_selection(selection):
            if selection:
                self.set_file(selection[0])

        def show_error():
            ErrorPopup.show(
                ('You cannot send a file if the app cannot access it.'))

        @ensure_storage_perms(show_error)
        def open_file_chooser():
            filechooser.open_file(title='Choose a file to send',
                                  on_selection=handle_selection)

        open_file_chooser()

    def send(self):
        """
        Send the selected file down the wormhole.

        This method is called when the user releases the send button.
        """
        if not self.file_path:
            return ErrorPopup.show('Please choose a file to send.')

        def exchange_keys():
            self.send_button_disabled = True
            self.send_button_text = 'exchanging keys'
            deferred = self.wormhole.exchange_keys(timeout=600)
            deferred.addCallbacks(send_file, ErrorPopup.show)

        def send_file(verifier):
            self.send_button_disabled = True
            self.send_button_text = 'sending file'
            deferred = self.wormhole.send_file(self.file_path)
            deferred.addCallbacks(show_done, ErrorPopup.show)

        def show_done(hex_digest):
            self.send_button_disabled = True
            self.send_button_text = 'done'

        exchange_keys()

    def on_leave(self):
        """
        Close the magic wormhole instance, if this is still open.

        This method is called when the user leaves this screen.
        """
        self.wormhole.close()
Exemple #22
0
class KbDesbloqueio(ModalView):

    txt_cabecalho = StringProperty(
        'DIGITE CÓDIGO DE ACESSO PARA CANCELAR ALARME')
    txt_acesso_usu_view = StringProperty()
    txt_acesso_sys = ''
    acesso_confirmado = BooleanProperty(False)

    conta_tentativas = NumericProperty(0)

    acesso = JsonStore('json/acesso.json')

    if len(acesso) <= 1:
        tst_cad_acess = False
        txt_cabecalho = 'DIGITE CÓDIGO DE ACESSO PARA CANCELAR ALARME'

    def kb_keypress(self, keypressed):
        st_sys = JsonStore('json/status.json')
        if self.conta_tentativas <= 3:
            self.txt_cabecalho = 'DIGITE CÓDIGO DE ACESSO PARA CANCELAR ALARME'
            if len(self.txt_acesso_sys) != 6:
                self.txt_acesso_sys = self.txt_acesso_sys + keypressed
                self.txt_acesso_usu_view = self.txt_acesso_usu_view + '*'
                if len(self.txt_acesso_sys) == 6:
                    if self.consulta_acesso(self.txt_acesso_sys):
                        self.acesso_confirmado = True
                        self.txt_cabecalho = 'PRESSIONE "CONFIRMA" PARA CANCELAR ALARME'
                        self.conta_tentativas = 0
                        if self.conta_tentativas <= 3 and self.acesso_confirmado:
                            st_sys.put('alrm', st=1)
                            self.dismiss()
                    else:
                        self.acesso_confirmado = False
                        self.txt_cabecalho = 'CÓDIGO INVÁLIDO'
                        self.txt_acesso_usu_view = ''
                        self.txt_acesso_sys = ''
                        self.conta_tentativas += 1
                        st_sys.put('alrm', st=2)
                        if self.conta_tentativas > 3:
                            st_sys.put('alrm', st=3)
                            self.dismiss()
        else:
            self.dismiss()

    def consulta_acesso(self, codigo_acesso):
        if self.acesso.exists(codigo_acesso):
            self.txt_cabecalho = 'PRESSIONE "CONFIRMA" PARA CACELAR ALARME'
            return True
        else:
            self.txt_cabecalho = 'CÓDIGO INVÁLIDO'
            return False

    def ajusta_st_alr_desbloqueio(self):

        st_sys = JsonStore('json/status.json')

        print st_sys.get(
            'alrm')['st'], self.acesso_confirmado, self.conta_tentativas

        if st_sys.get('alrm')['st'] == 2 and self.conta_tentativas > 3:
            st_sys.put('alrm', st=3)
            self.dismiss()
        elif st_sys.get(
                'alrm'
        )['st'] == 1 and self.acesso_confirmado and self.conta_tentativas <= 3:
            st_sys.put('alrm', st=1)
            self.dismiss()
        elif st_sys.get(
                'alrm'
        )['st'] == 2 and self.acesso_confirmado and self.conta_tentativas <= 3:
            st_sys.put('alrm', st=1)
        elif st_sys.get(
                'alrm'
        )['st'] == 2 and not self.acesso_confirmado and self.conta_tentativas <= 3:
            st_sys.put('alrm', st=2)
Exemple #23
0
class Collection(EventDispatcher):
    games = ListProperty([])
    lazy_games = ListProperty([])
    name = StringProperty('Collection')
    defaultdir = StringProperty('./games/unsaved')
    lazy_loaded = BooleanProperty(False)
    finished_loading = BooleanProperty(False)

    def __init__(self, *args, **kwargs):
        if platform() == 'android':
            self.defaultdir = '/sdcard/noGo/collections/unsaved'
        super(Collection, self).__init__(*args, **kwargs)

    def __str__(self):
        return 'SGF collection {0} with {1} games'.format(
            self.name, self.number_of_games())

    def __repr__(self):
        return self.__str__()

    def remove_sgf(self, sgf):
        self.finish_lazy_loading()
        if sgf in self.games:
            self.games.remove(sgf)

    def get_default_dir(self):
        return '.' + '/' + self.name

    def number_of_games(self):
        if not self.finished_loading:
            return len(self.lazy_games)
        else:
            return len(self.games)

    def lazy_from_list(self, l):
        name, defaultdir, games = l
        self.name = name
        self.defaultdir = defaultdir
        self.lazy_games = games
        self.lazy_loaded = True
        self.finished_loading = False
        return self

    def finish_lazy_loading(self):
        print 'Finishing lazy loading'
        if not self.finished_loading and self.lazy_loaded:
            print 'need to load', len(self.lazy_games)
            for game in self.lazy_games:
                print 'currently doing', game
                try:
                    colsgf = CollectionSgf(collection=self).load(game)
                    self.games.append(colsgf)
                except IOError:
                    print '(lazy) Tried to load sgf that doesn\'t seem to exist. Skipping.'
                    print 'game was', game
            self.lazy_games = []
            self.finished_loading = True

    def from_list(self, l):
        name, defaultdir, games = l
        self.name = name
        self.defaultdir = defaultdir
        for game in games:
            try:
                print 'finish loading', game
                colsgf = CollectionSgf(collection=self).load(game)
                self.games.append(colsgf)
            except IOError:
                print 'Tried to load sgf that doesn\'t seem to exist. Skipping.'
                print 'game was', game
        #self.games = map(lambda j: CollectionSgf(collection=self).load(j),games)
        return self

    def as_list(self):
        self.finish_lazy_loading()
        return [
            self.name, self.defaultdir,
            map(lambda j: j.get_filen(), self.games)
        ]

    def serialise(self):
        self.finish_lazy_loading()
        return json.dumps([SERIALISATION_VERSION, self.as_list()])

    def save(self):
        self.finish_lazy_loading()
        if platform() == 'android':
            filen = '/sdcard/noGo/' + self.name + '.json'
        else:
            filen = '.' + '/collections/' + self.name + '.json'
        with open(filen, 'w') as fileh:
            fileh.write(self.serialise())
        return filen

    def get_filen(self):
        filen = '.' + '/collections/' + self.name + '.json'
        return filen

    def from_file(self, filen):
        # print 'Trying to load collection from',filen
        with open(filen, 'r') as fileh:
            jsonstr = fileh.read()
        #print 'File contents are',jsonstr
        version, selflist = json.loads(jsonstr)
        selflist = jsonconvert(selflist)
        return self.lazy_from_list(selflist)

    def add_game(self, can_change_name=True):
        self.finish_lazy_loading()
        game = CollectionSgf(collection=self, can_change_name=can_change_name)
        game.filen = game.get_default_filen() + '.sgf'
        self.games.append(game)
        self.save()
        return game

    def random_sgf(self):
        self.finish_lazy_loading()
        return random.choice(self.games)
Exemple #24
0
class ButtonBase(Button, Navigation):
    """Button widget that includes theme options and a variety of small additions over a basic button."""

    warn = BooleanProperty(False)
    target_background = ListProperty()
    target_text = ListProperty()
    background_animation = ObjectProperty()
    text_animation = ObjectProperty()
    last_disabled = False
    menu = BooleanProperty(False)
    toggle = BooleanProperty(False)

    button_update = BooleanProperty()

    def __init__(self, **kwargs):
        self.background_animation = Animation()
        self.text_animation = Animation()
        app = App.get_running_app()
        self.background_color = app.theme.button_up
        self.target_background = self.background_color
        self.color = app.theme.button_text
        self.target_text = self.color
        super(ButtonBase, self).__init__(**kwargs)

    def on_button_update(self, *_):
        Clock.schedule_once(lambda x: self.set_color())

    def set_color(self, instant=False):
        app = App.get_running_app()
        if self.disabled:
            self.set_text(app.theme.button_disabled_text, instant=instant)
            self.set_background(app.theme.button_disabled, instant=instant)
        else:
            self.set_text(app.theme.button_text, instant=instant)
            if self.menu:
                if self.state == 'down':
                    self.set_background(app.theme.button_menu_down,
                                        instant=True)
                else:
                    self.set_background(app.theme.button_menu_up,
                                        instant=instant)
            elif self.toggle:
                if self.state == 'down':
                    self.set_background(app.theme.button_toggle_true,
                                        instant=instant)
                else:
                    self.set_background(app.theme.button_toggle_false,
                                        instant=instant)

            elif self.warn:
                if self.state == 'down':
                    self.set_background(app.theme.button_warn_down,
                                        instant=True)
                else:
                    self.set_background(app.theme.button_warn_up,
                                        instant=instant)
            else:
                if self.state == 'down':
                    self.set_background(app.theme.button_down, instant=True)
                else:
                    self.set_background(app.theme.button_up, instant=instant)

    def on_disabled(self, *_):
        self.set_color()

    def on_menu(self, *_):
        self.set_color(instant=True)

    def on_toggle(self, *_):
        self.set_color(instant=True)

    def on_warn(self, *_):
        self.set_color(instant=True)

    def on_state(self, *_):
        self.set_color()

    def set_background(self, color, instant=False):
        if self.target_background == color:
            return
        app = App.get_running_app()
        self.background_animation.stop(self)
        if app.animations and not instant:
            self.background_animation = Animation(
                background_color=color, duration=app.animation_length)
            self.background_animation.start(self)
        else:
            self.background_color = color
        self.target_background = color

    def set_text(self, color, instant=False):
        if self.target_text == color:
            return
        app = App.get_running_app()
        self.text_animation.stop(self)
        if app.animations and not instant:
            self.text_animation = Animation(color=color,
                                            duration=app.animation_length)
            self.text_animation.start(self)
        else:
            self.color = color
        self.target_text = color
Exemple #25
0
class InstallerApp(App):
    status = StringProperty(
        'By hitting the [i]Install[/i] button you agree to delete all '
        f'contents of [b]{LOCAL_REPO_PATH}[/b] if it exists.')
    install_progress = NumericProperty()
    error = BooleanProperty(False)
    progress_items = OrderedDict({
        'Discover local repository': None,
        'Clean the installation directory': None,
        'Create the installation directory': None,
        'Clone the remote distribution repository': None,
        'Fetch the local repository': None,
        'Reset the local repository': None,
        'Add a shortcut': None,
        'Start the app': None
    })
    PROGRESS_MESSAGES = [
        'Using a magnifying glass to find the local repository..',
        'Training the installation directory..',
        'Creativiting the installation directory..',
        'Trying to bait the repository down off the cloud..',
        'Trying to get an attention from the remote origin..',
        'Hardly trying to reset the local repository..',
        'Creating a lovely shortcut..', 'Waking up XtremeUpdater..'
    ]

    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)

        self.Theme = Theme

        # animate window
        def on_frame(__):
            _height = Window.height
            Window.size = Window.width, 1
            Animation(size=[Window.width, _height], d=1,
                      t='out_expo').start(Window)

        Clock.schedule_once(on_frame)

    def install(self):
        self.error = False
        self.root.ids.install_btn.disabled = True
        self.root.ids.progressbar.normal = 0
        Animation(color=Theme.prim, d=.5).start(self.root.ids.progressbar)
        for key in self.progress_items.keys():
            self.progress_items[key] = None

        start_new(self._install, ())

    def _install(self):
        success = True
        keys = tuple(self.progress_items.keys())
        for index, ret in enumerate(Installer.full_install()):
            _last = index == len(keys) - 1

            # white text
            if ret and not _last:
                self.progress_items[keys[index + 1]] = -1
                self.status = self.PROGRESS_MESSAGES[index + 1]
            # colored text
            self.progress_items[keys[index]] = ret
            # reload todo-list
            self.root.ids.todo.items = self.progress_items
            self.root.ids.todo.reload()
            # error
            if not ret:
                success = False
                self.root.ids.install_btn.disabled = False
                self.error = True
                self.status = ("I'm sorry.. I was not a good boy this time. "
                               'An error occured, please contact our support.')
                break

        # Animate progressbar
        Animation(color=((not success) * .7 + .3, success * .7 + .3, .3, 1),
                  d=.5).start(self.root.ids.progressbar)
        self.root.ids.progressbar.normal = 1
        if success:
            self.status = 'You can now use [i]XtremeUpdater[/i]. Nice!'

        Clock.schedule_once(self.stop, 5)
Exemple #26
0
class NormalDropDown(DropDown):
    """Dropdown menu class with some nice animations."""

    show_percent = NumericProperty(1)
    invert = BooleanProperty(False)
    basic_animation = BooleanProperty(False)

    def open(self, *args, **kwargs):
        if self.parent:
            self.dismiss()
            return
        app = App.get_running_app()
        super(NormalDropDown, self).open(*args, **kwargs)

        if app.animations:
            if self.basic_animation:
                #Dont do fancy child opacity animation
                self.opacity = 0
                self.show_percent = 1
                anim = Animation(opacity=1, duration=app.animation_length)
                anim.start(self)
            else:
                #determine if we opened up or down
                attach_to_window = self.attach_to.to_window(
                    *self.attach_to.pos)
                if attach_to_window[1] > self.pos[1]:
                    self.invert = True
                    children = reversed(self.container.children)
                else:
                    self.invert = False
                    children = self.container.children

                #Animate background
                self.opacity = 1
                self.show_percent = 0
                anim = Animation(show_percent=1, duration=app.animation_length)
                anim.start(self)

                if len(self.container.children) > 0:
                    item_delay = app.animation_length / len(
                        self.container.children)
                else:
                    item_delay = 0

                for i, w in enumerate(children):
                    anim = (
                        Animation(duration=i * item_delay) +
                        Animation(opacity=1, duration=app.animation_length))
                    w.opacity = 0
                    anim.start(w)
        else:
            self.opacity = 1

    def dismiss(self, *args, **kwargs):
        app = App.get_running_app()
        if app.animations:
            anim = Animation(opacity=0, duration=app.animation_length)
            anim.start(self)
            anim.bind(on_complete=self.finish_dismiss)
        else:
            self.finish_dismiss()

    def finish_dismiss(self, *_):
        super(NormalDropDown, self).dismiss()
Exemple #27
0
class Splitter(BoxLayout):
    '''See module documentation.

    :Events:
        `on_press`:
            Fired when the splitter is pressed.
        `on_release`:
            Fired when the splitter is released.

    .. versionchanged:: 1.6.0
        Added `on_press` and `on_release` events.

    '''

    border = ListProperty([4, 4, 4, 4])
    '''Border used for the
    :class:`~kivy.graphics.vertex_instructions.BorderImage`
    graphics instruction.

    This must be a list of four values: (top, right, bottom, left).
    Read the BorderImage instructions for more information about how
    to use it.

    :attr:`border` is a :class:`~kivy.properties.ListProperty` and
    defaults to (4, 4, 4, 4).
    '''

    strip_cls = ObjectProperty(SplitterStrip)
    '''Specifies the class of the resize Strip.

    :attr:`strip_cls` is an :class:`kivy.properties.ObjectProperty` and
    defaults to :class:`~kivy.uix.splitter.SplitterStrip`, which is of type
    :class:`~kivy.uix.button.Button`.

    .. versionchanged:: 1.8.0
        If you set a string, the :class:`~kivy.factory.Factory` will be used to
        resolve the class.

    '''

    sizable_from = OptionProperty('left', options=(
        'left', 'right', 'top', 'bottom'))
    '''Specifies whether the widget is resizable. Options are::
        `left`, `right`, `top` or `bottom`

    :attr:`sizable_from` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `left`.
    '''

    strip_size = NumericProperty('10pt')
    '''Specifies the size of resize strip

    :attr:`strp_size` is a :class:`~kivy.properties.NumericProperty`
    defaults to `10pt`
    '''

    min_size = NumericProperty('100pt')
    '''Specifies the minimum size beyond which the widget is not resizable.

    :attr:`min_size` is a :class:`~kivy.properties.NumericProperty` and
    defaults to `100pt`.
    '''

    max_size = NumericProperty('500pt')
    '''Specifies the maximum size beyond which the widget is not resizable.

    :attr:`max_size` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `500pt`.
    '''

    _parent_proportion = NumericProperty(0.)
    '''(internal) Specifies the distance that the slider has travelled
    across its parent, used to automatically maintain a sensible
    position if the parent is resized.

    :attr:`_parent_proportion` is a
    :class:`~kivy.properties.NumericProperty` and defaults to 0.

    .. versionadded:: 1.9.0
    '''

    _bound_parent = ObjectProperty(None, allownone=True)
    '''(internal) References the widget whose size is currently being
    tracked by :attr:`_parent_proportion`.

    :attr:`_bound_parent` is a
    :class:`~kivy.properties.ObjectProperty` and defaults to None.

    .. versionadded:: 1.9.0
    '''

    keep_within_parent = BooleanProperty(False)
    '''If True, will limit the splitter to stay within its parent widget.

    :attr:`keep_within_parent` is a
    :class:`~kivy.properties.BooleanProperty` and defaults to False.

    .. versionadded:: 1.9.0
    '''

    rescale_with_parent = BooleanProperty(False)
    '''If True, will automatically change size to take up the same
    proportion of the parent widget when it is resized, while
    staying within :attr:`min_size` and :attr:`max_size`. As long as
    these attributes can be satisfied, this stops the
    :class:`Splitter` from exceeding the parent size during rescaling.

    :attr:`rescale_with_parent` is a
    :class:`~kivy.properties.BooleanProperty` and defaults to False.

    .. versionadded:: 1.9.0
    '''

    __events__ = ('on_press', 'on_release')

    def __init__(self, **kwargs):
        self._container = None
        self._strip = None
        super(Splitter, self).__init__(**kwargs)

        do_size = self._do_size
        fbind = self.fast_bind
        fbind('max_size', do_size)
        fbind('min_size', do_size)
        fbind('parent', self._rebind_parent)

    def on_sizable_from(self, instance, sizable_from):
        if not instance._container:
            return

        sup = super(Splitter, instance)
        _strp = instance._strip
        if _strp:
            # remove any previous binds
            _strp.unbind(on_touch_down=instance.strip_down)
            _strp.unbind(on_touch_move=instance.strip_move)
            _strp.unbind(on_touch_up=instance.strip_up)
            self.unbind(disabled=_strp.setter('disabled'))

            sup.remove_widget(instance._strip)
        else:
            cls = instance.strip_cls
            if isinstance(cls, string_types):
                cls = Factory.get(cls)
            instance._strip = _strp = cls()

        sz_frm = instance.sizable_from[0]
        if sz_frm in ('l', 'r'):
            _strp.size_hint = None, 1
            _strp.width = instance.strip_size
            instance.orientation = 'horizontal'
            instance.unbind(strip_size=_strp.setter('width'))
            instance.bind(strip_size=_strp.setter('width'))
        else:
            _strp.size_hint = 1, None
            _strp.height = instance.strip_size
            instance.orientation = 'vertical'
            instance.unbind(strip_size=_strp.setter('height'))
            instance.bind(strip_size=_strp.setter('height'))

        index = 1
        if sz_frm in ('r', 'b'):
            index = 0
        sup.add_widget(_strp, index)

        _strp.bind(on_touch_down=instance.strip_down)
        _strp.bind(on_touch_move=instance.strip_move)
        _strp.bind(on_touch_up=instance.strip_up)
        _strp.disabled = self.disabled
        self.bind(disabled=_strp.setter('disabled'))

    def add_widget(self, widget, index=0):
        if self._container or not widget:
            return Exception('Splitter accepts only one Child')
        self._container = widget
        sz_frm = self.sizable_from[0]
        if sz_frm in ('l', 'r'):
            widget.size_hint_x = 1
        else:
            widget.size_hint_y = 1

        index = 0
        if sz_frm in ('r', 'b'):
            index = 1
        super(Splitter, self).add_widget(widget, index)
        self.on_sizable_from(self, self.sizable_from)

    def remove_widget(self, widget, *largs):
        super(Splitter, self).remove_widget(widget)
        if widget == self._container:
            self._container = None

    def clear_widgets(self):
        self.remove_widget(self._container)

    def strip_down(self, instance, touch):
        if not instance.collide_point(*touch.pos):
            return False
        touch.grab(self)
        self.dispatch('on_press')

    def on_press(self):
        pass

    def _rebind_parent(self, instance, new_parent):
        if self._bound_parent is not None:
            self._bound_parent.unbind(size=self.rescale_parent_proportion)
        if self.parent is not None:
            new_parent.bind(size=self.rescale_parent_proportion)
        self._bound_parent = new_parent
        self.rescale_parent_proportion()

    def rescale_parent_proportion(self, *args):
        if self.rescale_with_parent:
            parent_proportion = self._parent_proportion
            if self.sizable_from in ('top', 'bottom'):
                new_height = parent_proportion * self.parent.height
                self.height = max(self.min_size, min(new_height, self.max_size))
            else:
                new_width = parent_proportion * self.parent.width
                self.width = max(self.min_size, min(new_width, self.max_size))

    def _do_size(self, instance, value):
        if self.sizable_from[0] in ('l', 'r'):
            self.width = max(self.min_size, min(self.width, self.max_size))
        else:
            self.height = max(self.min_size, min(self.height, self.max_size))

    def strip_move(self, instance, touch):
        if touch.grab_current is not instance:
            return False
        max_size = self.max_size
        min_size = self.min_size
        sz_frm = self.sizable_from[0]

        if sz_frm in ('t', 'b'):
            diff_y = (touch.dy)
            if self.keep_within_parent:
                if sz_frm == 't' and (self.top + diff_y) > self.parent.top:
                    diff_y = self.parent.top - self.top
                elif sz_frm == 'b' and (self.y + diff_y) < self.parent.y:
                    diff_y = self.parent.y - self.y
            if sz_frm == 'b':
                diff_y *= -1
            if self.size_hint_y:
                self.size_hint_y = None
            if self.height > 0:
                self.height += diff_y
            else:
                self.height = 1

            height = self.height
            self.height = max(min_size, min(height, max_size))

            self._parent_proportion = self.height / self.parent.height
        else:
            diff_x = (touch.dx)
            if self.keep_within_parent:
                if sz_frm == 'l' and (self.x + diff_x) < self.parent.x:
                    diff_x = self.parent.x - self.x
                elif (sz_frm == 'r' and
                      (self.right + diff_x) > self.parent.right):
                    diff_x = self.parent.right - self.right
            if sz_frm == 'l':
                diff_x *= -1
            if self.size_hint_x:
                self.size_hint_x = None
            if self.width > 0:
                self.width += diff_x
            else:
                self.width = 1

            width = self.width
            self.width = max(min_size, min(width, max_size))

            self._parent_proportion = self.width / self.parent.width

    def strip_up(self, instance, touch):
        if touch.grab_current is not instance:
            return

        if touch.is_double_tap:
            max_size = self.max_size
            min_size = self.min_size
            sz_frm = self.sizable_from[0]
            s = self.size

            if sz_frm in ('t', 'b'):
                if self.size_hint_y:
                    self.size_hint_y = None
                if s[1] - min_size <= max_size - s[1]:
                    self.height = max_size
                else:
                    self.height = min_size
            else:
                if self.size_hint_x:
                    self.size_hint_x = None
                if s[0] - min_size <= max_size - s[0]:
                    self.width = max_size
                else:
                    self.width = min_size
        touch.ungrab(instance)
        self.dispatch('on_release')

    def on_release(self):
        pass
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_checkpoint = NumericProperty(0)

    auto_connect = BooleanProperty(False)
    def on_auto_connect(self, instance, x):
        host, port, protocol, proxy, auto_connect = self.network.get_parameters()
        self.network.set_parameters(host, port, protocol, proxy, self.auto_connect)
    def toggle_auto_connect(self, x):
        self.auto_connect = not self.auto_connect

    def choose_server_dialog(self, popup):
        from .uix.dialogs.choice_dialog import ChoiceDialog
        protocol = 's'
        def cb2(host):
            from electrum_smart 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):
            for index, b in self.network.blockchains.items():
                if name == self.network.get_blockchain_name(b):
                    self.network.follow_chain(index)
                    #self.block
        names = [self.network.blockchains[b].get_name() for b in chains]
        if len(names) >1:
            ChoiceDialog(_('Choose your chain'), names, '', cb).open()

    use_rbf = BooleanProperty(False)
    def on_use_rbf(self, instance, x):
        self.electrum_config.set_key('use_rbf', self.use_rbf, False)

    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() != 'bitcoin':
            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_history()

    def on_history(self, d):
        Logger.info("on_history")
        self._trigger_update_history()

    def _get_bu(self):
        return self.electrum_config.get('base_unit', 'mSMART')

    def _set_bu(self, value):
        assert value in base_units.keys()
        self.electrum_config.set_key('base_unit', value, True)
        self._trigger_update_status()
        self._trigger_update_history()

    base_unit = AliasProperty(_get_bu, _set_bu)
    status = StringProperty('')
    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 = _('Electrum App')
        self.electrum_config = config = kwargs.get('config', None)
        self.language = config.get('language', 'en')
        self.network = network = kwargs.get('network', None)
        if self.network:
            self.num_blocks = self.network.get_local_height()
            self.num_nodes = len(self.network.get_interfaces())
            host, port, protocol, proxy_config, auto_connect = self.network.get_parameters()
            self.server_host = host
            self.server_port = port
            self.auto_connect = auto_connect
            self.proxy_config = proxy_config if proxy_config else {}

        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', False)
        self.use_change = config.get('use_change', True)
        self.use_unconfirmed = not config.get('confirmed_only', False)

        # 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_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 wallet_name(self):
        return os.path.basename(self.wallet.storage.path) if self.wallet else ' '

    def on_pr(self, pr):
        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_smart.bitcoin import base_decode, is_address
        data = data.strip()
        if is_address(data):
            self.set_URI(data)
            return
        if data.startswith('smartcash:'):
            self.set_URI(data)
            return
        # try to decode transaction
        from electrum_smart.transaction import Transaction
        from electrum_smart.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_smart.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('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_smart.util import format_time
        fund = req.get('fund')
        isaddr = 'y'
        popup = Builder.load_file('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):
        from .uix.dialogs.qr_dialog import QRDialog
        popup = QRDialog(title, data, show_text)
        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):
            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)
        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('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()
        # 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 bitcoin: 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 = ['updated', 'status', 'new_transaction', 'verified', 'interfaces']
            self.network.register_callback(self.on_network_event, interests)
            self.network.register_callback(self.on_fee, ['fee'])
            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, instance, wallet):
        if wallet:
            wallet.start_threads(self.daemon.network)
            self.daemon.add_wallet(wallet)
            self.load_wallet(wallet)

    def load_wallet_by_name(self, path):
        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:
            Logger.debug('Electrum: Wallet not found. Launching install wizard')
            storage = WalletStorage(path)
            wizard = Factory.InstallWizard(self.electrum_config, storage)
            wizard.bind(on_wizard_complete=self.on_wizard_complete)
            action = wizard.storage.get_action()
            wizard.run(action)

    def on_stop(self):
        Logger.info('on_stop')
        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()
        else:
            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

        self.funds_error = False
        # setup UX
        self.screens = {}

        #setup lazy imports for mainscreen
        Factory.register('AnimatedPopup',
                         module='electrum_smart_gui.kivy.uix.dialogs')
        Factory.register('QRCodeWidget',
                         module='electrum_smart_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 = "icons/electrum-smart.png"
        self.tabs = self.root.ids['tabs']

    def update_interfaces(self, dt):
        self.num_nodes = len(self.network.get_interfaces())
        self.num_chains = len(self.network.get_blockchains())
        chain = self.network.blockchain()
        self.blockchain_checkpoint = chain.get_checkpoint()
        self.blockchain_name = chain.get_name()
        if self.network.interface:
            self.server_host = self.network.interface.host

    def on_network_event(self, event, *args):
        Logger.info('network event: '+ event)
        if event == 'interfaces':
            self._trigger_update_interfaces()
        elif event == 'updated':
            self._trigger_update_wallet()
            self._trigger_update_status()
        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.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)

    def update_status(self, *dt):
        self.num_blocks = self.network.get_local_height()
        if not self.wallet:
            self.status = _("No Wallet")
            return
        if self.network is None or not self.network.is_running():
            status = _("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:
                status = _("Synchronizing...")
            elif server_lag > 1:
                status = _("Server lagging")
            else:
                status = ''
        else:
            status = _("Disconnected")
        self.status = self.wallet.basename() + (' [size=15dp](%s)[/size]'%status if status else '')
        # balance
        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):
        inputs = self.wallet.get_spendable_coins(None, self.electrum_config)
        addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
        outputs = [(TYPE_ADDRESS, addr, '!')]
        tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)
        amount = tx.output_value()
        return format_satoshis_plain(amount, self.decimal_point())

    def format_amount(self, x, is_diff=False, whitespaces=False):
        return format_satoshis(x, is_diff, 0, self.decimal_point(), 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('Electrum', message,
                            app_icon=icon, app_name='Electrum')
        except ImportError:
            Logger.Error('Notification: needs plyer; `sudo 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.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 set_send(self, address, amount, label, message):
        self.send_payment(address, amount=amount, label=label, message=message)

    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/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 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):
        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
        Clock.schedule_once(lambda dt: on_success(tx))

    def _broadcast_thread(self, tx, on_complete):
        ok, txid = self.network.broadcast(tx)
        Clock.schedule_once(lambda dt: on_complete(ok, txid))

    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:
                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:" + basename)
        d = os.listdir(dirname)
        name = 'default_wallet'
        new_path = os.path.join(dirname, name)
        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))
Exemple #29
0
class Image(Widget):
    '''Image class, see module documentation for more information.
    '''

    source = StringProperty(None)
    '''Filename / source of your image.

    :attr:`source` is a :class:`~kivy.properties.StringProperty` and
    defaults to None.
    '''

    texture = ObjectProperty(None, allownone=True)
    '''Texture object of the image. The texture represents the original, loaded
    image texture. It is stretched and positioned during rendering according to
    the :attr:`allow_stretch` and :attr:`keep_ratio` properties.

    Depending of the texture creation, the value will be a
    :class:`~kivy.graphics.texture.Texture` or a
    :class:`~kivy.graphics.texture.TextureRegion` object.

    :attr:`texture` is an :class:`~kivy.properties.ObjectProperty` and defaults
    to None.
    '''

    texture_size = ListProperty([0, 0])
    '''Texture size of the image. This represents the original, loaded image
    texture size.

    .. warning::

        The texture size is set after the texture property. So if you listen to
        the change on :attr:`texture`, the property texture_size will not be
        up-to-date. Use self.texture.size instead.
    '''
    def get_image_ratio(self):
        if self.texture:
            return self.texture.width / float(self.texture.height)
        return 1.

    mipmap = BooleanProperty(False)
    '''Indicate if you want OpenGL mipmapping to be applied to the texture.
    Read :ref:`mipmap` for more information.

    .. versionadded:: 1.0.7

    :attr:`mipmap` is a :class:`~kivy.properties.BooleanProperty` and defaults
    to False.
    '''

    image_ratio = AliasProperty(get_image_ratio,
                                bind=('texture', ),
                                cache=True)
    '''Ratio of the image (width / float(height).

    :attr:`image_ratio` is an :class:`~kivy.properties.AliasProperty` and is
    read-only.
    '''

    color = ColorProperty([1, 1, 1, 1])
    '''Image color, in the format (r, g, b, a). This attribute can be used to
    'tint' an image. Be careful: if the source image is not gray/white, the
    color will not really work as expected.

    .. versionadded:: 1.0.6

    :attr:`color` is a :class:`~kivy.properties.ColorProperty` and defaults to
    [1, 1, 1, 1].

    .. versionchanged:: 2.0.0
        Changed from :class:`~kivy.properties.ListProperty` to
        :class:`~kivy.properties.ColorProperty`.
    '''

    allow_stretch = BooleanProperty(False)
    '''If True, the normalized image size will be maximized to fit in the image
    box. Otherwise, if the box is too tall, the image will not be
    stretched more than 1:1 pixels.

    .. versionadded:: 1.0.7

    :attr:`allow_stretch` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to False.
    '''

    keep_ratio = BooleanProperty(True)
    '''If False along with allow_stretch being True, the normalized image
    size will be maximized to fit in the image box and ignores the aspect
    ratio of the image.
    Otherwise, if the box is too tall, the image will not be stretched more
    than 1:1 pixels.

    .. versionadded:: 1.0.8

    :attr:`keep_ratio` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to True.
    '''

    keep_data = BooleanProperty(False)
    '''If True, the underlaying _coreimage will store the raw image data.
    This is useful when performing pixel based collision detection.

    .. versionadded:: 1.3.0

    :attr:`keep_data` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to False.
    '''

    anim_delay = NumericProperty(.25)
    '''Delay the animation if the image is sequenced (like an animated gif).
    If anim_delay is set to -1, the animation will be stopped.

    .. versionadded:: 1.0.8

    :attr:`anim_delay` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0.25 (4 FPS).
    '''

    anim_loop = NumericProperty(0)
    '''Number of loops to play then stop animating. 0 means keep animating.

    .. versionadded:: 1.9.0

    :attr:`anim_loop` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0.
    '''

    nocache = BooleanProperty(False)
    '''If this property is set True, the image will not be added to the
    internal cache. The cache will simply ignore any calls trying to
    append the core image.

    .. versionadded:: 1.6.0

    :attr:`nocache` is a :class:`~kivy.properties.BooleanProperty` and defaults
    to False.
    '''

    def get_norm_image_size(self):
        if not self.texture:
            return list(self.size)
        ratio = self.image_ratio
        w, h = self.size
        tw, th = self.texture.size

        # ensure that the width is always maximized to the containter width
        if self.allow_stretch:
            if not self.keep_ratio:
                return [w, h]
            iw = w
        else:
            iw = min(w, tw)
        # calculate the appropriate height
        ih = iw / ratio
        # if the height is too higher, take the height of the container
        # and calculate appropriate width. no need to test further. :)
        if ih > h:
            if self.allow_stretch:
                ih = h
            else:
                ih = min(h, th)
            iw = ih * ratio
        return [iw, ih]

    norm_image_size = AliasProperty(get_norm_image_size,
                                    bind=('texture', 'size', 'allow_stretch',
                                          'image_ratio', 'keep_ratio'),
                                    cache=True)
    '''Normalized image size within the widget box.

    This size will always fit the widget size and will preserve the image
    ratio.

    :attr:`norm_image_size` is an :class:`~kivy.properties.AliasProperty` and
    is read-only.
    '''

    def __init__(self, **kwargs):
        self._coreimage = None
        self._loops = 0
        update = self.texture_update
        fbind = self.fbind
        fbind('source', update)
        fbind('mipmap', update)
        super().__init__(**kwargs)

    def texture_update(self, *largs):
        if not self.source:
            self._clear_core_image()
            return
        source = resource_find(self.source)
        if not source:
            Logger.error('Image: Not found <%s>' % self.source)
            self._clear_core_image()
            return
        if self._coreimage:
            self._coreimage.unbind(on_texture=self._on_tex_change)
        try:
            self._coreimage = image = CoreImage(source,
                                                mipmap=self.mipmap,
                                                anim_delay=self.anim_delay,
                                                keep_data=self.keep_data,
                                                nocache=self.nocache)
        except Exception:
            Logger.error('Image: Error loading <%s>' % self.source)
            self._clear_core_image()
            image = self._coreimage
        if image:
            image.bind(on_texture=self._on_tex_change)
            self.texture = image.texture

    def on_anim_delay(self, instance, value):
        if self._coreimage is None:
            return
        self._coreimage.anim_delay = value
        if value < 0:
            self._coreimage.anim_reset(False)

    def on_texture(self, instance, value):
        self.texture_size = value.size if value else [0, 0]

    def _clear_core_image(self):
        if self._coreimage:
            self._coreimage.unbind(on_texture=self._on_tex_change)
        self.texture = None
        self._coreimage = None
        self._loops = 0

    def _on_tex_change(self, *largs):
        # update texture from core image
        self.texture = self._coreimage.texture
        ci = self._coreimage
        if self.anim_loop and ci._anim_index == len(ci._image.textures) - 1:
            self._loops += 1
            if self.anim_loop == self._loops:
                ci.anim_reset(False)
                self._loops = 0

    def reload(self):
        '''Reload image from disk. This facilitates re-loading of
        images from disk in case the image content changes.

        .. versionadded:: 1.3.0

        Usage::

            im = Image(source = '1.jpg')
            # -- do something --
            im.reload()
            # image will be re-loaded from disk

        '''
        self.remove_from_cache()
        old_source = self.source
        self.source = ''
        self.source = old_source

    def remove_from_cache(self):
        '''Remove image from cache.

        .. versionadded:: 2.0.0
        '''
        if self._coreimage:
            self._coreimage.remove_from_cache()

    def on_nocache(self, *args):
        if self.nocache:
            self.remove_from_cache()
            if self._coreimage:
                self._coreimage._nocache = True
Exemple #30
0
class BaseSnackbar(MDCard):
    """
    :Events:
        :attr:`on_open`
            Called when a dialog is opened.
        :attr:`on_dismiss`
            When the front layer rises.

    Abstract base class for all Snackbars.
    This class handles sizing, positioning, shape and events for Snackbars

    All Snackbars will be made off of this `BaseSnackbar`.

    `BaseSnackbar` will always try to fill the remainder of the screen with
    your Snackbar.

    To make your Snackbar dynamic and symetric with snackbar_x.

    Set size_hint_x like below:

    .. code-block:: python

        size_hint_z = (
            Window.width - (snackbar_x * 2)
        ) / Window.width
    """

    duration = NumericProperty(3)
    """
    The amount of time that the snackbar will stay on screen for.

    :attr:`duration` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `3`.
    """

    auto_dismiss = BooleanProperty(True)
    """
    Whether to use automatic closing of the snackbar or not.

    :attr:`auto_dismiss` is a :class:`~kivy.properties.BooleanProperty`
    and defaults to `'True'`.
    """

    bg_color = ListProperty()
    """
    Snackbar background.

    :attr:`bg_color` is a :class:`~kivy.properties.ListProperty`
    and defaults to `'[]'`.
    """

    buttons = ListProperty()
    """
    Snackbar buttons.

    :attr:`buttons` is a :class:`~kivy.properties.ListProperty`
    and defaults to `'[]'`
    """

    radius = ListProperty([5, 5, 5, 5])
    """
    Snackbar radius.

    :attr:`radius` is a :class:`~kivy.properties.ListProperty`
    and defaults to `'[5, 5, 5, 5]'`
    """

    snackbar_animation_dir = OptionProperty(
        "Bottom",
        options=[
            "Top",
            "Bottom",
            "Left",
            "Right",
        ],
    )
    """
    Snackbar animation direction.

    Available options are: `"Top"`, `"Bottom"`, `"Left"`, `"Right"`

    :attr:`snackbar_animation_dir` is an :class:`~kivy.properties.OptionProperty`
    and defaults to `'Bottom'`.
    """

    snackbar_x = NumericProperty("0dp")
    """
    The snackbar x position in the screen

    :attr:`snackbar_x` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0dp`.
    """

    snackbar_y = NumericProperty("0dp")
    """
    The snackbar x position in the screen

    :attr:`snackbar_y` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0dp`.
    """

    _interval = 0

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.register_event_type("on_open")
        self.register_event_type("on_dismiss")

    def dismiss(self, *args):
        """Dismiss the snackbar."""

        def dismiss(interval):
            if self.snackbar_animation_dir == "Top":
                anim = Animation(y=(Window.height + self.height), d=0.2)
            elif self.snackbar_animation_dir == "Left":
                anim = Animation(x=-self.width, d=0.2)
            elif self.snackbar_animation_dir == "Right":
                anim = Animation(x=Window.width, d=0.2)
            else:
                anim = Animation(y=-self.height, d=0.2)

            anim.bind(
                on_complete=lambda *args: Window.parent.remove_widget(self)
            )
            anim.start(self)

        Clock.schedule_once(dismiss, 0.5)
        self.dispatch("on_dismiss")

    def open(self):
        """Show the snackbar."""

        def wait_interval(interval):
            self._interval += interval
            if self._interval > self.duration:
                self.dismiss()
                Clock.unschedule(wait_interval)
                self._interval = 0

        for c in Window.parent.children:
            if isinstance(c, BaseSnackbar):
                return

        if self.snackbar_y > (Window.height - self.height):
            self.snackbar_y = Window.height - self.height

        self._calc_radius()

        if self.size_hint_x == 1:
            self.size_hint_x = (Window.width - self.snackbar_x) / Window.width

        if (
            self.snackbar_animation_dir == "Top"
            or self.snackbar_animation_dir == "Bottom"
        ):
            self.x = self.snackbar_x

            if self.snackbar_animation_dir == "Top":
                self.y = Window.height + self.height
            else:
                self.y = -self.height

            Window.parent.add_widget(self)

            if self.snackbar_animation_dir == "Top":
                anim = Animation(
                    y=self.snackbar_y
                    if self.snackbar_y != 0
                    else Window.height - self.height,
                    d=0.2,
                )
            else:
                anim = Animation(
                    y=self.snackbar_y if self.snackbar_y != 0 else 0, d=0.2
                )

        elif (
            self.snackbar_animation_dir == "Left"
            or self.snackbar_animation_dir == "Right"
        ):
            self.y = self.snackbar_y

            if self.snackbar_animation_dir == "Left":
                self.x = -Window.width
            else:
                self.x = Window.width

            Window.parent.add_widget(self)
            anim = Animation(
                x=self.snackbar_x if self.snackbar_x != 0 else 0, d=0.2
            )

        if self.auto_dismiss:
            anim.bind(
                on_complete=lambda *args: Clock.schedule_interval(
                    wait_interval, 0
                )
            )
        anim.start(self)
        self.dispatch("on_open")

    def on_open(self, *args):
        """Called when a dialog is opened."""

    def on_dismiss(self, *args):
        """Called when the dialog is closed."""

    def on_buttons(self, instance, value):
        def on_buttons(interval):
            for button in value:
                if issubclass(button.__class__, (BaseButton,)):
                    self.add_widget(button)
                else:
                    raise ValueError(
                        f"The {button} object must be inherited from the base class <BaseButton>"
                    )

        Clock.schedule_once(on_buttons)

    def _calc_radius(self):
        if (
            self.snackbar_animation_dir == "Top"
            or self.snackbar_animation_dir == "Bottom"
        ):

            if self.snackbar_y == 0 and self.snackbar_x == 0:

                if self.size_hint_x == 1:
                    self.radius = [0, 0, 0, 0]
                else:
                    if self.snackbar_animation_dir == "Top":
                        self.radius = [0, 0, self.radius[2], 0]
                    else:
                        self.radius = [0, self.radius[1], 0, 0]

            elif self.snackbar_y != 0 and self.snackbar_x == 0:

                if self.size_hint_x == 1:
                    self.radius = [0, 0, 0, 0]
                else:
                    if self.snackbar_y >= Window.height - self.height:
                        self.radius = [0, 0, self.radius[2], 0]
                    else:
                        self.radius = [0, self.radius[1], self.radius[2], 0]

            elif self.snackbar_y == 0 and self.snackbar_x != 0:

                if self.size_hint_x == 1:
                    if self.snackbar_animation_dir == "Top":
                        self.radius = [0, 0, 0, self.radius[3]]
                    else:
                        self.radius = [self.radius[0], 0, 0, 0]
                else:
                    if self.snackbar_animation_dir == "Top":
                        self.radius = [0, 0, self.radius[2], self.radius[3]]
                    else:
                        self.radius = [self.radius[0], self.radius[1], 0, 0]

            else:  # self.snackbar_y != 0 and self.snackbar_x != 0

                if self.size_hint_x == 1:
                    self.radius = [self.radius[0], 0, 0, self.radius[3]]
                elif self.snackbar_y >= Window.height - self.height:
                    self.radius = [0, 0, self.radius[2], self.radius[3]]

        elif (
            self.snackbar_animation_dir == "Left"
            or self.snackbar_animation_dir == "Right"
        ):

            if self.snackbar_y == 0 and self.snackbar_x == 0:

                if self.size_hint_x == 1:
                    self.radius = [0, 0, 0, 0]
                else:
                    self.radius = [0, self.radius[1], 0, 0]

            elif self.snackbar_y != 0 and self.snackbar_x == 0:

                if self.size_hint_x == 1:
                    self.radius = [0, 0, 0, 0]
                else:
                    self.radius = [0, self.radius[1], self.radius[2], 0]

            elif self.snackbar_y == 0 and self.snackbar_x != 0:

                if self.size_hint_x == 1:
                    self.radius = [self.radius[0], 0, 0, 0]
                else:
                    self.radius = [self.radius[0], self.radius[1], 0, 0]

            else:  # self.snackbar_y != 0 and self.snackbar_x != 0

                if self.size_hint_x == 1:
                    if self.snackbar_y >= Window.height - self.height:
                        self.radius = [0, 0, 0, self.radius[3]]
                    else:
                        self.radius = [self.radius[0], 0, 0, self.radius[3]]
                elif self.snackbar_y >= Window.height - self.height:
                    self.radius = [0, 0, self.radius[2], self.radius[3]]