Mercurial > libervia-desktop-kivy
comparison 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 |
comparison
equal
deleted
inserted
replaced
125:b6e6afb0dc46 | 126:cd99f70ea592 |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Cagou: desktop/mobile frontend for Salut à Toi XMPP client | |
5 # Copyright (C) 2016-2018 Jérôme Poisson (goffi@goffi.org) | |
6 | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 | |
21 from sat.core import log as logging | |
22 log = logging.getLogger(__name__) | |
23 from sat_frontends.quick_frontend import quick_widgets | |
24 from kivy.uix.boxlayout import BoxLayout | |
25 from kivy.uix.button import Button | |
26 from kivy.uix.carousel import Carousel | |
27 from kivy.metrics import dp | |
28 from kivy import properties | |
29 from cagou import G | |
30 | |
31 | |
32 CAROUSEL_SCROLL_DISTANCE = dp(50) | |
33 CAROUSEL_SCROLL_TIMEOUT = 80 | |
34 NEW_WIDGET_DIST = 10 | |
35 REMOVE_WIDGET_DIST = NEW_WIDGET_DIST | |
36 | |
37 | |
38 class WHSplitter(Button): | |
39 horizontal=properties.BooleanProperty(True) | |
40 thickness=properties.NumericProperty(dp(20)) | |
41 split_move = None # we handle one split at a time, so we use a class attribute | |
42 | |
43 def __init__(self, handler, **kwargs): | |
44 super(WHSplitter, self).__init__(**kwargs) | |
45 self.handler = handler | |
46 | |
47 def getPos(self, touch): | |
48 if self.horizontal: | |
49 relative_y = self.handler.to_local(*touch.pos, relative=True)[1] | |
50 return self.handler.height - relative_y | |
51 else: | |
52 return touch.x | |
53 | |
54 def on_touch_move(self, touch): | |
55 if self.split_move is None and self.collide_point(*touch.opos): | |
56 WHSplitter.split_move = self | |
57 | |
58 if self.split_move is self: | |
59 pos = self.getPos(touch) | |
60 if pos > NEW_WIDGET_DIST: | |
61 # we are above minimal distance, we resize the widget | |
62 self.handler.setWidgetSize(self.horizontal, pos) | |
63 | |
64 def on_touch_up(self, touch): | |
65 if self.split_move is self: | |
66 pos = self.getPos(touch) | |
67 if pos <= REMOVE_WIDGET_DIST: | |
68 # if we go under minimal distance, the widget is not wanted anymore | |
69 self.handler.removeWidget(self.horizontal) | |
70 WHSplitter.split_move=None | |
71 return super(WHSplitter, self).on_touch_up(touch) | |
72 | |
73 | |
74 class HandlerCarousel(Carousel): | |
75 | |
76 def __init__(self, *args, **kwargs): | |
77 super(HandlerCarousel, self).__init__( | |
78 *args, | |
79 direction='right', | |
80 loop=True, | |
81 **kwargs) | |
82 self._former_slide = None | |
83 self.bind(current_slide=self.onSlideChange) | |
84 self._slides_update_lock = False | |
85 | |
86 def changeWidget(self, new_widget): | |
87 """Change currently displayed widget | |
88 | |
89 slides widgets will be updated | |
90 """ | |
91 # slides update need to be blocked to avoid the update in onSlideChange | |
92 # which would mess the removal of current widgets | |
93 self._slides_update_lock = True | |
94 current = self.current_slide | |
95 for w in self.slides: | |
96 if w == current or w == new_widget: | |
97 continue | |
98 if isinstance(w, quick_widgets.QuickWidget): | |
99 G.host.widgets.deleteWidget(w) | |
100 self.clear_widgets() | |
101 self.add_widget(new_widget) | |
102 self._slides_update_lock = False | |
103 self.updateHiddenSlides() | |
104 | |
105 def onSlideChange(self, handler, new_slide): | |
106 if isinstance(self._former_slide, quick_widgets.QuickWidget): | |
107 G.host.removeVisibleWidget(self._former_slide) | |
108 self._former_slide = new_slide | |
109 if isinstance(new_slide, quick_widgets.QuickWidget): | |
110 G.host.addVisibleWidget(new_slide) | |
111 self.updateHiddenSlides() | |
112 | |
113 def hiddenList(self, visible_list): | |
114 """return widgets of same class as holded one which are hidden | |
115 | |
116 @param visible_list(list[QuickWidget]): widgets visible | |
117 @return (iter[QuickWidget]): widgets hidden | |
118 """ | |
119 added = [(w.targets, w.profiles) for w in visible_list] # we want to avoid recreated widgets | |
120 for w in G.host.widgets.getWidgets(self.current_slide.__class__, profiles=self.current_slide.profiles): | |
121 if w in visible_list or (w.targets, w.profiles) in added: | |
122 continue | |
123 yield w | |
124 | |
125 def widgets_sort(self, widget): | |
126 """method used as key to sort the widgets | |
127 | |
128 order of the widgets when changing slide is affected | |
129 @param widget(QuickWidget): widget to sort | |
130 @return: a value which will be used for sorting | |
131 """ | |
132 try: | |
133 return unicode(widget.target).lower() | |
134 except AttributeError: | |
135 return unicode(list(widget.targets)[0]).lower() | |
136 | |
137 def updateHiddenSlides(self): | |
138 """adjust carousel slides according to visible widgets""" | |
139 if self._slides_update_lock: | |
140 return | |
141 if not isinstance(self.current_slide, quick_widgets.QuickWidget): | |
142 return | |
143 # lock must be used here to avoid recursions | |
144 self._slides_update_lock = True | |
145 visible_list = G.host.getVisibleList(self.current_slide.__class__) | |
146 hidden = list(self.hiddenList(visible_list)) | |
147 slides_sorted = sorted(hidden + [self.current_slide], key=self.widgets_sort) | |
148 to_remove = set(self.slides).difference({self.current_slide}) | |
149 for w in to_remove: | |
150 self.remove_widget(w) | |
151 if hidden: | |
152 # no need to add more than two widgets (next and previous), | |
153 # as the list will be updated on each new visible widget | |
154 current_idx = slides_sorted.index(self.current_slide) | |
155 try: | |
156 next_slide = slides_sorted[current_idx+1] | |
157 except IndexError: | |
158 next_slide = slides_sorted[0] | |
159 self.add_widget(G.host.getOrClone(next_slide)) | |
160 if len(hidden)>1: | |
161 previous_slide = slides_sorted[current_idx-1] | |
162 self.add_widget(G.host.getOrClone(previous_slide)) | |
163 | |
164 if len(self.slides) == 1: | |
165 # we block carousel with high scroll_distance to avoid swiping | |
166 # when the is not other instance of the widget | |
167 self.scroll_distance=2**32 | |
168 self.scroll_timeout=0 | |
169 else: | |
170 self.scroll_distance = CAROUSEL_SCROLL_DISTANCE | |
171 self.scroll_timeout=CAROUSEL_SCROLL_TIMEOUT | |
172 self._slides_update_lock = False | |
173 | |
174 | |
175 class WidgetsHandler(BoxLayout): | |
176 | |
177 def __init__(self, wid=None, **kw): | |
178 if wid is None: | |
179 wid=self.default_widget | |
180 self.vert_wid = self.hor_wid = None | |
181 BoxLayout.__init__(self, orientation="vertical", **kw) | |
182 self.blh = BoxLayout(orientation="horizontal") | |
183 self.blv = BoxLayout(orientation="vertical") | |
184 self.blv.add_widget(WHSplitter(self)) | |
185 self.carousel = HandlerCarousel() | |
186 self.blv.add_widget(self.carousel) | |
187 self.blh.add_widget(WHSplitter(self, horizontal=False)) | |
188 self.blh.add_widget(self.blv) | |
189 self.add_widget(self.blh) | |
190 self.changeWidget(wid) | |
191 | |
192 @property | |
193 def default_widget(self): | |
194 return G.host.default_wid['factory'](G.host.default_wid, None, None) | |
195 | |
196 @property | |
197 def cagou_widget(self): | |
198 """get holded CagouWidget""" | |
199 return self.carousel.current_slide | |
200 | |
201 def changeWidget(self, new_widget): | |
202 self.carousel.changeWidget(new_widget) | |
203 | |
204 def removeWidget(self, vertical): | |
205 if vertical and self.vert_wid is not None: | |
206 self.remove_widget(self.vert_wid) | |
207 self.vert_wid.onDelete() | |
208 self.vert_wid = None | |
209 elif self.hor_wid is not None: | |
210 self.blh.remove_widget(self.hor_wid) | |
211 self.hor_wid.onDelete() | |
212 self.hor_wid = None | |
213 | |
214 def setWidgetSize(self, vertical, size): | |
215 if vertical: | |
216 if self.vert_wid is None: | |
217 self.vert_wid = WidgetsHandler(self.default_widget, size_hint=(1, None)) | |
218 self.add_widget(self.vert_wid, len(self.children)) | |
219 self.vert_wid.height=size | |
220 else: | |
221 if self.hor_wid is None: | |
222 self.hor_wid = WidgetsHandler(self.default_widget, size_hint=(None, 1)) | |
223 self.blh.add_widget(self.hor_wid, len(self.blh.children)) | |
224 self.hor_wid.width=size | |
225 | |
226 def onDelete(self): | |
227 # when this handler is deleted, we need to delete the holded CagouWidget | |
228 cagou_widget = self.cagou_widget | |
229 if isinstance(cagou_widget, quick_widgets.QuickWidget): | |
230 G.host.removeVisibleWidget(cagou_widget) |