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()
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
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()
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
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
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
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)
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)
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
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"
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)
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
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
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)
class BaseFlatIconButton(MDFlatButton): icon = StringProperty("android") text = StringProperty("") button_label = BooleanProperty(False)
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)
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
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()
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
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)
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"))
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)
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
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)
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
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)
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