changeset 279:2d6bd975a72d

browser_side: set your own presence status and display those of others
author souliane <souliane@mailoo.org>
date Sat, 23 Nov 2013 14:46:03 +0100
parents 4517978a2e7e
children 1ccdc34cfb60
files browser_side/contact.py browser_side/list_manager.py browser_side/panels.py browser_side/tools.py libervia.py libervia.tac public/libervia.css
diffstat 7 files changed, 127 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/browser_side/contact.py	Fri Nov 22 21:43:08 2013 +0100
+++ b/browser_side/contact.py	Sat Nov 23 14:46:03 2013 +0100
@@ -31,7 +31,7 @@
 from pyjamas import DOM
 
 from browser_side.panels import ChatPanel, MicroblogPanel, PopupMenuPanel, WebPanel
-from browser_side.tools import DragLabel, html_sanitize
+from browser_side.tools import DragLabel, html_sanitize, setPresenceStyle
 from __pyjamas__ import doc
 
 
@@ -161,7 +161,7 @@
         self.context_menu = PopupMenuPanel(entries=self.menu_entries,
                                            hide=self.contextMenuHide,
                                            callback=self.contextMenuCallback,
-                                           vertical=False, menu_style="menu")
+                                           vertical=False, style={"selected": "menu-selected"})
 
     def contextMenuHide(self, sender, key):
         """Return True if the item for that sender should be hidden."""
@@ -194,10 +194,7 @@
         _item = self.getContactLabel(jid)
         if _item:
             if type_ == 'availability':
-                if state == 'unavailable':
-                    _item.removeStyleName('contactConnected')
-                else:
-                    _item.addStyleName('contactConnected')
+                setPresenceStyle(_item, state)
             elif type_ == 'messageWaiting':
                 _item.setMessageWaiting(state)
 
@@ -377,3 +374,4 @@
             for contact in self._contact_list:
                 if contact.jid in self.groups[sender.group]:
                     contact.removeStyleName("selected")
+
--- a/browser_side/list_manager.py	Fri Nov 22 21:43:08 2013 +0100
+++ b/browser_side/list_manager.py	Sat Nov 23 14:46:03 2013 +0100
@@ -236,7 +236,7 @@
 
     def registerPopupMenuPanel(self, entries, hide, callback):
         "Register a popup menu panel that will be bound to all contact keys elements."
-        self.popup_menu = panels.PopupMenuPanel(entries=entries, hide=hide, callback=callback, item_style=self.style["popupMenuItem"])
+        self.popup_menu = panels.PopupMenuPanel(entries=entries, hide=hide, callback=callback, style={"item": self.style["popupMenuItem"]})
 
 
 class DragAutoCompleteTextBox(AutoCompleteTextBox, DragWidget, MouseHandler, FocusHandler):
--- a/browser_side/panels.py	Fri Nov 22 21:43:08 2013 +0100
+++ b/browser_side/panels.py	Sat Nov 23 14:46:03 2013 +0100
@@ -37,13 +37,14 @@
 from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_UP, KEY_DOWN
 from pyjamas.ui.Event import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT
 from pyjamas.ui.MouseListener import MouseHandler
+from pyjamas.ui.ListBox import ListBox
 from pyjamas.Timer import Timer
 from pyjamas import DOM
 from card_game import CardPanel
 from radiocol import RadioColPanel
 from menu import Menu
 from jid import JID
-from tools import html_sanitize, addURLToText, inlineRoot
+from tools import html_sanitize, addURLToText, inlineRoot, setPresenceStyle
 from datetime import datetime
 from time import time
 import dialog
@@ -53,6 +54,11 @@
 from pyjamas import Window
 from __pyjamas__ import doc
 from sat_frontends.tools.games import SYMBOLS
+from sat_frontends import constants
+from pyjamas.ui.ContextMenuPopupPanel import ContextMenuPopupPanel
+
+
+const = constants.Const  # to directly import 'const' doesn't work
 
 
 class UniBoxPanel(HorizontalPanel):
