# HG changeset patch # User Goffi # Date 1468077841 -7200 # Node ID 56838ad5c84bc335a82dd5987c136a4acc8e282b # Parent 21a432afd06d0423e22e4104a76283de3dcc6749 files reorganisation, cagou is now launched with python2 cagou.py in src/ diff -r 21a432afd06d -r 56838ad5c84b src/__init__.py diff -r 21a432afd06d -r 56838ad5c84b src/button.png Binary file src/button.png has changed diff -r 21a432afd06d -r 56838ad5c84b src/button_selected.png Binary file src/button_selected.png has changed diff -r 21a432afd06d -r 56838ad5c84b src/cagou.kv --- 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 . - -#:include profile_manager.kv -#:include widgets_handler.kv -#:include cagou_widget.kv diff -r 21a432afd06d -r 56838ad5c84b src/cagou.py --- 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 . - -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() diff -r 21a432afd06d -r 56838ad5c84b src/cagou/__init__.py --- /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 . + +from core import cagou_main + +def run(): + host = cagou_main.Cagou() + host.run() diff -r 21a432afd06d -r 56838ad5c84b src/cagou/core/__init__.py diff -r 21a432afd06d -r 56838ad5c84b src/cagou/core/cagou_main.py --- /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 . + + +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) diff -r 21a432afd06d -r 56838ad5c84b src/cagou/core/cagou_widget.py --- /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 . + + +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) diff -r 21a432afd06d -r 56838ad5c84b src/cagou/core/config.py --- /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 . + +"""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) diff -r 21a432afd06d -r 56838ad5c84b src/cagou/core/constants.py --- /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 . + +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' diff -r 21a432afd06d -r 56838ad5c84b src/cagou/core/image.py --- /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 . + + +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 diff -r 21a432afd06d -r 56838ad5c84b src/cagou/core/logging_setter.py --- /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 . + +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 diff -r 21a432afd06d -r 56838ad5c84b src/cagou/core/profile_manager.py --- /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 . + + +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] diff -r 21a432afd06d -r 56838ad5c84b src/cagou/core/widgets_handler.py --- /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 . + + +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 diff -r 21a432afd06d -r 56838ad5c84b src/cagou/core/xmlui.py --- /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 . + +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 diff -r 21a432afd06d -r 56838ad5c84b src/cagou/images/button.png Binary file src/cagou/images/button.png has changed diff -r 21a432afd06d -r 56838ad5c84b src/cagou/images/button_selected.png Binary file src/cagou/images/button_selected.png has changed diff -r 21a432afd06d -r 56838ad5c84b src/cagou/kv/__init__.py diff -r 21a432afd06d -r 56838ad5c84b src/cagou/kv/cagou_widget.kv --- /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 . + + +: + size_hint_y: None + height: 44 + +: + size_hint: 0.3, 1 + auto_width: False + +: + 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 diff -r 21a432afd06d -r 56838ad5c84b src/cagou/kv/profile_manager.kv --- /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 . + + +: + Label: + text: "Profile Manager" + size_hint: 1,0.05 + +: + size_hint: 1, 0.1 + +: + multiline: False + size_hint: 1, 0.1 + write_tab: False + +: + size_hint: 1, 0.2 + + +: + 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: + + +: + 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: + + +: + 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' + + +: + text: "Connect" + size_hint: 1, 0.1 + disabled: not self.profile_screen.list_adapter.selection + on_press: self.pm._onConnectProfiles() + + +: + # 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 diff -r 21a432afd06d -r 56838ad5c84b src/cagou/kv/widgets_handler.kv --- /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 . + +: + 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 diff -r 21a432afd06d -r 56838ad5c84b src/cagou/plugins/__init__.py diff -r 21a432afd06d -r 56838ad5c84b src/cagou/plugins/plugin_wid_contact_list.kv --- /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 . + +: + 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: diff -r 21a432afd06d -r 56838ad5c84b src/cagou/plugins/plugin_wid_contact_list.py --- /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 . + + +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 + diff -r 21a432afd06d -r 56838ad5c84b src/cagou/plugins/plugin_wid_widget_selector.kv --- /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 . + +: + spacing: 20 + Widget: + Label: + text: root.plugin_info["name"] + bold: True + size_hint: (None, None) + Widget: diff -r 21a432afd06d -r 56838ad5c84b src/cagou/plugins/plugin_wid_widget_selector.py --- /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 . + + +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 diff -r 21a432afd06d -r 56838ad5c84b src/cagou_widget.kv --- 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 . - - -: - size_hint_y: None - height: 44 - -: - size_hint: 0.3, 1 - auto_width: False - -: - 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 diff -r 21a432afd06d -r 56838ad5c84b src/cagou_widget.py --- 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 . - - -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) diff -r 21a432afd06d -r 56838ad5c84b src/config.py --- 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 . - -"""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) diff -r 21a432afd06d -r 56838ad5c84b src/constants.py --- 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 . - -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' diff -r 21a432afd06d -r 56838ad5c84b src/image.py --- 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 . - - -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 diff -r 21a432afd06d -r 56838ad5c84b src/logging_setter.py --- 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 . - -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 diff -r 21a432afd06d -r 56838ad5c84b src/plugin_wid_contact_list.kv --- 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 . - -: - 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: diff -r 21a432afd06d -r 56838ad5c84b src/plugin_wid_contact_list.py --- 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 . - - -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 - diff -r 21a432afd06d -r 56838ad5c84b src/plugin_wid_widget_selector.kv --- 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 . - -: - spacing: 20 - Widget: - Label: - text: root.plugin_info["name"] - bold: True - size_hint: (None, None) - Widget: diff -r 21a432afd06d -r 56838ad5c84b src/plugin_wid_widget_selector.py --- 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 . - - -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 diff -r 21a432afd06d -r 56838ad5c84b src/profile_manager.kv --- 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 . - - -: - Label: - text: "Profile Manager" - size_hint: 1,0.05 - -: - size_hint: 1, 0.1 - -: - multiline: False - size_hint: 1, 0.1 - write_tab: False - -: - size_hint: 1, 0.2 - - -: - 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: - - -: - 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: - - -: - 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' - - -: - text: "Connect" - size_hint: 1, 0.1 - disabled: not self.profile_screen.list_adapter.selection - on_press: self.pm._onConnectProfiles() - - -: - 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 diff -r 21a432afd06d -r 56838ad5c84b src/profile_manager.py --- 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 . - - -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] diff -r 21a432afd06d -r 56838ad5c84b src/widgets_handler.kv --- 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 . - -: - 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 diff -r 21a432afd06d -r 56838ad5c84b src/widgets_handler.py --- 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 . - - -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 diff -r 21a432afd06d -r 56838ad5c84b src/xmlui.py --- 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 . - -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