changeset 14:21a432afd06d

plugin system, first draft: - widgets are now handled with plugins, ContactList and WidgetSelector have been changed to be pluggins - everything in PLUGIN_INFO (including PLUGIN_INFO itself) is optional. Best guess is used if a value is missing - WidgetsHandler use default widget from plugins, which is WidgetSelector for now - PLUGIN_INFO is used in the same way as for backed plugins, with (for now) following possible keys: - "name": human readable name - "description": long description of what widget do - "import_name": unique short name used as reference for import - "main": main class name - "factory": callback used to instanciate a widget (see Cagou._defaultFactory) - "kv_file": path to the kv language file to load (same filename with ".kv" will be used if key is not found) other data should be added quickly, as a path to a file used as icon - host.getPluggedWidgets can be used to find loaded widgets. except_cls can be used to excluse a class (self.__class__ usually) - fixed host.switchWidget when old widget is already a CagouWidget - CagouWidget header new display the available widgets
author Goffi <goffi@goffi.org>
date Sat, 09 Jul 2016 16:02:44 +0200
parents 12a189fbb9ba
children 56838ad5c84b
files src/cagou.kv src/cagou.py src/cagou_widget.kv src/cagou_widget.py src/constants.py src/contact_list.kv src/contact_list.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/widget_selector.kv src/widget_selector.py src/widgets_handler.py
diffstat 14 files changed, 342 insertions(+), 213 deletions(-) [+]
line wrap: on
line diff
--- a/src/cagou.kv	Fri Jul 08 20:18:43 2016 +0200
+++ b/src/cagou.kv	Sat Jul 09 16:02:44 2016 +0200
@@ -15,7 +15,5 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 #:include profile_manager.kv
-#:include contact_list.kv
 #:include widgets_handler.kv
 #:include cagou_widget.kv
-#:include widget_selector.kv
--- a/src/cagou.py	Fri Jul 08 20:18:43 2016 +0200
+++ b/src/cagou.py	Sat Jul 09 16:02:44 2016 +0200
@@ -18,6 +18,7 @@
 # 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
@@ -31,13 +32,15 @@
 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 widget_selector import WidgetSelector
 from cagou_widget import CagouWidget
+from importlib import import_module
 import os.path
+import glob
 
 
 class CagouRootWidget(BoxLayout):
@@ -69,13 +72,88 @@
         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 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):
-        widget_selector = WidgetSelector(self)
-        self.app.root.change_widgets([WidgetsHandler(self, widget_selector)])
+        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))
@@ -86,13 +164,22 @@
         old(CagouWidget): CagouWidget instance or a child
         new(CagouWidget): new widget instance
         """
-        for w in old.walk_reverse():
-            if isinstance(w, CagouWidget):
-                parent = w.parent
-                idx = parent.children.index(w)
-                parent.remove_widget(w)
-                parent.add_widget(new, index=idx)
-                break
+        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__':
--- a/src/cagou_widget.kv	Fri Jul 08 20:18:43 2016 +0200
+++ b/src/cagou_widget.kv	Sat Jul 09 16:02:44 2016 +0200
@@ -15,15 +15,13 @@
 # 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
-    Button:
-        text: "widget 1"
-        size_hint_y: None
-        height: 44
-        on_release: root.select('item1')
-    Button:
-        text: "widget 2"
+    auto_width: False
 
 <CagouWidget>:
     BoxLayout:
--- a/src/cagou_widget.py	Fri Jul 08 20:18:43 2016 +0200
+++ b/src/cagou_widget.py	Sat Jul 09 16:02:44 2016 +0200
@@ -20,16 +20,35 @@
 
 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):
-    pass
+
+    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):
+    def __init__(self, host):
+        self.host = host
         BoxLayout.__init__(self, orientation="vertical")
-        self.selector = HeaderWidgetSelector()
+        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/constants.py	Fri Jul 08 20:18:43 2016 +0200
+++ b/src/constants.py	Sat Jul 09 16:02:44 2016 +0200
@@ -24,3 +24,4 @@
     APP_NAME = "Cagou"
     LOG_OPT_SECTION = APP_NAME.lower()
     CONFIG_SECTION = APP_NAME.lower()
+    WID_SELECTOR = 'selector'
--- a/src/contact_list.kv	Fri Jul 08 20:18:43 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/contact_list.py	Fri Jul 08 20:18:43 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +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.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
-
-
-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)
-        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/plugin_wid_contact_list.kv	Sat Jul 09 16:02:44 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/plugin_wid_contact_list.py	Sat Jul 09 16:02:44 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_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
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugin_wid_widget_selector.kv	Sat Jul 09 16:02:44 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/plugin_wid_widget_selector.py	Sat Jul 09 16:02:44 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 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/widget_selector.kv	Fri Jul 08 20:18:43 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.widget_type
-        bold: True
-        size_hint: (None, None)
-    Widget:
--- a/src/widget_selector.py	Fri Jul 08 20:18:43 2016 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,69 +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.listview import ListView
-from kivy.adapters.listadapter import ListAdapter
-from kivy import properties
-from contact_list import ContactList
-from kivy.uix.behaviors import ButtonBehavior
-from cagou_widget import CagouWidget
-
-
-class WidgetSelItem(ButtonBehavior, BoxLayout):
-    widget_type = properties.StringProperty('')
-
-    def __init__(self, **kwargs):
-        super(WidgetSelItem, self).__init__(**kwargs)
-        self.host = kwargs['host']
-        self.callback = kwargs['callback']
-
-    def select(self, *args):
-        log.debug(u"widget selection: {}".format(self.widget_type))
-        self.host.switchWidget(self, self.callback())
-
-    def deselect(self, *args):
-        pass
-
-
-class WidgetSelector(CagouWidget):
-
-    def __init__(self, host):
-        super(WidgetSelector, self).__init__()
-        self.host = host
-        self.widget_factory = {
-            'contacts': lambda: host.widgets.getOrCreateWidget(ContactList, None, on_new_widget=None),
-            }
-        self.adapter = ListAdapter(data=self.widget_factory,
-            cls=WidgetSelItem,
-            args_converter=self.dataConverter,
-            selection_mode='single',
-            allow_empty_selection=True,
-           )
-        self.add_widget(ListView(adapter=self.adapter))
-
-    def dataConverter(self, idx, widget_type):
-        return {
-            "host": self.host,
-            "widget_type": widget_type,
-            "callback": self.widget_factory[widget_type]
-            }
--- a/src/widgets_handler.py	Fri Jul 08 20:18:43 2016 +0200
+++ b/src/widgets_handler.py	Sat Jul 09 16:02:44 2016 +0200
@@ -23,7 +23,6 @@
 from kivy.uix.boxlayout import BoxLayout
 from kivy.uix.button import Button
 from kivy import properties
-from widget_selector import WidgetSelector
 
 
 NEW_WIDGET_DIST = 10
@@ -68,8 +67,10 @@
 
 class WidgetsHandler(BoxLayout):
 
-    def __init__(self, host, wid, **kw):
+    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")
@@ -80,6 +81,10 @@
         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)
@@ -91,11 +96,11 @@
     def setWidgetSize(self, vertical, size):
         if vertical:
             if self.vert_wid is None:
-                self.vert_wid = WidgetsHandler(self.host, WidgetSelector(self.host), size_hint=(1, 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, WidgetSelector(self.host), size_hint=(None, 1))
+                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