예제 #1
0
class LabelBase(ButtonBehavior, Widget):
    bg_color = ObjectProperty((0, 1, .5, 1))
    color = ObjectProperty((0, 0, 0, 1))
    text = StringProperty()
    font_size = StringProperty('30dp')
    border_size = NumericProperty("3dp")
    valign = OptionProperty("middle", options=["top", "middle", "bottom"])
    halign = OptionProperty("center", options=["left", "center", "right"])
    tag = ObjectProperty(None, allowNone=True)
    clicable = BooleanProperty(False)
    pres = ObjectProperty(None)

    def __init__(self, **kargs):
        super(LabelBase, self).__init__(**kargs)
        self.__shape_down__ = None

    __listchild__ = ListProperty([])

    def on___listchild__(self, w, val):
        if self.container != None:
            for w in self.__listchild__:
                self.container.add_widget(w)

    def add_widget(self, widget):
        if type(widget) is LabelDecorators:
            super(LabelBase, self).add_widget(widget)
        else:
            self.__listchild__.append(widget)

    def on_color(self, w, val):
        if "#" in val:
            val = "".join(val)
            self.color = get_color_from_hex(val)
        else:
            self.color = val

    def on_bg_color(self, w, val):
        if "#" in val:
            val = "".join(val)
            self.bg_color = get_color_from_hex(val)
        else:
            self.bg_color = val

    def collide_point(self, x, y):
        return (x > self.x
                and x < self.x + self.width) and (y > self.y
                                                  and y < self.y + self.height)

    def on_touch_down(self, touch, *args):
        if self.collide_point(touch.x, touch.y) and self.clicable:
            size = dp(70), dp(70)
            w, h = size
            pos = touch.x - w / 2, touch.y - h / 2
            if self.__shape_down__ == None:
                self.__shape_down__ = InstructionGroup(group="__shape_down__")
            else:
                self.container.canvas.before.remove(self.__shape_down__)
                self.__shape_down__.clear()
            color = Color(0, 0, 0, .4)
            self.__shape_down__.add(color)
            self.__shape_down__.add(Ellipse(pos=pos, size=size))
            self.container.canvas.before.add(self.__shape_down__)
            Clock.schedule_once(self.remove_shape_down, .2)
            press = datetime.now()
            super(LabelBase, self).on_touch_down(touch)
            return True

    def remove_shape_down(self, dt):
        self.container.canvas.before.remove(self.__shape_down__)
        self.__shape_down__.clear()
예제 #2
0
파일: pedido.py 프로젝트: marceleta/nvendas
class Item_pedido(GridLayout, Button, RecycleDataViewBehavior):

    cols = 1
    selectable = BooleanProperty(True)
    selected = BooleanProperty(False)
    index = None
    _data_selected = None
    data = None
    alterar_qtd = None
    remove_produto = None
    tempo_precionado = 0

    def refresh_view_attrs(self, rv, index, data):

        self.data = data
        self.index = index
        #self.codigo_text = data['produto']['codigo']
        self.descricao_text = data['produto']['descricao']
        self.quantidade_text = data['produto']['quantidade']
        self.preco_text = data['produto']['preco']

        preco = float(data['produto']['preco'])
        quantidade = float(data['produto']['quantidade'])

        total = round(preco * quantidade)

        self.total_text = str(total)

        return super(Item_pedido, self).refresh_view_attrs(rv, index, data)

    def on_touch_down(self, touch):
        print('on_touch_down')
        self.alterar_qtd = None
        self.remove_produto = None
        self.tempo_precionado = 0
        Clock.schedule_interval(self._remover_produto, 0.5)

        return True  #super().on_touch_down(touch)

    def on_touch_up(self, touch):
        print('on_touch_up')
        Clock.unschedule(self._remover_produto)

        if self.tempo_precionado < 3:
            if self.alterar_qtd == None:
                print('tempo_precionado: {}'.format(self.tempo_precionado))
                self.alterar_qtd = Alterar_qtd(self._data_selected)
                self.alterar_qtd.open()

        return True  #super().on_touch_up(touch)

    def _remover_produto(self, *args):
        print('_remover_produto')
        self.tempo_precionado = self.tempo_precionado + 1
        print('_remover_produto:tempo_precionado: {}'.format(
            self.tempo_precionado))
        if self.tempo_precionado >= 3:
            print('if show remover_produto')
            if self.remove_produto == None:
                self.remove_produto = Remover_produto(self._data_selected)
                self.remove_produto.open()
        if self.tempo_precionado > 4:
            Clock.unschedule(self._remover_produto)

    def apply_selection(self, rv, index, is_selected):

        self.selected = is_selected
        self._data_selected = rv.data[index]

        return True
예제 #3
0
class QRCodeWidget(FloatLayout):

    show_border = BooleanProperty(True)
    '''Whether to show border around the widget.

    :data:`show_border` is a :class:`~kivy.properties.BooleanProperty`,
    defaulting to `True`.
    '''

    data = StringProperty(None, allow_none=True)
    ''' Data using which the qrcode is generated.

    :data:`data` is a :class:`~kivy.properties.StringProperty`, defaulting to
    `None`.
    '''

    background_color = ListProperty((1, 1, 1, 1))
    ''' Background color of the background of the widget to be displayed
    behind the qrcode.

    :data:`background_color` is a :class:`~kivy.properties.ListProperty`,
    defaulting to `(1, 1, 1, 1)`.
    '''

    loading_image = StringProperty('data/images/image-loading.gif')
    '''Intermediate image to be displayed while the widget ios being loaded.
    
    :data:`loading_image` is a :class:`~kivy.properties.StringProperty`,
    defaulting to `'data/images/image-loading.gif'`.
    '''
    def __init__(self, **kwargs):
        super(QRCodeWidget, self).__init__(**kwargs)
        self.addr = None
        self.qr = None
        self._qrtexture = None

    def on_data(self, instance, value):
        if not (self.canvas or value):
            return
        img = self.ids.get('qrimage', None)

        if not img:
            # if texture hasn't yet been created delay the texture updation
            Clock.schedule_once(lambda dt: self.on_data(instance, value))
            return
        img.anim_delay = .25
        img.source = self.loading_image
        Thread(target=partial(self.generate_qr, value)).start()

    def generate_qr(self, value):
        self.set_addr(value)
        self.update_qr()

    def set_addr(self, addr):
        if self.addr == addr:
            return
        MinSize = 210 if len(addr) < 128 else 500
        self.setMinimumSize((MinSize, MinSize))
        self.addr = addr
        self.qr = None

    def update_qr(self):
        if not self.addr and self.qr:
            return
        QRCode = qrcode.QRCode
        L = qrcode.constants.ERROR_CORRECT_L
        addr = self.addr
        try:
            self.qr = qr = QRCode(
                version=None,
                error_correction=L,
                box_size=10,
                border=0,
            )
            qr.add_data(addr)
            qr.make(fit=True)
        except Exception as e:
            print(e)
            self.qr = None
        self.update_texture()

    def setMinimumSize(self, size):
        # currently unused, do we need this?
        self._texture_size = size

    def _create_texture(self, k, dt):
        self._qrtexture = texture = Texture.create(size=(k, k), colorfmt='rgb')
        # don't interpolate texture
        texture.min_filter = 'nearest'
        texture.mag_filter = 'nearest'

    def update_texture(self):
        if not self.addr:
            return

        matrix = self.qr.get_matrix()
        k = len(matrix)

        # create the texture in main UI thread otherwise
        # this will lead to memory corruption
        Clock.schedule_once(partial(self._create_texture, k), -1)

        cr, cg, cb, ca = self.background_color[:]
        cr, cg, cb = cr * 255, cg * 255, cb * 255
        ###used bytearray for python 3.5 eliminates need for btext
        buff = bytearray()
        for r in range(k):
            for c in range(k):
                buff.extend([0, 0, 0] if matrix[r][c] else [cr, cg, cb])

        # then blit the buffer
        # join not neccesarry when using a byte array
        # buff =''.join(map(chr, buff))
        # update texture in UI thread.
        Clock.schedule_once(lambda dt: self._upd_texture(buff))

    def _upd_texture(self, buff):
        texture = self._qrtexture
        if not texture:
            # if texture hasn't yet been created delay the texture updation
            Clock.schedule_once(lambda dt: self._upd_texture(buff))
            return

        texture.blit_buffer(buff, colorfmt='rgb', bufferfmt='ubyte')
        texture.flip_vertical()
        img = self.ids.qrimage
        img.anim_delay = -1
        img.texture = texture
        img.canvas.ask_update()
