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
"""itemcontextmenu.py -- Handle popping up an context menu for an item
33
from miro import messages
34
from miro import prefs
35
from miro import config
36
from miro.gtcache import gettext as _
37
from miro.gtcache import ngettext
38
from miro.plat import resources
39
from miro.plat.frontends.widgets import file_navigator_name
42
class ItemContextMenuHandler(object):
43
"""Handles the context menus for rows in an item list."""
45
def callback(self, tableview):
46
"""Callback to handle the context menu.
48
This method can be passed into TableView.set_context_menu_callback
50
selected = [tableview.model[iter][0] for iter in \
51
tableview.get_selection()]
52
if len(selected) == 1:
53
return self._make_context_menu_single(selected[0])
55
return self._make_context_menu_multiple(selected)
57
def _remove_context_menu_item(self, selection):
58
"""The menu item to remove the item (or None to exclude it)."""
60
for info in selection:
65
return (_('Remove From the Library'), app.widgetapp.remove_items)
67
return (_('Delete'), app.widgetapp.remove_items)
69
def _add_remove_context_menu_item(self, menu, selection):
70
remove = self._remove_context_menu_item(selection)
71
if remove is not None:
74
def _make_context_menu_single(self, item):
75
"""Make the context menu for a single item."""
76
# Format for the menu list:
78
# Each item is either None or separated into (label, callback),
79
# more or less, kinda. If it's None, it's actually a
80
# separator. Otherwise..
82
# The label is one of two things:
83
# - A string, which is used as the label for the menu item
84
# - A tuple of (label_text, icon_path), in case you need icons
86
# The callback is one of three things:
87
# - None, in which case the menu item is made insensitive
88
# - A list, which means a submenu... should be in the same format
90
# - A method of some form. In other words, a *real* callback :)
93
app.playback_manager.start_with_items([item])
95
def play_externally():
96
app.widgetapp.open_file(item.video_path)
97
messages.MarkItemWatched(item.id).send_to_backend()
100
(_('Play'), app.widgetapp.play_selection),
102
if config.get(prefs.PLAY_IN_MIRO):
103
menu.append((_('Play Just This Item'), play_and_stop))
104
menu.append((_('Play Externally'), play_externally))
105
menu.append((_('Add to Playlist'), app.widgetapp.add_to_playlist))
106
self._add_remove_context_menu_item(menu, [item])
107
menu.append((_("Edit Item"), app.widgetapp.edit_item))
108
if item.video_watched:
109
menu.append((_('Mark as Unplayed'),
110
messages.MarkItemUnwatched(item.id).send_to_backend))
112
menu.append((_('Mark as Played'),
113
messages.MarkItemWatched(item.id).send_to_backend))
114
if item.expiration_date:
115
menu.append((_('Keep'),
116
messages.KeepVideo(item.id).send_to_backend))
117
if item.seeding_status == 'seeding':
118
menu.append((_('Stop Seeding'), messages.StopUpload(item.id).send_to_backend))
119
elif item.seeding_status == 'stopped':
120
menu.append((_('Resume Seeding'), messages.StartUpload(item.id).send_to_backend))
121
elif item.download_info is not None and item.download_info.state != 'failed':
123
(_('Cancel Download'),
124
messages.CancelDownload(item.id).send_to_backend)
126
if item.download_info.state != 'paused':
127
menu.append((_('Pause Download'),
128
messages.PauseDownload(item.id).send_to_backend))
130
menu.append((_('Resume Download'),
131
messages.ResumeDownload(item.id).send_to_backend))
135
messages.StartDownload(item.id).send_to_backend)
139
if not item.is_external:
140
view_menu.append((_('Web Page'), lambda: app.widgetapp.open_url(item.permalink)))
141
if item.commentslink and item.commentslink != item.permalink:
142
view_menu.append((_('Comments'), lambda: app.widgetapp.open_url(item.commentslink)))
143
if item.license and item.license != item.permalink:
144
view_menu.append((_('License'), lambda: app.widgetapp.open_url(item.license)))
146
if item.file_url != item.permalink and not item.file_url.startswith('file://'):
147
view_menu.append((_('File in Browser'), lambda: app.widgetapp.open_url(item.file_url)))
148
if file_navigator_name:
149
reveal_text = _('File in %(progname)s', {"progname": file_navigator_name})
151
reveal_text = _('File on Disk')
152
view_menu.append((reveal_text, lambda: app.widgetapp.check_then_reveal_file(item.video_path)))
154
menu.append((_('View'), view_menu))
156
if item.has_sharable_url:
157
menu.append((_('Share'), lambda: app.widgetapp.share_item(item)))
161
def _make_context_menu_multiple(self, selection):
162
"""Make the context menu for multiple items."""
171
for info in selection:
173
downloaded.append(info)
174
if info.video_watched:
176
if info.expiration_date:
177
expiring.append(info)
179
unwatched.append(info)
180
elif info.state == 'paused':
182
elif info.state == 'downloading':
183
downloading.append(info)
184
if (info.download_info.torrent and
185
info.download_info.state != 'uploading'):
186
uploadable.append(info)
188
available.append(info)
192
menu.append((ngettext('%(count)d Downloaded Item',
193
'%(count)d Downloaded Items',
195
{"count": len(downloaded)}),
197
menu.append((_('Play'), app.widgetapp.play_selection)),
198
menu.append((_('Add to Playlist'),
199
app.widgetapp.add_to_playlist))
200
self._add_remove_context_menu_item(menu, selection)
202
def mark_unwatched():
204
messages.MarkItemUnwatched(item.id).send_to_backend()
205
menu.append((_('Mark as Unplayed'), mark_unwatched))
208
for item in unwatched:
209
messages.MarkItemWatched(item.id).send_to_backend()
210
menu.append((_('Mark as Played'), mark_watched))
213
for item in expiring:
214
if item.expiration_date:
215
messages.KeepVideo(item.id).send_to_backend()
216
menu.append((_('Keep'), keep_videos))
221
menu.append((ngettext('%(count)d Available Item',
222
'%(count)d Available Items',
224
{"count": len(available)}),
227
for item in available:
228
messages.StartDownload(item.id).send_to_backend()
229
menu.append((_('Download'), download_all))
234
menu.append((ngettext('%(count)d Downloading Item',
235
'%(count)d Downloading Items',
237
{"count": len(downloading)}),
240
for item in downloading:
241
messages.CancelDownload(item.id).send_to_backend()
243
for item in downloading:
244
messages.PauseDownload(item.id).send_to_backend()
245
menu.append((_('Cancel Download'), cancel_all))
246
menu.append((_('Pause Download'), pause_all))
251
menu.append((ngettext('%(count)d Paused Item',
252
'%(count)d Paused Items',
254
{"count": len(paused)}),
258
messages.ResumeDownload(item.id).send_to_backend()
259
menu.append((_('Resume Download'), resume_all))
262
messages.CancelDownload(item.id).send_to_backend()
263
menu.append((_('Cancel Download'), cancel_all))
267
for item in uploadable:
268
messages.StartUpload(item.id).send_to_backend()
269
menu.append((_('Restart Upload'), restart_all))
273
class ItemContextMenuHandlerPlaylist(ItemContextMenuHandler):
274
"""Context menu handler for playlists."""
275
def __init__(self, playlist_id):
276
self.playlist_id = playlist_id
278
def _remove_context_menu_item(self, selection):
280
ids = [info.id for info in selection]
281
messages.RemoveVideosFromPlaylist(self.playlist_id,
282
ids).send_to_backend()
283
return (_('Remove From Playlist'), do_remove)
285
class ItemContextMenuHandlerPlaylistFolder(ItemContextMenuHandler):
286
"""Context menu handler for playlist folders."""
287
def _remove_context_menu_item(self, selection):