aboutsummaryrefslogtreecommitdiffstats
path: root/mvc/widgets/gtk/gtkmenus.py
diff options
context:
space:
mode:
Diffstat (limited to 'mvc/widgets/gtk/gtkmenus.py')
-rw-r--r--mvc/widgets/gtk/gtkmenus.py404
1 files changed, 404 insertions, 0 deletions
diff --git a/mvc/widgets/gtk/gtkmenus.py b/mvc/widgets/gtk/gtkmenus.py
new file mode 100644
index 0000000..926ba15
--- /dev/null
+++ b/mvc/widgets/gtk/gtkmenus.py
@@ -0,0 +1,404 @@
+# @Base: Miro - an RSS based video player application
+# Copyright (C) 2011
+# Participatory Culture Foundation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# In addition, as a special exception, the copyright holders give
+# permission to link the code of portions of this program with the OpenSSL
+# library.
+#
+# You must obey the GNU General Public License in all respects for all of
+# the code used other than OpenSSL. If you modify file(s) with this
+# exception, you may extend this exception to your version of the file(s),
+# but you are not obligated to do so. If you do not wish to do so, delete
+# this exception statement from your version. If you delete this exception
+# statement from all source files in the program, then also delete it here.
+
+"""gtkmenus.py -- Manage menu layout."""
+
+import gtk
+
+from mvc.widgets import app
+
+import base
+import keymap
+import wrappermap
+
+def _setup_accel(widget, name, shortcut=None):
+ """Setup accelerators for a menu item.
+
+ This method sets an accel path for the widget and optionally connects a
+ shortcut to that accel path.
+ """
+ # The GTK docs say that we should set the path using this form:
+ # <Window-Name>/Menu/Submenu/MenuItem
+ # ...but this is hard to do because we don't yet know what window/menu
+ # this menu item is going to be added to. gtk.Action and gtk.ActionGroup
+ # don't follow the above suggestion, so we don't need to either.
+ path = "<MiroActions>/MenuBar/%s" % name
+ widget.set_accel_path(path)
+ if shortcut is not None:
+ accel_string = keymap.get_accel_string(shortcut)
+ key, mods = gtk.accelerator_parse(accel_string)
+ if gtk.accel_map_lookup_entry(path) is None:
+ gtk.accel_map_add_entry(path, key, mods)
+ else:
+ gtk.accel_map_change_entry(path, key, mods, True)
+
+# map menu names to GTK stock ids.
+_STOCK_IDS = {
+ "SaveItem": gtk.STOCK_SAVE,
+ "CopyItemURL": gtk.STOCK_COPY,
+ "RemoveItems": gtk.STOCK_REMOVE,
+ "StopItem": gtk.STOCK_MEDIA_STOP,
+ "NextItem": gtk.STOCK_MEDIA_NEXT,
+ "PreviousItem": gtk.STOCK_MEDIA_PREVIOUS,
+ "PlayPauseItem": gtk.STOCK_MEDIA_PLAY,
+ "Open": gtk.STOCK_OPEN,
+ "EditPreferences": gtk.STOCK_PREFERENCES,
+ "Quit": gtk.STOCK_QUIT,
+ "Help": gtk.STOCK_HELP,
+ "About": gtk.STOCK_ABOUT,
+ "Translate": gtk.STOCK_EDIT
+}
+try:
+ _STOCK_IDS['Fullscreen'] = gtk.STOCK_FULLSCREEN
+except AttributeError:
+ # fullscreen not available on all GTK versions
+ pass
+
+class MenuItemBase(base.Widget):
+ """Base class for MenuItem and Separator."""
+
+ def show(self):
+ """Show this menu item."""
+ self._widget.show()
+
+ def hide(self):
+ """Hide and disable this menu item."""
+ self._widget.hide()
+
+ def remove_from_parent(self):
+ """Remove this menu item from it's parent Menu."""
+ parent_menu = self._widget.get_parent()
+ if parent_menu is None:
+ return
+ parent_menu_item = parent_menu.get_attach_widget()
+ if parent_menu_item is None:
+ return
+ parent_menu_item.remove(self._widget)
+
+ def _set_accel_group(self, accel_group):
+ # menu items don't care about the accel group, their parent Menu
+ # handles it for them
+ pass
+
+class MenuItem(MenuItemBase):
+ """Single item in the menu that can be clicked
+
+ :param label: The label it has (must be internationalized)
+ :param name: String identifier for this item
+ :param shortcut: Shortcut object to use
+
+ Signals:
+ - activate: menu item was clicked
+
+ Example:
+
+ >>> MenuItem(_("Preferences"), "EditPreferences")
+ >>> MenuItem(_("Cu_t"), "ClipboardCut", Shortcut("x", MOD))
+ >>> MenuItem(_("_Update Podcasts and Library"), "UpdatePodcasts",
+ ... (Shortcut("r", MOD), Shortcut(F5)))
+ >>> MenuItem(_("_Play"), "PlayPauseItem",
+ ... play=_("_Play"), pause=_("_Pause"))
+ """
+
+ def __init__(self, label, name, shortcut=None):
+ MenuItemBase.__init__(self)
+ self.name = name
+ self.set_widget(self.make_widget(label))
+ self.activate_id = self.wrapped_widget_connect('activate',
+ self._on_activate)
+ self._widget.show()
+ self.create_signal('activate')
+ _setup_accel(self._widget, self.name, shortcut)
+
+ def _on_activate(self, menu_item):
+ self.emit('activate')
+ gtk_menubar = self._find_menubar()
+ if gtk_menubar is not None:
+ try:
+ menubar = wrappermap.wrapper(gtk_menubar)
+ except KeyError:
+ logging.exception('menubar activate: '
+ 'no wrapper for gtbbk.MenuBar')
+ else:
+ menubar.emit('activate', self.name)
+
+ def _find_menubar(self):
+ """Find the MenuBar that this menu item is attached to."""
+ menu_item = self._widget
+ while True:
+ parent_menu = menu_item.get_parent()
+ if isinstance(parent_menu, gtk.MenuBar):
+ return parent_menu
+ elif parent_menu is None:
+ return None
+ menu_item = parent_menu.get_attach_widget()
+ if menu_item is None:
+ return None
+
+ def make_widget(self, label):
+ """Create the menu item to use for this widget.
+
+ Subclasses will probably want to override this.
+ """
+ if self.name in _STOCK_IDS:
+ mi = gtk.ImageMenuItem(stock_id=_STOCK_IDS[self.name])
+ mi.set_label(label)
+ return mi
+ else:
+ return gtk.MenuItem(label)
+
+ def set_label(self, new_label):
+ self._widget.set_label(new_label)
+
+ def get_label(self):
+ self._widget.get_label()
+
+class CheckMenuItem(MenuItem):
+ """MenuItem that toggles on/off"""
+
+ def make_widget(self, label):
+ return gtk.CheckMenuItem(label)
+
+ def set_state(self, active):
+ # prevent the activate signal from fireing in response to us manually
+ # changing a value
+ self._widget.handler_block(self.activate_id)
+ if active is not None:
+ self._widget.set_inconsistent(False)
+ self._widget.set_active(active)
+ else:
+ self._widget.set_inconsistent(True)
+ self._widget.set_active(False)
+ self._widget.handler_unblock(self.activate_id)
+
+ def get_state(self):
+ return self._widget.get_active()
+
+class RadioMenuItem(CheckMenuItem):
+ """MenuItem that toggles on/off and is grouped with other RadioMenuItems.
+ """
+
+ def make_widget(self, label):
+ widget = gtk.RadioMenuItem()
+ widget.set_label(label)
+ return widget
+
+ def set_group(self, group_item):
+ self._widget.set_group(group_item._widget)
+
+ def remove_from_group(self):
+ """Remove this RadioMenuItem from its current group."""
+ self._widget.set_group(None)
+
+ def _on_activate(self, menu_item):
+ # GTK sends the activate signal for both the radio button that's
+ # toggled on and the one that gets turned off. Just emit our signal
+ # for the active radio button.
+ if self.get_state():
+ MenuItem._on_activate(self, menu_item)
+
+class Separator(MenuItemBase):
+ """Separator item for menus"""
+
+ def __init__(self):
+ MenuItemBase.__init__(self)
+ self.set_widget(gtk.SeparatorMenuItem())
+ self._widget.show()
+ # Set name to be None just so that it has a similar API to other menu
+ # items.
+ self.name = None
+
+class MenuShell(base.Widget):
+ """Common code shared between Menu and MenuBar.
+
+ Subclasses must define a _menu attribute that's a gtk.MenuShell subclass.
+ """
+
+ def __init__(self):
+ base.Widget.__init__(self)
+ self._accel_group = None
+ self.children = []
+
+ def append(self, menu_item):
+ """Add a menu item to the end of this menu."""
+ self.children.append(menu_item)
+ menu_item._set_accel_group(self._accel_group)
+ self._menu.append(menu_item._widget)
+
+ def insert(self, index, menu_item):
+ """Insert a menu item in the middle of this menu."""
+ self.children.insert(index, menu_item)
+ menu_item._set_accel_group(self._accel_group)
+ self._menu.insert(menu_item._widget, index)
+
+ def remove(self, menu_item):
+ """Remove a child menu item.
+
+ :raises ValueError: menu_item is not a child of this menu
+ """
+ self.children.remove(menu_item)
+ self._menu.remove(menu_item._widget)
+ menu_item._set_accel_group(None)
+
+ def index(self, name):
+ """Get the position of a menu item in this list.
+
+ :param name: name of the menu
+ :returns: index of the menu item, or -1 if not found.
+ """
+ for i, menu_item in enumerate(self.children):
+ if menu_item.name == name:
+ return i
+ return -1
+
+ def get_children(self):
+ """Get the child menu items in order."""
+ return list(self.children)
+
+ def find(self, name):
+ """Search for a menu or menu item
+
+ This method recursively searches the entire menu structure for a Menu
+ or MenuItem object with a given name.
+
+ :raises KeyError: name not found
+ """
+ found = self._find(name)
+ if found is None:
+ raise KeyError(name)
+ else:
+ return found
+
+ def _find(self, name):
+ """Low-level helper-method for find().
+
+ :returns: found menu item or None.
+ """
+ for menu_item in self.get_children():
+ if menu_item.name == name:
+ return menu_item
+ if isinstance(menu_item, MenuShell):
+ submenu_find = menu_item._find(name)
+ if submenu_find is not None:
+ return submenu_find
+ return None
+
+class Menu(MenuShell):
+ """A Menu holds a list of MenuItems and Menus.
+
+ Example:
+ >>> Menu(_("P_layback"), "Playback", [
+ ... MenuItem(_("_Foo"), "Foo"),
+ ... MenuItem(_("_Bar"), "Bar")
+ ... ])
+ >>> Menu("", "toplevel", [
+ ... Menu(_("_File"), "File", [ ... ])
+ ... ])
+ """
+
+ def __init__(self, label, name, child_items):
+ MenuShell.__init__(self)
+ self.set_widget(gtk.MenuItem(label))
+ self._widget.show()
+ self.name = name
+ # set up _menu for the MenuShell code
+ self._menu = gtk.Menu()
+ _setup_accel(self._menu, self.name)
+ self._widget.set_submenu(self._menu)
+ for item in child_items:
+ self.append(item)
+
+ def show(self):
+ """Show this menu."""
+ self._widget.show()
+
+ def hide(self):
+ """Hide this menu."""
+ self._widget.hide()
+
+ def _set_accel_group(self, accel_group):
+ """Set the accel group for this widget.
+
+ Accel groups get created by the MenuBar. Whenever a menu or menu item
+ is added to that menu bar, the parent calls _set_accel_group() to give
+ the accel group to the child.
+ """
+ if accel_group == self._accel_group:
+ return
+ self._menu.set_accel_group(accel_group)
+ self._accel_group = accel_group
+ for child in self.children:
+ child._set_accel_group(accel_group)
+
+class MenuBar(MenuShell):
+ """Displays a list of Menu items.
+
+ Signals:
+
+ - activate(menu_bar, name): a menu item was activated
+ """
+
+ def __init__(self):
+ """Create a new MenuBar
+
+ :param name: string id to use for our action group
+ """
+ MenuShell.__init__(self)
+ self.create_signal('activate')
+ self.set_widget(gtk.MenuBar())
+ self._widget.show()
+ self._accel_group = gtk.AccelGroup()
+ # set up _menu for the MenuShell code
+ self._menu = self._widget
+
+ def get_accel_group(self):
+ return self._accel_group
+
+class MainWindowMenuBar(MenuBar):
+ """MenuBar for the main window.
+
+ This gets installed into app.widgetapp.menubar on GTK.
+ """
+ def add_initial_menus(self, menus):
+ """Add the initial set of menus.
+
+ We modify the menu structure slightly for GTK.
+ """
+ for menu in menus:
+ self.append(menu)
+ self._modify_initial_menus()
+
+ def _modify_initial_menus(self):
+ """Update the portable root menu with GTK-specific stuff."""
+ # on linux, we don't have a CheckVersion option because
+ # we update with the package system.
+ #this_platform = app.config.get(prefs.APP_PLATFORM)
+ #if this_platform == 'linux':
+ # self.find("CheckVersion").remove_from_parent()
+ #app.video_renderer.setup_subtitle_encoding_menu()