@@ -587,14 +593,12 @@
         return False
 
 
-class StatusPanel(HTMLPanel, ClickHandler):
+class StatusPanel(HTMLPanel):
     def __init__(self, host, status=''):
         self.host = host
         self.status = status or '&nbsp;'
         HTMLPanel.__init__(self, self.__getContent())
         self.setStyleName('statusPanel')
-        ClickHandler.__init__(self)
-        self.addClickListener(self)
 
     def __getContent(self):
         return "<span class='status'>%(status)s</span>" % {'status': html_sanitize(self.status)}
@@ -603,6 +607,49 @@
         self.status = new_status or '&nbsp;'
         self.setHTML(self.__getContent())
 
+
+class PresenceStatusPanel(HorizontalPanel, ClickHandler):
+
+    def __init__(self, host, presence="", status=""):
+        self.host = host
+        HorizontalPanel.__init__(self, Width='100%')
+        self.presence_button = Label(u"◉")
+        self.presence_button.setStyleName("presence-button")
+        self.status_panel = StatusPanel(host, status=status)
+        self.setPresence(presence)
+        entries = {}
+        for value in const.PRESENCE.keys():
+            entries.update({const.PRESENCE[value]: {"value": value}})
+
+        def callback(sender, key):
+            self.setPresence(entries[key]["value"])  # order matters
+            self.host.send([("STATUS", None)], self.status_panel.status)
+
+        self.presence_list = PopupMenuPanel(entries, callback=callback, style={"menu": "gwt-ListBox"})
+        self.presence_list.registerClickSender(self.presence_button)
+
+        panel = HorizontalPanel()
+        panel.add(self.presence_button)
+        panel.add(self.status_panel)
+        panel.setStyleName("marginAuto")
+        self.add(panel)
+
+        ClickHandler.__init__(self)
+        self.addClickListener(self)
+
+    def getPresence(self):
+        return self.presence
+
+    def setPresence(self, presence):
+        status = self.status_panel.status
+        if not status.strip() or status == "&nbsp;" or status == const.PRESENCE[self.presence]:
+            self.changeStatus(const.PRESENCE[presence])
+        self.presence = presence
+        setPresenceStyle(self.presence_button, self.presence)
+
+    def changeStatus(self, new_status):
+        self.status_panel.changeStatus(new_status)
+
     def onClick(self, sender):
         # As status is the default target of uniBar, we don't want to select anything if click on it
         self.host.setSelected(None)
@@ -978,7 +1025,7 @@
     certain items and also easily define their callbacks. The menu can be
     bound to any of the mouse button (left, middle, right).
     """
-    def __init__(self, entries, hide=None, callback=None, vertical=True, item_style="popupMenuItem", menu_style=None, **kwargs):
+    def __init__(self, entries, hide=None, callback=None, vertical=True, style={}, **kwargs):
         """
         @param entries: a dict of dicts, where each sub-dict is representing
         one menu item: the sub-dict key can be used as the item text and
@@ -987,7 +1034,7 @@
         more complicated stuff or overwrite the common methods.
         @param hide: function  with 2 args: widget, key as string and
         returns True if that item should be hidden from the context menu.
-        @param callback: function with 2 args: widget, key as string
+        @param callback: function with 2 args: sender, key as string
         @param vertical: True or False, to set the direction
         @param item_style: alternative CSS class for the menu items
         @param menu_style: supplementary CSS class for the sender widget
@@ -997,8 +1044,8 @@
         self._hide = hide
         self._callback = callback
         self.vertical = vertical
-        self.item_style = item_style
-        self.menu_style = menu_style
+        self.style = {"selected": None, "menu": "recipientTypeMenu", "item": "popupMenuItem"}
+        self.style.update(style)
         self._senders = {}
 
     def _show(self, sender):
