1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# In addition, as a special exception, the copyright holders give
19
# permission to link the code of portions of this program with the OpenSSL
22
# You must obey the GNU General Public License in all respects for all of
23
# the code used other than OpenSSL. If you modify file(s) with this
24
# exception, you may extend this exception to your version of the file(s),
25
# but you are not obligated to do so. If you do not wish to do so, delete
26
# this exception statement from your version. If you delete this exception
27
# statement from all source files in the program, then also delete it here.
29
"""menus.py -- Menu handling code."""
34
from objc import nil, NO, YES
36
from Foundation import *
38
from miro import app, config, prefs
40
from miro.gtcache import gettext as _
41
from miro.frontends.widgets import menus
42
from miro.frontends.widgets.menus import MOD, CTRL, ALT, SHIFT, CMD, RIGHT_ARROW, LEFT_ARROW, UP_ARROW, DOWN_ARROW, SPACE, ENTER, DELETE, BKSPACE, ESCAPE
43
from miro.plat.frontends.widgets import wrappermap
46
"HideMiro": (NSApp(), 'hide:'),
47
"HideOthers": (NSApp(), 'hideOtherApplications:'),
48
"ShowAll": (NSApp(), 'unhideAllApplications:'),
50
"Copy": (nil, 'copy:'),
51
"Paste": (nil, 'paste:'),
52
"Delete": (nil, 'delete:'),
53
"SelectAll": (nil, 'selectAll:'),
54
"Zoom": (nil, 'performZoom:'),
55
"Minimize": (nil, 'performMiniaturize:'),
56
"BringAllToFront": (nil, 'arrangeInFront:'),
57
"CloseWindow": (nil, 'performClose:'),
64
MOD: NSCommandKeyMask,
65
SHIFT: NSShiftKeyMask,
66
CTRL: NSControlKeyMask,
67
ALT: NSAlternateKeyMask
70
if isinstance(NSBackspaceCharacter, int):
71
backspace = NSBackspaceCharacter
73
backspace = ord(NSBackspaceCharacter)
77
BKSPACE: struct.pack("H", backspace),
78
DELETE: NSDeleteFunctionKey,
79
RIGHT_ARROW: NSRightArrowFunctionKey,
80
LEFT_ARROW: NSLeftArrowFunctionKey,
81
UP_ARROW: NSUpArrowFunctionKey,
82
DOWN_ARROW: NSDownArrowFunctionKey,
87
REVERSE_MODIFIERS_MAP = dict((i[1], i[0]) for i in MODIFIERS_MAP.items())
88
REVERSE_KEYS_MAP = dict((i[1], i[0]) for i in KEYS_MAP.items()
90
REVERSE_KEYS_MAP[u'\x7f'] = BKSPACE
91
REVERSE_KEYS_MAP[u'\x1b'] = ESCAPE
93
def make_modifier_mask(shortcut):
95
for modifier in shortcut.modifiers:
96
mask |= MODIFIERS_MAP[modifier]
99
def make_menu_item(menu_item):
100
nsmenuitem = NSMenuItem.alloc().init()
101
nsmenuitem.setTitleWithMnemonic_(menu_item.label.replace("_", "&"))
102
if isinstance(menu_item, menus.MenuItem):
103
for shortcut in menu_item.shortcuts:
104
if isinstance(shortcut.shortcut, str):
105
nsmenuitem.setKeyEquivalent_(shortcut.shortcut)
106
nsmenuitem.setKeyEquivalentModifierMask_(make_modifier_mask(shortcut))
109
if shortcut.shortcut in KEYS_MAP:
110
nsmenuitem.setKeyEquivalent_(KEYS_MAP[shortcut.shortcut])
111
nsmenuitem.setKeyEquivalentModifierMask_(make_modifier_mask(shortcut))
114
if menu_item.action in STD_ACTION_MAP:
115
nsmenuitem.setTarget_(STD_ACTION_MAP[menu_item.action][0])
116
nsmenuitem.setAction_(STD_ACTION_MAP[menu_item.action][1])
118
nsmenuitem.setRepresentedObject_(menu_item.action)
119
nsmenuitem.setTarget_(NSApp().delegate())
120
nsmenuitem.setAction_('handleMenuItem:')
123
def populate_single_menu(nsmenu, miro_menu):
124
for miro_item in miro_menu.menuitems:
125
if isinstance(miro_item, menus.Separator):
126
item = NSMenuItem.separatorItem()
127
elif isinstance(miro_item, menus.MenuItem):
128
item = make_menu_item(miro_item)
129
elif isinstance(miro_item, menus.Menu):
130
submenu = NSMenu.alloc().init()
131
populate_single_menu(submenu, miro_item)
132
item = NSMenuItem.alloc().init()
133
item.setTitle_(miro_item.label.replace("_", ""))
134
item.setSubmenu_(submenu)
135
nsmenu.addItem_(item)
137
def extract_menu_item(menu_structure, action):
138
if menu_structure.has(action):
139
menu = menu_structure.get(action)
140
menu_structure.remove(action)
144
_menu_structure = None
146
short_appname = config.get(prefs.SHORT_APP_NAME)
148
menubar = menus.get_menu()
152
extract_menu_item(menubar, "About"),
154
extract_menu_item(menubar, "Donate"),
155
extract_menu_item(menubar, "CheckVersion"),
157
extract_menu_item(menubar, "EditPreferences"),
159
menus.Menu(_("Services"), "ServicesMenu", []),
161
menus.MenuItem(_("Hide %(appname)s", {"appname": short_appname}),
162
"HideMiro", menus.Shortcut("h", MOD)),
163
menus.MenuItem(_("Hide Others"), "HideOthers",
164
menus.Shortcut("h", MOD, ALT)),
165
menus.MenuItem(_("Show All"), "ShowAll"),
167
extract_menu_item(menubar, "Quit")
169
miroMenu = menus.Menu(short_appname, "Miro", miroMenuItems)
170
miroMenu.get("EditPreferences").label = _("Preferences...")
171
miroMenu.get("EditPreferences").shortcuts = (menus.Shortcut(",", MOD),)
172
miroMenu.get("Quit").label = _("Quit %(appname)s",
173
{"appname": short_appname})
176
closeWinItem = menus.MenuItem(_("Close Window"), "CloseWindow",
177
menus.Shortcut("w", MOD))
178
menubar.get("FileMenu").append(closeWinItem)
182
menus.MenuItem(_("Cut"), "Cut", menus.Shortcut("x", MOD)),
183
menus.MenuItem(_("Copy"), "Copy", menus.Shortcut("c", MOD)),
184
menus.MenuItem(_("Paste"), "Paste", menus.Shortcut("v", MOD)),
185
menus.MenuItem(_("Delete"), "Delete"),
187
menus.MenuItem(_("Select All"), "SelectAll", menus.Shortcut("a", MOD))
189
editMenu = menus.Menu(_("Edit"), "Edit", editMenuItems)
190
menubar.insert(1, editMenu)
194
menus.MenuItem(_("Present Half Size"), "PresentHalfSize",
195
menus.Shortcut("0", MOD),
196
groups=["PlayingVideo", "PlayableVideosSelected"]),
197
menus.MenuItem(_("Present Actual Size"), "PresentActualSize",
198
menus.Shortcut("1", MOD),
199
groups=["PlayingVideo", "PlayableVideosSelected"]),
200
menus.MenuItem(_("Present Double Size"), "PresentDoubleSize",
201
menus.Shortcut("2", MOD),
202
groups=["PlayingVideo", "PlayableVideosSelected"]),
204
playback_menu = menubar.get("PlaybackMenu")
205
subtitlesMenu = playback_menu.get("SubtitlesMenu")
206
playback_menu.remove("SubtitlesMenu")
207
presentMenu = menus.Menu(_("Present Video"), "Present", presentMenuItems)
208
playback_menu.append(presentMenu)
209
playback_menu.append(subtitlesMenu)
213
menus.MenuItem(_("Zoom"), "Zoom"),
214
menus.MenuItem(_("Minimize"), "Minimize", menus.Shortcut("m", MOD)),
216
menus.MenuItem(_("Main Window"), "ShowMain",
217
menus.Shortcut("M", MOD, SHIFT)),
219
menus.MenuItem(_("Bring All to Front"), "BringAllToFront"),
221
windowMenu = menus.Menu(_("Window"), "Window", windowMenuItems)
222
menubar.insert(6, windowMenu)
225
helpItem = menubar.get("Help")
226
helpItem.label = _("%(appname)s Help", {"appname": short_appname})
227
helpItem.shortcuts = (menus.Shortcut("?", MOD),)
229
# Now populate the main menu bar
230
main_menu = NSApp().mainMenu()
231
appMenu = main_menu.itemAtIndex_(0).submenu()
232
populate_single_menu(appMenu, miroMenu)
233
servicesMenuItem = appMenu.itemWithTitle_(_("Services"))
234
NSApp().setServicesMenu_(servicesMenuItem)
236
for menu in menubar.menuitems:
237
nsmenu = NSMenu.alloc().init()
238
nsmenu.setTitle_(menu.label.replace("_", ""))
239
populate_single_menu(nsmenu, menu)
240
nsmenuitem = make_menu_item(menu)
241
nsmenuitem.setSubmenu_(nsmenu)
242
main_menu.addItem_(nsmenuitem)
244
# we do this to get groups correct
245
menubar.insert(0, miroMenu)
247
menus.osx_menu_structure = menubar
248
menus.osx_action_groups = menus.generate_action_groups(menubar)
250
# Keep the updated structure around
251
global _menu_structure
252
_menu_structure = menubar
254
class ContextMenuHandler(NSObject):
255
def initWithCallback_(self, callback):
256
self = super(ContextMenuHandler, self).init()
257
self.callback = callback
260
def handleMenuItem_(self, sender):
263
class MiroContextMenu(NSMenu):
264
# Works exactly like NSMenu, except it keeps a reference to the menu
267
self = super(MiroContextMenu, self).init()
268
self.handlers = set()
271
def addItem_(self, item):
272
if isinstance(item.target(), ContextMenuHandler):
273
self.handlers.add(item.target())
274
return NSMenu.addItem_(self, item)
276
def make_context_menu(menu_items):
277
nsmenu = MiroContextMenu.alloc().init()
278
for item in menu_items:
280
nsitem = NSMenuItem.separatorItem()
282
label, callback = item
283
nsitem = NSMenuItem.alloc().init()
284
if isinstance(label, tuple) and len(label) == 2:
285
label, icon_path = label
286
image = NSImage.alloc().initWithContentsOfFile_(icon_path)
287
nsitem.setImage_(image)
289
font_size = NSFont.systemFontSize()
290
font = NSFont.fontWithName_size_("Lucida Sans Italic", font_size)
292
font = NSFont.systemFontOfSize_(font_size)
293
attributes = {NSFontAttributeName: font}
294
attributed_label = NSAttributedString.alloc().initWithString_attributes_(label, attributes)
295
nsitem.setAttributedTitle_(attributed_label)
297
nsitem.setTitle_(label)
298
if isinstance(callback, list):
299
submenu = make_context_menu(callback)
300
nsmenu.setSubmenu_forItem_(submenu, nsitem)
302
handler = ContextMenuHandler.alloc().initWithCallback_(callback)
303
nsitem.setTarget_(handler)
304
nsitem.setAction_('handleMenuItem:')
305
nsmenu.addItem_(nsitem)
308
def translate_event_modifiers(event):
310
flags = event.modifierFlags()
311
if flags & NSCommandKeyMask:
313
if flags & NSControlKeyMask:
315
if flags & NSAlternateKeyMask:
317
if flags & NSShiftKeyMask:
321
class SubtitleChangesHandler(NSObject):
322
def selectSubtitleTrack_(self, sender):
323
app.playback_manager.player.enable_subtitle_track(sender.tag())
324
on_playback_change(app.playback_manager)
325
def openSubtitleFile_(self, sender):
326
app.playback_manager.open_subtitle_file()
327
def disableSubtitles_(self, sender):
328
app.playback_manager.player.disable_subtitles()
329
on_playback_change(app.playback_manager)
331
subtitles_menu_handler = SubtitleChangesHandler.alloc().init()
333
def on_menu_change(menu_manager):
334
main_menu = NSApp().mainMenu()
335
play_pause_menu_item = main_menu.itemAtIndex_(6).submenu().itemAtIndex_(0)
336
play_pause = _menu_structure.get("PlayPauseItem").state_labels[app.menu_manager.play_pause_state]
337
play_pause_menu_item.setTitleWithMnemonic_(play_pause.replace("_", "&"))
339
def on_playback_change(playback_manager):
340
main_menu = NSApp().mainMenu()
341
subtitles_menu_root = main_menu.itemAtIndex_(6).submenu().itemAtIndex_(15)
342
subtitles_menu = NSMenu.alloc().init()
343
subtitles_menu.setAutoenablesItems_(NO)
344
subtitles_tracks = None
345
if app.playback_manager.is_playing and not app.playback_manager.is_playing_audio:
346
subtitles_tracks = app.playback_manager.player.get_subtitle_tracks()
347
populate_subtitles_menu(subtitles_menu, subtitles_tracks)
348
subtitles_menu_root.setSubmenu_(subtitles_menu)
350
def populate_subtitles_menu(nsmenu, tracks):
351
if tracks is not None and len(tracks) > 0:
352
has_enabled_subtitle_track = False
354
item = NSMenuItem.alloc().init()
355
item.setTag_(track[0])
356
item.setTitle_(track[1])
357
item.setEnabled_(YES)
358
item.setTarget_(subtitles_menu_handler)
359
item.setAction_('selectSubtitleTrack:')
361
item.setState_(NSOnState)
362
has_enabled_subtitle_track = True
364
item.setState_(NSOffState)
365
nsmenu.addItem_(item)
367
nsmenu.addItem_(NSMenuItem.separatorItem())
369
disable_item = NSMenuItem.alloc().init()
370
disable_item.setTitle_(_("Disable Subtitles"))
371
disable_item.setEnabled_(YES)
372
disable_item.setTarget_(subtitles_menu_handler)
373
disable_item.setAction_('disableSubtitles:')
374
if has_enabled_subtitle_track:
375
disable_item.setState_(NSOffState)
377
disable_item.setState_(NSOnState)
378
nsmenu.addItem_(disable_item)
380
item = NSMenuItem.alloc().init()
381
item.setTitle_(_("None Available"))
383
nsmenu.addItem_(item)
385
nsmenu.addItem_(NSMenuItem.separatorItem())
387
load_item = NSMenuItem.alloc().init()
388
load_item.setTitle_(_("Select a Subtitles file..."))
389
load_item.setEnabled_(app.playback_manager.is_playing and not app.playback_manager.is_playing_audio)
390
load_item.setTarget_(subtitles_menu_handler)
391
load_item.setAction_('openSubtitleFile:')
392
nsmenu.addItem_(load_item)