comparison cagou/plugins/plugin_wid_file_sharing.py @ 222:a676cb07c1cb

core (menu): TouchMenuBehaviour: moved code showing ModernMenu on item from file sharing plugin to a generic behaviour, so it can be re-used elsewhere.
author Goffi <goffi@goffi.org>
date Tue, 26 Jun 2018 20:26:21 +0200
parents 286f14127f61
children 059c5b39032d
comparison
equal deleted inserted replaced
221:e1a385a791cc 222:a676cb07c1cb
25 from sat.tools.common import files_utils 25 from sat.tools.common import files_utils
26 from sat_frontends.quick_frontend import quick_widgets 26 from sat_frontends.quick_frontend import quick_widgets
27 from sat_frontends.tools import jid 27 from sat_frontends.tools import jid
28 from cagou.core.constants import Const as C 28 from cagou.core.constants import Const as C
29 from cagou.core import cagou_widget 29 from cagou.core import cagou_widget
30 from cagou.core.menu import EntitiesSelectorMenu 30 from cagou.core.menu import (EntitiesSelectorMenu, TouchMenuBehaviour,
31 TouchMenuItemBehaviour)
31 from cagou.core.utils import FilterBehavior 32 from cagou.core.utils import FilterBehavior
32 from cagou import G 33 from cagou import G
33 from kivy import properties 34 from kivy import properties
34 from kivy.uix.label import Label 35 from kivy.uix.label import Label
35 from kivy.uix.button import Button 36 from kivy.uix.button import Button
36 from kivy.uix.boxlayout import BoxLayout 37 from kivy.uix.boxlayout import BoxLayout
37 from kivy.garden import modernmenu
38 from kivy.clock import Clock
39 from kivy.metrics import dp 38 from kivy.metrics import dp
40 from kivy import utils as kivy_utils 39 from kivy import utils as kivy_utils
41 from functools import partial 40 from functools import partial
42 import os.path 41 import os.path
43 import json 42 import json
104 @property 103 @property
105 def name(self): 104 def name(self):
106 return self.identities.values()[0].values()[0][0] 105 return self.identities.values()[0].values()[0][0]
107 106
108 107
109 class ItemWidget(BoxLayout): 108 class ItemWidget(TouchMenuItemBehaviour, BoxLayout):
110 click_timeout = properties.NumericProperty(0.4) 109 name = properties.StringProperty()
111 base_width = properties.NumericProperty(dp(100)) 110 base_width = properties.NumericProperty(dp(100))
112 111
113 def __init__(self, sharing_wid, name):
114 self.sharing_wid = sharing_wid
115 self.name = name
116 super(ItemWidget, self).__init__()
117
118 def on_touch_down(self, touch):
119 if not self.collide_point(*touch.pos):
120 return
121 t = partial(self.open_menu, touch)
122 touch.ud['menu_timeout'] = t
123 Clock.schedule_once(t, self.click_timeout)
124 return super(ItemWidget, self).on_touch_down(touch)
125
126 def do_item_action(self, touch):
127 pass
128
129 def on_touch_up(self, touch):
130 if touch.ud.get('menu_timeout'):
131 Clock.unschedule(touch.ud['menu_timeout'])
132 if self.collide_point(*touch.pos) and self.sharing_wid.menu is None:
133 self.do_item_action(touch)
134 return super(ItemWidget, self).on_touch_up(touch)
135
136 def open_menu(self, touch, dt):
137 self.sharing_wid.open_menu(self, touch)
138 del touch.ud['menu_timeout']
139
140 def getMenuChoices(self):
141 """return choice adapted to selected item
142
143 @return (list[dict]): choices ad expected by ModernMenu
144 """
145 return []
146
147 112
148 class PathWidget(ItemWidget): 113 class PathWidget(ItemWidget):
149 114
150 def __init__(self, sharing_wid, filepath): 115 def __init__(self, filepath, main_wid, **kw):
151 name = os.path.basename(filepath) 116 name = os.path.basename(filepath)
152 self.filepath = os.path.normpath(filepath) 117 self.filepath = os.path.normpath(filepath)
153 if self.filepath == u'.': 118 if self.filepath == u'.':
154 self.filepath = u'' 119 self.filepath = u''
155 super(PathWidget, self).__init__(sharing_wid, name) 120 super(PathWidget, self).__init__(name=name, main_wid=main_wid, **kw)
156 121
157 @property 122 @property
158 def is_dir(self): 123 def is_dir(self):
159 raise NotImplementedError 124 raise NotImplementedError
160 125
161 def do_item_action(self, touch): 126 def do_item_action(self, touch):
162 if self.is_dir: 127 if self.is_dir:
163 self.sharing_wid.current_dir = self.filepath 128 self.main_wid.current_dir = self.filepath
164 129
165 def open_menu(self, touch, dt): 130 def open_menu(self, touch, dt):
166 log.debug(_(u"opening menu for {path}").format(path=self.filepath)) 131 log.debug(_(u"opening menu for {path}").format(path=self.filepath))
167 super(PathWidget, self).open_menu(touch, dt) 132 super(PathWidget, self).open_menu(touch, dt)
168 133
176 def getMenuChoices(self): 141 def getMenuChoices(self):
177 choices = [] 142 choices = []
178 if self.shared: 143 if self.shared:
179 choices.append(dict(text=_(u'unshare'), 144 choices.append(dict(text=_(u'unshare'),
180 index=len(choices)+1, 145 index=len(choices)+1,
181 callback=self.sharing_wid.unshare)) 146 callback=self.main_wid.unshare))
182 else: 147 else:
183 choices.append(dict(text=_(u'share'), 148 choices.append(dict(text=_(u'share'),
184 index=len(choices)+1, 149 index=len(choices)+1,
185 callback=self.sharing_wid.share)) 150 callback=self.main_wid.share))
186 return choices 151 return choices
187 152
188 153
189 class RemotePathWidget(PathWidget): 154 class RemotePathWidget(PathWidget):
190 155
191 def __init__(self, sharing_wid, filepath, type_): 156 def __init__(self, main_wid, filepath, type_, **kw):
192 self.type_ = type_ 157 self.type_ = type_
193 super(RemotePathWidget, self).__init__(sharing_wid, filepath) 158 super(RemotePathWidget, self).__init__(filepath, main_wid=main_wid, **kw)
194 159
195 @property 160 @property
196 def is_dir(self): 161 def is_dir(self):
197 return self.type_ == C.FILE_TYPE_DIRECTORY 162 return self.type_ == C.FILE_TYPE_DIRECTORY
198 163
199 def do_item_action(self, touch): 164 def do_item_action(self, touch):
200 if self.is_dir: 165 if self.is_dir:
201 if self.filepath == u'..': 166 if self.filepath == u'..':
202 self.sharing_wid.remote_entity = u'' 167 self.main_wid.remote_entity = u''
203 else: 168 else:
204 super(RemotePathWidget, self).do_item_action(touch) 169 super(RemotePathWidget, self).do_item_action(touch)
205 else: 170 else:
206 self.sharing_wid.request_item(self) 171 self.main_wid.request_item(self)
207 return True 172 return True
208 173
209 174
210 class DeviceWidget(ItemWidget): 175 class DeviceWidget(ItemWidget):
211 176
212 def __init__(self, sharing_wid, entity_jid, identities): 177 def __init__(self, main_wid, entity_jid, identities, **kw):
213 self.entity_jid = entity_jid 178 self.entity_jid = entity_jid
214 self.identities = identities 179 self.identities = identities
215 own_jid = next(G.host.profiles.itervalues()).whoami 180 own_jid = next(G.host.profiles.itervalues()).whoami
216 self.own_device = entity_jid.bare == own_jid 181 self.own_device = entity_jid.bare == own_jid
217 if self.own_device: 182 if self.own_device:
221 elif self.entity_jid.domain.endswith(own_jid.domain): 186 elif self.entity_jid.domain.endswith(own_jid.domain):
222 name = _(u"your server") 187 name = _(u"your server")
223 else: 188 else:
224 name = _(u"sharing component") 189 name = _(u"sharing component")
225 190
226 super(DeviceWidget, self).__init__(sharing_wid, name) 191 super(DeviceWidget, self).__init__(name=name, main_wid=main_wid, **kw)
227 192
228 def getSymbol(self): 193 def getSymbol(self):
229 if self.identities.type == 'desktop': 194 if self.identities.type == 'desktop':
230 return 'desktop' 195 return 'desktop'
231 elif self.identities.type == 'phone': 196 elif self.identities.type == 'phone':
236 return 'terminal' 201 return 'terminal'
237 else: 202 else:
238 return 'desktop' 203 return 'desktop'
239 204
240 def do_item_action(self, touch): 205 def do_item_action(self, touch):
241 self.sharing_wid.remote_entity = self.entity_jid 206 self.main_wid.remote_entity = self.entity_jid
242 self.sharing_wid.remote_dir = u'' 207 self.main_wid.remote_dir = u''
243 208
244 209
245 class CategorySeparator(Label): 210 class CategorySeparator(Label):
246 pass 211 pass
247 212
248 213
249 class Menu(modernmenu.ModernMenu): 214 class FileSharing(quick_widgets.QuickWidget, cagou_widget.CagouWidget, FilterBehavior,
250 pass 215 TouchMenuBehaviour):
251
252
253 class FileSharing(quick_widgets.QuickWidget, cagou_widget.CagouWidget, FilterBehavior):
254 SINGLE=False 216 SINGLE=False
255 float_layout = properties.ObjectProperty()
256 layout = properties.ObjectProperty() 217 layout = properties.ObjectProperty()
257 mode = properties.OptionProperty(MODE_LOCAL, options=[MODE_VIEW, MODE_LOCAL]) 218 mode = properties.OptionProperty(MODE_LOCAL, options=[MODE_VIEW, MODE_LOCAL])
258 local_dir = properties.StringProperty(expanduser(u'~')) 219 local_dir = properties.StringProperty(expanduser(u'~'))
259 remote_dir = properties.StringProperty(u'') 220 remote_dir = properties.StringProperty(u'')
260 remote_entity = properties.StringProperty(u'') 221 remote_entity = properties.StringProperty(u'')
263 224
264 def __init__(self, host, target, profiles): 225 def __init__(self, host, target, profiles):
265 quick_widgets.QuickWidget.__init__(self, host, target, profiles) 226 quick_widgets.QuickWidget.__init__(self, host, target, profiles)
266 cagou_widget.CagouWidget.__init__(self) 227 cagou_widget.CagouWidget.__init__(self)
267 FilterBehavior.__init__(self) 228 FilterBehavior.__init__(self)
229 TouchMenuBehaviour.__init__(self)
268 self.mode_btn = ModeBtn(self) 230 self.mode_btn = ModeBtn(self)
269 self.mode_btn.bind(on_release=self.change_mode) 231 self.mode_btn.bind(on_release=self.change_mode)
270 self.headerInputAddExtra(self.mode_btn) 232 self.headerInputAddExtra(self.mode_btn)
271 self.bind(local_dir=self.update_view, 233 self.bind(local_dir=self.update_view,
272 remote_dir=self.update_view, 234 remote_dir=self.update_view,
273 remote_entity=self.update_view) 235 remote_entity=self.update_view)
274 self.update_view() 236 self.update_view()
275 self.menu = None
276 self.menu_item = None
277 self.float_layout.bind(children=self.clean_fl_children)
278 if not FileSharing.signals_registered: 237 if not FileSharing.signals_registered:
279 # FIXME: we use this hack (registering the signal for the whole class) now 238 # FIXME: we use this hack (registering the signal for the whole class) now
280 # as there is currently no unregisterSignal available in bridges 239 # as there is currently no unregisterSignal available in bridges
281 G.host.registerSignal("FISSharedPathNew", 240 G.host.registerSignal("FISSharedPathNew",
282 handler=FileSharing.shared_path_new, 241 handler=FileSharing.shared_path_new,
373 332
374 def FISListCb(self, files_data): 333 def FISListCb(self, files_data):
375 for file_data in files_data: 334 for file_data in files_data:
376 filepath = os.path.join(self.current_dir, file_data[u'name']) 335 filepath = os.path.join(self.current_dir, file_data[u'name'])
377 item = RemotePathWidget( 336 item = RemotePathWidget(
378 self,
379 filepath=filepath, 337 filepath=filepath,
338 main_wid=self,
380 type_=file_data[u'type']) 339 type_=file_data[u'type'])
381 self.layout.add_widget(item) 340 self.layout.add_widget(item)
382 341
383 def FISListEb(self, failure_): 342 def FISListEb(self, failure_):
384 self.remote_dir = u'' 343 self.remote_dir = u''
398 self.header_input.text = u'' 357 self.header_input.text = u''
399 self.header_input.hint_text = self.current_dir 358 self.header_input.hint_text = self.current_dir
400 359
401 if self.mode == MODE_LOCAL: 360 if self.mode == MODE_LOCAL:
402 filepath = os.path.join(self.local_dir, u'..') 361 filepath = os.path.join(self.local_dir, u'..')
403 self.layout.add_widget(LocalPathWidget(sharing_wid=self, filepath=filepath)) 362 self.layout.add_widget(LocalPathWidget(filepath=filepath, main_wid=self))
404 try: 363 try:
405 files = sorted(os.listdir(self.local_dir)) 364 files = sorted(os.listdir(self.local_dir))
406 except OSError as e: 365 except OSError as e:
407 msg = _(u"can't list files in \"{local_dir}\": {msg}").format( 366 msg = _(u"can't list files in \"{local_dir}\": {msg}").format(
408 local_dir=self.local_dir, 367 local_dir=self.local_dir,
413 level=C.XMLUI_DATA_LVL_WARNING) 372 level=C.XMLUI_DATA_LVL_WARNING)
414 self.local_dir = expanduser(u'~') 373 self.local_dir = expanduser(u'~')
415 return 374 return
416 for f in files: 375 for f in files:
417 filepath = os.path.join(self.local_dir, f) 376 filepath = os.path.join(self.local_dir, f)
418 self.layout.add_widget(LocalPathWidget(sharing_wid=self, 377 self.layout.add_widget(LocalPathWidget(filepath=filepath,
419 filepath=filepath)) 378 main_wid=self))
420 elif self.mode == MODE_VIEW: 379 elif self.mode == MODE_VIEW:
421 if not self.remote_entity: 380 if not self.remote_entity:
422 self.discover_devices() 381 self.discover_devices()
423 else: 382 else:
424 # we always a way to go back 383 # we always a way to go back
425 # so user can return to previous list even in case of error 384 # so user can return to previous list even in case of error
426 parent_path = os.path.join(self.remote_dir, u'..') 385 parent_path = os.path.join(self.remote_dir, u'..')
427 item = RemotePathWidget( 386 item = RemotePathWidget(
428 self,
429 filepath = parent_path, 387 filepath = parent_path,
388 main_wid=self,
430 type_ = C.FILE_TYPE_DIRECTORY) 389 type_ = C.FILE_TYPE_DIRECTORY)
431 self.layout.add_widget(item) 390 self.layout.add_widget(item)
432 self.host.bridge.FISList( 391 self.host.bridge.FISList(
433 unicode(self.remote_entity), 392 unicode(self.remote_entity),
434 self.remote_dir, 393 self.remote_dir,
435 {}, 394 {},
436 self.profile, 395 self.profile,
437 callback=self.FISListCb, 396 callback=self.FISListCb,
438 errback=self.FISListEb) 397 errback=self.FISListEb)
439
440 ## menu methods ##
441
442 def clean_fl_children(self, layout, children):
443 """insure that self.menu and self.menu_item are None when menu is dimissed"""
444 if self.menu is not None and self.menu not in children:
445 self.menu = self.menu_item = None
446
447 def clear_menu(self):
448 """remove menu if there is one"""
449 if self.menu is not None:
450 self.menu.dismiss()
451 self.menu = None
452 self.menu_item = None
453
454 def open_menu(self, item, touch):
455 """open menu for item
456
457 @param item(PathWidget): item when the menu has been requested
458 @param touch(kivy.input.MotionEvent): touch data
459 """
460 if self.menu_item == item:
461 return
462 self.clear_menu()
463 pos = self.to_widget(*touch.pos)
464 choices = item.getMenuChoices()
465 if not choices:
466 return
467 self.menu = Menu(choices=choices,
468 center=pos,
469 size_hint=(None, None))
470 self.float_layout.add_widget(self.menu)
471 self.menu.start_display(touch)
472 self.menu_item = item
473 398
474 ## Share methods ## 399 ## Share methods ##
475 400
476 def do_share(self, entities_jids, item): 401 def do_share(self, entities_jids, item):
477 if entities_jids: 402 if entities_jids: