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='&nbsp;'
+            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='&nbsp;'
-            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()