changeset 637:7113d40533d6 frontends_multi_profiles

merged souliane changes
author Goffi <goffi@goffi.org>
date Mon, 23 Feb 2015 18:47:27 +0100
parents 86ae737da6f3 (diff) 66a547539185 (current diff)
children 63697f082e8a
files src/browser/libervia_main.py src/browser/sat_browser/base_widget.py src/browser/sat_browser/contact_list.py src/browser/sat_browser/panels.py
diffstat 5 files changed, 133 insertions(+), 114 deletions(-) [+]
line wrap: on
line diff
--- a/src/browser/libervia_main.py	Sun Feb 22 21:57:24 2015 +0100
+++ b/src/browser/libervia_main.py	Mon Feb 23 18:47:27 2015 +0100
@@ -26,7 +26,7 @@
 ###
 
 from sat_frontends.quick_frontend.quick_app import QuickApp
-from sat_frontends.quick_frontend.quick_widgets import WidgetAlreadyExistsError
+from sat_frontends.quick_frontend import quick_widgets
 
 from sat_frontends.tools.misc import InputHistory
 from sat_frontends.tools import strings
@@ -114,6 +114,11 @@
         return self.contact_lists[C.PROF_KEY_NONE]
 
     @property
+    def visible_widgets(self):
+        widgets_panel = self.tab_panel.getCurrentPanel()
+        return [wid for wid in widgets_panel.widgets if isinstance(wid, quick_widgets.QuickWidget)]
+
+    @property
     def base_location(self):
         """Return absolute base url of this Libervia instance"""
         url = Window.getLocation().getHref()
@@ -566,8 +571,8 @@
         something, a new widget is always created, otherwise we look for an
         existing widget and re-use it if it's in the current tab.
 
-        @arg class_(class): see sat_frontends.quick_frontend.quick_widgets.getOrCreateWidget
-        @arg target: see sat_frontends.quick_frontend.quick_widgets.getOrCreateWidget
+        @arg class_(class): see quick_widgets.getOrCreateWidget
+        @arg target: see quick_widgets.getOrCreateWidget
         @arg dropped(bool): if True, assume the widget has been dropped
         @arg new_tab(unicode): if not None, it holds the name of a new tab to
             open for the widget. If None, use the default behavior.
@@ -595,10 +600,15 @@
         kwargs['on_existing_widget'] = C.WIDGET_RAISE
         try:
             wid = self.widgets.getOrCreateWidget(class_, target, *args, **kwargs)
-        except WidgetAlreadyExistsError:
+        except quick_widgets.WidgetAlreadyExistsError:
             kwargs['on_existing_widget'] = C.WIDGET_KEEP
             wid = self.widgets.getOrCreateWidget(class_, target, *args, **kwargs)
-            if wid.getWidgetsPanel() != self.tab_panel.getCurrentPanel():
+            widgets_panel = wid.getParent(base_widget.WidgetsPanel, expect=False)
+            if widgets_panel is None:
+                # The widget exists but is hidden
+                self.addWidget(wid)
+            elif widgets_panel != self.tab_panel.getCurrentPanel():
+                # the widget is on an other tab, so we add a new one here
                 kwargs['on_existing_widget'] = C.WIDGET_RECREATE
                 wid = self.widgets.getOrCreateWidget(class_, target, *args, **kwargs)
                 self.addWidget(wid)
--- a/src/browser/sat_browser/base_menu.py	Sun Feb 22 21:57:24 2015 +0100
+++ b/src/browser/sat_browser/base_menu.py	Mon Feb 23 18:47:27 2015 +0100
@@ -37,7 +37,7 @@
 import re
 
 
-class MenuCmd:
+class MenuCmd(object):
     """Return an object with an "execute" method that can be set to a menu item callback"""
 
     def __init__(self, object_, handler=None, data=None):
@@ -58,7 +58,7 @@
         self.callback(self.data) if self.data else self.callback()
 
 
-class PluginMenuCmd:
+class PluginMenuCmd(object):
     """Like MenuCmd, but instead of executing a method, it will command the bridge to launch an action"""
 
     def __init__(self, host, action_id, menu_data=None):
--- a/src/browser/sat_browser/base_widget.py	Sun Feb 22 21:57:24 2015 +0100
+++ b/src/browser/sat_browser/base_widget.py	Mon Feb 23 18:47:27 2015 +0100
@@ -20,7 +20,7 @@
 import pyjd  # this is dummy in pyjs
 from sat.core.log import getLogger
 log = getLogger(__name__)