예제 #4
0
class ScrollView(StencilView):
    '''ScrollView class. See module documentation for more information.

    :Events:
        `on_scroll_start`
            Generic event fired when scrolling starts from touch.
        `on_scroll_move`
            Generic event fired when scrolling move from touch.
        `on_scroll_stop`
            Generic event fired when scrolling stops from touch.

    .. versionchanged:: 1.9.0
        `on_scroll_start`, `on_scroll_move` and `on_scroll_stop` events are
        now dispatched when scrolling to handle nested ScrollViews.

    .. versionchanged:: 1.7.0
        `auto_scroll`, `scroll_friction`, `scroll_moves`, `scroll_stoptime' has
        been deprecated, use :attr:`effect_cls` instead.
    '''

    scroll_distance = NumericProperty(_scroll_distance)
    '''Distance to move before scrolling the :class:`ScrollView`, in pixels. As
    soon as the distance has been traveled, the :class:`ScrollView` will start
    to scroll, and no touch event will go to children.
    It is advisable that you base this value on the dpi of your target device's
    screen.

    :attr:`scroll_distance` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 20 (pixels), according to the default value in user
    configuration.
    '''

    scroll_wheel_distance = NumericProperty('20sp')
    '''Distance to move when scrolling with a mouse wheel.
    It is advisable that you base this value on the dpi of your target device's
    screen.

    .. versionadded:: 1.8.0

    :attr:`scroll_wheel_distance` is a
    :class:`~kivy.properties.NumericProperty` , defaults to 20 pixels.
    '''

    scroll_timeout = NumericProperty(_scroll_timeout)
    '''Timeout allowed to trigger the :attr:`scroll_distance`, in milliseconds.
    If the user has not moved :attr:`scroll_distance` within the timeout,
    the scrolling will be disabled, and the touch event will go to the
    children.

    :attr:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 55 (milliseconds) according to the default value in user
    configuration.

    .. versionchanged:: 1.5.0
        Default value changed from 250 to 55.
    '''

    scroll_x = NumericProperty(0.)
    '''X scrolling value, between 0 and 1. If 0, the content's left side will
    touch the left side of the ScrollView. If 1, the content's right side will
    touch the right side.

    This property is controled by :class:`ScrollView` only if
    :attr:`do_scroll_x` is True.

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

    scroll_y = NumericProperty(1.)
    '''Y scrolling value, between 0 and 1. If 0, the content's bottom side will
    touch the bottom side of the ScrollView. If 1, the content's top side will
    touch the top side.

    This property is controled by :class:`ScrollView` only if
    :attr:`do_scroll_y` is True.

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

    do_scroll_x = BooleanProperty(True)
    '''Allow scroll on X axis.

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

    do_scroll_y = BooleanProperty(True)
    '''Allow scroll on Y axis.

    :attr:`do_scroll_y` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to True.
    '''
    def _get_do_scroll(self):
        return (self.do_scroll_x, self.do_scroll_y)

    def _set_do_scroll(self, value):
        if type(value) in (list, tuple):
            self.do_scroll_x, self.do_scroll_y = value
        else:
            self.do_scroll_x = self.do_scroll_y = bool(value)

    do_scroll = AliasProperty(_get_do_scroll,
                              _set_do_scroll,
                              bind=('do_scroll_x', 'do_scroll_y'))
    '''Allow scroll on X or Y axis.

    :attr:`do_scroll` is a :class:`~kivy.properties.AliasProperty` of
    (:attr:`do_scroll_x` + :attr:`do_scroll_y`)
    '''

    def _get_vbar(self):
        # must return (y, height) in %
        # calculate the viewport size / scrollview size %
        if self._viewport is None:
            return 0, 1.
        vh = self._viewport.height
        h = self.height
        if vh < h or vh == 0:
            return 0, 1.
        ph = max(0.01, h / float(vh))
        sy = min(1.0, max(0.0, self.scroll_y))
        py = (1. - ph) * sy
        return (py, ph)

    vbar = AliasProperty(_get_vbar,
                         None,
                         bind=('scroll_y', '_viewport', 'viewport_size'))
    '''Return a tuple of (position, size) of the vertical scrolling bar.

    .. versionadded:: 1.2.0

    The position and size are normalized between 0-1, and represent a
    percentage of the current scrollview height. This property is used
    internally for drawing the little vertical bar when you're scrolling.

    :attr:`vbar` is a :class:`~kivy.properties.AliasProperty`, readonly.
    '''

    def _get_hbar(self):
        # must return (x, width) in %
        # calculate the viewport size / scrollview size %
        if self._viewport is None:
            return 0, 1.
        vw = self._viewport.width
        w = self.width
        if vw < w or vw == 0:
            return 0, 1.
        pw = max(0.01, w / float(vw))
        sx = min(1.0, max(0.0, self.scroll_x))
        px = (1. - pw) * sx
        return (px, pw)

    hbar = AliasProperty(_get_hbar,
                         None,
                         bind=('scroll_x', '_viewport', 'viewport_size'))
    '''Return a tuple of (position, size) of the horizontal scrolling bar.

    .. versionadded:: 1.2.0

    The position and size are normalized between 0-1, and represent a
    percentage of the current scrollview height. This property is used
    internally for drawing the little horizontal bar when you're scrolling.

    :attr:`vbar` is a :class:`~kivy.properties.AliasProperty`, readonly.
    '''

    bar_color = ListProperty([.7, .7, .7, .9])
    '''Color of horizontal / vertical scroll bar, in RGBA format.

    .. versionadded:: 1.2.0

    :attr:`bar_color` is a :class:`~kivy.properties.ListProperty` and defaults
    to [.7, .7, .7, .9].
    '''

    bar_inactive_color = ListProperty([.7, .7, .7, .2])
    '''Color of horizontal / vertical scroll bar (in RGBA format), when no
    scroll is happening.

    .. versionadded:: 1.9.0

    :attr:`bar_inactive_color` is a
    :class:`~kivy.properties.ListProperty` and defaults to [.7, .7, .7, .2].
    '''

    bar_width = NumericProperty('2dp')
    '''Width of the horizontal / vertical scroll bar. The width is interpreted
    as a height for the horizontal bar.

    .. versionadded:: 1.2.0

    :attr:`bar_width` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 2.
    '''

    bar_pos_x = OptionProperty('bottom', options=('top', 'bottom'))
    '''Which side of the ScrollView the horizontal scroll bar should go
    on. Possible values are 'top' and 'bottom'.

    .. versionadded:: 1.8.0

    :attr:`bar_pos_x` is an :class:`~kivy.properties.OptionProperty`,
    defaults to 'bottom'.

    '''

    bar_pos_y = OptionProperty('right', options=('left', 'right'))
    '''Which side of the ScrollView the vertical scroll bar should go
    on. Possible values are 'left' and 'right'.

    .. versionadded:: 1.8.0

    :attr:`bar_pos_y` is an :class:`~kivy.properties.OptionProperty` and
    defaults to 'right'.

    '''

    bar_pos = ReferenceListProperty(bar_pos_x, bar_pos_y)
    '''Which side of the scroll view to place each of the bars on.

    :attr:`bar_pos` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:attr:`bar_pos_x`, :attr:`bar_pos_y`)
    '''

    bar_margin = NumericProperty(0)
    '''Margin between the bottom / right side of the scrollview when drawing
    the horizontal / vertical scroll bar.

    .. versionadded:: 1.2.0

    :attr:`bar_margin` is a :class:`~kivy.properties.NumericProperty`, default
    to 0
    '''

    effect_cls = ObjectProperty(DampedScrollEffect, allownone=True)
    '''Class effect to instantiate for X and Y axis.

    .. versionadded:: 1.7.0

    :attr:`effect_cls` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to :class:`DampedScrollEffect`.

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

    '''

    effect_x = ObjectProperty(None, allownone=True)
    '''Effect to apply for the X axis. If None is set, an instance of
    :attr:`effect_cls` will be created.

    .. versionadded:: 1.7.0

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

    effect_y = ObjectProperty(None, allownone=True)
    '''Effect to apply for the Y axis. If None is set, an instance of
    :attr:`effect_cls` will be created.

    .. versionadded:: 1.7.0

    :attr:`effect_y` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to None, read-only.
    '''

    viewport_size = ListProperty([0, 0])
    '''(internal) Size of the internal viewport. This is the size of your only
    child in the scrollview.
    '''

    scroll_type = OptionProperty(['content'],
                                 options=(['content'], ['bars'],
                                          ['bars',
                                           'content'], ['content', 'bars']))
    '''Sets the type of scrolling to use for the content of the scrollview.
    Available options are: ['content'], ['bars'], ['bars', 'content'].

    .. versionadded:: 1.8.0

    :attr:`scroll_type` is a :class:`~kivy.properties.OptionProperty`, defaults
    to ['content'].
    '''

    # private, for internal use only

    _viewport = ObjectProperty(None, allownone=True)
    _bar_color = ListProperty([0, 0, 0, 0])
    _effect_x_start_width = None
    _effect_y_start_height = None
    _update_effect_bounds_ev = None
    _bind_inactive_bar_color_ev = None

    def _set_viewport_size(self, instance, value):
        self.viewport_size = value

    def on__viewport(self, instance, value):
        if value:
            value.bind(size=self._set_viewport_size)
            self.viewport_size = value.size

    __events__ = ('on_scroll_start', 'on_scroll_move', 'on_scroll_stop')

    def __init__(self, **kwargs):
        self._touch = None
        self._trigger_update_from_scroll = Clock.create_trigger(
            self.update_from_scroll, -1)
        # create a specific canvas for the viewport
        from kivy.graphics import PushMatrix, Translate, PopMatrix, Canvas
        self.canvas_viewport = Canvas()
        self.canvas = Canvas()
        with self.canvas_viewport.before:
            PushMatrix()
            self.g_translate = Translate(0, 0)
        with self.canvas_viewport.after:
            PopMatrix()

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

        self.register_event_type('on_scroll_start')
        self.register_event_type('on_scroll_move')
        self.register_event_type('on_scroll_stop')

        # now add the viewport canvas to our canvas
        self.canvas.add(self.canvas_viewport)

        effect_cls = self.effect_cls
        if isinstance(effect_cls, string_types):
            effect_cls = Factory.get(effect_cls)
        if self.effect_x is None and effect_cls is not None:
            self.effect_x = effect_cls(target_widget=self._viewport)
        if self.effect_y is None and effect_cls is not None:
            self.effect_y = effect_cls(target_widget=self._viewport)

        trigger_update_from_scroll = self._trigger_update_from_scroll
        update_effect_widget = self._update_effect_widget
        update_effect_x_bounds = self._update_effect_x_bounds
        update_effect_y_bounds = self._update_effect_y_bounds
        fbind = self.fbind
        fbind('width', update_effect_x_bounds)
        fbind('height', update_effect_y_bounds)
        fbind('viewport_size', self._update_effect_bounds)
        fbind('_viewport', update_effect_widget)
        fbind('scroll_x', trigger_update_from_scroll)
        fbind('scroll_y', trigger_update_from_scroll)
        fbind('pos', trigger_update_from_scroll)
        fbind('size', trigger_update_from_scroll)
        # fbind('scroll_y', self._update_effect_bounds)
        # fbind('scroll_x', self._update_effect_bounds)

        update_effect_widget()
        update_effect_x_bounds()
        update_effect_y_bounds()

    def on_effect_x(self, instance, value):
        if value:
            value.bind(scroll=self._update_effect_x)
            value.target_widget = self._viewport

    def on_effect_y(self, instance, value):
        if value:
            value.bind(scroll=self._update_effect_y)
            value.target_widget = self._viewport

    def on_effect_cls(self, instance, cls):
        if isinstance(cls, string_types):
            cls = Factory.get(cls)
        self.effect_x = cls(target_widget=self._viewport)
        self.effect_x.bind(scroll=self._update_effect_x)
        self.effect_y = cls(target_widget=self._viewport)
        self.effect_y.bind(scroll=self._update_effect_y)

    def _update_effect_widget(self, *args):
        if self.effect_x:
            self.effect_x.target_widget = self._viewport
        if self.effect_y:
            self.effect_y.target_widget = self._viewport

    def _update_effect_x_bounds(self, *args):
        if not self._viewport or not self.effect_x:
            return
        self.effect_x.min = -(self.viewport_size[0] - self.width)
        self.effect_x.max = 0
        self.effect_x.value = self.effect_x.min * self.scroll_x

    def _update_effect_y_bounds(self, *args):
        if not self._viewport or not self.effect_y:
            return
        self.effect_y.min = -(self.viewport_size[1] - self.height)
        self.effect_y.max = 0
        self.effect_y.value = self.effect_y.min * self.scroll_y

    def _update_effect_bounds(self, *args):
        if not self._viewport:
            return
        if self.effect_x:
            self._update_effect_x_bounds()
        if self.effect_y:
            self._update_effect_y_bounds()

    def _update_effect_x(self, *args):
        vp = self._viewport
        if not vp or not self.effect_x:
            return

        if self.effect_x.is_manual:
            sw = vp.width - self._effect_x_start_width
        else:
            sw = vp.width - self.width
        if sw < 1:
            return
        sx = self.effect_x.scroll / float(sw)
        self.scroll_x = -sx
        self._trigger_update_from_scroll()

    def _update_effect_y(self, *args):
        vp = self._viewport
        if not vp or not self.effect_y:
            return
        if self.effect_y.is_manual:
            sh = vp.height - self._effect_y_start_height
        else:
            sh = vp.height - self.height
        if sh < 1:
            return
        sy = self.effect_y.scroll / float(sh)
        self.scroll_y = -sy
        self._trigger_update_from_scroll()

    def to_local(self, x, y, **k):
        tx, ty = self.g_translate.xy
        return x - tx, y - ty

    def to_parent(self, x, y, **k):
        tx, ty = self.g_translate.xy
        return x + tx, y + ty

    def _apply_transform(self, m, pos=None):
        tx, ty = self.g_translate.xy
        m.translate(tx, ty, 0)
        return super(ScrollView, self)._apply_transform(m, (0, 0))

    def simulate_touch_down(self, touch):
        # at this point the touch is in parent coords
        touch.push()
        touch.apply_transform_2d(self.to_local)
        ret = super(ScrollView, self).on_touch_down(touch)
        touch.pop()
        return ret

    def on_touch_down(self, touch):
        if self.dispatch('on_scroll_start', touch):
            self._touch = touch
            touch.grab(self)
            return True

    def _touch_in_handle(self, pos, size, touch):
        x, y = pos
        width, height = size
        return x <= touch.x <= x + width and y <= touch.y <= y + height

    def on_scroll_start(self, touch, check_children=True):
        if check_children:
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if self.dispatch_children('on_scroll_start', touch):
                touch.pop()
                return True
            touch.pop()

        if not self.collide_point(*touch.pos):
            touch.ud[self._get_uid('svavoid')] = True
            return
        if self.disabled:
            return True
        if self._touch or (not (self.do_scroll_x or self.do_scroll_y)):
            return self.simulate_touch_down(touch)

        # handle mouse scrolling, only if the viewport size is bigger than the
        # scrollview size, and if the user allowed to do it
        vp = self._viewport
        if not vp:
            return True
        scroll_type = self.scroll_type
        ud = touch.ud
        scroll_bar = 'bars' in scroll_type

        # check if touch is in bar_x(horizontal) or bay_y(bertical)
        ud['in_bar_x'] = ud['in_bar_y'] = False
        width_scrollable = vp.width > self.width
        height_scrollable = vp.height > self.height
        bar_pos_x = self.bar_pos_x[0]
        bar_pos_y = self.bar_pos_y[0]

        d = {
            'b': True if touch.y < self.y + self.bar_width else False,
            't': True if touch.y > self.top - self.bar_width else False,
            'l': True if touch.x < self.x + self.bar_width else False,
            'r': True if touch.x > self.right - self.bar_width else False
        }
        if scroll_bar:
            if (width_scrollable and d[bar_pos_x]):
                ud['in_bar_x'] = True
            if (height_scrollable and d[bar_pos_y]):
                ud['in_bar_y'] = True

        if vp and 'button' in touch.profile and \
                touch.button.startswith('scroll'):
            btn = touch.button
            m = self.scroll_wheel_distance
            e = None

            if ((btn == 'scrolldown' and self.scroll_y >= 1)
                    or (btn == 'scrollup' and self.scroll_y <= 0)
                    or (btn == 'scrollleft' and self.scroll_x >= 1)
                    or (btn == 'scrollright' and self.scroll_x <= 0)):
                return False

            if (self.effect_x and self.do_scroll_y and height_scrollable
                    and btn in ('scrolldown', 'scrollup')):
                e = self.effect_x if ud['in_bar_x'] else self.effect_y

            elif (self.effect_y and self.do_scroll_x and width_scrollable
                  and btn in ('scrollleft', 'scrollright')):
                e = self.effect_y if ud['in_bar_y'] else self.effect_x

            if e:
                if btn in ('scrolldown', 'scrollleft'):
                    e.value = max(e.value - m, e.min)
                    e.velocity = 0
                elif btn in ('scrollup', 'scrollright'):
                    e.value = min(e.value + m, e.max)
                    e.velocity = 0
                touch.ud[self._get_uid('svavoid')] = True
                e.trigger_velocity_update()
            return True

        in_bar = ud['in_bar_x'] or ud['in_bar_y']
        if scroll_type == ['bars'] and not in_bar:
            return self.simulate_touch_down(touch)

        if in_bar:
            if (ud['in_bar_y'] and not self._touch_in_handle(
                    self._handle_y_pos, self._handle_y_size, touch)):
                self.scroll_y = (touch.y - self.y) / self.height
            elif (ud['in_bar_x'] and not self._touch_in_handle(
                    self._handle_x_pos, self._handle_x_size, touch)):
                self.scroll_x = (touch.x - self.x) / self.width

        # no mouse scrolling, so the user is going to drag the scrollview with
        # this touch.
        self._touch = touch
        uid = self._get_uid()

        ud[uid] = {
            'mode': 'unknown',
            'dx': 0,
            'dy': 0,
            'user_stopped': in_bar,
            'frames': Clock.frames,
            'time': touch.time_start
        }

        if self.do_scroll_x and self.effect_x and not ud['in_bar_x']:
            self._effect_x_start_width = self.width
            self.effect_x.start(touch.x)
            self._scroll_x_mouse = self.scroll_x
        if self.do_scroll_y and self.effect_y and not ud['in_bar_y']:
            self._effect_y_start_height = self.height
            self.effect_y.start(touch.y)
            self._scroll_y_mouse = self.scroll_y

        if not in_bar:
            Clock.schedule_once(self._change_touch_mode,
                                self.scroll_timeout / 1000.)
        return True

    def on_touch_move(self, touch):
        if self._touch is not touch:
            # touch is in parent
            touch.push()
            touch.apply_transform_2d(self.to_local)
            super(ScrollView, self).on_touch_move(touch)
            touch.pop()
            return self._get_uid() in touch.ud
        if touch.grab_current is not self:
            return True

        if touch.ud.get(self._get_uid()) is None:
            return super(ScrollView, self).on_touch_move(touch)

        touch.ud['sv.handled'] = {'x': False, 'y': False}
        if self.dispatch('on_scroll_move', touch):
            return True

    def on_scroll_move(self, touch):
        if self._get_uid('svavoid') in touch.ud:
            return False

        touch.push()
        touch.apply_transform_2d(self.to_local)
        if self.dispatch_children('on_scroll_move', touch):
            touch.pop()
            return True
        touch.pop()

        rv = True

        # By default this touch can be used to defocus currently focused
        # widget, like any touch outside of ScrollView.
        touch.ud['sv.can_defocus'] = True

        uid = self._get_uid()
        if uid not in touch.ud:
            self._touch = False
            return self.on_scroll_start(touch, False)
        ud = touch.ud[uid]

        # check if the minimum distance has been travelled
        if ud['mode'] == 'unknown':
            if not self.do_scroll_x and not self.do_scroll_y:
                # touch is in parent, but _change expects window coords
                touch.push()
                touch.apply_transform_2d(self.to_local)
                touch.apply_transform_2d(self.to_window)
                self._change_touch_mode()
                touch.pop()
                return
            ud['dx'] += abs(touch.dx)
            ud['dy'] += abs(touch.dy)
            if ((ud['dx'] > self.scroll_distance and self.do_scroll_x)
                    or (ud['dy'] > self.scroll_distance and self.do_scroll_y)):
                ud['mode'] = 'scroll'

        if ud['mode'] == 'scroll':
            if not touch.ud['sv.handled']['x'] and self.do_scroll_x \
                    and self.effect_x:
                width = self.width
                if touch.ud.get('in_bar_x', False):
                    dx = touch.dx / float(width - width * self.hbar[1])
                    self.scroll_x = min(max(self.scroll_x + dx, 0.), 1.)
                    self._trigger_update_from_scroll()
                else:
                    if self.scroll_type != ['bars']:
                        self.effect_x.update(touch.x)
                if self.scroll_x < 0 or self.scroll_x > 1:
                    rv = False
                else:
                    touch.ud['sv.handled']['x'] = True
                # Touch resulted in scroll should not defocus focused widget
                touch.ud['sv.can_defocus'] = False
            if not touch.ud['sv.handled']['y'] and self.do_scroll_y \
                    and self.effect_y:
                height = self.height
                if touch.ud.get('in_bar_y', False):
                    dy = touch.dy / float(height - height * self.vbar[1])
                    self.scroll_y = min(max(self.scroll_y + dy, 0.), 1.)
                    self._trigger_update_from_scroll()
                else:
                    if self.scroll_type != ['bars']:
                        self.effect_y.update(touch.y)
                if self.scroll_y < 0 or self.scroll_y > 1:
                    rv = False
                else:
                    touch.ud['sv.handled']['y'] = True
                # Touch resulted in scroll should not defocus focused widget
                touch.ud['sv.can_defocus'] = False
            ud['dt'] = touch.time_update - ud['time']
            ud['time'] = touch.time_update
            ud['user_stopped'] = True
        return rv

    def on_touch_up(self, touch):
        uid = self._get_uid('svavoid')
        if self._touch is not touch and uid not in touch.ud:
            # touch is in parents
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if super(ScrollView, self).on_touch_up(touch):
                touch.pop()
                return True
            touch.pop()
            return False

        if self.dispatch('on_scroll_stop', touch):
            touch.ungrab(self)
            if not touch.ud.get('sv.can_defocus', True):
                # Focused widget should stay focused
                FocusBehavior.ignored_touch.append(touch)
            return True

    def on_scroll_stop(self, touch, check_children=True):
        self._touch = None

        if check_children:
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if self.dispatch_children('on_scroll_stop', touch):
                touch.pop()
                return True
            touch.pop()

        if self._get_uid('svavoid') in touch.ud:
            return
        if self._get_uid() not in touch.ud:
            return False

        self._touch = None
        uid = self._get_uid()
        ud = touch.ud[uid]
        if self.do_scroll_x and self.effect_x:
            if not touch.ud.get('in_bar_x', False) and\
                    self.scroll_type != ['bars']:
                self.effect_x.stop(touch.x)
        if self.do_scroll_y and self.effect_y and\
                self.scroll_type != ['bars']:
            if not touch.ud.get('in_bar_y', False):
                self.effect_y.stop(touch.y)
        if ud['mode'] == 'unknown':
            # we must do the click at least..
            # only send the click if it was not a click to stop
            # autoscrolling
            if not ud['user_stopped']:
                self.simulate_touch_down(touch)
            Clock.schedule_once(partial(self._do_touch_up, touch), .2)

        ev = self._update_effect_bounds_ev
        if ev is None:
            ev = self._update_effect_bounds_ev = Clock.create_trigger(
                self._update_effect_bounds)
        ev()

        # if we do mouse scrolling, always accept it
        if 'button' in touch.profile and touch.button.startswith('scroll'):
            return True

        return self._get_uid() in touch.ud

    def scroll_to(self, widget, padding=10, animate=True):
        '''Scrolls the viewport to ensure that the given widget is visible,
        optionally with padding and animation. If animate is True (the
        default), then the default animation parameters will be used.
        Otherwise, it should be a dict containing arguments to pass to
        :class:`~kivy.animation.Animation` constructor.

        .. versionadded:: 1.9.1
        '''
        if not self.parent:
            return

        # if _viewport is layout and has pending operation, reschedule
        if hasattr(self._viewport, 'do_layout'):
            if self._viewport._trigger_layout.is_triggered:
                Clock.schedule_once(
                    lambda *dt: self.scroll_to(widget, padding, animate))
                return

        if isinstance(padding, (int, float)):
            padding = (padding, padding)

        pos = self.parent.to_widget(*widget.to_window(*widget.pos))
        cor = self.parent.to_widget(
            *widget.to_window(widget.right, widget.top))

        dx = dy = 0

        if pos[1] < self.y:
            dy = self.y - pos[1] + dp(padding[1])
        elif cor[1] > self.top:
            dy = self.top - cor[1] - dp(padding[1])

        if pos[0] < self.x:
            dx = self.x - pos[0] + dp(padding[0])
        elif cor[0] > self.right:
            dx = self.right - cor[0] - dp(padding[0])

        dsx, dsy = self.convert_distance_to_scroll(dx, dy)
        sxp = min(1, max(0, self.scroll_x - dsx))
        syp = min(1, max(0, self.scroll_y - dsy))

        if animate:
            if animate is True:
                animate = {'d': 0.2, 't': 'out_quad'}
            Animation.stop_all(self, 'scroll_x', 'scroll_y')
            Animation(scroll_x=sxp, scroll_y=syp, **animate).start(self)
        else:
            self.scroll_x = sxp
            self.scroll_y = syp

    def convert_distance_to_scroll(self, dx, dy):
        '''Convert a distance in pixels to a scroll distance, depending on the
        content size and the scrollview size.

        The result will be a tuple of scroll distance that can be added to
        :data:`scroll_x` and :data:`scroll_y`
        '''
        if not self._viewport:
            return 0, 0
        vp = self._viewport
        if vp.width > self.width:
            sw = vp.width - self.width
            sx = dx / float(sw)
        else:
            sx = 0
        if vp.height > self.height:
            sh = vp.height - self.height
            sy = dy / float(sh)
        else:
            sy = 1
        return sx, sy

    def update_from_scroll(self, *largs):
        '''Force the reposition of the content, according to current value of
        :attr:`scroll_x` and :attr:`scroll_y`.

        This method is automatically called when one of the :attr:`scroll_x`,
        :attr:`scroll_y`, :attr:`pos` or :attr:`size` properties change, or
        if the size of the content changes.
        '''
        if not self._viewport:
            return
        vp = self._viewport

        # update from size_hint
        if vp.size_hint_x is not None:
            w = vp.size_hint_x * self.width
            if vp.size_hint_min_x is not None:
                w = max(w, vp.size_hint_min_x)
            if vp.size_hint_max_x is not None:
                w = min(w, vp.size_hint_max_x)
            vp.width = w
        if vp.size_hint_y is not None:
            h = vp.size_hint_y * self.height
            if vp.size_hint_min_y is not None:
                h = max(h, vp.size_hint_min_y)
            if vp.size_hint_max_y is not None:
                h = min(h, vp.size_hint_max_y)
            vp.height = h

        if vp.width > self.width:
            sw = vp.width - self.width
            x = self.x - self.scroll_x * sw
        else:
            x = self.x
        if vp.height > self.height:
            sh = vp.height - self.height
            y = self.y - self.scroll_y * sh
        else:
            y = self.top - vp.height

        # from 1.8.0, we now use a matrix by default, instead of moving the
        # widget position behind. We set it here, but it will be a no-op most
        # of the time.
        vp.pos = 0, 0
        self.g_translate.xy = x, y

        # New in 1.2.0, show bar when scrolling happens and (changed in 1.9.0)
        # fade to bar_inactive_color when no scroll is happening.
        ev = self._bind_inactive_bar_color_ev
        if ev is None:
            ev = self._bind_inactive_bar_color_ev = Clock.create_trigger(
                self._bind_inactive_bar_color, .5)
        self.funbind('bar_inactive_color', self._change_bar_color)
        Animation.stop_all(self, '_bar_color')
        self.fbind('bar_color', self._change_bar_color)
        self._bar_color = self.bar_color
        ev()

    def _bind_inactive_bar_color(self, *l):
        self.funbind('bar_color', self._change_bar_color)
        self.fbind('bar_inactive_color', self._change_bar_color)
        Animation(_bar_color=self.bar_inactive_color, d=.5,
                  t='out_quart').start(self)

    def _change_bar_color(self, inst, value):
        self._bar_color = value

    #
    # Private
    #
    def add_widget(self, widget, index=0):
        if self._viewport:
            raise Exception('ScrollView accept only one widget')
        canvas = self.canvas
        self.canvas = self.canvas_viewport
        super(ScrollView, self).add_widget(widget, index)
        self.canvas = canvas
        self._viewport = widget
        widget.bind(size=self._trigger_update_from_scroll,
                    size_hint_min=self._trigger_update_from_scroll)
        self._trigger_update_from_scroll()

    def remove_widget(self, widget):
        canvas = self.canvas
        self.canvas = self.canvas_viewport
        super(ScrollView, self).remove_widget(widget)
        self.canvas = canvas
        if widget is self._viewport:
            self._viewport = None

    def _get_uid(self, prefix='sv'):
        return '{0}.{1}'.format(prefix, self.uid)

    def _change_touch_mode(self, *largs):
        if not self._touch:
            return
        uid = self._get_uid()
        touch = self._touch
        if uid not in touch.ud:
            self._touch = False
            return
        ud = touch.ud[uid]
        if ud['mode'] != 'unknown' or ud['user_stopped']:
            return
        diff_frames = Clock.frames - ud['frames']

        # in order to be able to scroll on very slow devices, let at least 3
        # frames displayed to accumulate some velocity. And then, change the
        # touch mode. Otherwise, we might never be able to compute velocity,
        # and no way to scroll it. See #1464 and #1499
        if diff_frames < 3:
            Clock.schedule_once(self._change_touch_mode, 0)
            return

        if self.do_scroll_x and self.effect_x:
            self.effect_x.cancel()
        if self.do_scroll_y and self.effect_y:
            self.effect_y.cancel()
        # XXX the next line was in the condition. But this stop
        # the possibility to "drag" an object out of the scrollview in the
        # non-used direction: if you have an horizontal scrollview, a
        # vertical gesture will not "stop" the scroll view to look for an
        # horizontal gesture, until the timeout is done.
        # and touch.dx + touch.dy == 0:
        touch.ungrab(self)
        self._touch = None
        # touch is in window coords
        touch.push()
        touch.apply_transform_2d(self.to_widget)
        touch.apply_transform_2d(self.to_parent)
        self.simulate_touch_down(touch)
        touch.pop()
        return

    def _do_touch_up(self, touch, *largs):
        # touch is in window coords
        touch.push()
        touch.apply_transform_2d(self.to_widget)
        super(ScrollView, self).on_touch_up(touch)
        touch.pop()
        # don't forget about grab event!
        for x in touch.grab_list[:]:
            touch.grab_list.remove(x)
            x = x()
            if not x:
                continue
            touch.grab_current = x
            # touch is in window coords
            touch.push()
            touch.apply_transform_2d(self.to_widget)
            super(ScrollView, self).on_touch_up(touch)
            touch.pop()
        touch.grab_current = None
