1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Defines a horizontal menu bar."""
17
18 from warnings import warn
19
20 from collections import deque
21
22 from muntjac.ui.abstract_component import AbstractComponent
23
24 from muntjac.terminal.gwt.client.ui.v_menu_bar import VMenuBar
25
26
28 """A class representing a horizontal menu bar. The menu can contain
29 MenuItem objects, which in turn can contain more MenuBars. These sub-level
30 MenuBars are represented as vertical menu.
31 """
32
33 CLIENT_WIDGET = None
34
36 """Constructs an empty, horizontal menu"""
37 super(MenuBar, self).__init__()
38
39
40 self._menuItems = list()
41
42
43 self._numberOfItems = 0
44
45 self._collapseItems = None
46 self._submenuIcon = None
47 self._moreItem = None
48 self._openRootOnHover = False
49 self._htmlContentAllowed = False
50
51 self.setCollapse(True)
52 self.setMoreMenuItem(None)
53
54
56 """Paint (serialise) the component for the client."""
57
58
59 super(MenuBar, self).paintContent(target)
60
61 target.addAttribute(VMenuBar.OPEN_ROOT_MENU_ON_HOWER,
62 self._openRootOnHover)
63
64 if self.isHtmlContentAllowed():
65 target.addAttribute(VMenuBar.HTML_CONTENT_ALLOWED, True)
66
67 target.startTag('options')
68
69 if self._submenuIcon is not None:
70 target.addAttribute('submenuIcon', self._submenuIcon)
71
72 if self.getWidth() > -1:
73 target.startTag('moreItem')
74 target.addAttribute('text', self._moreItem.getText())
75 if self._moreItem.getIcon() is not None:
76 target.addAttribute('icon', self._moreItem.getIcon())
77 target.endTag('moreItem')
78
79 target.endTag('options')
80 target.startTag('items')
81
82
83 for item in self._menuItems:
84 self.paintItem(target, item)
85
86 target.endTag('items')
87
88
90 if not item.isVisible():
91 return
92
93 target.startTag('item')
94
95 target.addAttribute('id', item.getId())
96
97 if item.getStyleName() is not None:
98 target.addAttribute('style', item.getStyleName())
99
100 if item.isSeparator():
101 target.addAttribute('separator', True)
102 else:
103 target.addAttribute('text', item.getText())
104
105 command = item.getCommand()
106 if command is not None:
107 target.addAttribute('command', True)
108
109 icon = item.getIcon()
110 if icon is not None:
111 target.addAttribute('icon', icon)
112
113 if not item.isEnabled():
114 target.addAttribute('disabled', True)
115
116 description = item.getDescription()
117 if description is not None and len(description) > 0:
118 target.addAttribute('description', description)
119
120 if item.isCheckable():
121
122
123 target.addAttribute(VMenuBar.ATTRIBUTE_CHECKED,
124 item.isChecked())
125
126 if item.hasChildren():
127 for child in item.getChildren():
128 self.paintItem(target, child)
129
130 target.endTag('item')
131
132
134 """Deserialize changes received from client."""
135 items = deque()
136 found = False
137
138 if 'clickedId' in variables:
139 clickedId = variables.get('clickedId')
140 for itm in self.getItems():
141 items.append(itm)
142
143 tmpItem = None
144
145
146 while not found and len(items) > 0:
147 tmpItem = items.pop()
148 found = clickedId == tmpItem.getId()
149
150 if tmpItem.hasChildren():
151 for c in tmpItem.getChildren():
152 items.append(c)
153
154
155 if found and tmpItem.isEnabled():
156 if tmpItem.isCheckable():
157 tmpItem.setChecked(not tmpItem.isChecked())
158
159 if None is not tmpItem.getCommand():
160 tmpItem.getCommand().menuSelected(tmpItem)
161
162
164 """Add a new item to the menu bar. Icon and command can be null, but a
165 caption must be given.
166
167 @param args: tuple of the form
168 - (caption, command)
169 1. the text for the menu item
170 2. the command for the menu item
171 - (caption, icon, command)
172 1. the text for the menu item
173 2. the icon for the menu item
174 3. the command for the menu item
175 @raise ValueError:
176 """
177 nargs = len(args)
178 if nargs == 2:
179 caption, command = args
180 return self.addItem(caption, None, command)
181 elif nargs == 3:
182 caption, icon, command = args
183 if caption is None:
184 raise ValueError, 'caption cannot be null'
185 newItem = MenuItem(caption, icon, command, self)
186 self._menuItems.append(newItem)
187 self.requestRepaint()
188 return newItem
189 else:
190 raise ValueError, 'invalid number of arguments'
191
192
194 """Add an item before some item. If the given item does not exist the
195 item is added at the end of the menu. Icon and command can be null,
196 but a caption must be given.
197
198 @param caption:
199 the text for the menu item
200 @param icon:
201 the icon for the menu item
202 @param command:
203 the command for the menu item
204 @param itemToAddBefore:
205 the item that will be after the new item
206 @raise ValueError:
207 """
208 if caption is None:
209 raise ValueError, 'caption cannot be null'
210
211 newItem = MenuItem(caption, icon, command, self)
212
213 if itemToAddBefore in self._menuItems:
214 try:
215 index = self._menuItems.index(itemToAddBefore)
216 except ValueError:
217 index = -1
218 self._menuItems.insert(index, newItem)
219 else:
220 self._menuItems.append(newItem)
221
222 self.requestRepaint()
223
224 return newItem
225
226
228 """Returns a list with all the MenuItem objects in the menu bar
229
230 @return: a list containing the MenuItem objects in the menu bar
231 """
232 return self._menuItems
233
234
236 """Remove first occurrence the specified item from the main menu
237
238 @param item:
239 The item to be removed
240 """
241 if item is not None:
242 self._menuItems.remove(item)
243 self.requestRepaint()
244
245
250
251
253 """Returns the size of the menu.
254
255 @return: The size of the menu
256 """
257 return len(self._menuItems)
258
259
261 """Set the icon to be used if a sub-menu has children. Defaults to
262 null;
263
264 @deprecated: Icon is set in theme, no need to worry about the visual
265 representation here.
266 """
267 warn('icon is set in theme', DeprecationWarning)
268 self._submenuIcon = icon
269 self.requestRepaint()
270
271
273 """@see: L{setSubmenuIcon}
274 """
275 warn('icon is set in theme', DeprecationWarning)
276 return self._submenuIcon
277
278
280 """Enable or disable collapsing top-level items. Top-level items will
281 collapse together if there is not enough room for them. Items that
282 don't fit will be placed under the "More" menu item.
283
284 Collapsing is enabled by default.
285
286 @deprecated: Collapsing is always enabled if the MenuBar has a
287 specified width.
288 """
289 self._collapseItems = collapse
290 self.requestRepaint()
291
292
294 """@see: #setCollapse(boolean)
295 @deprecated
296 """
297 warn('deprecated', DeprecationWarning)
298 return self._collapseItems
299
300
302 """Set the item that is used when collapsing the top level menu. All
303 "overflowing" items will be added below this. The item command will
304 be ignored. If set to null, the default item with a downwards arrow
305 is used.
306
307 The item command (if specified) is ignored.
308 """
309 if item is not None:
310 self._moreItem = item
311 else:
312 self._moreItem = MenuItem('', None, None, self)
313 self.requestRepaint()
314
315
317 """Get the MenuItem used as the collapse menu item.
318 """
319 return self._moreItem
320
321
323 """Using this method menubar can be put into a special mode where top
324 level menus opens without clicking on the menu, but automatically when
325 mouse cursor is moved over the menu. In this mode the menu also closes
326 itself if the mouse is moved out of the opened menu.
327
328 Note, that on touch devices the menu still opens on a click event.
329
330 @param autoOpenTopLevelMenu:
331 true if menus should be opened without click, the default
332 is false
333 """
334 if autoOpenTopLevelMenu != self._openRootOnHover:
335 self._openRootOnHover = autoOpenTopLevelMenu
336 self.requestRepaint()
337
338
340 """Detects whether the menubar is in a mode where top level menus are
341 automatically opened when the mouse cursor is moved over the menu.
342 Normally root menu opens only by clicking on the menu. Submenus always
343 open automatically.
344
345 @return: true if the root menus open without click, the default
346 is false
347 """
348 return self._openRootOnHover
349
350
352 """Sets whether html is allowed in the item captions. If set to true,
353 the captions are passed to the browser as html and the developer is
354 responsible for ensuring no harmful html is used. If set to false, the
355 content is passed to the browser as plain text.
356
357 @param htmlContentAllowed:
358 true if the captions are used as html, false if used as plain
359 text
360 """
361 self._htmlContentAllowed = htmlContentAllowed
362 self.requestRepaint()
363
364
366 """Checks whether item captions are interpreted as html or plain text.
367
368 @return: true if the captions are used as html, false if used as plain
369 text
370 @see: L{setHtmlContentAllowed}
371 """
372 return self._htmlContentAllowed
373
374
376 """This interface contains the layer for menu commands of the L{MenuBar}
377 class. It's method will fire when the user clicks on the containing
378 L{MenuItem}. The selected item is given as an argument.
379 """
380
382 raise NotImplementedError
383
384
386 """A composite class for menu items and sub-menus. You can set commands
387 to be fired on user click by implementing the L{menu_bar.ICommand}
388 interface. You can also add multiple MenuItems to a MenuItem and create
389 a sub-menu.
390 """
391
393 """Constructs a new menu item that can optionally have an icon and a
394 command associated with it. Icon and command can be null, but a
395 caption must be given.
396
397 @param caption:
398 The text associated with the command
399 @param command:
400 The command to be fired
401 @raise ValueError:
402 """
403 self._menu = menu
404
405 self._itsId = None
406 self._itsCommand = None
407 self._itsText = None
408 self._itsChildren = None
409 self._itsIcon = None
410 self._itsParent = None
411 self._enabled = True
412 self._visible = True
413 self._isSeparator = False
414 self._styleName = None
415 self._description = None
416 self._checkable = False
417 self._checked = False
418
419 if caption is None:
420 raise ValueError, 'caption cannot be null'
421
422 menu._numberOfItems = menu._numberOfItems + 1
423 self._itsId = menu._numberOfItems
424 self._itsText = caption
425 self._itsIcon = icon
426 self._itsCommand = command
427
428
430 """Checks if the item has children (if it is a sub-menu).
431
432 @return: True if this item has children
433 """
434 return not self.isSeparator() and self._itsChildren is not None
435
436
438 """Adds a separator to this menu. A separator is a way to visually
439 group items in a menu, to make it easier for users to find what they
440 are looking for in the menu.
441
442 @author: Jouni Koivuviita / Vaadin Ltd.
443 """
444 item = self.addItem('', None, None)
445 item.setSeparator(True)
446 return item
447
448
453
454
456 """Add a new item inside this item, thus creating a sub-menu. Icon and
457 command can be null, but a caption must be given.
458
459 @param args: tuple of the form
460 - (caption, command)
461 1. the text for the menu item
462 2. the command for the menu item
463 - (caption, icon, command)
464 1. the text for the menu item
465 2. the icon for the menu item
466 3. the command for the menu item
467 @raise ValueError:
468 If the item is checkable and thus cannot have children.
469 """
470 nargs = len(args)
471 if nargs == 2:
472 caption, command = args
473 return self.addItem(caption, None, command)
474 elif nargs == 3:
475 caption, icon, command = args
476 if self.isSeparator():
477 raise NotImplementedError, 'Cannot add items to a separator'
478 if self.isCheckable():
479 raise ValueError, 'A checkable item cannot have children'
480 if caption is None:
481 raise ValueError, 'Caption cannot be null'
482 if self._itsChildren is None:
483 self._itsChildren = list()
484 newItem = MenuItem(caption, icon, command, self._menu)
485
486 newItem.setParent(self)
487 self._itsChildren.append(newItem)
488 self._menu.requestRepaint()
489 return newItem
490 else:
491 raise ValueError, 'invalid number of arguments'
492
493
495 """Add an item before some item. If the given item does not exist the
496 item is added at the end of the menu. Icon and command can be null,
497 but a caption must be given.
498
499 @param caption:
500 the text for the menu item
501 @param icon:
502 the icon for the menu item
503 @param command:
504 the command for the menu item
505 @param itemToAddBefore:
506 the item that will be after the new item
507 @raise ValueError:
508 If the item is checkable and thus cannot have children.
509 """
510 if self.isCheckable():
511 raise ValueError, 'A checkable item cannot have children'
512
513 newItem = None
514
515 if self.hasChildren() and itemToAddBefore in self._itsChildren:
516 try:
517 index = self._itsChildren.index(itemToAddBefore)
518 except ValueError:
519 index = -1
520 newItem = MenuItem(caption, icon, command, self._menu)
521 newItem.setParent(self)
522 self._itsChildren.append(index, newItem)
523 else:
524 newItem = self.addItem(caption, icon, command)
525 self._menu.requestRepaint()
526 return newItem
527
528
530 """For the associated command.
531
532 @return: The associated command, or null if there is none
533 """
534 return self._itsCommand
535
536
538 """Gets the objects icon.
539
540 @return: The icon of the item, null if the item doesn't have an icon
541 """
542 return self._itsIcon
543
544
546 """For the containing item. This will return null if the item is in
547 the top-level menu bar.
548
549 @return: The containing L{menu_bar.MenuItem} , or
550 null if there is none
551 """
552 return self._itsParent
553
554
556 """This will return the children of this item or null if there are
557 none.
558
559 @return: List of children items, or null if there are none
560 """
561 return self._itsChildren
562
563
565 """Gets the objects text
566
567 @return: The text
568 """
569 return self._itsText
570
571
573 """Returns the number of children.
574
575 @return: The number of child items
576 """
577 if self._itsChildren is not None:
578 return len(self._itsChildren)
579 return -1
580
581
583 """Get the unique identifier for this item.
584
585 @return: The id of this item
586 """
587 return self._itsId
588
589
591 """Set the command for this item. Set null to remove.
592
593 @param command:
594 The MenuCommand of this item
595 """
596 self._itsCommand = command
597
598
600 """Sets the icon. Set null to remove.
601
602 @param icon:
603 The icon for this item
604 """
605 self._itsIcon = icon
606 self._menu.requestRepaint()
607
608
610 """Set the text of this object.
611
612 @param text:
613 Text for this object
614 """
615 if text is not None:
616 self._itsText = text
617 self._menu.requestRepaint()
618
619
621 """Remove the first occurrence of the item.
622
623 @param item:
624 The item to be removed
625 """
626 if item is not None and self._itsChildren is not None:
627 self._itsChildren.remove(item)
628 if len(self._itsChildren) == 0:
629 self._itsChildren = None
630 self._menu.requestRepaint()
631
632
634 """Empty the list of children items."""
635 if self._itsChildren is not None:
636 del self._itsChildren[:]
637 self._itsChildren = None
638 self._menu.requestRepaint()
639
640
642 """Set the parent of this item. This is called by the addItem method.
643
644 @param parent:
645 The parent item
646 """
647 self._itsParent = parent
648
649
653
654
657
658
662
663
666
667
671
672
674 return self._isSeparator
675
676
680
681
683 return self._styleName
684
685
687 """Sets the items's description. See L{getDescription} for
688 more information on what the description is. This method will trigger a
689 L{RepaintRequestEvent<muntjac.terminal.paintable.RepaintRequestEvent>}.
690
691 @param description:
692 the new description string for the component.
693 """
694 self._description = description
695 self._menu.requestRepaint()
696
697
699 """Gets the items's description. The description can be used to
700 briefly describe the state of the item to the user. The description
701 string may contain certain XML tags:
702
703 <table border=1>
704 <tr>
705 <td width=120><b>Tag</b></td>
706 <td width=120><b>Description</b></td>
707 <td width=120><b>Example</b></td>
708 </tr>
709 <tr>
710 <td><b></td>
711 <td>bold</td>
712 <td><b>bold text</b></td>
713 </tr>
714 <tr>
715 <td><i></td>
716 <td>italic</td>
717 <td><i>italic text</i></td>
718 </tr>
719 <tr>
720 <td><u></td>
721 <td>underlined</td>
722 <td><u>underlined text</u></td>
723 </tr>
724 <tr>
725 <td><br></td>
726 <td>linebreak</td>
727 <td>N/A</td>
728 </tr>
729 <tr>
730 <td><ul><br>
731 <li>item1<br>
732 <li>item1<br>
733 </ul></td>
734 <td>item list</td>
735 <td>
736 <ul>
737 <li>item1
738 <li>item2
739 </ul>
740 </td>
741 </tr>
742 </table>
743
744 These tags may be nested.
745
746 @return: item's description string
747 """
748 return self._description
749
750
752 """Gets the checkable state of the item - whether the item has checked
753 and unchecked states. If an item is checkable its checked state (as
754 returned by L{isChecked}) is indicated in the UI.
755
756 An item is not checkable by default.
757
758 @return: true if the item is checkable, false otherwise
759 """
760 return self._checkable
761
762
764 """Sets the checkable state of the item. If an item is checkable its
765 checked state (as returned by L{isChecked}) is indicated in the UI.
766
767 An item is not checkable by default.
768
769 Items with sub items cannot be checkable.
770
771 @param checkable:
772 true if the item should be checkable, false otherwise
773 @raise ValueError:
774 If the item has children
775 """
776 if self.hasChildren():
777 raise ValueError, 'A menu item with children cannot be checkable'
778 self._checkable = checkable
779 self._menu.requestRepaint()
780
781
783 """Gets the checked state of the item (checked or unchecked). Only used
784 if the item is checkable (as indicated by L{isCheckable}).
785 The checked state is indicated in the UI with the item, if the item
786 is checkable.
787
788 An item is not checked by default.
789
790 The CSS style corresponding to the checked state is "-checked".
791
792 @return: true if the item is checked, false otherwise
793 """
794 return self._checked
795
796
798 """Sets the checked state of the item. Only used if the item is
799 checkable (indicated by L{isCheckable}). The checked state is
800 indicated in the UI with the item, if the item is checkable.
801
802 An item is not checked by default.
803
804 The CSS style corresponding to the checked state is "-checked".
805
806 @return: true if the item is checked, false otherwise
807 """
808 self._checked = checked
809 self._menu.requestRepaint()
810