@@ -1006,7 +1053,7 @@
         @param sender: the widget that has been clicked
         """
         menu = VerticalPanel() if self.vertical is True else HorizontalPanel()
-        menu.setStyleName("recipientTypeMenu")
+        menu.setStyleName(self.style["menu"])
 
         def button_cb(item):
             """You can not put that method in the loop and rely
@@ -1026,7 +1073,7 @@
             title = entry["title"] if "title" in entry.keys() else _key
             item = Button(title, button_cb)
             item.key = _key
-            item.setStyleName(self.item_style)
+            item.setStyleName(self.style["item"])
             item.setTitle(entry["desc"] if "desc" in entry.keys() else title)
             menu.add(item)
         if len(menu.getChildren()) == 0:
@@ -1040,12 +1087,12 @@
             y = sender.getAbsoluteTop() + sender.getOffsetHeight()
         self.setPopupPosition(x, y)
         self.show()
-        if self.menu_style:
-            sender.addStyleDependentName(self.menu_style)
+        if self.style["selected"]:
+            sender.addStyleDependentName(self.style["selected"])
 
         def _onHide(popup):
-            if hasattr(self, "menu_style") and self.menu_style is not None:
-                sender.removeStyleDependentName(self.menu_style)
+            if self.style["selected"]:
+                sender.removeStyleDependentName(self.style["selected"])
             return PopupPanel.onHideImpl(self, popup)
 
         self.onHideImpl = _onHide
--- a/browser_side/tools.py	Fri Nov 22 21:43:08 2013 +0100
+++ b/browser_side/tools.py	Sat Nov 23 14:46:03 2013 +0100
@@ -26,9 +26,10 @@
 
 dom = NativeDOM()
 
+
 def html_sanitize(html):
     """Naive sanitization of HTML"""
-    return html.replace('<','&lt;').replace('>','&gt;')
+    return html.replace('<', '&lt;').replace('>', '&gt;')
 
 
 def inlineRoot(xhtml):
@@ -36,6 +37,7 @@
     doc = dom.parseString(xhtml)
     return xml.inlineRoot(doc)
 
+
 def addURLToText(string):
     """Check a text for what looks like an URL and make it clickable. Regexp
     from http://daringfireball.net/2010/07/improved_regex_for_matching_urls"""
@@ -49,6 +51,22 @@
     return re.sub(pattern, repl, string)
 
 
+def setPresenceStyle(item, state, base_style="contact"):
+    """
+    @item: any UI element
+    @state: a value from ("", "chat", "away", "dnd", "xa")
+    """
+    if not hasattr(item, 'presence_style'):
+        item.presence_style = None
+    style = '%s-%s' % (base_style, state or 'connected')
+    if style == item.presence_style:
+        return
+    if item.presence_style is not None:
+        item.removeStyleName(item.presence_style)
+    item.addStyleName(style)
+    item.presence_style = style
+
+
 class DragLabel(DragWidget):
 
     def __init__(self, text, _type):
@@ -61,9 +79,10 @@
         dt.setData('text/plain', "%s\n%s" % (self._text, self._type))
         dt.setDragImage(self.getElement(), 15, 15)
 
+
 class LiberviaDragWidget(DragLabel):
     """ A DragLabel which keep the widget being dragged as class value """
-    current = None # widget currently dragged
+    current = None  # widget currently dragged
 
     def __init__(self, text, _type, widget):
         DragLabel.__init__(self, text, _type)
--- a/libervia.py	Fri Nov 22 21:43:08 2013 +0100
+++ b/libervia.py	Sat Nov 23 14:46:03 2013 +0100
@@ -161,7 +161,7 @@
         self.bridge = BridgeCall()
         self.bridge_signals = BridgeSignals(self)
         self.uni_box = None
