changeset 230:266e9678eec0

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.
author souliane <souliane@mailoo.org>
date Tue, 08 Oct 2013 13:57:35 +0200
parents e632f77c4219
children fab7aa366576
files browser_side/base_widget.py browser_side/contact.py browser_side/panels.py libervia.py
diffstat 4 files changed, 156 insertions(+), 47 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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"""
--- 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 <span class='warningTarget'>comment</span> 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 !")
--- 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):