
================
Menus management
================

:Author: Philippe Normand
:Contact: philippe -at- fluendo [dot] com
:Revision: $Revision: 620 $
:Date: $Date: 2006-08-22 10:14:53 +0200 (mar, 22 aoû 2006) $

Design
======

Elisa uses a tree-based structure to organize data. It then depends on
the skin how the tree is displayed. The application holds a
`core.menu.MenuTree` instance which is the root of the tree. Each item
of the tree (either nodes or leaves) is a `core.menu.MenuItem` instance.

MenuItems have a set of worth-looking-at attributes:

- the parent (either the MenuTree or another MenuItem)
- a short name
- an optional help string
- an icon, basically a picture filename located in skin pictures directory
- a target path, the place where the menu item points to
- a set of callbacks mainly meant to be fired by the skin:

  * 'action' : usually called when the user activates the menu item
  * 'loadmore' : internally called by DataAccess plugins when they
    require to parse children of the menu item

MenuItems are generated by DataAccessPlugins on the request of other
plugins, mainly the TreePlugins.

The `core.menu.MenuTree` and `core.menu.MenuItem` classes are used to
represent the hierarchical data model of the menus, which are then
displayed by the skin through the skin-developer-friendly
MenuItemManager. This manager does:

- map MenuItems to actual widgets
- support menu level widgets
- support menus navigation history via an adjustable widget cache

Usage API
=========

Menu navigation is provided by a simple API summarized in 4 methods:

- `go_up_level(record=True)`: navigate to up level and record path to history
- `go_down_level(recorded=True)`: navigate to the down level and
  optionally go to the last recorded item
- `go_next_item()`: increment the index of the active menu item
- `go_previous_item()`: decrement the index of the active menu item

The above four methods emit the 'active-changed' signal which is
explained in the `Available signals`_ section. Current menu item
selected can be retrieved using the method get_active_item(). The tree
can be updated using the following methods:

- `add_item(menu_item)`: a new menu item under parent's level and
  emit 'item-added' signal
- `insert_item(menu_item, index)`: insert a new menu item at given
  index under parent's level and emit 'item-added' signal
- `remove_item(widget)`: emit 'item-removed' signal, remove the
  widget, associated menu item from its level
- `add_level(menu_item)`: add new menu level for the given menu item
  and emit 'level-added' signal
- `remove_level(widget)`: emit 'level-removed' signal and remove the
  widget associated to the given menu level
- `bind_item_to_widget(menu_item, widget)`: bind a menu item with an
  actual widget
- `bind_level_to_widget(menu_item, widget)`: bind a level residing
  under the menu item to an actual widget

The above methods emit the signals
'{item,level}-{added,removed,bound,unbound}'. Widget creation process
can be a long task in the context of UI rendering and reactiveness, so
the MenuItemManager embeds a widget cache. It can be customized by
using the following methods:

- `get_widget_cache_size()`: retrieve current cache size as integer
- `set_widget_cache_size(new_size)`: update cache size
- `expire_cache()`: force the cache to purge itself

So if the developer really doesn't want to use the cache, he can set
its size to zero. The manager is able to keep the cache always at the
same size, although the developer can manually force it to expire,
thus purge it.

The MenuItemManager finally provides the method
`activate_current_item()` to be used when the developer has to fire
the action callback on the currently selected menu item.


Available signals
=================

The MenuItemManager can emit some signals the skin can hook into:

- `active-changed`: data=(menu_item: MenuItem, widget: Widget,
                          direction: string, step: int) Emitted when the
                          active selected menu item changes
- `item-added`: data=(menu_item: MenuItem, index: int)
  Emitted when a new menu_item has been added through the manager
- `item-removed`: data=(menu_item: MenuItem, index: int)
  Emitted when a menu_item has been removed through the manager
- `item-activated`: data=(menu_item: MenuItem, widget: Widget)
  Emitted when the 'action' callback of active item has been fired
- `level-added`: data=(menu_item: MenuItem)
  Emitted when a new menu level has been added through the manager
- `level-removed`: data=(menu_item: MenuItem)
  Emitted when a menu level has been removed through the manager
- `item-bound`: data=(menu_item: MenuItem, widget: Widget)
- `item-unbound`: data=(menu_item: MenuItem, widget: Widget)
- `level-bound`: data=(menu_item: MenuItem, widget: Widget)
- `level-unbound`: data=(menu_item: MenuItem, widget: Widget)


Example
=======

The following code snippet shows how to use some of the
MenuItemManager features from within the skin:

.. code-block:: python

  class MySkin(BaseSkin):

      # ...

      def prepare(self):
	  BaseSkin.prepare(self)
	  self.get_menu_manager().connect('item-activated',
					  self._item_activated)

      def _item_activated(self, menu_item, widget):
	  print 'Performing action on %s' % menu_item.get_target_path()
	  # do fancy stuff if renderer supports it...
	  # you can also launch widget animations from here
	  widget.rotate_z(20)

      def build_menu_item(self, image, caption, state, in_root_level):
	  """
	  This code may not actually work, but you get the philosophy :)
	  """
	  # the VBox containing image+label
	  vbx = VBox()

	  # pack the image
	  if in_root_level:
	      image.set_size((128,128))
	  else:
	      image.set_size((96,96))
	  vbx.pack(image)

	  # pack the label
	  lbl = Label(text=caption)
	  vbx.pack(lbl)

	  vbx.set_state(state)
	  return vbx

      def build_menu_level(self):
          return HBox()

      def keypress_cb(self, event, data):

	  # handle basic events
	  BaseSkin.keypress_cb(self, event, data)

	  # do some custom actions...
	  if event.value == key.ENTER:
	      self.get_menu_manager().activate_current_item()
