# HG changeset patch # User Goffi # Date 1471785325 -7200 # Node ID 8b5827c43155b188c4760007107344dc9f04c6cb # Parent 9f9532eb835f18277bb414437a878423921e77d9 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). diff -r 9f9532eb835f -r 8b5827c43155 src/cagou/core/cagou_main.py --- a/src/cagou/core/cagou_main.py Sun Aug 21 15:02:18 2016 +0200 +++ b/src/cagou/core/cagou_main.py Sun Aug 21 15:15:25 2016 +0200 @@ -32,11 +32,17 @@ 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 @@ -44,24 +50,118 @@ 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, widgets): + def __init__(self, main_widget): super(CagouRootWidget, self).__init__(orientation=("vertical")) - for wid in widgets: - self.add_widget(wid) + # header + self._head_widget = RootHeadWidget() + self.add_widget(self._head_widget) - def change_widgets(self, widgets): - self.clear_widgets() - for wid in widgets: - self.add_widget(wid) + # 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()]) + return CagouRootWidget(ProfileManager()) def expand(self, path): """expand path and replace known values @@ -79,6 +179,7 @@ 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 @@ -92,6 +193,8 @@ 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__) @@ -180,11 +283,7 @@ continue yield plugin_data - def plugging_profiles(self): - self.app.root.change_widgets([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)) + ## widgets handling def switchWidget(self, old, new): """Replace old widget by new one @@ -208,3 +307,26 @@ 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) diff -r 9f9532eb835f -r 8b5827c43155 src/cagou/core/cagou_widget.py --- a/src/cagou/core/cagou_widget.py Sun Aug 21 15:02:18 2016 +0200 +++ b/src/cagou/core/cagou_widget.py Sun Aug 21 15:15:25 2016 +0200 @@ -35,7 +35,6 @@ self.bind(on_release=lambda btn, plugin_info=plugin_info: cagou_widget.switchWidget(plugin_info)) - class HeaderWidgetCurrent(ButtonBehavior, Image): pass diff -r 9f9532eb835f -r 8b5827c43155 src/cagou/core/xmlui.py --- a/src/cagou/core/xmlui.py Sun Aug 21 15:02:18 2016 +0200 +++ b/src/cagou/core/xmlui.py Sun Aug 21 15:15:25 2016 +0200 @@ -27,6 +27,10 @@ from kivy.uix.label import Label from kivy.uix.button import Button from kivy.uix.widget import Widget +from cagou import G + + +## Widgets ## class TextWidget(xmlui.TextWidget, Label): @@ -48,6 +52,9 @@ return self.text +## Containers ## + + class VerticalContainer(xmlui.VerticalContainer, BoxLayout): def __init__(self, xmlui_parent): @@ -57,6 +64,22 @@ self.add_widget(widget) +## Dialogs ## + + +class NoteDialog(xmlui.NoteDialog): + + def __init__(self, _xmlui_parent, title, message, level): + xmlui.NoteDialog.__init__(self, _xmlui_parent) + self.title, self.message, self.level = title, message, level + + def _xmluiShow(self): + G.host.addNote(self.title, self.message, self.level) + + +## Factory ## + + class WidgetFactory(object): def __getattr__(self, attr): @@ -65,6 +88,9 @@ return cls +## Core ## + + class Title(Label): def __init__(self, *args, **kwargs): @@ -106,5 +132,10 @@ self.add_widget(Widget()) # to have elements on the top +class XMLUIDialog(xmlui.XMLUIDialog): + dialog_factory = WidgetFactory() + + xmlui.registerClass(xmlui.CLASS_PANEL, XMLUIPanel) +xmlui.registerClass(xmlui.CLASS_DIALOG, XMLUIDialog) create = xmlui.create diff -r 9f9532eb835f -r 8b5827c43155 src/cagou/kv/root_widget.kv --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cagou/kv/root_widget.kv Sun Aug 21 15:15:25 2016 +0200 @@ -0,0 +1,60 @@ +# 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 . + +#:import IconButton cagou.core.common.IconButton + +# : +# source: app.expand("{media}/icons/muchoslava/png/cagou_profil_bleu_32.png") +# size_hint: None, None +# size: self.texture_size + +: + text: self.message + +: + canvas.before: + Color: + rgba: 1, 1, 1, 1 + BorderImage: + pos: self.pos + size: self.size + source: 'atlas://data/images/defaulttheme/button' + size_hint: 1, None + height: 50 + +: + clear_btn: clear_btn.__self__ + auto_width: False + size_hint: 0.8, None + Button: + id: clear_btn + text: "clear" + bold: True + size_hint: 1, None + height: 50 + on_release: del root.notes[:]; root.dismiss() + +: + manager: manager + size_hint: 1, None + height: 35 + IconButton: + source: app.expand("{media}/icons/muchoslava/png/cagou_profil_bleu_32.png") if root.notes else app.expand("{media}/misc/black.png") + size_hint: None, 1 + width: self.texture_size[0] + on_release: root.notes_drop.open(self) if root.notes else None + ScreenManager: + id: manager