comparison src/browser/base_widget.py @ 449:981ed669d3b3

/!\ reorganize all the file hierarchy, move the code and launching script to src: - browser_side --> src/browser - public --> src/browser_side/public - libervia.py --> src/browser/libervia_main.py - libervia_server --> src/server - libervia_server/libervia.sh --> src/libervia.sh - twisted --> src/twisted - new module src/common - split constants.py in 3 files: - src/common/constants.py - src/browser/constants.py - src/server/constants.py - output --> html (generated by pyjsbuild during the installation) - new option/parameter "data_dir" (-d) to indicates the directory containing html and server_css - setup.py installs libervia to the following paths: - src/common --> <LIB>/libervia/common - src/server --> <LIB>/libervia/server - src/twisted --> <LIB>/twisted - html --> <SHARE>/libervia/html - server_side --> <SHARE>libervia/server_side - LIBERVIA_INSTALL environment variable takes 2 new options with prompt confirmation: - clean: remove previous installation directories - purge: remove building and previous installation directories You may need to update your sat.conf and/or launching script to update the following options/parameters: - ssl_certificate - data_dir
author souliane <souliane@mailoo.org>
date Tue, 20 May 2014 06:41:16 +0200
parents browser_side/base_widget.py@d52f529a6d42
children 1eeed8028199
comparison
equal deleted inserted replaced
448:14c35f7f1ef5 449:981ed669d3b3
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, verbose=True):
228 return self.getParent(WidgetsPanel, verbose)
229
230 def getParent(self, class_=None, verbose=True):
231 """
232 Note: this method overrides pyjamas.ui.Widget.getParent
233 @param class_: class of the ancestor to look for or None to return the first parent
234 @param verbose: set to True to log error messages # FIXME: must be removed
235 @return: the parent/ancestor or None if it has not been found
236 """
237 current = Widget.getParent(self)
238 if class_ is None:
239 return current # this is the default behavior
240 while current is not None and not isinstance(current, class_):
241 current = Widget.getParent(current)
242 if current is None and verbose:
243 log.debug("Can't find parent %s for %s" % (class_, self))
244 return current
245
246 def onClick(self, sender):
247 self.host.setSelected(self)
248
249 def onClose(self, sender):
250 """ Called when the close button is pushed """
251 _widgetspanel = self.getWidgetsPanel()
252 _widgetspanel.removeWidget(self)
253 for callback in self._close_listeners:
254 callback(self)
255 self.onQuit()
256
257 def onQuit(self):
258 """ Called when the widget is actually ending """
259 pass
260
261 def addCloseListener(self, callback):
262 """Add a close listener to this widget
263 @param callback: function to be called from self.onClose"""
264 self._close_listeners.append(callback)
265
266 def refresh(self):
267 """This can be overwritten by a child class to refresh the display when,
268 instead of creating a new one, an existing widget is found and reused.
269 """
270 pass
271
272 def onSetting(self, sender):
273 widpanel = self.getWidgetsPanel()
274 row, col = widpanel.getIndex(self)
275 body = VerticalPanel()
276
277 # colspan & rowspan
278 colspan = widpanel.getColSpan(row, col)
279 rowspan = widpanel.getRowSpan(row, col)
280
281 def onColSpanChange(value):
282 widpanel.setColSpan(row, col, value)
283
284 def onRowSpanChange(value):
285 widpanel.setRowSpan(row, col, value)
286 colspan_setter = dialog.IntSetter("Columns span", colspan)
287 colspan_setter.addValueChangeListener(onColSpanChange)
288 colspan_setter.setWidth('100%')
289 rowspan_setter = dialog.IntSetter("Rows span", rowspan)
290 rowspan_setter.addValueChangeListener(onRowSpanChange)
291 rowspan_setter.setWidth('100%')
292 body.add(colspan_setter)
293 body.add(rowspan_setter)
294
295 # size
296 width_str = self.getWidth()
297 if width_str.endswith('px'):
298 width = int(width_str[:-2])
299 else:
300 width = 0
301 height_str = self.getHeight()
302 if height_str.endswith('px'):
303 height = int(height_str[:-2])
304 else:
305 height = 0
306
307 def onWidthChange(value):
308 if not value:
309 self.setWidth('100%')
310 else:
311 self.setWidth('%dpx' % value)
312
313 def onHeightChange(value):
314 if not value:
315 self.setHeight('100%')
316 else:
317 self.setHeight('%dpx' % value)
318 width_setter = dialog.IntSetter("width (0=auto)", width)
319 width_setter.addValueChangeListener(onWidthChange)
320 width_setter.setWidth('100%')
321 height_setter = dialog.IntSetter("height (0=auto)", height)
322 height_setter.addValueChangeListener(onHeightChange)
323 height_setter.setHeight('100%')
324 body.add(width_setter)
325 body.add(height_setter)
326
327 # reset
328 def onReset(sender):
329 colspan_setter.setValue(1)
330 rowspan_setter.setValue(1)
331 width_setter.setValue(0)
332 height_setter.setValue(0)
333
334 reset_bt = Button("Reset", onReset)
335 body.add(reset_bt)
336 body.setCellHorizontalAlignment(reset_bt, HasAlignment.ALIGN_CENTER)
337
338 _dialog = dialog.GenericDialog("Widget setting", body)
339 _dialog.show()
340
341 def setTitle(self, text):
342 """change the title in the header of the widget
343 @param text: text of the new title"""
344 self.__title.setText(text)
345
346 def isSelectable(self):
347 return self.__selectable
348
349 def setSelectable(self, selectable):
350 if not self.__selectable:
351 try:
352 self.removeClickListener(self)
353 except ValueError:
354 pass
355 if self.selectable and not self in self._clickListeners:
356 self.addClickListener(self)
357 self.__selectable = selectable
358
359 def getWarningData(self):
360 """ Return exposition warning level when this widget is selected and something is sent to it
361 This method should be overriden by children
362 @return: tuple (warning level type/HTML msg). Type can be one of:
363 - PUBLIC
364 - GROUP
365 - ONE2ONE
366 - MISC
367 - NONE
368 """
369 if not self.__selectable:
370 log.error("getWarningLevel must not be called for an unselectable widget")
371 raise Exception
372 # TODO: cleaner warning types (more general constants)
373 return ("NONE", None)
374
375 def setWidget(self, widget, scrollable=True):
376 """Set the widget that will be in the body of the LiberviaWidget
377 @param widget: widget to put in the body
378 @param scrollable: if true, the widget will be in a ScrollPanelWrapper"""
379 if scrollable:
380 _scrollpanelwrapper = ScrollPanelWrapper()
381 _scrollpanelwrapper.setStyleName('widgetBody')
382 _scrollpanelwrapper.setWidget(widget)
383 body_wid = _scrollpanelwrapper
384 else:
385 body_wid = widget
386 self.add(body_wid)
387 self.setCellHeight(body_wid, '100%')
388
389 def doDetachChildren(self):
390 # We need to force the use of a panel subclass method here,
391 # for the same reason as doAttachChildren
392 VerticalPanel.doDetachChildren(self)
393
394 def doAttachChildren(self):
395 # We need to force the use of a panel subclass method here, else
396 # the event will not propagate to children
397 VerticalPanel.doAttachChildren(self)
398
399 def matchEntity(self, entity):
400 """This method should be overwritten by child classes."""
401 raise NotImplementedError
402
403
404 class ScrollPanelWrapper(SimplePanel):
405 """Scroll Panel like component, wich use the full available space
406 to work around percent size issue, it use some of the ideas found
407 here: http://code.google.com/p/google-web-toolkit/issues/detail?id=316
408 specially in code given at comment #46, thanks to Stefan Bachert"""
409
410 def __init__(self, *args, **kwargs):
411 SimplePanel.__init__(self)
412 self.spanel = ScrollPanel(*args, **kwargs)
413 SimplePanel.setWidget(self, self.spanel)
414 DOM.setStyleAttribute(self.getElement(), "position", "relative")
415 DOM.setStyleAttribute(self.getElement(), "top", "0px")
416 DOM.setStyleAttribute(self.getElement(), "left", "0px")
417 DOM.setStyleAttribute(self.getElement(), "width", "100%")
418 DOM.setStyleAttribute(self.getElement(), "height", "100%")
419 DOM.setStyleAttribute(self.spanel.getElement(), "position", "absolute")
420 DOM.setStyleAttribute(self.spanel.getElement(), "width", "100%")
421 DOM.setStyleAttribute(self.spanel.getElement(), "height", "100%")
422
423 def setWidget(self, widget):
424 self.spanel.setWidget(widget)
425
426 def setScrollPosition(self, position):
427 self.spanel.setScrollPosition(position)
428
429 def scrollToBottom(self):
430 self.setScrollPosition(self.spanel.getElement().scrollHeight)
431
432
433 class EmptyWidget(DropCell, SimplePanel):
434 """Empty dropable panel"""
435
436 def __init__(self, host):
437 SimplePanel.__init__(self)
438 DropCell.__init__(self, host)
439 #self.setWidget(HTML(''))
440 self.setSize('100%', '100%')
441
442
443 class BorderWidget(EmptyWidget):
444 def __init__(self, host):
445 EmptyWidget.__init__(self, host)
446 self.addStyleName('borderPanel')
447
448
449 class LeftBorderWidget(BorderWidget):
450 def __init__(self, host):
451 BorderWidget.__init__(self, host)
452 self.addStyleName('leftBorderWidget')
453
454
455 class RightBorderWidget(BorderWidget):
456 def __init__(self, host):
457 BorderWidget.__init__(self, host)
458 self.addStyleName('rightBorderWidget')
459
460
461 class BottomBorderWidget(BorderWidget):
462 def __init__(self, host):
463 BorderWidget.__init__(self, host)
464 self.addStyleName('bottomBorderWidget')
465
466
467 class WidgetsPanel(ScrollPanelWrapper):
468
469 def __init__(self, host, locked=False):
470 ScrollPanelWrapper.__init__(self)
471 self.setSize('100%', '100%')
472 self.host = host
473 self.locked = locked # if True: tab will not be removed when there are no more widgets inside
474 self.selected = None
475 self.flextable = FlexTable()
476 self.flextable.setSize('100%', '100%')
477 self.setWidget(self.flextable)
478 self.setStyleName('widgetsPanel')
479 _bottom = BottomBorderWidget(self.host)
480 self.flextable.setWidget(0, 0, _bottom) # There will be always an Empty widget on the last row,
481 # dropping a widget there will add a new row
482 td_elt = _bottom.getElement().parentNode
483 DOM.setStyleAttribute(td_elt, "height", "1px") # needed so the cell adapt to the size of the border (specially in webkit)
484 self._max_cols = 1 # give the maximum number of columns i a raw
485
486 def isLocked(self):
487 return self.locked
488
489 def changeWidget(self, row, col, wid):
490 """Change the widget in the given location, add row or columns when necessary"""
491 log.debug("changing widget: %s %s %s" % (wid.getDebugName(), row, col))
492 last_row = max(0, self.flextable.getRowCount() - 1)
493 try:
494 prev_wid = self.flextable.getWidget(row, col)
495 except:
496 log.error("Trying to change an unexisting widget !")
497 return
498
499 cellFormatter = self.flextable.getFlexCellFormatter()
500
501 if isinstance(prev_wid, BorderWidget):
502 # We are on a border, we must create a row and/or columns
503 log.debug("BORDER WIDGET")
504 prev_wid.removeStyleName('dragover')
505
506 if isinstance(prev_wid, BottomBorderWidget):
507 # We are on the bottom border, we create a new row
508 self.flextable.insertRow(last_row)
509 self.flextable.setWidget(last_row, 0, LeftBorderWidget(self.host))
510 self.flextable.setWidget(last_row, 1, wid)
511 self.flextable.setWidget(last_row, 2, RightBorderWidget(self.host))
512 cellFormatter.setHorizontalAlignment(last_row, 2, HasAlignment.ALIGN_RIGHT)
513 row = last_row
514
515 elif isinstance(prev_wid, LeftBorderWidget):
516 if col != 0:
517 log.error("LeftBorderWidget must be on the first column !")
518 return
519 self.flextable.insertCell(row, col + 1)
520 self.flextable.setWidget(row, 1, wid)
521
522 elif isinstance(prev_wid, RightBorderWidget):
523 if col != self.flextable.getCellCount(row) - 1:
524 log.error("RightBorderWidget must be on the last column !")
525 return
526 self.flextable.insertCell(row, col)
527 self.flextable.setWidget(row, col, wid)
528
529 else:
530 prev_wid.removeFromParent()
531 self.flextable.setWidget(row, col, wid)
532
533 _max_cols = max(self._max_cols, self.flextable.getCellCount(row))
534 if _max_cols != self._max_cols:
535 self._max_cols = _max_cols
536 self._sizesAdjust()
537
538 def _sizesAdjust(self):
539 cellFormatter = self.flextable.getFlexCellFormatter()
540 width = 100.0 / max(1, self._max_cols - 2) # we don't count the borders
541
542 for row_idx in xrange(self.flextable.getRowCount()):
543 for col_idx in xrange(self.flextable.getCellCount(row_idx)):
544 _widget = self.flextable.getWidget(row_idx, col_idx)
545 if not isinstance(_widget, BorderWidget):
546 td_elt = _widget.getElement().parentNode
547 DOM.setStyleAttribute(td_elt, "width", "%.2f%%" % width)
548
549 last_row = max(0, self.flextable.getRowCount() - 1)
550 cellFormatter.setColSpan(last_row, 0, self._max_cols)
551
552 def addWidget(self, wid):
553 """Add a widget to a new cell on the next to last row"""
554 last_row = max(0, self.flextable.getRowCount() - 1)
555 log.debug("putting widget %s at %d, %d" % (wid.getDebugName(), last_row, 0))
556 self.changeWidget(last_row, 0, wid)
557
558 def removeWidget(self, wid):
559 """Remove a widget and the cell where it is"""
560 _row, _col = self.flextable.getIndex(wid)
561 self.flextable.remove(wid)
562 self.flextable.removeCell(_row, _col)
563 if not self.getLiberviaRowWidgets(_row): # we have no more widgets, we remove the row
564 self.flextable.removeRow(_row)
565 _max_cols = 1
566 for row_idx in xrange(self.flextable.getRowCount()):
567 _max_cols = max(_max_cols, self.flextable.getCellCount(row_idx))
568 if _max_cols != self._max_cols:
569 self._max_cols = _max_cols
570 self._sizesAdjust()
571 current = self
572
573 blank_page = self.getLiberviaWidgetsCount() == 0 # do we still have widgets on the page ?
574
575 if blank_page and not self.isLocked():
576 # we now notice the MainTabPanel that the WidgetsPanel is empty and need to be removed
577 while current is not None:
578 if isinstance(current, MainTabPanel):
579 current.onWidgetPanelRemove(self)
580 return
581 current = current.getParent()
582 log.error("no MainTabPanel found !")
583
584 def getWidgetCoords(self, wid):
585 return self.flextable.getIndex(wid)
586
587 def getLiberviaRowWidgets(self, row):
588 """ Return all the LiberviaWidget in the row """
589 return [wid for wid in self.getRowWidgets(row) if isinstance(wid, LiberviaWidget)]
590
591 def getRowWidgets(self, row):
592 """ Return all the widgets in the row """
593 widgets = []
594 cols = self.flextable.getCellCount(row)
595 for col in xrange(cols):
596 widgets.append(self.flextable.getWidget(row, col))
597 return widgets
598
599 def getLiberviaWidgetsCount(self):
600 """ Get count of contained widgets """
601 return len([wid for wid in self.flextable if isinstance(wid, LiberviaWidget)])
602
603 def getIndex(self, wid):
604 return self.flextable.getIndex(wid)
605
606 def getColSpan(self, row, col):
607 cellFormatter = self.flextable.getFlexCellFormatter()
608 return cellFormatter.getColSpan(row, col)
609
610 def setColSpan(self, row, col, value):
611 cellFormatter = self.flextable.getFlexCellFormatter()
612 return cellFormatter.setColSpan(row, col, value)
613
614 def getRowSpan(self, row, col):
615 cellFormatter = self.flextable.getFlexCellFormatter()
616 return cellFormatter.getRowSpan(row, col)
617
618 def setRowSpan(self, row, col, value):
619 cellFormatter = self.flextable.getFlexCellFormatter()
620 return cellFormatter.setRowSpan(row, col, value)
621
622
623 class DropTab(Label, DropWidget):
624
625 def __init__(self, tab_panel, text):
626 Label.__init__(self, text)
627 DropWidget.__init__(self, tab_panel)
628 self.tab_panel = tab_panel
629 self.setStyleName('dropCell')
630 self.setWordWrap(False)
631 DOM.setStyleAttribute(self.getElement(), "min-width", "30px")
632
633 def _getIndex(self):
634 """ get current index of the DropTab """
635 # XXX: awful hack, but seems the only way to get index
636 return self.tab_panel.tabBar.panel.getWidgetIndex(self.getParent().getParent()) - 1
637
638 def onDragEnter(self, event):
639 #if self == LiberviaDragWidget.current:
640 # return
641 self.addStyleName('dragover')
642 DOM.eventPreventDefault(event)
643
644 def onDragLeave(self, event):
645 self.removeStyleName('dragover')
646
647 def onDragOver(self, event):
648 DOM.eventPreventDefault(event)
649
650 def onDrop(self, event):
651 DOM.eventPreventDefault(event)
652 self.removeStyleName('dragover')
653 if self._getIndex() == self.tab_panel.tabBar.getSelectedTab():
654 # the widget come from the DragTab, so nothing to do, we let it there
655 return
656
657 # FIXME: quite the same stuff as in DropCell, need some factorisation
658 dt = event.dataTransfer
659 # 'text', 'text/plain', and 'Text' are equivalent.
660 try:
661 item, item_type = dt.getData("text/plain").split('\n') # Workaround for webkit, only text/plain seems to be managed
662 if item_type and item_type[-1] == '\0': # Workaround for what looks like a pyjamas bug: the \0 should not be there, and
663 item_type = item_type[:-1] # .strip('\0') and .replace('\0','') don't work. TODO: check this and fill a bug report
664 # item_type = dt.getData("type")
665 log.debug("message: %s" % item)
666 log.debug("type: %s" % item_type)
667 except:
668 log.debug("no message found")
669 item = '&nbsp;'
670 item_type = None
671 if item_type == "WIDGET":
672 if not LiberviaDragWidget.current:
673 log.error("No widget registered in LiberviaDragWidget !")
674 return
675 _new_panel = LiberviaDragWidget.current
676 _new_panel.getWidgetsPanel().removeWidget(_new_panel)
677 elif item_type in DropCell.drop_keys:
678 _new_panel = DropCell.drop_keys[item_type](self.tab_panel.host, item)
679 else:
680 log.warning("unmanaged item type")
681 return
682
683 widgets_panel = self.tab_panel.getWidget(self._getIndex())
684 widgets_panel.addWidget(_new_panel)
685
686
687 class MainTabPanel(TabPanel):
688
689 def __init__(self, host):
690 TabPanel.__init__(self)
691 self.host = host
692 self.tabBar.setVisible(False)
693 self.setStyleName('liberviaTabPanel')
694 self.addStyleName('mainTabPanel')
695 Window.addWindowResizeListener(self)
696
697 def getCurrentPanel(self):
698 """ Get the panel of the currently selected tab """
699 return self.deck.visibleWidget
700
701 def onWindowResized(self, width, height):
702 tab_panel_elt = self.getElement()
703 _elts = doc().getElementsByClassName('gwt-TabBar')
704 if not _elts.length:
705 log.error("no TabBar found, it should exist !")
706 tab_bar_h = 0
707 else:
708 tab_bar_h = _elts.item(0).offsetHeight
709 ideal_height = height - DOM.getAbsoluteTop(tab_panel_elt) - tab_bar_h - 5
710 ideal_width = width - DOM.getAbsoluteLeft(tab_panel_elt) - 5
711 self.setWidth("%s%s" % (ideal_width, "px"))
712 self.setHeight("%s%s" % (ideal_height, "px"))
713
714 def add(self, widget, text=''):
715 tab = DropTab(self, text)
716 TabPanel.add(self, widget, tab, False)
717 if self.getWidgetCount() > 1:
718 self.tabBar.setVisible(True)
719 self.host.resize()
720
721 def onWidgetPanelRemove(self, panel):
722 """ Called when a child WidgetsPanel is empty and need to be removed """
723 self.remove(panel)
724 widgets_count = self.getWidgetCount()
725 if widgets_count == 1:
726 self.tabBar.setVisible(False)
727 self.host.resize()
728 self.selectTab(0)
729 else:
730 self.selectTab(widgets_count - 1)