1
# -*- coding: utf-8 -*-
2
# Copyright 2006 Markus Koller
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License version 2 as
6
# published by the Free Software Foundation
8
# $Id: media.py 4330 2008-09-14 03:19:26Z piman $
15
from quodlibet import config
16
from quodlibet import devices
17
from quodlibet import qltk
18
from quodlibet import stock
19
from quodlibet import util
21
from quodlibet.browsers._base import Browser
22
from quodlibet.formats._audio import AudioFile
23
from quodlibet.qltk.views import AllTreeView
24
from quodlibet.qltk.songsmenu import SongsMenu
25
from quodlibet.qltk.wlw import WaitLoadBar
26
from quodlibet.qltk.browser import LibraryBrowser
27
from quodlibet.qltk.delete import DeleteDialog
29
class DeviceProperties(gtk.Dialog):
30
def __init__(self, parent, device):
31
super(DeviceProperties, self).__init__(
32
_("Device Properties"), qltk.get_top_parent(parent),
33
buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE))
34
self.set_default_size(400, -1)
35
self.connect('response', self.__close)
38
table.set_border_width(8)
39
table.set_row_spacings(8)
40
table.set_col_spacings(8)
41
self.vbox.pack_start(table, expand=False)
45
props.append((_("Device:"), device.dev, None))
46
mountpoint = util.escape(
47
device.mountpoint or ("<i>%s</i>" % _("Not mounted")))
48
props.append((_("Mount Point:"), mountpoint, None))
50
props.append((None, None, None))
53
entry.set_text(device['name'])
54
props.append((_("_Name:"), entry, 'name'))
57
for title, value, key in props + device.Properties():
59
table.attach(gtk.HSeparator(), 0, 2, y, y + 1)
61
if key and isinstance(value, gtk.CheckButton):
62
value.set_label(title)
63
value.set_use_underline(True)
64
value.connect('toggled', self.__changed, key, device)
65
table.attach(value, 0, 2, y, y + 1, xoptions=gtk.FILL)
68
label.set_markup("<b>%s</b>" % util.escape(title))
69
label.set_alignment(0.0, 0.5)
70
table.attach(label, 0, 1, y, y + 1, xoptions=gtk.FILL)
71
if key and isinstance(value, gtk.Widget):
73
label.set_mnemonic_widget(widget)
74
label.set_use_underline(True)
75
widget.connect('changed', self.__changed, key, device)
77
widget = gtk.Label(value)
78
widget.set_use_markup(True)
79
widget.set_selectable(True)
80
widget.set_alignment(0.0, 0.5)
81
table.attach(widget, 1, 2, y, y + 1)
85
def __changed(self, widget, key, device):
86
if isinstance(widget, gtk.Entry):
87
value = widget.get_text()
88
elif isinstance(widget, gtk.SpinButton):
89
value = widget.get_value()
90
elif isinstance(widget, gtk.CheckButton):
91
value = widget.get_active()
93
raise NotImplementedError
96
def __close(self, dialog, response):
100
# This will be included in SongsMenu
101
class Menu(gtk.Menu):
102
def __init__(self, songs, library):
103
super(Menu, self).__init__()
104
for device in MediaDevices.devices():
105
i = qltk.MenuItem(device['name'], device.icon)
106
i.set_sensitive(device.is_connected())
108
'activate', self.__copy_to_device, device, songs, library)
112
def __copy_to_device(device, songs, library):
113
if len(MediaDevices.instances()) > 0:
114
browser = MediaDevices.instances()[0]
116
win = LibraryBrowser(MediaDevices, library)
117
browser = win.browser
118
browser.select(device)
119
browser.dropped(browser.get_toplevel().songlist, songs)
121
class MediaDevices(gtk.VBox, Browser, util.InstanceTracker):
122
__gsignals__ = Browser.__gsignals__
124
name = _("Media Devices")
125
accelerated_name = _("_Media Devices")
127
replaygain_profiles = ['track']
129
__devices = gtk.ListStore(object, str)
134
def cell_data(col, render, model, iter):
135
device = model[iter][0]
136
if device.is_connected():
137
render.markup = "<b>%s</b>" % util.escape(device['name'])
139
render.markup = util.escape(device['name'])
140
render.set_property('markup', render.markup)
143
def init(klass, library):
144
devices._hal.connect_to_signal(
145
'DeviceAdded', klass.__hal_device_added)
146
devices._hal.connect_to_signal(
147
'DeviceRemoved', klass.__hal_device_removed)
148
for udi in devices.discover():
149
klass.__hal_device_added(udi)
153
return [row[0] for row in klass.__devices]
156
def __hal_device_added(klass, udi):
157
device = devices.get_by_udi(udi)
159
klass.__add_device(device)
162
def __hal_device_removed(klass, udi):
163
for row in klass.__devices:
164
if row[0].udi == udi:
165
klass.__devices.remove(row.iter)
169
def __add_device(klass, device):
170
klass.__devices.append(row=[device, device.icon])
172
def __init__(self, library, player):
173
super(MediaDevices, self).__init__(spacing=6)
174
self._register_instance()
178
# Device list on the left pane
179
swin = gtk.ScrolledWindow()
180
swin.set_shadow_type(gtk.SHADOW_IN)
181
swin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
182
self.pack_start(swin)
184
self.__view = view = AllTreeView()
185
view.set_model(self.__devices)
186
view.set_rules_hint(True)
187
view.set_headers_visible(False)
188
view.get_selection().set_mode(gtk.SELECTION_BROWSE)
189
view.get_selection().connect_object('changed', self.__refresh, False)
190
view.connect('popup-menu', self.__popup_menu, library)
191
if player: view.connect('row-activated', lambda *a: player.reset())
194
col = gtk.TreeViewColumn("Devices")
195
view.append_column(col)
197
render = gtk.CellRendererPixbuf()
198
col.pack_start(render, expand=False)
199
col.add_attribute(render, 'stock-id', 1)
201
self.__render = render = gtk.CellRendererText()
202
render.set_property('ellipsize', pango.ELLIPSIZE_END)
203
render.connect('edited', self.__edited)
204
col.pack_start(render)
205
col.set_cell_data_func(render, MediaDevices.cell_data)
207
hbox = gtk.HBox(spacing=6)
208
hbox.set_homogeneous(True)
209
self.pack_start(hbox, expand=False)
211
self.__refresh_button = refresh = gtk.Button(stock=gtk.STOCK_REFRESH)
212
refresh.connect_object('clicked', self.__refresh, True)
213
refresh.set_sensitive(False)
214
hbox.pack_start(refresh)
216
self.__eject_button = eject = gtk.Button(stock=stock.EJECT)
217
eject.connect('clicked', self.__eject)
218
eject.set_sensitive(False)
219
hbox.pack_start(eject)
221
# Device info on the right pane
222
self.__header = table = gtk.Table()
223
table.set_col_spacings(8)
225
self.__device_icon = icon = gtk.Image()
226
icon.set_size_request(48, 48)
227
table.attach(icon, 0, 1, 0, 2, 0)
229
self.__device_name = label = gtk.Label()
230
label.set_alignment(0, 0)
231
table.attach(label, 1, 3, 0, 1)
233
self.__device_space = label = gtk.Label()
234
label.set_alignment(0, 0.5)
235
table.attach(label, 1, 2, 1, 2)
237
self.__progress = progress = gtk.ProgressBar()
238
progress.set_size_request(150, -1)
239
table.attach(progress, 2, 3, 1, 2, xoptions=0, yoptions=0)
241
self.accelerators = gtk.AccelGroup()
242
key, mod = gtk.accelerator_parse('F2')
243
self.accelerators.connect_group(key, mod, 0, self.__rename)
245
self.__statusbar = WaitLoadBar()
249
def pack(self, songpane):
250
self.__vbox = vbox = gtk.VBox(spacing=6)
251
vbox.pack_start(self.__header, expand=False)
252
vbox.pack_start(songpane)
253
vbox.pack_start(self.__statusbar, expand=False)
256
self.__header.show_all()
258
self.__statusbar.show_all()
259
self.__statusbar.hide()
261
self.__paned = paned = qltk.RHPaned()
266
def unpack(self, container, songpane):
267
self.__vbox.remove(songpane)
268
self.__paned.remove(self)
270
def Menu(self, songs, songlist, library):
271
model, iter = self.__view.get_selection().get_selected()
273
device = model[iter][0]
274
delete = device.delete and self.__delete_songs
278
menu = SongsMenu(library, songs, delete=delete, remove=False,
279
accels=songlist.accelerators)
286
selection = self.__view.get_selection()
287
model, iter = selection.get_selected()
288
config.set('browsers', 'media', model[iter][0]['name'])
291
try: name = config.get('browsers', 'media')
292
except config.error: pass
294
for row in self.__devices:
295
if row[0]['name'] == name: break
297
selection = self.__view.get_selection()
298
selection.unselect_all()
299
selection.select_iter(row.iter)
301
def select(self, device):
302
for row in self.__devices:
303
if row[0] == device: break
306
# Force a full refresh
307
try: del self.__cache[device.udi]
308
except KeyError: pass
310
selection = self.__view.get_selection()
311
selection.unselect_all()
312
selection.select_iter(row.iter)
314
def dropped(self, songlist, songs):
315
return self.__copy_songs(songlist, songs)
317
def __popup_menu(self, view, library):
318
model, iter = view.get_selection().get_selected()
319
device = model[iter][0]
321
if device.is_connected() and not self.__busy:
322
songs = self.__list_songs(device)
325
menu = SongsMenu(library, songs, playlists=False,
326
devices=False, remove=False)
330
props = gtk.ImageMenuItem(gtk.STOCK_PROPERTIES)
331
props.connect_object('activate', self.__properties, model[iter][0])
332
props.set_sensitive(not self.__busy)
335
ren = gtk.ImageMenuItem(stock.RENAME)
336
keyval, mod = gtk.accelerator_parse("F2")
338
'activate', self.accelerators, keyval, mod, gtk.ACCEL_VISIBLE)
340
self.__render.set_property('editable', True)
341
view.set_cursor(path, view.get_columns()[0], start_editing=True)
342
ren.connect_object('activate', rename, model.get_path(iter))
347
eject = gtk.ImageMenuItem(stock.EJECT)
349
not self.__busy and device.eject and device.is_connected())
350
eject.connect_object('activate', self.__eject, None)
353
refresh = gtk.ImageMenuItem(gtk.STOCK_REFRESH)
354
refresh.set_sensitive(device.is_connected())
355
refresh.connect_object('activate', self.__refresh, True)
356
menu.prepend(refresh)
359
menu.popup(None, None, None, 0, gtk.get_current_event_time())
362
def __properties(self, device):
363
DeviceProperties(self, device).run()
364
self.__set_name(device)
366
def __rename(self, group, acceleratable, keyval, modifier):
367
model, iter = self.__view.get_selection().get_selected()
369
self.__render.set_property('editable', True)
370
self.__view.set_cursor(model.get_path(iter),
371
self.__view.get_columns()[0],
374
def __edited(self, render, path, newname):
375
self.__devices[path][0]['name'] = newname
376
self.__set_name(self.__devices[path][0])
377
render.set_property('editable', False)
380
def __set_name(self, device):
381
self.__device_name.set_markup(
382
'<span size="x-large"><b>%s</b></span>' %
383
util.escape(device['name']))
385
def __refresh(self, rescan=False):
386
model, iter = self.__view.get_selection().get_selected()
388
path = model[iter].path
389
if not rescan and self.__last == path: return
392
device = model[iter][0]
393
self.__device_icon.set_from_stock(device.icon, gtk.ICON_SIZE_DIALOG)
394
self.__set_name(device)
397
if device.is_connected():
398
self.__header.show_all()
399
self.__eject_button.set_sensitive(bool(device.eject))
400
self.__refresh_button.set_sensitive(True)
401
self.__refresh_space(device)
403
try: songs = self.__list_songs(device, rescan)
404
except NotImplementedError: pass
406
self.__eject_button.set_sensitive(False)
407
self.__refresh_button.set_sensitive(False)
409
self.emit('songs-selected', songs, device.ordered)
412
self.emit('songs-selected', [], False)
414
def __refresh_space(self, device):
415
try: space, free = device.get_space()
416
except NotImplementedError:
417
self.__device_space.set_text("")
418
self.__progress.hide()
421
fraction = float(used) / space
423
self.__device_space.set_markup(
424
_("<b>%s</b> used, <b>%s</b> available") %
425
(util.format_size(used), util.format_size(free)))
426
self.__progress.set_fraction(fraction)
427
self.__progress.set_text("%.f%%" % round(fraction * 100))
428
self.__progress.show()
430
def __list_songs(self, device, rescan=False):
431
if rescan or not device.udi in self.__cache:
433
self.__cache[device.udi] = device.list(self.__statusbar)
435
return self.__cache[device.udi]
437
def __check_device(self, device, message):
438
if not device.is_connected():
441
_("<b>%s</b> is not connected.") % util.escape(device['name'])
446
def __copy_songs(self, songlist, songs):
447
model, iter = self.__view.get_selection().get_selected()
448
if not iter: return False
450
device = model[iter][0]
451
if not self.__check_device(device, _("Unable to copy songs")):
456
wlb = self.__statusbar
457
wlb.setup(len(songs), _("Copying <b>%(song)s</b>"), { 'song': '' })
460
model = songlist.get_model()
462
label = util.escape(song('~artist~title'))
463
if wlb.step(song=label):
468
songlist.scroll_to_cell(model[-1].path)
469
while gtk.events_pending(): gtk.main_iteration()
471
space, free = device.get_space()
472
if free < os.path.getsize(song['~filename']):
475
self, _("Unable to copy song"),
476
_("There is not enough free space for this song.")
480
status = device.copy(songlist, song)
481
if isinstance(status, AudioFile):
482
model.append([status])
483
try: self.__cache[device.udi].append(song)
484
except KeyError: pass
485
self.__refresh_space(device)
487
msg = _("<b>%s</b> could not be copied.") % label
488
if type(status) == unicode:
489
msg += "\n\n" + util.escape(status)
490
qltk.WarningMessage(self, _("Unable to copy song"), msg).run()
492
if device.cleanup and not device.cleanup(wlb, 'copy'):
500
def __delete_songs(self, songs):
501
model, iter = self.__view.get_selection().get_selected()
504
songlist = qltk.get_top_parent(self).songlist
506
device = model[iter][0]
507
if not self.__check_device(device, _("Unable to delete songs")):
510
song_titles = [s('~artist~title') for s in songs]
511
if DeleteDialog(self, song_titles, False, True).run() != 2:
516
wlb = self.__statusbar
517
wlb.setup(len(songs), _("Deleting <b>%(song)s</b>"), { 'song': '' })
520
model = songlist.get_model()
522
label = util.escape(song('~artist~title'))
523
if wlb.step(song=label):
527
status = device.delete(songlist, song)
529
model.remove(model.find(song))
530
try: self.__cache[device.udi].remove(song)
531
except (KeyError, ValueError): pass
532
self.__refresh_space(device)
534
msg = _("<b>%s</b> could not be deleted.") % label
535
if type(status) == unicode:
536
msg += "\n\n%s" % status
538
self, _("Unable to delete song"), msg).run()
540
if device.cleanup and not device.cleanup(wlb, 'delete'):
547
def __eject(self, button):
548
model, iter = self.__view.get_selection().get_selected()
550
device = model[iter][0]
551
status = device.eject()
555
msg = _("Ejecting <b>%s</b> failed.") % device['name']
557
msg += "\n\n%s" % status
558
qltk.ErrorMessage(self, _("Unable to eject device"), msg).run()
561
browsers = [MediaDevices]
563
print_w("Couldn't connect to HAL, disabling Media Devices browser.")