comparison src/browser/sat_browser/base_widget.py @ 467:97c72fe4a5f2

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