changeset 15:56838ad5c84b

files reorganisation, cagou is now launched with python2 cagou.py in src/
author Goffi <goffi@goffi.org>
date Sat, 09 Jul 2016 17:24:01 +0200
parents 21a432afd06d
children ba14b596b90e
files src/__init__.py src/button.png src/button_selected.png src/cagou.kv src/cagou.py src/cagou/__init__.py src/cagou/core/__init__.py src/cagou/core/cagou_main.py src/cagou/core/cagou_widget.py src/cagou/core/config.py src/cagou/core/constants.py src/cagou/core/image.py src/cagou/core/logging_setter.py src/cagou/core/profile_manager.py src/cagou/core/widgets_handler.py src/cagou/core/xmlui.py src/cagou/images/button.png src/cagou/images/button_selected.png src/cagou/kv/__init__.py src/cagou/kv/cagou_widget.kv src/cagou/kv/profile_manager.kv src/cagou/kv/widgets_handler.kv src/cagou/plugins/__init__.py src/cagou/plugins/plugin_wid_contact_list.kv src/cagou/plugins/plugin_wid_contact_list.py src/cagou/plugins/plugin_wid_widget_selector.kv src/cagou/plugins/plugin_wid_widget_selector.py src/cagou_widget.kv src/cagou_widget.py src/config.py src/constants.py src/image.py src/logging_setter.py src/plugin_wid_contact_list.kv src/plugin_wid_contact_list.py src/plugin_wid_widget_selector.kv src/plugin_wid_widget_selector.py src/profile_manager.kv src/profile_manager.py src/widgets_handler.kv src/widgets_handler.py src/xmlui.py
diffstat 38 files changed, 1289 insertions(+), 1253 deletions(-) [+]
line wrap: on
line diff
Binary file src/button.png has changed
Binary file src/button_selected.png has changed
--- a/src/cagou.kv	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-# 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/>.
-
-#:include profile_manager.kv
-#:include widgets_handler.kv
-#:include cagou_widget.kv
--- a/src/cagou.py	Sat Jul 09 16:02:44 2016 +0200
+++ b/src/cagou.py	Sat Jul 09 17:24:01 2016 +0200
@@ -17,170 +17,7 @@
 # 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