예제 #5
0
class ContentMDDialog(Heir):
    title = StringProperty()
    text = StringProperty()
    text_button_cancel = StringProperty()
    text_button_ok = StringProperty()
    device_ios = BooleanProperty()
class MDNavigationDrawer(MDCard):
    type = OptionProperty("modal", options=("standard", "modal"))
    """
    Type of drawer. Modal type will be on top of screen. Standard type will be
    at left or right of screen. Also it automatically disables
    :attr:`close_on_click` and :attr:`enable_swiping` to prevent closing
    drawer for standard type.

    :attr:`type` is a :class:`~kivy.properties.OptionProperty`
    and defaults to `modal`.
    """

    anchor = OptionProperty("left", options=("left", "right"))
    """
    Anchoring screen edge for drawer. Set it to `'right'` for right-to-left
    languages. Available options are: `'left'`, `'right'`.

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

    close_on_click = BooleanProperty(True)
    """
    Close when click on scrim or keyboard escape. It automatically sets to
    False for "standard" type.

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

    state = OptionProperty("close", options=("close", "open"))
    """
    Indicates if panel closed or opened. Sets after :attr:`status` change.
    Available options are: `'close'`, `'open'`.

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

    status = OptionProperty(
        "closed",
        options=(
            "closed",
            "opening_with_swipe",
            "opening_with_animation",
            "opened",
            "closing_with_swipe",
            "closing_with_animation",
        ),
    )
    """
    Detailed state. Sets before :attr:`state`. Bind to :attr:`state` instead
    of :attr:`status`. Available options are: `'closed'`,
    `'opening_with_swipe'`, `'opening_with_animation'`, `'opened'`,
    `'closing_with_swipe'`, `'closing_with_animation'`.

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

    open_progress = NumericProperty(0.0)
    """
    Percent of visible part of side panel. The percent is specified as a
    floating point number in the range 0-1. 0.0 if panel is closed and 1.0 if
    panel is opened.

    :attr:`open_progress` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.0`.
    """

    enable_swiping = BooleanProperty(True)
    """
    Allow to open or close navigation drawer with swipe. It automatically
    sets to False for "standard" type.

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

    swipe_distance = NumericProperty(10)
    """
    The distance of the swipe with which the movement of navigation drawer
    begins.

    :attr:`swipe_distance` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `10`.
    """

    swipe_edge_width = NumericProperty(20)
    """
    The size of the area in px inside which should start swipe to drag
    navigation drawer.

    :attr:`swipe_edge_width` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `20`.
    """

    scrim_color = ListProperty([0, 0, 0, 0.5])
    """
    Color for scrim. Alpha channel will be multiplied with
    :attr:`_scrim_alpha`. Set fourth channel to 0 if you want to disable
    scrim.

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

    def _get_scrim_alpha(self):
        _scrim_alpha = 0
        if self.type == "modal":
            _scrim_alpha = self._scrim_alpha_transition(self.open_progress)
        if (
            isinstance(self.parent, NavigationLayout)
            and self.parent._scrim_color
        ):
            self.parent._scrim_color.rgba = self.scrim_color[:3] + [
                self.scrim_color[3] * _scrim_alpha
            ]
        return _scrim_alpha

    _scrim_alpha = AliasProperty(
        _get_scrim_alpha,
        None,
        bind=("_scrim_alpha_transition", "open_progress", "scrim_color"),
    )
    """
    Multiplier for alpha channel of :attr:`scrim_color`. For internal
    usage only.
    """

    scrim_alpha_transition = StringProperty("linear")
    """
    The name of the animation transition type to use for changing
    :attr:`scrim_alpha`.

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

    def _get_scrim_alpha_transition(self):
        return getattr(AnimationTransition, self.scrim_alpha_transition)

    _scrim_alpha_transition = AliasProperty(
        _get_scrim_alpha_transition,
        None,
        bind=("scrim_alpha_transition",),
        cache=True,
    )

    opening_transition = StringProperty("out_cubic")
    """
    The name of the animation transition type to use when animating to
    the :attr:`state` `'open'`.

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

    opening_time = NumericProperty(0.2)
    """
    The time taken for the panel to slide to the :attr:`state` `'open'`.

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

    closing_transition = StringProperty("out_sine")
    """The name of the animation transition type to use when animating to
    the :attr:`state` 'close'.

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

    closing_time = NumericProperty(0.2)
    """
    The time taken for the panel to slide to the :attr:`state` `'close'`.

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

    def on_type(self, *args):
        if self.type == "standard":
            self.enable_swiping = False
            self.close_on_click = False
        else:
            self.enable_swiping = True
            self.close_on_click = True

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.bind(
            open_progress=self.update_status,
            status=self.update_status,
            state=self.update_status,
        )
        Window.bind(on_keyboard=self._handle_keyboard)

    def set_state(self, new_state="toggle", animation=True):
        """Change state of the side panel.
        New_state can be one of `"toggle"`, `"open"` or `"close"`.
        """

        if new_state == "toggle":
            new_state = "close" if self.state == "open" else "open"

        if new_state == "open":
            Animation.cancel_all(self, "open_progress")
            self.status = "opening_with_animation"
            if animation:
                Animation(
                    open_progress=1.0,
                    d=self.opening_time * (1 - self.open_progress),
                    t=self.opening_transition,
                ).start(self)
            else:
                self.open_progress = 1
        else:  # "close"
            Animation.cancel_all(self, "open_progress")
            self.status = "closing_with_animation"
            if animation:
                Animation(
                    open_progress=0.0,
                    d=self.closing_time * self.open_progress,
                    t=self.closing_transition,
                ).start(self)
            else:
                self.open_progress = 0

    def toggle_nav_drawer(self):
        Logger.warning(
            "KivyMD: The 'toggle_nav_drawer' method is deprecated, "
            "use 'set_state' instead."
        )
        self.set_state("toggle", animation=True)

    def update_status(self, *_):
        status = self.status
        if status == "closed":
            self.state = "close"
        elif status == "opened":
            self.state = "open"
        elif self.open_progress == 1 and status == "opening_with_animation":
            self.status = "opened"
            self.state = "open"
        elif self.open_progress == 0 and status == "closing_with_animation":
            self.status = "closed"
            self.state = "close"
        elif status in (
            "opening_with_swipe",
            "opening_with_animation",
            "closing_with_swipe",
            "closing_with_animation",
        ):
            pass
        if self.status == "closed":
            self._elevation = 0
            self._update_shadow(self, self._elevation)
        else:
            self._elevation = self.elevation
            self._update_shadow(self, self._elevation)

    def get_dist_from_side(self, x):
        if self.anchor == "left":
            return 0 if x < 0 else x
        return 0 if x > Window.width else Window.width - x

    def on_touch_down(self, touch):
        if self.status == "closed":
            return False
        elif self.status == "opened":
            for child in self.children[:]:
                if child.dispatch("on_touch_down", touch):
                    return True
        if self.type == "standard" and not self.collide_point(
            touch.ox, touch.oy
        ):
            return False
        return True

    def on_touch_move(self, touch):
        if self.enable_swiping:
            if self.status == "closed":
                if (
                    self.get_dist_from_side(touch.ox) <= self.swipe_edge_width
                    and abs(touch.x - touch.ox) > self.swipe_distance
                ):
                    self.status = "opening_with_swipe"
            elif self.status == "opened":
                self.status = "closing_with_swipe"

        if self.status in ("opening_with_swipe", "closing_with_swipe"):
            self.open_progress = max(
                min(self.open_progress + touch.dx / self.width, 1), 0
            )
            return True
        return super().on_touch_move(touch)

    def on_touch_up(self, touch):
        if self.status == "opening_with_swipe":
            if self.open_progress > 0.5:
                self.set_state("open", animation=True)
            else:
                self.set_state("close", animation=True)
        elif self.status == "closing_with_swipe":
            if self.open_progress < 0.5:
                self.set_state("close", animation=True)
            else:
                self.set_state("open", animation=True)
        elif self.status == "opened":
            if self.close_on_click and not self.collide_point(
                touch.ox, touch.oy
            ):
                self.set_state("close", animation=True)
            elif self.type == "standard" and not self.collide_point(
                touch.ox, touch.oy
            ):
                return False
        elif self.status == "closed":
            return False
        return True

    def _handle_keyboard(self, window, key, *largs):
        if key == 27 and self.status == "opened" and self.close_on_click:
            self.set_state("close")
            return True
예제 #7
0
class DropDestination(Widget):
    motion_over_widget_func = ObjectProperty(None)
    motion_over_widget_args = ListProperty([])
    motion_flee_widget_func = ObjectProperty(None)
    motion_flee_widget_args = ListProperty([])
    motion_outside_widget_func = ObjectProperty(None)
    motion_outside_widget_args = ListProperty([])
    motion_inside_widget_func = ObjectProperty(None)
    motion_inside_widget_args = ListProperty([])
    while_dragging_func = StringProperty(None)
    is_drop_eligible = BooleanProperty(True)
    drop_group = StringProperty("_kivy_dnd_default")
    widget_entered = None

    def __init__(self, **kw):
        super(DropDestination, self).__init__(**kw)
        self.register_event_type("on_motion_over")
        self.register_event_type("on_motion_flee")
        self.register_event_type("on_motion_outside")
        self.register_event_type("on_motion_inside")
        self.register_event_type("on_close")
        self.bind(motion_over_widget_func=self.bind_mouse_motion)
        self.bind(motion_flee_widget_func=self.bind_mouse_motion)
        self.bind(motion_outside_widget_func=self.bind_mouse_motion)
        self.bind(motion_inside_widget_func=self.bind_mouse_motion)
        self.motion_is_bound_to_window = False
        self.bind(drop_group=self.bind_drop_group)
        self.in_me = False

    def close(self):
        """
        You must call close() when you are removing the widget from the display.
        :return: 
        """
        self.dispatch("on_close")

    def on_close(self):
        self.unbind(motion_over_widget_func=self.bind_mouse_motion)
        self.unbind(motion_flee_widget_func=self.bind_mouse_motion)
        self.unbind(motion_outside_widget_func=self.bind_mouse_motion)
        self.unbind(motion_inside_widget_func=self.bind_mouse_motion)
        self.unbind(drop_group=self.bind_drop_group)
        self.unregister_event_types("on_motion_over")
        self.unregister_event_types("on_motion_flee")
        self.unregister_event_types("on_motion_outside")
        self.unregister_event_types("on_motion_inside")
        self.unregister_event_types("on_close")
        if self.motion_is_bound_to_window:
            Window.unbind(mouse_pos=self.on_motion)
            self.motion_is_bound_to_window = False

        for drop_group in drag_destinations_dict:
            if drag_destinations_dict[drop_group].get(self):
                del drag_destinations_dict[drop_group][self]
        # TODO: close all children (they have bound properties, too!

    def bind_drop_group(self, arg1, arg2):
        global DEBUG_BIND_DROP_GROUP
        debug.print ("BINDING DROP GROUP", self.drop_group, level=DEBUG_BIND_DROP_GROUP)
        if self.drop_group not in drag_destinations_dict:
            drag_destinations_dict[self.drop_group]={}
        drag_destinations_dict[self.drop_group][self]=True

    def bind_mouse_motion(self, instance, value):
        global DEBUG_BIND_MOUSE_MOTION
        # debug.print ("DropDestination: BINDNG WIDGETS to Mouse Motion!", instance, value, level=DEBUG_BIND_MOUSE_MOTION
        if self.motion_is_bound_to_window is False:
            Window.bind(mouse_pos=self.on_motion)
        self.motion_is_bound_to_window = True

    def on_motion(self, top_level_window, motion_xy_tuple):
        """
        As the mouse moves in the window, do stuff:
        - If it hits this widget, and
          - If it had not marked this widget as entered,
            - If it had not marked ANY widget as entered,,
              - we have moved over this widget; dispatch on_motion_over
              - make this widget as entered
            else: (it hit this widget, but it had marked another widget as entered)
                  (This means it left that widget without dispatching on_motion_flee)
              - dispatch on_motion_flee for the other widget
              - dispatch on_motion_over for this widget
              - mark this widget as entered.
        NOTE: Be careful if there's a RelativeLayout involved in your widget
        heirarchy. collide_point may not intersect with the widget you expect, and
        so you'll get false results! Remember that the x,y of the motion is in the
        parent coordinate system, and the parent coordinate system is lost on any
        widgets that are children of a RelativeLayout. ...But see the TODO just below.
        :param top_level_window: The top level kivy window
        :param motion_xy_tuple: The coordinates of the mouse in the Window's coordinate system
        :return:
        """
        global DEBUG_ON_MOTION
        # debug.print "event x,y:", motionevent[0], motionevent[1], "self x,y,w,h:", self.x, self.y, self.width, self.height
        # debug.print "event x,y:", motionevent[0], motionevent[1], "self:", self
        # motionevent is in the main Window's coordinate system.
        # debug.print "Self.to_window:", self.to_window(self.x, self.y)
        # TODO: I believe I have compensated for any relative widget, here.
        # TODO: ...but be wary. I'm still not sure about how RelativeLayout will behave.
        #debug.print("START motion", self, "window coords (self):",
        #            self.to_window(motion_xy_tuple[0], motion_xy_tuple[1]),
        #            level=DEBUG_ON_MOTION)
        #debug.print("Coords of motion:", motion_xy_tuple[0], motion_xy_tuple[1],
        #            level=DEBUG_ON_MOTION)
        # "self x,y,w,h:", self.x, self.y, self.width, self.height,
        if self.absolute_collide_point(motion_xy_tuple[0], motion_xy_tuple[1]):
            # debug.print(motion_xy_tuple[0], motion_xy_tuple[1], "pointer collides",
            #             self, level=DEBUG_ON_MOTION)
            if self.in_me:
                self.dispatch("on_motion_inside", motion_xy_tuple)
            else:
                # DropDestination.widget_entered.dispatch("on_motion_flee")
                self.in_me = True
                self.dispatch("on_motion_over", motion_xy_tuple)
        else:
            if self.in_me:
                self.dispatch("on_motion_flee", motion_xy_tuple)
                self.in_me = False
            else:
                self.dispatch("on_motion_outside", motion_xy_tuple)

    def absolute_collide_point(self, x, y):
        """

        :param x: x-value of a point in *Window* coordinates
        :param y: y-value of a point in *Window* coordinates
        :return: True or False
        """
        global DEBUG_COLLIDE_POINT
        (my_x, my_y)=self.to_window(self.x, self.y)
        if Window.mouse_pos[0] != x or Window.mouse_pos[1] != y:
            try:
                debug.print("Title:", self.title(), "==========================", level=DEBUG_COLLIDE_POINT)
            except:
                pass
            debug.print("point, x,y:       ", x, y, level=DEBUG_COLLIDE_POINT)
            debug.print("Window mouse pos:", Window.mouse_pos, level=DEBUG_COLLIDE_POINT)
            debug.print("me:", self, level=DEBUG_COLLIDE_POINT)
            debug.print("x,y,r,t:", my_x, my_y, self.width + my_x, my_y + self.height,
                        level=DEBUG_COLLIDE_POINT)
        #debug.print_widget_ancestry(self, level=DEBUG_COLLIDE_POINT)
        return my_x <= x <= (self.width + my_x) and my_y <= y <= (my_y + self.height)

    def on_motion_flee(self, motion_xy_tuple):
        """
        Called when your touch point leaves a draggable item.
        :return:
        """
        global DEBUG_ON_MOTION_FLEE
        # debug.print "DropDestination: MOTION flee"
        if self.motion_flee_widget_func is not None:
            self.motion_flee_widget_func(self, self.motion_flee_widget_args)
            # TODO: WAS... adding these binds. Not sure why.
            # self.easy_access_dnd_function_binds)
        else:
            pass
            # debug.print "FUNCTION MOTION FLEE NONE"
        DropDestination.widget_entered = None

    def on_motion_over(self, motion_xy_tuple):
        """
        Called when your touch point crosses into a DropDestination object.
        Self is added as an argument because if you set this up in a .kv file, the self
        given there is always the self of the object created in the .kv file.
        For example, if you want to create a copy of the object in the Python code, the self
        will still refer to the self of the original object.

        TODO: Doesn't this apply exclusively to DragNDropWidgets? This should not be a problem
        in DragDestination widgets, because the library is not creating copies of these
        objects.

        :return:
        """
        global DEBUG_ON_MOTION_OVER
        # debug.print "DropDestination: MOTION over", motion_xy_tuple
        if self.motion_over_widget_func is not None:
            self.motion_over_widget_func(self, self.motion_over_widget_args)
            # self.easy_access_dnd_function_binds)
        # else:
        #    debug.print "FUNCTION MOTION OVER NONE"

    def on_motion_outside(self, motion_xy_tuple):
        global DEBUG_ON_MOTION_OUTSIDE
        # debug.print "DropDestination: MOTION outside"
        try:
            if self.motion_outside_widget_func is not None:
                self.motion_outside_widget_func(self, self.motion_outside_widget_args)
            else:
                pass
                # debug.print "FUNCTION OUT NONE"
        except AttributeError:
            pass

    def on_motion_inside(self, motion_xy_tuple):
        global DEBUG_ON_MOTION_INSIDE
        # debug.print "on_motion_inside: DropDestination INSIDE"
        try:
            if self.motion_inside_widget_func is not None:
                self.motion_inside_widget_func(self, self.motion_inside_widget_args)
            else:
                pass
                # debug.print "FUNCTION OUT NONE"
        except AttributeError:
            pass
예제 #8
0
class SpectrumGraphBase(RelativeLayout, JSONMixin):
    spectrum_plot_container = ObjectProperty(None)
    tool_panel = ObjectProperty(None)
    plot_params = DictProperty()
    x_min = NumericProperty(0.)
    x_max = NumericProperty(1.)
    auto_scale_x = BooleanProperty(True)
    auto_scale_y = BooleanProperty(True)
    selected = ObjectProperty(None)

    def get_x_size(self):
        return self.x_max - self.x_min

    def set_x_size(self, value):
        x_center = self.x_center
        x_min = x_center - (value / 2.)
        x_max = x_center + (value / 2.)
        self.x_range = [x_min, x_max]
        return True

    x_size = AliasProperty(get_x_size, set_x_size, bind=('x_min', 'x_max'))
    x_range = ReferenceListProperty(x_min, x_max)

    def get_x_center(self):
        return (self.x_size / 2.) + self.x_min

    def set_x_center(self, value):
        size = self.x_size
        x_min = value - (size / 2.)
        x_max = x_min + size
        self.x_range = [x_min, x_max]
        return True

    x_center = AliasProperty(get_x_center,
                             set_x_center,
                             bind=('x_min', 'x_max'))
    y_min = NumericProperty(-100.)
    y_max = NumericProperty(0.)

    def get_y_size(self):
        return self.y_max - self.y_min

    def set_y_size(self, value):
        pass

    y_size = AliasProperty(get_y_size, set_y_size, bind=('y_min', 'y_max'))

    def on_x_min(self, instance, value):
        self.plot_params['x_min'] = value

    def on_x_max(self, instance, value):
        self.plot_params['x_max'] = value

    def on_y_min(self, instance, value):
        self.plot_params['y_min'] = value

    def on_y_max(self, instance, value):
        self.plot_params['y_max'] = value

    def add_plot(self, **kwargs):
        plot = kwargs.get('plot')
        if plot is None:
            if self.selected is None:
                kwargs['selected'] = True
            plot = SpectrumPlot(**kwargs)
        plot.bind(selected=self.on_plot_selected)
        self.spectrum_plot_container.add_widget(plot)
        self.calc_plot_scale()
        if plot.selected:
            self.selected = plot
        return plot

    def on_plot_selected(self, instance, value):
        if not value:
            return
        self.selected = instance

    def calc_plot_scale(self):
        auto_x = self.auto_scale_x
        auto_y = self.auto_scale_y
        if not auto_x and not auto_y:
            return
        d = {}
        for w in self.spectrum_plot_container.children:
            if not isinstance(w, SpectrumPlot):
                continue
            if not w.enabled:
                continue
            pscale = w.calc_plot_scale()
            for key, val in pscale.items():
                if key not in d:
                    d[key] = val
                    continue
                if 'min' in key:
                    if val < d[key]:
                        d[key] = val
                elif 'max' in key:
                    if val > d[key]:
                        d[key] = val
        for attr, val in d.items():
            if not auto_x and attr.split('_')[0] == 'x':
                continue
            if not auto_y and attr.split('_')[0] == 'y':
                continue
            setattr(self, attr, val)

    def freq_to_x(self, freq):
        x = (freq - self.x_min) / self.x_size
        return x * self.width

    def x_to_freq(self, x):
        return (x / self.width * self.x_size) + self.x_min

    def db_to_y(self, db):
        y = (db - self.y_min) / self.y_size
        return y * self.height

    def y_to_db(self, y):
        return (y / self.height * self.y_size) + self.y_min

    def _serialize(self):
        attrs = [
            'x_max', 'x_min', 'y_max', 'y_min', 'auto_scale_x', 'auto_scale_y'
        ]
        d = {attr: getattr(self, attr) for attr in attrs}
        d['plots'] = []
        for plot in self.spectrum_plot_container.children:
            if not isinstance(plot, SpectrumPlot):
                continue
            d['plots'].append(plot._serialize())
        return d

    def _deserialize(self, **kwargs):
        for c in self.spectrum_plot_container.children[:]:
            if isinstance(c, SpectrumPlot):
                self.remove_widget(c)
        for key, val in kwargs.items():
            if key == 'plots':
                for pldata in val:
                    plot = SpectrumPlot.from_json(pldata)
                    self.add_plot(plot=plot)
                    self.tool_panel.add_plot(plot)
            else:
                setattr(self, key, val)
예제 #9
0
class SpectrumPlot(Widget, JSONMixin):
    name = StringProperty('')
    points = ListProperty([])
    color = ListProperty([0., 1., 0., .8])
    enabled = BooleanProperty(True)
    selected = BooleanProperty(False)
    spectrum = ObjectProperty(None)
    spectrum_graph = ObjectProperty(None)

    def __init__(self, **kwargs):
        super(SpectrumPlot, self).__init__(**kwargs)
        self.bind(pos=self._trigger_update, size=self._trigger_update)

    def on_spectrum(self, *args):
        if self.spectrum is None:
            return
        self.build_data()
        if self.spectrum.name is not None:
            self.name = self.spectrum.name
        else:
            self.spectrum.name = self.name
        if self.color != [0., 1., 0., .8]:
            self.spectrum.color.from_list(self.color)
        else:
            self.color = self.spectrum.color.to_list()

    def on_name(self, instance, value):
        if self.spectrum is None:
            return
        if self.spectrum.name == value:
            return
        self.spectrum.name = value
        self.spectrum.update_dbstore('name')

    def on_color(self, instance, value):
        if self.spectrum is None:
            return
        if list(value) == self.spectrum.color.to_list():
            return
        self.spectrum.color.from_list(value)
        self.spectrum.update_dbstore('color')

    def on_parent(self, *args, **kwargs):
        if self.parent is None:
            return
        self.spectrum_graph = self.parent.parent

    def on_spectrum_graph(self, *args):
        self.spectrum_graph.bind(plot_params=self._trigger_update)
        self.spectrum_graph.calc_plot_scale()

    def on_enabled(self, instance, value):
        if value:
            self._trigger_update()
        else:
            self.points = []

    def _trigger_update(self, *args, **kwargs):
        self.draw_plot()

    def draw_plot(self):
        if self.spectrum_graph is None:
            return
        freq_to_x = self.spectrum_graph.freq_to_x
        db_to_y = self.spectrum_graph.db_to_y
        self.points = []
        if not self.enabled:
            return
        xy_data = self.xy_data
        for freq, db in zip(xy_data['x'], xy_data['y']):
            xy = [freq_to_x(freq), db_to_y(db)]
            self.points.extend(xy)

    def update_data(self):
        if not self.spectrum.data_updated.is_set():
            return
        self.build_data()
        self.spectrum_graph.calc_plot_scale()
        self.draw_plot()

    def build_data(self):
        spectrum = self.spectrum
        dtype = np.dtype(float)
        with spectrum.data_update_lock:
            x = np.fromiter(spectrum.iter_frequencies(), dtype)
            y = np.fromiter((s.dbFS for s in spectrum.iter_samples()), dtype)
            self.xy_data = {'x': x, 'y': y}
            spectrum.data_updated.clear()

    def calc_plot_scale(self):
        d = {}
        for key, data in self.xy_data.items():
            for mkey in ['min', 'max']:
                _key = '_'.join([key, mkey])
                m = getattr(data, mkey)
                val = float(m())
                if mkey == 'min':
                    val -= 1
                else:
                    val += 1
                d[_key] = val
        return d

    def get_nearest_by_freq(self, freq):
        spectrum = self.spectrum
        sample = spectrum.samples.get(freq)
        if sample is not None:
            return sample.frequency, sample.dbFS
        xy_data = self.xy_data
        if freq > xy_data['x'].max():
            return None, None
        if freq < xy_data['x'].min():
            return None, None
        i = np.abs(xy_data['x'] - freq).argmin()
        return xy_data['x'][i], xy_data['y'][i]

    def _serialize(self):
        attrs = ['name', 'color', 'enabled', 'selected']
        d = {attr: getattr(self, attr) for attr in attrs}
        d['spectrum_data'] = self.spectrum._serialize()
        return d

    def _deserialize(self, **kwargs):
        spdata = kwargs.get('spectrum_data')
        self.spectrum = Spectrum.from_json(spdata)
예제 #10
0
class ListAdapter(Adapter, EventDispatcher):
    '''
    A base class for adapters interfacing with lists, dictionaries or other
    collection type data, adding selection, view creation and management
    functonality.
    '''

    data = ListProperty([])
    '''The data list property is redefined here, overriding its definition as
    an ObjectProperty in the Adapter class. We bind to data so that any
    changes will trigger updates. See also how the
    :class:`~kivy.adapters.DictAdapter` redefines data as a
    :class:`~kivy.properties.DictProperty`.

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

    selection = ListProperty([])
    '''The selection list property is the container for selected items.

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

    selection_mode = OptionProperty('single',
            options=('none', 'single', 'multiple'))
    '''The selection_mode is a string and can be set to one of the following
    values:

       * 'none': use the list as a simple list (no select action). This option
         is here so that selection can be turned off, momentarily or
         permanently, for an existing list adapter.
         A :class:`~kivy.adapters.listadapter.ListAdapter` is not meant to be
         used as a primary no-selection list adapter. Use a
         :class:`~kivy.adapters.simplelistadapter.SimpleListAdapter` for that.

       * 'single': multi-touch/click ignored. Single item selection only.

       * 'multiple': multi-touch / incremental addition to selection allowed;
         may be limited to a count by setting the
         :attr:`~ListAdapter.selection_limit`.

    :attr:`selection_mode` is an :class:`~kivy.properties.OptionProperty` and
    defaults to 'single'.
    '''

    propagate_selection_to_data = BooleanProperty(False)
    '''Normally, data items are not selected/deselected because the data items
    might not have an is_selected boolean property -- only the item view for a
    given data item is selected/deselected as part of the maintained selection
    list. However, if the data items do have an is_selected property, or if
    they mix in :class:`~kivy.adapters.models.SelectableDataItem`, the
    selection machinery can propagate selection to data items. This can be
    useful for storing selection state in a local database or backend database
    for maintaining state in game play or other similar scenarios. It is a
    convenience function.

    To propagate selection or not?

    Consider a shopping list application for shopping for fruits at the
    market. The app allows for the selection of fruits to buy for each day of
    the week, presenting seven lists: one for each day of the week. Each list is
    loaded with all the available fruits, but the selection for each is a
    subset. There is only one set of fruit data shared between the lists, so
    it would not make sense to propagate selection to the data because
    selection in any of the seven lists would clash and mix with that of the
    others.

    However, consider a game that uses the same fruits data for selecting
    fruits available for fruit-tossing. A given round of play could have a
    full fruits list, with fruits available for tossing shown selected. If the
    game is saved and rerun, the full fruits list, with selection marked on
    each item, would be reloaded correctly if selection is always propagated to
    the data. You could accomplish the same functionality by writing code to
    operate on list selection, but having selection stored in the data
    ListProperty might prove convenient in some cases.

    .. note::

        This setting should be set to True if you wish to initialize the view
        with item views already selected.

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

    allow_empty_selection = BooleanProperty(True)
    '''The allow_empty_selection may be used for cascading selection between
    several list views, or between a list view and an observing view. Such
    automatic maintenance of the selection is important for all but simple
    list displays. Set allow_empty_selection to False and the selection is
    auto-initialized and always maintained, so any observing views
    may likewise be updated to stay in sync.

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

    selection_limit = NumericProperty(-1)
    '''When the :attr:`~ListAdapter.selection_mode` is 'multiple' and the
    selection_limit is
    non-negative, this number will limit the number of selected items. It can
    be set to 1, which is equivalent to single selection. If selection_limit is
    not set, the default value is -1, meaning that no limit will be enforced.

    :attr:`selection_limit` is a :class:`~kivy.properties.NumericProperty` and
    defaults to -1 (no limit).
    '''

    cached_views = DictProperty({})
    '''View instances for data items are instantiated and managed by the
    adapter. Here we maintain a dictionary containing the view
    instances keyed to the indices in the data.

    This dictionary works as a cache. get_view() only asks for a view from
    the adapter if one is not already stored for the requested index.

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

    __events__ = ('on_selection_change', )

    def __init__(self, **kwargs):
        super(ListAdapter, self).__init__(**kwargs)

        fbind = self.fast_bind
        fbind('selection_mode', self.selection_mode_changed)
        fbind('allow_empty_selection', self.check_for_empty_selection)
        fbind('data', self.update_for_new_data)

        self.update_for_new_data()

    def delete_cache(self, *args):
        self.cached_views = {}

    def get_count(self):
        return len(self.data)

    def get_data_item(self, index):
        if index < 0 or index >= len(self.data):
            return None
        return self.data[index]

    def selection_mode_changed(self, *args):
        if self.selection_mode == 'none':
            for selected_view in self.selection:
                self.deselect_item_view(selected_view)
        else:
            self.check_for_empty_selection()

    def get_view(self, index):
        if index in self.cached_views:
            return self.cached_views[index]
        item_view = self.create_view(index)
        if item_view:
            self.cached_views[index] = item_view
        return item_view

    def create_view(self, index):
        '''This method is more complicated than the ones in the
        :class:`~kivy.adapters.adapter.Adapter` and
        :class:`~kivy.adapters.simplelistadapter.SimpleListAdapter` classes
        because here we create bindings for the data items and their children
        back to the *self.handle_selection()* event. We also perform
        other selection-related tasks to keep item views in sync with the data.
        '''
        item = self.get_data_item(index)
        if item is None:
            return None

        item_args = self.args_converter(index, item)

        item_args['index'] = index

        cls = self.get_cls()
        if cls:
            view_instance = cls(**item_args)
        else:
            view_instance = Builder.template(self.template, **item_args)

        if self.propagate_selection_to_data:
            # The data item must be a subclass of SelectableDataItem, or must
            # have an is_selected boolean or function, so it has is_selected
            # available. If is_selected is unavailable on the data item, an
            # exception is raised.
            #
            if isinstance(item, SelectableDataItem):
                if item.is_selected:
                    self.handle_selection(view_instance)
            elif type(item) == dict and 'is_selected' in item:
                if item['is_selected']:
                    self.handle_selection(view_instance)
            elif hasattr(item, 'is_selected'):
                if (inspect.isfunction(item.is_selected)
                        or inspect.ismethod(item.is_selected)):
                    if item.is_selected():
                        self.handle_selection(view_instance)
                else:
                    if item.is_selected:
                        self.handle_selection(view_instance)
            else:
                msg = "ListAdapter: unselectable data item for {0}"
                raise Exception(msg.format(index))

        view_instance.bind(on_release=self.handle_selection)

        for child in view_instance.children:
            child.bind(on_release=self.handle_selection)

        return view_instance

    def on_selection_change(self, *args):
        '''on_selection_change() is the default handler for the
        on_selection_change event. You can bind to this event to get notified
        of selection changes.

        :Parameters:
            adapter: :class:`~ListAdapter` or subclass
                The instance of the list adapter where the selection changed.
                Use the adapters :attr:`selection` property to see what has been
                selected.
        '''
        pass

    def handle_selection(self, view, hold_dispatch=False, *args):
        if view not in self.selection:
            if self.selection_mode in ['none', 'single'] and \
                    len(self.selection) > 0:
                for selected_view in self.selection:
                    self.deselect_item_view(selected_view)
            if self.selection_mode != 'none':
                if self.selection_mode == 'multiple':
                    if self.allow_empty_selection:
                        # If < 0, selection_limit is not active.
                        if self.selection_limit < 0:
                            self.select_item_view(view)
                        else:
                            if len(self.selection) < self.selection_limit:
                                self.select_item_view(view)
                    else:
                        self.select_item_view(view)
                else:
                    self.select_item_view(view)
        else:
            self.deselect_item_view(view)
            if self.selection_mode != 'none':
                # If the deselection makes selection empty, the following call
                # will check allows_empty_selection, and if False, will
                # select the first item. If view happens to be the first item,
                # this will be a reselection, and the user will notice no
                # change, except perhaps a flicker.
                #
                self.check_for_empty_selection()

        if not hold_dispatch:
            self.dispatch('on_selection_change')

    def select_data_item(self, item):
        self.set_data_item_selection(item, True)

    def deselect_data_item(self, item):
        self.set_data_item_selection(item, False)

    def set_data_item_selection(self, item, value):
        if isinstance(item, SelectableDataItem):
            item.is_selected = value
        elif type(item) == dict:
            item['is_selected'] = value
        elif hasattr(item, 'is_selected'):
            if (inspect.isfunction(item.is_selected)
                    or inspect.ismethod(item.is_selected)):
                item.is_selected()
            else:
                item.is_selected = value

    def select_item_view(self, view):
        view.select()
        view.is_selected = True
        self.selection.append(view)

        # [TODO] sibling selection for composite items
        #        Needed? Or handled from parent?
        #        (avoid circular, redundant selection)
        #if hasattr(view, 'parent') and hasattr(view.parent, 'children'):
         #siblings = [child for child in view.parent.children if child != view]
         #for sibling in siblings:
             #if hasattr(sibling, 'select'):
                 #sibling.select()

        if self.propagate_selection_to_data:
            data_item = self.get_data_item(view.index)
            self.select_data_item(data_item)

    def select_list(self, view_list, extend=True):
        '''The select call is made for the items in the provided view_list.

        Arguments:

            view_list: the list of item views to become the new selection, or
            to add to the existing selection

            extend: boolean for whether or not to extend the existing list
        '''
        if not extend:
            self.selection = []

        for view in view_list:
            self.handle_selection(view, hold_dispatch=True)

        self.dispatch('on_selection_change')

    def deselect_item_view(self, view):
        view.deselect()
        view.is_selected = False
        self.selection.remove(view)

        # [TODO] sibling deselection for composite items
        #        Needed? Or handled from parent?
        #        (avoid circular, redundant selection)
        #if hasattr(view, 'parent') and hasattr(view.parent, 'children'):
         #siblings = [child for child in view.parent.children if child != view]
         #for sibling in siblings:
             #if hasattr(sibling, 'deselect'):
                 #sibling.deselect()

        if self.propagate_selection_to_data:
            item = self.get_data_item(view.index)
            self.deselect_data_item(item)

    def deselect_list(self, l):
        for view in l:
            self.handle_selection(view, hold_dispatch=True)

        self.dispatch('on_selection_change')

    # [TODO] Could easily add select_all() and deselect_all().

    def update_for_new_data(self, *args):
        self.delete_cache()
        self.initialize_selection()

    def initialize_selection(self, *args):
        if len(self.selection) > 0:
            self.selection = []
            self.dispatch('on_selection_change')

        self.check_for_empty_selection()

    def check_for_empty_selection(self, *args):
        if not self.allow_empty_selection:
            if len(self.selection) == 0:
                # Select the first item if we have it.
                v = self.get_view(0)
                if v is not None:
                    self.handle_selection(v)

    # [TODO] Also make methods for scroll_to_sel_start, scroll_to_sel_end,
    #        scroll_to_sel_middle.

    def trim_left_of_sel(self, *args):
        '''Cut list items with indices in sorted_keys that are less than the
        index of the first selected item if there is a selection.
        '''
        if len(self.selection) > 0:
            first_sel_index = min([sel.index for sel in self.selection])
            self.data = self.data[first_sel_index:]

    def trim_right_of_sel(self, *args):
        '''Cut list items with indices in sorted_keys that are greater than
        the index of the last selected item if there is a selection.
        '''
        if len(self.selection) > 0:
            last_sel_index = max([sel.index for sel in self.selection])
            print('last_sel_index', last_sel_index)
            self.data = self.data[:last_sel_index + 1]

    def trim_to_sel(self, *args):
        '''Cut list items with indices in sorted_keys that are less than or
        greater than the index of the last selected item if there is a
        selection. This preserves intervening list items within the selected
        range.
        '''
        if len(self.selection) > 0:
            sel_indices = [sel.index for sel in self.selection]
            first_sel_index = min(sel_indices)
            last_sel_index = max(sel_indices)
            self.data = self.data[first_sel_index:last_sel_index + 1]

    def cut_to_sel(self, *args):
        '''Same as trim_to_sel, but intervening list items within the selected
        range are also cut, leaving only list items that are selected.
        '''
        if len(self.selection) > 0:
            self.data = self.selection
예제 #11
0
class GraphViewControls(BoxLayout):
    spectrum_graph = ObjectProperty(None)
    h_slider = ObjectProperty(None)
    zoom_in_btn = ObjectProperty(None)
    zoom_out_btn = ObjectProperty(None)
    h_scroll_range = ListProperty([100, 1000])
    h_scroll_value = NumericProperty(500)
    h_scroll_step = NumericProperty(1)
    h_scrolling = BooleanProperty(False)
    zoom_step = NumericProperty(1.)
    zoom_timeout = NumericProperty(.25)
    zoom_event = ObjectProperty(None, allownone=True)

    def on_spectrum_graph(self, *args):
        sg = self.spectrum_graph
        if sg is None:
            return
        sg.bind(x_range=self.on_spectrum_graph_x_range,
                x_center=self.on_spectrum_graph_x_center)

    def on_spectrum_graph_x_range(self, instance, value):
        pass

    def on_spectrum_graph_x_center(self, instance, value):
        if self.h_scrolling:
            return
        self.h_scroll_value = value

    def on_h_scroll_value(self, *args):
        sg = self.spectrum_graph
        if sg is None:
            return
        self.h_scrolling = True
        sg.x_center = self.h_scroll_value
        self.h_scrolling = False

    def on_zoom_btn_press(self, btn):
        self.cancel_zoom()
        if btn == self.zoom_in_btn:
            m = self.zoom_in
        else:
            m = self.zoom_out
        m()
        self.zoom_event = Clock.schedule_interval(m, self.zoom_timeout)

    def on_zoom_btn_release(self, btn):
        self.cancel_zoom()

    def cancel_zoom(self, *args):
        if self.zoom_event is None:
            return
        Clock.unschedule(self.zoom_event)
        self.zoom_event = None

    def zoom_in(self, *args):
        sg = self.spectrum_graph
        if sg is None:
            return
        sg.x_size -= self.zoom_step

    def zoom_out(self, *args):
        sg = self.spectrum_graph
        if sg is None:
            return
        sg.x_size += self.zoom_step
class MDSwitch(ThemableBehavior, ButtonBehavior, FloatLayout):
    active = BooleanProperty(False)

    _thumb_color = ListProperty(get_color_from_hex(colors["Gray"]["50"]))

    def _get_thumb_color(self):
        return self._thumb_color

    def _set_thumb_color(self, color, alpha=None):
        if len(color) == 2:
            self._thumb_color = get_color_from_hex(colors[color[0]][color[1]])
            if alpha:
                self._thumb_color[3] = alpha
        elif len(color) == 4:
            self._thumb_color = color

    thumb_color = AliasProperty(_get_thumb_color,
                                _set_thumb_color,
                                bind=["_thumb_color"])

    _thumb_color_down = ListProperty([1, 1, 1, 1])

    def _get_thumb_color_down(self):
        return self._thumb_color_down

    def _set_thumb_color_down(self, color, alpha=None):
        if len(color) == 2:
            self._thumb_color_down = get_color_from_hex(
                colors[color[0]][color[1]])
            if alpha:
                self._thumb_color_down[3] = alpha
            else:
                self._thumb_color_down[3] = 1
        elif len(color) == 4:
            self._thumb_color_down = color

    thumb_color_down = AliasProperty(_get_thumb_color_down,
                                     _set_thumb_color_down,
                                     bind=["_thumb_color_down"])

    _thumb_color_disabled = ListProperty(
        get_color_from_hex(colors["Gray"]["400"]))

    thumb_color_disabled = get_color_from_hex(colors["Gray"]["800"])

    def _get_thumb_color_disabled(self):
        return self._thumb_color_disabled

    def _set_thumb_color_disabled(self, color, alpha=None):
        if len(color) == 2:
            self._thumb_color_disabled = get_color_from_hex(
                colors[color[0]][color[1]])
            if alpha:
                self._thumb_color_disabled[3] = alpha
        elif len(color) == 4:
            self._thumb_color_disabled = color

    thumb_color_down = AliasProperty(
        _get_thumb_color_disabled,
        _set_thumb_color_disabled,
        bind=["_thumb_color_disabled"],
    )

    _track_color_active = ListProperty()
    _track_color_normal = ListProperty()
    _track_color_disabled = ListProperty()
    _thumb_pos = ListProperty([0, 0])

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.theme_cls.bind(
            theme_style=self._set_colors,
            primary_color=self._set_colors,
            primary_palette=self._set_colors,
        )
        self._set_colors()

    def _set_colors(self, *args):
        self._track_color_normal = self.theme_cls.disabled_hint_text_color
        if self.theme_cls.theme_style == "Dark":
            self._track_color_active = self.theme_cls.primary_color
            self._track_color_active[3] = 0.5
            self._track_color_disabled = get_color_from_hex("FFFFFF")
            self._track_color_disabled[3] = 0.1
            self.thumb_color = get_color_from_hex(colors["Gray"]["400"])
            self.thumb_color_down = get_color_from_hex(
                colors[self.theme_cls.primary_palette]["200"])
        else:
            self._track_color_active = get_color_from_hex(
                colors[self.theme_cls.primary_palette]["200"])
            self._track_color_active[3] = 0.5
            self._track_color_disabled = self.theme_cls.disabled_hint_text_color
            self.thumb_color_down = self.theme_cls.primary_color

    def on_pos(self, *args):
        if self.active:
            self._thumb_pos = (self.right - dp(12), self.center_y - dp(12))
        else:
            self._thumb_pos = (self.x - dp(12), self.center_y - dp(12))
        self.bind(active=self._update_thumb)

    def _update_thumb(self, *args):
        if self.active:
            Animation.cancel_all(self, "_thumb_pos")
            anim = Animation(
                _thumb_pos=(self.right - dp(12), self.center_y - dp(12)),
                duration=0.2,
                t="out_quad",
            )
        else:
            Animation.cancel_all(self, "_thumb_pos")
            anim = Animation(
                _thumb_pos=(self.x - dp(12), self.center_y - dp(12)),
                duration=0.2,
                t="out_quad",
            )
        anim.start(self)
class MDCheckbox(CircularRippleBehavior, ToggleButtonBehavior, MDIcon):
    active = BooleanProperty(False)

    checkbox_icon_normal = StringProperty("checkbox-blank-outline")
    checkbox_icon_down = StringProperty("checkbox-marked-outline")
    radio_icon_normal = StringProperty("checkbox-blank-circle-outline")
    radio_icon_down = StringProperty("checkbox-marked-circle-outline")

    selected_color = ListProperty()
    unselected_color = ListProperty()
    disabled_color = ListProperty()
    _current_color = ListProperty([0.0, 0.0, 0.0, 0.0])

    def __init__(self, **kwargs):
        self.check_anim_out = Animation(font_size=0,
                                        duration=0.1,
                                        t="out_quad")
        self.check_anim_in = Animation(font_size=sp(24),
                                       duration=0.1,
                                       t="out_quad")
        super().__init__(**kwargs)
        self.selected_color = self.theme_cls.primary_color
        self.unselected_color = self.theme_cls.secondary_text_color
        self.disabled_color = self.theme_cls.divider_color
        self._current_color = self.unselected_color
        self.check_anim_out.bind(
            on_complete=lambda *x: self.check_anim_in.start(self))
        self.bind(
            checkbox_icon_normal=self.update_icon,
            checkbox_icon_down=self.update_icon,
            radio_icon_normal=self.update_icon,
            radio_icon_down=self.update_icon,
            group=self.update_icon,
            selected_color=self.update_color,
            unselected_color=self.update_color,
            disabled_color=self.update_color,
            disabled=self.update_color,
            state=self.update_color,
        )
        self.update_icon()
        self.update_color()

    def update_icon(self, *args):
        if self.state == "down":
            self.icon = self.radio_icon_down if self.group else self.checkbox_icon_down
        else:
            self.icon = (self.radio_icon_normal
                         if self.group else self.checkbox_icon_normal)

    def update_color(self, *args):
        if self.disabled:
            self._current_color = self.disabled_color
        elif self.state == "down":
            self._current_color = self.selected_color
        else:
            self._current_color = self.unselected_color

    def on_state(self, *args):
        if self.state == "down":
            self.check_anim_in.cancel(self)
            self.check_anim_out.start(self)
            self.update_icon()
            self.active = True
        else:
            self.check_anim_in.cancel(self)
            self.check_anim_out.start(self)
            self.update_icon()
            self.active = False

    def on_active(self, *args):
        self.state = "down" if self.active else "normal"
예제 #14
0
class MDFileManager(ThemableBehavior, FloatLayout):
    icon = StringProperty('check')
    '''The icon that will be used on the directory selection button.'''

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

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

    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.'''

    search = StringProperty('all')
    '''It can take the values 'dirs' 'files' - display only directories
    or only files. By default, it displays and folders, and files.'''

    current_path = StringProperty('/')
    '''Current directory.'''

    use_access = BooleanProperty(True)
    '''Show accec to files and directories.'''

    previous = BooleanProperty(False)
    '''Shows only image previews.'''
    def __init__(self, **kwargs):
        super(MDFileManager, self).__init__(**kwargs)
        self.history = []  # directory navigation history
        # If False - do not add a directory to the history -
        # The user moves down the tree.
        self.history_flag = True
        toolbar_label = self.ids.toolbar.children[1].children[0]
        toolbar_label.font_style = 'Subhead'

        if self.previous:
            self.ext = ['.png', '.jpg', '.jpeg']
            self.app = App.get_running_app()
            if not os.path.exists('%s/thumb' % self.app.user_data_dir):
                os.mkdir('%s/thumb' % self.app.user_data_dir)
        else:
            action_button = FloatButton(
                callback=self.select_directory_on_press_button,
                md_bg_color=self.theme_cls.primary_color,
                icon=self.icon)
            self.add_widget(action_button)

    def update_list_images(self):
        self.ids.rv.refresh_from_layout()

    def split_list(self, l, n):
        n = max(1, n)
        if PY2:
            return (l[i:i + n] for i in xrange(0, len(l), n))
        else:
            return (l[i:i + n] for i in range(0, len(l), n))

    def create_previous(self, path):
        for image in os.listdir(path):
            _path = os.path.join(path, image)
            if os.path.isfile(_path):
                if self.count_ext(_path):
                    path_to_thumb = \
                        '%s/thumb/thumb_%s' % (self.app.user_data_dir, image)
                    if not os.path.exists(path_to_thumb):
                        im = Image.open(os.path.join(path, image))
                        im.thumbnail((200, 200))
                        im.save(path_to_thumb, "PNG")

    def check_theme(self):
        self.canvas.children[0].rgba = \
            [0, 0, 0, 1] if self.theme_cls.theme_style == 'Dark' \
                else [1, 1, 1, 1]

    def show(self, path):
        '''Forms the body of a directory tree.'''

        self.check_theme()
        dirs, files = self.get_content(path)

        if self.previous:
            threading.Thread(target=self.create_previous,
                             args=(path, )).start()
            split_dirs = self.split_list(dirs, 3)
            split_files = self.split_list(files, 3)

        self.current_path = path
        manager_list = []

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

        if self.previous:
            for list_dirs in split_dirs:
                manager_list.append({
                    'viewclass': 'BodyManagerWithPrevious',
                    'path': path,
                    'paths': list_dirs,
                    'type': 'folder',
                    'events_callback': self.select_dir_or_file,
                    'height': dp(105)
                })

            for list_files in list(split_files):
                manager_list.append({
                    'viewclass': 'BodyManagerWithPrevious',
                    'path': path,
                    'paths': list_files,
                    'type': 'files',
                    'events_callback': self.select_dir_or_file,
                    'height': dp(105)
                })
        else:
            for name in dirs:
                _path = path + name if path == '/' else 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,
                    'access_string': access_string,
                    'events_callback': self.select_dir_or_file
                })

            for name in files:
                _path = path + name if path == '/' else path + '/' + name
                manager_list.append({
                    'viewclass':
                    'BodyManager',
                    'path':
                    _path,
                    'icon':
                    'file-outline',
                    'dir_or_file_name':
                    name,
                    'access_string':
                    self.get_access_string(_path),
                    'events_callback':
                    self.select_dir_or_file
                })

        self.ids.rv.data = manager_list

    def count_ext(self, path):
        ext = os.path.splitext(path)[1]
        if ext != '':
            if ext.lower() in self.ext or ext.upper() in self.ext:
                return True
        return False

    def get_access_string(self, path):
        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, path):
        '''Returns a list of the type [[Folder List], [file list]].'''

        try:
            files = []
            dirs = []

            if self.history_flag:
                self.history.append(path)
            if not self.history_flag:
                self.history_flag = True

            for content in os.listdir(path):
                if os.path.isdir('%s/%s' % (path, content)):
                    if self.search == 'all' or self.search == 'dirs':
                        dirs.append(content)
                else:
                    if self.search == 'all' or self.search == 'files':
                        if len(self.ext) != 0:
                            try:
                                if self.count_ext(content):
                                    if self.previous:
                                        files.append(
                                            '%s/thumb/thumb_%s' %
                                            (self.app.user_data_dir, content))
                                    else:
                                        files.append(content)
                            except IndexError:
                                pass
                        else:
                            files.append(content)
            return dirs, files
        except OSError:
            self.history.pop()
            return None, None

    def select_dir_or_file(self, path):
        '''Called by tap on the name of the directory or file.'''

        if os.path.isfile(path):
            self.history = []
            self.select_path(path)
            return

        self.current_path = path
        self.show(path)

    def back(self):
        '''Returning to the branch down in the directory tree.'''

        if len(self.history) == 1:
            path, end = os.path.split(self.history[0])
            if end == '':
                self.exit_manager(1)
                return
            self.history[0] = path
        else:
            self.history.pop()
            path = self.history[-1]
        self.history_flag = False
        self.select_dir_or_file(path)

    def select_directory_on_press_button(self, *args):
        self.history = []
        self.select_path(self.current_path)
예제 #15
0
class HFPManager(EventDispatcher):

    connected = BooleanProperty()
    status = StringProperty()
    carrier = StringProperty()
    network_strength = NumericProperty()
    calls = ListProperty()
    attention = ListProperty()

    def __init__(self, bluetooth):
        Logger.info('HFP: Loading hfp-module')
        bluetooth.bind(modems=self.on_modems_change)
        self.bluetooth = bluetooth
        self.on_modems_change(self, bluetooth.modems)

    def on_modems_change(self, instance, value):
        for modem in value:
            if value[modem]['Online'] is True:
                self.modem = self.bluetooth.bus.get('org.ofono', modem)
                self.modem.CallAdded.connect(self.on_call_added)
                self.modem[
                    'org.ofono.NetworkRegistration'].PropertyChanged.connect(
                        self.on_netstat_changed)
                self.connected = True
                Logger.info('HFP: Connected')
                self.get_properties()
                return
        self.modem = None
        self.connected = False

    def call(self, number):
        self.modem.Dial(number, '')
        Logger.info('HFP: Call initialised')

    def on_property_changed(self, sender, message):
        Logger.info('Telephony: ' + sender + ' changed to ' + message)
        if sender == 'State':
            self.status = message
        elif sender == 'Name':
            self.network_name = message

    def on_netstat_changed(self, sender, message):
        Logger.info('Network: ' + sender + ' ' + str(message))
        if sender == 'Name':
            self.carrier = message
        elif sender == 'Strength':
            self.network_strength = message

    def on_call_added(self, path, properties):
        call = Call(path, self.bluetooth, properties, self)
        self.calls.append(call)
        Logger.info('HFP: Call created')

    def on_call_removed(self, path, properties):
        for call in self.calls:
            if call.path == path:
                self.calls.pop(call)
        Logger.info('HFP: Call destroyed')

    def get_properties(self):
        properties = self.modem['org.ofono.NetworkRegistration'].GetProperties(
        )
        Logger.info('Network: ' + str(properties))
        try:
            self.carrier = properties['Name']
            self.network_strength = properties['Strength']
        except KeyError:
            pass
예제 #16
0
파일: button.py 프로젝트: ikolim/KivyMD
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, 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)
    font_name = StringProperty()
    font_size = NumericProperty(14)

    def on_font_name(self, instance, value):
        instance.ids.lbl_txt.font_name = value

    def __init__(self, **kwargs):
        super().__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 self.disabled:
            self._current_button_color = self.md_bg_color_disabled
        else:
            self._current_button_color = self.md_bg_color
예제 #17
0
class TabbedPanel(GridLayout):
    '''The TabbedPanel class. See module documentation for more information.
    '''

    background_color = ColorProperty([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].

    .. versionchanged:: 2.0.0
        Changed from :class:`~kivy.properties.ListProperty` to
        :class:`~kivy.properties.ColorProperty`.
    '''

    border = ListProperty([16, 16, 16, 16])
    '''Border used for :class:`~kivy.graphics.vertex_instructions.BorderImage`
    graphics instruction, used itself for :attr:`background_image`.
    Can be changed for a custom background.

    It must be a list of four values: (bottom, right, top, left). Read the
    BorderImage instructions for more information.

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

    background_image = StringProperty('atlas://data/images/defaulttheme/tab')
    '''Background image of the main shared content object.

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

    background_disabled_image = StringProperty(
        'atlas://data/images/defaulttheme/tab_disabled')
    '''Background image of the main shared content object when disabled.

    .. versionadded:: 1.8.0

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

    strip_image = StringProperty(
        'atlas://data/images/defaulttheme/action_view')
    '''Background image of the tabbed strip.

    .. versionadded:: 1.8.0

    :attr:`strip_image` is a :class:`~kivy.properties.StringProperty`
    and defaults to a empty image.
    '''

    strip_border = ListProperty([4, 4, 4, 4])
    '''Border to be used on :attr:`strip_image`.

    .. versionadded:: 1.8.0

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

    _current_tab = ObjectProperty(None)

    def get_current_tab(self):
        return self._current_tab

    current_tab = AliasProperty(get_current_tab, None, bind=('_current_tab', ))
    '''Links to the currently selected or active tab.

    .. versionadded:: 1.4.0

    :attr:`current_tab` is an :class:`~kivy.AliasProperty`, read-only.
    '''

    tab_pos = OptionProperty(
        'top_left',
        options=('left_top', 'left_mid', 'left_bottom', 'top_left',
                 'top_mid', 'top_right', 'right_top', 'right_mid',
                 'right_bottom', 'bottom_left', 'bottom_mid', 'bottom_right'))
    '''Specifies the position of the tabs relative to the content.
    Can be one of: `left_top`, `left_mid`, `left_bottom`, `top_left`,
    `top_mid`, `top_right`, `right_top`, `right_mid`, `right_bottom`,
    `bottom_left`, `bottom_mid`, `bottom_right`.

    :attr:`tab_pos` is an :class:`~kivy.properties.OptionProperty` and
    defaults to 'top_left'.
    '''

    tab_height = NumericProperty('40dp')
    '''Specifies the height of the tab header.

    :attr:`tab_height` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 40.
    '''

    tab_width = NumericProperty('100dp', allownone=True)
    '''Specifies the width of the tab header.

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

    do_default_tab = BooleanProperty(True)
    '''Specifies whether a default_tab head is provided.

    .. versionadded:: 1.5.0

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

    default_tab_text = StringProperty('Default tab')
    '''Specifies the text displayed on the default tab header.

    :attr:`default_tab_text` is a :class:`~kivy.properties.StringProperty` and
    defaults to 'default tab'.
    '''

    default_tab_cls = ObjectProperty(TabbedPanelHeader)
    '''Specifies the class to use for the styling of the default tab.

    .. versionadded:: 1.4.0

    .. warning::
        `default_tab_cls` should be subclassed from `TabbedPanelHeader`

    :attr:`default_tab_cls` is an :class:`~kivy.properties.ObjectProperty`
    and defaults to `TabbedPanelHeader`. If you set a string, the
    :class:`~kivy.factory.Factory` will be used to resolve the class.

    .. versionchanged:: 1.8.0
        The :class:`~kivy.factory.Factory` will resolve the class if a string
        is set.
    '''

    def get_tab_list(self):
        if self._tab_strip:
            return self._tab_strip.children
        return 1.

    tab_list = AliasProperty(get_tab_list, None)
    '''List of all the tab headers.

    :attr:`tab_list` is an :class:`~kivy.properties.AliasProperty` and is
    read-only.
    '''

    content = ObjectProperty(None)
    '''This is the object holding (current_tab's content is added to this)
    the content of the current tab. To Listen to the changes in the content
    of the current tab, you should bind to current_tabs `content` property.

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

    _default_tab = ObjectProperty(None, allow_none=True)

    def get_def_tab(self):
        return self._default_tab

    def set_def_tab(self, new_tab):
        if not issubclass(new_tab.__class__, TabbedPanelHeader):
            raise TabbedPanelException('`default_tab_class` should be\
                subclassed from `TabbedPanelHeader`')
        if self._default_tab == new_tab:
            return
        oltab = self._default_tab
        self._default_tab = new_tab
        self.remove_widget(oltab)
        self._original_tab = None
        self.switch_to(new_tab)
        new_tab.state = 'down'

    default_tab = AliasProperty(get_def_tab, set_def_tab,
                                bind=('_default_tab', ))
    '''Holds the default tab.

    .. Note:: For convenience, the automatically provided default tab is
              deleted when you change default_tab to something else.
              As of 1.5.0, this behaviour has been extended to every
              `default_tab` for consistency and not just the automatically
              provided one.

    :attr:`default_tab` is an :class:`~kivy.properties.AliasProperty`.
    '''

    def get_def_tab_content(self):
        return self.default_tab.content

    def set_def_tab_content(self, *l):
        self.default_tab.content = l[0]

    default_tab_content = AliasProperty(get_def_tab_content,
                                        set_def_tab_content)
    '''Holds the default tab content.

    :attr:`default_tab_content` is an :class:`~kivy.properties.AliasProperty`.
    '''

    _update_top_ev = _update_tab_ev = _update_tabs_ev = None

    def __init__(self, **kwargs):
        # these variables need to be initialized before the kv lang is
        # processed setup the base layout for the tabbed panel
        self._childrens = []
        self._tab_layout = StripLayout(rows=1)
        self.rows = 1
        self._tab_strip = TabbedPanelStrip(
            tabbed_panel=self,
            rows=1, size_hint=(None, None),
            height=self.tab_height, width=self.tab_width)

        self._partial_update_scrollview = None
        self.content = TabbedPanelContent()
        self._current_tab = self._original_tab \
            = self._default_tab = TabbedPanelHeader()

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

        self.fbind('size', self._reposition_tabs)
        if not self.do_default_tab:
            Clock.schedule_once(self._switch_to_first_tab)
            return
        self._setup_default_tab()
        self.switch_to(self.default_tab)

    def switch_to(self, header, do_scroll=False):
        '''Switch to a specific panel header.

        .. versionchanged:: 1.10.0

        If used with `do_scroll=True`, it scrolls
        to the header's tab too.
        '''
        header_content = header.content
        self._current_tab.state = 'normal'
        header.state = 'down'
        self._current_tab = header
        self.clear_widgets()
        if header_content is None:
            return
        # if content has a previous parent remove it from that parent
        parent = header_content.parent
        if parent:
            parent.remove_widget(header_content)
        self.add_widget(header_content)

        if do_scroll:
            tabs = self._tab_strip
            tabs.parent.scroll_to(header)

    def clear_tabs(self, *l):
        self_tabs = self._tab_strip
        self_tabs.clear_widgets()
        if self.do_default_tab:
            self_default_tab = self._default_tab
            self_tabs.add_widget(self_default_tab)
            self_tabs.width = self_default_tab.width
        self._reposition_tabs()

    def add_widget(self, widget, index=0):
        content = self.content
        if content is None:
            return
        parent = widget.parent
        if parent:
            parent.remove_widget(widget)
        if widget in (content, self._tab_layout):
            super(TabbedPanel, self).add_widget(widget, index)
        elif isinstance(widget, TabbedPanelHeader):
            self_tabs = self._tab_strip
            self_tabs.add_widget(widget, index)
            widget.group = '__tab%r__' % self_tabs.uid
            self.on_tab_width()
        else:
            widget.pos_hint = {'x': 0, 'top': 1}
            self._childrens.append(widget)
            content.disabled = self.current_tab.disabled
            content.add_widget(widget, index)

    def remove_widget(self, widget):
        content = self.content
        if content is None:
            return
        if widget in (content, self._tab_layout):
            super(TabbedPanel, self).remove_widget(widget)
        elif isinstance(widget, TabbedPanelHeader):
            if not (self.do_default_tab and widget is self._default_tab):
                self_tabs = self._tab_strip
                self_tabs.width -= widget.width
                self_tabs.remove_widget(widget)
                if widget.state == 'down' and self.do_default_tab:
                    self._default_tab.on_release()
                self._reposition_tabs()
            else:
                Logger.info('TabbedPanel: default tab! can\'t be removed.\n' +
                            'Change `default_tab` to a different tab.')
        else:
            if widget in self._childrens:
                self._childrens.remove(widget)
            if widget in content.children:
                content.remove_widget(widget)

    def clear_widgets(self, **kwargs):
        content = self.content
        if content is None:
            return
        if kwargs.get('do_super', False):
            super(TabbedPanel, self).clear_widgets()
        else:
            content.clear_widgets()

    def on_strip_image(self, instance, value):
        if not self._tab_layout:
            return
        self._tab_layout.background_image = value

    def on_strip_border(self, instance, value):
        if not self._tab_layout:
            return
        self._tab_layout.border = value

    def on_do_default_tab(self, instance, value):
        if not value:
            dft = self.default_tab
            if dft in self.tab_list:
                self.remove_widget(dft)
                self._switch_to_first_tab()
                self._default_tab = self._current_tab
        else:
            self._current_tab.state = 'normal'
            self._setup_default_tab()

    def on_default_tab_text(self, *args):
        self._default_tab.text = self.default_tab_text

    def on_tab_width(self, *l):
        ev = self._update_tab_ev
        if ev is None:
            ev = self._update_tab_ev = Clock.create_trigger(
                self._update_tab_width, 0)
        ev()

    def on_tab_height(self, *l):
        self._tab_layout.height = self._tab_strip.height = self.tab_height
        self._reposition_tabs()

    def on_tab_pos(self, *l):
        # ensure canvas
        self._reposition_tabs()

    def _setup_default_tab(self):
        if self._default_tab in self.tab_list:
            return
        content = self._default_tab.content
        _tabs = self._tab_strip
        cls = self.default_tab_cls

        if isinstance(cls, string_types):
            cls = Factory.get(cls)

        if not issubclass(cls, TabbedPanelHeader):
            raise TabbedPanelException('`default_tab_class` should be\
                subclassed from `TabbedPanelHeader`')

        # no need to instantiate if class is TabbedPanelHeader
        if cls != TabbedPanelHeader:
            self._current_tab = self._original_tab = self._default_tab = cls()

        default_tab = self.default_tab
        if self._original_tab == self.default_tab:
            default_tab.text = self.default_tab_text

        default_tab.height = self.tab_height
        default_tab.group = '__tab%r__' % _tabs.uid
        default_tab.state = 'down'
        default_tab.width = self.tab_width if self.tab_width else 100
        default_tab.content = content

        tl = self.tab_list
        if default_tab not in tl:
            _tabs.add_widget(default_tab, len(tl))

        if default_tab.content:
            self.clear_widgets()
            self.add_widget(self.default_tab.content)
        else:
            Clock.schedule_once(self._load_default_tab_content)
        self._current_tab = default_tab

    def _switch_to_first_tab(self, *l):
        ltl = len(self.tab_list) - 1
        if ltl > -1:
            self._current_tab = dt = self._original_tab \
                = self.tab_list[ltl]
            self.switch_to(dt)

    def _load_default_tab_content(self, dt):
        if self.default_tab:
            self.switch_to(self.default_tab)

    def _reposition_tabs(self, *l):
        ev = self._update_tabs_ev
        if ev is None:
            ev = self._update_tabs_ev = Clock.create_trigger(
                self._update_tabs, 0)
        ev()

    def _update_tabs(self, *l):
        self_content = self.content
        if not self_content:
            return
        # cache variables for faster access
        tab_pos = self.tab_pos
        tab_layout = self._tab_layout
        tab_layout.clear_widgets()
        scrl_v = ScrollView(size_hint=(None, 1))
        tabs = self._tab_strip
        parent = tabs.parent
        if parent:
            parent.remove_widget(tabs)
        scrl_v.add_widget(tabs)
        scrl_v.pos = (0, 0)
        self_update_scrollview = self._update_scrollview

        # update scrlv width when tab width changes depends on tab_pos
        if self._partial_update_scrollview is not None:
            tabs.unbind(width=self._partial_update_scrollview)
        self._partial_update_scrollview = partial(
            self_update_scrollview, scrl_v)
        tabs.bind(width=self._partial_update_scrollview)

        # remove all widgets from the tab_strip
        self.clear_widgets(do_super=True)
        tab_height = self.tab_height

        widget_list = []
        tab_list = []
        pos_letter = tab_pos[0]
        if pos_letter == 'b' or pos_letter == 't':
            # bottom or top positions
            # one col containing the tab_strip and the content
            self.cols = 1
            self.rows = 2
            # tab_layout contains the scrollview containing tabs and two blank
            # dummy widgets for spacing
            tab_layout.rows = 1
            tab_layout.cols = 3
            tab_layout.size_hint = (1, None)
            tab_layout.height = (tab_height + tab_layout.padding[1] +
                                 tab_layout.padding[3] + dp(2))
            self_update_scrollview(scrl_v)

            if pos_letter == 'b':
                # bottom
                if tab_pos == 'bottom_mid':
                    tab_list = (Widget(), scrl_v, Widget())
                    widget_list = (self_content, tab_layout)
                else:
                    if tab_pos == 'bottom_left':
                        tab_list = (scrl_v, Widget(), Widget())
                    elif tab_pos == 'bottom_right':
                        # add two dummy widgets
                        tab_list = (Widget(), Widget(), scrl_v)
                    widget_list = (self_content, tab_layout)
            else:
                # top
                if tab_pos == 'top_mid':
                    tab_list = (Widget(), scrl_v, Widget())
                elif tab_pos == 'top_left':
                    tab_list = (scrl_v, Widget(), Widget())
                elif tab_pos == 'top_right':
                    tab_list = (Widget(), Widget(), scrl_v)
                widget_list = (tab_layout, self_content)
        elif pos_letter == 'l' or pos_letter == 'r':
            # left ot right positions
            # one row containing the tab_strip and the content
            self.cols = 2
            self.rows = 1
            # tab_layout contains two blank dummy widgets for spacing
            # "vertically" and the scatter containing scrollview
            # containing tabs
            tab_layout.rows = 3
            tab_layout.cols = 1
            tab_layout.size_hint = (None, 1)
            tab_layout.width = tab_height
            scrl_v.height = tab_height
            self_update_scrollview(scrl_v)

            # rotate the scatter for vertical positions
            rotation = 90 if tab_pos[0] == 'l' else -90
            sctr = Scatter(do_translation=False,
                           rotation=rotation,
                           do_rotation=False,
                           do_scale=False,
                           size_hint=(None, None),
                           auto_bring_to_front=False,
                           size=scrl_v.size)
            sctr.add_widget(scrl_v)

            lentab_pos = len(tab_pos)

            # Update scatter's top when its pos changes.
            # Needed for repositioning scatter to the correct place after its
            # added to the parent. Use clock_schedule_once to ensure top is
            # calculated after the parent's pos on canvas has been calculated.
            # This is needed for when tab_pos changes to correctly position
            # scatter. Without clock.schedule_once the positions would look
            # fine but touch won't translate to the correct position

            if tab_pos[lentab_pos - 4:] == '_top':
                # on positions 'left_top' and 'right_top'
                sctr.bind(pos=partial(self._update_top, sctr, 'top', None))
                tab_list = (sctr, )
            elif tab_pos[lentab_pos - 4:] == '_mid':
                # calculate top of scatter
                sctr.bind(pos=partial(self._update_top, sctr, 'mid',
                                      scrl_v.width))
                tab_list = (Widget(), sctr, Widget())
            elif tab_pos[lentab_pos - 7:] == '_bottom':
                tab_list = (Widget(), Widget(), sctr)

            if pos_letter == 'l':
                widget_list = (tab_layout, self_content)
            else:
                widget_list = (self_content, tab_layout)

        # add widgets to tab_layout
        add = tab_layout.add_widget
        for widg in tab_list:
            add(widg)

        # add widgets to self
        add = self.add_widget
        for widg in widget_list:
            add(widg)

    def _update_tab_width(self, *l):
        if self.tab_width:
            for tab in self.tab_list:
                tab.size_hint_x = 1
            tsw = self.tab_width * len(self._tab_strip.children)
        else:
            # tab_width = None
            tsw = 0
            for tab in self.tab_list:
                if tab.size_hint_x:
                    # size_hint_x: x/.xyz
                    tab.size_hint_x = 1
                    # drop to default tab_width
                    tsw += 100
                else:
                    # size_hint_x: None
                    tsw += tab.width
        self._tab_strip.width = tsw
        self._reposition_tabs()

    def _update_top(self, *args):
        sctr, top, scrl_v_width, x, y = args
        ev = self._update_top_ev
        if ev is not None:
            ev.cancel()

        ev = self._update_top_ev = Clock.schedule_once(
            partial(self._updt_top, sctr, top, scrl_v_width), 0)

    def _updt_top(self, sctr, top, scrl_v_width, *args):
        if top[0] == 't':
            sctr.top = self.top
        else:
            sctr.top = self.top - (self.height - scrl_v_width) / 2

    def _update_scrollview(self, scrl_v, *l):
        self_tab_pos = self.tab_pos
        self_tabs = self._tab_strip
        if self_tab_pos[0] == 'b' or self_tab_pos[0] == 't':
            # bottom or top
            scrl_v.width = min(self.width, self_tabs.width)
            # required for situations when scrl_v's pos is calculated
            # when it has no parent
            scrl_v.top += 1
            scrl_v.top -= 1
        else:
            # left or right
            scrl_v.width = min(self.height, self_tabs.width)
            self_tabs.pos = (0, 0)
예제 #18
0
파일: button.py 프로젝트: ikolim/KivyMD
class BaseFlatIconButton(MDFlatButton):
    icon = StringProperty("android")
    text = StringProperty("")
    button_label = BooleanProperty(False)
예제 #19
0
class Player(BoxLayout, BorderBehavior):
    name = StringProperty("player")
    number = NumericProperty(0)
    icon = StringProperty()
    alive = BooleanProperty(True)
    mafia = BooleanProperty(False)
    agent = BooleanProperty(False)
    is_on_trial = BooleanProperty(False)
    strategic_value = NumericProperty(0)
    # Holds a reference to the action that is to be taken on another player.
    current_action = StringProperty()
    actions = DictProperty()

    def __iter__(self):
        flattened_actions = self.actions.copy()
        for action, decision in self.actions.items():
            flattened_actions[action] = decision.copy()
            for key, value in decision.items():
                if hasattr(value, "number"):
                    flattened_actions[action][key] = value.number

        return iter([('name', self.name),
                     ('agent', self.agent),
                     ('number', self.number),
                     ('is_on_trial', self.is_on_trial),
                     ('icon', self.icon),
                     ('mafia', self.mafia),
                     ('alive', self.alive),
                     ('strategic_value', self.strategic_value),
                     ('current_action', self.current_action),
                     ('actions', flattened_actions)])

    def suspect(self, other):
        pass

    def accuse(self, other):
        pass

    def kill(self, other):
        pass

    def die(self):
        stage = App.get_running_app().root.current_screen
        stage.players[self.number].alive = self.alive = False
        stage.players[self.number].icon = self.icon = "data/icons/player_dead.png"

    def set_strategic_value(self, strategic_value):
        tolerance = 1.5

        if strategic_value > tolerance:
            self.borders = (2, "solid", to_rgba("05F5F5"))
        elif strategic_value < tolerance:
            self.borders = (2, "solid", to_rgba("05F5F5"))
        else:
            self.borders = (2, "solid", to_rgba("05F5F5"))

        self.update_borders()

    def ready_action(self, action) -> None:
        """
        Designate the current player as the one who will be performing actions.
        This is done by setting the player instance as the selected player.
        """
        stage = App.get_running_app().root.current_screen
        self.current_action = action.lower()
        stage.selected_player = self
        Logger.info(f"Player: {self.name} readies {self.current_action}")

        if self.current_action == "die":
            self.die()

        if self.current_action == "guilty" or self.current_action == "innocent":
            stage.players[self.number].actions["vote"]["decision"] = self.current_action

        if self.current_action == "abstain":
            # Fix Issue #17
            stage.players[self.number].actions["accuse"]["player"] = None
            stage.players[self.number].actions["suspect"]["player"] = None
            stage.selected_player = None

    def act_on(self, player) -> None:
        assert self.actions is not None
        assert player is not None
        assert issubclass(type(self), Player)
        assert self.actions != {}

        self.current_action = self.current_action.lower()

        if self == player:
            Logger.warning(f"Player: {self.name} tried to act on themselves.")
            return

        if self.current_action == 'suspect' and self.actions["accuse"]["player"] != player:
            self.actions["suspect"]['player'] = player
        elif self.current_action == 'accuse' and self.actions["suspect"]["player"] != player:
            self.actions["accuse"]['player'] = player

        Logger.info(f"Player: {self.name} {self.current_action} {player.name}")

    def show_bubble(self) -> None:
        self.bubb = Bubble(size_hint=(None, None),
                           size=(160, 30),
                           pos_hint={'center_x': .5, 'y': .6})
        accuse = BubbleButton(text='Accuse')
        suspect = BubbleButton(text='Suspect')
        accuse.bind(on_press=partial(self.hide_bubble, accuse))
        suspect.bind(on_press=partial(self.hide_bubble, suspect))
        self.bubb.add_widget(accuse)
        self.bubb.add_widget(suspect)
        self.ids.empty.add_widget(self.bubb)

    def hide_bubble(self, instance, *args):
        self.ids.empty.remove_widget(self.bubb)
예제 #20
0
class SweetAlert(MDDialog):
    animation_type = OptionProperty("pulse", options=("pulse"))
    """
    Dialog appearance animation type. Available options are: `'pulse'`.

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-anim-pulse.gif
        :align: center

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

    font_style_title = OptionProperty("H4", options=theme_font_styles)
    """
    Dialog title style.

    .. code-block:: python

        SweetAlert(
            window_control_buttons="mac-style",
            font_style_title="H4",
        ).fire("Title", "Message!")

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-title-style.png
        :align: center

    :attr:`font_style_title` is an :class:`~kivy.properties.OptionProperty`
    and defaults to attr:`~kivymd.font_definitions.theme_font_styles`
    """

    font_style_text = OptionProperty("H6", options=theme_font_styles)
    """
    Dialog text style.

    .. code-block:: python

        SweetAlert(
            window_control_buttons="mac-style",
            font_style_text="H4",
        ).fire("Title", "Message!")

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-text-style.png
        :align: center

    :attr:`font_style_text` is an :class:`~kivy.properties.OptionProperty`
    and defaults to attr:`~kivymd.font_definitions.theme_font_styles`
    """

    font_style_footer = OptionProperty("H6", options=theme_font_styles)
    """
    Dialog footer style.

    .. code-block:: python

        SweetAlert(
            window_control_buttons="mac-style",
            font_style_footer="H4",
        ).fire("Title", "Message!", "Footer")

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-text-footer.png
        :align: center

    :attr:`font_style_footer` is an :class:`~kivy.properties.OptionProperty`
    and defaults to attr:`~kivymd.font_definitions.theme_font_styles`
    """

    font_size_button = NumericProperty("16sp")
    """
    Button font size.

    .. code-block:: python

        SweetAlert(
            window_control_buttons="mac-style",
            font_size_button="24sp",
        ).fire("Message!")

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-size-button.png
        :align: center

    :attr:`font_size_button` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `'16sp'`.
    """

    color_button = ListProperty()
    """
    Button color.

    .. code-block:: python

        SweetAlert(
            window_control_buttons="mac-style",
            color_button=(1, 0, 1, 1),
        ).fire("Message!")

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-color-button.png
        :align: center

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

    text_color_button = ListProperty()
    """
    Button text color.

    .. code-block:: python

        SweetAlert(
            window_control_buttons="mac-style",
            text_color_button=(1, 0, 0, 1),
        ).fire("Message!")

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-text-color-button.png
        :align: center

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

    position = OptionProperty(
        "center",
        options=("center", "top-left", "top-right", "bottom-left",
                 "bottom-right"),
    )
    """
    Dialog position. Available options are: `'center'`, `'top-left'`, `'top-right'`,
    `'bottom-left'`, `'bottom-right'`.

    .. code-block:: python

        SweetAlert(
            window_control_buttons="mac-style",
            position="top-right",
        ).fire("Message!")

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-top-right.png
        :align: center

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

    window_control_buttons = OptionProperty(None,
                                            options=["close", "mac-style"],
                                            allownone=True)
    """
    Type of buttons of window header.

    .. code-block:: python

        SweetAlert(window_control_buttons="mac-style").fire("Message!")

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-window-control-buttons.png
        :align: center

    .. code-block:: python

        SweetAlert(window_control_buttons="close").fire("Message!")

    .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-close.png
        :align: center

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

    window_control_callback = ObjectProperty()
    """
    Callback for buttons of window header.

    .. code-block:: python

        SweetAlert(
            window_control_buttons="close",
            window_control_callback=self.callback,
        ).fire("Message!")

    .. code-block:: python

        def callback(self, instance_button):
            print(instance_button)

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

    timer = NumericProperty(0)
    """
    Dialog closes automatically by timer.

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

    request = BooleanProperty(False)

    _timer = 0
    _created = False

    def __init__(self, **kwargs):
        self.type = "custom"
        if hasattr(self, "overlay_color"):
            self.overlay_color = (0, 0, 0, 0)
        else:
            self.background_color = (0, 0, 0, 0)
        # A box in which text, buttons and icons will be placed of window.
        self.content_cls = MDBoxLayout(
            adaptive_height=True,
            orientation="vertical",
            padding=("24dp", "68dp", "24dp", "24dp"),
            spacing="24dp",
        )
        self.opacity = 0
        self.bind(
            on_open={"pulse":
                     self.anim_open_dialog_pulse}.get(self.animation_type),
            on_dismiss={"pulse":
                        self.anim_close_dialog_pulse}.get(self.animation_type),
        )
        self._scale_x = 0
        self._scale_y = 0
        super().__init__(**kwargs)

        # Creating and adding control buttons (close/collapse/expand)
        # in the window header.
        if self.window_control_buttons:
            button_box = RelativeLayout()
            self.add_widget(button_box)
            if self.window_control_buttons == "close":
                button = MDIconButton(
                    icon="close",
                    pos_hint={"top": 1},
                    x=self.width - self.content_cls.padding[0] - dp(32),
                )
                if self.window_control_callback:
                    button.bind(on_release=self.window_control_callback)
                button_box.add_widget(button)
            elif self.window_control_buttons == "mac-style":
                # Color and padding of buttons.
                data = {"#eb5b53": 8, "#f5bc48": 28, "#64ca43": 48}
                for color in data:
                    button = MacOSWindowHeaderButton(
                        text_color=get_color_from_hex(color),
                        x=data[color],
                    )
                    if self.window_control_callback:
                        button.bind(on_release=lambda x=button: self.
                                    window_control_callback(x))
                    button_box.add_widget(button)

        Window.bind(on_resize=lambda *args: self.dismiss())

    def fire(
        self,
        title="",
        text="",
        footer="",
        image="",
        height_image="200dp",
        input="",
        buttons=None,
        type="",
    ):
        """
        Arguments:
        ----------

        `title`

        .. code-block:: python

            SweetAlert().fire("Title")

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-title.png
            :align: center

        `text`

        .. code-block:: python

            SweetAlert().fire("Title", "Text")

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-title-text.png
            :align: center

        Or without title:

        .. code-block:: python

            SweetAlert().fire(text="Text")

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-text.png
            :align: center

        `footer`

        .. code-block:: python

            SweetAlert().fire(text="Message", footer="Footer text")

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-footer.png
            :align: center

        `image`

        .. code-block:: python

            SweetAlert().fire(text="Message", image="https://picsum.photos/600/400/?blur")

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-image.png
            :align: center

        `input`

        .. code-block:: python

            SweetAlert().fire(text="Message", input="Input Email")

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-input.png
            :align: center

        Or combine parameters:

        .. code-block:: python

            SweetAlert().fire(
                text="Message",
                image="https://picsum.photos/600/400/?blur",
                input="Input Email",
            )

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-combine.png
            :align: center

        `buttons`

        .. code-block:: python

            from kivy.lang import Builder

            from kivymd.app import MDApp
            from kivymd.uix.button import MDRaisedButton, MDFlatButton

            from kivymd_extensions.sweetalert import SweetAlert

            KV = '''
            MDScreen:

                MDRaisedButton:
                    text: "EXAMPLE"
                    pos_hint: {"center_x": .5, "center_y": .5}
                    on_release: app.show_dialog()
            '''


            class Test(MDApp):
                def build(self):
                    return Builder.load_string(KV)

                def show_dialog(self):
                    button_ok = MDRaisedButton(
                        text='OK',
                        font_size=16,
                        on_release=self.callback,
                    )
                    button_cancel = MDFlatButton(
                        text='CANCEL',
                        font_size=16,
                        on_release=self.callback,
                    )
                    self.alert = SweetAlert()
                    self.alert.fire(
                        'Your public IP', buttons=[button_ok, button_cancel],
                    )

                def callback(self, instance_button):
                    print(self.alert, instance_button)


            Test().run()

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-buttons.png
            :align: center

        `type`:

        ``success``

        .. code-block:: python

            SweetAlert().fire('That thing is still around?', type='success')

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-success.png
            :align: center

        ``failure``

        .. code-block:: python

            SweetAlert().fire('That thing is still around?', type='failure')

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-failure.png
            :align: center

        ``warning``

        .. code-block:: python

            SweetAlert().fire('That thing is still around?', type='warning')

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-warning.png
            :align: center

        ``info``

        .. code-block:: python

            SweetAlert().fire('That thing is still around?', type='info')

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-info.png
            :align: center

        ``question``

        .. code-block:: python

            SweetAlert().fire('That thing is still around?', type='question')

        .. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/sweet-alert-question.png
            :align: center
        """

        if self._created:
            self._open(0)
            return

        if not image:
            self.add_icon(type)
        else:
            self.add_image(image, height_image)
        self.add_title(title)
        self.add_text(text)
        self.add_input(input)
        self.add_buttons(buttons)
        self.add_footer(footer)
        if self.timer:
            Clock.schedule_interval(self.check_timer, 1)
        Clock.schedule_once(self._open)

    def update_width(self, *args):
        """Updates window width."""

        self.width = max(
            self.height + dp(48),
            min(
                dp(560) if DEVICE_TYPE == "desktop" else dp(280),
                Window.width - dp(48),
            ),
        )

        # Updates button "close" position.
        for widget in self.children:
            if isinstance(widget, RelativeLayout):
                if len(widget.children) == 1 and isinstance(
                        widget.children[0], MDIconButton):
                    if widget.children[0].icon == "close":
                        widget.children[0].x = (self.width -
                                                self.content_cls.padding[0] -
                                                dp(32))
                        break

    def on_request(self, instance, value):
        """Adds a ``MDSpinner`` to the dialog."""

        for widget in self.content_cls.children:
            if isinstance(widget, MDBoxLayout):
                widget.clear_widgets()
                self.content_cls.spacing = "12dp"
                self.content_cls.add_widget(
                    MDSpinner(
                        size_hint=(None, None),
                        size=("48dp", "48dp"),
                        pos_hint={"center_x": 0.5},
                    ))
                break

    def add_input(self, input):
        if input:
            field = MDTextFieldRect(
                size_hint=(None, None),
                size=(self.width - dp(40), dp(30)),
                pos_hint={
                    "center_y": 0.5,
                    "center_x": 0.5
                },
            )
            if isinstance(input, str):
                field.hint_text = input
            self.content_cls.add_widget(field)

    def check_timer(self, timer, interval=1):
        self._timer += 1
        if self._timer > self.timer:
            Clock.unschedule(self.check_timer)
            self._timer = 0
            self.dismiss()

    def add_image(self, image, height_image):
        self.content_cls.padding[1] = 0
        self.content_cls.add_widget(
            FitImage(source=image, size_hint_y=None, height=height_image))

    def add_footer(self, footer):
        if footer:
            self.content_cls.add_widget(MDSeparator())
            footer_text = SweetAlertLabel(
                text=footer,
                font_style=self.font_style_footer,
                theme_text_color="Custom",
                text_color=get_color_from_hex("#429cf4"),
            )
            self.content_cls.add_widget(footer_text)

    def add_buttons(self, buttons):
        if not buttons:
            button = MDRaisedButton(
                text="OK",
                pos_hint={"center_x": 0.5},
                font_size=self.font_size_button,
                on_release=lambda x: self.dismiss(),
            )
            button.md_bg_color = (self.color_button if self.color_button else
                                  self.theme_cls.primary_color)
            button.text_color = (self.text_color_button
                                 if self.text_color_button else (0, 0, 0, 1))
            self.content_cls.add_widget(button)
        else:
            box = MDBoxLayout(adaptive_size=True,
                              spacing="12dp",
                              pos_hint={"center_x": 0.5})
            self.content_cls.add_widget(box)
            for button in buttons:
                box.add_widget(button)

    def add_text(self, text):
        if text:
            label_text = SweetAlertLabel(
                text=text,
                font_style=self.font_style_text,
                theme_text_color="Custom",
                text_color=self.theme_cls.disabled_hint_text_color,
            )
            self.content_cls.add_widget(label_text)

    def add_title(self, title):
        if title:
            label_title = SweetAlertLabel(text=title,
                                          font_style=self.font_style_title)
            self.content_cls.add_widget(label_title)

    def add_icon(self, type, char="", color=()):
        if type not in ("success", "failure", "question", "info", "warning"):
            type = ""
        if type == "success":
            self.content_cls.add_widget(SuccessAnimation())
        elif type == "failure":
            self.content_cls.add_widget(FailureAnimation())
        else:
            if type == "question":
                char = "?"
                color = get_color_from_hex("#7ea1af")
            elif type == "info":
                char = "i"
                color = get_color_from_hex("#55bce5")
            elif type == "warning":
                char = "!"
                color = get_color_from_hex("#edb481")
            if char and color:
                self.content_cls.add_widget(
                    OthersAnimation(char=char, color=color))
        if type:
            self.content_cls.add_widget(Widget(size_hint_y=None,
                                               height="48dp"))
        else:
            self.content_cls.padding = ("24dp", "36dp", "24dp", "24dp")

    def anim_open_dialog_pulse(self, *args):
        anim = Animation(opacity=1,
                         _scale_x=1,
                         _scale_y=1,
                         t="out_bounce",
                         d=0.6)
        anim.start(self)
        # Starts the animation of the "failure" and "success" icons.
        for widget in self.content_cls.children:
            if isinstance(widget, (SuccessAnimation, FailureAnimation)):
                widget.play()

    def anim_close_dialog_pulse(self, *args):
        Animation(opacity=0, _scale_x=0, _scale_y=0, t="out_quad",
                  d=0.6).start(self)

    def _open(self, interval):
        self.ids.container.height = self.content_cls.height
        self.open()
        self._created = True

    def _align_center(self, *l):
        if self._window:
            if self.position == "top-left":
                self.pos = (10, Window.height - self.height - 10)
            elif self.position == "top-right":
                self.pos = (
                    Window.width - self.width - 10,
                    Window.height - self.height - 10,
                )
            elif self.position == "bottom-left":
                self.pos = (10, 10)
            elif self.position == "bottom-right":
                self.pos = (Window.width - self.width - 10, 10)
            else:
                self.center = self._window.center
