1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Defines a tab sheet component."""
17
18 from warnings import warn
19
20 from muntjac.terminal.key_mapper import KeyMapper
21 from muntjac.terminal.paintable import IRepaintRequestListener,\
22 RepaintRequestEvent
23
24 from muntjac.ui.component import IComponent, Event as ComponentEvent
25 from muntjac.ui.abstract_component_container import AbstractComponentContainer
26
27 from muntjac.terminal.gwt.server.communication_manager import \
28 CommunicationManager
29
30
32 """Selected tab change event listener. The listener is called whenever
33 another tab is selected, including when adding the first tab to a
34 tabsheet.
35
36 @author: Vaadin Ltd.
37 @author: Richard Lincoln
38 @version: 1.1.2
39 """
40
42 """Selected (shown) tab in tab sheet has has been changed.
43
44 @param event: the selected tab change event.
45 """
46 raise NotImplementedError
47
48
49 _SELECTED_TAB_CHANGE_METHOD = getattr(ISelectedTabChangeListener,
50 'selectedTabChange')
51
52
53 -class TabSheet(AbstractComponentContainer):
54 """TabSheet component.
55
56 Tabs are typically identified by the component contained on the tab (see
57 L{ComponentContainer}), and tab metadata (including caption, icon,
58 visibility, enabledness, closability etc.) is kept in separate {@link
59 ITab} instances.
60
61 Tabs added with L{addComponent} get the caption and the
62 icon of the component at the time when the component is created, and these
63 are not automatically updated after tab creation.
64
65 A tab sheet can have multiple tab selection listeners and one tab close
66 handler (L{ICloseHandler}), which by default removes the tab from the
67 TabSheet.
68
69 The L{TabSheet} can be styled with the .v-tabsheet, .v-tabsheet-tabs
70 and .v-tabsheet-content styles. Themes may also have pre-defined
71 variations of the tab sheet presentation, such as
72 L{Reindeer.TABSHEET_BORDERLESS}, L{Runo.TABSHEET_SMALL} and
73 several other styles in L{Reindeer}.
74
75 The current implementation does not load the tabs to the UI before the
76 first time they are shown, but this may change in future releases.
77
78 @author: Vaadin Ltd.
79 @author: Richard Lincoln
80 @version: 1.1.2
81 """
82
83 CLIENT_WIDGET = None
84
86 """Constructs a new Tabsheet. Tabsheet is immediate by default, and
87 the default close handler removes the tab being closed.
88 """
89 super(TabSheet, self).__init__()
90
91
92
93
94 self._components = list()
95
96
97 self._tabs = dict()
98
99
100 self._selected = None
101
102
103
104 self._keyMapper = KeyMapper()
105
106
107 self._tabsHidden = None
108
109
110
111 self._paintedTabs = set()
112
113
114 self._closeHandler = None
115
116
117 self.setWidth(100.0, self.UNITS_PERCENTAGE)
118 self.setImmediate(True)
119
120 self.setCloseHandler(InnerHandler())
121
122
124 """Gets the component container iterator for going through all the
125 components (tab contents).
126
127 @return: the unmodifiable Iterator of the tab content components
128 """
129 return iter(self._components)
130
131
133 """Gets the number of contained components (tabs). Consistent with
134 the iterator returned by L{getComponentIterator}.
135
136 @return: the number of contained components
137 """
138 return len(self._components)
139
140
142 """Removes a component and its corresponding tab.
143
144 If the tab was selected, the first eligible (visible and enabled)
145 remaining tab is selected.
146
147 @param c: the component to be removed.
148 """
149 if c is not None and c in self._components:
150 super(TabSheet, self).removeComponent(c)
151 self._keyMapper.remove(c)
152 self._components.remove(c)
153 if c in self._tabs:
154 del self._tabs[c]
155 if c == self._selected:
156 if len(self._components) == 0:
157 self._selected = None
158 else:
159
160 self.updateSelection()
161 self.fireSelectedTabChange()
162
163 self.requestRepaint()
164
165
167 """Removes a L{ITab} and the component associated with it, as
168 previously added with L{addTab}, or L{addComponent}.
169
170 If the tab was selected, the first eligible (visible and enabled)
171 remaining tab is selected.
172
173 @see: L{addTab}
174 @see: L{addComponent}
175 @see: L{removeComponent}
176 @param tab: the ITab to remove
177 """
178 self.removeComponent(tab.getComponent())
179
180
182 """Adds a new tab into TabSheet. IComponent caption and icon are
183 copied to the tab metadata at creation time.
184
185 @see: L{addTab}
186 @param c: the component to be added.
187 """
188 self.addTab(c)
189
190
192 """Adds a new tab into TabSheet.
193
194 The first tab added to a tab sheet is automatically selected and a
195 tab selection event is fired.
196
197 If the component is already present in the tab sheet, changes its
198 caption and icon and returns the corresponding (old) tab, preserving
199 other tab metadata.
200
201 @param args: tuple of the form
202 - (c, caption, icon)
203 1. the component to be added onto tab - should not be null.
204 2. the caption to be set for the component and used rendered
205 in tab bar
206 3. the icon to be set for the component and used rendered in
207 tab bar
208 - (c, caption, icon, position)
209 1. the component to be added onto tab - should not be null.
210 2. the caption to be set for the component and used rendered
211 in tab bar
212 3. the icon to be set for the component and used rendered in
213 tab bar
214 4. the position at where the the tab should be added
215 - (c)
216 1. the component to be added onto tab - should not be null.
217 - (c, position)
218 1. the component to be added onto tab - should not be null.
219 2. The position where the tab should be added
220 - (c, caption)
221 1. the component to be added onto tab - should not be null.
222 2. the caption to be set for the component and used rendered
223 in tab bar
224
225 @return: the created L{ITab}
226 """
227 nargs = len(args)
228 if nargs == 1:
229 c, = args
230 return self.addTab(c, len(self._components))
231 elif nargs == 2:
232 if isinstance(args[1], basestring):
233 c, caption = args
234 self.addTab(c, caption, None)
235 else:
236 c, position = args
237 if c is None:
238 return None
239 elif c in self._tabs:
240 return self._tabs.get(c)
241 else:
242 return self.addTab(c, c.getCaption(), c.getIcon(), position)
243 elif nargs == 3:
244 c, caption, icon = args
245 return self.addTab(c, caption, icon, len(self._components))
246 elif nargs == 4:
247 c, caption, icon, position = args
248 if c is None:
249 return None
250 elif c in self._tabs:
251 tab = self._tabs.get(c)
252 tab.setCaption(caption)
253 tab.setIcon(icon)
254 return tab
255 else:
256 self._components.insert(position, c)
257 tab = TabSheetTabImpl(caption, icon, self)
258 self._tabs[c] = tab
259 if self._selected is None:
260 self._selected = c
261 self.fireSelectedTabChange()
262 super(TabSheet, self).addComponent(c)
263 self.requestRepaint()
264 return tab
265 else:
266 raise ValueError, 'invalid number of arguments'
267
268
270 """Moves all components from another container to this container. The
271 components are removed from the other container.
272
273 If the source container is a L{TabSheet}, component captions and
274 icons are copied from it.
275
276 @param source:
277 the container components are removed from.
278 """
279 i = source.getComponentIterator()
280 while True:
281 try:
282 c = i.next()
283 caption = None
284 icon = None
285 if issubclass(source.__class__, TabSheet):
286 caption = source.getTabCaption(c)
287 icon = source.getTabIcon(c)
288 source.removeComponent(c)
289 self.addTab(c, caption, icon)
290 except StopIteration:
291 break
292
293
294 - def paintContent(self, target):
295 """Paints the content of this component.
296
297 @param target:
298 the paint target
299 @raise PaintException:
300 if the paint operation failed.
301 """
302 if self.areTabsHidden():
303 target.addAttribute('hidetabs', True)
304
305 target.startTag('tabs')
306
307 orphaned = set(self._paintedTabs)
308
309 i = self.getComponentIterator()
310 while True:
311 try:
312 component = i.next()
313 if component in orphaned:
314 orphaned.remove(component)
315 tab = self._tabs.get(component)
316 target.startTag('tab')
317 if not tab.isEnabled() and tab.isVisible():
318 target.addAttribute('disabled', True)
319
320 if not tab.isVisible():
321 target.addAttribute('hidden', True)
322
323 if tab.isClosable():
324 target.addAttribute('closable', True)
325
326 icon = tab.getIcon()
327 if icon is not None:
328 target.addAttribute('icon', icon)
329
330 caption = tab.getCaption()
331 if caption is not None and len(caption) > 0:
332 target.addAttribute('caption', caption)
333
334 description = tab.getDescription()
335 if description is not None:
336 target.addAttribute('description', description)
337
338 componentError = tab.getComponentError()
339 if componentError is not None:
340 componentError.paint(target)
341
342 target.addAttribute('key', self._keyMapper.key(component))
343 if component == self._selected:
344 target.addAttribute('selected', True)
345 component.paint(target)
346 self._paintedTabs.add(component)
347
348 elif component in self._paintedTabs:
349 component.paint(target)
350 else:
351 component.requestRepaintRequests()
352 target.endTag('tab')
353 except StopIteration:
354 break
355
356 target.endTag('tabs')
357
358 if self._selected is not None:
359 target.addVariable(self, 'selected',
360 self._keyMapper.key(self._selected))
361
362
363 for component2 in orphaned:
364 self._paintedTabs.remove(component2)
365
366
368 """Are the tab selection parts ("tabs") hidden.
369
370 @return: true if the tabs are hidden in the UI
371 """
372 return self._tabsHidden
373
374
376 """Hides or shows the tab selection parts ("tabs").
377
378 @param tabsHidden:
379 true if the tabs should be hidden
380 """
381 self._tabsHidden = tabsHidden
382 self.requestRepaint()
383
384
386 """Gets tab caption. The tab is identified by the tab content
387 component.
388
389 @param c: the component in the tab
390 @deprecated: Use L{getTab} and L{ITab.getCaption} instead.
391 """
392 warn('use getTab() and ITab.getCaption() instead', DeprecationWarning)
393
394 info = self._tabs.get(c)
395 if info is None:
396 return ''
397 else:
398 return info.getCaption()
399
400
402 """Sets tab caption. The tab is identified by the tab content
403 component.
404
405 @param c: the component in the tab
406 @param caption: the caption to set.
407 @deprecated: Use L{getTab} and L{ITab.setCaption} instead.
408 """
409 warn('use getTab() and ITab.getCaption() instead', DeprecationWarning)
410
411 info = self._tabs.get(c)
412 if info is not None:
413 info.setCaption(caption)
414 self.requestRepaint()
415
416
418 """Gets the icon for a tab. The tab is identified by the tab content
419 component.
420
421 @param c: the component in the tab
422 @deprecated: Use L{getTab} and L{ITab.getIcon} instead.
423 """
424 warn('use getTab() and ITab.getIcon() instead', DeprecationWarning)
425
426 info = self._tabs.get(c)
427 if info is None:
428 return None
429 else:
430 return info.getIcon()
431
432
434 """Sets icon for the given component. The tab is identified by the
435 tab content component.
436
437 @param c:
438 the component in the tab
439 @param icon:
440 the icon to set
441 @deprecated: Use L{getTab} and L{ITab.setIcon} instead.
442 """
443 warn('use getTab() and ITab.getIcon() instead', DeprecationWarning)
444
445 info = self._tabs.get(c)
446 if info is not None:
447 info.setIcon(icon)
448 self.requestRepaint()
449
450
452 """Returns the L{ITab} (metadata) for a component. The
453 L{ITab} object can be used for setting caption,icon, etc
454 for the tab.
455
456 @param arg:
457 the component or the position of the tab
458 """
459 if isinstance(arg, IComponent):
460 c = arg
461 return self._tabs.get(c)
462 else:
463 position = arg
464 c = self._components[position]
465 if c is not None:
466 return self._tabs.get(c)
467 return None
468
469
471 """Sets the selected tab. The tab is identified by the tab content
472 component.
473 """
474 if (c is not None and c in self._components
475 and c != self._selected):
476 self._selected = c
477 self.updateSelection()
478 self.fireSelectedTabChange()
479 self.requestRepaint()
480
481
483 """Checks if the current selection is valid, and updates the selection
484 if the previously selected component is not visible and enabled. The
485 first visible and enabled tab is selected if the current selection is
486 empty or invalid.
487
488 This method does not fire tab change events, but the caller should do
489 so if appropriate.
490
491 @return: true if selection was changed, false otherwise
492 """
493 originalSelection = self._selected
494
495 i = self.getComponentIterator()
496 while True:
497 try:
498 component = i.next()
499 tab = self._tabs.get(component)
500
501
502
503 selectedTabInfo = None
504 if self._selected is not None:
505 selectedTabInfo = self._tabs.get(self._selected)
506
507 if (self._selected is None
508 or selectedTabInfo is None
509 or not selectedTabInfo.isVisible()
510 or not selectedTabInfo.isEnabled()):
511
512
513 if tab.isEnabled() and tab.isVisible():
514 self._selected = component
515 break
516 else:
517
518
519 self._selected = None
520 except StopIteration:
521 break
522
523 return originalSelection != self._selected
524
525
527 """Gets the selected tab content component.
528
529 @return: the selected tab contents
530 """
531 return self._selected
532
533
535 if 'selected' in variables:
536 select = self._keyMapper.get(variables.get('selected'))
537 self.setSelectedTab(select)
538
539 if 'close' in variables:
540 tab = self._keyMapper.get(variables.get('close'))
541
542 if tab is not None:
543 self._closeHandler.onTabClose(self, tab)
544
545
547 """Replaces a component (tab content) with another. This can be used
548 to change tab contents or to rearrange tabs. The tab position and some
549 metadata are preserved when moving components within the same
550 L{TabSheet}.
551
552 If the oldComponent is not present in the tab sheet, the new one is
553 added at the end.
554
555 If the oldComponent is already in the tab sheet but the newComponent
556 isn't, the old tab is replaced with a new one, and the caption and
557 icon of the old one are copied to the new tab.
558
559 If both old and new components are present, their positions are
560 swapped.
561 """
562 if self._selected == oldComponent:
563
564 self._selected = newComponent
565
566 newTab = self._tabs.get(newComponent)
567 oldTab = self._tabs.get(oldComponent)
568
569
570 oldCaption = None
571 oldIcon = None
572 newCaption = None
573 newIcon = None
574
575 if oldTab is not None:
576 oldCaption = oldTab.getCaption()
577 oldIcon = oldTab.getIcon()
578
579 if newTab is not None:
580 newCaption = newTab.getCaption()
581 newIcon = newTab.getIcon()
582 else:
583 newCaption = newComponent.getCaption()
584 newIcon = newComponent.getIcon()
585
586
587 oldLocation = -1
588 newLocation = -1
589 location = 0
590 for component in self._components:
591 if component == oldComponent:
592 oldLocation = location
593 if component == newComponent:
594 newLocation = location
595 location += 1
596
597 if oldLocation == -1:
598 self.addComponent(newComponent)
599 elif newLocation == -1:
600 self.removeComponent(oldComponent)
601 self._keyMapper.remove(oldComponent)
602 newTab = self.addTab(newComponent)
603 self._components.remove(newComponent)
604 self._components.append(oldLocation, newComponent)
605 newTab.setCaption(oldCaption)
606 newTab.setIcon(oldIcon)
607 else:
608 if oldLocation > newLocation:
609 self._components.remove(oldComponent)
610 self._components.append(newLocation, oldComponent)
611 self._components.remove(newComponent)
612 self._components.append(oldLocation, newComponent)
613 else:
614 self._components.remove(newComponent)
615 self._components.append(oldLocation, newComponent)
616 self._components.remove(oldComponent)
617 self._components.append(newLocation, oldComponent)
618
619 if newTab is not None:
620
621 newTab.setCaption(oldCaption)
622 newTab.setIcon(oldIcon)
623
624 if oldTab is not None:
625
626 oldTab.setCaption(newCaption)
627 oldTab.setIcon(newIcon)
628
629 self.requestRepaint()
630
631
645
646
647 - def addCallback(self, callback, eventType=None, *args):
656
657
659 """Removes a tab selection listener
660
661 @param listener:
662 the Listener to be removed.
663 """
664 if (isinstance(listener, IRepaintRequestListener) and
665 (iface is None or issubclass(iface, IRepaintRequestListener))):
666 super(TabSheet, self).removeListener(listener, iface)
667 if isinstance(listener, CommunicationManager):
668
669
670 self._paintedTabs.clear()
671
672 if (isinstance(listener, ISelectedTabChangeListener) and
673 (iface is None or
674 issubclass(iface, ISelectedTabChangeListener))):
675 self.withdrawListener(SelectedTabChangeEvent,
676 listener, _SELECTED_TAB_CHANGE_METHOD)
677
678 super(TabSheet, self).removeListener(listener, iface)
679
680
689
690
695
696
698 """Provide a custom L{ICloseHandler} for this TabSheet if you
699 wish to perform some additional tasks when a user clicks on a tabs
700 close button, e.g. show a confirmation dialogue before removing the
701 tab.
702
703 To remove the tab, if you provide your own close handler, you must
704 call L{removeComponent} yourself.
705
706 The default ICloseHandler for TabSheet will only remove the tab.
707 """
708 self._closeHandler = handler
709
710
723
724
726 """Gets the position of the tab
727
728 @param tab: The tab
729 """
730 try:
731 return self._components.index( tab.getComponent() )
732 except ValueError:
733 return -1
734
735
737 """Selected tab change event. This event is sent when the selected
738 (shown) tab in the tab sheet is changed.
739
740 @author: Vaadin Ltd.
741 @author: Richard Lincoln
742 @version: 1.1.2
743 """
744
746 """New instance of selected tab change event
747
748 @param source:
749 the Source of the event.
750 """
751 super(SelectedTabChangeEvent, self).__init__(source)
752
753
755 """TabSheet where the event occurred.
756
757 @return: the Source of the event.
758 """
759 return self.getSource()
760
761
763 """ITab meta-data for a component in a L{TabSheet}.
764
765 The meta-data includes the tab caption, icon, visibility and enabledness,
766 closability, description (tooltip) and an optional component error shown
767 in the tab.
768
769 Tabs are identified by the component contained on them in most cases, and
770 the meta-data can be obtained with L{TabSheet.getTab}.
771 """
772
774 """Returns the visible status for the tab. An invisible tab is not
775 shown in the tab bar and cannot be selected.
776
777 @return: true for visible, false for hidden
778 """
779 raise NotImplementedError
780
781
783 """Sets the visible status for the tab. An invisible tab is not shown
784 in the tab bar and cannot be selected, selection is changed
785 automatically when there is an attempt to select an invisible tab.
786
787 @param visible:
788 true for visible, false for hidden
789 """
790 raise NotImplementedError
791
792
794 """Returns the closability status for the tab.
795
796 @return: true if the tab is allowed to be closed by the end user,
797 false for not allowing closing
798 """
799 raise NotImplementedError
800
801
803 """Sets the closability status for the tab. A closable tab can be
804 closed by the user through the user interface. This also controls
805 if a close button is shown to the user or not.
806
807 Note! Currently only supported by TabSheet, not Accordion.
808
809 @param closable:
810 true if the end user is allowed to close the tab, false
811 for not allowing to close. Should default to false.
812 """
813 raise NotImplementedError
814
815
817 """Returns the enabled status for the tab. A disabled tab is shown as
818 such in the tab bar and cannot be selected.
819
820 @return: true for enabled, false for disabled
821 """
822 raise NotImplementedError
823
824
826 """Sets the enabled status for the tab. A disabled tab is shown as
827 such in the tab bar and cannot be selected.
828
829 @param enabled:
830 true for enabled, false for disabled
831 """
832 raise NotImplementedError
833
834
836 """Sets the caption for the tab.
837
838 @param caption:
839 the caption to set
840 """
841 raise NotImplementedError
842
843
845 """Gets the caption for the tab."""
846 raise NotImplementedError
847
848
850 """Gets the icon for the tab."""
851 raise NotImplementedError
852
853
855 """Sets the icon for the tab.
856
857 @param icon:
858 the icon to set
859 """
860 raise NotImplementedError
861
862
864 """Gets the description for the tab. The description can be used to
865 briefly describe the state of the tab to the user, and is typically
866 shown as a tooltip when hovering over the tab.
867
868 @return: the description for the tab
869 """
870 raise NotImplementedError
871
872
874 """Sets the description for the tab. The description can be used to
875 briefly describe the state of the tab to the user, and is typically
876 shown as a tooltip when hovering over the tab.
877
878 @param description:
879 the new description string for the tab.
880 """
881 raise NotImplementedError
882
883
885 """Sets an error indicator to be shown in the tab. This can be used
886 e.g. to communicate to the user that there is a problem in the
887 contents of the tab.
888
889 @see: L{AbstractComponent.setComponentError}
890
891 @param componentError:
892 error message or null for none
893 """
894 raise NotImplementedError
895
896
898 """Gets the curent error message shown for the tab.
899
900 @see: L{AbstractComponent.setComponentError}
901
902 @return: message or null if none
903 """
904 raise NotImplementedError
905
906
908 """Get the component related to the ITab"""
909 raise NotImplementedError
910
911
913 """TabSheet's implementation of L{ITab} - tab metadata."""
914
915 - def __init__(self, caption, icon, sheet):
916 self._sheet = sheet
917
918 self._enabled = True
919 self._visible = True
920 self._closable = False
921 self._description = None
922 self._componentError = None
923
924 if caption is None:
925 caption = ''
926 self._caption = caption
927 self._icon = icon
928
929
931 """Returns the tab caption. Can never be null."""
932 return self._caption
933
934
938
939
942
943
947
948
951
952
958
959
962
963
969
970
972 return self._closable
973
974
978
979
982
983
985 return self._description
986
987
991
992
994 return self._componentError
995
996
1000
1001
1003 for k, v in self._sheet._tabs.iteritems():
1004 if v == self:
1005 return k
1006 return None
1007
1008
1010 """ICloseHandler is used to process tab closing events. Default behavior
1011 is to remove the tab from the TabSheet.
1012
1013 @author: Jouni Koivuviita / Vaadin Ltd.
1014 @author: Richard Lincoln
1015 """
1016
1018 """Called when a user has pressed the close icon of a tab in the
1019 client side widget.
1020
1021 @param tabsheet:
1022 the TabSheet to which the tab belongs to
1023 @param tabContent:
1024 the component that corresponds to the tab whose close
1025 button was clicked
1026 """
1027 raise NotImplementedError
1028
1029
1034