Mercurial > libervia-web
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 = ' ' | |
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 = ' ' | |
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) |