~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to osx/plat/frontends/widgets/osxmenus.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Miro - an RSS based video player application
 
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
 
3
#
 
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.
 
8
#
 
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.
 
13
#
 
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
 
17
#
 
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
 
20
# library.
 
21
#
 
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.
 
28
 
 
29
"""menus.py -- Menu handling code."""
 
30
 
 
31
import struct
 
32
import logging
 
33
 
 
34
from objc import nil, NO, YES
 
35
from AppKit import *
 
36
from Foundation import *
 
37
 
 
38
from miro import app, config, prefs
 
39
 
 
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
 
44
 
 
45
STD_ACTION_MAP = {
 
46
    "HideMiro":         (NSApp(), 'hide:'),
 
47
    "HideOthers":       (NSApp(), 'hideOtherApplications:'),
 
48
    "ShowAll":          (NSApp(), 'unhideAllApplications:'),
 
49
    "Cut":              (nil,     'cut:'),
 
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:'),
 
58
}
 
59
 
 
60
menus.set_mod(CMD)
 
61
MOD=CMD
 
62
 
 
63
MODIFIERS_MAP = {
 
64
    MOD:   NSCommandKeyMask,
 
65
    SHIFT: NSShiftKeyMask,
 
66
    CTRL:  NSControlKeyMask,
 
67
    ALT:   NSAlternateKeyMask
 
68
}
 
69
 
 
70
if isinstance(NSBackspaceCharacter, int):
 
71
    backspace = NSBackspaceCharacter
 
72
else:
 
73
    backspace = ord(NSBackspaceCharacter)
 
74
    
 
75
KEYS_MAP = {
 
76
    SPACE: " ",
 
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,
 
83
    '.': '.',
 
84
    ',': ','
 
85
}
 
86
 
 
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() 
 
89
        if i[0] != BKSPACE)
 
90
REVERSE_KEYS_MAP[u'\x7f'] = BKSPACE
 
91
REVERSE_KEYS_MAP[u'\x1b'] = ESCAPE
 
92
 
 
93
def make_modifier_mask(shortcut):
 
94
    mask = 0
 
95
    for modifier in shortcut.modifiers:
 
96
        mask |= MODIFIERS_MAP[modifier]
 
97
    return mask
 
98
 
 
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))
 
107
                continue
 
108
            else:
 
109
                if shortcut.shortcut in KEYS_MAP:
 
110
                    nsmenuitem.setKeyEquivalent_(KEYS_MAP[shortcut.shortcut])
 
111
                    nsmenuitem.setKeyEquivalentModifierMask_(make_modifier_mask(shortcut))
 
112
                    continue
 
113
 
 
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])
 
117
        else:
 
118
            nsmenuitem.setRepresentedObject_(menu_item.action)
 
119
            nsmenuitem.setTarget_(NSApp().delegate())
 
120
            nsmenuitem.setAction_('handleMenuItem:')
 
121
    return nsmenuitem
 
122
 
 
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)
 
136
 
 
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)
 
141
        return menu
 
142
    return None
 
143
 
 
144
_menu_structure = None
 
145
def populate_menu():
 
146
    short_appname = config.get(prefs.SHORT_APP_NAME)
 
147
 
 
148
    menubar = menus.get_menu()
 
149
 
 
150
    # Application menu
 
151
    miroMenuItems = [
 
152
        extract_menu_item(menubar, "About"),
 
153
        menus.Separator(),
 
154
        extract_menu_item(menubar, "Donate"),
 
155
        extract_menu_item(menubar, "CheckVersion"),
 
156
        menus.Separator(),
 
157
        extract_menu_item(menubar, "EditPreferences"),
 
158
        menus.Separator(),
 
159
        menus.Menu(_("Services"), "ServicesMenu", []),
 
160
        menus.Separator(),
 
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"),
 
166
        menus.Separator(),
 
167
        extract_menu_item(menubar, "Quit")
 
168
    ]
 
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})
 
174
 
 
175
    # File menu
 
176
    closeWinItem = menus.MenuItem(_("Close Window"), "CloseWindow", 
 
177
                                  menus.Shortcut("w", MOD))
 
178
    menubar.get("FileMenu").append(closeWinItem)
 
179
 
 
180
    # Edit menu
 
181
    editMenuItems = [
 
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"),
 
186
        menus.Separator(),
 
187
        menus.MenuItem(_("Select All"), "SelectAll", menus.Shortcut("a", MOD))
 
188
    ]
 
189
    editMenu = menus.Menu(_("Edit"), "Edit", editMenuItems)
 
190
    menubar.insert(1, editMenu)
 
191
 
 
192
    # Playback menu
 
193
    presentMenuItems = [
 
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"]),
 
203
    ]
 
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)
 
210
 
 
211
    # Window menu
 
212
    windowMenuItems = [
 
213
        menus.MenuItem(_("Zoom"), "Zoom"),
 
214
        menus.MenuItem(_("Minimize"), "Minimize", menus.Shortcut("m", MOD)),
 
215
        menus.Separator(),
 
216
        menus.MenuItem(_("Main Window"), "ShowMain", 
 
217
                       menus.Shortcut("M", MOD, SHIFT)),
 
218
        menus.Separator(),
 
219
        menus.MenuItem(_("Bring All to Front"), "BringAllToFront"),
 
220
    ]
 
