Mercurial > libervia-web
changeset 195:dd27072d8ae0
browser side: widgets refactoring:
- moved base widgets in a base_widget module
- widgets class now register themselves their Drag/Drop type
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 04 Mar 2013 23:01:57 +0100 |
parents | 6198be95a39c |
children | c2639c9f86ea |
files | browser_side/base_widget.py browser_side/contact.py browser_side/panels.py libervia.py |
diffstat | 4 files changed, 575 insertions(+), 511 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/browser_side/base_widget.py Mon Mar 04 23:01:57 2013 +0100 @@ -0,0 +1,521 @@ +#!/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.FlexTable import FlexTable +from pyjamas.ui.TabPanel import TabPanel +from pyjamas.ui.HTMLPanel import HTMLPanel +from pyjamas.ui.Label import Label +from pyjamas.ui.Button import Button +from pyjamas.ui.Image import Image +from pyjamas.ui.DropWidget import DropWidget +from pyjamas.ui.ClickListener import ClickHandler +from pyjamas.ui import HasAlignment +from pyjamas import DOM +import dialog +from pyjamas import Window +from __pyjamas__ import doc + +class DropCell(DropWidget): + """Cell in the middle grid which replace itself with the dropped widget on DnD""" + drop_keys = {} + + def __init__(self, host): + DropWidget.__init__(self) + self.host = host + self.setStyleName('dropCell') + + @classmethod + def addDropKey(cls, key, callback): + DropCell.drop_keys[key] = callback + + 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 in self.drop_keys: + _new_panel = self.drop_keys[item_type](self.host,item) + 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 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 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) + widgets_count = self.getWidgetCount() + if widgets_count == 1: + self.tabBar.setVisible(False) + self.host.resize() + self.selectTab(0) + else: + self.selectTab(widgets_count - 1) +
--- a/browser_side/contact.py Mon Mar 04 00:19:03 2013 +0100 +++ b/browser_side/contact.py Mon Mar 04 23:01:57 2013 +0100 @@ -36,10 +36,10 @@ class DragLabel(DragWidget): - def __init__(self, text, type): + def __init__(self, text, _type): DragWidget.__init__(self) self._text = text - self._type = type + self._type = _type def onDragStart(self, event): dt = event.dataTransfer
--- a/browser_side/panels.py Mon Mar 04 00:19:03 2013 +0100 +++ b/browser_side/panels.py Mon Mar 04 23:01:57 2013 +0100 @@ -24,24 +24,17 @@ 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 @@ -51,314 +44,10 @@ from datetime import datetime from time import time import dialog +import base_widget +from pyjamas import Window 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""" @@ -549,14 +238,14 @@ self.avatar.setUrl(new_avatar) -class MicroblogPanel(LiberviaWidget): +class MicroblogPanel(base_widget.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) + base_widget.LiberviaWidget.__init__(self, host, ", ".join(accepted_groups)) + #base_widget.ScrollPanelWrapper.__init__(self) #DropCell.__init__(self) self.accepted_groups = accepted_groups self.entries = {} @@ -564,6 +253,26 @@ self.vpanel.setStyleName('microblogPanel') self.setWidget(self.vpanel) + @classmethod + def registerClass(cls): + base_widget.LiberviaWidget.addDropKey("GROUP", cls.onDropCreateGroup) + base_widget.LiberviaWidget.addDropKey("CONTACT_TITLE", cls.onDropCreateMeta) + + @classmethod + def onDropCreateGroup(cls, host, item): + _new_panel = MicroblogPanel(host, [item]) #XXX: pyjamas doesn't support use of cls directly + _new_panel.setAcceptedGroup(item) + host.FillMicroblogPanel(_new_panel) + host.bridge.call('getMassiveLastMblogs', _new_panel.massiveInsert, 'GROUP', [item], 10) + return _new_panel + + @classmethod + def onDropCreateMeta(cls, host, item): + _new_panel = MicroblogPanel(host, []) #XXX: pyjamas doesn't support use of cls directly + host.FillMicroblogPanel(_new_panel) + host.bridge.call('getMassiveLastMblogs', _new_panel.massiveInsert, 'ALL', [], 10) + return _new_panel + def accept_all(self): return not self.accepted_groups #we accept every microblog only if we are not filtering by groups @@ -704,14 +413,14 @@ self.occupants_list.clear() AbsolutePanel.clear(self) -class ChatPanel(LiberviaWidget): +class ChatPanel(base_widget.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) + base_widget.LiberviaWidget.__init__(self, host, target.bare, selectable = True) self.vpanel = VerticalPanel() self.vpanel.setSize('100%','100%') self.type = type_ @@ -730,7 +439,7 @@ self.__body.add(chat_area) self.content = AbsolutePanel() self.content.setStyleName('chatContent') - self.content_scroll = ScrollPanelWrapper(self.content) + 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) @@ -740,15 +449,27 @@ """def doDetachChildren(self): #We need to force the use of a panel subclass method here, #for the same reason as doAttachChildren - ScrollPanelWrapper.doDetachChildren(self) + base_widget.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)""" + base_widget.ScrollPanelWrapper.doAttachChildren(self)""" + + @classmethod + def registerClass(cls): + base_widget.LiberviaWidget.addDropKey("CONTACT", cls.onDropCreate) + + @classmethod + def onDropCreate(cls, host, item): + _contact = JID(item) + host.contact_panel.setContactMessageWaiting(_contact.bare, False) + _new_panel = ChatPanel(host, _contact) #XXX: pyjamas doesn't seems to support creating with cls directly + _new_panel.historyPrint() + return _new_panel def onQuit(self): - LiberviaWidget.onQuit(self) + base_widget.LiberviaWidget.onQuit(self) if self.type == 'group': self.host.bridge.call('mucLeave', None, self.target.bare) @@ -833,14 +554,14 @@ elif game_type=="RadioCol": return self.radiocol_panel -class WebPanel(LiberviaWidget): +class WebPanel(base_widget.LiberviaWidget): """ (mini)browser like widget """ def __init__(self, host, url=None): """ @param host: SatWebFrontend instance """ - LiberviaWidget.__init__(self, host) + base_widget.LiberviaWidget.__init__(self, host) self._vpanel = VerticalPanel() self._vpanel.setSize('100%', '100%') self._url = dialog.ExtTextBox(enter_cb = self.onUrlClick) @@ -864,143 +585,6 @@ 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 """ @@ -1012,7 +596,7 @@ contacts_switch.addStyleName('contactsSwitch') self._left.add(contacts_switch) self._left.add(self.host.contact_panel) - self._right = WidgetsPanel(host) + self._right = base_widget.WidgetsPanel(host) self._right.setWidth('100%') self._right.setHeight('100%') self.add(self._left) @@ -1024,50 +608,6 @@ 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) - widgets_count = self.getWidgetCount() - if widgets_count == 1: - self.tabBar.setVisible(False) - self.host.resize() - self.selectTab(0) - else: - self.selectTab(widgets_count - 1) - class MainPanel(AbsolutePanel): def __init__(self, host): @@ -1092,8 +632,8 @@ _contacts.add(self.host.contact_panel) #tabs - self.tab_panel = MainTabPanel(host) - self.discuss_panel = WidgetsPanel(self.host, locked=True) + self.tab_panel = base_widget.MainTabPanel(host) + self.discuss_panel = base_widget.WidgetsPanel(self.host, locked=True) self.tab_panel.add(self.discuss_panel, "Discussions") self.tab_panel.selectTab(0)
--- a/libervia.py Mon Mar 04 00:19:03 2013 +0100 +++ b/libervia.py Mon Mar 04 23:01:57 2013 +0100 @@ -28,7 +28,8 @@ from pyjamas.JSONService import JSONProxy from browser_side.register import RegisterBox from browser_side.contact import ContactPanel -from browser_side.panels import WidgetsPanel, MicroblogItem +from browser_side.base_widget import WidgetsPanel +from browser_side.panels import MicroblogItem from browser_side import panels, dialog from browser_side.jid import JID from browser_side.tools import html_sanitize @@ -120,6 +121,8 @@ class SatWebFrontend: def onModuleLoad(self): print "============ onModuleLoad ==============" + panels.ChatPanel.registerClass() + panels.MicroblogPanel.registerClass() self.whoami = None self._selected_listeners = set() self.bridge = BridgeCall()