-
+from sat.core import exceptions
 from sat.core.i18n import _
 from sat_frontends.quick_frontend import quick_widgets
 
@@ -172,7 +172,7 @@
             if self == _new_panel:  # We can't drop on ourself
                 return
             # we need to remove the widget from the panel as it will be inserted elsewhere
-            widgets_panel = _new_panel.getWidgetsPanel()
+            widgets_panel = _new_panel.getParent(WidgetsPanel, expect=True)
             wid_row = widgets_panel.getWidgetCoords(_new_panel)[0]
             row_wids = widgets_panel.getLiberviaRowWidgets(wid_row)
             if len(row_wids) == 1 and wid_row == widgets_panel.getWidgetCoords(self)[0]:
@@ -226,10 +226,13 @@
             menu_styles.update(styles)
         base_menu.GenericMenuBar.__init__(self, host, vertical=vertical, styles=menu_styles)
 
-        if hasattr(parent, 'addMenus'):
-            # regroup all the dynamic menu categories in a sub-menu
-            sub_menu = WidgetSubMenuBar(host, vertical=True)
+        # regroup all the dynamic menu categories in a sub-menu
+        sub_menu = WidgetSubMenuBar(host, vertical=True)
+        try:
             parent.addMenus(sub_menu)
+        except (AttributeError, TypeError): # FIXME: pyjamas can throw a TypeError depending on compilation options
+            pass
+        else:
             if len(sub_menu.getCategories()) > 0:
                 self.addCategory('', '', 'plugins', sub_menu)
 
@@ -329,17 +332,15 @@
     def getDebugName(self):
         return "%s (%s)" % (self, self._title.getText())
 
-    def getWidgetsPanel(self, expect=True):
-        return self.getParent(WidgetsPanel, expect)
-
     def getParent(self, class_=None, expect=True):
         """Return the closest ancestor of the specified class.
 
         Note: this method overrides pyjamas.ui.Widget.getParent
 
         @param class_: class of the ancestor to look for or None to return the first parent
-        @param expect: set to True if the parent is expected (print a message if not found)
+        @param expect: set to True if the parent is expected (raise an error if not found)
         @return: the parent/ancestor or None if it has not been found
+        @raise exceptions.InternalError: expect is True and no parent is found
         """
         current = Widget.getParent(self)
         if class_ is None:
@@ -347,7 +348,7 @@
         while current is not None and not isinstance(current, class_):
             current = Widget.getParent(current)
         if current is None and expect:
-            log.error("Can't find parent %s for %s" % (class_, self))
+            raise exceptions.InternalError("Can't find parent %s for %s" % (class_, self))
         return current
 
     def onClick(self, sender):
@@ -355,8 +356,8 @@
 
     def onClose(self, sender):
         """ Called when the close button is pushed """
-        _widgetspanel = self.getWidgetsPanel()
-        _widgetspanel.removeWidget(self)
+        widgets_panel = self.getParent(WidgetsPanel, expect=True)
+        widgets_panel.removeWidget(self)
         self.onQuit()
         self.host.widgets.deleteWidget(self)
 
@@ -371,7 +372,7 @@
         pass
 
     def onSetting(self, sender):
-        widpanel = self.getWidgetsPanel()
+        widpanel = self.getParent(WidgetsPanel, expect=True)
         row, col = widpanel.getIndex(self)
         body = VerticalPanel()
 
@@ -803,7 +804,7 @@
                 log.error("No widget registered in LiberviaDragWidget !")
                 return
             _new_panel = LiberviaDragWidget.current
-            _new_panel.getWidgetsPanel().removeWidget(_new_panel)
+            _new_panel.getParent(WidgetsPanel, expect=True).removeWidget(_new_panel)
         elif item_type in DropCell.drop_keys:
             _new_panel = DropCell.drop_keys[item_type](self.tab_panel.host, item)
         else:
--- a/src/browser/sat_browser/contact_list.py	Sun Feb 22 21:57:24 2015 +0100
+++ b/src/browser/sat_browser/contact_list.py	Mon Feb 23 18:47:27 2015 +0100
@@ -93,24 +93,31 @@
 
 
 class ContactLabel(HTML):
-    def __init__(self, jid_, name=None):
+    """Display a contact in HTML, selecting best display (jid/nick/etc)"""
+
+    def __init__(self, host, jid_):
+        # TODO: add a listener for nick changes
         HTML.__init__(self)
-        self.name = name or unicode(jid_)
-        self.waiting = False
+        self.host = host
+        self.jid = jid_.bare
+        self.nick = self.host.contact_lists[C.PROF_KEY_NONE].getCache(self.jid, "nick")
+        self.alert = False
         self.refresh()
         self.setStyleName('contactLabel')
 
     def refresh(self):