예제 #21
0
class SelectableButton(RecycleDataViewBehavior, Button):
    ''' Add selection support to the Button '''
    index = None
    selected = BooleanProperty(False)
    selectable = BooleanProperty(True)
    rv_data = ObjectProperty(None)
    start_point = NumericProperty(0)
    color = [0, 0, 0, 1]

    def refresh_view_attrs(self, rv, index, data):
        ''' Catch and handle the view changes '''
        self.index = index
        return super(SelectableButton,
                     self).refresh_view_attrs(rv, index, data)

    def on_touch_down(self, touch):
        ''' Add selection on touch down '''
        if super(SelectableButton, self).on_touch_down(touch):
            return True
        if self.collide_point(*touch.pos) and self.selectable:
            return self.parent.select_with_touch(self.index, touch)

    def apply_selection(self, rv, index, is_selected):
        ''' Respond to the selection of items in the view. '''
        self.background_color = [.98, .99, .98, 0]
        self.selected = not is_selected
        self.rv_data = rv.data

    def on_press(self):

        self.start_point = 0
        end_point = MAX_TABLE_COLS
        rows = len(self.rv_data) // MAX_TABLE_COLS
        for row in range(rows):
            if self.index in list(range(end_point)):
                break
            self.start_point += MAX_TABLE_COLS
            end_point += MAX_TABLE_COLS

        global choosed
        if not choosed:
            choosed = self
            choosed.background_color = [.30, .84, .84, .5]
        else:
            choosed.background_color = [.98, .99, .98, 0]
            choosed = self
            choosed.background_color = [.30, .84, .84, .5]

        buttons = self.parent.parent.parent.parent.parent.parent.children[
            0].children
        for i in buttons:
            i.disabled = False
            if i.text == "Edit":
                editPage = App.get_running_app().root.get_screen('edit')
                editPage.col_data = [
                    self.rv_data[self.start_point]['text'],
                    self.rv_data[self.start_point + 1]['text'],
                    self.rv_data[self.start_point + 2]['text']
                ]
                editPage.editConnection()

            elif i.text == "Remove":
                remove = App.get_running_app().root.get_screen('main')
                remove.col_data = [
                    self.rv_data[self.start_point]['text'],
                ]

            elif i.text == "Pay Roll":
                payrollPage = App.get_running_app().root.get_screen('payroll')
                employeecheckPage = App.get_running_app().root.get_screen(
                    'employeecheck')
                payrollPage.col_data = [
                    self.rv_data[self.start_point]['text'],
                    self.rv_data[self.start_point + 1]['text'],
                    self.rv_data[self.start_point + 2]['text']
                ]
                employeecheckPage.col_data = [
                    self.rv_data[self.start_point]['text'],
                    self.rv_data[self.start_point + 1]['text'],
                    self.rv_data[self.start_point + 2]['text']
                ]
                payrollPage.payrollConnection()
                employeecheckPage.employeecheckConnection()
