Mercurial > libervia-desktop-kivy
view src/cagou/core/cagou_main.py @ 29:8b5827c43155
notes first draft:
Implementation of XMLUI notes. There is a new header on top of root widget which display notifications, and notes are shown for a couple of seconds.
A blue Cagou head appear when there are notes, and user can display 10 last when clicking on it.
This header will probably not be present on platforms such as Android, because there is already a system-wide notifications handler which can be used instead (saving visual space).
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 21 Aug 2016 15:15:25 +0200 |
parents | 9f9532eb835f |
children | 4f9e701d76b4 |
line wrap: on
line source
#!/usr//bin/env python2 # -*- coding: utf-8 -*- # Cagou: desktop/mobile frontend for Salut à Toi XMPP client # Copyright (C) 2016 Jérôme Poisson (goffi@goffi.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.i18n import _ import logging_setter logging_setter.set_logging() from constants import Const as C from sat.core import log as logging log = logging.getLogger(__name__) from sat_frontends.quick_frontend.quick_app import QuickApp from sat_frontends.bridge.DBus import DBusBridgeFrontend import kivy kivy.require('1.9.1') import kivy.support kivy.support.install_gobject_iteration() from kivy.app import App from kivy.lang import Builder from kivy import properties import xmlui from profile_manager import ProfileManager from widgets_handler import WidgetsHandler from kivy.clock import Clock from kivy.uix.label import Label from kivy.uix.boxlayout import BoxLayout from kivy.uix.screenmanager import ScreenManager, Screen from kivy.uix.dropdown import DropDown from cagou_widget import CagouWidget from .common import IconButton from importlib import import_module import os.path import glob import cagou.plugins import cagou.kv class NotifIcon(IconButton): def __init__(self, callback, callback_args): self._callback = callback self._callback_args = callback_args super(NotifIcon, self).__init__() def on_release(self): self.parent.remove_widget(self) self._callback(*self._callback_args) class Note(Label): title = properties.StringProperty() message = properties.StringProperty() level = properties.OptionProperty(C.XMLUI_DATA_LVL_DEFAULT, options=list(C.XMLUI_DATA_LVLS)) class NoteDrop(Note): pass class NotesDrop(DropDown): clear_btn = properties.ObjectProperty() def __init__(self, notes): super(NotesDrop, self).__init__() self.notes = notes def open(self, widget): self.clear_widgets() for n in self.notes: self.add_widget(NoteDrop(title=n.title, message=n.message, level=n.level)) self.add_widget(self.clear_btn) super(NotesDrop, self).open(widget) class RootHeadWidget(BoxLayout): """Notifications widget""" manager = properties.ObjectProperty() notes = properties.ListProperty() def __init__(self): super(RootHeadWidget, self).__init__() self.notes_last = None self.notes_event = None self.notes_drop = NotesDrop(self.notes) # auto_with=False, width=100) def addNotif(self, callback, *args): icon = NotifIcon(callback, args) self.add_widget(icon) def addNote(self, title, message, level): note = Note(title=title, message=message, level=level) self.notes.append(note) if len(self.notes) > 10: del self.notes[:-10] if self.notes_event is None: self.notes_event = Clock.schedule_interval(self._displayNextNote, 5) self._displayNextNote() def _displayNextNote(self, dummy=None): screen = Screen() try: idx = self.notes.index(self.notes_last) + 1 except ValueError: idx = 0 try: note = self.notes_last = self.notes[idx] except IndexError: self.notes_event.cancel() self.notes_event = None else: screen.add_widget(note) self.manager.switch_to(screen) class CagouRootWidget(BoxLayout): def __init__(self, main_widget): super(CagouRootWidget, self).__init__(orientation=("vertical")) # header self._head_widget = RootHeadWidget() self.add_widget(self._head_widget) # body self._manager = ScreenManager() main_screen = Screen(name='main') main_screen.add_widget(main_widget) self._manager.add_widget(main_screen) self.add_widget(self._manager) self.change_widget(main_widget) def change_widget(self, main_widget, screen="main"): """change main widget""" main_screen = self._manager.get_screen(screen) main_screen.clear_widgets() main_screen.add_widget(main_widget) def newAction(self, handler, action_data, id_, security_limit, profile): """Add a notification for an action""" self._head_widget.addNotif(handler, action_data, id_, security_limit, profile) def addNote(self, title, message, level): self._head_widget.addNote(title, message, level) class CagouApp(App): """Kivy App for Cagou""" def build(self): return CagouRootWidget(ProfileManager()) def expand(self, path): """expand path and replace known values useful in kv. Values which can be used: - {media}: media dir """ return os.path.expanduser(path).format(media=self.host.media_dir) class Cagou(QuickApp): MB_HANDLE = False def __init__(self): super(Cagou, self).__init__(create_bridge=DBusBridgeFrontend, xmlui=xmlui) self._import_kv() self.app = CagouApp() self.app.host = self self.media_dir = self.app.media_dir = self.bridge.getConfig("", "media_dir") self.app.default_avatar = os.path.join(self.media_dir, "misc/default_avatar.png") self._plg_wids = [] # widget plugins self._import_plugins() def run(self): self.app.run() def _defaultFactory(self, plugin_info, target, profiles): """factory used to create widget instance when PLUGIN_INFO["factory"] is not set""" main_cls = plugin_info['main'] return self.widgets.getOrCreateWidget(main_cls, target, on_new_widget=None, profiles=iter(self.profiles)) ## plugins & kv import ## def _import_kv(self): """import all kv files in cagou.kv""" path = os.path.dirname(cagou.kv.__file__) for kv_path in glob.glob(os.path.join(path, "*.kv")): Builder.load_file(kv_path) log.debug(u"kv file {} loaded".format(kv_path)) def _import_plugins(self): """import all plugins""" self.default_wid = None plugins_path = os.path.dirname(cagou.plugins.__file__) plug_lst = [os.path.splitext(p)[0] for p in map(os.path.basename, glob.glob(os.path.join(plugins_path, "plugin*.py")))] imported_names = set() # use to avoid loading 2 times plugin with same import name for plug in plug_lst: plugin_path = 'cagou.plugins.' + plug mod = import_module(plugin_path) try: plugin_info = mod.PLUGIN_INFO except AttributeError: plugin_info = {} # import name is used to differentiate plugins if 'import_name' not in plugin_info: plugin_info['import_name'] = plug if 'import_name' in imported_names: log.warning(_(u"there is already a plugin named {}, ignoring new one").format(plugin_info['import_name'])) continue if plugin_info['import_name'] == C.WID_SELECTOR: # if WidgetSelector exists, it will be our default widget self.default_wid = plugin_info # we want everything optional, so we use plugin file name # if actual name is not found if 'name' not in plugin_info: plugin_info['name'] = plug[plug.rfind('_')+1:] # we need to load the kv file if 'kv_file' not in plugin_info: plugin_info['kv_file'] = u'{}.kv'.format(plug) kv_path = os.path.join(plugins_path, plugin_info['kv_file']) Builder.load_file(kv_path) # what is the main class ? main_cls = getattr(mod, plugin_info['main']) plugin_info['main'] = main_cls # factory is used to create the instance # if not found, we use a defaut one with getOrCreateWidget if 'factory' not in plugin_info: plugin_info['factory'] = self._defaultFactory # icons for size in ('small', 'medium'): key = u'icon_{}'.format(size) try: path = plugin_info[key] except KeyError: path = C.DEFAULT_WIDGET_ICON.format(media=self.media_dir) else: path = path.format(media=self.media_dir) if not os.path.isfile(path): path = C.DEFAULT_WIDGET_ICON.format(media=self.media_dir) plugin_info[key] = path self._plg_wids.append(plugin_info) if not self._plg_wids: log.error(_(u"no widget plugin found")) return # we want widgets sorted by names self._plg_wids.sort(key=lambda p: p['name'].lower()) if self.default_wid is None: # we have no selector widget, we use the first widget as default self.default_wid = self._plg_wids[0] def getPluggedWidgets(self, except_cls=None): """get available widgets plugin infos @param except_cls(None, class): if not None, widgets from this class will be excluded @return (list[dict]): available widgets plugin infos """ for plugin_data in self._plg_wids: if plugin_data['main'] == except_cls: continue yield plugin_data ## widgets handling def switchWidget(self, old, new): """Replace old widget by new one old(CagouWidget): CagouWidget instance or a child new(CagouWidget): new widget instance """ to_change = None if isinstance(old, CagouWidget): to_change = old else: for w in old.walk_reverse(): if isinstance(w, CagouWidget): to_change = w break if to_change is None: log.error(u"no CagouWidget found when trying to switch widget") else: parent = to_change.parent idx = parent.children.index(to_change) parent.remove_widget(to_change) parent.add_widget(new, index=idx) ## misc ## def plugging_profiles(self): self.app.root.change_widget(WidgetsHandler()) def setPresenceStatus(self, show='', status=None, profile=C.PROF_KEY_NONE): log.info(u"Profile presence status set to {show}/{status}".format(show=show, status=status)) def addNote(self, title, message, level): """add a note (message which disappear) to root widget's header""" self.app.root.addNote(title, message, level) ## signals handling ## def actionNewHandler(self, action_data, id_, security_limit, profile): handler = super(Cagou, self).actionNewHandler # FIXME: temporarily deactivated # if 'xmlui' in action_data: # self.app.root.newAction(handler, action_data, id_, security_limit, profile) # else: # handler(action_data, id_, security_limit, profile) handler(action_data, id_, security_limit, profile)