-        if self.waiting:
-            wait_html = "<b>(*)</b>&nbsp;"
-        self.setHTML("%(wait)s%(name)s" % {'wait': wait_html,
-                                           'name': html_tools.html_sanitize(self.name)})
+        alert_html = "<strong>(*)</strong>&nbsp;" if self.alert else ""
+        contact_html = html_tools.html_sanitize(self.nick or unicode(self.jid))
+        html = "%(alert)s%(contact)s" % {'alert': alert_html,
+                                         'contact': contact_html}
+        self.setHTML(html)
 
-    def setMessageWaiting(self, waiting):
-        """Show a visual indicator if message are waiting
+    def setAlert(self, alert):
+        """Show a visual indicator
 
-        @param waiting: True if message are waiting"""
-        self.waiting = waiting
+        @param alert: True if alert must be shown
+        """
+        self.alert = alert
         self.refresh()
 
 
@@ -131,37 +138,31 @@
 
 class ContactBox(VerticalPanel, ClickHandler, base_widget.DragLabel):
 
-    def __init__(self, host, jid_, name=None, click_listener=None, handle_menu=None):
+    def __init__(self, parent, jid_):
         """
-
-        @param host (SatWebFrontend)
+        @param parent (ContactPanel): ContactPanel hosting this box
         @param jid_ (jid.JID): contact JID
-        @param name (unicode): contact alias
-        @param click_listener (callable): click callback
-        @param handle_menu (bool): if True, bind a popup menu to the avatar
         """
         VerticalPanel.__init__(self, StyleName='contactBox', VerticalAlignment='middle')
-        base_widget.DragLabel.__init__(self, jid_, "CONTACT", host)
-        self.jid = jid_
-        self.label = ContactLabel(jid_, name)
-        self.avatar = ContactMenuBar(self, host) if handle_menu else Image()
-        self.updateAvatar(host.getAvatarURL(jid_))
+        ClickHandler.__init__(self)
+        base_widget.DragLabel.__init__(self, jid_, "CONTACT", parent.host)
+        self.jid = jid_.bare
+        self.label = ContactLabel(parent.host, self.jid)
+        self.avatar = ContactMenuBar(self, parent.host) if parent.handle_menu else Image()
+        self.updateAvatar(parent.host.getAvatarURL(self.jid))
         self.add(self.avatar)
         self.add(self.label)
-        if click_listener:
-            ClickHandler.__init__(self)
-            self.addClickListener(self)
-            self.click_listener = click_listener
+        self.addClickListener(self)
 
     def addMenus(self, menu_bar):
         menu_bar.addCachedMenus(C.MENU_ROSTER_JID_CONTEXT, {'jid': unicode(self.jid)})
         menu_bar.addCachedMenus(C.MENU_JID_CONTEXT, {'jid': unicode(self.jid)})
 
-    def setMessageWaiting(self, waiting):
-        """Show a visual indicator if message are waiting
+    def setAlert(self, alert):
+        """Show a visual indicator
 
-        @param waiting: True if message are waiting"""
-        self.label.setMessageWaiting(waiting)
+        @param alert: True if alert indicator show be shown"""
+        self.label.setAlert(alert)
 
     def updateAvatar(self, url):
         """Update the avatar.
@@ -171,7 +172,12 @@
         self.avatar.setUrl(url)
 
     def onClick(self, sender):
-        self.click_listener(self.jid)
+        try:
+            self.parent.onClick(self.jid)
+        except AttributeError:
+            pass
+        else:
+            self.setAlert(False)
 
 
 class GroupPanel(VerticalPanel):
@@ -220,64 +226,64 @@
 
 
 class BaseContactsPanel(VerticalPanel):
-    """Class that can be used to represent a contact list, but not necessarily
-    the one that is displayed on the left side. Special features like popup menu
-    panel or changing the contact states must be done in a sub-class."""
+    """ContactList graphic representation
+
+    Special features like popup menu panel or changing the contact states must be done in a sub-class.
+    """
 
-    def __init__(self, host, handle_click=False, handle_menu=False):
+    def __init__(self, parent, handle_click=True, handle_menu=True):
+        """@param on_click (callable): click callback (used if ContactBox is created)
+        @param handle_menu (bool): if True, bind a popup menu to the avatar (used if ContactBox is created)
+        """ # FIXME
         VerticalPanel.__init__(self)
-        self.host = host
-        self.contacts = []
+        self._parent = parent
+        self.host = parent.host
+        self._contacts = {} # entity jid to ContactBox map
         self.click_listener = None
         self.handle_menu = handle_menu
 
         if handle_click:
             def cb(contact_jid):
-                host.displayWidget(chat.Chat, contact_jid, type_=C.CHAT_ONE2ONE)
-            self.click_listener = cb
+                self.host.displayWidget(chat.Chat, contact_jid, type_=C.CHAT_ONE2ONE)
+            self.onClick = cb
 
-    def add(self, jid_, name=None):
-        """Add a contact to the list.
+    def display(self, jids):
+        """Display a contact in the list.
 
-        @param jid_ (jid.JID): jid_ of the contact
+        @param jids (list[jid.JID]): jids to display (the order is kept)
         @param name (unicode): optional name of the contact
         """
