Mercurial > libervia-backend
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 |