-# from sat_frontends.quick_frontend import quick_utils
-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
-import xmlui
-from profile_manager import ProfileManager
-from widgets_handler import WidgetsHandler
-from kivy.uix.boxlayout import BoxLayout
-from cagou_widget import CagouWidget
-from importlib import import_module
-import os.path
-import glob
-
-
-class CagouRootWidget(BoxLayout):
-
-    def __init__(self, widgets):
-        super(CagouRootWidget, self).__init__(orientation=("vertical"))
-        for wid in widgets:
-            self.add_widget(wid)
-
-    def change_widgets(self, widgets):
-        self.clear_widgets()
-        for wid in widgets:
-            self.add_widget(wid)
-
-
-class CagouApp(App):
-    """Kivy App for Cagou"""
-
-    def build(self):
-        return CagouRootWidget([ProfileManager(self.host)])
-
-
-class Cagou(QuickApp):
-    MB_HANDLE = False
-
-    def __init__(self):
-        super(Cagou, self).__init__(create_bridge=DBusBridgeFrontend, xmlui=xmlui)
-        self.app = CagouApp()
-        self.app.host = self
-        media_dir = self.app.media_dir = self.bridge.getConfig("", "media_dir")
-        self.app.default_avatar = os.path.join(media_dir, "misc/default_avatar.png")
-        self._plg_wids = []  # widget plugins
-        self._import_plugins()
-
-    def run(self):
-        self.app.run()
-
-    def _defaultFactory(self, host, 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=profiles)
-
-    def _import_plugins(self):
-        """Import all plugins"""
-        self.default_wid = None
-        plugins_path = os.path.dirname(__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:
-            mod = import_module(plug)
-            try:
-                plugin_info = mod.PLUGIN_INFO
-            except AttributeError:
-                plugin_info = {}
+import cagou
 
-            # 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)
-            Builder.load_file(plugin_info['kv_file'])
-
-            # 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
-
-            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
-        """
-        lst = self._plg_wids[:]
-        if except_cls is not None:
-            for plugin_info in lst:
-                if plugin_info['main'] == except_cls:
-                    lst.remove(plugin_info)
-                    break
-        return lst
-
-    def plugging_profiles(self):
-        self.app.root.change_widgets([WidgetsHandler(self)])
-
-    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 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)
-
-
-if __name__ == '__main__':
-    Cagou().run()
+if __name__ == "__main__":
+    cagou.run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/__init__.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,24 @@
+#!/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 core import cagou_main
+
+def run():
+    host = cagou_main.Cagou()
+    host.run()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/core/cagou_main.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,193 @@
+#!/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
+import xmlui
+from profile_manager import ProfileManager
+from widgets_handler import WidgetsHandler
+from kivy.uix.boxlayout import BoxLayout
+from cagou_widget import CagouWidget
+from importlib import import_module
+import os.path
+import glob
+import cagou.plugins
+import cagou.kv
+
+
+class CagouRootWidget(BoxLayout):
+
+    def __init__(self, widgets):
+        super(CagouRootWidget, self).__init__(orientation=("vertical"))
+        for wid in widgets:
+            self.add_widget(wid)
+
+    def change_widgets(self, widgets):
+        self.clear_widgets()
+        for wid in widgets:
+            self.add_widget(wid)
+
+
+class CagouApp(App):
+    """Kivy App for Cagou"""
+
+    def build(self):
+        return CagouRootWidget([ProfileManager(self.host)])
+
+
+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
+        media_dir = self.app.media_dir = self.bridge.getConfig("", "media_dir")
+        self.app.default_avatar = os.path.join(media_dir, "misc/default_avatar.png")
+        self._plg_wids = []  # widget plugins
+        self._import_plugins()
+
+    def run(self):
+        self.app.run()
+
+    def _defaultFactory(self, host, 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=profiles)
+
+    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
+
+            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
+        """
+        lst = self._plg_wids[:]
+        if except_cls is not None:
+            for plugin_info in lst:
+                if plugin_info['main'] == except_cls:
+                    lst.remove(plugin_info)
+                    break
+        return lst
+
+    def plugging_profiles(self):
+        self.app.root.change_widgets([WidgetsHandler(self)])
+
+    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 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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/core/cagou_widget.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,54 @@
+#!/usr/bin/python
+# -*- 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 import log as logging
+log = logging.getLogger(__name__)
+from kivy.uix.button import Button
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.dropdown import DropDown
+
+
+class HeaderWidgetButton(Button):
+    pass
+
+
+class HeaderWidgetSelector(DropDown):
+
+    def __init__(self, cagou_widget):
+        super(HeaderWidgetSelector, self).__init__()
+        host = cagou_widget.host
+        for plugin_info in host.getPluggedWidgets(except_cls=cagou_widget.__class__):
+            btn = HeaderWidgetButton(text=plugin_info["name"])
+            btn.bind(on_release=lambda btn, plugin_info=plugin_info: cagou_widget.switchWidget(plugin_info))
+            self.add_widget(btn)
+
+
+class CagouWidget(BoxLayout):
+
+    def __init__(self, host):
+        self.host = host
+        BoxLayout.__init__(self, orientation="vertical")
+        self.selector = HeaderWidgetSelector(self)
+
+    def switchWidget(self, plugin_info):
+        self.selector.dismiss()
+        factory = plugin_info["factory"]
+        new_widget = factory(self.host, plugin_info, None, None)
+        self.host.switchWidget(self, new_widget)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/core/config.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,27 @@
+#!/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/>.
+
+"""This module keep an open instance of sat configuration"""
+
+from sat.tools import config
+sat_conf = config.parseMainConf()
+
+
+def getConfig(section, name, default):
+    return config.getConfig(sat_conf, section, name, default)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/core/constants.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,27 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Primitivus: a SAT frontend
+# Copyright (C) 2009-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_frontends.quick_frontend import constants
+
+
+class Const(constants.Const):
+    APP_NAME = "Cagou"
+    LOG_OPT_SECTION = APP_NAME.lower()
+    CONFIG_SECTION = APP_NAME.lower()
+    WID_SELECTOR = 'selector'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/core/image.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,73 @@
+#!/usr/bin/python
+# -*- 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 import log as logging
+log = logging.getLogger(__name__)
+from kivy.uix import image as kivy_img
+from kivy.core.image import Image as CoreImage
+from kivy.resources import resource_find
+import io
+import PIL
+
+
+class Image(kivy_img.Image):
+    """Image widget which accept source without extension"""
+
+    def texture_update(self, *largs):
+        if not self.source:
+            self.texture = None
+        else:
+            filename = resource_find(self.source)
+            self._loops = 0
+            if filename is None:
+                return log.error('Image: Error reading file {filename}'.
+                                    format(filename=self.source))
+            mipmap = self.mipmap
+            if self._coreimage is not None:
+                self._coreimage.unbind(on_texture=self._on_tex_change)
+            try:
+                self._coreimage = ci = CoreImage(filename, mipmap=mipmap,
+                                                 anim_delay=self.anim_delay,
+                                                 keep_data=self.keep_data,
+                                                 nocache=self.nocache)
+            except Exception as e:
+                # loading failed probably because of unmanaged extention,
+                # we try our luck with with PIL
+                try:
+                    im = PIL.Image.open(filename)
+                    ext = im.format.lower()
+                    del im
+                    # we can't use im.tobytes as it would use the
+                    # internal decompressed representation from pillow
+                    # and im.save would need processing to handle format
+                    data = io.BytesIO(open(filename, "rb").read())
+                    cache_filename = u"{}.{}".format(filename,ext) # needed for kivy's Image to use cache
+                    self._coreimage = ci = CoreImage(data, ext=ext,
+                                                     filename=cache_filename, mipmap=mipmap,
+                                                     anim_delay=self.anim_delay,
+                                                     keep_data=self.keep_data,
+                                                     nocache=self.nocache)
+                except Exception as e:
+                    log.warning(u"Can't load image: {}".format(e))
+                    self._coreimage = ci = None
+
+            if ci:
+                ci.bind(on_texture=self._on_tex_change)
+                self.texture = ci.texture
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/core/logging_setter.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,61 @@
+#!/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/>.
+
+CONF_KIVY_LEVEL = 'log_kivy_level'
+
+def set_logging():
+    from constants import Const as C
+    from sat.core import log_config
+    log_config.satConfigure(C.LOG_BACKEND_STANDARD, C)
+
+    import config
+    kivy_level = config.getConfig(C.CONFIG_SECTION, CONF_KIVY_LEVEL, 'follow').upper()
+
+    # kivy handles its own loggers, we don't want that!
+    import logging
+    root_logger = logging.root
+    kivy_logger = logging.getLogger('kivy')
+    ori_addHandler = kivy_logger.addHandler
+    kivy_logger.addHandler = lambda dummy: None
+    ori_setLevel = kivy_logger.setLevel
+    if kivy_level == 'FOLLOW':
+        # level is following SàT level
+        kivy_logger.setLevel = lambda level: None
+    elif kivy_level == 'KIVY':
+        # level will be set by Kivy according to its own conf
+        pass
+    elif kivy_level in C.LOG_LEVELS:
+        kivy_logger.setLevel(kivy_level)
+        kivy_logger.setLevel = lambda level: None
+    else:
+        raise ValueError(u"Unknown value for {name}: {value}".format(name=CONF_KIVY_LEVEL, value=kivy_level))
+
+    # during import kivy set its logging stuff
+    import kivy
+    kivy # to avoid pyflakes warning
+
+    # we want to separate kivy logs from other logs
+    logging.root = root_logger
+    import sys
+    from kivy import logger
+    sys.stderr = logger.previous_stderr
+
+    # we restore original methods
+    kivy_logger.addHandler = ori_addHandler
+    kivy_logger.setLevel = ori_setLevel
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/core/profile_manager.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,173 @@
+#!/usr/bin/python
+# -*- 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 import log as logging
+log = logging.getLogger(__name__)
+from sat_frontends.constants import Const as C
+from sat_frontends.quick_frontend.quick_profile_manager import QuickProfileManager
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix import listview
+from kivy.uix.button import Button
+from kivy.uix.screenmanager import ScreenManager, Screen
+from kivy.adapters import listadapter
+from kivy import properties
+
+
+class ProfileItem(listview.ListItemButton):
+    pass
+
+
+class ProfileListAdapter(listadapter.ListAdapter):
+
+    def __init__(self, pm, *args, **kwargs):
+        super(ProfileListAdapter, self).__init__(*args, **kwargs)
+        self.pm = pm
+        self.host = pm.host
+
+    def closeUI(self, xmlui):
+        self.pm.screen_manager.transition.direction = 'right'
+        self.pm.screen_manager.current = 'profiles'
+
+    def showUI(self, xmlui):
+        xmlui.setCloseCb(self.closeUI)
+        if xmlui.type == 'popup':
+            xmlui.bind(on_touch_up=lambda obj, value: self.closeUI(xmlui))
+        self.pm.xmlui_screen.clear_widgets()
+        self.pm.xmlui_screen.add_widget(xmlui)
+        self.pm.screen_manager.transition.direction = 'left'
+        self.pm.screen_manager.current = 'xmlui'
+
+    def select_item_view(self, view):
+        def authenticate_cb(data, cb_id, profile):
+            if C.bool(data.pop('validated', C.BOOL_FALSE)):
+                super(ProfileListAdapter, self).select_item_view(view)
+            self.host.actionManager(data, callback=authenticate_cb, ui_show_cb=self.showUI, profile=profile)
+
+        self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=view.text)
+
+
+class ConnectButton(Button):
+
+    def __init__(self, profile_screen):
+        self.profile_screen = profile_screen
+        self.pm = profile_screen.pm
+        super(ConnectButton, self).__init__()
+
+
+class NewProfileScreen(Screen):
+    profile_name = properties.ObjectProperty(None)
+    jid = properties.ObjectProperty(None)
+    password = properties.ObjectProperty(None)
+    error_msg = properties.StringProperty('')
+
+    def __init__(self, pm):
+        super(NewProfileScreen, self).__init__(name=u'new_profile')
+        self.pm = pm
+        self.host = pm.host
+
+    def onCreationFailure(self, failure):
+        msg = [l for l in unicode(failure).split('\n') if l][-1]
+        self.error_msg = unicode(msg)
+
+    def onCreationSuccess(self, profile):
+        self.pm.profiles_screen.reload()
+        self.host.bridge.profileStartSession(self.password.text, profile, callback=lambda dummy: self._sessionStarted(profile), errback=self.onCreationFailure)
+
+    def _sessionStarted(self, profile):
+        jid = self.jid.text.strip()
+        self.host.bridge.setParam("JabberID", jid, "Connection", -1, profile)
+        self.host.bridge.setParam("Password", self.password.text, "Connection", -1, profile)
+        self.pm.screen_manager.transition.direction = 'right'
+        self.pm.screen_manager.current = 'profiles'
+
+    def doCreate(self):
+        name = self.profile_name.text.strip()
+        # XXX: we use XMPP password for profile password to simplify
+        #      if user want to change profile password, he can do it in preferences
+        self.host.bridge.asyncCreateProfile(name, self.password.text, callback=lambda: self.onCreationSuccess(name), errback=self.onCreationFailure)
+
+
+class DeleteProfilesScreen(Screen):
+
+    def __init__(self, pm):
+        self.pm = pm
+        self.host = pm.host
+        super(DeleteProfilesScreen, self).__init__(name=u'delete_profiles')
+
+    def doDelete(self):
+        """This method will delete *ALL* selected profiles"""
+        to_delete = self.pm.getProfiles()
+        deleted = [0]
+
+        def deleteInc():
+            deleted[0] += 1
+            if deleted[0] == len(to_delete):
+                self.pm.profiles_screen.reload()
+                self.pm.screen_manager.transition.direction = 'right'
+                self.pm.screen_manager.current = 'profiles'
+
+        for profile in to_delete:
+            log.info(u"Deleteing profile [{}]".format(profile))
+            self.host.bridge.asyncDeleteProfile(profile, callback=deleteInc, errback=deleteInc)
+
+
+class ProfilesScreen(Screen):
+    layout = properties.ObjectProperty(None)
+
+    def __init__(self, pm):
+        self.pm = pm
+        profiles = pm.host.bridge.getProfilesList()
+        profiles.sort()
+        self.list_adapter = ProfileListAdapter(pm,
+                                               data=profiles,
+                                               cls=ProfileItem,
+                                               selection_mode='multiple',
+                                               allow_empty_selection=True,
+                                              )
+        super(ProfilesScreen, self).__init__(name=u'profiles')
+        self.layout.add_widget(listview.ListView(adapter=self.list_adapter))
+        connect_btn = ConnectButton(self)
+        self.layout.add_widget(connect_btn)
+
+    def reload(self):
+        """Reload profiles list"""
+        profiles = self.pm.host.bridge.getProfilesList()
+        profiles.sort()
+        self.list_adapter.data = profiles
+
+
+class ProfileManager(QuickProfileManager, BoxLayout):
+
+    def __init__(self, host, autoconnect=None):
+        QuickProfileManager.__init__(self, host, autoconnect)
+        BoxLayout.__init__(self, orientation="vertical")
+        self.screen_manager = ScreenManager()
+        self.profiles_screen = ProfilesScreen(self)
+        self.new_profile_screen = NewProfileScreen(self)
+        self.delete_profiles_screen = DeleteProfilesScreen(self)
+        self.xmlui_screen = Screen(name=u'xmlui')
+        self.screen_manager.add_widget(self.profiles_screen)
+        self.screen_manager.add_widget(self.xmlui_screen)
+        self.screen_manager.add_widget(self.new_profile_screen)
+        self.screen_manager.add_widget(self.delete_profiles_screen)
+        self.add_widget(self.screen_manager)
+
+    def getProfiles(self):
+        return [pi.text for pi in self.profiles_screen.list_adapter.selection]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/core/widgets_handler.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,106 @@
+#!/usr/bin/python
+# -*- 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 import log as logging
+log = logging.getLogger(__name__)
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.button import Button
+from kivy import properties
+
+
+NEW_WIDGET_DIST = 10
+REMOVE_WIDGET_DIST = NEW_WIDGET_DIST
+
+
+class WHSplitter(Button):
+    horizontal=properties.BooleanProperty(True)
+    thickness=properties.NumericProperty(15)
+    split_move = None # we handle one split at a time, so we use a class attribute
+
+    def __init__(self, handler, **kwargs):
+        super(WHSplitter, self).__init__(**kwargs)
+        self.handler = handler
+
+    def getPos(self, touch):
+        if self.horizontal:
+            relative_y = self.handler.to_local(*touch.pos, relative=True)[1]
+            return self.handler.height - relative_y
+        else:
+            return touch.x
+
+    def on_touch_move(self, touch):
+        if self.split_move is None and self.collide_point(*touch.opos):
+            WHSplitter.split_move = self
+
+        if self.split_move is self:
+            pos = self.getPos(touch)
+            if pos > NEW_WIDGET_DIST:
+                # we are above minimal distance, we resize the widget
+                self.handler.setWidgetSize(self.horizontal, pos)
+
+    def on_touch_up(self, touch):
+        if self.split_move is self:
+            pos = self.getPos(touch)
+            if pos <= REMOVE_WIDGET_DIST:
+                # if we go under minimal distance, the widget is not wanted anymore
+                self.handler.removeWidget(self.horizontal)
+            WHSplitter.split_move=None
+        return super(WHSplitter, self).on_touch_up(touch)
+
+
+class WidgetsHandler(BoxLayout):
+
+    def __init__(self, host, wid=None, **kw):
+        self.host = host
+        if wid is None:
+            wid=self.default_widget
+        self.vert_wid = self.hor_wid = None
+        BoxLayout.__init__(self, orientation="vertical", **kw)
+        self.blh = BoxLayout(orientation="horizontal")
+        self.blv = BoxLayout(orientation="vertical")
+        self.blv.add_widget(WHSplitter(self))
+        self.blv.add_widget(wid)
+        self.blh.add_widget(WHSplitter(self, horizontal=False))
+        self.blh.add_widget(self.blv)
+        self.add_widget(self.blh)
+
+    @property
+    def default_widget(self):
+        return self.host.default_wid['factory'](self.host, self.host.default_wid, None, None)
+
+    def removeWidget(self, vertical):
+        if vertical and self.vert_wid is not None:
+            self.remove_widget(self.vert_wid)
+            self.vert_wid = None
+        elif self.hor_wid is not None:
+            self.blh.remove_widget(self.hor_wid)
+            self.hor_wid = None
+
+    def setWidgetSize(self, vertical, size):
+        if vertical:
+            if self.vert_wid is None:
+                self.vert_wid = WidgetsHandler(self.host, self.default_widget, size_hint=(1, None))
+                self.add_widget(self.vert_wid, len(self.children))
+            self.vert_wid.height=size
+        else:
+            if self.hor_wid is None:
+                self.hor_wid = WidgetsHandler(self.host, self.default_widget, size_hint=(None, 1))
+                self.blh.add_widget(self.hor_wid, len(self.blh.children))
+            self.hor_wid.width=size
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/core/xmlui.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,110 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Cagou: a SàT frontend
+# 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 _
+from sat_frontends.constants import Const as C
+from sat.core.log import getLogger
+log = getLogger(__name__)
+from sat_frontends.tools import xmlui
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.textinput import TextInput
+from kivy.uix.label import Label
+from kivy.uix.button import Button
+from kivy.uix.widget import Widget
+
+
+class TextWidget(xmlui.TextWidget, Label):
+
+    def __init__(self, xmlui_parent, value):
+        Label.__init__(self, text=value)
+
+
+class PasswordWidget(xmlui.PasswordWidget, TextInput):
+
+    def __init__(self, _xmlui_parent, value, read_only=False):
+        TextInput.__init__(self, password=True, multiline=False,
+            text=value, readonly=read_only, size=(100,25), size_hint=(1,None))
+
+    def _xmluiSetValue(self, value):
+        self.text = value
+
+    def _xmluiGetValue(self):
+        return self.text
+
+
+class VerticalContainer(xmlui.VerticalContainer, BoxLayout):
+
+    def __init__(self, xmlui_parent):
+        BoxLayout.__init__(self, orientation='vertical')
+
+    def _xmluiAppend(self, widget):
+        self.add_widget(widget)
+
+
+class WidgetFactory(object):
+
+    def __getattr__(self, attr):
+        if attr.startswith("create"):
+            cls = globals()[attr[6:]]
+            return cls
+
+
+class Title(Label):
+
+    def __init__(self, *args, **kwargs):
+        kwargs['size'] = (100, 25)
+        kwargs['size_hint'] = (1,None)
+        super(Title, self).__init__(*args, **kwargs)
+
+
+class XMLUIPanel(xmlui.XMLUIPanel, BoxLayout):
+    widget_factory = WidgetFactory()
+
+    def __init__(self, host, parsed_xml, title=None, flags=None, callback=None, profile=C.PROF_KEY_NONE):
+        self.close_cb = None
+        BoxLayout.__init__(self, orientation='vertical')
+        xmlui.XMLUIPanel.__init__(self, host, parsed_xml, title, flags, callback, profile)
+
+    def setCloseCb(self, close_cb):
+        self.close_cb = close_cb
+
+    def _xmluiClose(self):
+        if self.close_cb is not None:
+            self.close_cb(self)
+        else:
+            log.error(u"No close method defined")
+
+    def constructUI(self, parsed_dom):
+        xmlui.XMLUIPanel.constructUI(self, parsed_dom)
+        if self.xmlui_title:
+            self.add_widget(Title(text=self.xmlui_title))
+        self.add_widget(self.main_cont)
+        if self.type == 'form':
+            submit_btn = Button(text=_(u"Submit"), size_hint=(1,0.2))
+            submit_btn.bind(on_press=self.onFormSubmitted)
+            self.add_widget(submit_btn)
+            if not 'NO_CANCEL' in self.flags:
+                cancel_btn = Button(text=_(u"Cancel"), size_hint=(1,0.2))
+                cancel_btn.bind(on_press=self.onFormCancelled)
+                self.add_widget(cancel_btn)
+        self.add_widget(Widget()) # to have elements on the top
+
+
+xmlui.registerClass(xmlui.CLASS_PANEL, XMLUIPanel)
+create = xmlui.create
Binary file src/cagou/images/button.png has changed
Binary file src/cagou/images/button_selected.png has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/kv/cagou_widget.kv	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,36 @@
+# 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/>.
+
+
+<HeaderWidgetButton>:
+    size_hint_y: None
+    height: 44
+
+<HeaderWidgetSelector>:
+    size_hint: 0.3, 1
+    auto_width: False
+
+<CagouWidget>:
+    BoxLayout:
+        size_hint: 1, None
+        height: 30
+        Button:
+            text: "wid_XXX"
+            on_release: root.selector.open(self)
+            size_hint: None, 1
+            width: 60
+        TextInput:
+            multiline: False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/kv/profile_manager.kv	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,166 @@
+# 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/>.
+
+
+<ProfileManager>:
+    Label:
+        text: "Profile Manager"
+        size_hint: 1,0.05
+
+<PMLabel@Label>:
+    size_hint: 1, 0.1
+
+<PMInput@TextInput>:
+    multiline: False
+    size_hint: 1, 0.1
+    write_tab: False
+
+<PMButton@Button>:
+    size_hint: 1, 0.2
+
+
+<NewProfileScreen>:
+    profile_name: profile_name
+    jid: jid
+    password: password
+
+    BoxLayout:
+        orientation: "vertical"
+
+        Label:
+            text: "Creation of a new profile"
+            bold: True
+            size_hint: 1, 0.1
+        Label:
+            text: root.error_msg
+            bold: True
+            size_hint: 1, 0.1
+            color: 1,0,0,1
+        GridLayout:
+            cols: 2
+
+            PMLabel:
+                text: "Profile name"
+            PMInput:
+                id: profile_name
+
+            PMLabel:
+                text: "JID"
+            PMInput:
+                id: jid
+
+            PMLabel:
+                text: "Password"
+            PMInput:
+                id: password
+                password: True
+
+            Widget:
+                size_hint: 1, 0.2
+
+            Widget:
+                size_hint: 1, 0.2
+
+            PMButton:
+                text: "OK"
+                on_press: root.doCreate()
+
+            PMButton:
+                text: "Cancel"
+                on_press:
+                    root.pm.screen_manager.transition.direction = 'right'
+                    root.pm.screen_manager.current = 'profiles'
+
+            Widget:
+
+
+<DeleteProfilesScreen>:
+    BoxLayout:
+        orientation: "vertical"
+
+        Label:
+            text: "Are you sure you want to delete the following profiles?"
+            size_hint: 1, 0.1
+
+        Label:
+            text: u'\n'.join([i.text for i in root.pm.profiles_screen.list_adapter.selection])
+            bold: True
+
+        Label:
+            text: u'/!\\ WARNING: this operation is irreversible'
+            color: 1,0,0,1
+            bold: True
+            size_hint: 1, 0.2
+
+        GridLayout:
+            cols: 2
+
+            Button:
+                text: "Delete"
+                size_hint: 1, 0.2
+                on_press: root.doDelete()
+
+            Button:
+                text: "Cancel"
+                size_hint: 1, 0.2
+                on_press:
+                    root.pm.screen_manager.transition.direction = 'right'
+                    root.pm.screen_manager.current = 'profiles'
+
+            Widget:
+
+
+<ProfilesScreen>:
+    layout: layout
+    BoxLayout:
+        id: layout
+        orientation: 'vertical'
+
+        Label:
+            text: "Select a profile to connect with, or create a new one"
+            size_hint: 1,0.05
+
+        GridLayout:
+            cols: 2
+            size_hint: 1, 0.1
+            Button:
+                size_hint: 1, 0.1
+                text: "New"
+                on_press:
+                    root.pm.screen_manager.transition.direction = 'left'
+                    root.pm.screen_manager.current = 'new_profile'
+            Button:
+                disabled: not root.list_adapter.selection
+                text: "Delete"
+                size_hint: 1, 0.1
+                on_press:
+                    root.pm.screen_manager.transition.direction = 'left'
+                    root.pm.screen_manager.current = 'delete_profiles'
+
+
+<ConnectButton>:
+    text: "Connect"
+    size_hint: 1, 0.1
+    disabled: not self.profile_screen.list_adapter.selection
+    on_press: self.pm._onConnectProfiles()
+
+
+<ProfileItem>:
+    # FIXME: using cagou/images path for now, will use atlas later
+    background_normal: "cagou/images/button_selected.png" if self.is_selected else "cagou/images/button.png"
+    deselected_color: 1,1,1,1
+    selected_color: 1,1,1,1
+    color: 0,0,0,1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/kv/widgets_handler.kv	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,28 @@
+# 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/>.
+
+<WHSplitter>:
+    border: (3, 3, 3, 3)
+    horizontal_suff: '_h' if self.horizontal else ''
+    background_normal: 'atlas://data/images/defaulttheme/splitter{}{}'.format('_disabled' if self.disabled else '', self.horizontal_suff)
+    background_down: 'atlas://data/images/defaulttheme/splitter_down{}{}'.format('_disabled' if self.disabled else '', self.horizontal_suff)
+    size_hint: (1, None) if self.horizontal else (None, 1)
+    size: (100, self.thickness) if self.horizontal else (self.thickness, 100)
+    Image:
+        pos: root.pos
+        size: root.size
+        allow_stretch: True
+        source: 'atlas://data/images/defaulttheme/splitter_grip' + root.horizontal_suff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/plugins/plugin_wid_contact_list.kv	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,27 @@
+# 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/>.
+
+<ContactItem>:
+    spacing: 20
+    Widget:
+    Avatar:
+        source: root.data.get('avatar', app.default_avatar)
+        size_hint: (None, None)
+    Label:
+        text: root.jid
+        bold: True
+        size_hint: (None, None)
+    Widget:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/plugins/plugin_wid_contact_list.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+# -*- 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 import log as logging
+log = logging.getLogger(__name__)
+from sat.core.i18n import _
+from sat_frontends.quick_frontend.quick_contact_list import QuickContactList
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.listview import ListView
+from kivy.adapters.listadapter import ListAdapter
+from kivy import properties
+from cagou.core import cagou_widget
+from cagou.core import image
+
+
+PLUGIN_INFO = {
+    "name": _(u"contacts"),
+    "main": "ContactList",
+    "description": _(u"list of contacts"),
+}
+
+
+class Avatar(image.Image):
+    pass
+
+
+class ContactItem(BoxLayout):
+    data = properties.DictProperty()
+    jid = properties.StringProperty('')
+
+    def __init__(self, **kwargs):
+        BoxLayout.__init__(self, **kwargs)
+
+
+class ContactList(QuickContactList, cagou_widget.CagouWidget):
+
+    def __init__(self, host, target, profiles):
+        QuickContactList.__init__(self, host, profiles)
+        cagou_widget.CagouWidget.__init__(self, host)
+        self.adapter = ListAdapter(data={},
+                                   cls=ContactItem,
+                                   args_converter=self.contactDataConverter,
+                                   selection_mode='multiple',
+                                   allow_empty_selection=True,
+                                  )
+        self.add_widget(ListView(adapter=self.adapter))
+        self.update()
+
+    def contactDataConverter(self, idx, bare_jid):
+        return {"jid": bare_jid, "data": self._items_cache[bare_jid]}
+
+    def update(self, entities=None, type_=None, profile=None):
+        log.info("update: %s %s %s" % (entities, type_, profile))
+        # FIXME: for now we update on each event
+        # if entities is None and type_ is None:
+        self._items_cache = self.items
+        self.adapter.data = self.items_sorted
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/plugins/plugin_wid_widget_selector.kv	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,24 @@
+# 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/>.
+
+<WidgetSelItem>:
+    spacing: 20
+    Widget:
+    Label:
+        text: root.plugin_info["name"]
+        bold: True
+        size_hint: (None, None)
+    Widget:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cagou/plugins/plugin_wid_widget_selector.py	Sat Jul 09 17:24:01 2016 +0200
@@ -0,0 +1,82 @@
+#!/usr/bin/python
+# -*- 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 import log as logging
+log = logging.getLogger(__name__)
+from sat.core.i18n import _
+from cagou.core.constants import Const as C
+from kivy.uix.boxlayout import BoxLayout
+from kivy.uix.listview import ListView
+from kivy.adapters.listadapter import ListAdapter
+from kivy import properties
+from kivy.uix.behaviors import ButtonBehavior
+from cagou.core import cagou_widget
+
+
+PLUGIN_INFO = {
+    "name": _(u"widget selector"),
+    "import_name": C.WID_SELECTOR,
+    "main": "WidgetSelector",
+    "description": _(u"show available widgets and allow to select one"),
+}
+
+
+class WidgetSelItem(ButtonBehavior, BoxLayout):
+    plugin_info = properties.DictProperty()
+
+    def __init__(self, **kwargs):
+        super(WidgetSelItem, self).__init__(**kwargs)
+        self.host = kwargs['host']
+
+    def select(self, *args):
+        log.debug(u"widget selection: {}".format(self.plugin_info["name"]))
+        factory = self.plugin_info["factory"]
+        self.host.switchWidget(self, factory(self.host, self.plugin_info, None, None))
+
+    def deselect(self, *args):
+        pass
+
+
+class WidgetSelector(cagou_widget.CagouWidget):
+
+    def __init__(self, host):
+        super(WidgetSelector, self).__init__(host)
+        self.host = host
+        self.adapter = ListAdapter(
+            data=host.getPluggedWidgets(except_cls=self.__class__),
+            cls=WidgetSelItem,
+            args_converter=self.dataConverter,
+            selection_mode='single',
+            allow_empty_selection=True,
+           )
+        self.add_widget(ListView(adapter=self.adapter))
+
+    @classmethod
+    def factory(cls, host, plugin_info, target, profiles):
+        return cls(host)
+
+    def dataConverter(self, idx, plugin_info):
+        return {
+            "host": self.host,
+            "plugin_info": plugin_info,
+            }
+
+
+PLUGIN_INFO["factory"] = WidgetSelector.factory
--- a/src/cagou_widget.kv	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-# 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/>.
-
-
-<HeaderWidgetButton>:
-    size_hint_y: None
-    height: 44
-
-<HeaderWidgetSelector>:
-    size_hint: 0.3, 1
-    auto_width: False
-
-<CagouWidget>:
-    BoxLayout:
-        size_hint: 1, None
-        height: 30
-        Button:
-            text: "wid_XXX"
-            on_release: root.selector.open(self)
-            size_hint: None, 1
-            width: 60
-        TextInput:
-            multiline: False
--- a/src/cagou_widget.py	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-#!/usr/bin/python
-# -*- 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 import log as logging
-log = logging.getLogger(__name__)
-from kivy.uix.button import Button
-from kivy.uix.boxlayout import BoxLayout
-from kivy.uix.dropdown import DropDown
-
-
-class HeaderWidgetButton(Button):
-    pass
-
-
-class HeaderWidgetSelector(DropDown):
-
-    def __init__(self, cagou_widget):
-        super(HeaderWidgetSelector, self).__init__()
-        host = cagou_widget.host
-        for plugin_info in host.getPluggedWidgets(except_cls=cagou_widget.__class__):
-            btn = HeaderWidgetButton(text=plugin_info["name"])
-            btn.bind(on_release=lambda btn, plugin_info=plugin_info: cagou_widget.switchWidget(plugin_info))
-            self.add_widget(btn)
-
-
-class CagouWidget(BoxLayout):
-
-    def __init__(self, host):
-        self.host = host
-        BoxLayout.__init__(self, orientation="vertical")
-        self.selector = HeaderWidgetSelector(self)
-
-    def switchWidget(self, plugin_info):
-        self.selector.dismiss()
-        factory = plugin_info["factory"]
-        new_widget = factory(self.host, plugin_info, None, None)
-        self.host.switchWidget(self, new_widget)
--- a/src/config.py	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-#!/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/>.
-
-"""This module keep an open instance of sat configuration"""
-
-from sat.tools import config
-sat_conf = config.parseMainConf()
-
-
-def getConfig(section, name, default):
-    return config.getConfig(sat_conf, section, name, default)
--- a/src/constants.py	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Primitivus: a SAT frontend
-# Copyright (C) 2009-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_frontends.quick_frontend import constants
-
-
-class Const(constants.Const):
-    APP_NAME = "Cagou"
-    LOG_OPT_SECTION = APP_NAME.lower()
-    CONFIG_SECTION = APP_NAME.lower()
-    WID_SELECTOR = 'selector'
--- a/src/image.py	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,73 +0,0 @@
-#!/usr/bin/python
-# -*- 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 import log as logging
-log = logging.getLogger(__name__)
-from kivy.uix import image as kivy_img
-from kivy.core.image import Image as CoreImage
-from kivy.resources import resource_find
-import io
-import PIL
-
-
-class Image(kivy_img.Image):
-    """Image widget which accept source without extension"""
-
-    def texture_update(self, *largs):
-        if not self.source:
-            self.texture = None
-        else:
-            filename = resource_find(self.source)
-            self._loops = 0
-            if filename is None:
-                return log.error('Image: Error reading file {filename}'.
-                                    format(filename=self.source))
-            mipmap = self.mipmap
-            if self._coreimage is not None:
-                self._coreimage.unbind(on_texture=self._on_tex_change)
-            try:
-                self._coreimage = ci = CoreImage(filename, mipmap=mipmap,
-                                                 anim_delay=self.anim_delay,
-                                                 keep_data=self.keep_data,
-                                                 nocache=self.nocache)
-            except Exception as e:
-                # loading failed probably because of unmanaged extention,
-                # we try our luck with with PIL
-                try:
-                    im = PIL.Image.open(filename)
-                    ext = im.format.lower()
-                    del im
-                    # we can't use im.tobytes as it would use the
-                    # internal decompressed representation from pillow
-                    # and im.save would need processing to handle format
-                    data = io.BytesIO(open(filename, "rb").read())
-                    cache_filename = u"{}.{}".format(filename,ext) # needed for kivy's Image to use cache
-                    self._coreimage = ci = CoreImage(data, ext=ext,
-                                                     filename=cache_filename, mipmap=mipmap,
-                                                     anim_delay=self.anim_delay,
-                                                     keep_data=self.keep_data,
-                                                     nocache=self.nocache)
-                except Exception as e:
-                    log.warning(u"Can't load image: {}".format(e))
-                    self._coreimage = ci = None
-
-            if ci:
-                ci.bind(on_texture=self._on_tex_change)
-                self.texture = ci.texture
--- a/src/logging_setter.py	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-#!/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/>.
-
-CONF_KIVY_LEVEL = 'log_kivy_level'
-
-def set_logging():
-    from constants import Const as C
-    from sat.core import log_config
-    log_config.satConfigure(C.LOG_BACKEND_STANDARD, C)
-
-    import config
-    kivy_level = config.getConfig(C.CONFIG_SECTION, CONF_KIVY_LEVEL, 'follow').upper()
-
-    # kivy handles its own loggers, we don't want that!
-    import logging
-    root_logger = logging.root
-    kivy_logger = logging.getLogger('kivy')
-    ori_addHandler = kivy_logger.addHandler
-    kivy_logger.addHandler = lambda dummy: None
-    ori_setLevel = kivy_logger.setLevel
-    if kivy_level == 'FOLLOW':
-        # level is following SàT level
-        kivy_logger.setLevel = lambda level: None
-    elif kivy_level == 'KIVY':
-        # level will be set by Kivy according to its own conf
-        pass
-    elif kivy_level in C.LOG_LEVELS:
-        kivy_logger.setLevel(kivy_level)
-        kivy_logger.setLevel = lambda level: None
-    else:
-        raise ValueError(u"Unknown value for {name}: {value}".format(name=CONF_KIVY_LEVEL, value=kivy_level))
-
-    # during import kivy set its logging stuff
-    import kivy
-    kivy # to avoid pyflakes warning
-
-    # we want to separate kivy logs from other logs
-    logging.root = root_logger
-    import sys
-    from kivy import logger
-    sys.stderr = logger.previous_stderr
-
-    # we restore original methods
-    kivy_logger.addHandler = ori_addHandler
-    kivy_logger.setLevel = ori_setLevel
--- a/src/plugin_wid_contact_list.kv	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-# 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/>.
-
-<ContactItem>:
-    spacing: 20
-    Widget:
-    Avatar:
-        source: root.data.get('avatar', app.default_avatar)
-        size_hint: (None, None)
-    Label:
-        text: root.jid
-        bold: True
-        size_hint: (None, None)
-    Widget:
--- a/src/plugin_wid_contact_list.py	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +0,0 @@
-#!/usr/bin/python
-# -*- 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 import log as logging
-log = logging.getLogger(__name__)
-from sat.core.i18n import _
-from sat_frontends.quick_frontend.quick_contact_list import QuickContactList
-from kivy.uix.boxlayout import BoxLayout
-from kivy.uix.listview import ListView
-from kivy.adapters.listadapter import ListAdapter
-from kivy import properties
-from cagou_widget import CagouWidget
-import image
-
-
-PLUGIN_INFO = {
-    "name": _(u"contacts"),
-    "main": "ContactList",
-    "description": _(u"list of contacts"),
-}
-
-
-class Avatar(image.Image):
-    pass
-
-
-class ContactItem(BoxLayout):
-    data = properties.DictProperty()
-    jid = properties.StringProperty('')
-
-    def __init__(self, **kwargs):
-        BoxLayout.__init__(self, **kwargs)
-
-
-class ContactList(QuickContactList, CagouWidget):
-
-    def __init__(self, host, target, profiles):
-        QuickContactList.__init__(self, host, profiles)
-        CagouWidget.__init__(self, host)
-        self.adapter = ListAdapter(data={},
-                                   cls=ContactItem,
-                                   args_converter=self.contactDataConverter,
-                                   selection_mode='multiple',
-                                   allow_empty_selection=True,
-                                  )
-        self.add_widget(ListView(adapter=self.adapter))
-        self.update()
-
-    def contactDataConverter(self, idx, bare_jid):
-        return {"jid": bare_jid, "data": self._items_cache[bare_jid]}
-
-    def update(self, entities=None, type_=None, profile=None):
-        log.info("update: %s %s %s" % (entities, type_, profile))
-        # FIXME: for now we update on each event
-        # if entities is None and type_ is None:
-        self._items_cache = self.items
-        self.adapter.data = self.items_sorted
-
--- a/src/plugin_wid_widget_selector.kv	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-# 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/>.
-
-<WidgetSelItem>:
-    spacing: 20
-    Widget:
-    Label:
-        text: root.plugin_info["name"]
-        bold: True
-        size_hint: (None, None)
-    Widget:
--- a/src/plugin_wid_widget_selector.py	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-#!/usr/bin/python
-# -*- 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 import log as logging
-log = logging.getLogger(__name__)
-from sat.core.i18n import _
-from constants import Const as C
-from kivy.uix.boxlayout import BoxLayout
-from kivy.uix.listview import ListView
-from kivy.adapters.listadapter import ListAdapter
-from kivy import properties
-from kivy.uix.behaviors import ButtonBehavior
-from cagou_widget import CagouWidget
-
-
-PLUGIN_INFO = {
-    "name": _(u"widget selector"),
-    "import_name": C.WID_SELECTOR,
-    "main": "WidgetSelector",
-    "description": _(u"show available widgets and allow to select one"),
-}
-
-
-class WidgetSelItem(ButtonBehavior, BoxLayout):
-    plugin_info = properties.DictProperty()
-
-    def __init__(self, **kwargs):
-        super(WidgetSelItem, self).__init__(**kwargs)
-        self.host = kwargs['host']
-
-    def select(self, *args):
-        log.debug(u"widget selection: {}".format(self.plugin_info["name"]))
-        factory = self.plugin_info["factory"]
-        self.host.switchWidget(self, factory(self.host, self.plugin_info, None, None))
-
-    def deselect(self, *args):
-        pass
-
-
-class WidgetSelector(CagouWidget):
-
-    def __init__(self, host):
-        super(WidgetSelector, self).__init__(host)
-        self.host = host
-        self.adapter = ListAdapter(
-            data=host.getPluggedWidgets(except_cls=self.__class__),
-            cls=WidgetSelItem,
-            args_converter=self.dataConverter,
-            selection_mode='single',
-            allow_empty_selection=True,
-           )
-        self.add_widget(ListView(adapter=self.adapter))
-
-    @classmethod
-    def factory(cls, host, plugin_info, target, profiles):
-        return cls(host)
-
-    def dataConverter(self, idx, plugin_info):
-        return {
-            "host": self.host,
-            "plugin_info": plugin_info,
-            }
-
-
-PLUGIN_INFO["factory"] = WidgetSelector.factory
--- a/src/profile_manager.kv	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,165 +0,0 @@
-# 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/>.
-
-
-<ProfileManager>:
-    Label:
-        text: "Profile Manager"
-        size_hint: 1,0.05
-
-<PMLabel@Label>:
-    size_hint: 1, 0.1
-
-<PMInput@TextInput>:
-    multiline: False
-    size_hint: 1, 0.1
-    write_tab: False
-
-<PMButton@Button>:
-    size_hint: 1, 0.2
-
-
-<NewProfileScreen>:
-    profile_name: profile_name
-    jid: jid
-    password: password
-
-    BoxLayout:
-        orientation: "vertical"
-
-        Label:
-            text: "Creation of a new profile"
-            bold: True
-            size_hint: 1, 0.1
-        Label:
-            text: root.error_msg
-            bold: True
-            size_hint: 1, 0.1
-            color: 1,0,0,1
-        GridLayout:
-            cols: 2
-
-            PMLabel:
-                text: "Profile name"
-            PMInput:
-                id: profile_name
-
-            PMLabel:
-                text: "JID"
-            PMInput:
-                id: jid
-
-            PMLabel:
-                text: "Password"
-            PMInput:
-                id: password
-                password: True
-
-            Widget:
-                size_hint: 1, 0.2
-
-            Widget:
-                size_hint: 1, 0.2
-
-            PMButton:
-                text: "OK"
-                on_press: root.doCreate()
-
-            PMButton:
-                text: "Cancel"
-                on_press:
-                    root.pm.screen_manager.transition.direction = 'right'
-                    root.pm.screen_manager.current = 'profiles'
-
-            Widget:
-
-
-<DeleteProfilesScreen>:
-    BoxLayout:
-        orientation: "vertical"
-
-        Label:
-            text: "Are you sure you want to delete the following profiles?"
-            size_hint: 1, 0.1
-
-        Label:
-            text: u'\n'.join([i.text for i in root.pm.profiles_screen.list_adapter.selection])
-            bold: True
-
-        Label:
-            text: u'/!\\ WARNING: this operation is irreversible'
-            color: 1,0,0,1
-            bold: True
-            size_hint: 1, 0.2
-
-        GridLayout:
-            cols: 2
-
-            Button:
-                text: "Delete"
-                size_hint: 1, 0.2
-                on_press: root.doDelete()
-
-            Button:
-                text: "Cancel"
-                size_hint: 1, 0.2
-                on_press:
-                    root.pm.screen_manager.transition.direction = 'right'
-                    root.pm.screen_manager.current = 'profiles'
-
-            Widget:
-
-
-<ProfilesScreen>:
-    layout: layout
-    BoxLayout:
-        id: layout
-        orientation: 'vertical'
-
-        Label:
-            text: "Select a profile to connect with, or create a new one"
-            size_hint: 1,0.05
-
-        GridLayout:
-            cols: 2
-            size_hint: 1, 0.1
-            Button:
-                size_hint: 1, 0.1
-                text: "New"
-                on_press:
-                    root.pm.screen_manager.transition.direction = 'left'
-                    root.pm.screen_manager.current = 'new_profile'
-            Button:
-                disabled: not root.list_adapter.selection
-                text: "Delete"
-                size_hint: 1, 0.1
-                on_press:
-                    root.pm.screen_manager.transition.direction = 'left'
-                    root.pm.screen_manager.current = 'delete_profiles'
-
-
-<ConnectButton>:
-    text: "Connect"
-    size_hint: 1, 0.1
-    disabled: not self.profile_screen.list_adapter.selection
-    on_press: self.pm._onConnectProfiles()
-
-
-<ProfileItem>:
-    background_normal: "button_selected.png" if self.is_selected else "button.png"
-    deselected_color: 1,1,1,1
-    selected_color: 1,1,1,1
-    color: 0,0,0,1
--- a/src/profile_manager.py	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,173 +0,0 @@
-#!/usr/bin/python
-# -*- 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 import log as logging
-log = logging.getLogger(__name__)
-from sat_frontends.constants import Const as C
-from sat_frontends.quick_frontend.quick_profile_manager import QuickProfileManager
-from kivy.uix.boxlayout import BoxLayout
-from kivy.uix import listview
-from kivy.uix.button import Button
-from kivy.uix.screenmanager import ScreenManager, Screen
-from kivy.adapters import listadapter
-from kivy import properties
-
-
-class ProfileItem(listview.ListItemButton):
-    pass
-
-
-class ProfileListAdapter(listadapter.ListAdapter):
-
-    def __init__(self, pm, *args, **kwargs):
-        super(ProfileListAdapter, self).__init__(*args, **kwargs)
-        self.pm = pm
-        self.host = pm.host
-
-    def closeUI(self, xmlui):
-        self.pm.screen_manager.transition.direction = 'right'
-        self.pm.screen_manager.current = 'profiles'
-
-    def showUI(self, xmlui):
-        xmlui.setCloseCb(self.closeUI)
-        if xmlui.type == 'popup':
-            xmlui.bind(on_touch_up=lambda obj, value: self.closeUI(xmlui))
-        self.pm.xmlui_screen.clear_widgets()
-        self.pm.xmlui_screen.add_widget(xmlui)
-        self.pm.screen_manager.transition.direction = 'left'
-        self.pm.screen_manager.current = 'xmlui'
-
-    def select_item_view(self, view):
-        def authenticate_cb(data, cb_id, profile):
-            if C.bool(data.pop('validated', C.BOOL_FALSE)):
-                super(ProfileListAdapter, self).select_item_view(view)
-            self.host.actionManager(data, callback=authenticate_cb, ui_show_cb=self.showUI, profile=profile)
-
-        self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, callback=authenticate_cb, profile=view.text)
-
-
-class ConnectButton(Button):
-
-    def __init__(self, profile_screen):
-        self.profile_screen = profile_screen
-        self.pm = profile_screen.pm
-        super(ConnectButton, self).__init__()
-
-
-class NewProfileScreen(Screen):
-    profile_name = properties.ObjectProperty(None)
-    jid = properties.ObjectProperty(None)
-    password = properties.ObjectProperty(None)
-    error_msg = properties.StringProperty('')
-
-    def __init__(self, pm):
-        super(NewProfileScreen, self).__init__(name=u'new_profile')
-        self.pm = pm
-        self.host = pm.host
-
-    def onCreationFailure(self, failure):
-        msg = [l for l in unicode(failure).split('\n') if l][-1]
-        self.error_msg = unicode(msg)
-
-    def onCreationSuccess(self, profile):
-        self.pm.profiles_screen.reload()
-        self.host.bridge.profileStartSession(self.password.text, profile, callback=lambda dummy: self._sessionStarted(profile), errback=self.onCreationFailure)
-
-    def _sessionStarted(self, profile):
-        jid = self.jid.text.strip()
-        self.host.bridge.setParam("JabberID", jid, "Connection", -1, profile)
-        self.host.bridge.setParam("Password", self.password.text, "Connection", -1, profile)
-        self.pm.screen_manager.transition.direction = 'right'
-        self.pm.screen_manager.current = 'profiles'
-
-    def doCreate(self):
-        name = self.profile_name.text.strip()
-        # XXX: we use XMPP password for profile password to simplify
-        #      if user want to change profile password, he can do it in preferences
-        self.host.bridge.asyncCreateProfile(name, self.password.text, callback=lambda: self.onCreationSuccess(name), errback=self.onCreationFailure)
-
-
-class DeleteProfilesScreen(Screen):
-
-    def __init__(self, pm):
-        self.pm = pm
-        self.host = pm.host
-        super(DeleteProfilesScreen, self).__init__(name=u'delete_profiles')
-
-    def doDelete(self):
-        """This method will delete *ALL* selected profiles"""
-        to_delete = self.pm.getProfiles()
-        deleted = [0]
-
-        def deleteInc():
-            deleted[0] += 1
-            if deleted[0] == len(to_delete):
-                self.pm.profiles_screen.reload()
-                self.pm.screen_manager.transition.direction = 'right'
-                self.pm.screen_manager.current = 'profiles'
-
-        for profile in to_delete:
-            log.info(u"Deleteing profile [{}]".format(profile))
-            self.host.bridge.asyncDeleteProfile(profile, callback=deleteInc, errback=deleteInc)
-
-
-class ProfilesScreen(Screen):
-    layout = properties.ObjectProperty(None)
-
-    def __init__(self, pm):
-        self.pm = pm
-        profiles = pm.host.bridge.getProfilesList()
-        profiles.sort()
-        self.list_adapter = ProfileListAdapter(pm,
-                                               data=profiles,
-                                               cls=ProfileItem,
-                                               selection_mode='multiple',
-                                               allow_empty_selection=True,
-                                              )
-        super(ProfilesScreen, self).__init__(name=u'profiles')
-        self.layout.add_widget(listview.ListView(adapter=self.list_adapter))
-        connect_btn = ConnectButton(self)
-        self.layout.add_widget(connect_btn)
-
-    def reload(self):
-        """Reload profiles list"""
-        profiles = self.pm.host.bridge.getProfilesList()
-        profiles.sort()
-        self.list_adapter.data = profiles
-
-
-class ProfileManager(QuickProfileManager, BoxLayout):
-
-    def __init__(self, host, autoconnect=None):
-        QuickProfileManager.__init__(self, host, autoconnect)
-        BoxLayout.__init__(self, orientation="vertical")
-        self.screen_manager = ScreenManager()
-        self.profiles_screen = ProfilesScreen(self)
-        self.new_profile_screen = NewProfileScreen(self)
-        self.delete_profiles_screen = DeleteProfilesScreen(self)
-        self.xmlui_screen = Screen(name=u'xmlui')
-        self.screen_manager.add_widget(self.profiles_screen)
-        self.screen_manager.add_widget(self.xmlui_screen)
-        self.screen_manager.add_widget(self.new_profile_screen)
-        self.screen_manager.add_widget(self.delete_profiles_screen)
-        self.add_widget(self.screen_manager)
-
-    def getProfiles(self):
-        return [pi.text for pi in self.profiles_screen.list_adapter.selection]
--- a/src/widgets_handler.kv	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,28 +0,0 @@
-# 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/>.
-
-<WHSplitter>:
-    border: (3, 3, 3, 3)
-    horizontal_suff: '_h' if self.horizontal else ''
-    background_normal: 'atlas://data/images/defaulttheme/splitter{}{}'.format('_disabled' if self.disabled else '', self.horizontal_suff)
-    background_down: 'atlas://data/images/defaulttheme/splitter_down{}{}'.format('_disabled' if self.disabled else '', self.horizontal_suff)
-    size_hint: (1, None) if self.horizontal else (None, 1)
-    size: (100, self.thickness) if self.horizontal else (self.thickness, 100)
-    Image:
-        pos: root.pos
-        size: root.size
-        allow_stretch: True
-        source: 'atlas://data/images/defaulttheme/splitter_grip' + root.horizontal_suff
--- a/src/widgets_handler.py	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,106 +0,0 @@
-#!/usr/bin/python
-# -*- 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 import log as logging
-log = logging.getLogger(__name__)
-from kivy.uix.boxlayout import BoxLayout
-from kivy.uix.button import Button
-from kivy import properties
-
-
-NEW_WIDGET_DIST = 10
-REMOVE_WIDGET_DIST = NEW_WIDGET_DIST
-
-
-class WHSplitter(Button):
-    horizontal=properties.BooleanProperty(True)
-    thickness=properties.NumericProperty(15)
-    split_move = None # we handle one split at a time, so we use a class attribute
-
-    def __init__(self, handler, **kwargs):
-        super(WHSplitter, self).__init__(**kwargs)
-        self.handler = handler
-
-    def getPos(self, touch):
-        if self.horizontal:
-            relative_y = self.handler.to_local(*touch.pos, relative=True)[1]
-            return self.handler.height - relative_y
-        else:
-            return touch.x
-
-    def on_touch_move(self, touch):
-        if self.split_move is None and self.collide_point(*touch.opos):
-            WHSplitter.split_move = self
-
-        if self.split_move is self:
-            pos = self.getPos(touch)
-            if pos > NEW_WIDGET_DIST:
-                # we are above minimal distance, we resize the widget
-                self.handler.setWidgetSize(self.horizontal, pos)
-
-    def on_touch_up(self, touch):
-        if self.split_move is self:
-            pos = self.getPos(touch)
-            if pos <= REMOVE_WIDGET_DIST:
-                # if we go under minimal distance, the widget is not wanted anymore
-                self.handler.removeWidget(self.horizontal)
-            WHSplitter.split_move=None
-        return super(WHSplitter, self).on_touch_up(touch)
-
-
-class WidgetsHandler(BoxLayout):
-
-    def __init__(self, host, wid=None, **kw):
-        self.host = host
-        if wid is None:
-            wid=self.default_widget
-        self.vert_wid = self.hor_wid = None
-        BoxLayout.__init__(self, orientation="vertical", **kw)
-        self.blh = BoxLayout(orientation="horizontal")
-        self.blv = BoxLayout(orientation="vertical")
-        self.blv.add_widget(WHSplitter(self))
-        self.blv.add_widget(wid)
-        self.blh.add_widget(WHSplitter(self, horizontal=False))
-        self.blh.add_widget(self.blv)
-        self.add_widget(self.blh)
-
-    @property
-    def default_widget(self):
-        return self.host.default_wid['factory'](self.host, self.host.default_wid, None, None)
-
-    def removeWidget(self, vertical):
-        if vertical and self.vert_wid is not None:
-            self.remove_widget(self.vert_wid)
-            self.vert_wid = None
-        elif self.hor_wid is not None:
-            self.blh.remove_widget(self.hor_wid)
-            self.hor_wid = None
-
-    def setWidgetSize(self, vertical, size):
-        if vertical:
-            if self.vert_wid is None:
-                self.vert_wid = WidgetsHandler(self.host, self.default_widget, size_hint=(1, None))
-                self.add_widget(self.vert_wid, len(self.children))
-            self.vert_wid.height=size
-        else:
-            if self.hor_wid is None:
-                self.hor_wid = WidgetsHandler(self.host, self.default_widget, size_hint=(None, 1))
-                self.blh.add_widget(self.hor_wid, len(self.blh.children))
-            self.hor_wid.width=size
--- a/src/xmlui.py	Sat Jul 09 16:02:44 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-# Cagou: a SàT frontend
-# 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 _
-from sat_frontends.constants import Const as C
-from sat.core.log import getLogger
-log = getLogger(__name__)
-from sat_frontends.tools import xmlui
-from kivy.uix.boxlayout import BoxLayout
-from kivy.uix.textinput import TextInput
-from kivy.uix.label import Label
-from kivy.uix.button import Button
-from kivy.uix.widget import Widget
-
-
-class TextWidget(xmlui.TextWidget, Label):
-
-    def __init__(self, xmlui_parent, value):
-        Label.__init__(self, text=value)
-
-
-class PasswordWidget(xmlui.PasswordWidget, TextInput):
-
-    def __init__(self, _xmlui_parent, value, read_only=False):
-        TextInput.__init__(self, password=True, multiline=False,
-            text=value, readonly=read_only, size=(100,25), size_hint=(1,None))
-
-    def _xmluiSetValue(self, value):
-        self.text = value
-
-    def _xmluiGetValue(self):
-        return self.text
-
-
-class VerticalContainer(xmlui.VerticalContainer, BoxLayout):
-
-    def __init__(self, xmlui_parent):
-        BoxLayout.__init__(self, orientation='vertical')
-
-    def _xmluiAppend(self, widget):
-        self.add_widget(widget)
-
-
-class WidgetFactory(object):
-
-    def __getattr__(self, attr):
-        if attr.startswith("create"):
-            cls = globals()[attr[6:]]
-            return cls
-
-
-class Title(Label):
-
-    def __init__(self, *args, **kwargs):
-        kwargs['size'] = (100, 25)
-        kwargs['size_hint'] = (1,None)
-        super(Title, self).__init__(*args, **kwargs)
-
-
-class XMLUIPanel(xmlui.XMLUIPanel, BoxLayout):
-    widget_factory = WidgetFactory()
-
-    def __init__(self, host, parsed_xml, title=None, flags=None, callback=None, profile=C.PROF_KEY_NONE):
-        self.close_cb = None
-        BoxLayout.__init__(self, orientation='vertical')
-        xmlui.XMLUIPanel.__init__(self, host, parsed_xml, title, flags, callback, profile)
-
-    def setCloseCb(self, close_cb):
-        self.close_cb = close_cb
-
-    def _xmluiClose(self):
-        if self.close_cb is not None:
-            self.close_cb(self)
-        else:
-            log.error(u"No close method defined")
-
-    def constructUI(self, parsed_dom):
-        xmlui.XMLUIPanel.constructUI(self, parsed_dom)
-        if self.xmlui_title:
-            self.add_widget(Title(text=self.xmlui_title))
-        self.add_widget(self.main_cont)
-        if self.type == 'form':
-            submit_btn = Button(text=_(u"Submit"), size_hint=(1,0.2))
-            submit_btn.bind(on_press=self.onFormSubmitted)
-            self.add_widget(submit_btn)
-            if not 'NO_CANCEL' in self.flags:
-                cancel_btn = Button(text=_(u"Cancel"), size_hint=(1,0.2))
-                cancel_btn.bind(on_press=self.onFormCancelled)
-                self.add_widget(cancel_btn)
-        self.add_widget(Widget()) # to have elements on the top
-
-
-xmlui.registerClass(xmlui.CLASS_PANEL, XMLUIPanel)
-create = xmlui.create