-        assert isinstance(jid_, jid.JID)
-        if jid_ in self.contacts:
+        # FIXME: we do a full clear and add boxes after, we should only remove recently hidden boxes and add new ones, and re-order
+        current = [box.jid for box in self.children if isinstance(box, ContactBox)]
+        if current == jids:
+            # the display doesn't change
             return
-        index = 0
-        for contact_ in self.contacts:
-            if contact_ > jid_:
-                break
-            index += 1
-        self.contacts.insert(index, jid_)
-        box = ContactBox(self.host, jid_, name, self.click_listener, self.handle_menu)
-        VerticalPanel.insert(self, box, index)
-
-    def remove(self, jid_):
-        box = self.getContactBox(jid_)
-        if not box:
-            return
-        VerticalPanel.remove(self, box)
-        self.contacts.remove(jid_)
+        self.clear()
+        for jid_ in jids:
+            assert isinstance(jid_, jid.JID)
+            box = self.getContactBox(jid_)
+            VerticalPanel.append(self, box)
 
     def isContactPresent(self, contact_jid):
         """Return True if a contact is present in the panel"""
-        return contact_jid in self.contacts
+        return contact_jid in self._contacts
 
     def getContacts(self):
-        return self.contacts
+        return self._contacts
 
     def getContactBox(self, contact_jid):
-        """get the widget of a contact
+        """get the Contactbox of a contact
 
+        if the Contactbox doesn't exists, it will be created
         @param contact_jid (jid.JID): the contact
-        @return: ContactBox instance if present, else None"""
-        assert isinstance(contact_jid, jid.JID)
-        for wid in self:
-            if isinstance(wid, ContactBox) and wid.jid == contact_jid:
-                return wid
-        return None
+        @return: ContactBox instance
+        """
+        try:
+            return self._contacts[contact_jid.bare]
+        except KeyError:
+            box = ContactBox(self, contact_jid)
+            self._contacts[contact_jid.bare] = box
+            return box
 
     def updateAvatar(self, jid_, url):
         """Update the avatar of the given contact
@@ -294,11 +300,12 @@
 class ContactsPanel(BaseContactsPanel):
     """The contact list that is displayed on the left side."""
 
-    def __init__(self, host):
-        BaseContactsPanel.__init__(self, host, handle_click=True, handle_menu=True)
+    def __init__(self, parent):
+        BaseContactsPanel.__init__(self, parent, handle_click=True, handle_menu=True)
 
     def setState(self, jid_, type_, state):
         """Change the appearance of the contact, according to the state
+
         @param jid_ (jid.JID): jid.JID which need to change state
         @param type_ (unicode): one of "availability", "messageWaiting"
         @param state:
@@ -308,18 +315,16 @@
                 C.PRESENCE_UNAVAILABLE or None if not connected, else presence like RFC6121 #4.7.2.1"""
         assert type_ in ('availability', 'messageWaiting')
         contact_box = self.getContactBox(jid_)
-        if not contact_box:
-            log.warning("No contact box found for {}".format(jid_))
-        else:
-            if type_ == 'availability':
-                if state is None:
-                    state = C.PRESENCE_UNAVAILABLE
-                setPresenceStyle(contact_box.label, state)
-            elif type_ == 'messageWaiting':
-                contact_box.setMessageWaiting(state)
+        if type_ == 'availability':
+            if state is None:
+                state = C.PRESENCE_UNAVAILABLE
+            setPresenceStyle(contact_box.label, state)
+        elif type_ == 'messageWaiting':
+            contact_box.setAlert(state)
 
 
 class ContactTitleLabel(base_widget.DragLabel, Label, ClickHandler):
