diff cagou/core/widgets_handler.py @ 126:cd99f70ea592

global file reorganisation: - follow common convention by puttin cagou in "cagou" instead of "src/cagou" - added VERSION in cagou with current version - updated dates - moved main executable in /bin - moved buildozer files in root directory - temporary moved platform to assets/platform
author Goffi <goffi@goffi.org>
date Thu, 05 Apr 2018 17:11:21 +0200
parents src/cagou/core/widgets_handler.py@1922506846be
children a5e8833184c6
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cagou/core/widgets_handler.py	Thu Apr 05 17:11:21 2018 +0200
@@ -0,0 +1,230 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Cagou: desktop/mobile frontend for Salut à Toi XMPP client
+# Copyright (C) 2016-2018 Jérôme Poisson (goffi@goffi.org)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+from sat.core import log as logging
+log = logging.getLogger(__name__)
+from sat_frontends.quick_frontend import quick_widgets
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.button import Button
+from kivy.uix.carousel import Carousel
+from kivy.metrics import dp
+from kivy import properties
+from cagou import G
+
+
+CAROUSEL_SCROLL_DISTANCE = dp(50)
+CAROUSEL_SCROLL_TIMEOUT = 80
+NEW_WIDGET_DIST = 10
+REMOVE_WIDGET_DIST = NEW_WIDGET_DIST
+
+
+class WHSplitter(Button):
+    horizontal=properties.BooleanProperty(True)
+    thickness=properties.NumericProperty(dp(20))
+    split_move = None # we handle one split at a time, so we use a class attribute
+
+    def __init__(self, handler, **kwargs):
+        super(WHSplitter, self).__init__(**kwargs)
+        self.handler = handler
+
+    def getPos(self, touch):
+        if self.horizontal:
+            relative_y = self.handler.to_local(*touch.pos, relative=True)[1]
+            return self.handler.height - relative_y
+        else:
+            return touch.x
+
+    def on_touch_move(self, touch):
+        if self.split_move is None and self.collide_point(*touch.opos):
+            WHSplitter.split_move = self
+
+        if self.split_move is self:
+            pos = self.getPos(touch)
+            if pos > NEW_WIDGET_DIST:
+                # we are above minimal distance, we resize the widget
+                self.handler.setWidgetSize(self.horizontal, pos)
+
+    def on_touch_up(self, touch):
+        if self.split_move is self:
+            pos = self.getPos(touch)
+            if pos <= REMOVE_WIDGET_DIST:
+                # if we go under minimal distance, the widget is not wanted anymore
+                self.handler.removeWidget(self.horizontal)
+            WHSplitter.split_move=None
+        return super(WHSplitter, self).on_touch_up(touch)
+
+
+class HandlerCarousel(Carousel):
+
+    def __init__(self, *args, **kwargs):
+        super(HandlerCarousel, self).__init__(
+            *args,
+            direction='right',
+            loop=True,
+            **kwargs)
+        self._former_slide = None
+        self.bind(current_slide=self.onSlideChange)
+        self._slides_update_lock = False
+
+    def changeWidget(self, new_widget):
+        """Change currently displayed widget
+
+        slides widgets will be updated
+        """
+        # slides update need to be blocked to avoid the update in onSlideChange
+        # which would mess the removal of current widgets
+        self._slides_update_lock = True
+        current = self.current_slide
+        for w in self.slides:
+            if w == current or w == new_widget:
+                continue
+            if isinstance(w, quick_widgets.QuickWidget):
+                G.host.widgets.deleteWidget(w)
+        self.clear_widgets()
+        self.add_widget(new_widget)
+        self._slides_update_lock = False
+        self.updateHiddenSlides()
+
+    def onSlideChange(self, handler, new_slide):
+        if isinstance(self._former_slide, quick_widgets.QuickWidget):
+            G.host.removeVisibleWidget(self._former_slide)
+        self._former_slide = new_slide
+        if isinstance(new_slide, quick_widgets.QuickWidget):
+            G.host.addVisibleWidget(new_slide)
+            self.updateHiddenSlides()
+
+    def hiddenList(self, visible_list):
+        """return widgets of same class as holded one which are hidden
+
+        @param visible_list(list[QuickWidget]): widgets visible
+        @return (iter[QuickWidget]): widgets hidden
+        """
+        added = [(w.targets, w.profiles) for w in visible_list]  # we want to avoid recreated widgets
+        for w in G.host.widgets.getWidgets(self.current_slide.__class__, profiles=self.current_slide.profiles):
+            if w in visible_list or (w.targets, w.profiles) in added:
+                continue
+            yield w
+
+    def widgets_sort(self, widget):
+        """method used as key to sort the widgets
+
+        order of the widgets when changing slide is affected
+        @param widget(QuickWidget): widget to sort
+        @return: a value which will be used for sorting
+        """
+        try:
+            return unicode(widget.target).lower()
+        except AttributeError:
+            return unicode(list(widget.targets)[0]).lower()
+
+    def updateHiddenSlides(self):
+        """adjust carousel slides according to visible widgets"""
+        if self._slides_update_lock:
+            return
+        if not isinstance(self.current_slide, quick_widgets.QuickWidget):
+            return
+        # lock must be used here to avoid recursions
+        self._slides_update_lock = True
+        visible_list = G.host.getVisibleList(self.current_slide.__class__)
+        hidden = list(self.hiddenList(visible_list))
+        slides_sorted =  sorted(hidden + [self.current_slide], key=self.widgets_sort)
+        to_remove = set(self.slides).difference({self.current_slide})
+        for w in to_remove:
+            self.remove_widget(w)
+        if hidden:
+            # no need to add more than two widgets (next and previous),
+            # as the list will be updated on each new visible widget
+            current_idx = slides_sorted.index(self.current_slide)
+            try:
+                next_slide = slides_sorted[current_idx+1]
+            except IndexError:
+                next_slide = slides_sorted[0]
+            self.add_widget(G.host.getOrClone(next_slide))
+            if len(hidden)>1:
+                previous_slide = slides_sorted[current_idx-1]
+                self.add_widget(G.host.getOrClone(previous_slide))
+
+        if len(self.slides) == 1:
+            # we block carousel with high scroll_distance to avoid swiping
+            # when the is not other instance of the widget
+            self.scroll_distance=2**32
+            self.scroll_timeout=0
+        else:
+            self.scroll_distance = CAROUSEL_SCROLL_DISTANCE
+            self.scroll_timeout=CAROUSEL_SCROLL_TIMEOUT
+        self._slides_update_lock = False
+
+
+class WidgetsHandler(BoxLayout):
+
+    def __init__(self, wid=None, **kw):
+        if wid is None:
+            wid=self.default_widget
+        self.vert_wid = self.hor_wid = None
+        BoxLayout.__init__(self, orientation="vertical", **kw)
+        self.blh = BoxLayout(orientation="horizontal")
+        self.blv = BoxLayout(orientation="vertical")
+        self.blv.add_widget(WHSplitter(self))
+        self.carousel = HandlerCarousel()
+        self.blv.add_widget(self.carousel)
+        self.blh.add_widget(WHSplitter(self, horizontal=False))
+        self.blh.add_widget(self.blv)
+        self.add_widget(self.blh)
+        self.changeWidget(wid)
+
+    @property
+    def default_widget(self):
+        return G.host.default_wid['factory'](G.host.default_wid, None, None)
+
+    @property
+    def cagou_widget(self):
+        """get holded CagouWidget"""
+        return self.carousel.current_slide
+
+    def changeWidget(self, new_widget):
+        self.carousel.changeWidget(new_widget)
+
+    def removeWidget(self, vertical):
+        if vertical and self.vert_wid is not None:
+            self.remove_widget(self.vert_wid)
+            self.vert_wid.onDelete()
+            self.vert_wid = None
+        elif self.hor_wid is not None:
+            self.blh.remove_widget(self.hor_wid)
+            self.hor_wid.onDelete()
+            self.hor_wid = None
+
+    def setWidgetSize(self, vertical, size):
+        if vertical:
+            if self.vert_wid is None:
+                self.vert_wid = WidgetsHandler(self.default_widget, size_hint=(1, None))
+                self.add_widget(self.vert_wid, len(self.children))
+            self.vert_wid.height=size
+        else:
+            if self.hor_wid is None:
+                self.hor_wid = WidgetsHandler(self.default_widget, size_hint=(None, 1))
+                self.blh.add_widget(self.hor_wid, len(self.blh.children))
+            self.hor_wid.width=size
+
+    def onDelete(self):
+        # when this handler is deleted, we need to delete the holded CagouWidget
+        cagou_widget = self.cagou_widget
+        if isinstance(cagou_widget, quick_widgets.QuickWidget):
+            G.host.removeVisibleWidget(cagou_widget)