예제 #22
0
파일: __init__.py 프로젝트: xaviercd/kivy
class Sound(EventDispatcher):
    '''Represents a sound to play. This class is abstract, and cannot be used
    directly.

    Use SoundLoader to load a sound.

    :Events:
        `on_play` : None
            Fired when the sound is played.
        `on_stop` : None
            Fired when the sound is stopped.
    '''

    source = StringProperty(None)
    '''Filename / source of your audio file.

    .. versionadded:: 1.3.0

    :attr:`source` is a :class:`~kivy.properties.StringProperty` that defaults
    to None and is read-only. Use the :meth:`SoundLoader.load` for loading
    audio.
    '''

    volume = NumericProperty(1.)
    '''Volume, in the range 0-1. 1 means full volume, 0 means mute.

    .. versionadded:: 1.3.0

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

    state = OptionProperty('stop', options=('stop', 'play'))
    '''State of the sound, one of 'stop' or 'play'.

    .. versionadded:: 1.3.0

    :attr:`state` is a read-only :class:`~kivy.properties.OptionProperty`.'''

    loop = BooleanProperty(False)
    '''Set to True if the sound should automatically loop when it finishes.

    .. versionadded:: 1.8.0

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

    #
    # deprecated
    #
    def _get_status(self):
        return self.state

    status = AliasProperty(_get_status, None, bind=('state', ))
    '''
    .. deprecated:: 1.3.0
        Use :attr:`state` instead.
    '''

    def _get_filename(self):
        return self.source

    filename = AliasProperty(_get_filename, None, bind=('source', ))
    '''
    .. deprecated:: 1.3.0
        Use :attr:`source` instead.
    '''

    __events__ = ('on_play', 'on_stop')

    def on_source(self, instance, filename):
        self.unload()
        if filename is None:
            return
        self.load()

    def get_pos(self):
        '''
        Returns the current position of the audio file.
        Returns 0 if not playing.

        .. versionadded:: 1.4.1
        '''
        return 0

    def _get_length(self):
        return 0

    length = property(lambda self: self._get_length(),
                      doc='Get length of the sound (in seconds).')

    def load(self):
        '''Load the file into memory.'''
        pass

    def unload(self):
        '''Unload the file from memory.'''
        pass

    def play(self):
        '''Play the file.'''
        self.state = 'play'
        self.dispatch('on_play')

    def stop(self):
        '''Stop playback.'''
        self.state = 'stop'
        self.dispatch('on_stop')

    def seek(self, position):
        '''Go to the <position> (in seconds).'''
        pass

    def on_play(self):
        pass

    def on_stop(self):
        pass
