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