comparison sat_frontends/quick_frontend/quick_widgets.py @ 4037:524856bd7b19

massive refactoring to switch from camelCase to snake_case: historically, Libervia (SàT before) was using camelCase as allowed by PEP8 when using a pre-PEP8 code, to use the same coding style as in Twisted. However, snake_case is more readable and it's better to follow PEP8 best practices, so it has been decided to move on full snake_case. Because Libervia has a huge codebase, this ended with a ugly mix of camelCase and snake_case. To fix that, this patch does a big refactoring by renaming every function and method (including bridge) that are not coming from Twisted or Wokkel, to use fully snake_case. This is a massive change, and may result in some bugs.
author Goffi <goffi@goffi.org>
date Sat, 08 Apr 2023 13:54:42 +0200
parents be6d91572633
children 4b842c1fb686
comparison
equal deleted inserted replaced
4036:c4464d7ae97b 4037:524856bd7b19
66 for widget_map in self._widgets.values(): 66 for widget_map in self._widgets.values():
67 for widget_instances in widget_map.values(): 67 for widget_instances in widget_map.values():
68 for widget in widget_instances: 68 for widget in widget_instances:
69 yield widget 69 yield widget
70 70
71 def getRealClass(self, class_): 71 def get_real_class(self, class_):
72 """Return class registered for given class_ 72 """Return class registered for given class_
73 73
74 @param class_: subclass of QuickWidget 74 @param class_: subclass of QuickWidget
75 @return: class actually used to create widget 75 @return: class actually used to create widget
76 """ 76 """
84 raise exceptions.InternalError( 84 raise exceptions.InternalError(
85 "There is not class registered for {}".format(class_) 85 "There is not class registered for {}".format(class_)
86 ) 86 )
87 return cls 87 return cls
88 88
89 def getWidgetInstances(self, widget): 89 def get_widget_instances(self, widget):
90 """Get all instance of a widget 90 """Get all instance of a widget
91 91
92 This is a helper method which call getWidgets 92 This is a helper method which call get_widgets
93 @param widget(QuickWidget): retrieve instances of this widget 93 @param widget(QuickWidget): retrieve instances of this widget
94 @return: iterator on widgets 94 @return: iterator on widgets
95 """ 95 """
96 return self.getWidgets(widget.__class__, widget.target, widget.profiles) 96 return self.get_widgets(widget.__class__, widget.target, widget.profiles)
97 97
98 def getWidgets(self, class_, target=None, profiles=None, with_duplicates=True): 98 def get_widgets(self, class_, target=None, profiles=None, with_duplicates=True):
99 """Get all subclassed widgets instances 99 """Get all subclassed widgets instances
100 100
101 @param class_: subclass of QuickWidget, same parameter as used in 101 @param class_: subclass of QuickWidget, same parameter as used in
102 [getOrCreateWidget] 102 [get_or_create_widget]
103 @param target: if not None, construct a hash with this target and filter 103 @param target: if not None, construct a hash with this target and filter
104 corresponding widgets 104 corresponding widgets
105 recreated widgets are handled 105 recreated widgets are handled
106 @param profiles(iterable, None): if not None, filter on instances linked to these 106 @param profiles(iterable, None): if not None, filter on instances linked to these
107 profiles 107 profiles
108 @param with_duplicates(bool): if False, only first widget with a given hash is 108 @param with_duplicates(bool): if False, only first widget with a given hash is
109 returned 109 returned
110 @return: iterator on widgets 110 @return: iterator on widgets
111 """ 111 """
112 class_ = self.getRealClass(class_) 112 class_ = self.get_real_class(class_)
113 try: 113 try:
114 widgets_map = self._widgets[class_.__name__] 114 widgets_map = self._widgets[class_.__name__]
115 except KeyError: 115 except KeyError:
116 return 116 return
117 else: 117 else:
118 if target is not None: 118 if target is not None:
119 filter_hash = str(class_.getWidgetHash(target, profiles)) 119 filter_hash = str(class_.get_widget_hash(target, profiles))
120 else: 120 else:
121 filter_hash = None 121 filter_hash = None
122 if filter_hash is not None: 122 if filter_hash is not None:
123 for widget in widgets_map.get(filter_hash, []): 123 for widget in widgets_map.get(filter_hash, []):
124 yield widget 124 yield widget
131 if not with_duplicates: 131 if not with_duplicates:
132 # widgets are set by hashes, so if don't want duplicates 132 # widgets are set by hashes, so if don't want duplicates
133 # we only return the first widget of the list 133 # we only return the first widget of the list
134 break 134 break
135 135
136 def getWidget(self, class_, target=None, profiles=None): 136 def get_widget(self, class_, target=None, profiles=None):
137 """Get a widget without creating it if it doesn't exist. 137 """Get a widget without creating it if it doesn't exist.
138 138
139 if several instances of widgets with this hash exist, the first one is returned 139 if several instances of widgets with this hash exist, the first one is returned
140 @param class_: subclass of QuickWidget, same parameter as used in [getOrCreateWidget] 140 @param class_: subclass of QuickWidget, same parameter as used in [get_or_create_widget]
141 @param target: target depending of the widget, usually a JID instance 141 @param target: target depending of the widget, usually a JID instance
142 @param profiles (unicode, iterable[unicode], None): profile(s) to use (may or may not be 142 @param profiles (unicode, iterable[unicode], None): profile(s) to use (may or may not be
143 used, depending of the widget class) 143 used, depending of the widget class)
144 @return: a class_ instance or None if the widget doesn't exist 144 @return: a class_ instance or None if the widget doesn't exist
145 """ 145 """
146 assert (target is not None) or (profiles is not None) 146 assert (target is not None) or (profiles is not None)
147 if profiles is not None and isinstance(profiles, str): 147 if profiles is not None and isinstance(profiles, str):
148 profiles = [profiles] 148 profiles = [profiles]
149 class_ = self.getRealClass(class_) 149 class_ = self.get_real_class(class_)
150 hash_ = class_.getWidgetHash(target, profiles) 150 hash_ = class_.get_widget_hash(target, profiles)
151 try: 151 try:
152 return self._widgets[class_.__name__][hash_][0] 152 return self._widgets[class_.__name__][hash_][0]
153 except KeyError: 153 except KeyError:
154 return None 154 return None
155 155
156 def getOrCreateWidget(self, class_, target, *args, **kwargs): 156 def get_or_create_widget(self, class_, target, *args, **kwargs):
157 """Get an existing widget or create a new one when necessary 157 """Get an existing widget or create a new one when necessary
158 158
159 If the widget is new, self.host.newWidget will be called with it. 159 If the widget is new, self.host.new_widget will be called with it.
160 @param class_(class): class of the widget to create 160 @param class_(class): class of the widget to create
161 @param target: target depending of the widget, usually a JID instance 161 @param target: target depending of the widget, usually a JID instance
162 @param args(list): optional args to create a new instance of class_ 162 @param args(list): optional args to create a new instance of class_
163 @param kwargs(dict): optional kwargs to create a new instance of class_ 163 @param kwargs(dict): optional kwargs to create a new instance of class_
164 if 'profile' key is present, it will be popped and put in 'profiles' 164 if 'profile' key is present, it will be popped and put in 'profiles'
165 if there is neither 'profile' nor 'profiles', None will be used for 'profiles' 165 if there is neither 'profile' nor 'profiles', None will be used for 'profiles'
166 if 'on_new_widget' is present it can have the following values: 166 if 'on_new_widget' is present it can have the following values:
167 C.WIDGET_NEW [default]: self.host.newWidget will be called on widget creation 167 C.WIDGET_NEW [default]: self.host.new_widget will be called on widget creation
168 [callable]: this method will be called instead of self.host.newWidget 168 [callable]: this method will be called instead of self.host.new_widget
169 None: do nothing 169 None: do nothing
170 if 'on_existing_widget' is present it can have the following values: 170 if 'on_existing_widget' is present it can have the following values:
171 C.WIDGET_KEEP [default]: return the existing widget 171 C.WIDGET_KEEP [default]: return the existing widget
172 C.WIDGET_RAISE: raise WidgetAlreadyExistsError 172 C.WIDGET_RAISE: raise WidgetAlreadyExistsError
173 C.WIDGET_RECREATE: create a new widget 173 C.WIDGET_RECREATE: create a new widget
174 if the existing widget has a "recreateArgs" method, it will be called with args list and kwargs dict 174 if the existing widget has a "recreate_args" method, it will be called with args list and kwargs dict
175 so the values can be completed to create correctly the new instance 175 so the values can be completed to create correctly the new instance
176 [callable]: this method will be called with existing widget as argument, the widget to use must be returned 176 [callable]: this method will be called with existing widget as argument, the widget to use must be returned
177 if 'force_hash' is present, the hash given in value will be used instead of the one returned by class_.getWidgetHash 177 if 'force_hash' is present, the hash given in value will be used instead of the one returned by class_.get_widget_hash
178 other keys will be used to instanciate class_ if the case happen (e.g. if type_ is present and class_ is a QuickChat subclass, 178 other keys will be used to instanciate class_ if the case happen (e.g. if type_ is present and class_ is a QuickChat subclass,
179 it will be used to create a new QuickChat instance). 179 it will be used to create a new QuickChat instance).
180 @return: a class_ instance, either new or already existing 180 @return: a class_ instance, either new or already existing
181 """ 181 """
182 cls = self.getRealClass(class_) 182 cls = self.get_real_class(class_)
183 183
184 ## arguments management ## 184 ## arguments management ##
185 _args = [self.host, target] + list( 185 _args = [self.host, target] + list(
186 args 186 args
187 ) or [] # FIXME: check if it's really necessary to use optional args 187 ) or [] # FIXME: check if it's really necessary to use optional args
210 210
211 ## we get the hash ## 211 ## we get the hash ##
212 try: 212 try:
213 hash_ = _kwargs.pop("force_hash") 213 hash_ = _kwargs.pop("force_hash")
214 except KeyError: 214 except KeyError:
215 hash_ = cls.getWidgetHash(target, _kwargs["profiles"]) 215 hash_ = cls.get_widget_hash(target, _kwargs["profiles"])
216 216
217 ## widget creation or retrieval ## 217 ## widget creation or retrieval ##
218 218
219 widgets_map = self._widgets.setdefault( 219 widgets_map = self._widgets.setdefault(
220 cls.__name__, {} 220 cls.__name__, {}
225 try: 225 try:
226 widget = widgets_map[hash_][0] 226 widget = widgets_map[hash_][0]
227 except KeyError: 227 except KeyError:
228 widget = None 228 widget = None
229 else: 229 else:
230 widget.addTarget(target) 230 widget.add_target(target)
231 231
232 if widget is None: 232 if widget is None:
233 # we need to create a new widget 233 # we need to create a new widget
234 log.debug(f"Creating new widget for target {target} {cls}") 234 log.debug(f"Creating new widget for target {target} {cls}")
235 widget = cls(*_args, **_kwargs) 235 widget = cls(*_args, **_kwargs)
236 widgets_map.setdefault(hash_, []).append(widget) 236 widgets_map.setdefault(hash_, []).append(widget)
237 self.host.callListeners("widgetNew", widget) 237 self.host.call_listeners("widgetNew", widget)
238 238
239 if on_new_widget == C.WIDGET_NEW: 239 if on_new_widget == C.WIDGET_NEW:
240 self.host.newWidget(widget) 240 self.host.new_widget(widget)
241 elif callable(on_new_widget): 241 elif callable(on_new_widget):
242 on_new_widget(widget) 242 on_new_widget(widget)
243 else: 243 else:
244 assert on_new_widget is None 244 assert on_new_widget is None
245 else: 245 else:
248 pass 248 pass
249 elif on_existing_widget == C.WIDGET_RAISE: 249 elif on_existing_widget == C.WIDGET_RAISE:
250 raise WidgetAlreadyExistsError(hash_) 250 raise WidgetAlreadyExistsError(hash_)
251 elif on_existing_widget == C.WIDGET_RECREATE: 251 elif on_existing_widget == C.WIDGET_RECREATE:
252 try: 252 try:
253 recreateArgs = widget.recreateArgs 253 recreate_args = widget.recreate_args
254 except AttributeError: 254 except AttributeError:
255 pass 255 pass
256 else: 256 else:
257 recreateArgs(_args, _kwargs) 257 recreate_args(_args, _kwargs)
258 widget = cls(*_args, **_kwargs) 258 widget = cls(*_args, **_kwargs)
259 widgets_map[hash_].append(widget) 259 widgets_map[hash_].append(widget)
260 log.debug("widget <{wid}> already exists, a new one has been recreated" 260 log.debug("widget <{wid}> already exists, a new one has been recreated"
261 .format(wid=widget)) 261 .format(wid=widget))
262 elif callable(on_existing_widget): 262 elif callable(on_existing_widget):
272 raise exceptions.InternalError( 272 raise exceptions.InternalError(
273 "Unexpected on_existing_widget value ({})".format(on_existing_widget)) 273 "Unexpected on_existing_widget value ({})".format(on_existing_widget))
274 274
275 return widget 275 return widget
276 276
277 def deleteWidget(self, widget_to_delete, *args, **kwargs): 277 def delete_widget(self, widget_to_delete, *args, **kwargs):
278 """Delete a widget instance 278 """Delete a widget instance
279 279
280 this method must be called by frontends when a widget is deleted 280 this method must be called by frontends when a widget is deleted
281 widget's onDelete method will be called before deletion, and deletion will be 281 widget's on_delete method will be called before deletion, and deletion will be
282 stopped if it returns False. 282 stopped if it returns False.
283 @param widget_to_delete(QuickWidget): widget which need to deleted 283 @param widget_to_delete(QuickWidget): widget which need to deleted
284 @param *args: extra arguments to pass to onDelete 284 @param *args: extra arguments to pass to on_delete
285 @param *kwargs: extra keywords arguments to pass to onDelete 285 @param *kwargs: extra keywords arguments to pass to on_delete
286 the extra arguments are not used by QuickFrontend, it's is up to 286 the extra arguments are not used by QuickFrontend, it's is up to
287 the frontend to use them or not. 287 the frontend to use them or not.
288 following extra arguments are well known: 288 following extra arguments are well known:
289 - "all_instances" can be used as kwarg, if it evaluate to True, 289 - "all_instances" can be used as kwarg, if it evaluate to True,
290 all instances of the widget will be deleted (if onDelete is 290 all instances of the widget will be deleted (if on_delete is
291 not returning False for any of the instance). This arguments 291 not returning False for any of the instance). This arguments
292 is not sent to onDelete methods. 292 is not sent to on_delete methods.
293 - "explicit_close" is used when the deletion is requested by 293 - "explicit_close" is used when the deletion is requested by
294 the user or a leave signal, "all_instances" is usually set at 294 the user or a leave signal, "all_instances" is usually set at
295 the same time. 295 the same time.
296 """ 296 """
297 # TODO: all_instances must be independante kwargs, this is not possible with Python 2 297 # TODO: all_instances must be independante kwargs, this is not possible with Python 2
298 # but will be with Python 3 298 # but will be with Python 3
299 all_instances = kwargs.get('all_instances', False) 299 all_instances = kwargs.get('all_instances', False)
300 300
301 if all_instances: 301 if all_instances:
302 for w in self.getWidgetInstances(widget_to_delete): 302 for w in self.get_widget_instances(widget_to_delete):
303 if w.onDelete(**kwargs) == False: 303 if w.on_delete(**kwargs) == False:
304 log.debug( 304 log.debug(
305 f"Deletion of {widget_to_delete} cancelled by widget itself") 305 f"Deletion of {widget_to_delete} cancelled by widget itself")
306 return 306 return
307 else: 307 else:
308 if widget_to_delete.onDelete(**kwargs) == False: 308 if widget_to_delete.on_delete(**kwargs) == False:
309 log.debug(f"Deletion of {widget_to_delete} cancelled by widget itself") 309 log.debug(f"Deletion of {widget_to_delete} cancelled by widget itself")
310 return 310 return
311 311
312 if self.host.selected_widget == widget_to_delete: 312 if self.host.selected_widget == widget_to_delete:
313 self.host.selected_widget = None 313 self.host.selected_widget = None
314 314
315 class_ = self.getRealClass(widget_to_delete.__class__) 315 class_ = self.get_real_class(widget_to_delete.__class__)
316 try: 316 try:
317 widgets_map = self._widgets[class_.__name__] 317 widgets_map = self._widgets[class_.__name__]
318 except KeyError: 318 except KeyError:
319 log.error("no widgets_map found for class {cls}".format(cls=class_)) 319 log.error("no widgets_map found for class {cls}".format(cls=class_))
320 return 320 return
321 widget_hash = str(class_.getWidgetHash(widget_to_delete.target, 321 widget_hash = str(class_.get_widget_hash(widget_to_delete.target,
322 widget_to_delete.profiles)) 322 widget_to_delete.profiles))
323 try: 323 try:
324 widget_instances = widgets_map[widget_hash] 324 widget_instances = widgets_map[widget_hash]
325 except KeyError: 325 except KeyError:
326 log.error(f"no instance of {class_.__name__} found with hash {widget_hash!r}") 326 log.error(f"no instance of {class_.__name__} found with hash {widget_hash!r}")
340 # all instances with this hash have been deleted 340 # all instances with this hash have been deleted
341 # we remove the hash itself 341 # we remove the hash itself
342 del widgets_map[widget_hash] 342 del widgets_map[widget_hash]
343 log.debug("All instances of {cls} with hash {widget_hash!r} have been deleted" 343 log.debug("All instances of {cls} with hash {widget_hash!r} have been deleted"
344 .format(cls=class_, widget_hash=widget_hash)) 344 .format(cls=class_, widget_hash=widget_hash))
345 self.host.callListeners("widgetDeleted", widget_to_delete) 345 self.host.call_listeners("widgetDeleted", widget_to_delete)
346 346
347 347
348 class QuickWidget(object): 348 class QuickWidget(object):
349 """generic widget base""" 349 """generic widget base"""
350 # FIXME: sometime a single target is used, sometimes several ones 350 # FIXME: sometime a single target is used, sometimes several ones
366 - None: no profile is managed by this widget class (rare) 366 - None: no profile is managed by this widget class (rare)
367 @raise: ValueError when (iterable) or None is given to profiles for a widget class which manage one unique profile. 367 @raise: ValueError when (iterable) or None is given to profiles for a widget class which manage one unique profile.
368 """ 368 """
369 self.host = host 369 self.host = host
370 self.targets = set() 370 self.targets = set()
371 self.addTarget(target) 371 self.add_target(target)
372 self.profiles = set() 372 self.profiles = set()
373 self._sync = True 373 self._sync = True
374 if isinstance(profiles, str): 374 if isinstance(profiles, str):
375 self.addProfile(profiles) 375 self.add_profile(profiles)
376 elif profiles is None: 376 elif profiles is None:
377 if not self.PROFILES_ALLOW_NONE: 377 if not self.PROFILES_ALLOW_NONE:
378 raise ValueError("profiles can't have a value of None") 378 raise ValueError("profiles can't have a value of None")
379 else: 379 else:
380 for profile in profiles: 380 for profile in profiles:
381 self.addProfile(profile) 381 self.add_profile(profile)
382 if not self.profiles: 382 if not self.profiles:
383 raise ValueError("no profile found, use None for no profile classes") 383 raise ValueError("no profile found, use None for no profile classes")
384 384
385 @property 385 @property
386 def profile(self): 386 def profile(self):
400 return next(iter(self.targets)) 400 return next(iter(self.targets))
401 401
402 @property 402 @property
403 def widget_hash(self): 403 def widget_hash(self):
404 """Return quick widget hash""" 404 """Return quick widget hash"""
405 return self.getWidgetHash(self.target, self.profiles) 405 return self.get_widget_hash(self.target, self.profiles)
406 406
407 # synchronisation state 407 # synchronisation state
408 408
409 @property 409 @property
410 def sync(self): 410 def sync(self):
427 """ 427 """
428 pass 428 pass
429 429
430 # target/profile 430 # target/profile
431 431
432 def addTarget(self, target): 432 def add_target(self, target):
433 """Add a target if it doesn't already exists 433 """Add a target if it doesn't already exists
434 434
435 @param target: target to add 435 @param target: target to add
436 """ 436 """
437 self.targets.add(target) 437 self.targets.add(target)
438 438
439 def addProfile(self, profile): 439 def add_profile(self, profile):
440 """Add a profile is if doesn't already exists 440 """Add a profile is if doesn't already exists
441 441
442 @param profile: profile to add 442 @param profile: profile to add
443 """ 443 """
444 if self.profiles and not self.PROFILES_MULTIPLE: 444 if self.profiles and not self.PROFILES_MULTIPLE:
446 self.profiles.add(profile) 446 self.profiles.add(profile)
447 447
448 # widget identitication 448 # widget identitication
449 449
450 @staticmethod 450 @staticmethod
451 def getWidgetHash(target, profiles): 451 def get_widget_hash(target, profiles):
452 """Return the hash associated with this target for this widget class 452 """Return the hash associated with this target for this widget class
453 453
454 some widget classes can manage several target on the same instance 454 some widget classes can manage several target on the same instance
455 (e.g.: a chat widget with multiple resources on the same bare jid), 455 (e.g.: a chat widget with multiple resources on the same bare jid),
456 this method allow to return a hash associated to one or several targets 456 this method allow to return a hash associated to one or several targets
463 """ 463 """
464 return str(target) # by defaut, there is one hash for one target 464 return str(target) # by defaut, there is one hash for one target
465 465
466 # widget life events 466 # widget life events
467 467
468 def onDelete(self, *args, **kwargs): 468 def on_delete(self, *args, **kwargs):
469 """Called when a widget is being deleted 469 """Called when a widget is being deleted
470 470
471 @return (boot, None): False to cancel deletion 471 @return (boot, None): False to cancel deletion
472 all other value continue deletion 472 all other value continue deletion
473 """ 473 """
474 return True 474 return True
475 475
476 def onSelected(self): 476 def on_selected(self):
477 """Called when host.selected_widget is this instance""" 477 """Called when host.selected_widget is this instance"""
478 pass 478 pass