예제 #23
0
class VideoPlayer(GridLayout):
    '''VideoPlayer class. See module documentation for more information.
    '''

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

    :data:`source` a :class:`~kivy.properties.StringProperty`, default to ''.
    .. versionchanged:: 1.4.0
    '''

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

    :data:`thumbnail` a :class:`~kivy.properties.StringProperty`, default 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.

    :data:`duration` is a :class:`~kivy.properties.NumericProperty`, default to
    -1.
    '''

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

    :data:`position` is a :class:`~kivy.properties.NumericProperty`, default to
    -1.
    '''

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

    :data:`volume` is a :class:`~kivy.properties.NumericProperty`, default 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 = Video(source='movie.mkv', state='play')

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

    :data:`state` is a :class:`~kivy.properties.OptionProperty`, default to
    'play'.
    '''

    play = BooleanProperty(False)
    '''
    .. deprecated:: 1.4.0
        Use :data:`state` instead.

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

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

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

    :data:`play` is a :class:`~kivy.properties.BooleanProperty`, default to
    False.
    '''

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

    :data:`image_overlay_play` a :class:`~kivy.properties.StringProperty`
    '''

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

    :data:`image_loading` a :class:`~kivy.properties.StringProperty`
    '''

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

    :data:`image_play` a :class:`~kivy.properties.StringProperty`
    '''

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

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

    :data:`image_pause` a :class:`~kivy.properties.StringProperty`
    '''

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

    :data:`image_volumehigh` a :class:`~kivy.properties.StringProperty`
    '''

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

    :data:`image_volumemedium` a :class:`~kivy.properties.StringProperty`
    '''

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

    :data:`image_volumelow` a :class:`~kivy.properties.StringProperty`
    '''

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

    :data:`image_volumemuted` a :class:`~kivy.properties.StringProperty`
    '''

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

    fullscreen = BooleanProperty(False)
    '''Switch to control fullscreen view. This must 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 reset to
    its previous parent.

    .. warning::

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

    :data:`fullscreen` a :class:`~kivy.properties.BooleanProperty`, default 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.

    :data:`allow_fullscreen` a :class:`~kivy.properties.BooleanProperty`,
    default to True
    '''

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

    :data:`options` a :class:`~kivy.properties.DictProperty`,
    default to {}
    '''

    # internals
    container = ObjectProperty(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()

    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()
        self._video = None

    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'
        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 None:
            self._video = Video(source=self.source,
                                state='play',
                                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'))
        self._video.state = value

    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):
        '''Change the position to a percentage of duration. Percentage must be a
        value between 0-1.

        .. warning::

            Calling seek() before video is loaded has no impact.
        '''
        if not self._video:
            return
        self._video.seek(percent)

    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 reajusted
            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)
예제 #24
0
파일: __init__.py 프로젝트: vesellov/KivyMD
class MDAdaptiveWidget(SpecificBackgroundColorBehavior):
    adaptive_height = BooleanProperty(False)
    """
    If `True`, the following properties will be applied to the widget:

    .. code-block:: kv

        size_hint_y: None
        height: self.minimum_height

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

    adaptive_width = BooleanProperty(False)
    """
    If `True`, the following properties will be applied to the widget:

    .. code-block:: kv

        size_hint_x: None
        width: self.minimum_width

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

    adaptive_size = BooleanProperty(False)
    """
    If `True`, the following properties will be applied to the widget:

    .. code-block:: kv

        size_hint: None, None
        size: self.minimum_size

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

    def on_adaptive_height(self, md_widget, value: bool) -> None:
        self.size_hint_y = None
        if issubclass(self.__class__, Label):
            self.bind(
                texture_size=lambda *x: self.setter("height")(
                    self, self.texture_size[1]
                )
            )
        else:
            if not isinstance(self, (FloatLayout, Screen)):
                self.bind(minimum_height=self.setter("height"))

    def on_adaptive_width(self, md_widget, value: bool) -> None:
        self.size_hint_x = None
        if issubclass(self.__class__, Label):
            self.bind(
                texture_size=lambda *x: self.setter("width")(
                    self, self.texture_size[0]
                )
            )
        else:
            if not isinstance(self, (FloatLayout, Screen)):
                self.bind(minimum_width=self.setter("width"))

    def on_adaptive_size(self, md_widget, value: bool) -> None:
        self.size_hint = (None, None)
        if issubclass(self.__class__, Label):
            self.text_size = (None, None)
            self.bind(
                texture_size=lambda *x: self.setter("size")(
                    self, self.texture_size
                )
            )
        else:
            if not isinstance(self, (FloatLayout, Screen)):
                self.bind(minimum_size=self.setter("size"))
예제 #25
0
class InfoBubble(Factory.Bubble):
    '''Bubble to be used to display short Help Information'''

    message = StringProperty(_('Nothing set !'))
    '''Message to be displayed; defaults to "nothing set"'''

    icon = StringProperty('')
    ''' Icon to be displayed along with the message defaults to ''

    :attr:`icon` is a  `StringProperty` defaults to `''`
    '''

    fs = BooleanProperty(False)
    ''' Show Bubble in half screen mode

    :attr:`fs` is a `BooleanProperty` defaults to `False`
    '''

    modal = BooleanProperty(False)
    ''' Allow bubble to be hidden on touch.

    :attr:`modal` is a `BooleanProperty` defauult to `False`.
    '''

    exit = BooleanProperty(False)
    '''Indicates whether to exit app after bubble is closed.

    :attr:`exit` is a `BooleanProperty` defaults to False.
    '''

    dim_background = BooleanProperty(False)
    ''' Indicates Whether to draw a background on the windows behind the bubble.

    :attr:`dim` is a `BooleanProperty` defaults to `False`.
    '''
    def on_touch_down(self, touch):
        if self.modal:
            return True
        self.hide()
        if self.collide_point(*touch.pos):
            return True

    def show(self, pos, duration, width=None, modal=False, exit=False):
        '''Animate the bubble into position'''
        self.modal, self.exit = modal, exit
        if width:
            self.width = width
        if self.modal:
            from kivy.uix.modalview import ModalView
            self._modal_view = m = ModalView(background_color=[.5, .5, .5, .2])
            Window.add_widget(m)
            m.add_widget(self)
        else:
            Window.add_widget(self)

        # wait for the bubble to adjust its size according to text then animate
        Clock.schedule_once(lambda dt: self._show(pos, duration))

    def _show(self, pos, duration):
        def on_stop(*l):
            if duration:
                Clock.schedule_once(self.hide, duration + .5)

        self.opacity = 0
        arrow_pos = self.arrow_pos
        if arrow_pos[0] in ('l', 'r'):
            pos = pos[0], pos[1] - (self.height / 2)
        else:
            pos = pos[0] - (self.width / 2), pos[1]

        self.limit_to = Window

        anim = Factory.Animation(opacity=1, pos=pos, d=.32)
        anim.bind(on_complete=on_stop)
        anim.cancel_all(self)
        anim.start(self)

    def hide(self, now=False):
        ''' Auto fade out the Bubble
        '''
        def on_stop(*l):
            if self.modal:
                m = self._modal_view
                m.remove_widget(self)
                Window.remove_widget(m)
            Window.remove_widget(self)
            if self.exit:
                App.get_running_app().stop()
                import sys
                sys.exit()
            else:
                App.get_running_app().is_exit = False

        if now:
            return on_stop()

        anim = Factory.Animation(opacity=0, d=.25)
        anim.bind(on_complete=on_stop)
        anim.cancel_all(self)
        anim.start(self)
예제 #26
0
class MDTextField(ThemableBehavior, TextInput):
    helper_text = StringProperty("This field is required")
    """
    Text for ``helper_text`` mode.

    :attr:`helper_text` is an :class:`~kivy.properties.StringProperty`
    and defaults to `'This field is required'`.
    """

    helper_text_mode = OptionProperty(
        "none", options=["none", "on_error", "persistent", "on_focus"]
    )
    """
    Helper text mode. Available options are: `'on_error'`, `'persistent'`,
    `'on_focus'`.

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

    max_text_length = NumericProperty(None)
    """
    Maximum allowed value of characters in a text field.

    :attr:`max_text_length` is an :class:`~kivy.properties.NumericProperty`
    and defaults to `None`.
    """

    required = BooleanProperty(False)
    """
    Required text. If True then the text field requires text.

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

    color_mode = OptionProperty(
        "primary", options=["primary", "accent", "custom"]
    )
    """
    Color text mode. Available options are: `'primary'`, `'accent'`,
    `'custom'`.

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

    mode = OptionProperty("line", options=["rectangle", "fill"])
    """
    Text field mode. Available options are: `'line'`, `'rectangle'`, `'fill'`.

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

    line_color_normal = ListProperty()
    """
    Line color normal in ``rgba`` format.

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

    line_color_focus = ListProperty()
    """
    Line color focus in ``rgba`` format.

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

    error_color = ListProperty()
    """
    Error color in ``rgba`` format for ``required = True``.

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

    fill_color = ListProperty((0, 0, 0, 0))
    """
    The background color of the fill in rgba format when the ``mode`` parameter
    is "fill".

    :attr:`fill_color` is an :class:`~kivy.properties.ListProperty`
    and defaults to `(0, 0, 0, 0)`.
    """

    active_line = BooleanProperty(True)
    """
    Show active line or not.

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

    error = BooleanProperty(False)
    """
    If True, then the text field goes into ``error`` mode.

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

    current_hint_text_color = ListProperty()
    """
    ``hint_text`` text color.

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

    icon_right = StringProperty()
    """Right icon.

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

    icon_right_color = ListProperty((0, 0, 0, 1))
    """Color of right icon in ``rgba`` format.

    :attr:`icon_right_color` is an :class:`~kivy.properties.ListProperty`
    and defaults to `(0, 0, 0, 1)`.
    """

    _text_len_error = BooleanProperty(False)
    _hint_lbl_font_size = NumericProperty("16sp")
    _line_blank_space_right_point = NumericProperty(0)
    _line_blank_space_left_point = NumericProperty(0)
    _hint_y = NumericProperty("38dp")
    _line_width = NumericProperty(0)
    _current_line_color = ListProperty((0, 0, 0, 0))
    _current_error_color = ListProperty((0, 0, 0, 0))
    _current_hint_text_color = ListProperty((0, 0, 0, 0))
    _current_right_lbl_color = ListProperty((0, 0, 0, 0))

    _msg_lbl = None
    _right_msg_lbl = None
    _hint_lbl = None
    _lbl_icon_right = None

    def __init__(self, **kwargs):
        self.set_objects_labels()
        super().__init__(**kwargs)
        # Sets default colors.
        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 set_objects_labels(self):
        """Creates labels objects for the parameters
        `helper_text`,`hint_text`, etc."""

        # Label object for `helper_text` parameter.
        self._msg_lbl = TextfieldLabel(
            font_style="Caption",
            halign="left",
            valign="middle",
            text=self.helper_text,
            field=self,
        )
        # Label object for `max_text_length` parameter.
        self._right_msg_lbl = TextfieldLabel(
            font_style="Caption",
            halign="right",
            valign="middle",
            text="",
            field=self,
        )
        # Label object for `hint_text` parameter.
        self._hint_lbl = TextfieldLabel(
            font_style="Subtitle1", halign="left", valign="middle", field=self
        )
        # MDIcon object for the icon on the right.
        self._lbl_icon_right = MDIcon(theme_text_color="Custom")

    def on_icon_right(self, instance, value):
        self._lbl_icon_right.icon = value

    def on_icon_right_color(self, instance, value):
        self._lbl_icon_right.text_color = value

    def on_width(self, instance, width):
        """Called when the application window is resized."""

        if (
            any((self.focus, self.error, self._text_len_error))
            and instance is not None
        ):
            # Bottom line width when active focus.
            self._line_width = width
        self._msg_lbl.width = self.width
        self._right_msg_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"
        )
        self._set_text_len_error()

        if self.focus:
            self._line_blank_space_right_point = (
                self._get_line_blank_space_right_point()
            )
            _fill_color = self.fill_color
            _fill_color[3] = self.fill_color[3] - 0.1
            Animation(
                _line_blank_space_right_point=self._line_blank_space_right_point,
                _line_blank_space_left_point=self._hint_lbl.x - dp(5),
                _current_hint_text_color=self.line_color_focus,
                fill_color=_fill_color,
                duration=0.2,
                t="out_quad",
            ).start(self)
            self.has_had_text = True
            Animation.cancel_all(
                self, "_line_width", "_hint_y", "_hint_lbl_font_size"
            )
            if not self.text:
                self._anim_lbl_font_size(dp(14), sp(12))
            Animation(_line_width=self.width, duration=0.2, t="out_quad").start(
                self
            )
            if self._get_has_error():
                self._anim_current_error_color(self.error_color)
                if self.helper_text_mode == "on_error" and (
                    self.error or self._text_len_error
                ):
                    self._anim_current_error_color(self.error_color)
                elif (
                    self.helper_text_mode == "on_error"
                    and not self.error
                    and not self._text_len_error
                ):
                    self._anim_current_error_color((0, 0, 0, 0))
                elif self.helper_text_mode in ("persistent", "on_focus"):
                    self._anim_current_error_color(disabled_hint_text_color)
            else:
                self._anim_current_right_lbl_color(disabled_hint_text_color)
                Animation(duration=0.2, color=self.line_color_focus).start(
                    self._hint_lbl
                )
                if self.helper_text_mode == "on_error":
                    self._anim_current_error_color((0, 0, 0, 0))
                if self.helper_text_mode in ("persistent", "on_focus"):
                    self._anim_current_error_color(disabled_hint_text_color)
        else:
            _fill_color = self.fill_color
            _fill_color[3] = self.fill_color[3] + 0.1
            Animation(fill_color=_fill_color, duration=0.2, t="out_quad").start(
                self
            )
            if not self.text:
                self._anim_lbl_font_size(dp(38), sp(16))
                Animation(
                    _line_blank_space_right_point=0,
                    _line_blank_space_left_point=0,
                    duration=0.2,
                    t="out_quad",
                ).start(self)
            if self._get_has_error():
                self._anim_get_has_error_color(self.error_color)
                if self.helper_text_mode == "on_error" and (
                    self.error or self._text_len_error
                ):
                    self._anim_current_error_color(self.error_color)
                elif (
                    self.helper_text_mode == "on_error"
                    and not self.error
                    and not self._text_len_error
                ):
                    self._anim_current_error_color((0, 0, 0, 0))
                elif self.helper_text_mode == "persistent":
                    self._anim_current_error_color(disabled_hint_text_color)
                elif self.helper_text_mode == "on_focus":
                    self._anim_current_error_color((0, 0, 0, 0))
            else:
                Animation(duration=0.2, color=(1, 1, 1, 1)).start(
                    self._hint_lbl
                )
                self._anim_get_has_error_color()
                if self.helper_text_mode == "on_error":
                    self._anim_current_error_color((0, 0, 0, 0))
                elif self.helper_text_mode == "persistent":
                    self._anim_current_error_color(disabled_hint_text_color)
                elif self.helper_text_mode == "on_focus":
                    self._anim_current_error_color((0, 0, 0, 0))
                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}"
        self._set_text_len_error()
        if self.error or self._text_len_error:
            if self.focus:
                self._anim_current_line_color(self.error_color)
                if self.helper_text_mode == "on_error" and (
                    self.error or self._text_len_error
                ):
                    self._anim_current_error_color(self.error_color)
                if self._text_len_error:
                    self._anim_current_right_lbl_color(self.error_color)
        else:
            if self.focus:
                self._anim_current_right_lbl_color(
                    self.theme_cls.disabled_hint_text_color
                )
                self._anim_current_line_color(self.line_color_focus)
                if self.helper_text_mode == "on_error":
                    self._anim_current_error_color((0, 0, 0, 0))
        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
        self._set_text_len_error()

    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)

    def on__hint_text(self, instance, value):
        pass

    def _anim_get_has_error_color(self, color=None):
        # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_get_has_error.png
        if not color:
            line_color = self.line_color_focus
            hint_text_color = self.theme_cls.disabled_hint_text_color
            right_lbl_color = (0, 0, 0, 0)
        else:
            line_color = color
            hint_text_color = color
            right_lbl_color = color
        Animation(
            duration=0.2,
            _current_line_color=line_color,
            _current_hint_text_color=hint_text_color,
            _current_right_lbl_color=right_lbl_color,
        ).start(self)

    def _anim_current_line_color(self, color):
        # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_line_color.gif
        Animation(
            duration=0.2,
            _current_hint_text_color=color,
            _current_line_color=color,
        ).start(self)

    def _anim_lbl_font_size(self, hint_y, font_size):
        Animation(
            _hint_y=hint_y,
            _hint_lbl_font_size=font_size,
            duration=0.2,
            t="out_quad",
        ).start(self)

    def _anim_current_right_lbl_color(self, color, duration=0.2):
        # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_right_lbl_color.png
        Animation(duration=duration, _current_right_lbl_color=color).start(self)

    def _anim_current_error_color(self, color, duration=0.2):
        # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_error_color_to_disabled_color.gif
        # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_anim_current_error_color_to_fade.gif
        Animation(duration=duration, _current_error_color=color).start(self)

    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 _get_has_error(self):
        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
        return has_error

    def _get_line_blank_space_right_point(self):
        # https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/_line_blank_space_right_point.png
        return self._hint_lbl.texture_size[0] - self._hint_lbl.texture_size[
            0
        ] / 100 * dp(18)

    def _get_max_text_length(self):
        """Returns the maximum number of characters that can be entered in a
        text field."""

        return (
            sys.maxsize
            if self.max_text_length is None
            else self.max_text_length
        )

    def _set_text_len_error(self):
        if len(self.text) > self._get_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

    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":
            self._anim_current_error_color(
                self.theme_cls.disabled_hint_text_color, 0.1
            )

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

    def _refresh_hint_text(self):
        pass
예제 #27
0
class SmartTile(ThemableBehavior, RectangularRippleBehavior, ButtonBehavior,
                FloatLayout):
    """
    A tile for more complex needs.

    Includes an image, a container to place overlays and a box that can act
    as a header or a footer, as described in the Material Design specs.
    """

    box_color = ListProperty([0, 0, 0, 0.5])
    """
    Sets the color and opacity for the information box.

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

    box_position = OptionProperty("footer", options=["footer", "header"])
    """
    Determines wether the information box acts as a header or footer to the
    image. Available are options: `'footer'`, `'header'`.

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

    lines = OptionProperty(1, options=[1, 2])
    """
    Number of lines in the `header/footer`. As per `Material Design specs`,
    only 1 and 2 are valid values. Available are options: ``1``, ``2``.

    :attr:`lines` is a :class:`~kivy.properties.OptionProperty`
    and defaults to `1`.
    """

    overlap = BooleanProperty(True)
    """
    Determines if the `header/footer` overlaps on top of the image or not.

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

    allow_stretch = BooleanProperty(True)
    """
    See :attr:`~kivy.uix.image.Image.allow_stretch`.

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

    anim_delay = NumericProperty(0.25)
    """
    See :attr:`~kivy.uix.image.Image.anim_delay`.

    :attr:`anim_delay` is a :class:`~kivy.properties.NumericProperty`
    and defaults to `0.25`.
    """

    anim_loop = NumericProperty(0)
    """
    See :attr:`~kivy.uix.image.Image.anim_loop`.

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

    keep_ratio = BooleanProperty(False)
    """
    See :attr:`~kivy.uix.image.Image.keep_ratio`.

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

    mipmap = BooleanProperty(False)
    """
    See :attr:`~kivy.uix.image.Image.mipmap`.

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

    source = StringProperty()
    """
    Path to tile image. See :attr:`~kivy.uix.image.Image.source`.

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

    _img_widget = ObjectProperty()
    _img_overlay = ObjectProperty()
    _box_overlay = ObjectProperty()
    _box_label = ObjectProperty()

    def reload(self):
        self._img_widget.reload()

    def add_widget(self, widget, index=0, canvas=None):
        if issubclass(widget.__class__, IOverlay):
            self._img_overlay.add_widget(widget, index, canvas)
        elif issubclass(widget.__class__, IBoxOverlay):
            self._box_overlay.add_widget(widget, index, canvas)
        else:
            super().add_widget(widget, index, canvas)
예제 #28
0
class Switch(Widget):
    '''Switch class. See module documentation for more information.
    '''
    active = BooleanProperty(False)
    '''Indicate whether the switch is active or inactive.

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

    touch_control = ObjectProperty(None, allownone=True)
    '''(internal) Contains the touch that currently interacts with the switch.

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

    touch_distance = NumericProperty(0)
    '''(internal) Contains the distance between the initial position of the
    touch and the current position to determine if the swipe is from the left
    or right.

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

    active_norm_pos = NumericProperty(0)
    '''(internal) Contains the normalized position of the movable element
    inside the switch, in the 0-1 range.

    :attr:`active_norm_pos` is a :class:`~kivy.properties.NumericProperty`
    and defaults to 0.
    '''
    def on_touch_down(self, touch):
        if self.disabled or self.touch_control is not None:
            return
        if not self.collide_point(*touch.pos):
            return
        touch.grab(self)
        self.touch_distance = 0
        self.touch_control = touch
        return True

    def on_touch_move(self, touch):
        if touch.grab_current is not self:
            return
        self.touch_distance = touch.x - touch.ox
        return True

    def on_touch_up(self, touch):
        if touch.grab_current is not self:
            return
        touch.ungrab(self)
        # depending of the distance, activate by norm pos or invert
        if abs(touch.ox - touch.x) < 5:
            self.active = not self.active
        else:
            self.active = self.active_norm_pos > 0.5
        Animation(active_norm_pos=int(self.active), t='out_quad',
                  d=.2).start(self)
        self.touch_control = None
        return True