+
     def __init__(self, host, text):
         Label.__init__(self, text)  # , Element=DOM.createElement('div')
         self.setStyleName('contactTitle')
@@ -337,11 +342,12 @@
     def __init__(self, host):
         QuickContactList.__init__(self, host, C.PROF_KEY_NONE)
         SimplePanel.__init__(self)
+        self.host = host
         self.scroll_panel = ScrollPanel()
         self.vPanel = VerticalPanel()
         _title = ContactTitleLabel(host, 'Contacts')
         DOM.setStyleAttribute(_title.getElement(), "cursor", "pointer")
-        self._contacts_panel = ContactsPanel(host)
+        self._contacts_panel = ContactsPanel(self)
         self._contacts_panel.setStyleName('contactPanel') # FIXME: style doesn't exists !
         self._group_panel = GroupPanel(self)
 
@@ -383,15 +389,13 @@
             self._group_panel.remove(group)
 
         ### JIDS ###
-        current_contacts = set(self._cache.keys())
-        shown_contacts = set(self._contacts_panel.getContacts())
-        new_contacts = current_contacts.difference(shown_contacts)
-        removed_contacts = shown_contacts.difference(current_contacts)
+        to_show = [jid_ for jid_ in self._cache.keys() if self.entityToShow(jid_)]
+        to_show.sort()
 
-        for contact in new_contacts:
-            self._contacts_panel.add(contact)
-        for contact in removed_contacts:
-            self._contacts_panel.remove(contact)
+        self._contacts_panel.display(to_show)
+
+        for jid_ in self._alerts:
+            self._contacts_panel.setState(jid_, "messageWaiting", True)
 
     def onWindowResized(self, width, height):
         contact_panel_elt = self.getElement()
@@ -479,10 +483,12 @@
     #     self.updateVisibility([jid_s], self.getContactGroups(jid_s))
 
     def setContactMessageWaiting(self, jid, waiting):
-        """Show an visual indicator that contact has send a message
+        """Show a visual indicator that contact has send a message
+
         @param jid: jid of the contact
         @param waiting: True if message are waiting"""
-        self._contacts_panel.setState(jid, "messageWaiting", waiting)
+        raise Exception("Should not be there")
+        # self._contacts_panel.setState(jid, "messageWaiting", waiting)
 
     # def getConnected(self, filter_muc=False):
     #     """return a list of all jid (bare jid) connected
@@ -570,6 +576,7 @@
         @param group (unicode): the group to check
         @return: boolean
         """
+        raise Exception # FIXME: remove this method
         for jid_ in self.groups[group]:
             if self._contacts_panel.getContactBox(jid_).isVisible():
                 return True
@@ -580,20 +587,21 @@
 
         @return: boolean
         """
-        return self.host.getCachedParam('General', C.SHOW_OFFLINE_CONTACTS) == 'true'
+        return C.bool(self.host.getCachedParam('General', C.SHOW_OFFLINE_CONTACTS))
 
     def emtyGroupsToShow(self):
         """Tell if empty groups should be visible according to the user settings
 
         @return: boolean
         """
-        return self.host.getCachedParam('General', C.SHOW_EMPTY_GROUPS) == 'true'
+        return C.bool(self.host.getCachedParam('General', C.SHOW_EMPTY_GROUPS))
 
     def updatePresence(self, entity, show, priority, statuses):
         QuickContactList.updatePresence(self, entity, show, priority, statuses)
         entity_bare = entity.bare
         show = self.getCache(entity_bare, C.PRESENCE_SHOW) # we use cache to have the show nformation of main resource only
         self._contacts_panel.setState(entity_bare, "availability", show)
+        self.update()
 
     # def updateVisibility(self, jids, groups):
     #     """Set the widgets visibility for the given contacts and groups
--- a/src/browser/sat_browser/panels.py	Sun Feb 22 21:57:24 2015 +0100
+++ b/src/browser/sat_browser/panels.py	Mon Feb 23 18:47:27 2015 +0100
@@ -389,7 +389,7 @@
 class PresenceStatusMenuBar(base_widget.WidgetMenuBar):
     def __init__(self, parent):
         styles = {'menu_bar': 'presence-button'}
-        base_widget.WidgetMenuBar.__init__(self, None, parent.host, styles=styles)
+        base_widget.WidgetMenuBar.__init__(self, parent, parent.host, styles=styles)
         self.button = self.addCategory(u"◉", u"◉", '')
         for presence, presence_i18n in C.PRESENCE.items():
             html = u'<span class="%s">◉</span> %s' % (contact_list.buildPresenceStyle(presence), presence_i18n)