-        self.status_panel = panels.StatusPanel(self)
+        self.status_panel = panels.PresenceStatusPanel(self)
         self.contact_panel = ContactPanel(self)
         self.panel = panels.MainPanel(self)
         self.discuss_panel = self.panel.discuss_panel
@@ -577,6 +577,7 @@
         _entity = JID(entity)
         #XXX: QnD way to get our status
         if self.whoami and self.whoami.bare == _entity.bare and statuses:
+            self.status_panel.setPresence(show)
             self.status_panel.changeStatus(statuses.values()[0])
         if (not self.whoami or self.whoami.bare != _entity.bare):
             self.contact_panel.setConnected(_entity.bare, _entity.resource, show, priority, statuses)
@@ -738,7 +739,7 @@
             elif type_ == "COMMENT":
                 self.bridge.call("sendMblogComment", None, entities, text, extra)
             elif type_ == "STATUS":
-                self.bridge.call('setStatus', None, text)
+                self.bridge.call('setStatus', None, self.status_panel.presence, text)
             elif type_ in ("groupchat", "chat"):
                 self.bridge.call('sendMessage', None, entities, text, '', type_, extra)
             else:
--- a/libervia.tac	Fri Nov 22 21:43:08 2013 +0100
+++ b/libervia.tac	Sat Nov 23 14:46:03 2013 +0100
@@ -217,10 +217,13 @@
         profile = ISATSession(self.session).profile
         return self.sat_host.bridge.getWaitingSub(profile)
 
-    def jsonrpc_setStatus(self, status):
-        """Change the status"""
+    def jsonrpc_setStatus(self, presence, status):
+        """Change the presence and/or status
+        @param presence: value from ("", "chat", "away", "dnd", "xa")
+        @param status: any string to describe your status
+        """
         profile = ISATSession(self.session).profile
-        self.sat_host.bridge.setPresence('', '', 0, {'':status}, profile)
+        self.sat_host.bridge.setPresence('', presence, 0, {'': status}, profile)
 
 
     def jsonrpc_sendMessage(self, to_jid, msg, subject, _type, options={}):
--- a/public/libervia.css	Fri Nov 22 21:43:08 2013 +0100
+++ b/public/libervia.css	Sat Nov 23 14:46:03 2013 +0100
@@ -368,6 +368,7 @@
 .contactsChooser {
     text-align: center;
     margin:auto;
+    cursor: pointer;
 }
 
 .infoDialogBody {
@@ -454,7 +455,7 @@
     padding: 3px 10px 3px 10px;
 }
 
-.contact-menu {
+.contact-menu-selected {
     font-size: 1em;
     margin-top: 3px;
     padding: 3px 10px 3px 10px;
@@ -462,10 +463,30 @@
 	background-color: rgb(175, 175, 175);
 }
 
-.contactConnected {
+/* START - contact presence status */
+.contact-connected {
+    color: #3c7e0c;
+    font-weight: bold;
+}
+.contact-unavailable {
+}
+.contact-chat {
     color: #3c7e0c;
     font-weight: bold;
 }
+.contact-away {
+    color: brown;
+    font-weight: bold;
+}
+.contact-dnd {
+    color: red;
+    font-weight: bold;
+}
+.contact-xa {
+    color: red;
+    font-weight: bold;
+}
+/* END - contact presence status */
 
 .selected {
     color: #fff;
@@ -517,6 +538,12 @@
     border-bottom: 1px solid #ddd;
 }
 
+.presence-button {
+	font-size: x-large;
+	padding-right: 5px;
+	cursor: pointer;
+}
+
 .status {
     font-style: italic;
     font-weight: bold;
@@ -749,6 +776,7 @@
 .mb_entry_header
 {
     background: #AFAFAF;
+    cursor: pointer;
 }
 
 .selected_widget .selected_entry .mb_entry_header
@@ -1264,6 +1292,7 @@
 .popupMenuItem {
     cursor: pointer;
     border-radius: 5px;
+    width: 100%;
 }
 
 /* Contact group manager */