예제 #29
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"]

    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

    def show(self, path):
        """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):
        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):
        """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):
        """Closes the file manager window."""

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

    def select_dir_or_file(self, path, widget):
        """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):
        """Returning to the branch down in the directory tree."""

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

        if not end:
            self.close()
            self.exit_manager(1)

        else:
            self.show(path)

    def select_directory_on_press_button(self, *args):
        """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)
예제 #30
0
class FloatLabel(AnchorLayout):
    text = StringProperty("")
    position = OptionProperty("bottom", options=["bottom", "top"])
    duration = NumericProperty(3)
    clicable = BooleanProperty(False)
    bg_color = ObjectProperty((0, 1, .2, 1))
    color = ObjectProperty((0, 0, 0, 1))

    __show__ = BooleanProperty(False)
    __contador__ = NumericProperty(0)

    def __get_pos_widget__(self):
        if not self.__show__:
            if self.position == 'bottom':
                return 0, -self.height
            else:
                return 0, self.height

    __pos_widget__ = AliasProperty(__get_pos_widget__,
                                   bind=["position", "size"])

    def on_color(self, w, val):
        if "#" in val:
            val = "".join(val)
            self.color = get_color_from_hex(val)
        else:
            self.color = val

    def on_bg_color(self, w, val):
        if "#" in val:
            val = "".join(val)
            self.bg_color = get_color_from_hex(val)
        else:
            self.bg_color = val

    def __init__(self, **kargs):
        super(FloatLabel, self).__init__(**kargs)

    def show(self):
        if not self.__show__:
            self.__show__ = True
            y = self.height if self.position == 'bottom' else -self.height
            ani = Animation(y=self.__label_widget__.y + y, duration=.5)
            ani.start(self.__label_widget__)
            Clock.schedule_once(self.hide_label, self.duration + .5)
        else:
            self.__contador__ += 1
            Clock.schedule_once(self.hide_label, self.duration + .5)

    def hide_label(self, dt):
        if self.__contador__ <= 0:
            self.__contador__ = 0
            y = -self.height if self.position == 'bottom' else self.height
            ani = Animation(y=self.__label_widget__.y + y, duration=.5)
            ani.start(self.__label_widget__)
            Clock.schedule_once(self.clear_show, .5)
        else:
            self.__contador__ -= 1

    def clear_show(self, dt):
        self.__show__ = False