Mercurial > libervia-desktop-kivy
comparison cagou/core/cagou_main.py @ 220:24f8ab7c08be
core: implemented showDialog so message/dialogs from QuickFrontend are displayed
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 26 Jun 2018 07:11:16 +0200 |
parents | c5c1dd7f88e1 |
children | e702228b655a |
comparison
equal
deleted
inserted
replaced
219:9faccd140119 | 220:24f8ab7c08be |
---|---|
48 from profile_manager import ProfileManager | 48 from profile_manager import ProfileManager |
49 from kivy.clock import Clock | 49 from kivy.clock import Clock |
50 from kivy.uix.label import Label | 50 from kivy.uix.label import Label |
51 from kivy.uix.boxlayout import BoxLayout | 51 from kivy.uix.boxlayout import BoxLayout |
52 from kivy.uix.floatlayout import FloatLayout | 52 from kivy.uix.floatlayout import FloatLayout |
53 from kivy.uix.screenmanager import ScreenManager, Screen, FallOutTransition, RiseInTransition | 53 from kivy.uix.screenmanager import (ScreenManager, Screen, |
54 FallOutTransition, RiseInTransition) | |
54 from kivy.uix.dropdown import DropDown | 55 from kivy.uix.dropdown import DropDown |
55 from kivy.core.window import Window | 56 from kivy.core.window import Window |
56 from kivy.animation import Animation | 57 from kivy.animation import Animation |
57 from kivy.metrics import dp | 58 from kivy.metrics import dp |
58 from cagou_widget import CagouWidget | 59 from cagou_widget import CagouWidget |
59 from . import widgets_handler | 60 from . import widgets_handler |
60 from .common import IconButton | 61 from .common import IconButton |
61 from . import menu | 62 from . import menu |
63 from . import dialog | |
62 from importlib import import_module | 64 from importlib import import_module |
63 import os.path | 65 import os.path |
64 import glob | 66 import glob |
65 import cagou.plugins | 67 import cagou.plugins |
66 import cagou.kv | 68 import cagou.kv |
97 | 99 |
98 | 100 |
99 class Note(Label): | 101 class Note(Label): |
100 title = properties.StringProperty() | 102 title = properties.StringProperty() |
101 message = properties.StringProperty() | 103 message = properties.StringProperty() |
102 level = properties.OptionProperty(C.XMLUI_DATA_LVL_DEFAULT, options=list(C.XMLUI_DATA_LVLS)) | 104 level = properties.OptionProperty(C.XMLUI_DATA_LVL_DEFAULT, |
105 options=list(C.XMLUI_DATA_LVLS)) | |
103 | 106 |
104 | 107 |
105 class NoteDrop(Label): | 108 class NoteDrop(Label): |
106 title = properties.StringProperty() | 109 title = properties.StringProperty() |
107 message = properties.StringProperty() | 110 message = properties.StringProperty() |
108 level = properties.OptionProperty(C.XMLUI_DATA_LVL_DEFAULT, options=list(C.XMLUI_DATA_LVLS)) | 111 level = properties.OptionProperty(C.XMLUI_DATA_LVL_DEFAULT, |
112 options=list(C.XMLUI_DATA_LVLS)) | |
109 | 113 |
110 | 114 |
111 class NotesDrop(DropDown): | 115 class NotesDrop(DropDown): |
112 clear_btn = properties.ObjectProperty() | 116 clear_btn = properties.ObjectProperty() |
113 | 117 |
135 self.notes_last = None | 139 self.notes_last = None |
136 self.notes_event = None | 140 self.notes_event = None |
137 self.notes_drop = NotesDrop(self.notes) | 141 self.notes_drop = NotesDrop(self.notes) |
138 | 142 |
139 def addNotif(self, callback, *args, **kwargs): | 143 def addNotif(self, callback, *args, **kwargs): |
144 """add a notification with a callback attached | |
145 | |
146 when notification is pressed, callback is called | |
147 @param *args, **kwargs: arguments of callback | |
148 """ | |
140 self.notifs_icon.addNotif(callback, *args, **kwargs) | 149 self.notifs_icon.addNotif(callback, *args, **kwargs) |
141 | 150 |
142 def addNote(self, title, message, level): | 151 def addNote(self, title, message, level): |
143 note = Note(title=title, message=message, level=level) | 152 note = Note(title=title, message=message, level=level) |
144 self.notes.append(note) | 153 self.notes.append(note) |
146 self.notes_event = Clock.schedule_interval(self._displayNextNote, 5) | 155 self.notes_event = Clock.schedule_interval(self._displayNextNote, 5) |
147 self._displayNextNote() | 156 self._displayNextNote() |
148 | 157 |
149 def addNotifUI(self, ui): | 158 def addNotifUI(self, ui): |
150 self.notifs_icon.addNotif(ui.show, force=True) | 159 self.notifs_icon.addNotif(ui.show, force=True) |
160 | |
161 def addNotifWidget(self, widget): | |
162 app = App.get_running_app() | |
163 self.notifs_icon.addNotif(app.host.showExtraUI, widget=widget) | |
151 | 164 |
152 def _displayNextNote(self, dummy=None): | 165 def _displayNextNote(self, dummy=None): |
153 screen = Screen() | 166 screen = Screen() |
154 try: | 167 try: |
155 idx = self.notes.index(self.notes_last) + 1 | 168 idx = self.notes.index(self.notes_last) + 1 |
227 self.head_widget.addNote(title, message, level) | 240 self.head_widget.addNote(title, message, level) |
228 | 241 |
229 def addNotifUI(self, ui): | 242 def addNotifUI(self, ui): |
230 self.head_widget.addNotifUI(ui) | 243 self.head_widget.addNotifUI(ui) |
231 | 244 |
245 def addNotifWidget(self, widget): | |
246 self.head_widget.addNotifWidget(widget) | |
247 | |
232 | 248 |
233 class CagouApp(App): | 249 class CagouApp(App): |
234 """Kivy App for Cagou""" | 250 """Kivy App for Cagou""" |
235 c_prim = properties.ListProperty(C.COLOR_PRIM) | 251 c_prim = properties.ListProperty(C.COLOR_PRIM) |
236 c_prim_light = properties.ListProperty(C.COLOR_PRIM_LIGHT) | 252 c_prim_light = properties.ListProperty(C.COLOR_PRIM_LIGHT) |
267 """ | 283 """ |
268 return os.path.expanduser(path).format(*args, media=self.host.media_dir, **kwargs) | 284 return os.path.expanduser(path).format(*args, media=self.host.media_dir, **kwargs) |
269 | 285 |
270 def on_start(self): | 286 def on_start(self): |
271 if sys.platform == "android": | 287 if sys.platform == "android": |
272 # XXX: we use memory map instead of bridge because if we try to call a bridge method | 288 # XXX: we use memory map instead of bridge because if we |
273 # in on_pause method, the call data is not written before the actual pause | 289 # try to call a bridge method in on_pause method, the call data |
290 # is not written before the actual pause | |
274 # we create a memory map on .cagou_status file with a 1 byte status | 291 # we create a memory map on .cagou_status file with a 1 byte status |
275 # status is: | 292 # status is: |
276 # R => running | 293 # R => running |
277 # P => paused | 294 # P => paused |
278 # S => stopped | 295 # S => stopped |
279 self._first_pause = True | 296 self._first_pause = True |
280 self.cagou_status_fd = open('.cagou_status', 'wb+') | 297 self.cagou_status_fd = open('.cagou_status', 'wb+') |
281 self.cagou_status_fd.write('R') | 298 self.cagou_status_fd.write('R') |
282 self.cagou_status_fd.flush() | 299 self.cagou_status_fd.flush() |
283 self.cagou_status = mmap.mmap(self.cagou_status_fd.fileno(), 1, prot=mmap.PROT_WRITE) | 300 self.cagou_status = mmap.mmap(self.cagou_status_fd.fileno(), 1, |
301 prot=mmap.PROT_WRITE) | |
284 | 302 |
285 def on_pause(self): | 303 def on_pause(self): |
286 self.cagou_status[0] = 'P' | 304 self.cagou_status[0] = 'P' |
287 return True | 305 return True |
288 | 306 |
345 if bridge_module is None: | 363 if bridge_module is None: |
346 log.error(u"Can't import {} bridge".format(bridge_name)) | 364 log.error(u"Can't import {} bridge".format(bridge_name)) |
347 sys.exit(3) | 365 sys.exit(3) |
348 else: | 366 else: |
349 log.debug(u"Loading {} bridge".format(bridge_name)) | 367 log.debug(u"Loading {} bridge".format(bridge_name)) |
350 super(Cagou, self).__init__(bridge_factory=bridge_module.Bridge, xmlui=xmlui, check_options=quick_utils.check_options, connect_bridge=False) | 368 super(Cagou, self).__init__(bridge_factory=bridge_module.Bridge, |
369 xmlui=xmlui, | |
370 check_options=quick_utils.check_options, | |
371 connect_bridge=False) | |
351 self._import_kv() | 372 self._import_kv() |
352 self.app = CagouApp() | 373 self.app = CagouApp() |
353 self.app.host = self | 374 self.app.host = self |
354 self.media_dir = self.app.media_dir = config.getConfig(main_config, '', 'media_dir') | 375 self.media_dir = self.app.media_dir = config.getConfig(main_config, '', |
355 self.downloads_dir = self.app.downloads_dir = config.getConfig(main_config, '', 'downloads_dir') | 376 'media_dir') |
377 self.downloads_dir = self.app.downloads_dir = config.getConfig(main_config, '', | |
378 'downloads_dir') | |
356 if not os.path.exists(self.downloads_dir): | 379 if not os.path.exists(self.downloads_dir): |
357 try: | 380 try: |
358 os.makedirs(self.downloads_dir) | 381 os.makedirs(self.downloads_dir) |
359 except OSError as e: | 382 except OSError as e: |
360 log.warnings(_(u"Can't create downloads dir: {reason}").format(reason=e)) | 383 log.warnings(_(u"Can't create downloads dir: {reason}").format(reason=e)) |
361 self.app.default_avatar = os.path.join(self.media_dir, "misc/default_avatar.png") | 384 self.app.default_avatar = os.path.join(self.media_dir, "misc/default_avatar.png") |
362 self.app.icon = os.path.join(self.media_dir, "icons/muchoslava/png/cagou_profil_bleu_96.png") | 385 self.app.icon = os.path.join(self.media_dir, |
386 "icons/muchoslava/png/cagou_profil_bleu_96.png") | |
363 self._plg_wids = [] # main widgets plugins | 387 self._plg_wids = [] # main widgets plugins |
364 self._plg_wids_transfer = [] # transfer widgets plugins | 388 self._plg_wids_transfer = [] # transfer widgets plugins |
365 self._import_plugins() | 389 self._import_plugins() |
366 self._visible_widgets = {} # visible widgets by classes | 390 self._visible_widgets = {} # visible widgets by classes |
367 | 391 |
421 @param plugin_info(dict): plugin datas | 445 @param plugin_info(dict): plugin datas |
422 @param target: QuickWidget target | 446 @param target: QuickWidget target |
423 @param profiles(iterable): list of profiles | 447 @param profiles(iterable): list of profiles |
424 """ | 448 """ |
425 main_cls = plugin_info['main'] | 449 main_cls = plugin_info['main'] |
426 return self.widgets.getOrCreateWidget(main_cls, target, on_new_widget=None, profiles=iter(self.profiles)) | 450 return self.widgets.getOrCreateWidget(main_cls, |
451 target, | |
452 on_new_widget=None, | |
453 profiles=iter(self.profiles)) | |
427 | 454 |
428 def _defaultFactoryTransfer(self, plugin_info, callback, cancel_cb, profiles): | 455 def _defaultFactoryTransfer(self, plugin_info, callback, cancel_cb, profiles): |
429 """default factory used to create transfer widgets instances | 456 """default factory used to create transfer widgets instances |
430 | 457 |
431 @param plugin_info(dict): plugin datas | 458 @param plugin_info(dict): plugin datas |
461 def _import_plugins(self): | 488 def _import_plugins(self): |
462 """import all plugins""" | 489 """import all plugins""" |
463 self.default_wid = None | 490 self.default_wid = None |
464 plugins_path = os.path.dirname(cagou.plugins.__file__) | 491 plugins_path = os.path.dirname(cagou.plugins.__file__) |
465 plugin_glob = u"plugin*." + C.PLUGIN_EXT | 492 plugin_glob = u"plugin*." + C.PLUGIN_EXT |
466 plug_lst = [os.path.splitext(p)[0] for p in map(os.path.basename, glob.glob(os.path.join(plugins_path, plugin_glob)))] | 493 plug_lst = [os.path.splitext(p)[0] for p in |
467 | 494 map(os.path.basename, glob.glob(os.path.join(plugins_path, |
468 imported_names_main = set() # used to avoid loading 2 times plugin with same import name | 495 plugin_glob)))] |
496 | |
497 imported_names_main = set() # used to avoid loading 2 times | |
498 # plugin with same import name | |
469 imported_names_transfer = set() | 499 imported_names_transfer = set() |
470 for plug in plug_lst: | 500 for plug in plug_lst: |
471 plugin_path = 'cagou.plugins.' + plug | 501 plugin_path = 'cagou.plugins.' + plug |
472 | 502 |
473 # we get type from plugin name | 503 # we get type from plugin name |
483 default_factory = self._defaultFactoryMain | 513 default_factory = self._defaultFactoryMain |
484 elif plugin_type == C.PLUG_TYPE_TRANSFER: | 514 elif plugin_type == C.PLUG_TYPE_TRANSFER: |
485 imported_names = imported_names_transfer | 515 imported_names = imported_names_transfer |
486 default_factory = self._defaultFactoryTransfer | 516 default_factory = self._defaultFactoryTransfer |
487 else: | 517 else: |
488 log.error(u"unknown plugin type {type_} for plugin {file_}, skipping".format( | 518 log.error(u"unknown plugin type {type_} for plugin {file_}, skipping" |
519 .format( | |
489 type_ = plugin_type, | 520 type_ = plugin_type, |
490 file_ = plug | 521 file_ = plug |
491 )) | 522 )) |
492 continue | 523 continue |
493 plugins_set = self._getPluginsSet(plugin_type) | 524 plugins_set = self._getPluginsSet(plugin_type) |
501 plugin_info['plugin_file'] = plug | 532 plugin_info['plugin_file'] = plug |
502 plugin_info['plugin_type'] = plugin_type | 533 plugin_info['plugin_type'] = plugin_type |
503 | 534 |
504 if 'platforms' in plugin_info: | 535 if 'platforms' in plugin_info: |
505 if sys.platform not in plugin_info['platforms']: | 536 if sys.platform not in plugin_info['platforms']: |
506 log.info(u"{plugin_file} is not used on this platform, skipping".format(**plugin_info)) | 537 log.info(u"{plugin_file} is not used on this platform, skipping" |
538 .format(**plugin_info)) | |
507 continue | 539 continue |
508 | 540 |
509 # import name is used to differentiate plugins | 541 # import name is used to differentiate plugins |
510 if 'import_name' not in plugin_info: | 542 if 'import_name' not in plugin_info: |
511 plugin_info['import_name'] = plug | 543 plugin_info['import_name'] = plug |
512 if plugin_info['import_name'] in imported_names: | 544 if plugin_info['import_name'] in imported_names: |
513 log.warning(_(u"there is already a plugin named {}, ignoring new one").format(plugin_info['import_name'])) | 545 log.warning(_(u"there is already a plugin named {}, " |
546 u"ignoring new one").format(plugin_info['import_name'])) | |
514 continue | 547 continue |
515 if plugin_info['import_name'] == C.WID_SELECTOR: | 548 if plugin_info['import_name'] == C.WID_SELECTOR: |
516 if plugin_type != C.PLUG_TYPE_WID: | 549 if plugin_type != C.PLUG_TYPE_WID: |
517 log.error(u"{import_name} import name can only be used with {type_} type, skipping {name}".format(type_=C.PLUG_TYPE_WID, **plugin_info)) | 550 log.error(u"{import_name} import name can only be used with {type_} " |
551 u"type, skipping {name}".format(type_=C.PLUG_TYPE_WID, | |
552 **plugin_info)) | |
518 continue | 553 continue |
519 # if WidgetSelector exists, it will be our default widget | 554 # if WidgetSelector exists, it will be our default widget |
520 self.default_wid = plugin_info | 555 self.default_wid = plugin_info |
521 | 556 |
522 # we want everything optional, so we use plugin file name | 557 # we want everything optional, so we use plugin file name |
632 if isinstance(w, CagouWidget): | 667 if isinstance(w, CagouWidget): |
633 to_change = w | 668 to_change = w |
634 break | 669 break |
635 | 670 |
636 if to_change is None: | 671 if to_change is None: |
637 raise exceptions.InternalError(u"no CagouWidget found when trying to switch widget") | 672 raise exceptions.InternalError(u"no CagouWidget found when " |
673 u"trying to switch widget") | |
638 | 674 |
639 wrapper = to_change.parent | 675 wrapper = to_change.parent |
640 while wrapper is not None and not(isinstance(wrapper, widgets_handler.WHWrapper)): | 676 while wrapper is not None and not(isinstance(wrapper, widgets_handler.WHWrapper)): |
641 wrapper = wrapper.parent | 677 wrapper = wrapper.parent |
642 | 678 |
675 def getOrClone(self, widget): | 711 def getOrClone(self, widget): |
676 """Get a QuickWidget if it has not parent set else clone it""" | 712 """Get a QuickWidget if it has not parent set else clone it""" |
677 if widget.parent is None: | 713 if widget.parent is None: |
678 return widget | 714 return widget |
679 targets = list(widget.targets) | 715 targets = list(widget.targets) |
680 w = self.widgets.getOrCreateWidget(widget.__class__, targets[0], on_new_widget=None, on_existing_widget=C.WIDGET_RECREATE, profiles=widget.profiles) | 716 w = self.widgets.getOrCreateWidget(widget.__class__, |
717 targets[0], | |
718 on_new_widget=None, | |
719 on_existing_widget=C.WIDGET_RECREATE, | |
720 profiles=widget.profiles) | |
681 for t in targets[1:]: | 721 for t in targets[1:]: |
682 w.addTarget(t) | 722 w.addTarget(t) |
683 return w | 723 return w |
684 | 724 |
685 ## menus ## | 725 ## menus ## |
686 | 726 |
687 def _menusGetCb(self, backend_menus): | 727 def _menusGetCb(self, backend_menus): |
688 main_menu = self.app.root.root_menus | 728 main_menu = self.app.root.root_menus |
689 self.menus.addMenus(backend_menus) | 729 self.menus.addMenus(backend_menus) |
690 self.menus.addMenu(C.MENU_GLOBAL, (_(u"Help"), _(u"About")), callback=main_menu.onAbout) | 730 self.menus.addMenu(C.MENU_GLOBAL, |
731 (_(u"Help"), _(u"About")), | |
732 callback=main_menu.onAbout) | |
691 main_menu.update(C.MENU_GLOBAL) | 733 main_menu.update(C.MENU_GLOBAL) |
692 | 734 |
693 ## bridge handlers ## | 735 ## bridge handlers ## |
694 | 736 |
695 def otrStateHandler(self, state, dest_jid, profile): | 737 def otrStateHandler(self, state, dest_jid, profile): |
704 def plugging_profiles(self): | 746 def plugging_profiles(self): |
705 self.app.root.changeWidget(widgets_handler.WidgetsHandler()) | 747 self.app.root.changeWidget(widgets_handler.WidgetsHandler()) |
706 self.bridge.menusGet("", C.NO_SECURITY_LIMIT, callback=self._menusGetCb) | 748 self.bridge.menusGet("", C.NO_SECURITY_LIMIT, callback=self._menusGetCb) |
707 | 749 |
708 def setPresenceStatus(self, show='', status=None, profile=C.PROF_KEY_NONE): | 750 def setPresenceStatus(self, show='', status=None, profile=C.PROF_KEY_NONE): |
709 log.info(u"Profile presence status set to {show}/{status}".format(show=show, status=status)) | 751 log.info(u"Profile presence status set to {show}/{status}".format(show=show, |
710 | 752 status=status)) |
711 def errback(self, failure_, title=_('error'), message=_(u'error while processing: {msg}')): | 753 |
754 def errback(self, failure_, title=_('error'), | |
755 message=_(u'error while processing: {msg}')): | |
712 self.addNote(title, message.format(msg=failure_), level=C.XMLUI_DATA_LVL_WARNING) | 756 self.addNote(title, message.format(msg=failure_), level=C.XMLUI_DATA_LVL_WARNING) |
713 | 757 |
714 def addNote(self, title, message, level=C.XMLUI_DATA_LVL_INFO): | 758 def addNote(self, title, message, level=C.XMLUI_DATA_LVL_INFO): |
715 """add a note (message which disappear) to root widget's header""" | 759 """add a note (message which disappear) to root widget's header""" |
716 self.app.root.addNote(title, message, level) | 760 self.app.root.addNote(title, message, level) |
719 """add a notification with a XMLUI attached | 763 """add a notification with a XMLUI attached |
720 | 764 |
721 @param ui(xmlui.XMLUIPanel): XMLUI instance to show when notification is selected | 765 @param ui(xmlui.XMLUIPanel): XMLUI instance to show when notification is selected |
722 """ | 766 """ |
723 self.app.root.addNotifUI(ui) | 767 self.app.root.addNotifUI(ui) |
768 | |
769 def addNotifWidget(self, widget): | |
770 """add a notification with a Kivy widget attached | |
771 | |
772 @param widget(kivy.uix.Widget): widget to attach to notification | |
773 """ | |
774 self.app.root.addNotifWidget(widget) | |
724 | 775 |
725 def showUI(self, ui): | 776 def showUI(self, ui): |
726 """show a XMLUI""" | 777 """show a XMLUI""" |
727 self.app.root.changeWidget(ui, "xmlui") | 778 self.app.root.changeWidget(ui, "xmlui") |
728 self.app.root.show("xmlui") | 779 self.app.root.show("xmlui") |
736 self.app.root.show() | 787 self.app.root.show() |
737 | 788 |
738 def getDefaultAvatar(self, entity=None): | 789 def getDefaultAvatar(self, entity=None): |
739 return self.app.default_avatar | 790 return self.app.default_avatar |
740 | 791 |
792 def _dialog_cb(self, cb, *args, **kwargs): | |
793 """generic dialog callback | |
794 | |
795 close dialog then call the callback with given arguments | |
796 """ | |
797 def callback(): | |
798 self.closeUI() | |
799 cb(*args, **kwargs) | |
800 return callback | |
801 | |
741 def showDialog(self, message, title, type="info", answer_cb=None, answer_data=None): | 802 def showDialog(self, message, title, type="info", answer_cb=None, answer_data=None): |
742 # TODO | 803 if type in ('info', 'warning', 'error'): |
743 log.info(u"FIXME: showDialog not implemented") | 804 self.addNote(title, message, type) |
744 log.info(u"message: {} -- {}".format(title, message)) | 805 elif type == "yes/no": |
806 wid = dialog.ConfirmDialog(title=title, message=message, | |
807 yes_cb=self._dialog_cb(answer_cb, | |
808 True, | |
809 answer_data), | |
810 no_cb=self._dialog_cb(answer_cb, | |
811 False, | |
812 answer_data) | |
813 ) | |
814 self.addNotifWidget(wid) | |
815 else: | |
816 log.warning(_(u"unknown dialog type: {dialog_type}").format(dialog_type=type)) | |
745 | 817 |
746 | 818 |
747 def desktop_notif(self, message, title=u'', duration=5000): | 819 def desktop_notif(self, message, title=u'', duration=5000): |
748 if notification is not None: | 820 if notification is not None: |
749 notification.notify(title=title, | 821 notification.notify(title=title, |