221
    windowMenu = menus.Menu(_("Window"), "Window", windowMenuItems)
 
222
    menubar.insert(6, windowMenu)
 
223
 
 
224
    # Help Menu
 
225
    helpItem = menubar.get("Help")
 
226
    helpItem.label = _("%(appname)s Help", {"appname": short_appname})
 
227
    helpItem.shortcuts = (menus.Shortcut("?", MOD),)
 
228
 
 
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)
 
235
 
 
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)
 
243
 
 
244
    # we do this to get groups correct
 
245
    menubar.insert(0, miroMenu)
 
246
 
 
247
    menus.osx_menu_structure = menubar
 
248
    menus.osx_action_groups = menus.generate_action_groups(menubar)
 
249
    
 
250
    # Keep the updated structure around
 
251
    global _menu_structure
 
252
    _menu_structure = menubar
 
253
    
 
254
class ContextMenuHandler(NSObject):
 
255
    def initWithCallback_(self, callback):
 
256
        self = super(ContextMenuHandler, self).init()
 
257
        self.callback = callback
 
258
        return self
 
259
 
 
260
    def handleMenuItem_(self, sender):
 
261
        self.callback()
 
262
 
 
263
class MiroContextMenu(NSMenu):
 
264
    # Works exactly like NSMenu, except it keeps a reference to the menu
 
265
    # handler objects.
 
266
    def init(self):
 
267
        self = super(MiroContextMenu, self).init()
 
268
        self.handlers = set()
 
269
        return self
 
270
 
 
271
    def addItem_(self, item):
 
272
        if isinstance(item.target(), ContextMenuHandler):
 
273
            self.handlers.add(item.target())
 
274
        return NSMenu.addItem_(self, item)
 
275
 
 
276
def make_context_menu(menu_items):
 
277
    nsmenu = MiroContextMenu.alloc().init()
 
278
    for item in menu_items:
 
279
        if item is None:
 
280
            nsitem = NSMenuItem.separatorItem()
 
281
        else:
 
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)
 
288
            if callback is None:
 
289
                font_size = NSFont.systemFontSize()
 
290
                font = NSFont.fontWithName_size_("Lucida Sans Italic", font_size)
 
291
                if font is None:
 
292
                    font = NSFont.systemFontOfSize_(font_size)
 
293
                attributes = {NSFontAttributeName: font}
 
294
                attributed_label = NSAttributedString.alloc().initWithString_attributes_(label, attributes)
 
295
                nsitem.setAttributedTitle_(attributed_label)
 
296
            else:
 
297
                nsitem.setTitle_(label)
 
298
                if isinstance(callback, list):
 
299
                    submenu = make_context_menu(callback)
 
300
                    nsmenu.setSubmenu_forItem_(submenu, nsitem)
 
301
                else:
 
302
                    handler = ContextMenuHandler.alloc().initWithCallback_(callback)
 
303
                    nsitem.setTarget_(handler)
 
304
                    nsitem.setAction_('handleMenuItem:')
 
305
        nsmenu.addItem_(nsitem)
 
306
    return nsmenu
 
307
 
 
308
def translate_event_modifiers(event):
 
309
    mods = set()
 
310
    flags = event.modifierFlags()
 
311
    if flags & NSCommandKeyMask:
 
312
        mods.add(CMD)
 
313
    if flags & NSControlKeyMask:
 
314
        mods.add(CTRL)
 
315
    if flags & NSAlternateKeyMask:
 
316
        mods.add(ALT)
 
317
    if flags & NSShiftKeyMask:
 
318
        mods.add(SHIFT)
 
319
    return mods
 
320
 
 
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)
 
330
 
 
331
subtitles_menu_handler = SubtitleChangesHandler.alloc().init()
 
332
 
 
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("_", "&"))
 
338
 
 
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)
 
349
 
 
350
def populate_subtitles_menu(nsmenu, tracks):
 
351
    if tracks is not None and len(tracks) > 0:
 
352
        has_enabled_subtitle_track = False
 
353
        for track in tracks:
 
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:')
 
360
            if track[2]:
 
361
                item.setState_(NSOnState)
 
362
                has_enabled_subtitle_track = True
 
363
            else:
 
364
                item.setState_(NSOffState)
 
365
            nsmenu.addItem_(item)
 
366
 
 
367
        nsmenu.addItem_(NSMenuItem.separatorItem())
 
368
    
 
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)
 
376
        else:
 
377
            disable_item.setState_(NSOnState)
 
378
        nsmenu.addItem_(disable_item)
 
379
    else:
 
380
        item = NSMenuItem.alloc().init()
 
381
        item.setTitle_(_("None Available"))
 
382
        item.setEnabled_(NO)
 
383
        nsmenu.addItem_(item)
 
384
 
 
385
    nsmenu.addItem_(NSMenuItem.separatorItem())
 
386
 
 
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)