Mercurial > libervia-web
diff src/browser/sat_browser/panels.py @ 589:a5019e62c3e9 frontends_multi_profiles
browser side: big refactoring to base Libervia on QuickFrontend, first draft:
/!\ not finished, partially working and highly instable
- add collections module with an OrderedDict like class
- SatWebFrontend inherit from QuickApp
- general sat_frontends tools.jid module is used
- bridge/json methods have moved to json module
- UniBox is partially removed (should be totally removed before merge to trunk)
- Signals are now register with the generic registerSignal method (which is called mainly in QuickFrontend)
- the generic getOrCreateWidget method from QuickWidgetsManager is used instead of Libervia's specific methods
- all Widget are now based more or less directly on QuickWidget
- with the new QuickWidgetsManager.getWidgets method, it's no more necessary to check all widgets which are instance of a particular class
- ChatPanel and related moved to chat module
- MicroblogPanel and related moved to blog module
- global and overcomplicated send method has been disabled: each class should manage its own sending
- for consistency with other frontends, former ContactPanel has been renamed to ContactList and vice versa
- for the same reason, ChatPanel has been renamed to Chat
- for compatibility with QuickFrontend, a fake profile is used in several places, it is set to C.PROF_KEY_NONE (real profile is managed server side for obvious security reasons)
- changed default url for web panel to SàT website, and contact address to generic SàT contact address
- ContactList is based on QuickContactList, UI changes are done in update method
- bride call (now json module) have been greatly improved, in particular call can be done in the same way as for other frontends (bridge.method_name(arg1, arg2, ..., callback=cb, errback=eb). Blocking method must be called like async methods due to javascript architecture
- in bridge calls, a callback can now exists without errback
- hard reload on BridgeSignals remote error has been disabled, a better option should be implemented
- use of constants where that make sens, some style improvments
- avatars are temporarily disabled
- lot of code disabled, will be fixed or removed before merge
- various other changes, check diff for more details
server side: manage remote exception on getEntityData, removed getProfileJid call, added getWaitingConf, added getRoomsSubjects
author | Goffi <goffi@goffi.org> |
---|---|
date | Sat, 24 Jan 2015 01:45:39 +0100 |
parents | bade589dbd5a |
children | 5983d6be4f07 |
line wrap: on
line diff
--- a/src/browser/sat_browser/panels.py Thu Oct 23 16:56:36 2014 +0200 +++ b/src/browser/sat_browser/panels.py Sat Jan 24 01:45:39 2015 +0100 @@ -22,102 +22,82 @@ log = getLogger(__name__) from sat_frontends.tools.strings import addURLToText -from sat_frontends.tools.games import SYMBOLS -from sat.core.i18n import _ -from pyjamas.ui.SimplePanel import SimplePanel 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.Frame import Frame from pyjamas.ui.TextArea import TextArea -from pyjamas.ui.Label import Label from pyjamas.ui.Button import Button from pyjamas.ui.HTML import HTML -from pyjamas.ui.Image import Image from pyjamas.ui.ClickListener import ClickHandler -from pyjamas.ui.FlowPanel import FlowPanel -from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_UP, KEY_DOWN, KeyboardHandler +from pyjamas.ui.KeyboardListener import KEY_ENTER, KEY_UP, KEY_DOWN from pyjamas.ui.MouseListener import MouseHandler -from pyjamas.ui.FocusListener import FocusHandler +from pyjamas.ui.Frame import Frame from pyjamas.Timer import Timer +from pyjamas import Window from pyjamas import DOM -from pyjamas import Window from __pyjamas__ import doc -from datetime import datetime -from time import time -import jid -import html_tools import base_panels import base_menu -import card_game -import radiocol import menu import dialog import base_widget -import richtext -import contact +import contact_list from constants import Const as C -import plugin_xep_0085 - - -# TODO: at some point we should decide which behaviors to keep and remove these two constants -TOGGLE_EDITION_USE_ICON = False # set to True to use an icon inside the "toggle syntax" button -NEW_MESSAGE_USE_BUTTON = False # set to True to display the "New message" button instead of an empty entry +from sat_frontends.quick_frontend import quick_widgets -class UniBoxPanel(HorizontalPanel): - """Panel containing the UniBox""" - - def __init__(self, host): - HorizontalPanel.__init__(self) - self.host = host - self.setStyleName('uniBoxPanel') - self.unibox = None - - def refresh(self): - """Enable or disable this panel. Contained widgets are created when necessary.""" - enable = self.host.getCachedParam(C.COMPOSITION_KEY, C.ENABLE_UNIBOX_PARAM) == 'true' - self.setVisible(enable) - if enable and not self.unibox: - self.button = Button('<img src="media/icons/tango/actions/32/format-text-italic.png" class="richTextIcon"/>') - self.button.setTitle('Open the rich text editor') - self.button.addStyleName('uniBoxButton') - self.add(self.button) - self.unibox = UniBox(self.host) - self.add(self.unibox) - self.setCellWidth(self.unibox, '100%') - self.button.addClickListener(self.openRichMessageEditor) - self.unibox.addKey("@@: ") - self.unibox.onSelectedChange(self.host.getSelected()) - - def openRichMessageEditor(self): - """Open the rich text editor.""" - self.button.setVisible(False) - self.unibox.setVisible(False) - self.setCellWidth(self.unibox, '0px') - self.host.panel._contactsMove(self) - - def afterEditCb(): - Window.removeWindowResizeListener(self) - self.host.panel._contactsMove(self.host.panel._hpanel) - self.setCellWidth(self.unibox, '100%') - self.button.setVisible(True) - self.unibox.setVisible(True) - self.host.resize() - - richtext.RichMessageEditor.getOrCreate(self.host, self, afterEditCb) - Window.addWindowResizeListener(self) - self.host.resize() - - def onWindowResized(self, width, height): - right = self.host.panel.menu.getAbsoluteLeft() + self.host.panel.menu.getOffsetWidth() - left = self.host.panel._contacts.getAbsoluteLeft() + self.host.panel._contacts.getOffsetWidth() - ideal_width = right - left - 40 - self.host.richtext.setWidth("%spx" % ideal_width) +# class UniBoxPanel(HorizontalPanel): +# """Panel containing the UniBox""" +# +# def __init__(self, host): +# HorizontalPanel.__init__(self) +# self.host = host +# self.setStyleName('uniBoxPanel') +# self.unibox = None +# +# def refresh(self): +# """Enable or disable this panel. Contained widgets are created when necessary.""" +# enable = self.host.getCachedParam(C.COMPOSITION_KEY, C.ENABLE_UNIBOX_PARAM) == 'true' +# self.setVisible(enable) +# if enable and not self.unibox: +# self.button = Button('<img src="media/icons/tango/actions/32/format-text-italic.png" class="richTextIcon"/>') +# self.button.setTitle('Open the rich text editor') +# self.button.addStyleName('uniBoxButton') +# self.add(self.button) +# self.unibox = UniBox(self.host) +# self.add(self.unibox) +# self.setCellWidth(self.unibox, '100%') +# self.button.addClickListener(self.openRichMessageEditor) +# self.unibox.addKey("@@: ") +# self.unibox.onSelectedChange(self.host.getSelected()) +# +# def openRichMessageEditor(self): +# """Open the rich text editor.""" +# self.button.setVisible(False) +# self.unibox.setVisible(False) +# self.setCellWidth(self.unibox, '0px') +# self.host.panel._contactsMove(self) +# +# def afterEditCb(): +# Window.removeWindowResizeListener(self) +# self.host.panel._contactsMove(self.host.panel._hpanel) +# self.setCellWidth(self.unibox, '100%') +# self.button.setVisible(True) +# self.unibox.setVisible(True) +# self.host.resize() +# +# richtext.RichMessageEditor.getOrCreate(self.host, self, afterEditCb) +# Window.addWindowResizeListener(self) +# self.host.resize() +# +# def onWindowResized(self, width, height): +# right = self.host.panel.menu.getAbsoluteLeft() + self.host.panel.menu.getOffsetWidth() +# left = self.host.panel._contacts.getAbsoluteLeft() + self.host.panel._contacts.getOffsetWidth() +# ideal_width = right - left - 40 +# self.host.richtext.setWidth("%spx" % ideal_width) class MessageBox(TextArea): @@ -126,12 +106,11 @@ def __init__(self, host): TextArea.__init__(self) self.host = host - self.__size = (0, 0) + self.size = (0, 0) self.setStyleName('messageBox') self.addKeyboardListener(self) MouseHandler.__init__(self) self.addMouseListener(self) - self._selected_cache = None def onBrowserEvent(self, event): # XXX: woraroung a pyjamas bug: self.currentEvent is not set @@ -149,8 +128,8 @@ if keycode == KEY_ENTER: if _txt: - self._selected_cache.onTextEntered(_txt) - self.host._updateInputHistory(_txt) + self.host.selected_widget.onTextEntered(_txt) + self.host._updateInputHistory(_txt) # FIXME: why using a global variable ? self.setText('') sender.cancelKey() elif keycode == KEY_UP: @@ -158,140 +137,140 @@ elif keycode == KEY_DOWN: self.host._updateInputHistory(_txt, +1, history_cb) else: - self.__onComposing() + self._onComposing() - def __onComposing(self): + def _onComposing(self): """Callback when the user is composing a text.""" - if hasattr(self._selected_cache, "target"): - self._selected_cache.state_machine._onEvent("composing") + self.host.selected_widget.state_machine._onEvent("composing") def onMouseUp(self, sender, x, y): size = (self.getOffsetWidth(), self.getOffsetHeight()) - if size != self.__size: - self.__size = size + if size != self.size: + self.size = size self.host.resize() def onSelectedChange(self, selected): self._selected_cache = selected -class UniBox(MessageBox, MouseHandler): # AutoCompleteTextBox): - """This text box is used as a main typing point, for message, microblog, etc""" - - def __init__(self, host): - MessageBox.__init__(self, host) - #AutoCompleteTextBox.__init__(self) - self.setStyleName('uniBox') - host.addSelectedListener(self.onSelectedChange) - - def addKey(self, key): - return - #self.getCompletionItems().completions.append(key) - - def removeKey(self, key): - return - # TODO: investigate why AutoCompleteTextBox doesn't work here, - # maybe it can work on a TextBox but no TextArea. Remove addKey - # and removeKey methods if they don't serve anymore. - try: - self.getCompletionItems().completions.remove(key) - except KeyError: - log.warning("trying to remove an unknown key") - - def _getTarget(self, txt): - """ Say who will receive the messsage - @return: a tuple (selected, target_type, target info) with: - - target_hook: None if we use the selected widget, (msg, data) if we have a hook (e.g. "@@: " for a public blog), where msg is the parsed message (i.e. without the "hook key: "@@: bla" become ("bla", None)) - - target_type: one of PUBLIC, GROUP, ONE2ONE, STATUS, MISC - - msg: HTML message which will appear in the privacy warning banner """ - target = self._selected_cache - - def getSelectedOrStatus(): - if target and target.isSelectable(): - _type, msg = target.getWarningData() - target_hook = None # we use the selected widget, not a hook - else: - _type, msg = "STATUS", "This will be your new status message" - target_hook = (txt, None) - return (target_hook, _type, msg) - - if not txt.startswith('@'): - target_hook, _type, msg = getSelectedOrStatus() - elif txt.startswith('@@: '): - _type = "PUBLIC" - msg = MicroblogPanel.warning_msg_public - target_hook = (txt[4:], None) - elif txt.startswith('@'): - _end = txt.find(': ') - if _end == -1: - target_hook, _type, msg = getSelectedOrStatus() - else: - group = txt[1:_end] # only one target group is managed for the moment - if not group or not group in self.host.contact_panel.getGroups(): - # the group doesn't exists, we ignore the key - group = None - target_hook, _type, msg = getSelectedOrStatus() - else: - _type = "GROUP" - msg = MicroblogPanel.warning_msg_group % group - target_hook = (txt[_end + 2:], group) - else: - log.error("Unknown target") - target_hook, _type, msg = getSelectedOrStatus() - - return (target_hook, _type, msg) - - def onKeyPress(self, sender, keycode, modifiers): - _txt = self.getText() - target_hook, type_, msg = self._getTarget(_txt) - - if keycode == KEY_ENTER: - if _txt: - if target_hook: - parsed_txt, data = target_hook - self.host.send([(type_, data)], parsed_txt) - self.host._updateInputHistory(_txt) - self.setText('') - self.host.showWarning(None, None) - else: - self.host.showWarning(type_, msg) - MessageBox.onKeyPress(self, sender, keycode, modifiers) - - def getTargetAndData(self): - """For external use, to get information about the (hypothetical) message - that would be sent if we press Enter right now in the unibox. - @return a tuple (target, data) with: - - data: what would be the content of the message (body) - - target: JID, group with the prefix "@" or the public entity "@@" - """ - _txt = self.getText() - target_hook, _type, _msg = self._getTarget(_txt) - if target_hook: - data, target = target_hook - if target is None: - return target_hook - return (data, "@%s" % (target if target != "" else "@")) - if isinstance(self._selected_cache, MicroblogPanel): - groups = self._selected_cache.accepted_groups - target = "@%s" % (groups[0] if len(groups) > 0 else "@") - if len(groups) > 1: - Window.alert("Sole the first group of the selected panel is taken in consideration: '%s'" % groups[0]) - elif isinstance(self._selected_cache, ChatPanel): - target = self._selected_cache.target - else: - target = None - return (_txt, target) - - def onWidgetClosed(self, lib_wid): - """Called when a libervia widget is closed""" - if self._selected_cache == lib_wid: - self.onSelectedChange(None) - - """def complete(self): - - #self.visible=False #XXX: self.visible is not unset in pyjamas when ENTER is pressed and a completion is done - #XXX: fixed directly on pyjamas, if the patch is accepted, no need to walk around this - return AutoCompleteTextBox.complete(self)""" +# class UniBox(MessageBox, MouseHandler): # AutoCompleteTextBox): +# """This text box is used as a main typing point, for message, microblog, etc""" +# +# def __init__(self, host): +# MessageBox.__init__(self, host) +# #AutoCompleteTextBox.__init__(self) +# self.setStyleName('uniBox') +# # FIXME +# # host.addSelectedListener(self.onSelectedChange) +# +# def addKey(self, key): +# return +# #self.getCompletionItems().completions.append(key) +# +# def removeKey(self, key): +# return +# # TODO: investigate why AutoCompleteTextBox doesn't work here, +# # maybe it can work on a TextBox but no TextArea. Remove addKey +# # and removeKey methods if they don't serve anymore. +# try: +# self.getCompletionItems().completions.remove(key) +# except KeyError: +# log.warning("trying to remove an unknown key") +# +# def _getTarget(self, txt): +# """ Say who will receive the messsage +# @return: a tuple (selected, target_type, target info) with: +# - target_hook: None if we use the selected widget, (msg, data) if we have a hook (e.g. "@@: " for a public blog), where msg is the parsed message (i.e. without the "hook key: "@@: bla" become ("bla", None)) +# - target_type: one of PUBLIC, GROUP, ONE2ONE, STATUS, MISC +# - msg: HTML message which will appear in the privacy warning banner """ +# target = self._selected_cache +# +# def getSelectedOrStatus(): +# if target and target.isSelectable(): +# _type, msg = target.getWarningData() +# target_hook = None # we use the selected widget, not a hook +# else: +# _type, msg = "STATUS", "This will be your new status message" +# target_hook = (txt, None) +# return (target_hook, _type, msg) +# +# if not txt.startswith('@'): +# target_hook, _type, msg = getSelectedOrStatus() +# elif txt.startswith('@@: '): +# _type = "PUBLIC" +# msg = MicroblogPanel.warning_msg_public +# target_hook = (txt[4:], None) +# elif txt.startswith('@'): +# _end = txt.find(': ') +# if _end == -1: +# target_hook, _type, msg = getSelectedOrStatus() +# else: +# group = txt[1:_end] # only one target group is managed for the moment +# if not group or not group in self.host.contact_panel.getGroups(): +# # the group doesn't exists, we ignore the key +# group = None +# target_hook, _type, msg = getSelectedOrStatus() +# else: +# _type = "GROUP" +# msg = MicroblogPanel.warning_msg_group % group +# target_hook = (txt[_end + 2:], group) +# else: +# log.error("Unknown target") +# target_hook, _type, msg = getSelectedOrStatus() +# +# return (target_hook, _type, msg) +# +# def onKeyPress(self, sender, keycode, modifiers): +# _txt = self.getText() +# target_hook, type_, msg = self._getTarget(_txt) +# +# if keycode == KEY_ENTER: +# if _txt: +# if target_hook: +# parsed_txt, data = target_hook +# self.host.send([(type_, data)], parsed_txt) +# self.host._updateInputHistory(_txt) +# self.setText('') +# self.host.showWarning(None, None) +# else: +# self.host.showWarning(type_, msg) +# MessageBox.onKeyPress(self, sender, keycode, modifiers) +# +# def getTargetAndData(self): +# """For external use, to get information about the (hypothetical) message +# that would be sent if we press Enter right now in the unibox. +# @return a tuple (target, data) with: +# - data: what would be the content of the message (body) +# - target: JID, group with the prefix "@" or the public entity "@@" +# """ +# _txt = self.getText() +# target_hook, _type, _msg = self._getTarget(_txt) +# if target_hook: +# data, target = target_hook +# if target is None: +# return target_hook +# return (data, "@%s" % (target if target != "" else "@")) +# if isinstance(self._selected_cache, MicroblogPanel): +# groups = self._selected_cache.accepted_groups +# target = "@%s" % (groups[0] if len(groups) > 0 else "@") +# if len(groups) > 1: +# Window.alert("Sole the first group of the selected panel is taken in consideration: '%s'" % groups[0]) +# # elif isinstance(self._selected_cache, ChatPanel): # FIXME +# # target = self._selected_cache.target +# else: +# target = None +# return (_txt, target) +# +# def onWidgetClosed(self, lib_wid): +# """Called when a libervia widget is closed""" +# if self._selected_cache == lib_wid: +# self.onSelectedChange(None) +# +# """def complete(self): +# +# #self.visible=False #XXX: self.visible is not unset in pyjamas when ENTER is pressed and a completion is done +# #XXX: fixed directly on pyjamas, if the patch is accepted, no need to walk around this +# return AutoCompleteTextBox.complete(self)""" class WarningPopup(): @@ -303,21 +282,21 @@ def showWarning(self, type_=None, msg=None, duration=2000): """Display a popup information message, e.g. to notify the recipient of a message being composed. If type_ is None, a popup being currently displayed will be hidden. - @type_: a type determining the CSS style to be applied (see __showWarning) + @type_: a type determining the CSS style to be applied (see _showWarning) @msg: message to be displayed """ if type_ is None: self.__removeWarning() return if not self._popup: - self.__showWarning(type_, msg) + self._showWarning(type_, msg) elif (type_, msg) != self._popup.target_data: self._timeCb(None) # we remove the popup - self.__showWarning(type_, msg) + self._showWarning(type_, msg) self._timer.schedule(duration) - def __showWarning(self, type_, msg): + def _showWarning(self, type_, msg): """Display a popup information message, e.g. to notify the recipient of a message being composed. @type_: a type determining the CSS style to be applied. For now the defined styles are "NONE" (will do nothing), "PUBLIC", "GROUP", "STATUS" and "ONE2ONE". @@ -364,641 +343,6 @@ self._timeCb(None) -class MicroblogItem(): - # XXX: should be moved in a separated module - - def __init__(self, data): - self.id = data['id'] - self.type = data.get('type', 'main_item') - self.empty = data.get('new', False) - self.title = data.get('title', '') - self.title_xhtml = data.get('title_xhtml', '') - self.content = data.get('content', '') - self.content_xhtml = data.get('content_xhtml', '') - self.author = data['author'] - self.updated = float(data.get('updated', 0)) # XXX: int doesn't work here - self.published = float(data.get('published', self.updated)) # XXX: int doesn't work here - self.service = data.get('service', '') - self.node = data.get('node', '') - self.comments = data.get('comments', False) - self.comments_service = data.get('comments_service', '') - self.comments_node = data.get('comments_node', '') - - -class MicroblogEntry(SimplePanel, ClickHandler, FocusHandler, KeyboardHandler): - - def __init__(self, blog_panel, data): - """ - @param blog_panel: the parent panel - @param data: dict containing the blog item data, or a MicroblogItem instance. - """ - self._base_item = data if isinstance(data, MicroblogItem) else MicroblogItem(data) - for attr in ['id', 'type', 'empty', 'title', 'title_xhtml', 'content', 'content_xhtml', - 'author', 'updated', 'published', 'comments', 'service', 'node', - 'comments_service', 'comments_node']: - getter = lambda attr: lambda inst: getattr(inst._base_item, attr) - setter = lambda attr: lambda inst, value: setattr(inst._base_item, attr, value) - setattr(MicroblogEntry, attr, property(getter(attr), setter(attr))) - - SimplePanel.__init__(self) - self._blog_panel = blog_panel - - self.panel = FlowPanel() - self.panel.setStyleName('mb_entry') - - self.header = HTMLPanel('') - self.panel.add(self.header) - - self.entry_actions = VerticalPanel() - self.entry_actions.setStyleName('mb_entry_actions') - self.panel.add(self.entry_actions) - - entry_avatar = SimplePanel() - entry_avatar.setStyleName('mb_entry_avatar') - self.avatar = Image(self._blog_panel.host.getAvatar(self.author)) - entry_avatar.add(self.avatar) - self.panel.add(entry_avatar) - - if TOGGLE_EDITION_USE_ICON: - self.entry_dialog = HorizontalPanel() - else: - self.entry_dialog = VerticalPanel() - self.entry_dialog.setStyleName('mb_entry_dialog') - self.panel.add(self.entry_dialog) - - self.add(self.panel) - ClickHandler.__init__(self) - self.addClickListener(self) - - self.__pub_data = (self.service, self.node, self.id) - self.__setContent() - - def __setContent(self): - """Actually set the entry content (header, icons, bubble...)""" - self.delete_label = self.update_label = self.comment_label = None - self.bubble = self._current_comment = None - self.__setHeader() - self.__setBubble() - self.__setIcons() - - def __setHeader(self): - """Set the entry header""" - if self.empty: - return - update_text = u" — ✍ " + "<span class='mb_entry_timestamp'>%s</span>" % datetime.fromtimestamp(self.updated) - self.header.setHTML("""<div class='mb_entry_header'> - <span class='mb_entry_author'>%(author)s</span> on - <span class='mb_entry_timestamp'>%(published)s</span>%(updated)s - </div>""" % {'author': html_tools.html_sanitize(self.author), - 'published': datetime.fromtimestamp(self.published), - 'updated': update_text if self.published != self.updated else '' - } - ) - - def __setIcons(self): - """Set the entry icons (delete, update, comment)""" - if self.empty: - return - - def addIcon(label, title): - label = Label(label) - label.setTitle(title) - label.addClickListener(self) - self.entry_actions.add(label) - return label - - if self.comments: - self.comment_label = addIcon(u"↶", "Comment this message") - self.comment_label.setStyleName('mb_entry_action_larger') - is_publisher = self.author == self._blog_panel.host.whoami.bare - if is_publisher: - self.update_label = addIcon(u"✍", "Edit this message") - if is_publisher or str(self.node).endswith(self._blog_panel.host.whoami.bare): - self.delete_label = addIcon(u"✗", "Delete this message") - - def updateAvatar(self, new_avatar): - """Change the avatar of the entry - @param new_avatar: path to the new image""" - self.avatar.setUrl(new_avatar) - - def onClick(self, sender): - if sender == self: - self._blog_panel.setSelectedEntry(self) - elif sender == self.delete_label: - self._delete() - elif sender == self.update_label: - self.edit(True) - elif sender == self.comment_label: - self._comment() - - def __modifiedCb(self, content): - """Send the new content to the backend - @return: False to restore the original content if a deletion has been cancelled - """ - if not content['text']: # previous content has been emptied - self._delete(True) - return False - extra = {'published': str(self.published)} - if isinstance(self.bubble, richtext.RichTextEditor): - # TODO: if the user change his parameters after the message edition started, - # the message syntax could be different then the current syntax: pass the - # message syntax in extra for the frontend to use it instead of current syntax. - extra.update({'content_rich': content['text'], 'title': content['title']}) - if self.empty: - if self.type == 'main_item': - self._blog_panel.host.bridge.call('sendMblog', None, None, self._blog_panel.accepted_groups, content['text'], extra) - else: - self._blog_panel.host.bridge.call('sendMblogComment', None, self._parent_entry.comments, content['text'], extra) - else: - self._blog_panel.host.bridge.call('updateMblog', None, self.__pub_data, self.comments, content['text'], extra) - return True - - def __afterEditCb(self, content): - """Remove the entry if it was an empty one (used for creating a new blog post). - Data for the actual new blog post will be received from the bridge""" - if self.empty: - self._blog_panel.removeEntry(self.type, self.id) - if self.type == 'main_item': # restore the "New message" button - self._blog_panel.refresh() - else: # allow to create a new comment - self._parent_entry._current_comment = None - self.entry_dialog.setWidth('auto') - try: - self.toggle_syntax_button.removeFromParent() - except TypeError: - pass - - def __setBubble(self, edit=False): - """Set the bubble displaying the initial content.""" - content = {'text': self.content_xhtml if self.content_xhtml else self.content, - 'title': self.title_xhtml if self.title_xhtml else self.title} - if self.content_xhtml: - content.update({'syntax': C.SYNTAX_XHTML}) - if self.author != self._blog_panel.host.whoami.bare: - options = ['read_only'] - else: - options = [] if self.empty else ['update_msg'] - self.bubble = richtext.RichTextEditor(self._blog_panel.host, content, self.__modifiedCb, self.__afterEditCb, options) - else: # assume raw text message have no title - self.bubble = base_panels.LightTextEditor(content, self.__modifiedCb, self.__afterEditCb, options={'no_xhtml': True}) - self.bubble.addStyleName("bubble") - try: - self.toggle_syntax_button.removeFromParent() - except TypeError: - pass - self.entry_dialog.add(self.bubble) - self.edit(edit) - self.bubble.addEditListener(self.__showWarning) - - def __showWarning(self, sender, keycode): - if keycode == KEY_ENTER: - self._blog_panel.host.showWarning(None, None) - else: - self._blog_panel.host.showWarning(*self._blog_panel.getWarningData(self.type == 'comment')) - - def _delete(self, empty=False): - """Ask confirmation for deletion. - @return: False if the deletion has been cancelled.""" - def confirm_cb(answer): - if answer: - self._blog_panel.host.bridge.call('deleteMblog', None, self.__pub_data, self.comments) - else: # restore the text if it has been emptied during the edition - self.bubble.setContent(self.bubble._original_content) - - if self.empty: - text = _("New ") + (_("message") if self.comments else _("comment")) + _(" without body has been cancelled.") - dialog.InfoDialog(_("Information"), text).show() - return - text = "" - if empty: - text = (_("Message") if self.comments else _("Comment")) + _(" body has been emptied.<br/>") - target = _('message and all its comments') if self.comments else _('comment') - text += _("Do you really want to delete this %s?") % target - dialog.ConfirmDialog(confirm_cb, text=text).show() - - def _comment(self): - """Add an empty entry for a new comment""" - if self._current_comment: - self._current_comment.bubble.setFocus(True) - self._blog_panel.setSelectedEntry(self._current_comment, True) - return - data = {'id': str(time()), - 'new': True, - 'type': 'comment', - 'author': self._blog_panel.host.whoami.bare, - 'service': self.comments_service, - 'node': self.comments_node - } - entry = self._blog_panel.addEntry(data) - if entry is None: - log.info("The entry of id %s can not be commented" % self.id) - return - entry._parent_entry = self - self._current_comment = entry - self.edit(True, entry) - self._blog_panel.setSelectedEntry(entry, True) - - def edit(self, edit, entry=None): - """Toggle the bubble between display and edit mode - @edit: boolean value - @entry: MicroblogEntry instance, or None to use self - """ - if entry is None: - entry = self - try: - entry.toggle_syntax_button.removeFromParent() - except TypeError: - pass - entry.bubble.edit(edit) - if edit: - if isinstance(entry.bubble, richtext.RichTextEditor): - image = '<a class="richTextIcon">A</a>' - html = '<a style="color: blue;">raw text</a>' - title = _('Switch to raw text edition') - else: - image = '<img src="media/icons/tango/actions/32/format-text-italic.png" class="richTextIcon"/>' - html = '<a style="color: blue;">rich text</a>' - title = _('Switch to rich text edition') - if TOGGLE_EDITION_USE_ICON: - entry.entry_dialog.setWidth('80%') - entry.toggle_syntax_button = Button(image, entry.toggleContentSyntax) - entry.toggle_syntax_button.setTitle(title) - entry.entry_dialog.add(entry.toggle_syntax_button) - else: - entry.toggle_syntax_button = HTML(html) - entry.toggle_syntax_button.addClickListener(entry.toggleContentSyntax) - entry.toggle_syntax_button.addStyleName('mb_entry_toggle_syntax') - entry.entry_dialog.add(entry.toggle_syntax_button) - entry.toggle_syntax_button.setStyleAttribute('top', '-20px') # XXX: need to force CSS - entry.toggle_syntax_button.setStyleAttribute('left', '-20px') - - def toggleContentSyntax(self): - """Toggle the editor between raw and rich text""" - original_content = self.bubble.getOriginalContent() - rich = not isinstance(self.bubble, richtext.RichTextEditor) - if rich: - original_content['syntax'] = C.SYNTAX_XHTML - - def setBubble(text): - self.content = text - self.content_xhtml = text if rich else '' - self.content_title = self.content_title_xhtml = '' - self.bubble.removeFromParent() - self.__setBubble(True) - self.bubble.setOriginalContent(original_content) - if rich: - self.bubble.setDisplayContent() # needed in case the edition is aborted, to not end with an empty bubble - - text = self.bubble.getContent()['text'] - if not text: - setBubble(' ') # something different than empty string is needed to initialize the rich text editor - return - if not rich: - def confirm_cb(answer): - if answer: - self._blog_panel.host.bridge.call('syntaxConvert', setBubble, text, C.SYNTAX_CURRENT, C.SYNTAX_TEXT) - dialog.ConfirmDialog(confirm_cb, text=_("Do you really want to lose the title and text formatting?")).show() - else: - self._blog_panel.host.bridge.call('syntaxConvert', setBubble, text, C.SYNTAX_TEXT, C.SYNTAX_XHTML) - - -class MicroblogPanel(base_widget.LiberviaWidget): - warning_msg_public = "This message will be <b>PUBLIC</b> and everybody will be able to see it, even people you don't know" - warning_msg_group = "This message will be published for all the people of the group <span class='warningTarget'>%s</span>" - - def __init__(self, host, accepted_groups): - """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) - self.setAcceptedGroup(accepted_groups) - self.host = host - self.entries = {} - self.comments = {} - self.selected_entry = None - self.vpanel = VerticalPanel() - self.vpanel.setStyleName('microblogPanel') - self.setWidget(self.vpanel) - - def refresh(self): - """Refresh the display of this widget. If the unibox is disabled, - display the 'New message' button or an empty bubble on top of the panel""" - if hasattr(self, 'new_button'): - self.new_button.setVisible(self.host.uni_box is None) - return - if self.host.uni_box is None: - def addBox(): - if hasattr(self, 'new_button'): - self.new_button.setVisible(False) - data = {'id': str(time()), - 'new': True, - 'author': self.host.whoami.bare, - } - entry = self.addEntry(data) - entry.edit(True) - if NEW_MESSAGE_USE_BUTTON: - self.new_button = Button("New message", listener=addBox) - self.new_button.setStyleName("microblogNewButton") - self.vpanel.insert(self.new_button, 0) - elif not self.getNewMainEntry(): - addBox() - - def getNewMainEntry(self): - """Get the new entry being edited, or None if it doesn't exists. - - @return (MicroblogEntry): the new entry being edited. - """ - try: - first = self.vpanel.children[0] - except IndexError: - return None - assert(first.type == 'main_item') - return first if first.empty else None - - @classmethod - def registerClass(cls): - base_widget.LiberviaWidget.addDropKey("GROUP", cls.createPanel) - base_widget.LiberviaWidget.addDropKey("CONTACT_TITLE", cls.createMetaPanel) - - @classmethod - 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, _type, _items, 10) - host.setSelected(_new_panel) - _new_panel.refresh() - return _new_panel - - @classmethod - 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, item): - """ - @param item: 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 - """ - groups = item if isinstance(item, list) else ([] if item is None else [item]) - groups.sort() # sort() do not return the sorted list: do it here, not on the "return" line - return self.accepted_groups == groups - - def getWarningData(self, comment=None): - """ - @param comment: True if the composed message is a comment. If None, consider we are - composing from the unibox and guess the message type from self.selected_entry - @return: a couple (type, msg) for calling self.host.showWarning""" - if comment is None: # composing from the unibox - if self.selected_entry and not self.selected_entry.comments: - log.error("an item without comment is selected") - return ("NONE", None) - comment = self.selected_entry is not None - if comment: - 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: - # 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]) - - def onTextEntered(self, text): - if self.selected_entry: - # we are entering a comment - comments_url = self.selected_entry.comments - if not comments_url: - raise Exception("ERROR: the comments URL is empty") - target = ("COMMENT", comments_url) - elif not self._accepted_groups: - # we are entering a public microblog - target = ("PUBLIC", None) - else: - # we are entering a microblog restricted to a group - # FIXME: manage several groups - target = ("GROUP", self._accepted_groups[0]) - self.host.send([target], text) - - def accept_all(self): - 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, - and fill the panel""" - - def massiveInsert(self, mblogs): - """Insert several microblogs at once - @param mblogs: dictionary of microblogs, as the result of getMassiveLastGroupBlogs - """ - count = sum([len(value) for value in mblogs.values()]) - log.debug("Massive insertion of %d microblogs" % count) - for publisher in mblogs: - log.debug("adding blogs for [%s]" % publisher) - for mblog in mblogs[publisher]: - if not "content" in mblog: - log.warning("No content found in microblog [%s]" % mblog) - continue - self.addEntry(mblog) - - def mblogsInsert(self, mblogs): - """ Insert several microblogs at once - @param mblogs: list of microblogs - """ - for mblog in mblogs: - if not "content" in mblog: - log.warning("No content found in microblog [%s]" % mblog) - continue - self.addEntry(mblog) - - def _chronoInsert(self, vpanel, entry, reverse=True): - """ Insert an entry in chronological order - @param vpanel: VerticalPanel instance - @param entry: MicroblogEntry - @param reverse: more recent entry on top if True, chronological order else""" - assert(isinstance(reverse, bool)) - if entry.empty: - entry.published = time() - # we look for the right index to insert our entry: - # if reversed, we insert the entry above the first entry - # in the past - idx = 0 - - for child in vpanel.children: - if not isinstance(child, MicroblogEntry): - idx += 1 - continue - condition_to_stop = child.empty or (child.published > entry.published) - if condition_to_stop != reverse: # != is XOR - break - idx += 1 - - vpanel.insert(entry, idx) - - def addEntry(self, data): - """Add an entry to the panel - @param data: dict containing the item data - @return: the added entry, or None - """ - _entry = MicroblogEntry(self, data) - if _entry.type == "comment": - comments_hash = (_entry.service, _entry.node) - if not comments_hash in self.comments: - # The comments node is not known in this panel - return None - parent = self.comments[comments_hash] - parent_idx = self.vpanel.getWidgetIndex(parent) - # we find or create the panel where the comment must be inserted - try: - sub_panel = self.vpanel.getWidget(parent_idx + 1) - except IndexError: - sub_panel = None - if not sub_panel or not isinstance(sub_panel, VerticalPanel): - sub_panel = VerticalPanel() - sub_panel.setStyleName('microblogPanel') - sub_panel.addStyleName('subPanel') - self.vpanel.insert(sub_panel, parent_idx + 1) - for idx in xrange(0, len(sub_panel.getChildren())): - comment = sub_panel.getIndexedChild(idx) - if comment.id == _entry.id: - # update an existing comment - sub_panel.remove(comment) - sub_panel.insert(_entry, idx) - return _entry - # we want comments to be inserted in chronological order - self._chronoInsert(sub_panel, _entry, reverse=False) - return _entry - - if _entry.id in self.entries: # update - idx = self.vpanel.getWidgetIndex(self.entries[_entry.id]) - self.vpanel.remove(self.entries[_entry.id]) - self.vpanel.insert(_entry, idx) - else: # new entry - self._chronoInsert(self.vpanel, _entry) - self.entries[_entry.id] = _entry - - if _entry.comments: - # entry has comments, we keep the comments service/node as a reference - comments_hash = (_entry.comments_service, _entry.comments_node) - self.comments[comments_hash] = _entry - self.host.bridge.call('getMblogComments', self.mblogsInsert, _entry.comments_service, _entry.comments_node) - - return _entry - - def removeEntry(self, type_, id_): - """Remove an entry from the panel - @param type_: entry type ('main_item' or 'comment') - @param id_: entry id - """ - for child in self.vpanel.getChildren(): - if isinstance(child, MicroblogEntry) and type_ == 'main_item': - if child.id == id_: - main_idx = self.vpanel.getWidgetIndex(child) - try: - sub_panel = self.vpanel.getWidget(main_idx + 1) - if isinstance(sub_panel, VerticalPanel): - sub_panel.removeFromParent() - except IndexError: - pass - child.removeFromParent() - self.selected_entry = None - break - elif isinstance(child, VerticalPanel) and type_ == 'comment': - for comment in child.getChildren(): - if comment.id == id_: - comment.removeFromParent() - self.selected_entry = None - break - - def ensureVisible(self, entry): - """Scroll to an entry to ensure its visibility - - @param entry (MicroblogEntry): the entry - """ - try: - self.vpanel.getParent().ensureVisible(entry) # scroll to the clicked entry - except AttributeError: - log.warning("FIXME: MicroblogPanel.vpanel should be wrapped in a ScrollPanel!") - - def setSelectedEntry(self, entry, ensure_visible=False): - """Select an entry. - - @param entry (MicroblogEntry): the entry to select - @param ensure_visible (boolean): if True, also scroll to the entry - """ - if ensure_visible: - self.ensureVisible(entry) - - if not self.host.uni_box or not entry.comments: - entry.addStyleName('selected_entry') # blink the clicked entry - clicked_entry = entry # entry may be None when the timer is done - Timer(500, lambda timer: clicked_entry.removeStyleName('selected_entry')) - if not self.host.uni_box: - return # unibox is disabled - - # from here the previous behavior (toggle main item selection) is conserved - entry = entry if entry.comments else None - if self.selected_entry == entry: - entry = None - if self.selected_entry: - self.selected_entry.removeStyleName('selected_entry') - if entry: - log.debug("microblog entry selected (author=%s)" % entry.author) - entry.addStyleName('selected_entry') - self.selected_entry = entry - - def updateValue(self, type_, jid, value): - """Update a jid value in entries - @param type_: one of 'avatar', 'nick' - @param jid: jid concerned - @param value: new value""" - def updateVPanel(vpanel): - for child in vpanel.children: - if isinstance(child, MicroblogEntry) and child.author == jid: - child.updateAvatar(value) - elif isinstance(child, VerticalPanel): - updateVPanel(child) - if type_ == 'avatar': - updateVPanel(self.vpanel) - - def setAcceptedGroup(self, group): - """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 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_s): - """Tell if a jid is actepted and shown in this panel - @param jid_s: jid - @return: True if the jid is accepted""" - if self.accept_all(): - return True - for group in self._accepted_groups: - if self.host.contact_panel.isContactInGroup(group, jid_s): - return True - return False - - class StatusPanel(base_panels.HTMLTextEditor): EMPTY_STATUS = '<click to set a status>' @@ -1047,7 +391,7 @@ base_widget.WidgetMenuBar.__init__(self, None, 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.buildPresenceStyle(presence), presence_i18n) + html = u'<span class="%s">◉</span> %s' % (contact_list.buildPresenceStyle(presence), presence_i18n) self.addMenuItem([u"◉", presence], [u"◉", html], '', base_menu.MenuCmd(self, 'changePresenceCb', presence), asHTML=True) self.parent_panel = parent @@ -1094,7 +438,7 @@ def setPresence(self, presence): self._presence = presence - contact.setPresenceStyle(self.menu.button, self._presence) + contact_list.setPresenceStyle(self.menu.button, self._presence) def setStatus(self, status): self.status_panel.setContent({'text': status}) @@ -1105,283 +449,20 @@ self.host.setSelected(None) -class ChatPanel(base_widget.LiberviaWidget, KeyboardHandler): - - def __init__(self, host, target, type_='one2one'): - """Panel used for conversation (one 2 one or group chat) - @param host: SatWebFrontend instance - @param target: entity (jid.JID) with who we have a conversation (contact's jid for one 2 one chat, or MUC room) - @param type: one2one for simple conversation, group for MUC""" - self.vpanel = VerticalPanel() - self.vpanel.setSize('100%', '100%') - self.nick = None - if not target: - log.error("Empty target !") - return - self.target = target - self.type = type_ - - # FIXME: temporary dirty initialization to display the OTR state - def header_info_cb(cb): - host.plugins['otr'].infoTextCallback(target, cb) - header_info = header_info_cb if (type_ == 'one2one' and 'otr' in host.plugins) else None - - base_widget.LiberviaWidget.__init__(self, host, title=target.bare, info=header_info, selectable=True) - self.__body = AbsolutePanel() - self.__body.setStyleName('chatPanel_body') - chat_area = HorizontalPanel() - chat_area.setStyleName('chatArea') - if type_ == 'group': - self.occupants_list = base_panels.OccupantsList() - self.occupants_initialised = False - chat_area.add(self.occupants_list) - self.__body.add(chat_area) - self.content = AbsolutePanel() - self.content.setStyleName('chatContent') - self.content_scroll = base_widget.ScrollPanelWrapper(self.content) - chat_area.add(self.content_scroll) - chat_area.setCellWidth(self.content_scroll, '100%') - self.vpanel.add(self.__body) - self.vpanel.setCellHeight(self.__body, '100%') - self.addStyleName('chatPanel') - self.setWidget(self.vpanel) - self.state_machine = plugin_xep_0085.ChatStateMachine(self.host, str(self.target)) - self._state = None - - @classmethod - def registerClass(cls): - base_widget.LiberviaWidget.addDropKey("CONTACT", cls.createPanel) - - @classmethod - def createPanel(cls, host, item, type_='one2one'): - assert(item) - _contact = item if isinstance(item, jid.JID) else jid.JID(item) - host.contact_panel.setContactMessageWaiting(_contact.bare, False) - _new_panel = ChatPanel(host, _contact, type_) # XXX: pyjamas doesn't seems to support creating with cls directly - _new_panel.historyPrint() - host.setSelected(_new_panel) - _new_panel.refresh() - return _new_panel - - def refresh(self): - """Refresh the display of this widget. If the unibox is disabled, - add a message box at the bottom of the panel""" - self.host.contact_panel.setContactMessageWaiting(self.target.bare, False) - self.content_scroll.scrollToBottom() - - enable_box = self.host.uni_box is None - if hasattr(self, 'message_box'): - self.message_box.setVisible(enable_box) - return - if enable_box: - self.message_box = MessageBox(self.host) - self.message_box.onSelectedChange(self) - self.message_box.addKeyboardListener(self) - self.vpanel.add(self.message_box) - - def onKeyDown(self, sender, keycode, modifiers): - if keycode == KEY_ENTER: - self.host.showWarning(None, None) - else: - self.host.showWarning(*self.getWarningData()) - - def matchEntity(self, item, type_=None): - """ - @param entity: target jid as a string or jid.JID instance. - @return: True if self matches the given entity - """ - if type_ is None: - type_ = self.type - entity = item if isinstance(item, jid.JID) else jid.JID(item) - try: - return self.target.bare == entity.bare and self.type == type_ - except AttributeError as e: - e.include_traceback() - return False - - def addMenus(self, menu_bar): - """Add cached menus to the header. - - @param menu_bar (GenericMenuBar): menu bar of the widget's header - """ - if self.type == 'group': - menu_bar.addCachedMenus(C.MENU_ROOM, {'room_jid': self.target.bare}) - elif self.type == 'one2one': - menu_bar.addCachedMenus(C.MENU_SINGLE, {'jid': self.target}) - - def getWarningData(self): - if self.type not in ["one2one", "group"]: - raise Exception("Unmanaged type !") - if self.type == "one2one": - msg = "This message will be sent to your contact <span class='warningTarget'>%s</span>" % self.target - elif self.type == "group": - msg = "This message will be sent to all the participants of the multi-user room <span class='warningTarget'>%s</span>" % self.target - return ("ONE2ONE" if self.type == "one2one" else "GROUP", msg) - - def onTextEntered(self, text): - self.host.send([("groupchat" if self.type == 'group' else "chat", str(self.target))], text) - self.state_machine._onEvent("active") - - def onQuit(self): - base_widget.LiberviaWidget.onQuit(self) - if self.type == 'group': - self.host.bridge.call('mucLeave', None, self.target.bare) - - def setUserNick(self, nick): - """Set the nick of the user, usefull for e.g. change the color of the user""" - self.nick = nick - - def setPresents(self, nicks): - """Set the users presents in this room - @param occupants: list of nicks (string)""" - for nick in nicks: - self.occupants_list.addOccupant(nick) - self.occupants_initialised = True - - def userJoined(self, nick, data): - if self.occupants_list.getOccupantBox(nick): - return # user is already displayed - self.occupants_list.addOccupant(nick) - if self.occupants_initialised: - self.printInfo("=> %s has joined the room" % nick) - - def userLeft(self, nick, data): - self.occupants_list.removeOccupant(nick) - self.printInfo("<= %s has left the room" % nick) - - def changeUserNick(self, old_nick, new_nick): - assert(self.type == "group") - self.occupants_list.removeOccupant(old_nick) - self.occupants_list.addOccupant(new_nick) - self.printInfo(_("%(old_nick)s is now known as %(new_nick)s") % {'old_nick': old_nick, 'new_nick': new_nick}) - - def historyPrint(self, size=C.HISTORY_LIMIT_DEFAULT): - """Print the initial history""" - def getHistoryCB(history): - # display day change - day_format = "%A, %d %b %Y" - previous_day = datetime.now().strftime(day_format) - for line in history: - timestamp, from_jid_s, to_jid_s, message, mess_type, extra = line - message_day = datetime.fromtimestamp(float(timestamp or time())).strftime(day_format) - if previous_day != message_day: - self.printInfo("* " + message_day) - previous_day = message_day - self.printMessage(jid.JID(from_jid_s), message, extra, timestamp) - self.host.bridge.call('getHistory', getHistoryCB, self.host.whoami.bare, self.target.bare, size, True) - - def printInfo(self, msg, type_='normal', link_cb=None): - """Print general info - @param msg: message to print - @param type_: one of: - "normal": general info like "toto has joined the room" (will be sanitized) - "link": general info that is clickable like "click here to join the main room" (no sanitize done) - "me": "/me" information like "/me clenches his fist" ==> "toto clenches his fist" (will stay on one line) - @param link_cb: method to call when the info is clicked, ignored if type_ is not 'link' - """ - if type_ == 'normal': - _wid = HTML(addURLToText(html_tools.XHTML2Text(msg))) - _wid.setStyleName('chatTextInfo') - elif type_ == 'link': - _wid = HTML(msg) - _wid.setStyleName('chatTextInfo-link') - if link_cb: - _wid.addClickListener(link_cb) - elif type_ == 'me': - _wid = Label(msg) - _wid.setStyleName('chatTextMe') - else: - raise ValueError("Unknown printInfo type %s" % type_) - self.content.add(_wid) - self.content_scroll.scrollToBottom() - - def printMessage(self, from_jid, msg, extra, timestamp=None): - """Print message in chat window. Must be implemented by child class""" - nick = from_jid.node if self.type == 'one2one' else from_jid.resource - mymess = from_jid.resource == self.nick if self.type == "group" else from_jid.bare == self.host.whoami.bare # mymess = True if message comes from local user - if msg.startswith('/me '): - self.printInfo('* %s %s' % (nick, msg[4:]), type_='me') - return - self.content.add(base_panels.ChatText(timestamp, nick, mymess, msg, extra.get('xhtml'))) - self.content_scroll.scrollToBottom() - - def startGame(self, game_type, waiting, referee, players, *args): - """Configure the chat window to start a game""" - classes = {"Tarot": card_game.CardPanel, "RadioCol": radiocol.RadioColPanel} - if game_type not in classes.keys(): - return # unknown game - attr = game_type.lower() - self.occupants_list.updateSpecials(players, SYMBOLS[attr]) - if waiting or not self.nick in players: - return # waiting for player or not playing - attr = "%s_panel" % attr - if hasattr(self, attr): - return - log.info("%s Game Started \o/" % game_type) - panel = classes[game_type](self, referee, self.nick, players, *args) - setattr(self, attr, panel) - self.vpanel.insert(panel, 0) - self.vpanel.setCellHeight(panel, panel.getHeight()) - - def getGame(self, game_type): - """Return class managing the game type""" - # TODO: check that the game is launched, and manage errors - if game_type == "Tarot": - return self.tarot_panel - elif game_type == "RadioCol": - return self.radiocol_panel - - def setState(self, state, nick=None): - """Set the chat state (XEP-0085) of the contact. Leave nick to None - to set the state for a one2one conversation, or give a nickname or - C.ALL_OCCUPANTS to set the state of a participant within a MUC. - @param state: the new chat state - @param nick: ignored for one2one, otherwise the MUC user nick or C.ALL_OCCUPANTS - """ - if self.type == 'group': - assert(nick) - if nick == C.ALL_OCCUPANTS: - occupants = self.occupants_list.occupants_list.keys() - else: - occupants = [nick] if nick in self.occupants_list.occupants_list else [] - for occupant in occupants: - self.occupants_list.occupants_list[occupant].setState(state) - else: - self._state = state - self.refreshTitle() - self.state_machine.started = not not state # start to send "composing" state from now - - def refreshTitle(self): - """Refresh the title of this ChatPanel dialog""" - if self._state: - self.setTitle(self.target.bare + " (" + self._state + ")") - else: - self.setTitle(self.target.bare) - - def setConnected(self, jid_s, resource, availability, priority, statuses): - """Set connection status - @param jid_s (str): JID userhost as unicode - """ - assert(jid_s == self.target.bare) - if self.type != 'group': - return - box = self.occupants_list.getOccupantBox(resource) - if box: - contact.setPresenceStyle(box, availability) - - -class WebPanel(base_widget.LiberviaWidget): +class WebPanel(quick_widgets.QuickWidget, base_widget.LiberviaWidget): """ (mini)browser like widget """ - def __init__(self, host, url=None): + def __init__(self, host, target, profiles=None): """ @param host: SatWebFrontend instance + @param target: url to open """ + quick_widgets.QuickWidget.__init__(self, host, target, C.PROF_KEY_NONE) base_widget.LiberviaWidget.__init__(self, host) self._vpanel = VerticalPanel() self._vpanel.setSize('100%', '100%') self._url = dialog.ExtTextBox(enter_cb=self.onUrlClick) - self._url.setText(url or "") + self._url.setText(target or "") self._url.setWidth('100%') hpanel = HorizontalPanel() hpanel.add(self._url) @@ -1392,7 +473,7 @@ hpanel.add(btn) self._vpanel.add(hpanel) self._vpanel.setCellHeight(hpanel, '20px') - self._frame = Frame(url or "") + self._frame = Frame(target or "") self._frame.setSize('100%', '100%') DOM.setStyleAttribute(self._frame.getElement(), "position", "relative") self._vpanel.add(self._frame) @@ -1411,9 +492,9 @@ # menu self.menu = menu.MainMenuPanel(host) - # unibox - self.unibox_panel = UniBoxPanel(host) - self.unibox_panel.setVisible(False) + # # unibox + # self.unibox_panel = UniBoxPanel(host) + # self.unibox_panel.setVisible(False) # contacts self._contacts = HorizontalPanel() @@ -1421,7 +502,6 @@ self.contacts_switch = Button(u'«', self._contactsSwitch) self.contacts_switch.addStyleName('contactsSwitch') self._contacts.add(self.contacts_switch) - self._contacts.add(self.host.contact_panel) # tabs self.tab_panel = base_widget.MainTabPanel(host) @@ -1431,7 +511,7 @@ self.header = AbsolutePanel() self.header.add(self.menu) - self.header.add(self.unibox_panel) + # self.header.add(self.unibox_panel) self.header.add(self.host.status_panel) self.header.setStyleName('header') self.add(self.header) @@ -1444,13 +524,16 @@ self.setWidth("100%") Window.addWindowResizeListener(self) + def addContactList(self, contact_list): + self._contacts.add(contact_list) + def _contactsSwitch(self, btn=None): """ (Un)hide contacts panel """ if btn is None: btn = self.contacts_switch - cpanel = self.host.contact_panel - cpanel.setVisible(not cpanel.getVisible()) - btn.setText(u"«" if cpanel.getVisible() else u"»") + clist = self.host.contact_list + clist.setVisible(not clist.getVisible()) + btn.setText(u"«" if clist.getVisible() else u"»") self.host.resize() def _contactsMove(self, parent):