diff options
Diffstat (limited to 'mvc/widgets/osx/osxmenus.py')
-rw-r--r-- | mvc/widgets/osx/osxmenus.py | 571 |
1 files changed, 0 insertions, 571 deletions
diff --git a/mvc/widgets/osx/osxmenus.py b/mvc/widgets/osx/osxmenus.py deleted file mode 100644 index 32ca469..0000000 --- a/mvc/widgets/osx/osxmenus.py +++ /dev/null @@ -1,571 +0,0 @@ -# @Base: Miro - an RSS based video player application -# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 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. - -"""menus.py -- Menu handling code.""" - -import logging -import struct - -from objc import nil, NO, YES -import AppKit -from AppKit import * -from Foundation import * - -from mvc import signals -from mvc.widgets import keyboard -# import these names directly into our namespace for easy access -from mvc.widgets.keyboard import Shortcut, MOD - -# XXX hacks -def _(text, *params): - if params: - return text % params[0] - return text - -MODIFIERS_MAP = { - keyboard.MOD: NSCommandKeyMask, - keyboard.CMD: NSCommandKeyMask, - keyboard.SHIFT: NSShiftKeyMask, - keyboard.CTRL: NSControlKeyMask, - keyboard.ALT: NSAlternateKeyMask -} - -if isinstance(NSBackspaceCharacter, int): - backspace = NSBackspaceCharacter -else: - backspace = ord(NSBackspaceCharacter) - -KEYS_MAP = { - keyboard.SPACE: " ", - keyboard.ENTER: "\r", - keyboard.BKSPACE: struct.pack("H", backspace), - keyboard.DELETE: NSDeleteFunctionKey, - keyboard.RIGHT_ARROW: NSRightArrowFunctionKey, - keyboard.LEFT_ARROW: NSLeftArrowFunctionKey, - keyboard.UP_ARROW: NSUpArrowFunctionKey, - keyboard.DOWN_ARROW: NSDownArrowFunctionKey, - '.': '.', - ',': ',' -} -# add function keys -for i in range(1, 13): - portable_key = getattr(keyboard, "F%s" % i) - osx_key = getattr(AppKit, "NSF%sFunctionKey" % i) - KEYS_MAP[portable_key] = osx_key - -REVERSE_MODIFIERS_MAP = dict((i[1], i[0]) for i in MODIFIERS_MAP.items()) -REVERSE_KEYS_MAP = dict((i[1], i[0]) for i in KEYS_MAP.items() - if i[0] != keyboard.BKSPACE) -REVERSE_KEYS_MAP[u'\x7f'] = keyboard.BKSPACE -REVERSE_KEYS_MAP[u'\x1b'] = keyboard.ESCAPE - -def make_modifier_mask(shortcut): - mask = 0 - for modifier in shortcut.modifiers: - mask |= MODIFIERS_MAP[modifier] - return mask - -VIEW_ITEM_MAP = {} - -def _remove_mnemonic(label): - """Remove the underscore used by GTK for mnemonics. - - We totally ignore them on OSX, since they are now deprecated. - """ - return label.replace("_", "") - -def handle_menu_activate(ns_menu_item): - """Handle a menu item being activated. - - This gets called by our application delegate. - """ - - menu_item = ns_menu_item.representedObject() - menu_item.emit("activate") - menubar = menu_item._find_menubar() - if menubar is not None: - menubar.emit("activate", menu_item.name) - -class MenuItemBase(signals.SignalEmitter): - """Base class for MenuItem and Separator""" - def __init__(self): - signals.SignalEmitter.__init__(self) - self.name = None - self.parent = None - - def show(self): - self._menu_item.setHidden_(False) - - def hide(self): - self._menu_item.setHidden_(True) - - def enable(self): - self._menu_item.setEnabled_(True) - - def disable(self): - self._menu_item.setEnabled_(False) - - def remove_from_parent(self): - """Remove this menu item from it's parent Menu.""" - if self.parent is not None: - self.parent.remove(self) - -class MenuItem(MenuItemBase): - """See the GTK version of this method for the current docstring.""" - - # map Miro action names to standard OSX actions. - _STD_ACTION_MAP = { - "HideMiro": (NSApp(), 'hide:'), - "HideOthers": (NSApp(), 'hideOtherApplications:'), - "ShowAll": (NSApp(), 'unhideAllApplications:'), - "Cut": (nil, 'cut:'), - "Copy": (nil, 'copy:'), - "Paste": (nil, 'paste:'), - "Delete": (nil, 'delete:'), - "SelectAll": (nil, 'selectAll:'), - "Zoom": (nil, 'performZoom:'), - "Minimize": (nil, 'performMiniaturize:'), - "BringAllToFront": (nil, 'arrangeInFront:'), - "CloseWindow": (nil, 'performClose:'), - } - - def __init__(self, label, name, shortcut=None): - MenuItemBase.__init__(self) - self.name = name - self._menu_item = self._make_menu_item(label) - self.create_signal('activate') - self._setup_shortcut(shortcut) - - def _make_menu_item(self, label): - menu_item = NSMenuItem.alloc().init() - menu_item.setTitle_(_remove_mnemonic(label)) - # we set ourselves as the represented object for the menu item so we - # can easily translate one to the other - menu_item.setRepresentedObject_(self) - if self.name in self._STD_ACTION_MAP: - menu_item.setTarget_(self._STD_ACTION_MAP[self.name][0]) - menu_item.setAction_(self._STD_ACTION_MAP[self.name][1]) - else: - menu_item.setTarget_(NSApp().delegate()) - menu_item.setAction_('handleMenuActivate:') - return menu_item - - def _setup_shortcut(self, shortcut): - if shortcut is None: - key = '' - modifier_mask = 0 - elif isinstance(shortcut.shortcut, str): - key = shortcut.shortcut - modifier_mask = make_modifier_mask(shortcut) - elif shortcut.shortcut in KEYS_MAP: - key = KEYS_MAP[shortcut.shortcut] - modifier_mask = make_modifier_mask(shortcut) - else: - logging.warn("Don't know how to handle shortcut: %s", shortcut) - return - self._menu_item.setKeyEquivalent_(key) - self._menu_item.setKeyEquivalentModifierMask_(modifier_mask) - - def _change_shortcut(self, shortcut): - self._setup_shortcut(shortcut) - - def set_label(self, new_label): - self._menu_item.setTitle_(new_label) - - def get_label(self): - self._menu_item.title() - - def _find_menubar(self): - """Remove this menu item from it's parent Menu.""" - menu_item = self - while menu_item.parent is not None: - menu_item = menu_item.parent - if isinstance(menu_item, MenuBar): - return menu_item - else: - return None - -class CheckMenuItem(MenuItem): - """See the GTK version of this method for the current docstring.""" - def set_state(self, active): - if active is None: - state = NSMixedState - elif active: - state = NSOnState - else: - state = NSOffState - self._menu_item.setState_(state) - - def get_state(self): - return self._menu_item.state() == NSOnState - - def do_activate(self): - if self._menu_item.state() == NSOffState: - self._menu_item.setState_(NSOnState) - else: - self._menu_item.setState_(NSOffState) - -class RadioMenuItem(CheckMenuItem): - """See the GTK version of this method for the current docstring.""" - def __init__(self, label, name, shortcut=None): - CheckMenuItem.__init__(self, label, name, shortcut) - # The leader of a radio group stores the list of all items in the - # group - self.group_leader = None - self.others_in_group = set() - - def set_group(self, group_item): - if self.group_leader is not None: - raise ValueError("%s is already in a group" % self) - if group_item.group_leader is None: - group_leader = group_item - else: - group_leader = group_item.group_leader - if group_leader.group_leader is not None: - raise AssertionError("group_leader structure is wrong") - self.group_leader = group_leader - group_leader.others_in_group.add(self) - - def remove_from_group(self): - """Remove this RadioMenuItem from its current group.""" - if self.group_leader is not None: - # we have a group leader, remove ourself from their list. - # Note that this code will work even if we're the last item in - # others_in_group. - self.group_leader.others_in_group.remove(self) - self.group_leader = None - elif len(self.others_in_group) > 1: - # we're the group leader, hand off the leader to a different item - first_item = iter(self.others_in_group).next() - for other in self.others_in_group: - if other is first_item: - other.others_in_group = self.others_in_group - other.others_in_group.remove(first_item) - other.group_leader = None - else: - other.group_leader = first_item - self.others_in_group = set() - elif len(self.others_in_group) == 1: - # we're the group leader, but there's only 1 other item. unset - # everything. - for other in self.others_in_group: - other.group_leader = None - self.others_in_group = set() - - def _items_in_group(self): - if self.group_leader is not None: # we have a group leader - yield self.group_leader - for other in self.group_leader.others_in_group: - yield other - elif self.others_in_group: # we're the group leader - yield self - for other in self.others_in_group: - yield other - else: # we don't have a group set - yield self - - def do_activate(self): - for item in self._items_in_group(): - if item is not self: - item.set_state(False) - CheckMenuItem.do_activate(self) - -class Separator(MenuItemBase): - """See the GTK version of this method for the current docstring.""" - def __init__(self): - MenuItemBase.__init__(self) - self._menu_item = NSMenuItem.separatorItem() - -class MenuShell(signals.SignalEmitter): - def __init__(self, nsmenu): - signals.SignalEmitter.__init__(self) - self._menu = nsmenu - self.children = [] - self.parent = None - - def append(self, menu_item): - """Add a menu item to the end of this menu.""" - self.children.append(menu_item) - self._menu.addItem_(menu_item._menu_item) - menu_item.parent = self - - def insert(self, index, menu_item): - """Insert a menu item in the middle of this menu.""" - self.children.insert(index, menu_item) - self._menu.insertItem_atIndex_(menu_item._menu_item, index) - menu_item.parent = self - - def index(self, name): - """Find the position of a child menu item.""" - for i, menu_item in enumerate(self.children): - if menu_item.name == name: - return i - return -1 - - 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.removeItem_(menu_item._menu_item) - menu_item.parent = None - - 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, Menu): - submenu_find = menu_item._find(name) - if submenu_find is not None: - return submenu_find - return None - -class Menu(MenuShell): - """See the GTK version of this method for the current docstring.""" - def __init__(self, label, name, child_items=None): - MenuShell.__init__(self, NSMenu.alloc().init()) - self._menu.setTitle_(_remove_mnemonic(label)) - # we will enable/disable menu items manually - self._menu.setAutoenablesItems_(False) - self.name = name - if child_items is not None: - for item in child_items: - self.append(item) - self._menu_item = NSMenuItem.alloc().init() - self._menu_item.setTitle_(_remove_mnemonic(label)) - self._menu_item.setSubmenu_(self._menu) - # Hack to set the services menu - if name == "ServicesMenu": - NSApp().setServicesMenu_(self._menu_item) - - def show(self): - self._menu_item.setHidden_(False) - - def hide(self): - self._menu_item.setHidden_(True) - - def enable(self): - self._menu_item.setEnabled_(True) - - def disable(self): - self._menu_item.setEnabled_(False) - -class AppMenu(MenuShell): - """Wrapper for the application menu (AKA the Miro menu) - - We need to special case this because OSX automatically creates the menu - item. - """ - def __init__(self): - MenuShell.__init__(self, NSApp().mainMenu().itemAtIndex_(0).submenu()) - self.name = "Libre Video Converter" - -class MenuBar(MenuShell): - """See the GTK version of this method for the current docstring.""" - def __init__(self): - MenuShell.__init__(self, NSApp().mainMenu()) - self.create_signal('activate') - self._add_app_menu() - - def _add_app_menu(self): - """Add the app menu to this menu bar. - - We need to special case this because OSX automatically adds the - NSMenuItem for the app menu, we just need to set up our wrappers. - """ - self._app_menu = AppMenu() - self.children.append(self._app_menu) - self._app_menu.parent = self - - def add_initial_menus(self, menus): - for menu in menus: - self.append(menu) - self._modify_initial_menus() - - def _extract_menu_item(self, name): - """Helper method for changing the portable menu structure.""" - menu_item = self.find(name) - menu_item.remove_from_parent() - return menu_item - - def _modify_initial_menus(self): - short_appname = "Libre Video Converter" # XXX - - # Application menu - miroMenuItems = [ - self._extract_menu_item("About"), - Separator(), - self._extract_menu_item("Quit") - ] - - for item in miroMenuItems: - self._app_menu.append(item) - - self._app_menu.find("Quit").set_label(_("Quit %(appname)s", - {"appname": short_appname})) - - # Help Menu - #helpItem = self.find("Help") - #helpItem.set_label(_("%(appname)s Help", {"appname": short_appname})) - #helpItem._change_shortcut(Shortcut("?", MOD)) - - self._update_present_menu() - self._connect_to_signals() - - def do_activate(self, name): - # We handle a couple OSX-specific actions here - if name == "PresentActualSize": - NSApp().delegate().present_movie('natural-size') - elif name == "PresentDoubleSize": - NSApp().delegate().present_movie('double-size') - elif name == "PresentHalfSize": - NSApp().delegate().present_movie('half-size') - elif name == "ShowMain": - app.widgetapp.window.nswindow.makeKeyAndOrderFront_(self) - - def _connect_to_signals(self): - return - app.playback_manager.connect("will-play", self._on_playback_change) - app.playback_manager.connect("will-stop", self._on_playback_change) - - def _on_playback_change(self, playback_manager, *args): - self._update_present_menu() - - def _update_present_menu(self): - return - if self._should_enable_present_menu(): - for menu_item in self.present_menu.get_children(): - menu_item.enable() - else: - for menu_item in self.present_menu.get_children(): - menu_item.disable() - - def _should_enable_present_menu(self): - return False - if (app.playback_manager.is_playing and - not app.playback_manager.is_playing_audio): - # we're currently playing video, allow the user to fullscreen - return True - selection_info = app.item_list_controller_manager.get_selection_info() - if (selection_info.has_download and - selection_info.has_file_type('video')): - # A downloaded video is selected, allow the user to start playback - # in fullscreen - return True - return False - -#class ContextMenuHandler(NSObject): -# def initWithCallback_(self, callback): -# self = super(ContextMenuHandler, self).init() -# self.callback = callback -# return self -# -# def handleMenuItem_(self, sender): -# self.callback() -# -#class MiroContextMenu(NSMenu): -# # Works exactly like NSMenu, except it keeps a reference to the menu -# # handler objects. -# def init(self): -# self = super(MiroContextMenu, self).init() -# self.handlers = set() -# return self -# -# def addItem_(self, item): -# if isinstance(item.target(), ContextMenuHandler): -# self.handlers.add(item.target()) -# return NSMenu.addItem_(self, item) -# -def make_context_menu(menu_items): - nsmenu = MiroContextMenu.alloc().init() - for item in menu_items: - if item is None: - nsitem = NSMenuItem.separatorItem() - else: - label, callback = item - nsitem = NSMenuItem.alloc().init() - if isinstance(label, tuple) and len(label) == 2: - label, icon_path = label - image = NSImage.alloc().initWithContentsOfFile_(icon_path) - nsitem.setImage_(image) - if callback is None: - font_size = NSFont.systemFontSize() - font = NSFont.fontWithName_size_("Lucida Sans Italic", font_size) - if font is None: - font = NSFont.systemFontOfSize_(font_size) - attributes = {NSFontAttributeName: font} - attributed_label = NSAttributedString.alloc().initWithString_attributes_(label, attributes) - nsitem.setAttributedTitle_(attributed_label) - else: - nsitem.setTitle_(label) - if isinstance(callback, list): - submenu = make_context_menu(callback) - nsmenu.setSubmenu_forItem_(submenu, nsitem) - else: - handler = ContextMenuHandler.alloc().initWithCallback_(callback) - nsitem.setTarget_(handler) - nsitem.setAction_('handleMenuItem:') - nsmenu.addItem_(nsitem) - return nsmenu - -def translate_event_modifiers(event): - mods = set() - flags = event.modifierFlags() - if flags & NSCommandKeyMask: - mods.add(keyboard.CMD) - if flags & NSControlKeyMask: - mods.add(keyboard.CTRL) - if flags & NSAlternateKeyMask: - mods.add(keyboard.ALT) - if flags & NSShiftKeyMask: - mods.add(keyboard.SHIFT) - return mods |