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,