comparison src/browser/sat_browser/base_widget.py @ 679:a90cc8fc9605

merged branch frontends_multi_profiles
author Goffi <goffi@goffi.org>
date Wed, 18 Mar 2015 16:15:18 +0100
parents 849ffb24d5bf
children 9877607c719a
comparison
equal deleted inserted replaced
590:1bffc4c244c3 679:a90cc8fc9605
15 # GNU Affero General Public License for more details. 15 # GNU Affero General Public License for more details.
16 16
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 import pyjd # this is dummy in pyjs
21 from sat.core.log import getLogger 20 from sat.core.log import getLogger
22 log = getLogger(__name__) 21 log = getLogger(__name__)
23 from pyjamas.ui.SimplePanel import SimplePanel
24 from pyjamas.ui.AbsolutePanel import AbsolutePanel
25 from pyjamas.ui.VerticalPanel import VerticalPanel
26 from pyjamas.ui.HorizontalPanel import HorizontalPanel
27 from pyjamas.ui.ScrollPanel import ScrollPanel
28 from pyjamas.ui.FlexTable import FlexTable
29 from pyjamas.ui.TabPanel import TabPanel
30 from pyjamas.ui.HTMLPanel import HTMLPanel
31 from pyjamas.ui.Label import Label
32 from pyjamas.ui.HTML import HTML
33 from pyjamas.ui.Button import Button
34 from pyjamas.ui.Widget import Widget
35 from pyjamas.ui.DragWidget import DragWidget
36 from pyjamas.ui.DropWidget import DropWidget
37 from pyjamas.ui.ClickListener import ClickHandler
38 from pyjamas.ui import HasAlignment
39 from pyjamas import DOM
40 from pyjamas import Window
41
42 from __pyjamas__ import doc
43
44 import dialog
45 import base_menu 22 import base_menu
23 from sat_frontends.quick_frontend import quick_menus
46 24
47 25
48 class DragLabel(DragWidget): 26 ### Exceptions ###
49
50 def __init__(self, text, _type):
51 DragWidget.__init__(self)
52 self._text = text
53 self._type = _type
54
55 def onDragStart(self, event):
56 dt = event.dataTransfer
57 dt.setData('text/plain', "%s\n%s" % (self._text, self._type))
58 dt.setDragImage(self.getElement(), 15, 15)
59 27
60 28
61 class LiberviaDragWidget(DragLabel): 29 class NoLiberviaWidgetException(Exception):
62 """ A DragLabel which keep the widget being dragged as class value """ 30 """A Libervia widget was expected"""
63 current = None # widget currently dragged 31 pass
64
65 def __init__(self, text, _type, widget):
66 DragLabel.__init__(self, text, _type)
67 self.widget = widget
68
69 def onDragStart(self, event):
70 LiberviaDragWidget.current = self.widget
71 DragLabel.onDragStart(self, event)
72
73 def onDragEnd(self, event):
74 LiberviaDragWidget.current = None
75 32
76 33
77 class DropCell(DropWidget): 34 ### Menus ###
78 """Cell in the middle grid which replace itself with the dropped widget on DnD"""
79 drop_keys = {}
80
81 def __init__(self, host):
82 DropWidget.__init__(self)
83 self.host = host
84 self.setStyleName('dropCell')
85
86 @classmethod
87 def addDropKey(cls, key, callback):
88 DropCell.drop_keys[key] = callback
89
90 def onDragEnter(self, event):
91 if self == LiberviaDragWidget.current:
92 return
93 self.addStyleName('dragover')
94 DOM.eventPreventDefault(event)
95
96 def onDragLeave(self, event):
97 if event.clientX <= self.getAbsoluteLeft() or event.clientY <= self.getAbsoluteTop() or\
98 event.clientX >= self.getAbsoluteLeft() + self.getOffsetWidth() - 1 or event.clientY >= self.getAbsoluteTop() + self.getOffsetHeight() - 1:
99 # We check that we are inside widget's box, and we don't remove the style in this case because
100 # if the mouse is over a widget inside the DropWidget, if will leave the DropWidget, and we
101 # don't want that
102 self.removeStyleName('dragover')
103
104 def onDragOver(self, event):
105 DOM.eventPreventDefault(event)
106
107 def _getCellAndRow(self, grid, event):
108 """Return cell and row index where the event is occuring"""
109 cell = grid.getEventTargetCell(event)
110 row = DOM.getParent(cell)
111 return (row.rowIndex, cell.cellIndex)
112
113 def onDrop(self, event):
114 self.removeStyleName('dragover')
115 DOM.eventPreventDefault(event)
116 dt = event.dataTransfer
117 # 'text', 'text/plain', and 'Text' are equivalent.
118 try:
119 item, item_type = dt.getData("text/plain").split('\n') # Workaround for webkit, only text/plain seems to be managed
120 if item_type and item_type[-1] == '\0': # Workaround for what looks like a pyjamas bug: the \0 should not be there, and
121 item_type = item_type[:-1] # .strip('\0') and .replace('\0','') don't work. TODO: check this and fill a bug report
122 # item_type = dt.getData("type")
123 log.debug("message: %s" % item)
124 log.debug("type: %s" % item_type)
125 except:
126 log.debug("no message found")
127 item = '&nbsp;'
128 item_type = None
129 if item_type == "WIDGET":
130 if not LiberviaDragWidget.current:
131 log.error("No widget registered in LiberviaDragWidget !")
132 return
133 _new_panel = LiberviaDragWidget.current
134 if self == _new_panel: # We can't drop on ourself
135 return
136 # we need to remove the widget from the panel as it will be inserted elsewhere
137 widgets_panel = _new_panel.getWidgetsPanel()
138 wid_row = widgets_panel.getWidgetCoords(_new_panel)[0]
139 row_wids = widgets_panel.getLiberviaRowWidgets(wid_row)
140 if len(row_wids) == 1 and wid_row == widgets_panel.getWidgetCoords(self)[0]:
141 # the dropped widget is the only one in the same row
142 # as the target widget (self), we don't do anything
143 return
144 widgets_panel.removeWidget(_new_panel)
145 elif item_type in self.drop_keys:
146 _new_panel = self.drop_keys[item_type](self.host, item)
147 else:
148 log.warning("unmanaged item type")
149 return
150 if isinstance(self, LiberviaWidget):
151 self.host.unregisterWidget(self)
152 self.onQuit()
153 if not isinstance(_new_panel, LiberviaWidget):
154 log.warning("droping an object which is not a class of LiberviaWidget")
155 _flextable = self.getParent()
156 _widgetspanel = _flextable.getParent().getParent()
157 row_idx, cell_idx = self._getCellAndRow(_flextable, event)
158 if self.host.getSelected == self:
159 self.host.setSelected(None)
160 _widgetspanel.changeWidget(row_idx, cell_idx, _new_panel)
161 """_unempty_panels = filter(lambda wid:not isinstance(wid,EmptyWidget),list(_flextable))
162 _width = 90/float(len(_unempty_panels) or 1)
163 #now we resize all the cell of the column
164 for panel in _unempty_panels:
165 td_elt = panel.getElement().parentNode
166 DOM.setStyleAttribute(td_elt, "width", "%s%%" % _width)"""
167 #FIXME: delete object ? Check the right way with pyjamas
168 35
169 36
170 class WidgetMenuBar(base_menu.GenericMenuBar): 37 class WidgetMenuBar(base_menu.GenericMenuBar):
171 38
172 ITEM_TPL = "<img src='media/icons/misc/%s.png' />" 39 ITEM_TPL = "<img src='media/icons/misc/%s.png' />"
173 40
174 def __init__(self, parent, host, vertical=False, styles=None): 41 def __init__(self, parent, host, vertical=False, styles=None):
42 """
43
44 @param parent (Widget): LiberviaWidget, or instance of another class
45 implementing the method addMenus
46 @param host (SatWebFrontend)
47 @param vertical (bool): if True, set the menu vertically
48 @param styles (dict): optional styles dict
49 """
175 menu_styles = {'menu_bar': 'widgetHeader_buttonGroup'} 50 menu_styles = {'menu_bar': 'widgetHeader_buttonGroup'}
176 if styles: 51 if styles:
177 menu_styles.update(styles) 52 menu_styles.update(styles)
178 base_menu.GenericMenuBar.__init__(self, host, vertical=vertical, styles=menu_styles) 53 base_menu.GenericMenuBar.__init__(self, host, vertical=vertical, styles=menu_styles)
179 54
180 if hasattr(parent, 'addMenus'): 55 # regroup all the dynamic menu categories in a sub-menu
181 # regroup all the dynamic menu categories in a sub-menu 56 for menu_context in parent.plugin_menu_context:
182 sub_menu = WidgetSubMenuBar(host, vertical=True) 57 main_cont = host.menus.getMainContainer(menu_context)
183 parent.addMenus(sub_menu) 58 if len(main_cont)>0: # we don't add the icon if the menu is empty
184 if len(sub_menu.getCategories()) > 0: 59 sub_menu = base_menu.GenericMenuBar(host, vertical=True, flat_level=1)
185 self.addCategory('', '', 'plugins', sub_menu) 60 sub_menu.update(menu_context, parent)
61 menu_category = quick_menus.MenuCategory("plugins", extra={'icon':'plugins'})
62 self.addCategory(menu_category, sub_menu)
186 63
187 @classmethod 64 @classmethod
188 def getCategoryHTML(cls, menu_name_i18n, type_): 65 def getCategoryHTML(cls, category):
189 return cls.ITEM_TPL % type_ 66 return cls.ITEM_TPL % category.icon
190
191
192 class WidgetSubMenuBar(base_menu.GenericMenuBar):
193
194 def __init__(self, host, vertical=True):
195 base_menu.GenericMenuBar.__init__(self, host, vertical=vertical, flat_level=1)
196
197 @classmethod
198 def getCategoryHTML(cls, menu_name_i18n, type_):
199 return menu_name_i18n
200
201
202 class WidgetHeader(AbsolutePanel, LiberviaDragWidget):
203
204 def __init__(self, parent, host, title, info=None):
205 """
206 @param parent (LiberviaWidget): LiberWidget instance
207 @param host (SatWebFrontend): SatWebFrontend instance
208 @param title (Label, HTML): text widget instance
209 @param info (Widget): text widget instance
210 """
211 AbsolutePanel.__init__(self)
212 self.add(title)
213 if info:
214 # FIXME: temporary design to display the info near the menu
215 button_group_wrapper = HorizontalPanel()
216 button_group_wrapper.add(info)
217 else:
218 button_group_wrapper = SimplePanel()
219 button_group_wrapper.setStyleName('widgetHeader_buttonsWrapper')
220 button_group = WidgetMenuBar(parent, host)
221 button_group.addItem('<img src="media/icons/misc/settings.png"/>', True, base_menu.MenuCmd(parent, 'onSetting'))
222 button_group.addItem('<img src="media/icons/misc/close.png"/>', True, base_menu.MenuCmd(parent, 'onClose'))
223 button_group_wrapper.add(button_group)
224 self.add(button_group_wrapper)
225 self.addStyleName('widgetHeader')
226 LiberviaDragWidget.__init__(self, "", "WIDGET", parent)
227
228
229 class LiberviaWidget(DropCell, VerticalPanel, ClickHandler):
230 """Libervia's widget which can replace itself with a dropped widget on DnD"""
231
232 def __init__(self, host, title='', info=None, selectable=False):
233 """Init the widget
234 @param host (SatWebFrontend): SatWebFrontend instance
235 @param title (str): title shown in the header of the widget
236 @param info (str, callable): info shown in the header of the widget
237 @param selectable (bool): True is widget can be selected by user"""
238 VerticalPanel.__init__(self)
239 DropCell.__init__(self, host)
240 ClickHandler.__init__(self)
241 self.__selectable = selectable
242 self.__title_id = HTMLPanel.createUniqueId()
243 self.__setting_button_id = HTMLPanel.createUniqueId()
244 self.__close_button_id = HTMLPanel.createUniqueId()
245 self.__title = Label(title)
246 self.__title.setStyleName('widgetHeader_title')
247 if info is not None:
248 if isinstance(info, str):
249 self.__info = HTML(info)
250 else: # the info will be set by a callback
251 assert(callable(info))
252 self.__info = HTML()
253 info(self.__info.setHTML)
254 self.__info.setStyleName('widgetHeader_info')
255 else:
256 self.__info = None
257 self._close_listeners = []
258 header = WidgetHeader(self, host, self.__title, self.__info)
259 self.add(header)
260 self.setSize('100%', '100%')
261 self.addStyleName('widget')
262 if self.__selectable:
263 self.addClickListener(self)
264
265 def onClose(sender):
266 """Check dynamically if the unibox is enable or not"""
267 if self.host.uni_box:
268 self.host.uni_box.onWidgetClosed(sender)
269
270 self.addCloseListener(onClose)
271 self.host.registerWidget(self)
272
273 def getDebugName(self):
274 return "%s (%s)" % (self, self.__title.getText())
275
276 def getWidgetsPanel(self, expect=True):
277 return self.getParent(WidgetsPanel, expect)
278
279 def getParent(self, class_=None, expect=True):
280 """Return the closest ancestor of the specified class.
281
282 Note: this method overrides pyjamas.ui.Widget.getParent
283
284 @param class_: class of the ancestor to look for or None to return the first parent
285 @param expect: set to True if the parent is expected (print a message if not found)
286 @return: the parent/ancestor or None if it has not been found
287 """
288 current = Widget.getParent(self)
289 if class_ is None:
290 return current # this is the default behavior
291 while current is not None and not isinstance(current, class_):
292 current = Widget.getParent(current)
293 if current is None and expect:
294 log.error("Can't find parent %s for %s" % (class_, self))
295 return current
296
297 def onClick(self, sender):
298 self.host.setSelected(self)
299
300 def onClose(self, sender):
301 """ Called when the close button is pushed """
302 _widgetspanel = self.getWidgetsPanel()
303 _widgetspanel.removeWidget(self)
304 for callback in self._close_listeners:
305 callback(self)
306 self.onQuit()
307
308 def onQuit(self):
309 """ Called when the widget is actually ending """
310 pass
311
312 def addCloseListener(self, callback):
313 """Add a close listener to this widget
314 @param callback: function to be called from self.onClose"""
315 self._close_listeners.append(callback)
316
317 def refresh(self):
318 """This can be overwritten by a child class to refresh the display when,
319 instead of creating a new one, an existing widget is found and reused.
320 """
321 pass
322
323 def onSetting(self, sender):
324 widpanel = self.getWidgetsPanel()
325 row, col = widpanel.getIndex(self)
326 body = VerticalPanel()
327
328 # colspan & rowspan
329 colspan = widpanel.getColSpan(row, col)
330 rowspan = widpanel.getRowSpan(row, col)
331
332 def onColSpanChange(value):
333 widpanel.setColSpan(row, col, value)
334
335 def onRowSpanChange(value):
336 widpanel.setRowSpan(row, col, value)
337 colspan_setter = dialog.IntSetter("Columns span", colspan)
338 colspan_setter.addValueChangeListener(onColSpanChange)
339 colspan_setter.setWidth('100%')
340 rowspan_setter = dialog.IntSetter("Rows span", rowspan)
341 rowspan_setter.addValueChangeListener(onRowSpanChange)
342 rowspan_setter.setWidth('100%')
343 body.add(colspan_setter)
344 body.add(rowspan_setter)
345
346 # size
347 width_str = self.getWidth()
348 if width_str.endswith('px'):
349 width = int(width_str[:-2])
350 else:
351 width = 0
352 height_str = self.getHeight()
353 if height_str.endswith('px'):
354 height = int(height_str[:-2])
355 else:
356 height = 0
357
358 def onWidthChange(value):
359 if not value:
360 self.setWidth('100%')
361 else:
362 self.setWidth('%dpx' % value)
363
364 def onHeightChange(value):
365 if not value:
366 self.setHeight('100%')
367 else:
368 self.setHeight('%dpx' % value)
369 width_setter = dialog.IntSetter("width (0=auto)", width)
370 width_setter.addValueChangeListener(onWidthChange)
371 width_setter.setWidth('100%')
372 height_setter = dialog.IntSetter("height (0=auto)", height)
373 height_setter.addValueChangeListener(onHeightChange)
374 height_setter.setHeight('100%')
375 body.add(width_setter)
376 body.add(height_setter)
377
378 # reset
379 def onReset(sender):
380 colspan_setter.setValue(1)
381 rowspan_setter.setValue(1)
382 width_setter.setValue(0)
383 height_setter.setValue(0)
384
385 reset_bt = Button("Reset", onReset)
386 body.add(reset_bt)
387 body.setCellHorizontalAlignment(reset_bt, HasAlignment.ALIGN_CENTER)
388
389 _dialog = dialog.GenericDialog("Widget setting", body)
390 _dialog.show()
391
392 def setTitle(self, text):
393 """change the title in the header of the widget
394 @param text: text of the new title"""
395 self.__title.setText(text)
396
397 def setHeaderInfo(self, text):
398 """change the info in the header of the widget
399 @param text: text of the new title"""
400 try:
401 self.__info.setHTML(text)
402 except TypeError:
403 log.error("LiberviaWidget.setInfo: info widget has not been initialized!")
404
405 def isSelectable(self):
406 return self.__selectable
407
408 def setSelectable(self, selectable):
409 if not self.__selectable:
410 try:
411 self.removeClickListener(self)
412 except ValueError:
413 pass
414 if self.selectable and not self in self._clickListeners:
415 self.addClickListener(self)
416 self.__selectable = selectable
417
418 def getWarningData(self):
419 """ Return exposition warning level when this widget is selected and something is sent to it
420 This method should be overriden by children
421 @return: tuple (warning level type/HTML msg). Type can be one of:
422 - PUBLIC
423 - GROUP
424 - ONE2ONE
425 - MISC
426 - NONE
427 """
428 if not self.__selectable:
429 log.error("getWarningLevel must not be called for an unselectable widget")
430 raise Exception
431 # TODO: cleaner warning types (more general constants)
432 return ("NONE", None)
433
434 def setWidget(self, widget, scrollable=True):
435 """Set the widget that will be in the body of the LiberviaWidget
436 @param widget: widget to put in the body
437 @param scrollable: if true, the widget will be in a ScrollPanelWrapper"""
438 if scrollable:
439 _scrollpanelwrapper = ScrollPanelWrapper()
440 _scrollpanelwrapper.setStyleName('widgetBody')
441 _scrollpanelwrapper.setWidget(widget)
442 body_wid = _scrollpanelwrapper
443 else:
444 body_wid = widget
445 self.add(body_wid)
446 self.setCellHeight(body_wid, '100%')
447
448 def doDetachChildren(self):
449 # We need to force the use of a panel subclass method here,
450 # for the same reason as doAttachChildren
451 VerticalPanel.doDetachChildren(self)
452
453 def doAttachChildren(self):
454 # We need to force the use of a panel subclass method here, else
455 # the event will not propagate to children
456 VerticalPanel.doAttachChildren(self)
457
458 def matchEntity(self, item):
459 """Check if this widget corresponds to the given entity.
460
461 This method should be overwritten by child classes.
462 @return: True if the widget matches the entity"""
463 raise NotImplementedError
464
465 def addMenus(self, menu_bar):
466 """Add menus to the header.
467
468 This method can be overwritten by child classes.
469 @param menu_bar (GenericMenuBar): menu bar of the widget's header
470 """
471 pass
472
473
474 class ScrollPanelWrapper(SimplePanel):
475 """Scroll Panel like component, wich use the full available space
476 to work around percent size issue, it use some of the ideas found
477 here: http://code.google.com/p/google-web-toolkit/issues/detail?id=316
478 specially in code given at comment #46, thanks to Stefan Bachert"""
479
480 def __init__(self, *args, **kwargs):
481 SimplePanel.__init__(self)
482 self.spanel = ScrollPanel(*args, **kwargs)
483 SimplePanel.setWidget(self, self.spanel)
484 DOM.setStyleAttribute(self.getElement(), "position", "relative")
485 DOM.setStyleAttribute(self.getElement(), "top", "0px")
486 DOM.setStyleAttribute(self.getElement(), "left", "0px")
487 DOM.setStyleAttribute(self.getElement(), "width", "100%")
488 DOM.setStyleAttribute(self.getElement(), "height", "100%")
489 DOM.setStyleAttribute(self.spanel.getElement(), "position", "absolute")
490 DOM.setStyleAttribute(self.spanel.getElement(), "width", "100%")
491 DOM.setStyleAttribute(self.spanel.getElement(), "height", "100%")
492
493 def setWidget(self, widget):
494 self.spanel.setWidget(widget)
495
496 def setScrollPosition(self, position):
497 self.spanel.setScrollPosition(position)
498
499 def scrollToBottom(self):
500 self.setScrollPosition(self.spanel.getElement().scrollHeight)
501
502
503 class EmptyWidget(DropCell, SimplePanel):
504 """Empty dropable panel"""
505
506 def __init__(self, host):
507 SimplePanel.__init__(self)
508 DropCell.__init__(self, host)
509 #self.setWidget(HTML(''))
510 self.setSize('100%', '100%')
511
512
513 class BorderWidget(EmptyWidget):
514 def __init__(self, host):
515 EmptyWidget.__init__(self, host)
516 self.addStyleName('borderPanel')
517
518
519 class LeftBorderWidget(BorderWidget):
520 def __init__(self, host):
521 BorderWidget.__init__(self, host)
522 self.addStyleName('leftBorderWidget')
523
524
525 class RightBorderWidget(BorderWidget):
526 def __init__(self, host):
527 BorderWidget.__init__(self, host)
528 self.addStyleName('rightBorderWidget')
529
530
531 class BottomBorderWidget(BorderWidget):
532 def __init__(self, host):
533 BorderWidget.__init__(self, host)
534 self.addStyleName('bottomBorderWidget')
535
536
537 class WidgetsPanel(ScrollPanelWrapper):
538
539 def __init__(self, host, locked=False):
540 ScrollPanelWrapper.__init__(self)
541 self.setSize('100%', '100%')
542 self.host = host
543 self.locked = locked # if True: tab will not be removed when there are no more widgets inside
544 self.selected = None
545 self.flextable = FlexTable()
546 self.flextable.setSize('100%', '100%')
547 self.setWidget(self.flextable)
548 self.setStyleName('widgetsPanel')
549 _bottom = BottomBorderWidget(self.host)
550 self.flextable.setWidget(0, 0, _bottom) # There will be always an Empty widget on the last row,
551 # dropping a widget there will add a new row
552 td_elt = _bottom.getElement().parentNode
553 DOM.setStyleAttribute(td_elt, "height", "1px") # needed so the cell adapt to the size of the border (specially in webkit)
554 self._max_cols = 1 # give the maximum number of columns i a raw
555
556 def isLocked(self):
557 return self.locked
558
559 def changeWidget(self, row, col, wid):
560 """Change the widget in the given location, add row or columns when necessary"""
561 log.debug("changing widget: %s %s %s" % (wid.getDebugName(), row, col))
562 last_row = max(0, self.flextable.getRowCount() - 1)
563 try:
564 prev_wid = self.flextable.getWidget(row, col)
565 except:
566 log.error("Trying to change an unexisting widget !")
567 return
568
569 cellFormatter = self.flextable.getFlexCellFormatter()
570
571 if isinstance(prev_wid, BorderWidget):
572 # We are on a border, we must create a row and/or columns
573 log.debug("BORDER WIDGET")
574 prev_wid.removeStyleName('dragover')
575
576 if isinstance(prev_wid, BottomBorderWidget):
577 # We are on the bottom border, we create a new row
578 self.flextable.insertRow(last_row)
579 self.flextable.setWidget(last_row, 0, LeftBorderWidget(self.host))
580 self.flextable.setWidget(last_row, 1, wid)
581 self.flextable.setWidget(last_row, 2, RightBorderWidget(self.host))
582 cellFormatter.setHorizontalAlignment(last_row, 2, HasAlignment.ALIGN_RIGHT)
583 row = last_row
584
585 elif isinstance(prev_wid, LeftBorderWidget):
586 if col != 0:
587 log.error("LeftBorderWidget must be on the first column !")
588 return
589 self.flextable.insertCell(row, col + 1)
590 self.flextable.setWidget(row, 1, wid)
591
592 elif isinstance(prev_wid, RightBorderWidget):
593 if col != self.flextable.getCellCount(row) - 1:
594 log.error("RightBorderWidget must be on the last column !")
595 return
596 self.flextable.insertCell(row, col)
597 self.flextable.setWidget(row, col, wid)
598
599 else:
600 prev_wid.removeFromParent()
601 self.flextable.setWidget(row, col, wid)
602
603 _max_cols = max(self._max_cols, self.flextable.getCellCount(row))
604 if _max_cols != self._max_cols:
605 self._max_cols = _max_cols
606 self._sizesAdjust()
607
608 def _sizesAdjust(self):
609 cellFormatter = self.flextable.getFlexCellFormatter()
610 width = 100.0 / max(1, self._max_cols - 2) # we don't count the borders
611
612 for row_idx in xrange(self.flextable.getRowCount()):
613 for col_idx in xrange(self.flextable.getCellCount(row_idx)):
614 _widget = self.flextable.getWidget(row_idx, col_idx)
615 if not isinstance(_widget, BorderWidget):
616 td_elt = _widget.getElement().parentNode
617 DOM.setStyleAttribute(td_elt, "width", "%.2f%%" % width)
618
619 last_row = max(0, self.flextable.getRowCount() - 1)
620 cellFormatter.setColSpan(last_row, 0, self._max_cols)
621
622 def addWidget(self, wid):
623 """Add a widget to a new cell on the next to last row"""
624 last_row = max(0, self.flextable.getRowCount() - 1)
625 log.debug("putting widget %s at %d, %d" % (wid.getDebugName(), last_row, 0))
626 self.changeWidget(last_row, 0, wid)
627
628 def removeWidget(self, wid):
629 """Remove a widget and the cell where it is"""
630 _row, _col = self.flextable.getIndex(wid)
631 self.flextable.remove(wid)
632 self.flextable.removeCell(_row, _col)
633 if not self.getLiberviaRowWidgets(_row): # we have no more widgets, we remove the row
634 self.flextable.removeRow(_row)
635 _max_cols = 1
636 for row_idx in xrange(self.flextable.getRowCount()):
637 _max_cols = max(_max_cols, self.flextable.getCellCount(row_idx))
638 if _max_cols != self._max_cols:
639 self._max_cols = _max_cols
640 self._sizesAdjust()
641 current = self
642
643 blank_page = self.getLiberviaWidgetsCount() == 0 # do we still have widgets on the page ?
644
645 if blank_page and not self.isLocked():
646 # we now notice the MainTabPanel that the WidgetsPanel is empty and need to be removed
647 while current is not None:
648 if isinstance(current, MainTabPanel):
649 current.onWidgetPanelRemove(self)
650 return
651 current = current.getParent()
652 log.error("no MainTabPanel found !")
653
654 def getWidgetCoords(self, wid):
655 return self.flextable.getIndex(wid)
656
657 def getLiberviaRowWidgets(self, row):
658 """ Return all the LiberviaWidget in the row """
659 return [wid for wid in self.getRowWidgets(row) if isinstance(wid, LiberviaWidget)]
660
661 def getRowWidgets(self, row):
662 """ Return all the widgets in the row """
663 widgets = []
664 cols = self.flextable.getCellCount(row)
665 for col in xrange(cols):
666 widgets.append(self.flextable.getWidget(row, col))
667 return widgets
668
669 def getLiberviaWidgetsCount(self):
670 """ Get count of contained widgets """
671 return len([wid for wid in self.flextable if isinstance(wid, LiberviaWidget)])
672
673 def getIndex(self, wid):
674 return self.flextable.getIndex(wid)
675
676 def getColSpan(self, row, col):
677 cellFormatter = self.flextable.getFlexCellFormatter()
678 return cellFormatter.getColSpan(row, col)
679
680 def setColSpan(self, row, col, value):
681 cellFormatter = self.flextable.getFlexCellFormatter()
682 return cellFormatter.setColSpan(row, col, value)
683
684 def getRowSpan(self, row, col):
685 cellFormatter = self.flextable.getFlexCellFormatter()
686 return cellFormatter.getRowSpan(row, col)
687
688 def setRowSpan(self, row, col, value):
689 cellFormatter = self.flextable.getFlexCellFormatter()
690 return cellFormatter.setRowSpan(row, col, value)
691
692
693 class DropTab(Label, DropWidget):
694
695 def __init__(self, tab_panel, text):
696 Label.__init__(self, text)
697 DropWidget.__init__(self, tab_panel)
698 self.tab_panel = tab_panel
699 self.setStyleName('dropCell')
700 self.setWordWrap(False)
701 DOM.setStyleAttribute(self.getElement(), "min-width", "30px")
702
703 def _getIndex(self):
704 """ get current index of the DropTab """
705 # XXX: awful hack, but seems the only way to get index
706 return self.tab_panel.tabBar.panel.getWidgetIndex(self.getParent().getParent()) - 1
707
708 def onDragEnter(self, event):
709 #if self == LiberviaDragWidget.current:
710 # return
711 self.addStyleName('dragover')
712 DOM.eventPreventDefault(event)
713
714 def onDragLeave(self, event):
715 self.removeStyleName('dragover')
716
717 def onDragOver(self, event):
718 DOM.eventPreventDefault(event)
719
720 def onDrop(self, event):
721 DOM.eventPreventDefault(event)
722 self.removeStyleName('dragover')
723 if self._getIndex() == self.tab_panel.tabBar.getSelectedTab():
724 # the widget come from the DragTab, so nothing to do, we let it there
725 return
726
727 # FIXME: quite the same stuff as in DropCell, need some factorisation
728 dt = event.dataTransfer
729 # 'text', 'text/plain', and 'Text' are equivalent.
730 try:
731 item, item_type = dt.getData("text/plain").split('\n') # Workaround for webkit, only text/plain seems to be managed
732 if item_type and item_type[-1] == '\0': # Workaround for what looks like a pyjamas bug: the \0 should not be there, and
733 item_type = item_type[:-1] # .strip('\0') and .replace('\0','') don't work. TODO: check this and fill a bug report
734 # item_type = dt.getData("type")
735 log.debug("message: %s" % item)
736 log.debug("type: %s" % item_type)
737 except:
738 log.debug("no message found")
739 item = '&nbsp;'
740 item_type = None
741 if item_type == "WIDGET":
742 if not LiberviaDragWidget.current:
743 log.error("No widget registered in LiberviaDragWidget !")
744 return
745 _new_panel = LiberviaDragWidget.current
746 _new_panel.getWidgetsPanel().removeWidget(_new_panel)
747 elif item_type in DropCell.drop_keys:
748 _new_panel = DropCell.drop_keys[item_type](self.tab_panel.host, item)
749 else:
750 log.warning("unmanaged item type")
751 return
752
753 widgets_panel = self.tab_panel.getWidget(self._getIndex())
754 widgets_panel.addWidget(_new_panel)
755
756
757 class MainTabPanel(TabPanel):
758
759 def __init__(self, host):
760 TabPanel.__init__(self)
761 self.host = host
762 self.tabBar.setVisible(False)
763 self.setStyleName('liberviaTabPanel')
764 self.addStyleName('mainTabPanel')
765 Window.addWindowResizeListener(self)
766
767 def getCurrentPanel(self):
768 """ Get the panel of the currently selected tab """
769 return self.deck.visibleWidget
770
771 def onWindowResized(self, width, height):
772 tab_panel_elt = self.getElement()
773 _elts = doc().getElementsByClassName('gwt-TabBar')
774 if not _elts.length:
775 log.error("no TabBar found, it should exist !")
776 tab_bar_h = 0
777 else:
778 tab_bar_h = _elts.item(0).offsetHeight
779 ideal_height = height - DOM.getAbsoluteTop(tab_panel_elt) - tab_bar_h - 5
780 ideal_width = width - DOM.getAbsoluteLeft(tab_panel_elt) - 5
781 self.setWidth("%s%s" % (ideal_width, "px"))
782 self.setHeight("%s%s" % (ideal_height, "px"))
783
784 def add(self, widget, text=''):
785 tab = DropTab(self, text)
786 TabPanel.add(self, widget, tab, False)
787 if self.getWidgetCount() > 1:
788 self.tabBar.setVisible(True)
789 self.host.resize()
790
791 def onWidgetPanelRemove(self, panel):
792 """ Called when a child WidgetsPanel is empty and need to be removed """
793 self.remove(panel)
794 widgets_count = self.getWidgetCount()
795 if widgets_count == 1:
796 self.tabBar.setVisible(False)
797 self.host.resize()
798 self.selectTab(0)
799 else:
800 self.selectTab(widgets_count - 1)