Mercurial > libervia-web
view browser_side/base_panels.py @ 347:f1ba38043d78
browser_side: status panel is based on a new class LightTextEditor which uses HTML5 "editablecontent" property
author | souliane <souliane@mailoo.org> |
---|---|
date | Fri, 07 Feb 2014 20:14:11 +0100 |
parents | 82f9e92379b0 |
children | f488692c4903 |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- # Libervia: a Salut à Toi frontend # Copyright (C) 2011, 2012, 2013, 2014 Jérôme Poisson <goffi@goffi.org> # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import pyjd # this is dummy in pyjs from pyjamas.ui.AbsolutePanel import AbsolutePanel from pyjamas.ui.VerticalPanel import VerticalPanel from pyjamas.ui.HorizontalPanel import HorizontalPanel from pyjamas.ui.HTMLPanel import HTMLPanel from pyjamas.ui.Button import Button from pyjamas.ui.HTML import HTML from pyjamas.ui.PopupPanel import PopupPanel from pyjamas.ui.StackPanel import StackPanel from pyjamas.ui.TextArea import TextArea from pyjamas.ui.Event import BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT from pyjamas.ui.KeyboardListener import KEY_ENTER, KeyboardHandler from pyjamas.ui.FocusListener import FocusHandler from pyjamas import DOM from datetime import datetime from time import time from tools import html_sanitize, html_clean, inlineRoot from sat_frontends.tools.strings import addURLToText from sat.core.i18n import _ class ChatText(HTMLPanel): def __init__(self, timestamp, nick, mymess, msg, xhtml=None): _date = datetime.fromtimestamp(float(timestamp or time())) _msg_class = ["chat_text_msg"] if mymess: _msg_class.append("chat_text_mymess") HTMLPanel.__init__(self, "<span class='chat_text_timestamp'>%(timestamp)s</span> <span class='chat_text_nick'>%(nick)s</span> <span class='%(msg_class)s'>%(msg)s</span>" % {"timestamp": _date.strftime("%H:%M"), "nick": "[%s]" % html_sanitize(nick), "msg_class": ' '.join(_msg_class), "msg": addURLToText(html_sanitize(msg)) if not xhtml else inlineRoot(xhtml)} # FIXME: images and external links must be removed according to preferences ) self.setStyleName('chatText') class Occupant(HTML): """Occupant of a MUC room""" def __init__(self, nick, special=""): HTML.__init__(self) self.nick = nick self.special = special self._refresh() def __str__(self): return self.nick def addSpecial(self, special): """@param special: unicode""" if special not in self.special: self.special += special self._refresh() def removeSpecials(self, special): """@param special: unicode or list""" if not isinstance(special, list): special = [special] for symbol in special: self.special = self.special.replace(symbol, "") self._refresh() def _refresh(self): special = "" if len(self.special) == 0 else " %s" % self.special self.setHTML("<div class='occupant'>%s%s</div>" % (html_sanitize(self.nick), special)) class OccupantsList(AbsolutePanel): """Panel user to show occupants of a room""" def __init__(self): AbsolutePanel.__init__(self) self.occupants_list = {} self.setStyleName('occupantsList') def addOccupant(self, nick): _occupant = Occupant(nick) self.occupants_list[nick] = _occupant self.add(_occupant) def removeOccupant(self, nick): try: self.remove(self.occupants_list[nick]) except KeyError: print "ERROR: trying to remove an unexisting nick" def clear(self): self.occupants_list.clear() AbsolutePanel.clear(self) def updateSpecials(self, occupants=[], html=""): """Set the specified html "symbol" to the listed occupants, and eventually remove it from the others (if they got it). This is used for example to visualize who is playing a game. @param occupants: list of the occupants that need the symbol @param html: unicode symbol (actually one character or more) or a list to assign different symbols of the same family. """ index = 0 special = html for occupant in self.occupants_list.keys(): if occupant in occupants: if isinstance(html, list): special = html[index] index = (index + 1) % len(html) self.occupants_list[occupant].addSpecial(special) else: self.occupants_list[occupant].removeSpecials(html) class PopupMenuPanel(PopupPanel): """This implementation of a popup menu (context menu) allow you to assign two special methods which are common to all the items, in order to hide 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, 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 description, but optional "title" and "desc" entries would be used if they exists. The sub-dicts may be extended later to do 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: 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 """ PopupPanel.__init__(self, autoHide=True, **kwargs) self._entries = entries self._hide = hide self._callback = callback self.vertical = vertical self.style = {"selected": None, "menu": "recipientTypeMenu", "item": "popupMenuItem"} self.style.update(style) self._senders = {} def _show(self, sender): """Popup the menu relative to this sender's position. @param sender: the widget that has been clicked """ menu = VerticalPanel() if self.vertical is True else HorizontalPanel() menu.setStyleName(self.style["menu"]) def button_cb(item): """You can not put that method in the loop and rely on _key, because it is overwritten by each step. You can rely on item.key instead, which is copied from _key after the item creation. @param item: the menu item that has been clicked """ if self._callback is not None: self._callback(sender=sender, key=item.key) self.hide(autoClosed=True) for _key in self._entries.keys(): entry = self._entries[_key] if self._hide is not None and self._hide(sender=sender, key=_key) is True: continue title = entry["title"] if "title" in entry.keys() else _key item = Button(title, button_cb) item.key = _key item.setStyleName(self.style["item"]) item.setTitle(entry["desc"] if "desc" in entry.keys() else title) menu.add(item) if len(menu.getChildren()) == 0: return self.add(menu) if self.vertical is True: x = sender.getAbsoluteLeft() + sender.getOffsetWidth() y = sender.getAbsoluteTop() else: x = sender.getAbsoluteLeft() y = sender.getAbsoluteTop() + sender.getOffsetHeight() self.setPopupPosition(x, y) self.show() if self.style["selected"]: sender.addStyleDependentName(self.style["selected"]) def _onHide(popup): if self.style["selected"]: sender.removeStyleDependentName(self.style["selected"]) return PopupPanel.onHideImpl(self, popup) self.onHideImpl = _onHide def registerClickSender(self, sender, button=BUTTON_LEFT): """Bind the menu to the specified sender. @param sender: the widget to which the menu should be bound @param: BUTTON_LEFT, BUTTON_MIDDLE or BUTTON_RIGHT """ self._senders.setdefault(sender, []) self._senders[sender].append(button) if button == BUTTON_RIGHT: # WARNING: to disable the context menu is a bit tricky... # The following seems to work on Firefox 24.0, but: # TODO: find a cleaner way to disable the context menu sender.getElement().setAttribute("oncontextmenu", "return false") def _onBrowserEvent(event): button = DOM.eventGetButton(event) if DOM.eventGetType(event) == "mousedown" and button in self._senders[sender]: self._show(sender) return sender.__class__.onBrowserEvent(sender, event) sender.onBrowserEvent = _onBrowserEvent def registerMiddleClickSender(self, sender): self.registerClickSender(sender, BUTTON_MIDDLE) def registerRightClickSender(self, sender): self.registerClickSender(sender, BUTTON_RIGHT) class ToggleStackPanel(StackPanel): """This is a pyjamas.ui.StackPanel with modified behavior. All sub-panels ca be visible at the same time, clicking a sub-panel header will not display it and hide the others but only toggle its own visibility. The argument 'visibleStack' is ignored. Note that the argument 'visible' has been added to listener's 'onStackChanged' method. """ def __init__(self, **kwargs): StackPanel.__init__(self, **kwargs) def onBrowserEvent(self, event): if DOM.eventGetType(event) == "click": index = self.getDividerIndex(DOM.eventGetTarget(event)) if index != -1: self.toggleStack(index) def add(self, widget, stackText="", asHTML=False, visible=False): StackPanel.add(self, widget, stackText, asHTML) self.setStackVisible(self.getWidgetCount() - 1, visible) def toggleStack(self, index): if index >= self.getWidgetCount(): return visible = not self.getWidget(index).getVisible() self.setStackVisible(index, visible) for listener in self.stackListeners: listener.onStackChanged(self, index, visible) class TitlePanel(ToggleStackPanel): """A toggle panel to set the message title""" def __init__(self): ToggleStackPanel.__init__(self, Width="100%") self.text_area = TextArea() self.add(self.text_area, _("Title")) self.addStackChangeListener(self) def onStackChanged(self, sender, index, visible=None): if visible is None: visible = sender.getWidget(index).getVisible() text = self.text_area.getText() suffix = "" if (visible or not text) else (": %s" % text) sender.setStackText(index, _("Title") + suffix) def getText(self): return self.text_area.getText() def setText(self, text): self.text_area.setText(text) class LightTextEditor(HTML, FocusHandler, KeyboardHandler): def __init__(self, content='', single_line=False, callback=None): HTML.__init__(self) if single_line: FocusHandler.__init__(self) KeyboardHandler.__init__(self) self.__single_line = single_line self.__callback = callback self.setContent(content) def setContent(self, content=''): self.__original_content = html_clean(content) if self.__single_line else content.strip() self.setHTML('<div>%s</div>' % self.__original_content) def getContent(self): content = DOM.getInnerHTML(self.getElement().firstChild) return html_clean(content) if self.__single_line else content.strip() def modified(self, content=None): if content is None: content = self.getContent() return content != self.__original_content def edit(self, edit): self.getElement().setAttribute('contenteditable', 'true' if edit else 'false') if edit: self.setFocus(True) if self.__single_line: self.addFocusListener(self) self.addKeyboardListener(self) else: if self.__single_line: self.removeFocusListener(self) self.removeKeyboardListener(self) content = self.getContent() if self.modified(content) and self.__callback: self.__callback(content) def setFocus(self, focus): if focus: self.getElement().focus() else: self.getElement().blur() def onKeyDown(self, sender, keycode, modifiers): if keycode == KEY_ENTER: self.setFocus(False) def onLostFocus(self, sender): self.edit(False)