comparison frontends/src/quick_frontend/quick_widgets.py @ 1265:e3a9ea76de35 frontends_multi_profiles

quick_frontend, primitivus: multi-profiles refactoring part 1 (big commit, sorry :p): This refactoring allow primitivus to manage correctly several profiles at once, with various other improvments: - profile_manager can now plug several profiles at once, requesting password when needed. No more profile plug specific method is used anymore in backend, instead a "validated" key is used in actions - Primitivus widget are now based on a common "PrimitivusWidget" classe which mainly manage the decoration so far - all widgets are treated in the same way (contactList, Chat, Progress, etc), no more chat_wins specific behaviour - widgets are created in a dedicated manager, with facilities to react on new widget creation or other events - quick_frontend introduce a new QuickWidget class, which aims to be as generic and flexible as possible. It can manage several targets (jids or something else), and several profiles - each widget class return a Hash according to its target. For example if given a target jid and a profile, a widget class return a hash like (target.bare, profile), the same widget will be used for all resources of the same jid - better management of CHAT_GROUP mode for Chat widgets - some code moved from Primitivus to QuickFrontend, the final goal is to have most non backend code in QuickFrontend, and just graphic code in subclasses - no more (un)escapePrivate/PRIVATE_PREFIX - contactList improved a lot: entities not in roster and special entities (private MUC conversations) are better managed - resources can be displayed in Primitivus, and their status messages - profiles are managed in QuickFrontend with dedicated managers This is work in progress, other frontends are broken. Urwid SàText need to be updated. Most of features of Primitivus should work as before (or in a better way ;))
author Goffi <goffi@goffi.org>
date Wed, 10 Dec 2014 19:00:09 +0100
parents frontends/src/quick_frontend/quick_chat_list.py@75025461141f
children faa1129559b8
comparison
equal deleted inserted replaced
1264:60dfa2f5d61f 1265:e3a9ea76de35
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # helper class for making a SAT frontend
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from sat.core.log import getLogger
21 log = getLogger(__name__)
22 from sat.core import exceptions
23
24
25 classes_map = {}
26
27
28 def register(base_cls, child_cls=None):
29 """Register a child class to use by default when a base class is needed
30
31 @param base_cls: "Quick..." base class (like QuickChat or QuickContact), must inherit from QuickWidget
32 @param child_cls: inherited class to use when Quick... class is requested, must inherit from base_cls.
33 Can be None if it's the base_cls itself which register
34 """
35 classes_map[base_cls] = child_cls
36
37
38 class QuickWidgetsManager(object):
39 """This class is used to manage all the widgets of a frontend
40 A widget can be a window, a graphical thing, or someting else depending of the frontend"""
41
42 def __init__(self, host):
43 self.host = host
44 self._widgets = {}
45
46 def getOrCreateWidget(self, class_, target, *args, **kwargs):
47 """Get an existing widget or create a new one when necessary
48
49 If the widget is new, self.host.newWidget will be called with it.
50 @param class_(class): class of the widget to create
51 @param target: target depending of the widget, usually a JID instance
52 @param args(list): optional args to create a new instance of class_
53 @param kwargs(list): optional kwargs to create anew instance of class_
54 if 'profile' key is present, it will be popped and put in 'profiles'
55 if there is neither 'profile' nor 'profiles', None will be used for 'profiles'
56 if 'on_new_widget' is present it can have the following values:
57 'NEW_WIDGET' [default]: self.host.newWidget will be called on widget creation
58 [callable]: this method will be called instead of self.host.newWidget
59 None: do nothing
60 if 'force_hash' is present, the hash given in value will be used instead of the one returned by class_.getWidgetHash
61 @return: a class_ instance, either new or already existing
62 """
63 # class management
64 try:
65 cls = classes_map[class_]
66 except KeyError:
67 cls = class_
68 if cls is None:
69 raise exceptions.InternalError("There is not class registered for {}".format(class_))
70
71 # arguments management
72 _args = [self.host, target] + list(args) or [] # FIXME: check if it's really necessary to use optional args
73 _kwargs = kwargs or {}
74 if 'profiles' in _kwargs and 'profile' in _kwargs:
75 raise ValueError("You can't have 'profile' and 'profiles' keys at the same time")
76 try:
77 _kwargs['profiles'] = _kwargs.pop('profile')
78 except KeyError:
79 if not 'profiles' in _kwargs:
80 _kwargs['profiles'] = None
81
82 # we get the hash
83 try:
84 hash_ = _kwargs.pop('force_hash')
85 except KeyError:
86 hash_ = cls.getWidgetHash(target, _kwargs['profiles'])
87
88 # widget creation or retrieval
89 widgets_list = self._widgets.setdefault(cls, {}) # we sorts widgets by classes
90 if not cls.SINGLE:
91 widget = None # if the class is not SINGLE, we always create a new widget
92 else:
93 try:
94 widget = widgets_list[hash_]
95 widget.addTarget(target)
96 except KeyError:
97 widget = None
98
99 if widget is None:
100 # we need to create a new widget
101 try:
102 #on_new_widget tell what to do for the new widget creation
103 on_new_widget = _kwargs.pop('on_new_widget')
104 except KeyError:
105 on_new_widget = 'NEW_WIDGET'
106
107 log.debug(u"Creating new widget for target {} {}".format(target, cls))
108 widget = cls(*_args, **_kwargs)
109 widgets_list[hash_] = widget
110
111 if on_new_widget == 'NEW_WIDGET':
112 self.host.newWidget(widget)
113 elif callable(on_new_widget):
114 on_new_widget(widget)
115 else:
116 assert on_new_widget is None
117
118 return widget
119
120
121 class QuickWidget(object):
122 """generic widget base"""
123 SINGLE=True # if True, there can be only one widget per target(s)
124 PROFILES_MULTIPLE=False
125 PROFILES_ALLOW_NONE=False
126
127 def __init__(self, host, target, profiles=None):
128 """
129 @param host: %(doc_host)s
130 @param target: target specific for this widget class
131 @param profiles: can be either:
132 - (unicode): used when widget class manage a unique profile
133 - (iterable): some widget class can manage several profiles, several at once can be specified here
134 - None: no profile is managed by this widget class (rare)
135 @raise: ValueError when (iterable) or None is given to profiles for a widget class which manage one unique profile.
136 """
137 self.host = host
138 self.targets = set()
139 self.addTarget(target)
140 self.profiles = set()
141 if isinstance(profiles, basestring):
142 self.addProfile(profiles)
143 elif profiles is None:
144 if not self.PROFILES_ALLOW_NONE:
145 raise ValueError("profiles can't have a value of None")
146 else:
147 if not self.PROFILES_MULTIPLE:
148 raise ValueError("multiple profiles are not allowed")
149 for profile in profiles:
150 self.addProfile(profile)
151
152 @property
153 def profile(self):
154 assert len(self.profiles) == 1 and not self.PROFILES_MULTIPLE and not self.PROFILES_ALLOW_NONE
155 return list(self.profiles)[0]
156
157 def addTarget(self, target):
158 """Add a target if it doesn't already exists
159
160 @param target: target to add
161 """
162 self.targets.add(target)
163
164 def addProfile(self, profile):
165 """Add a profile is if doesn't already exists
166
167 @param profile: profile to add
168 """
169 if self.profiles and not self.PROFILES_MULTIPLE:
170 raise ValueError("multiple profiles are not allowed")
171 self.profiles.add(profile)
172
173 @staticmethod
174 def getWidgetHash(target, profiles):
175 """Return the hash associated with this target for this widget class
176
177 some widget classes can manage several target on the same instance
178 (e.g.: a chat widget with multiple resources on the same bare jid),
179 this method allow to return a hash associated to one or several targets
180 to retrieve the good instance. For example, a widget managing JID targets,
181 and all resource of the same bare jid would return the bare jid as hash.
182
183 @param target: target to check
184 @param profiles: profile(s) associated to target, see __init__ docstring
185 @return: a hash (can correspond to one or many targets or profiles, depending of widget class)
186 """
187 return unicode(target) # by defaut, there is one hash for one target