# HG changeset patch # User souliane # Date 1385214363 -3600 # Node ID 2d6bd975a72d3f5b7fde0392c4956d9ad7d226af # Parent 4517978a2e7ec33d82f39124b55dc8df495b48fb browser_side: set your own presence status and display those of others diff -r 4517978a2e7e -r 2d6bd975a72d browser_side/contact.py --- 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") + diff -r 4517978a2e7e -r 2d6bd975a72d browser_side/list_manager.py --- 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): diff -r 4517978a2e7e -r 2d6bd975a72d browser_side/panels.py --- 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 ' ' HTMLPanel.__init__(self, self.__getContent()) self.setStyleName('statusPanel') - ClickHandler.__init__(self) - self.addClickListener(self) def __getContent(self): return "%(status)s" % {'status': html_sanitize(self.status)} @@ -603,6 +607,49 @@ self.status = new_status or ' ' 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 == " " 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 diff -r 4517978a2e7e -r 2d6bd975a72d browser_side/tools.py --- 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('<','<').replace('>','>') + return html.replace('<', '<').replace('>', '>') 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) diff -r 4517978a2e7e -r 2d6bd975a72d libervia.py --- 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: diff -r 4517978a2e7e -r 2d6bd975a72d libervia.tac --- 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={}): diff -r 4517978a2e7e -r 2d6bd975a72d public/libervia.css --- 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 */