# HG changeset patch # User souliane # Date 1381233455 -7200 # Node ID 266e9678eec0626abb201916b15f7715192787b3 # Parent e632f77c4219d1f2f7c542cd686217dd69f78f0c browser_side: added the flag REUSE_EXISTING_LIBERVIA_WIDGETS - do not create a new chat panel for contacts/groups if a similar one is found in the same tab - this goes with a modification of the methods to create new panels, especially arguments handling - improvement of the accepted groups list for MicroblogPanel (remove duplicates and keep sorted) Details for the new flag: # Set to true to not create a new LiberviaWidget when a similar one # already exist (i.e. a chat panel with the same target). Instead # the existing widget will be eventually removed from its parent # and added to new WidgetsPanel, or replaced to the expected # position if the previous and the new parent are the same. diff -r e632f77c4219 -r 266e9678eec0 browser_side/base_widget.py --- a/browser_side/base_widget.py Tue Oct 08 13:48:00 2013 +0200 +++ b/browser_side/base_widget.py Tue Oct 08 13:57:35 2013 +0200 @@ -207,6 +207,12 @@ """ Called when the widget is actually ending """ pass + def refresh(self): + """This can be overwritten by a child class to refresh the display when, + instead of creating a new one, an existing widget is found and reused. + """ + pass + def onSetting(self, sender): widpanel = self.getWidgetsPanel() row, col = widpanel.getIndex(self) @@ -334,6 +340,10 @@ # the event will not propagate to children VerticalPanel.doAttachChildren(self) + def matchEntity(self, entity): + """This method should be overwritten by child classes.""" + raise NotImplementedError + class ScrollPanelWrapper(SimplePanel): """Scroll Panel like component, wich use the full available space diff -r e632f77c4219 -r 266e9678eec0 browser_side/contact.py --- a/browser_side/contact.py Tue Oct 08 13:48:00 2013 +0200 +++ b/browser_side/contact.py Tue Oct 08 13:57:35 2013 +0200 @@ -44,9 +44,8 @@ self.addClickListener(self) def onClick(self, sender): - new_wid = MicroblogPanel.createGroup(self.host, self.group) - self.host.addWidget(new_wid) - + self.host.getOrCreateLiberviaWidget(MicroblogPanel, self.group) + class ContactLabel(DragLabel, HTML, ClickHandler): def __init__(self, host, jid, name=None): @@ -74,8 +73,8 @@ self._fill() def onClick(self, sender): - new_wid = ChatPanel.createChat(self.host, self.jid) - self.host.addWidget(new_wid) + self.host.getOrCreateLiberviaWidget(ChatPanel, self.jid) + class GroupList(VerticalPanel): @@ -162,8 +161,8 @@ self.addClickListener(self) def onClick(self, sender): - new_wid = MicroblogPanel.createMeta(self.host, None) - self.host.addWidget(new_wid) + self.host.getOrCreateLiberviaWidget(MicroblogPanel, None) + class ContactPanel(SimplePanel): """Manage the contacts and groups""" diff -r e632f77c4219 -r 266e9678eec0 browser_side/panels.py --- a/browser_side/panels.py Tue Oct 08 13:48:00 2013 +0200 +++ b/browser_side/panels.py Tue Oct 08 13:57:35 2013 +0200 @@ -301,10 +301,8 @@ """Panel used to show microblog @param accepted_groups: groups displayed in this panel, if empty, show all microblogs from all contacts """ - base_widget.LiberviaWidget.__init__(self, host, ", ".join(accepted_groups), selectable = True) - #base_widget.ScrollPanelWrapper.__init__(self) - #DropCell.__init__(self) - self.accepted_groups = accepted_groups + base_widget.LiberviaWidget.__init__(self, host, ", ".join(accepted_groups), selectable=True) + self.setAcceptedGroup(accepted_groups) self.entries = {} self.comments = {} self.selected_entry = None @@ -314,23 +312,44 @@ @classmethod def registerClass(cls): - base_widget.LiberviaWidget.addDropKey("GROUP", cls.createGroup) - base_widget.LiberviaWidget.addDropKey("CONTACT_TITLE", cls.createMeta) + base_widget.LiberviaWidget.addDropKey("GROUP", cls.createPanel) + base_widget.LiberviaWidget.addDropKey("CONTACT_TITLE", cls.createMetaPanel) @classmethod - def createGroup(cls, host, item): - _new_panel = MicroblogPanel(host, [item]) #XXX: pyjamas doesn't support use of cls directly - _new_panel.setAcceptedGroup(item) + def createPanel(cls, host, item): + """Generic panel creation for one, several or all groups (meta). + @parem host: the SatWebFrontend instance + @param item: single group as a string, list of groups + (as an array) or None (for the meta group = "all groups") + @return: the created MicroblogPanel + """ + _items = item if isinstance(item, list) else ([] if item is None else [item]) + _type = 'ALL' if _items == [] else 'GROUP' + # XXX: pyjamas doesn't support use of cls directly + _new_panel = MicroblogPanel(host, _items) host.FillMicroblogPanel(_new_panel) - host.bridge.call('getMassiveLastMblogs', _new_panel.massiveInsert, 'GROUP', [item], 10) + host.bridge.call('getMassiveLastMblogs', _new_panel.massiveInsert, _type, _items, 10) + host.setSelected(_new_panel) return _new_panel @classmethod - def createMeta(cls, host, item): - _new_panel = MicroblogPanel(host, []) #XXX: pyjamas doesn't support use of cls directly - host.FillMicroblogPanel(_new_panel) - host.bridge.call('getMassiveLastMblogs', _new_panel.massiveInsert, 'ALL', [], 10) - return _new_panel + def createMetaPanel(cls, host, item): + """Needed for the drop keys to not be mixed between meta panel and panel for "Contacts" group""" + return MicroblogPanel.createPanel(host, None) + + @property + def accepted_groups(self): + return self._accepted_groups + + def matchEntity(self, entity): + """ + @param entity: single group as a string, list of groups + (as an array) or None (for the meta group = "all groups") + @return: True if self matches the given entity + """ + entity = entity if isinstance(entity, list) else ([] if entity is None else [entity]) + entity.sort() # sort() do not return the sorted list: do it here, not on the "return" line + return self.accepted_groups == entity def getWarningData(self): if self.selected_entry: @@ -339,13 +358,13 @@ return ("NONE", None) return ("PUBLIC", "This is a comment and keep the initial post visibility, so it is potentialy public") - elif not self.accepted_groups: + elif not self._accepted_groups: # we have a meta MicroblogPanel, we publish publicly return ("PUBLIC", self.warning_msg_public) else: # we only accept one group at the moment # FIXME: manage several groups - return ("GROUP", self.warning_msg_group % self.accepted_groups[0]) + return ("GROUP", self.warning_msg_group % self._accepted_groups[0]) def onTextEntered(self, text): if self.selected_entry: @@ -354,16 +373,16 @@ if not comments_node: raise Exception("ERROR: comments node is empty") self.host.bridge.call("sendMblogComment", None, comments_node, text) - elif not self.accepted_groups: + elif not self._accepted_groups: # we are entering a public microblog self.host.bridge.call("sendMblog", None, "PUBLIC", None, text) else: # we are entering a microblog restricted to a group # FIXME: manage several groups - self.host.bridge.call("sendMblog", None, "GROUP", self.accepted_groups[0], text) + self.host.bridge.call("sendMblog", None, "GROUP", self._accepted_groups[0], text) def accept_all(self): - return not self.accepted_groups #we accept every microblog only if we are not filtering by groups + return not self._accepted_groups # we accept every microblog only if we are not filtering by groups def getEntries(self): """Ask all the entries for the currenly accepted groups, @@ -481,13 +500,17 @@ updateVPanel(self.vpanel) def setAcceptedGroup(self, group): - """Set the group which can be displayed in this panel + """Add one or more group(s) which can be displayed in this panel. + Prevent from duplicate values and keep the list sorted. @param group: string of the group, or list of string """ - if isinstance(group, list): - self.accepted_groups.extend(group) - else: - self.accepted_groups.append(group) + if not hasattr(self, "_accepted_groups"): + self._accepted_groups = [] + groups = group if isinstance(group, list) else [group] + for _group in groups: + if _group not in self._accepted_groups: + self._accepted_groups.append(_group) + self._accepted_groups.sort() def isJidAccepted(self, jid): """Tell if a jid is actepted and shown in this panel @@ -495,7 +518,7 @@ @return: True if the jid is accepted""" if self.accept_all(): return True - for group in self.accepted_groups: + for group in self._accepted_groups: if self.host.contact_panel.isContactInGroup(group, jid): return True return False @@ -619,16 +642,34 @@ @classmethod def registerClass(cls): - base_widget.LiberviaWidget.addDropKey("CONTACT", cls.createChat) + base_widget.LiberviaWidget.addDropKey("CONTACT", cls.createPanel) @classmethod - def createChat(cls, host, item): - _contact = JID(item) + def createPanel(cls, host, item): + _contact = item if isinstance(item, JID) else JID(item) host.contact_panel.setContactMessageWaiting(_contact.bare, False) _new_panel = ChatPanel(host, _contact) # XXX: pyjamas doesn't seems to support creating with cls directly _new_panel.historyPrint() + host.setSelected(_new_panel) return _new_panel + def refresh(self): + """Refresh the display of this widget.""" + self.host.contact_panel.setContactMessageWaiting(self.target.bare, False) + self.content_scroll.scrollToBottom() + + def matchEntity(self, entity): + """ + @param entity: target jid as a string or JID instance + @return: True if self matches the given entity + """ + entity = entity if isinstance(entity, JID) else JID(entity) + try: + return self.target.bare == entity.bare + except AttributeError as e: + e.include_traceback() + return False + def getWarningData(self): if self.type not in ["one2one", "group"]: raise Exception("Unmanaged type !") diff -r e632f77c4219 -r 266e9678eec0 libervia.py --- a/libervia.py Tue Oct 08 13:48:00 2013 +0200 +++ b/libervia.py Tue Oct 08 13:57:35 2013 +0200 @@ -34,7 +34,16 @@ from browser_side.jid import JID from browser_side.tools import html_sanitize -MAX_MBLOG_CACHE = 500 #Max microblog entries kept in memories + +MAX_MBLOG_CACHE = 500 # Max microblog entries kept in memories + +# Set to true to not create a new LiberviaWidget when a similar one +# already exist (i.e. a chat panel with the same target). Instead +# the existing widget will be eventually removed from its parent +# and added to new WidgetsPanel, or replaced to the expected +# position if the previous and the new parent are the same. +REUSE_EXISTING_LIBERVIA_WIDGETS = True + class LiberviaJsonProxy(JSONProxy): def __init__(self, *args, **kwargs): @@ -237,14 +246,14 @@ return self.avatars_cache[jid_str] def registerWidget(self, wid): - print "Registering", wid + print "Registering", wid.getDebugName() self.libervia_widgets.add(wid) def unregisterWidget(self, wid): try: self.libervia_widgets.remove(wid) except KeyError: - print ('WARNING: trying to remove a non registered Widget:', wid) + print ('WARNING: trying to remove a non registered Widget:', wid.getDebugName()) def setUniBox(self, unibox): """register the unibox widget""" @@ -445,18 +454,68 @@ if lib_wid.isJidAccepted(entity): self.bridge.call('getMassiveLastMblogs', lib_wid.massiveInsert, 'JID', [entity], 10) + def getLiberviaWidget(self, class_, entity, ignoreOtherTabs=True): + """Get the corresponding panel if it exists. + @param class_: class of the panel (ChatPanel, MicroblogPanel...) + @param entity: polymorphic parameter, see class_.matchEntity. + @param ignoreOtherTabs: if True, the widgets that are not + contained by the currently selected tab will be ignored + @return: the existing widget that has been found or None.""" + selected_tab = self.tab_panel.getCurrentPanel() + for lib_wid in self.libervia_widgets: + parent = lib_wid.getWidgetsPanel(verbose=False) + if parent is None or (ignoreOtherTabs and parent != selected_tab): + # do not return a widget that is not in the currently selected tab + continue + if isinstance(lib_wid, class_): + try: + if lib_wid.matchEntity(entity): + print "existing widget found: %s" % lib_wid.getDebugName() + return lib_wid + except AttributeError as e: + e.stack_list() + return None + return None + + def getOrCreateLiberviaWidget(self, class_, entity, add=True, refresh=True, add=True): + """Get the matching LiberviaWidget if it exists, or create a new one. + @param class_: class of the panel (ChatPanel, MicroblogPanel...) + @param entity: polymorphic parameter, see class_.matchEntity. + @param refresh: if True, refresh the display of a widget that is found + (ignored if no widget is found and a new one is created) + @param add: if True, add a widget that is created to the selected tab + (ignored if a widget is found and no new one is created) + @return: the newly created wigdet if REUSE_EXISTING_LIBERVIA_WIDGETS + is set to False or if the widget has not been found, the existing + widget that has been found otherwise.""" + lib_wid = None + if REUSE_EXISTING_LIBERVIA_WIDGETS: + lib_wid = self.getLiberviaWidget(class_, entity) + if lib_wid is None: + lib_wid = class_.createPanel(self, entity) + elif refresh: + # remove the widget from its previous panel + panel = lib_wid.getWidgetsPanel(verbose=False) + if panel is not None: + panel.removeWidget(lib_wid) + if add or refresh: + self.addWidget(lib_wid) + if refresh: + # must be done after the widget is added, + # for example to scroll to the bottom + self.setSelected(lib_wid) + lib_wid.refresh() + return lib_wid def _newMessageCb(self, from_jid, msg, msg_type, to_jid): _from = JID(from_jid) _to = JID(to_jid) - showed = False - for lib_wid in self.libervia_widgets: - if isinstance(lib_wid,panels.ChatPanel) and (lib_wid.target.bare == _from.bare or lib_wid.target.bare == _to.bare): - lib_wid.printMessage(_from, msg) - showed = True - if not showed: - #The message has not been showed, we must indicate it - other = _to if _from.bare == self.whoami.bare else _from + other = _to if _from.bare == self.whoami.bare else _from + lib_wid = self.getLiberviaWidget(panels.ChatPanel, other, ignoreOtherTabs=False) + if lib_wid is not None: + lib_wid.printMessage(_from, msg) + else: + # The message has not been showed, we must indicate it self.contact_panel.setContactMessageWaiting(other.bare, True) def _presenceUpdateCb(self, entity, show, priority, statuses):