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)