Mercurial > libervia-web
view browser_side/panels.py @ 193:f2ae8e170c49
browser side: selected widget caching in UniBox, to avoid to ask the getter at each key pressed, which make the box very slow.
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 04 Mar 2013 00:01:23 +0100 |
parents | cf5c83e7d515 |
children | 6198be95a39c |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- """ Libervia: a Salut à Toi frontend Copyright (C) 2011, 2012, 2013 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.SimplePanel import SimplePanel from pyjamas.ui.AbsolutePanel import AbsolutePanel from pyjamas.ui.VerticalPanel import VerticalPanel from pyjamas.ui.HorizontalPanel import HorizontalPanel from pyjamas.ui.ScrollPanel import ScrollPanel from pyjamas.ui.TabPanel import TabPanel from pyjamas.ui.HTMLPanel import HTMLPanel from pyjamas.ui.FlexTable import FlexTable from pyjamas.ui.Frame import Frame from pyjamas.ui.TextArea import TextArea from pyjamas.ui.TextBox import TextBox 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.DropWidget import DropWidget from pyjamas.ui.ClickListener import ClickHandler from pyjamas.ui.KeyboardListener import KEY_ENTER from pyjamas.ui.MouseListener import MouseHandler from pyjamas.ui import HasAlignment from pyjamas.Timer import Timer from pyjamas import Window 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 from datetime import datetime from time import time import dialog from __pyjamas__ import doc class DropCell(DropWidget): """Cell in the middle grid which replace itself with the dropped widget on DnD""" def __init__(self, host): DropWidget.__init__(self) self.host = host self.setStyleName('dropCell') def onDragEnter(self, event): self.addStyleName('dragover') DOM.eventPreventDefault(event) def onDragLeave(self, event): if event.clientX <= self.getAbsoluteLeft() or event.clientY <= self.getAbsoluteTop() or\ event.clientX >= self.getAbsoluteLeft() + self.getOffsetWidth()-1 or event.clientY >= self.getAbsoluteTop() + self.getOffsetHeight()-1: #We check that we are inside widget's box, and we don't remove the style in this case because #if the mouse is over a widget inside the DropWidget, if will leave the DropWidget, and we #don't want that self.removeStyleName('dragover') def onDragOver(self, event): DOM.eventPreventDefault(event) def _getCellAndRow(self, grid, event): """Return cell and row index where the event is occuring""" cell = grid.getEventTargetCell(event) row = DOM.getParent(cell) return (row.rowIndex, cell.cellIndex) def onDrop(self, event): dt = event.dataTransfer #'text', 'text/plain', and 'Text' are equivalent. try: item, item_type = dt.getData("text/plain").split('\n') #Workaround for webkit, only text/plain seems to be managed if item_type and item_type[-1] == '\0': #Workaround for what looks like a pyjamas bug: the \0 should not be there, and item_type = item_type[:-1] # .strip('\0') and .replace('\0','') don't work. TODO: check this and fill a bug report #item_type = dt.getData("type") print "message: %s" % item print "type: %s" % item_type except: print "no message found" item=' ' item_type = None DOM.eventPreventDefault(event) if item_type=="GROUP": _new_panel = MicroblogPanel(self.host, [item]) _new_panel.setAcceptedGroup(item) self.host.FillMicroblogPanel(_new_panel) self.host.bridge.call('getMassiveLastMblogs', _new_panel.massiveInsert, 'GROUP', [item], 10) elif item_type=="CONTACT": _contact = JID(item) self.host.contact_panel.setContactMessageWaiting(_contact.bare, False) _new_panel = ChatPanel(self.host, _contact) _new_panel.historyPrint() elif item_type=="CONTACT_TITLE": _new_panel = MicroblogPanel(self.host, []) self.host.FillMicroblogPanel(_new_panel) self.host.bridge.call('getMassiveLastMblogs', _new_panel.massiveInsert, 'ALL', [], 10) else: return False if isinstance(self, LiberviaWidget): self.host.unregisterWidget(self) self.onQuit() if not isinstance(_new_panel, LiberviaWidget): print ('WARNING: droping an object which is not a class of LiberviaWidget') _flextable = self.getParent() _widgetspanel = _flextable.getParent().getParent() row_idx, cell_idx = self._getCellAndRow(_flextable, event) if self.host.getSelected == self: self.host.setSelected(None) _widgetspanel.changeWidget(row_idx, cell_idx, _new_panel) """_unempty_panels = filter(lambda wid:not isinstance(wid,EmptyWidget),list(_flextable)) _width = 90/float(len(_unempty_panels) or 1) #now we resize all the cell of the column for panel in _unempty_panels: td_elt = panel.getElement().parentNode DOM.setStyleAttribute(td_elt, "width", "%s%%" % _width)""" #FIXME: delete object ? Check the right way with pyjamas class LiberviaWidget(DropCell, VerticalPanel, ClickHandler): """Libervia's widget which can replace itself with a dropped widget on DnD""" def __init__(self, host, title='', selectable=False): """Init the widget @param host: SatWebFrontend object @param title: title show in the header of the widget @param selectable: True is widget can be selected by user""" VerticalPanel.__init__(self) DropCell.__init__(self, host) ClickHandler.__init__(self) self.__selectable = selectable self.__title_id = HTMLPanel.createUniqueId() self.__setting_button_id = HTMLPanel.createUniqueId() self.__close_button_id = HTMLPanel.createUniqueId() header = AbsolutePanel() self.__title = Label(title) self.__title.setStyleName('widgetHeader_title') header.add(self.__title) button_group_wrapper = SimplePanel() button_group_wrapper.setStyleName('widgetHeader_buttonsWrapper') button_group = HorizontalPanel() button_group.setStyleName('widgetHeader_buttonGroup') setting_button = Image("media/icons/misc/settings.png") setting_button.setStyleName('widgetHeader_settingButton') setting_button.addClickListener(self.onSetting) close_button = Image("media/icons/misc/close.png") close_button.setStyleName('widgetHeader_closeButton') close_button.addClickListener(self.onClose) button_group.add(setting_button) button_group.add(close_button) button_group_wrapper.setWidget(button_group) header.add(button_group_wrapper) self.add(header) header.addStyleName('widgetHeader') self.setSize('100%', '100%') self.addStyleName('widget') if self.__selectable: self.addClickListener(self) self.host.registerWidget(self) def _getWidgetsPanel(self): current = self while current is not None and current.__class__ != WidgetsPanel: current = current.getParent() if current is None: print "Error: can't find WidgetsPanel" return current def onClick(self, sender): self.host.setSelected(self) def onClose(self, sender): """ Called when the close button is pushed """ _widgetspanel = self._getWidgetsPanel() _widgetspanel.removeWidget(self) self.onQuit() def onQuit(self): """ Called when the widget is actually ending """ pass def onSetting(self, sender): widpanel = self._getWidgetsPanel() row, col = widpanel.getIndex(self) body = VerticalPanel() #colspan & rowspan colspan = widpanel.getColSpan(row, col) rowspan = widpanel.getRowSpan(row, col) def onColSpanChange(value): widpanel.setColSpan(row, col, value) def onRowSpanChange(value): widpanel.setRowSpan(row, col, value) colspan_setter = dialog.IntSetter("Columns span", colspan) colspan_setter.addValueChangeListener(onColSpanChange) colspan_setter.setWidth('100%') rowspan_setter = dialog.IntSetter("Rows span", rowspan) rowspan_setter.addValueChangeListener(onRowSpanChange) rowspan_setter.setWidth('100%') body.add(colspan_setter) body.add(rowspan_setter) #size width_str = self.getWidth() if width_str.endswith('px'): width=int(width_str[:-2]) else: width = 0 height_str = self.getHeight() if height_str.endswith('px'): height=int(height_str[:-2]) else: height = 0 def onWidthChange(value): if not value: self.setWidth('100%') else: self.setWidth('%dpx' % value) def onHeightChange(value): if not value: self.setHeight('100%') else: self.setHeight('%dpx' % value) width_setter = dialog.IntSetter("width (0=auto)", width) width_setter.addValueChangeListener(onWidthChange) width_setter.setWidth('100%') height_setter = dialog.IntSetter("height (0=auto)", height) height_setter.addValueChangeListener(onHeightChange) height_setter.setHeight('100%') body.add(width_setter) body.add(height_setter) #reset def onReset(sender): colspan_setter.setValue(1) rowspan_setter.setValue(1) width_setter.setValue(0) height_setter.setValue(0) reset_bt = Button("Reset", onReset) body.add(reset_bt) body.setCellHorizontalAlignment(reset_bt, HasAlignment.ALIGN_CENTER) _dialog = dialog.GenericDialog("Widget setting", body) _dialog.show() def setTitle(self, text): """change the title in the header of the widget @param text: text of the new title""" self.__title.setText(text) def isSelectable(self): return self.__selectable def setSelectable(self, selectable): if not self.__selectable: try: self.removeClickListener(self) except ValueError: pass if self.selectable and not self in self._clickListeners: self.addClickListener(self) self.__selectable = selectable def setWidget(self, widget, scrollable=True): """Set the widget that will be in the body of the LiberviaWidget @param widget: widget to put in the body @param scrollable: if true, the widget will be in a ScrollPanelWrapper""" if scrollable: _scrollpanelwrapper = ScrollPanelWrapper() _scrollpanelwrapper.setStyleName('widgetBody') _scrollpanelwrapper.setWidget(widget) body_wid = _scrollpanelwrapper else: body_wid = widget self.add(body_wid) self.setCellHeight(body_wid, '100%') def doDetachChildren(self): #We need to force the use of a panel subclass method here, #for the same reason as doAttachChildren VerticalPanel.doDetachChildren(self) def doAttachChildren(self): #We need to force the use of a panel subclass method here, else #the event will not propagate to children VerticalPanel.doAttachChildren(self) class ScrollPanelWrapper(SimplePanel): """Scroll Panel like component, wich use the full available space to work around percent size issue, it use some of the ideas found here: http://code.google.com/p/google-web-toolkit/issues/detail?id=316 specially in code given at comment #46, thanks to Stefan Bachert""" def __init__(self, *args, **kwargs): SimplePanel.__init__(self) self.spanel = ScrollPanel(*args, **kwargs) SimplePanel.setWidget(self, self.spanel) DOM.setStyleAttribute(self.getElement(), "position", "relative") DOM.setStyleAttribute(self.getElement(), "top", "0px") DOM.setStyleAttribute(self.getElement(), "left", "0px") DOM.setStyleAttribute(self.getElement(), "width", "100%") DOM.setStyleAttribute(self.getElement(), "height", "100%") DOM.setStyleAttribute(self.spanel.getElement(), "position", "absolute") DOM.setStyleAttribute(self.spanel.getElement(), "width", "100%") DOM.setStyleAttribute(self.spanel.getElement(), "height", "100%") def setWidget(self, widget): self.spanel.setWidget(widget) def setScrollPosition(self, position): self.spanel.setScrollPosition(position) def scrollToBottom(self): self.setScrollPosition(self.spanel.getElement().scrollHeight) class EmptyWidget(DropCell, SimplePanel): """Empty dropable panel""" def __init__(self, host): SimplePanel.__init__(self) DropCell.__init__(self, host) #self.setWidget(HTML('')) self.setSize('100%','100%') class BorderWidget(EmptyWidget): def __init__(self, host): EmptyWidget.__init__(self, host) self.addStyleName('borderPanel') class LeftBorderWidget(BorderWidget): def __init__(self, host): BorderWidget.__init__(self, host) self.addStyleName('leftBorderWidget') class RightBorderWidget(BorderWidget): def __init__(self, host): BorderWidget.__init__(self, host) self.addStyleName('rightBorderWidget') class BottomBorderWidget(BorderWidget): def __init__(self, host): BorderWidget.__init__(self, host) self.addStyleName('bottomBorderWidget') class UniBoxPanel(SimplePanel): """Panel containing the UniBox""" def __init__(self, host): SimplePanel.__init__(self) self.setStyleName('uniBoxPanel') self.unibox = UniBox(host) self.unibox.setWidth('100%') self.add(self.unibox) class UniBox(TextArea, MouseHandler): #AutoCompleteTextBox): """This text box is used as a main typing point, for message, microblog, etc""" def __init__(self, host): TextArea.__init__(self) #AutoCompleteTextBox.__init__(self) self.__size = (0,0) self._popup = None self._timer = Timer(notify=self._timeCb) self.host = host self.setStyleName('uniBox') self.addKeyboardListener(self) MouseHandler.__init__(self) self.addMouseListener(self) self._selected_cache = None host.addSelectedListener(self.onSelectedChange) def addKey(self, key): return #self.getCompletionItems().completions.append(key) def removeKey(self, key): try: self.getCompletionItems().completions.remove(key) except KeyError: print "WARNING: trying to remove an unknown key" def showWarning(self, target_data): type, target = target_data if type == "PUBLIC": msg = "This message will be PUBLIC and everybody will be able to see it, even people you don't know" style = "targetPublic" elif type == "GROUP": msg = "This message will be published for all the people of the group <span class='warningTarget'>%s</span>" % (target or '') style = "targetGroup" elif type == "STATUS": msg = "This will be your new status message" style = "targetStatus" elif type == "ONE2ONE": msg = "This message will be sent to your contact <span class='warningTarget'>%s</span>" % target style = "targetOne2One" else: print "WARNING: undetermined target for this message" return contents = HTML(msg) self._popup = dialog.PopupPanelWrapper(autoHide=False, modal=False) self._popup.target_data = target_data self._popup.add(contents) self._popup.setStyleName("warningPopup") if style: self._popup.addStyleName(style) left = 0 top = 0 #max(0, self.getAbsoluteTop() - contents.getOffsetHeight() - 2) self._popup.setPopupPosition(left, top) self._popup.setPopupPosition(left, top) self._popup.show() def _timeCb(self, timer): if self._popup: self._popup.hide() del self._popup self._popup = None def _getTarget(self, txt): """Say who will receive the messsage Return a tuple (target_type, target info)""" type = None target = None if txt.startswith('@@: '): type = "PUBLIC" elif txt.startswith('@'): type = "GROUP" _end = txt.find(': ') if _end == -1: type = "STATUS" else: target = txt[1:_end] #only one target group is managed for the moment if not target in self.host.contact_panel.getGroups(): target = None elif self._selected_cache == None: type = "STATUS" elif isinstance(self._selected_cache, ChatPanel): type = "ONE2ONE" target = str(self._selected_cache.target) else: print "Unknown selected host:",self._selected_cache type = "UNKNOWN" return (type, target) def onBrowserEvent(self, event): #XXX: woraroung a pyjamas bug: self.currentEvent is not set # so the TextBox's cancelKey doens't work. This is a workaround # FIXME: fix the bug upstream self.currentEvent = event TextArea.onBrowserEvent(self, event) def onKeyPress(self, sender, keycode, modifiers): _txt = self.getText() if not self._popup: self.showWarning(self._getTarget(_txt)) else: _target = self._getTarget(_txt) if _target != self._popup.target_data: self._timeCb(None) #we remove the popup self.showWarning(_target) self._timer.schedule(2000) #if keycode == KEY_ENTER and not self.visible: if keycode == KEY_ENTER: if _txt: if _txt.startswith('@'): self.host.bridge.call('sendMblog', None, self.getText()) elif self._selected_cache == None: self.host.bridge.call('setStatus', None, _txt) elif isinstance(self._selected_cache, ChatPanel): _chat = self._selected_cache mess_type = "groupchat" if _chat.type=='group' else "chat" self.host.bridge.call('sendMessage', None, str(_chat.target), _txt, '', mess_type) self.setText('') self._timeCb(None) #we remove the popup sender.cancelKey() def onMouseUp(self, sender, x, y): size = (self.getOffsetWidth(), self.getOffsetHeight()) if size != self.__size: self.__size = size self.host.resize() def onSelectedChange(self, selected): self._selected_cache = selected """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 MicroblogItem(): #XXX: should be moved in a separated module def __init__(self, data): self.id = data['id'] self.content = data['content'] self.author = data['author'] self.timestamp = float(data.get('timestamp',0)) #XXX: int doesn't work here class MicroblogEntry(SimplePanel): def __init__(self, host, mblog_entry): SimplePanel.__init__(self) self.author = mblog_entry.author self.timestamp = mblog_entry.timestamp _datetime = datetime.fromtimestamp(mblog_entry.timestamp) self.panel = HTMLPanel(""" <div class='mb_entry_header'><span class='mb_entry_author'>%(author)s</span> on <span class='mb_entry_timestamp'>%(timestamp)s</span></div> <div class="mb_entry_avatar" id='id_avatar'></div> <div class="mb_entry_dialog"> <p class="bubble">%(body)s</p> </div> """ % {"author": html_sanitize(self.author), "timestamp": _datetime, "body": html_sanitize(mblog_entry.content) }) self.avatar = Image(host.getAvatar(self.author)) self.panel.add(self.avatar, "id_avatar") self.panel.setStyleName('mb_entry') self.add(self.panel) def updateAvatar(self, new_avatar): """Change the avatar of the entry @param new_avatar: path to the new image""" self.avatar.setUrl(new_avatar) class MicroblogPanel(LiberviaWidget): 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 """ LiberviaWidget.__init__(self, host, ", ".join(accepted_groups)) #ScrollPanelWrapper.__init__(self) #DropCell.__init__(self) self.accepted_groups = accepted_groups self.entries = {} self.vpanel = VerticalPanel() self.vpanel.setStyleName('microblogPanel') self.setWidget(self.vpanel) 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 """ print "Massive insertion of microblogs" for publisher in mblogs: print "adding blogs for [%s]" % publisher for mblog in mblogs[publisher]: if not mblog.has_key('content'): print ("WARNING: No content found in microblog [%s]", mblog) continue mblog_entry = MicroblogItem(mblog) self.addEntry(mblog_entry) def addEntry(self, mblog_entry): """Add an entry to the panel @param mblog_entry: MicroblogItem instance """ if mblog_entry.id in self.entries: return _entry = MicroblogEntry(self.host, mblog_entry) self.entries[mblog_entry.id] = _entry # we look for the right index to insert our entry: # we insert the entry above the first entry # in the past idx = 0 for child in self.vpanel.children: if not isinstance(child, MicroblogEntry): break if child.timestamp < mblog_entry.timestamp: break idx+=1 self.vpanel.insert(_entry,idx) 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""" if type=='avatar': for entry in self.entries.values(): if entry.author == jid: entry.updateAvatar(value) def setAcceptedGroup(self, group): """Set the group which can be displayed in this panel @param group: string of the group, or list of string """ if isinstance(group, list): self.accepted_groups.extend(group) else: self.accepted_groups.append(group) def isJidAccepted(self, jid): """Tell if a jid is actepted and shown in this panel @param jid: 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): return True return False class StatusPanel(HTMLPanel, ClickHandler): 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 "<span class='status'>%(status)s</span>" % {'status':html_sanitize(self.status)} def changeStatus(self, new_status): self.status = new_status or ' ' self.setHTML(self.__getContent()) 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) class ChatText(HTMLPanel): def __init__(self, timestamp, nick, mymess, msg): _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": html_sanitize(msg)} ) self.setStyleName('chatText') class Occupant(HTML): """Occupant of a MUC room""" def __init__(self, nick): self.nick = nick HTML.__init__(self, "<div class='occupant'>%s</div>" % html_sanitize(nick)) def __str__(self): return self.nick 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) class ChatPanel(LiberviaWidget): 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) 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""" LiberviaWidget.__init__(self, host, target.bare, selectable = True) self.vpanel = VerticalPanel() self.vpanel.setSize('100%','100%') self.type = type_ self.nick = None if not target: print "ERROR: Empty target !" return self.target = target self.__body = AbsolutePanel() self.__body.setStyleName('chatPanel_body') chat_area = HorizontalPanel() chat_area.setStyleName('chatArea') if type_ == 'group': self.occupants_list = OccupantsList() chat_area.add(self.occupants_list) self.__body.add(chat_area) self.content = AbsolutePanel() self.content.setStyleName('chatContent') self.content_scroll = ScrollPanelWrapper(self.content) chat_area.add(self.content_scroll) chat_area.setCellWidth(self.content_scroll, '100%') self.vpanel.add(self.__body) self.addStyleName('chatPanel') self.setWidget(self.vpanel) """def doDetachChildren(self): #We need to force the use of a panel subclass method here, #for the same reason as doAttachChildren ScrollPanelWrapper.doDetachChildren(self) def doAttachChildren(self): #We need to force the use of a panel subclass method here, else #the event will not propagate to children ScrollPanelWrapper.doAttachChildren(self)""" def onQuit(self): 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)""" self.occupants_list.clear() for nick in nicks: self.occupants_list.addOccupant(nick) def userJoined(self, nick, data): self.occupants_list.addOccupant(nick) 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 historyPrint(self, size=20): """Print the initial history""" def getHistoryCB(history): for line in history: timestamp, from_jid, to_jid, message, mess_type = line self.printMessage(from_jid, message, timestamp) self.host.bridge.call('getHistory', getHistoryCB, self.host.whoami.bare, self.target.bare, size, True) def printInfo(self, msg, type='normal'): """Print general info @param msg: message to print @type: one of: normal: general info like "toto has joined the room" me: "/me" information like "/me clenches his fist" ==> "toto clenches his fist" """ _wid = Label(msg) if type == 'normal': _wid.setStyleName('chatTextInfo') elif type == 'me': _wid.setStyleName('chatTextMe') else: _wid.setStyleName('chatTextInfo') self.content.add(_wid) def printMessage(self, from_jid, msg, timestamp=None): """Print message in chat window. Must be implemented by child class""" _jid=JID(from_jid) nick = _jid.node if self.type=='one2one' else _jid.resource mymess = _jid.resource == self.nick if self.type == "group" else _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(ChatText(timestamp, nick, mymess, msg)) self.content_scroll.scrollToBottom() def startGame(self, game_type, referee, players): """Configure the chat window to start a game""" if game_type=="Tarot": if hasattr(self, "tarot_panel"): return self.tarot_panel = CardPanel(self, referee, players, self.nick) self.vpanel.insert(self.tarot_panel, 0) self.vpanel.setCellHeight(self.tarot_panel, self.tarot_panel.getHeight()) elif game_type=="RadioCol": #XXX: We can have double panel if we join quickly enough to have the group chat start signal # on invitation + the one triggered on room join if hasattr(self, "radiocol_panel"): return self.radiocol_panel = RadioColPanel(self, referee, self.nick) self.vpanel.insert(self.radiocol_panel, 0) self.vpanel.setCellHeight(self.radiocol_panel, self.radiocol_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 class WebPanel(LiberviaWidget): """ (mini)browser like widget """ def __init__(self, host, url=None): """ @param host: SatWebFrontend instance """ 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.setWidth('100%') hpanel = HorizontalPanel() hpanel.add(self._url) btn = Button("Go", self.onUrlClick) hpanel.setCellWidth(self._url, "100%") #self.setCellWidth(btn, "10%") hpanel.add(self._url) hpanel.add(btn) self._vpanel.add(hpanel) self._vpanel.setCellHeight(hpanel, '20px') self._frame = Frame(url or "") self._frame.setSize('100%', '100%') DOM.setStyleAttribute(self._frame.getElement(), "position", "relative") self._vpanel.add(self._frame) self.setWidget(self._vpanel) def onUrlClick(self, sender): self._frame.setUrl(self._url.getText()) class WidgetsPanel(ScrollPanelWrapper): def __init__(self, host, locked = False): ScrollPanelWrapper.__init__(self) self.setSize('100%', '100%') self.host = host self.locked = locked #if True: tab will not be removed when there are no more widgets inside self.selected = None self.flextable = FlexTable() self.flextable.setSize('100%','100%') self.setWidget(self.flextable) self.setStyleName('widgetsPanel') _bottom = BottomBorderWidget(self.host) self.flextable.setWidget(0, 0, _bottom) #There will be always an Empty widget on the last row, #dropping a widget there will add a new row td_elt = _bottom.getElement().parentNode DOM.setStyleAttribute(td_elt, "height", "1px") #needed so the cell adapt to the size of the border (specially in webkit) self._max_cols = 1 #give the maximum number of columns i a raw def isLocked(self): return self.locked def changeWidget(self, row, col, wid): """Change the widget in the given location, add row or columns when necessary""" print "changing widget:", wid, row, col last_row = max(0, self.flextable.getRowCount()-1) try: prev_wid = self.flextable.getWidget(row, col) except: print "ERROR: Trying to change an unexisting widget !" return cellFormatter = self.flextable.getFlexCellFormatter() if isinstance(prev_wid, BorderWidget): #We are on a border, we must create a row and/or columns print "BORDER WIDGET" prev_wid.removeStyleName('dragover') if isinstance(prev_wid, BottomBorderWidget): #We are on the bottom border, we create a new row self.flextable.insertRow(last_row) self.flextable.setWidget(last_row, 0, LeftBorderWidget(self.host)) self.flextable.setWidget(last_row, 1, wid) self.flextable.setWidget(last_row, 2, RightBorderWidget(self.host)) cellFormatter.setHorizontalAlignment(last_row, 2, HasAlignment.ALIGN_RIGHT) row = last_row elif isinstance(prev_wid, LeftBorderWidget): if col!=0: print "ERROR: LeftBorderWidget must be on the first column !" return self.flextable.insertCell(row, col+1) self.flextable.setWidget(row, 1, wid) elif isinstance(prev_wid, RightBorderWidget): if col!=self.flextable.getCellCount(row)-1: print "ERROR: RightBorderWidget must be on the last column !" return self.flextable.insertCell(row, col) self.flextable.setWidget(row, col, wid) else: prev_wid.removeFromParent() self.flextable.setWidget(row, col, wid) _max_cols = max(self._max_cols, self.flextable.getCellCount(row)) if _max_cols != self._max_cols: self._max_cols = _max_cols self._sizesAdjust() def _sizesAdjust(self): cellFormatter = self.flextable.getFlexCellFormatter() width = 100.0/max(1, self._max_cols-2) #we don't count the borders for row_idx in xrange(self.flextable.getRowCount()): for col_idx in xrange(self.flextable.getCellCount(row_idx)): _widget = self.flextable.getWidget(row_idx, col_idx) if not isinstance(_widget, BorderWidget): td_elt = _widget.getElement().parentNode DOM.setStyleAttribute(td_elt, "width", "%.2f%%" % width) last_row = max(0, self.flextable.getRowCount()-1) cellFormatter.setColSpan(last_row, 0, self._max_cols) def addWidget(self, wid): """Add a widget to a new cell on the next to last row""" last_row = max(0, self.flextable.getRowCount()-1) print "putting widget %s at %d, %d" % (wid, last_row, 0) self.changeWidget(last_row, 0, wid) def removeWidget(self, wid): """Remove a widget and the cell where it is""" _row, _col = self.flextable.getIndex(wid) self.flextable.remove(wid) self.flextable.removeCell(_row, _col) if self.flextable.getCellCount(_row) == 2: #we have only the borders left, we remove the row self.flextable.removeRow(_row) _max_cols = 1 for row_idx in xrange(self.flextable.getRowCount()): _max_cols = max(_max_cols, self.flextable.getCellCount(row_idx)) if _max_cols != self._max_cols: self._max_cols = _max_cols self._sizesAdjust() current = self blank_page = not [wid for wid in self.flextable if isinstance(wid, LiberviaWidget)] # do we still have widgets on the page ? if blank_page and not self.isLocked(): #we now notice the MainTabPanel that the WidgetsPanel is empty and need to be removed while current is not None: if isinstance(current, MainTabPanel): current.onWidgetPanelRemove(self) return current = current.getParent() print "Error: no MainTabPanel found !" def getIndex(self, wid): return self.flextable.getIndex(wid) def getColSpan(self, row, col): cellFormatter = self.flextable.getFlexCellFormatter() return cellFormatter.getColSpan(row, col) def setColSpan(self, row, col, value): cellFormatter = self.flextable.getFlexCellFormatter() return cellFormatter.setColSpan(row, col, value) def getRowSpan(self, row, col): cellFormatter = self.flextable.getFlexCellFormatter() return cellFormatter.getRowSpan(row, col) def setRowSpan(self, row, col, value): cellFormatter = self.flextable.getFlexCellFormatter() return cellFormatter.setRowSpan(row, col, value) class ContactTabPanel(HorizontalPanel): """ TabPanel with a contacts list which can be hidden """ def __init__(self, host, locked = False): self.host=host HorizontalPanel.__init__(self) self._left = VerticalPanel() contacts_switch = Button('<<', self._contactsSwitch) contacts_switch.addStyleName('contactsSwitch') self._left.add(contacts_switch) self._left.add(self.host.contact_panel) self._right = WidgetsPanel(host) self._right.setWidth('100%') self._right.setHeight('100%') self.add(self._left) self.add(self._right) self.setCellWidth(self._right, "100%") def addWidget(self, wid): """Add a widget to the WidgetsPanel""" print "main addWidget", wid self._right.addWidget(wid) class MainTabPanel(TabPanel): def __init__(self, host): TabPanel.__init__(self) self.host=host self.tabBar.setVisible(False) self.setStyleName('liberviaTabPanel') self.addStyleName('mainTabPanel') Window.addWindowResizeListener(self) def getCurrentPanel(self): """ Get the panel of the currently selected tab """ return self.deck.visibleWidget def onWindowResized(self, width, height): tab_panel_elt = self.getElement() _elts = doc().getElementsByClassName('gwt-TabBar') if not _elts.length: print ("ERROR: no TabBar found, it should exist !") tab_bar_h = 0 else: tab_bar_h = _elts.item(0).offsetHeight ideal_height = height - DOM.getAbsoluteTop(tab_panel_elt) - tab_bar_h - 5 ideal_width = width - DOM.getAbsoluteLeft(tab_panel_elt) - 5 self.setWidth("%s%s" % (ideal_width, "px")); self.setHeight("%s%s" % (ideal_height, "px")); def add(self, widget, tabText=None, asHTML=False): TabPanel.add(self, widget, tabText, asHTML) if self.getWidgetCount()>1: self.tabBar.setVisible(True) self.host.resize() def onWidgetPanelRemove(self, panel): """ Called when a child WidgetsPanel is empty and need to be removed """ self.remove(panel) if self.getWidgetCount() == 1: self.tabBar.setVisible(False) self.host.resize() self.selectTab(0) class MainPanel(AbsolutePanel): def __init__(self, host): self.host=host AbsolutePanel.__init__(self) #menu menu = Menu(host) #unibox unibox_panel = UniBoxPanel(host) self.host.setUniBox(unibox_panel.unibox) #status bar status = host.status_panel #contacts _contacts = VerticalPanel() contacts_switch = Button('<<', self._contactsSwitch) contacts_switch.addStyleName('contactsSwitch') _contacts.add(contacts_switch) _contacts.add(self.host.contact_panel) #tabs self.tab_panel = MainTabPanel(host) self.discuss_panel = WidgetsPanel(self.host, locked=True) self.tab_panel.add(self.discuss_panel, "Discussions") self.tab_panel.selectTab(0) header=AbsolutePanel() header.add(menu) header.add(unibox_panel) header.add(status) header.setStyleName('header') self.add(header) _hpanel = HorizontalPanel() _hpanel.add(_contacts) _hpanel.add(self.tab_panel) self.add(_hpanel) self.setWidth("100%") Window.addWindowResizeListener(self) def _contactsSwitch(self, btn): """ (Un)hide contacts panel """ cpanel = self.host.contact_panel cpanel.setVisible(not cpanel.getVisible()) btn.setText("<<" if cpanel.getVisible() else ">>") self.host.resize() def onWindowResized(self, width, height): _elts = doc().getElementsByClassName('gwt-TabBar') if not _elts.length: tab_bar_h = 0 else: tab_bar_h = _elts.item(0).offsetHeight ideal_height = Window.getClientHeight() - tab_bar_h self.setHeight("%s%s" % (ideal_height, "px"));