Mercurial > libervia-desktop-kivy
comparison cagou/core/cagou_main.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/cagou_main.py@dcd6fbb3f010 |
children | 0ec3c3c0ed92 |
comparison
equal
deleted
inserted
replaced
125:b6e6afb0dc46 | 126:cd99f70ea592 |
---|---|
1 #!/usr//bin/env python2 | |
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.i18n import _ | |
22 from . import kivy_hack | |
23 kivy_hack.do_hack() | |
24 from constants import Const as C | |
25 from sat.core import log as logging | |
26 log = logging.getLogger(__name__) | |
27 from sat.core import exceptions | |
28 from sat_frontends.quick_frontend.quick_app import QuickApp | |
29 from sat_frontends.quick_frontend import quick_widgets | |
30 from sat_frontends.quick_frontend import quick_chat | |
31 from sat_frontends.quick_frontend import quick_utils | |
32 from sat.tools import config | |
33 from sat.tools.common import dynamic_import | |
34 import kivy | |
35 kivy.require('1.9.1') | |
36 import kivy.support | |
37 main_config = config.parseMainConf() | |
38 bridge_name = config.getConfig(main_config, '', 'bridge', 'dbus') | |
39 # FIXME: event loop is choosen according to bridge_name, a better way should be used | |
40 if 'dbus' in bridge_name: | |
41 kivy.support.install_gobject_iteration() | |
42 elif bridge_name in ('pb', 'embedded'): | |
43 kivy.support.install_twisted_reactor() | |
44 from kivy.app import App | |
45 from kivy.lang import Builder | |
46 from kivy import properties | |
47 import xmlui | |
48 from profile_manager import ProfileManager | |
49 from widgets_handler import WidgetsHandler | |
50 from kivy.clock import Clock | |
51 from kivy.uix.label import Label | |
52 from kivy.uix.boxlayout import BoxLayout | |
53 from kivy.uix.floatlayout import FloatLayout | |
54 from kivy.uix.screenmanager import ScreenManager, Screen, FallOutTransition, RiseInTransition | |
55 from kivy.uix.dropdown import DropDown | |
56 from cagou_widget import CagouWidget | |
57 from . import widgets_handler | |
58 from .common import IconButton | |
59 from . import menu | |
60 from importlib import import_module | |
61 import os.path | |
62 import glob | |
63 import cagou.plugins | |
64 import cagou.kv | |
65 from kivy import utils as kivy_utils | |
66 import sys | |
67 if kivy_utils.platform == "android": | |
68 # FIXME: move to separate android module | |
69 kivy.support.install_android() | |
70 # sys.platform is "linux" on android by default | |
71 # so we change it to allow backend to detect android | |
72 sys.platform = "android" | |
73 import mmap | |
74 C.PLUGIN_EXT = 'pyo' | |
75 | |
76 | |
77 class NotifsIcon(IconButton): | |
78 notifs = properties.ListProperty() | |
79 | |
80 def on_release(self): | |
81 callback, args, kwargs = self.notifs.pop(0) | |
82 callback(*args, **kwargs) | |
83 | |
84 def addNotif(self, callback, *args, **kwargs): | |
85 self.notifs.append((callback, args, kwargs)) | |
86 | |
87 | |
88 class Note(Label): | |
89 title = properties.StringProperty() | |
90 message = properties.StringProperty() | |
91 level = properties.OptionProperty(C.XMLUI_DATA_LVL_DEFAULT, options=list(C.XMLUI_DATA_LVLS)) | |
92 | |
93 | |
94 class NoteDrop(Note): | |
95 pass | |
96 | |
97 | |
98 class NotesDrop(DropDown): | |
99 clear_btn = properties.ObjectProperty() | |
100 | |
101 def __init__(self, notes): | |
102 super(NotesDrop, self).__init__() | |
103 self.notes = notes | |
104 | |
105 def open(self, widget): | |
106 self.clear_widgets() | |
107 for n in self.notes: | |
108 self.add_widget(NoteDrop(title=n.title, message=n.message, level=n.level)) | |
109 self.add_widget(self.clear_btn) | |
110 super(NotesDrop, self).open(widget) | |
111 | |
112 | |
113 class RootHeadWidget(BoxLayout): | |
114 """Notifications widget""" | |
115 manager = properties.ObjectProperty() | |
116 notifs_icon = properties.ObjectProperty() | |
117 notes = properties.ListProperty() | |
118 | |
119 def __init__(self): | |
120 super(RootHeadWidget, self).__init__() | |
121 self.notes_last = None | |
122 self.notes_event = None | |
123 self.notes_drop = NotesDrop(self.notes) | |
124 | |
125 def addNotif(self, callback, *args, **kwargs): | |
126 self.notifs_icon.addNotif(callback, *args, **kwargs) | |
127 | |
128 def addNote(self, title, message, level): | |
129 note = Note(title=title, message=message, level=level) | |
130 self.notes.append(note) | |
131 if len(self.notes) > 10: | |
132 del self.notes[:-10] | |
133 if self.notes_event is None: | |
134 self.notes_event = Clock.schedule_interval(self._displayNextNote, 5) | |
135 self._displayNextNote() | |
136 | |
137 def addNotifUI(self, ui): | |
138 self.notifs_icon.addNotif(ui.show, force=True) | |
139 | |
140 def _displayNextNote(self, dummy=None): | |
141 screen = Screen() | |
142 try: | |
143 idx = self.notes.index(self.notes_last) + 1 | |
144 except ValueError: | |
145 idx = 0 | |
146 try: | |
147 note = self.notes_last = self.notes[idx] | |
148 except IndexError: | |
149 self.notes_event.cancel() | |
150 self.notes_event = None | |
151 else: | |
152 screen.add_widget(note) | |
153 self.manager.switch_to(screen) | |
154 | |
155 | |
156 class RootMenus(menu.MenusWidget): | |
157 pass | |
158 | |
159 | |
160 class RootBody(BoxLayout): | |
161 pass | |
162 | |
163 | |
164 class CagouRootWidget(FloatLayout): | |
165 root_menus = properties.ObjectProperty() | |
166 root_body = properties.ObjectProperty | |
167 | |
168 def __init__(self, main_widget): | |
169 super(CagouRootWidget, self).__init__() | |
170 # header | |
171 self._head_widget = RootHeadWidget() | |
172 self.root_body.add_widget(self._head_widget) | |
173 # body | |
174 self._manager = ScreenManager() | |
175 # main widgets | |
176 main_screen = Screen(name='main') | |
177 main_screen.add_widget(main_widget) | |
178 self._manager.add_widget(main_screen) | |
179 # backend XMLUI (popups, forms, etc) | |
180 xmlui_screen = Screen(name='xmlui') | |
181 self._manager.add_widget(xmlui_screen) | |
182 # extra (file chooser, audio record, etc) | |
183 extra_screen = Screen(name='extra') | |
184 self._manager.add_widget(extra_screen) | |
185 self.root_body.add_widget(self._manager) | |
186 | |
187 def changeWidget(self, widget, screen_name="main"): | |
188 """change main widget""" | |
189 if self._manager.transition.is_active: | |
190 # FIXME: workaround for what seems a Kivy bug | |
191 # TODO: report this upstream | |
192 self._manager.transition.stop() | |
193 screen = self._manager.get_screen(screen_name) | |
194 screen.clear_widgets() | |
195 screen.add_widget(widget) | |
196 | |
197 def show(self, screen="main"): | |
198 if self._manager.transition.is_active: | |
199 # FIXME: workaround for what seems a Kivy bug | |
200 # TODO: report this upstream | |
201 self._manager.transition.stop() | |
202 if self._manager.current == screen: | |
203 return | |
204 if screen == "main": | |
205 self._manager.transition = FallOutTransition() | |
206 else: | |
207 self._manager.transition = RiseInTransition() | |
208 self._manager.current = screen | |
209 | |
210 def newAction(self, handler, action_data, id_, security_limit, profile): | |
211 """Add a notification for an action""" | |
212 self._head_widget.addNotif(handler, action_data, id_, security_limit, profile) | |
213 | |
214 def addNote(self, title, message, level): | |
215 self._head_widget.addNote(title, message, level) | |
216 | |
217 def addNotifUI(self, ui): | |
218 self._head_widget.addNotifUI(ui) | |
219 | |
220 | |
221 class CagouApp(App): | |
222 """Kivy App for Cagou""" | |
223 | |
224 def build(self): | |
225 return CagouRootWidget(Label(text=u"Loading please wait")) | |
226 | |
227 def showWidget(self): | |
228 self._profile_manager = ProfileManager() | |
229 self.root.changeWidget(self._profile_manager) | |
230 | |
231 def expand(self, path, *args, **kwargs): | |
232 """expand path and replace known values | |
233 | |
234 useful in kv. Values which can be used: | |
235 - {media}: media dir | |
236 @param path(unicode): path to expand | |
237 @param *args: additional arguments used in format | |
238 @param **kwargs: additional keyword arguments used in format | |
239 """ | |
240 return os.path.expanduser(path).format(*args, media=self.host.media_dir, **kwargs) | |
241 | |
242 def on_start(self): | |
243 if sys.platform == "android": | |
244 # XXX: we use memory map instead of bridge because if we try to call a bridge method | |
245 # in on_pause method, the call data is not written before the actual pause | |
246 # we create a memory map on .cagou_status file with a 1 byte status | |
247 # status is: | |
248 # R => running | |
249 # P => paused | |
250 # S => stopped | |
251 self._first_pause = True | |
252 self.cagou_status_fd = open('.cagou_status', 'wb+') | |
253 self.cagou_status_fd.write('R') | |
254 self.cagou_status_fd.flush() | |
255 self.cagou_status = mmap.mmap(self.cagou_status_fd.fileno(), 1, prot=mmap.PROT_WRITE) | |
256 | |
257 def on_pause(self): | |
258 self.cagou_status[0] = 'P' | |
259 return True | |
260 | |
261 def on_resume(self): | |
262 self.cagou_status[0] = 'R' | |
263 | |
264 def on_stop(self): | |
265 if sys.platform == "android": | |
266 self.cagou_status[0] = 'S' | |
267 self.cagou_status.flush() | |
268 self.cagou_status_fd.close() | |
269 | |
270 | |
271 class Cagou(QuickApp): | |
272 MB_HANDLE = False | |
273 | |
274 def __init__(self): | |
275 if bridge_name == 'embedded': | |
276 from sat.core import sat_main | |
277 self.sat = sat_main.SAT() | |
278 if sys.platform == 'android': | |
279 from android import AndroidService | |
280 service = AndroidService(u'Cagou (SàT)'.encode('utf-8'), u'Salut à Toi backend'.encode('utf-8')) | |
281 service.start(u'service started') | |
282 self.service = service | |
283 | |
284 bridge_module = dynamic_import.bridge(bridge_name, 'sat_frontends.bridge') | |
285 if bridge_module is None: | |
286 log.error(u"Can't import {} bridge".format(bridge_name)) | |
287 sys.exit(3) | |
288 else: | |
289 log.debug(u"Loading {} bridge".format(bridge_name)) | |
290 super(Cagou, self).__init__(bridge_factory=bridge_module.Bridge, xmlui=xmlui, check_options=quick_utils.check_options, connect_bridge=False) | |
291 self._import_kv() | |
292 self.app = CagouApp() | |
293 self.app.host = self | |
294 self.media_dir = self.app.media_dir = config.getConfig(main_config, '', 'media_dir') | |
295 self.app.default_avatar = os.path.join(self.media_dir, "misc/default_avatar.png") | |
296 self.app.icon = os.path.join(self.media_dir, "icons/muchoslava/png/cagou_profil_bleu_96.png") | |
297 self._plg_wids = [] # main widgets plugins | |
298 self._plg_wids_transfer = [] # transfer widgets plugins | |
299 self._import_plugins() | |
300 self._visible_widgets = {} # visible widgets by classes | |
301 | |
302 @property | |
303 def visible_widgets(self): | |
304 for w_list in self._visible_widgets.itervalues(): | |
305 for w in w_list: | |
306 yield w | |
307 | |
308 def onBridgeConnected(self): | |
309 self.registerSignal("otrState", iface="plugin") | |
310 self.bridge.getReady(self.onBackendReady) | |
311 | |
312 def _bridgeEb(self, failure): | |
313 if bridge_name == "pb" and sys.platform == "android": | |
314 try: | |
315 self.retried += 1 | |
316 except AttributeError: | |
317 self.retried = 1 | |
318 from twisted.internet.error import ConnectionRefusedError | |
319 if failure.check(ConnectionRefusedError) and self.retried < 100: | |
320 if self.retried % 20 == 0: | |
321 log.debug("backend not ready, retrying ({})".format(self.retried)) | |
322 Clock.schedule_once(lambda dummy: self.connectBridge(), 0.05) | |
323 return | |
324 super(Cagou, self)._bridgeEb(failure) | |
325 | |
326 def run(self): | |
327 self.connectBridge() | |
328 self.app.bind(on_stop=self.onStop) | |
329 self.app.run() | |
330 | |
331 def onStop(self, obj): | |
332 try: | |
333 sat_instance = self.sat | |
334 except AttributeError: | |
335 pass | |
336 else: | |
337 sat_instance.stopService() | |
338 | |
339 def onBackendReady(self): | |
340 self.app.showWidget() | |
341 self.postInit() | |
342 | |
343 def postInit(self, dummy=None): | |
344 # FIXME: resize seem to bug on android, so we use below_target for now | |
345 self.app.root_window.softinput_mode = "below_target" | |
346 profile_manager = self.app._profile_manager | |
347 del self.app._profile_manager | |
348 super(Cagou, self).postInit(profile_manager) | |
349 | |
350 def _defaultFactoryMain(self, plugin_info, target, profiles): | |
351 """default factory used to create main widgets instances | |
352 | |
353 used when PLUGIN_INFO["factory"] is not set | |
354 @param plugin_info(dict): plugin datas | |
355 @param target: QuickWidget target | |
356 @param profiles(iterable): list of profiles | |
357 """ | |
358 main_cls = plugin_info['main'] | |
359 return self.widgets.getOrCreateWidget(main_cls, target, on_new_widget=None, profiles=iter(self.profiles)) | |
360 | |
361 def _defaultFactoryTransfer(self, plugin_info, callback, cancel_cb, profiles): | |
362 """default factory used to create transfer widgets instances | |
363 | |
364 @param plugin_info(dict): plugin datas | |
365 @param callback(callable): method to call with path to file to transfer | |
366 @param cancel_cb(callable): call when transfer is cancelled | |
367 transfer widget must be used as first argument | |
368 @param profiles(iterable): list of profiles | |
369 None if not specified | |
370 """ | |
371 main_cls = plugin_info['main'] | |
372 return main_cls(callback=callback, cancel_cb=cancel_cb) | |
373 | |
374 ## plugins & kv import ## | |
375 | |
376 def _import_kv(self): | |
377 """import all kv files in cagou.kv""" | |
378 path = os.path.dirname(cagou.kv.__file__) | |
379 for kv_path in glob.glob(os.path.join(path, "*.kv")): | |
380 Builder.load_file(kv_path) | |
381 log.debug(u"kv file {} loaded".format(kv_path)) | |
382 | |
383 def _import_plugins(self): | |
384 """import all plugins""" | |
385 self.default_wid = None | |
386 plugins_path = os.path.dirname(cagou.plugins.__file__) | |
387 plugin_glob = u"plugin*." + C.PLUGIN_EXT | |
388 plug_lst = [os.path.splitext(p)[0] for p in map(os.path.basename, glob.glob(os.path.join(plugins_path, plugin_glob)))] | |
389 | |
390 imported_names_main = set() # used to avoid loading 2 times plugin with same import name | |
391 imported_names_transfer = set() | |
392 for plug in plug_lst: | |
393 plugin_path = 'cagou.plugins.' + plug | |
394 | |
395 # we get type from plugin name | |
396 suff = plug[7:] | |
397 if u'_' not in suff: | |
398 log.error(u"invalid plugin name: {}, skipping".format(plug)) | |
399 continue | |
400 plugin_type = suff[:suff.find(u'_')] | |
401 | |
402 # and select the variable to use according to type | |
403 if plugin_type == C.PLUG_TYPE_WID: | |
404 imported_names = imported_names_main | |
405 default_factory = self._defaultFactoryMain | |
406 elif plugin_type == C.PLUG_TYPE_TRANSFER: | |
407 imported_names = imported_names_transfer | |
408 default_factory = self._defaultFactoryTransfer | |
409 else: | |
410 log.error(u"unknown plugin type {type_} for plugin {file_}, skipping".format( | |
411 type_ = plugin_type, | |
412 file_ = plug | |
413 )) | |
414 continue | |
415 plugins_set = self._getPluginsSet(plugin_type) | |
416 | |
417 mod = import_module(plugin_path) | |
418 try: | |
419 plugin_info = mod.PLUGIN_INFO | |
420 except AttributeError: | |
421 plugin_info = {} | |
422 | |
423 plugin_info['plugin_file'] = plug | |
424 plugin_info['plugin_type'] = plugin_type | |
425 | |
426 if 'platforms' in plugin_info: | |
427 if sys.platform not in plugin_info['platforms']: | |
428 log.info(u"{plugin_file} is not used on this platform, skipping".format(**plugin_info)) | |
429 continue | |
430 | |
431 # import name is used to differentiate plugins | |
432 if 'import_name' not in plugin_info: | |
433 plugin_info['import_name'] = plug | |
434 if plugin_info['import_name'] in imported_names: | |
435 log.warning(_(u"there is already a plugin named {}, ignoring new one").format(plugin_info['import_name'])) | |
436 continue | |
437 if plugin_info['import_name'] == C.WID_SELECTOR: | |
438 if plugin_type != C.PLUG_TYPE_WID: | |
439 log.error(u"{import_name} import name can only be used with {type_} type, skipping {name}".format(type_=C.PLUG_TYPE_WID, **plugin_info)) | |
440 continue | |
441 # if WidgetSelector exists, it will be our default widget | |
442 self.default_wid = plugin_info | |
443 | |
444 # we want everything optional, so we use plugin file name | |
445 # if actual name is not found | |
446 if 'name' not in plugin_info: | |
447 name_start = 8 + len(plugin_type) | |
448 plugin_info['name'] = plug[name_start:] | |
449 | |
450 # we need to load the kv file | |
451 if 'kv_file' not in plugin_info: | |
452 plugin_info['kv_file'] = u'{}.kv'.format(plug) | |
453 kv_path = os.path.join(plugins_path, plugin_info['kv_file']) | |
454 if not os.path.exists(kv_path): | |
455 log.debug(u"no kv found for {plugin_file}".format(**plugin_info)) | |
456 else: | |
457 Builder.load_file(kv_path) | |
458 | |
459 # what is the main class ? | |
460 main_cls = getattr(mod, plugin_info['main']) | |
461 plugin_info['main'] = main_cls | |
462 | |
463 # factory is used to create the instance | |
464 # if not found, we use a defaut one with getOrCreateWidget | |
465 if 'factory' not in plugin_info: | |
466 plugin_info['factory'] = default_factory | |
467 | |
468 # icons | |
469 for size in ('small', 'medium'): | |
470 key = u'icon_{}'.format(size) | |
471 try: | |
472 path = plugin_info[key] | |
473 except KeyError: | |
474 path = C.DEFAULT_WIDGET_ICON.format(media=self.media_dir) | |
475 else: | |
476 path = path.format(media=self.media_dir) | |
477 if not os.path.isfile(path): | |
478 path = C.DEFAULT_WIDGET_ICON.format(media=self.media_dir) | |
479 plugin_info[key] = path | |
480 | |
481 plugins_set.append(plugin_info) | |
482 if not self._plg_wids: | |
483 log.error(_(u"no widget plugin found")) | |
484 return | |
485 | |
486 # we want widgets sorted by names | |
487 self._plg_wids.sort(key=lambda p: p['name'].lower()) | |
488 self._plg_wids_transfer.sort(key=lambda p: p['name'].lower()) | |
489 | |
490 if self.default_wid is None: | |
491 # we have no selector widget, we use the first widget as default | |
492 self.default_wid = self._plg_wids[0] | |
493 | |
494 def _getPluginsSet(self, type_): | |
495 if type_ == C.PLUG_TYPE_WID: | |
496 return self._plg_wids | |
497 elif type_ == C.PLUG_TYPE_TRANSFER: | |
498 return self._plg_wids_transfer | |
499 else: | |
500 raise KeyError(u"{} plugin type is unknown".format(type_)) | |
501 | |
502 def getPluggedWidgets(self, type_=C.PLUG_TYPE_WID, except_cls=None): | |
503 """get available widgets plugin infos | |
504 | |
505 @param type_(unicode): type of widgets to get | |
506 one of C.PLUG_TYPE_* constant | |
507 @param except_cls(None, class): if not None, | |
508 widgets from this class will be excluded | |
509 @return (iter[dict]): available widgets plugin infos | |
510 """ | |
511 plugins_set = self._getPluginsSet(type_) | |
512 for plugin_data in plugins_set: | |
513 if plugin_data['main'] == except_cls: | |
514 continue | |
515 yield plugin_data | |
516 | |
517 def getPluginInfo(self, type_=C.PLUG_TYPE_WID, **kwargs): | |
518 """get first plugin info corresponding to filters | |
519 | |
520 @param type_(unicode): type of widgets to get | |
521 one of C.PLUG_TYPE_* constant | |
522 @param **kwargs: filter(s) to use, each key present here must also | |
523 exist and be of the same value in requested plugin info | |
524 @return (dict, None): found plugin info or None | |
525 """ | |
526 plugins_set = self._getPluginsSet(type_) | |
527 for plugin_info in plugins_set: | |
528 for k, w in kwargs.iteritems(): | |
529 try: | |
530 if plugin_info[k] != w: | |
531 continue | |
532 except KeyError: | |
533 continue | |
534 return plugin_info | |
535 | |
536 ## widgets handling | |
537 | |
538 def newWidget(self, widget): | |
539 log.debug(u"new widget created: {}".format(widget)) | |
540 if isinstance(widget, quick_chat.QuickChat) and widget.type == C.CHAT_GROUP: | |
541 self.addNote(u"", _(u"room {} has been joined").format(widget.target)) | |
542 | |
543 def getParentHandler(self, widget): | |
544 """Return handler holding this widget | |
545 | |
546 @return (WidgetsHandler): handler | |
547 """ | |
548 w_handler = widget.parent | |
549 while w_handler and not(isinstance(w_handler, widgets_handler.WidgetsHandler)): | |
550 w_handler = w_handler.parent | |
551 return w_handler | |
552 | |
553 def switchWidget(self, old, new): | |
554 """Replace old widget by new one | |
555 | |
556 old(CagouWidget): CagouWidget instance or a child | |
557 new(CagouWidget): new widget instance | |
558 """ | |
559 to_change = None | |
560 if isinstance(old, CagouWidget): | |
561 to_change = old | |
562 else: | |
563 for w in old.walk_reverse(): | |
564 if isinstance(w, CagouWidget): | |
565 to_change = w | |
566 break | |
567 | |
568 if to_change is None: | |
569 raise exceptions.InternalError(u"no CagouWidget found when trying to switch widget") | |
570 handler = self.getParentHandler(to_change) | |
571 handler.changeWidget(new) | |
572 | |
573 def addVisibleWidget(self, widget): | |
574 """declare a widget visible | |
575 | |
576 for internal use only! | |
577 """ | |
578 assert isinstance(widget, quick_widgets.QuickWidget) | |
579 self._visible_widgets.setdefault(widget.__class__, []).append(widget) | |
580 | |
581 def removeVisibleWidget(self, widget): | |
582 """declare a widget not visible anymore | |
583 | |
584 for internal use only! | |
585 """ | |
586 self._visible_widgets[widget.__class__].remove(widget) | |
587 self.widgets.deleteWidget(widget) | |
588 | |
589 def getVisibleList(self, cls): | |
590 """get list of visible widgets for a given class | |
591 | |
592 @param cls(QuickWidget class): type of widgets to get | |
593 @return (list[QuickWidget class]): visible widgets of this class | |
594 """ | |
595 try: | |
596 return self._visible_widgets[cls] | |
597 except KeyError: | |
598 return [] | |
599 | |
600 def getOrClone(self, widget): | |
601 """Get a QuickWidget if it has not parent set else clone it""" | |
602 if widget.parent is None: | |
603 return widget | |
604 targets = list(widget.targets) | |
605 w = self.widgets.getOrCreateWidget(widget.__class__, targets[0], on_new_widget=None, on_existing_widget=C.WIDGET_RECREATE, profiles=widget.profiles) | |
606 for t in targets[1:]: | |
607 w.addTarget(t) | |
608 return w | |
609 | |
610 ## menus ## | |
611 | |
612 def _menusGetCb(self, backend_menus): | |
613 main_menu = self.app.root.root_menus | |
614 self.menus.addMenus(backend_menus) | |
615 self.menus.addMenu(C.MENU_GLOBAL, (_(u"Help"), _(u"About")), callback=main_menu.onAbout) | |
616 main_menu.update(C.MENU_GLOBAL) | |
617 | |
618 ## bridge handlers ## | |
619 | |
620 def otrStateHandler(self, state, dest_jid, profile): | |
621 """OTR state has changed for on destinee""" | |
622 # XXX: this method could be in QuickApp but it's here as | |
623 # it's only used by Cagou so far | |
624 for widget in self.widgets.getWidgets(quick_chat.QuickChat, profiles=(profile,)): | |
625 widget.onOTRState(state, dest_jid, profile) | |
626 | |
627 ## misc ## | |
628 | |
629 def plugging_profiles(self): | |
630 self.app.root.changeWidget(WidgetsHandler()) | |
631 self.bridge.menusGet("", C.NO_SECURITY_LIMIT, callback=self._menusGetCb) | |
632 | |
633 def setPresenceStatus(self, show='', status=None, profile=C.PROF_KEY_NONE): | |
634 log.info(u"Profile presence status set to {show}/{status}".format(show=show, status=status)) | |
635 | |
636 def addNote(self, title, message, level=C.XMLUI_DATA_LVL_INFO): | |
637 """add a note (message which disappear) to root widget's header""" | |
638 self.app.root.addNote(title, message, level) | |
639 | |
640 def addNotifUI(self, ui): | |
641 """add a notification with a XMLUI attached | |
642 | |
643 @param ui(xmlui.XMLUIPanel): XMLUI instance to show when notification is selected | |
644 """ | |
645 self.app.root.addNotifUI(ui) | |
646 | |
647 def showUI(self, ui): | |
648 """show a XMLUI""" | |
649 self.app.root.changeWidget(ui, "xmlui") | |
650 self.app.root.show("xmlui") | |
651 | |
652 def showExtraUI(self, widget): | |
653 """show any extra widget""" | |
654 self.app.root.changeWidget(widget, "extra") | |
655 self.app.root.show("extra") | |
656 | |
657 def closeUI(self): | |
658 self.app.root.show() | |
659 | |
660 def getDefaultAvatar(self, entity=None): | |
661 return self.app.default_avatar | |
662 | |
663 def showDialog(self, message, title, type="info", answer_cb=None, answer_data=None): | |
664 # TODO | |
665 log.info(u"FIXME: showDialog not implemented") | |
666 log.info(u"message: {} -- {}".format(title, message)) |