1
# -*- coding: utf-8 -*-
2
# Copyright 2004-2005 Joe Wreschnig, Michael Urman, Iñigo Serna
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: quodlibet.py 4046 2007-04-29 18:07:23Z piman $
28
from formats.remote import RemoteFile
29
from library import library, librarian
30
from parse import Query
31
from qltk.browser import LibraryBrowser
32
from qltk.chooser import FolderChooser, FileChooser
33
from qltk.controls import PlayControls
34
from qltk.cover import CoverImage
35
from qltk.getstring import GetStringDialog
36
from qltk.info import SongInfo
37
from qltk.information import Information
38
from qltk.mmkeys import MmKeys
39
from qltk.msg import ErrorMessage
40
from qltk.playorder import PlayOrder
41
from qltk.pluginwin import PluginWindow
42
from qltk.properties import SongProperties
43
from qltk.prefs import PreferencesWindow
44
from qltk.queue import QueueExpander
45
from qltk.songlist import SongList, PlaylistMux
46
from qltk.x import RPaned
47
from util import copool
48
from util.uri import URI
50
class MainSongList(SongList):
51
# The SongList that represents the current playlist.
53
class CurrentColumn(gtk.TreeViewColumn):
54
# Displays the current song indicator, either a play or pause icon.
56
_render = gtk.CellRendererPixbuf()
57
_render.set_property('xalign', 0.5)
58
header_name = "~current"
60
def _cdf(self, column, cell, model, iter,
61
pixbuf=(gtk.STOCK_MEDIA_PLAY, gtk.STOCK_MEDIA_PAUSE)):
63
if model.get_path(iter) == model.current_path:
65
stock = pixbuf[player.playlist.paused]
67
stock = gtk.STOCK_MEDIA_STOP
68
elif model[iter][0].get("~errors"):
69
stock = gtk.STOCK_DIALOG_ERROR
70
elif model.get_path(iter) != model.current_path:
72
cell.set_property('stock-id', stock)
73
except AttributeError: pass
76
super(MainSongList.CurrentColumn, self).__init__("", self._render)
77
self.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
78
self.set_fixed_width(24)
79
self.set_cell_data_func(self._render, self._cdf)
80
self.header_name = "~current"
82
def __init__(self, library, player, visible):
83
super(MainSongList, self).__init__(library, player)
84
self.set_rules_hint(True)
85
s = library.librarian.connect_object('removed', map, player.remove)
86
self.connect_object('destroy', library.librarian.disconnect, s)
87
self.connect_object('row-activated', self.__select_song, player)
88
self.connect_object('notify::visible', self.__visibility, visible)
90
def __visibility(self, visible, event):
91
visible.set_active(self.get_property('visible'))
93
def __select_song(self, player, indices, col):
94
iter = self.model.get_iter(indices)
96
if player.song: player.paused = False
98
def set_sort_by(self, *args, **kwargs):
99
super(MainSongList, self).set_sort_by(*args, **kwargs)
100
tag, reverse = self.get_sort_by()
101
config.set('memory', 'sortby', "%d%s" % (int(reverse), tag))
103
class StatusBar(gtk.HBox):
105
super(StatusBar, self).__init__()
106
self.progress = gtk.ProgressBar()
107
self.count = gtk.Label(_("No time information"))
108
self.count.set_alignment(1.0, 0.5)
109
self.count.set_ellipsize(pango.ELLIPSIZE_START)
110
self.pack_start(self.count)
111
progress_label = gtk.Label()
112
progress_label.set_alignment(1.0, 0.5)
113
progress_label.set_ellipsize(pango.ELLIPSIZE_START)
114
# GtkProgressBar can't show text when pulsing. Proxy its set_text
115
# method to a label that can.
116
self.progress.set_text = progress_label.set_text
117
hb = gtk.HBox(spacing=12)
118
hb.pack_start(progress_label)
119
hb.pack_start(self.progress, expand=False)
120
pause = gtk.ToggleButton()
121
pause.add(gtk.image_new_from_stock(
122
gtk.STOCK_MEDIA_PAUSE, gtk.ICON_SIZE_MENU))
123
pause.connect('toggled', self.__pause)
124
hb.pack_start(pause, expand=False)
126
self.progress.connect('notify::visible', self.__toggle, pause, hb)
127
self.progress.connect_object(
128
'notify::fraction', lambda *args: args[0].set_active(False), pause)
131
def __pause(self, pause):
132
if pause.get_active():
133
copool.pause("library")
135
copool.resume("library")
137
def __toggle(self, bar, property, pause, hb):
138
if self.progress.props.visible:
140
pause.set_active(False)
146
class QuodLibetWindow(gtk.Window):
147
def __init__(self, library, player):
148
super(QuodLibetWindow, self).__init__()
149
self.last_dir = os.path.expanduser("~")
151
tips = qltk.Tooltips(self)
152
self.set_title("Quod Libet")
154
self.set_default_size(
155
*map(int, config.get('memory', 'size').split()))
158
# create main menubar, load/restore accelerator groups
159
self.__create_menu(tips, player)
160
self.add_accel_group(self.ui.get_accel_group())
162
accel_fn = os.path.join(const.USERDIR, "accels")
163
gtk.accel_map_load(accel_fn)
164
accelgroup = gtk.accel_groups_from_object(self)[0]
165
accelgroup.connect('accel-changed',
166
lambda *args: gtk.accel_map_save(accel_fn))
167
self.child.pack_start(self.ui.get_widget("/Menu"), expand=False)
169
self.__vbox = realvbox = gtk.VBox(spacing=6)
170
realvbox.set_border_width(6)
171
self.child.pack_start(realvbox)
173
# get the playlist up before other stuff
174
self.songlist = MainSongList(
175
library, player, self.ui.get_widget("/Menu/View/SongList"))
176
self.add_accel_group(self.songlist.accelerators)
177
self.songlist.connect_after(
178
'drag-data-received', self.__songlist_drag_data_recv)
179
self.qexpander = QueueExpander(
180
self.ui.get_widget("/Menu/View/Queue"), library, player)
181
self.playlist = PlaylistMux(
182
player, self.qexpander.model, self.songlist.model)
184
# song info (top part of window)
185
hbox = gtk.HBox(spacing=6)
188
t = PlayControls(player, library.librarian)
189
self.volume = t.volume
190
hbox.pack_start(t, expand=False, fill=False)
193
text = SongInfo(library.librarian, player)
194
hbox.pack_start(text)
197
self.image = CoverImage()
198
player.connect('song-started', self.image.set_song)
199
hbox.pack_start(self.image, expand=False)
201
realvbox.pack_start(hbox, expand=False)
204
align = gtk.Alignment(xscale=1, yscale=1)
205
align.set_padding(0, 6, 6, 6)
206
hbox = gtk.HBox(spacing=12)
207
hb = gtk.HBox(spacing=3)
208
label = gtk.Label(_("_Order:"))
209
label.set_size_request(-1, 28)
210
self.order = order = PlayOrder(self.songlist.model, player)
211
label.set_mnemonic_widget(order)
212
label.set_use_underline(True)
215
hbox.pack_start(hb, expand=False)
216
self.repeat = repeat = qltk.ccb.ConfigCheckButton(
217
_("_Repeat"), "settings", "repeat")
218
tips.set_tip(repeat, _("Restart the playlist when finished"))
219
hbox.pack_start(repeat, expand=False)
220
self.statusbar = StatusBar()
221
hbox.pack_start(self.statusbar)
223
self.child.pack_end(align, expand=False)
226
self.song_scroller = sw = gtk.ScrolledWindow()
227
sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
228
sw.set_shadow_type(gtk.SHADOW_IN)
229
sw.add(self.songlist)
231
self.songpane = gtk.VBox(spacing=6)
232
self.songpane.pack_start(self.song_scroller)
233
self.songpane.pack_start(self.qexpander, expand=False, fill=True)
234
self.songpane.show_all()
235
self.song_scroller.connect('notify::visible', self.__show_or)
236
self.qexpander.connect('notify::visible', self.__show_or)
238
sort = config.get('memory', 'sortby')
239
self.songlist.set_sort_by(None, sort[1:], order=int(sort[0]))
241
self.inter = gtk.VBox()
245
self.__keys = MmKeys(player)
247
self.child.show_all()
250
self, config.get("memory", "browser"), library, player)
251
self.browser.restore()
252
self.browser.activate()
253
self.showhide_playlist(self.ui.get_widget("/Menu/View/SongList"))
254
self.showhide_playqueue(self.ui.get_widget("/Menu/View/Queue"))
256
repeat.connect('toggled', self.__repeat, self.songlist.model)
257
repeat.set_active(config.getboolean('settings', 'repeat'))
259
self.connect('configure-event', QuodLibetWindow.__save_size)
260
self.connect('window-state-event', self.__window_state_changed)
261
self.__hidden_state = 0
263
self.songlist.connect('popup-menu', self.__songs_popup_menu)
264
self.songlist.connect('columns-changed', self.__cols_changed)
265
self.songlist.connect('columns-changed', self.__hide_headers)
266
self.songlist.get_selection().connect('changed', self.__set_time)
268
library.librarian.connect('removed', self.__set_time)
269
library.librarian.connect('added', self.__set_time)
270
library.librarian.connect_object('changed', self.__update_title, player)
271
player.connect('song-ended', self.__song_ended)
272
player.connect('song-started', self.__song_started)
273
player.connect('paused', self.__update_paused, True)
274
player.connect('unpaused', self.__update_paused, False)
276
targets = [("text/uri-list", 0, 1)]
278
gtk.DEST_DEFAULT_ALL, targets, gtk.gdk.ACTION_DEFAULT)
279
self.connect_object('drag-motion', QuodLibetWindow.__drag_motion, self)
280
self.connect_object('drag-leave', QuodLibetWindow.__drag_leave, self)
282
'drag-data-received', QuodLibetWindow.__drag_data_received, self)
284
self.resize(*map(int, config.get("memory", "size").split()))
285
self.__rebuild(None, False)
287
def __drag_motion(self, ctx, x, y, time):
288
# Don't accept drops from QL itself, since it offers text/uri-list.
289
if ctx.get_source_widget() is None:
290
self.drag_highlight()
294
def __drag_leave(self, ctx, time):
295
self.drag_unhighlight()
297
def __drag_data_received(self, ctx, x, y, sel, tid, etime):
298
if tid == 1: uris = sel.get_uris()
300
uri = sel.data.decode('utf16', 'replace').split('\n')[0]
301
uris = [uri.encode('ascii', 'replace')]
308
except ValueError: continue
311
loc = os.path.normpath(uri.filename)
312
if os.path.isdir(loc): dirs.append(loc)
314
loc = os.path.realpath(loc)
315
if loc not in library:
316
song = library.add_filename(loc)
317
if song: files.append(song)
318
elif gst.element_make_from_uri(gst.URI_SRC, uri, ''):
319
if uri not in library:
320
files.append(RemoteFile(uri))
321
library.add([files[-1]])
325
ctx.finish(not error, False, etime)
328
self, _("Unable to add songs"),
329
_("<b>%s</b> uses an unsupported protocol.") % uri).run()
332
copool.add(library.scan, dirs, self.__status.bar.progress,
335
def __songlist_drag_data_recv(self, view, *args):
336
if callable(self.browser.reordered): self.browser.reordered(view)
337
self.songlist.set_sort_by(None, refresh=False)
339
def __window_state_changed(self, window, event):
340
assert window is self
341
self.__window_state = event.new_window_state
344
self.__hidden_state = self.__window_state
345
if self.__hidden_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
347
super(QuodLibetWindow, self).hide()
350
super(QuodLibetWindow, self).present()
351
if self.__hidden_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
355
super(QuodLibetWindow, self).show()
356
if self.__hidden_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
359
def __show_or(self, widget, prop):
360
ssv = self.song_scroller.get_property('visible')
361
qxv = self.qexpander.get_property('visible')
362
self.songpane.set_property('visible', ssv or qxv)
363
self.songpane.set_child_packing(
364
self.qexpander, expand=not ssv, fill=True, padding=0,
365
pack_type=gtk.PACK_START)
367
self.qexpander.set_expanded(True)
369
def __create_menu(self, tips, player):
370
ag = gtk.ActionGroup('QuodLibetWindowActions')
373
('Music', None, _("_Music")),
374
('AddFolders', gtk.STOCK_ADD, _('_Add a Folder...'),
375
"<control>O", None, self.open_chooser),
376
('AddFiles', gtk.STOCK_ADD, _('_Add a File...'),
377
None, None, self.open_chooser),
378
('AddLocation', gtk.STOCK_ADD, _('_Add a Location...'),
379
None, None, self.open_location),
380
('BrowseLibrary', gtk.STOCK_FIND, _('_Browse Library'), ""),
381
("Preferences", gtk.STOCK_PREFERENCES, None, None, None,
383
("Plugins", stock.PLUGINS, None, None, None,
385
("Quit", gtk.STOCK_QUIT, None, None, None, gtk.main_quit),
386
('Filters', None, _("_Filters")),
388
("NotPlayedDay", gtk.STOCK_FIND, _("Not Played To_day"),
389
"", None, self.lastplayed_day),
390
("NotPlayedWeek", gtk.STOCK_FIND, _("Not Played in a _Week"),
391
"", None, self.lastplayed_week),
392
("NotPlayedMonth", gtk.STOCK_FIND, _("Not Played in a _Month"),
393
"", None, self.lastplayed_month),
394
("NotPlayedEver", gtk.STOCK_FIND, _("_Never Played"),
395
"", None, self.lastplayed_never),
396
("Top", gtk.STOCK_GO_UP, _("_Top 40"), "", None, self.__top40),
397
("Bottom", gtk.STOCK_GO_DOWN,_("B_ottom 40"), "",
398
None, self.__bottom40),
399
("Control", None, _("_Control")),
400
("EditTags", stock.EDIT_TAGS, None, "", None,
401
self.__current_song_prop),
402
("Information", gtk.STOCK_INFO, None, None, None,
403
self.__current_song_info),
405
("Jump", gtk.STOCK_JUMP_TO, _("_Jump to Playing Song"),
406
"<control>J", None, self.__jump_to_current),
408
("View", None, _("_View")),
409
("Help", None, _("_Help")),
412
actions.append(("Previous", gtk.STOCK_MEDIA_PREVIOUS, None,
413
"<control>comma", None, self.__previous_song))
415
actions.append(("PlayPause", gtk.STOCK_MEDIA_PLAY, None,
416
"<control>space", None, self.__play_pause))
418
actions.append(("Next", gtk.STOCK_MEDIA_NEXT, None,
419
"<control>period", None, self.__next_song))
421
ag.add_actions(actions)
423
act = gtk.Action("About", None, None, gtk.STOCK_ABOUT)
424
act.connect_object('activate', qltk.about.show, self, player)
425
ag.add_action_with_accel(act, None)
428
"RefreshLibrary", _("Re_fresh Library"), None, gtk.STOCK_REFRESH)
429
act.connect('activate', self.__rebuild, False)
430
ag.add_action_with_accel(act, None)
432
"ReloadLibrary", _("Re_load Library"), None, gtk.STOCK_REFRESH)
433
act.connect('activate', self.__rebuild, True)
434
ag.add_action_with_accel(act, None)
437
("genre", _("Filter on _Genre")),
438
("artist", _("Filter on _Artist")),
439
("album", _("Filter on Al_bum"))]:
441
"Filter%s" % util.capitalize(tag_), lab, None, gtk.STOCK_INDEX)
442
act.connect_object('activate', self.__filter_on, tag_, None, player)
443
ag.add_action_with_accel(act, None)
445
for (tag_, accel, label) in [
446
("genre", "G", _("Random _Genre")),
447
("artist", "T", _("Random _Artist")),
448
("album", "M", _("Random Al_bum"))]:
449
act = gtk.Action("Random%s" % util.capitalize(tag_), label,
450
None, gtk.STOCK_DIALOG_QUESTION)
451
act.connect('activate', self.__random, tag_)
452
ag.add_action_with_accel(act, "<control>" + accel)
454
ag.add_toggle_actions([
455
("SongList", None, _("Song _List"), None, None,
456
self.showhide_playlist,
457
config.getboolean("memory", "songlist"))])
459
ag.add_toggle_actions([
460
("Queue", None, _("_Queue"), None, None,
461
self.showhide_playqueue,
462
config.getboolean("memory", "queue"))])
465
for i, Kind in enumerate(browsers.browsers):
466
action = "View" + Kind.__name__
467
label = Kind.accelerated_name
468
view_actions.append((action, None, label, None, None, i))
469
current = browsers.index(config.get("memory", "browser"))
470
ag.add_radio_actions(
471
view_actions, current, self.select_browser, (library, player))
473
for Kind in browsers.browsers:
474
if not Kind.in_menu: continue
475
action = "Browser" + Kind.__name__
476
label = Kind.accelerated_name
477
act = gtk.Action(action, label, None, None)
478
act.connect_object('activate', LibraryBrowser, Kind, library)
479
ag.add_action_with_accel(act, None)
481
self.ui = gtk.UIManager()
482
self.ui.insert_action_group(ag, -1)
483
menustr = const.MENU%(browsers.BrowseLibrary(), browsers.ViewBrowser())
484
self.ui.add_ui_from_string(menustr)
486
# Cute. So. UIManager lets you attach tooltips, but when they're
487
# for menu items, they just get ignored. So here I get to actually
490
self.ui.get_widget("/Menu/Music/RefreshLibrary"),
491
_("Check for changes in your library"))
493
self.ui.get_widget("/Menu/Music/ReloadLibrary"),
494
_("Reload all songs in your library (this can take a long time)"))
496
self.ui.get_widget("/Menu/Filters/Top"),
497
_("The 40 songs you've played most (more than 40 may "
498
"be chosen if there are ties)"))
500
self.ui.get_widget("/Menu/Filters/Bottom"),
501
_("The 40 songs you've played least (more than 40 may "
502
"be chosen if there are ties)"))
504
def __browser_configure(self, paned, event, browser):
505
if paned.get_property('position-set'):
506
key = "%s_pos" % browser.__class__.__name__
507
config.set("browsers", key, str(paned.get_relative()))
509
def select_browser(self, activator, current, library, player):
510
if isinstance(current, gtk.RadioAction):
511
current = current.get_current_value()
512
Browser = browsers.get(current)
513
config.set("memory", "browser", Browser.__name__)
515
container = self.browser.__container
516
self.browser.unpack(container, self.songpane)
517
if self.browser.accelerators:
518
self.remove_accel_group(self.browser.accelerators)
520
self.browser.destroy()
521
self.browser = Browser(library, player)
522
self.browser.connect('songs-selected', self.__browser_cb)
523
if self.browser.reordered:
524
self.songlist.enable_drop()
525
elif self.browser.dropped:
526
self.songlist.enable_drop(False)
527
else: self.songlist.disable_drop()
528
if self.browser.accelerators:
529
self.add_accel_group(self.browser.accelerators)
531
container = self.browser.__container = self.browser.pack(self.songpane)
532
# Save position if container is a RPaned
533
if isinstance(container, RPaned):
535
key = "%s_pos" % self.browser.__class__.__name__
536
val = config.getfloat("browsers", key)
539
'notify::position', self.__browser_configure, self.browser)
540
def set_size(paned, alloc, pos):
541
paned.set_relative(pos)
542
paned.disconnect(paned._size_sig)
543
# The signal disconnects itself! I hate GTK sizing.
545
sig = container.connect('size-allocate', set_size, val)
546
container._size_sig = sig
548
player.replaygain_profiles[0] = self.browser.replaygain_profiles
549
player.volume = player.volume
550
self.__vbox.pack_end(container)
553
self.__hide_headers()
554
self.__refresh_size()
556
def __update_paused(self, player, paused):
557
menu = self.ui.get_widget("/Menu/Control/PlayPause")
558
if paused: key = gtk.STOCK_MEDIA_PLAY
559
else: key = gtk.STOCK_MEDIA_PAUSE
560
text = gtk.stock_lookup(key)[1]
561
menu.get_image().set_from_stock(key, gtk.ICON_SIZE_MENU)
562
menu.child.set_text(text)
563
menu.child.set_use_underline(True)
565
def __song_ended(self, player, song, stopped):
566
if song is None: return
567
if not self.browser.dynamic(song):
569
iter = self.songlist.model.find(song)
571
self.songlist.model.remove(iter)
574
def __update_title(self, player, songs):
577
self.set_title("Quod Libet - " + song.comma("~title~version"))
578
else: self.set_title("Quod Libet")
580
def __song_started(self, player, song):
582
self.__update_title(player, [song])
584
for wid in ["Jump", "Next", "EditTags", "Information"]:
585
self.ui.get_widget('/Menu/Control/'+wid).set_sensitive(bool(song))
586
for wid in ["FilterAlbum", "FilterArtist", "FilterGenre"]:
587
self.ui.get_widget('/Menu/Filters/'+wid).set_sensitive(bool(song))
589
for h in ['genre', 'artist', 'album']:
591
"/Menu/Filters/Filter%s" % h.capitalize()).set_sensitive(
593
if song and config.getboolean("settings", "jump"):
594
self.__jump_to_current(False)
596
def __save_size(self, event):
597
config.set("memory", "size", "%d %d" % (event.width, event.height))
599
def __refresh_size(self):
600
if (not self.browser.expand and
601
not self.songpane.get_property('visible')):
602
width, height = self.get_size()
603
height = self.size_request()[1]
604
self.resize(width, height)
605
self.set_geometry_hints(None, max_height=height, max_width=32000)
607
self.set_geometry_hints(None, max_height=-1, max_width=-1)
609
def showhide_playlist(self, toggle):
610
self.song_scroller.set_property('visible', toggle.get_active())
611
config.set("memory", "songlist", str(toggle.get_active()))
612
self.__refresh_size()
614
def showhide_playqueue(self, toggle):
615
self.qexpander.set_property('visible', toggle.get_active())
616
self.__refresh_size()
618
def __play_pause(self, *args):
619
if player.playlist.song is None:
620
player.playlist.reset()
621
else: player.playlist.paused ^= True
623
def __jump_to_current(self, explicit):
624
if player.playlist.song is None: return
625
elif player.playlist.song == self.songlist.model.current:
626
path = self.songlist.model.current_path
627
self.songlist.scroll_to_cell(
628
path[0], use_align=True, row_align=0.5)
630
iter = self.songlist.model.current_iter
631
selection = self.songlist.get_selection()
632
selection.unselect_all()
633
selection.select_path(path)
634
if explicit: self.browser.scroll(player.playlist.song)
636
def __next_song(self, *args): player.playlist.next()
637
def __previous_song(self, *args): player.playlist.previous()
639
def __repeat(self, button, model):
640
model.repeat = button.get_active()
642
def __random(self, item, key):
643
if self.browser.can_filter(key):
644
values = self.browser.list(key)
646
value = random.choice(values)
647
self.browser.filter(key, [value])
649
def lastplayed_day(self, menuitem):
650
self.__make_query("#(lastplayed > today)")
651
def lastplayed_week(self, menuitem):
652
self.__make_query("#(lastplayed > 7 days ago)")
653
def lastplayed_month(self, menuitem):
654
self.__make_query("#(lastplayed > 30 days ago)")
655
def lastplayed_never(self, menuitem):
656
self.__make_query("#(playcount = 0)")
658
def __top40(self, menuitem):
659
songs = [song["~#playcount"] for song in library]
660
if len(songs) == 0: return
663
self.__make_query("#(playcount > %d)" % (songs[0] - 1))
665
self.__make_query("#(playcount > %d)" % (songs[-40] - 1))
667
def __bottom40(self, menuitem):
668
songs = [song["~#playcount"] for song in library]
669
if len(songs) == 0: return
672
self.__make_query("#(playcount < %d)" % (songs[0] + 1))
674
self.__make_query("#(playcount < %d)" % (songs[-40] + 1))
676
def __rebuild(self, activator, force):
677
paths = config.get("settings", "scan").split(":")
678
copool.add(library.rebuild, paths, self.statusbar.progress, force,
681
# Set up the preferences window.
682
def __preferences(self, activator):
683
PreferencesWindow(self)
685
def __plugins(self, activator):
688
def open_location(self, action):
689
name = GetStringDialog(self, _("Add a Location"),
690
_("Enter the location of an audio file:"),
691
okbutton=gtk.STOCK_ADD).run()
693
if not gst.uri_is_valid(name):
695
self, _("Unable to add location"),
696
_("<b>%s</b> is not a valid location.") %(
697
util.escape(name))).run()
698
elif not gst.element_make_from_uri(gst.URI_SRC, name, ""):
700
self, _("Unable to add location"),
701
_("<b>%s</b> uses an unsupported protocol.") %(
702
util.escape(name))).run()
704
if name not in library:
705
song = library.add([RemoteFile(name)])
707
def open_chooser(self, action):
708
if not os.path.exists(self.last_dir):
709
self.last_dir = const.HOME
711
if action.get_name() == "AddFolders":
712
chooser = FolderChooser(self, _("Add Music"), self.last_dir)
713
cb = gtk.CheckButton(_("Watch this folder for new songs"))
714
cb.set_active(not config.get("settings", "scan"))
716
chooser.set_extra_widget(cb)
718
chooser = FileChooser(
719
self, _("Add Music"), formats.filter, self.last_dir)
725
if action.get_name() == "AddFolders":
726
self.last_dir = fns[0]
727
copool.add(library.scan, fns, self.statusbar.progress,
731
self.last_dir = os.path.basename(fns[0])
732
for filename in map(os.path.realpath, fns):
733
if filename in library: continue
734
song = library.add_filename(filename)
735
if song: added.append(song)
737
from traceback import format_exception_only as feo
738
tb = feo(sys.last_type, sys.last_value)
739
msg = _("%s could not be added to your library.\n\n")
740
msg %= util.escape(util.fsdecode(
741
os.path.basename(filename)))
742
msg += util.escape("".join(tb).decode(
743
const.ENCODING, "replace"))
744
d = ErrorMessage(self, _("Unable to add song"), msg)
745
d.label.set_selectable(True)
749
self.browser.activate()
751
if cb and cb.get_active():
752
dirs = config.get("settings", "scan").split(":")
754
if fn not in dirs: dirs.append(fn)
755
dirs = ":".join(dirs)
756
config.set("settings", "scan", dirs)
758
def __songs_popup_menu(self, songlist):
759
path, col = songlist.get_cursor()
760
header = col.header_name
761
menu = self.songlist.Menu(header, self.browser, library)
763
return self.songlist.popup_menu(menu, 0,
764
gtk.get_current_event_time())
766
def __current_song_prop(self, *args):
767
song = player.playlist.song
768
if song: SongProperties(librarian, [song])
770
def __current_song_info(self, *args):
771
song = player.playlist.song
772
if song: Information(librarian, [song])
774
def __hide_menus(self):
775
menus = {'genre': ["/Menu/Filters/FilterGenre",
776
"/Menu/Filters/RandomGenre"],
777
'artist': ["/Menu/Filters/FilterArtist",
778
"/Menu/Filters/RandomArtist"],
779
'album': ["/Menu/Filters/FilterAlbum",
780
"/Menu/Filters/RandomAlbum"],
781
None: ["/Menu/Filters/NotPlayedDay",
782
"/Menu/Filters/NotPlayedWeek",
783
"/Menu/Filters/NotPlayedMonth",
784
"/Menu/Filters/NotPlayedEver",
786
"/Menu/Filters/Bottom"]}
787
for key, widgets in menus.items():
788
c = self.browser.can_filter(key)
789
for widget in widgets:
790
self.ui.get_widget(widget).set_property('visible', c)
792
def __browser_cb(self, browser, songs, sorted):
793
if browser.background:
794
try: bg = config.get("browsers", "background").decode('utf-8')
795
except UnicodeError: bg = ""
797
try: search = Query(bg, SongList.star).search
798
except Query.error: pass
799
else: songs = filter(search, songs)
801
self.__set_time(songs=songs)
802
self.songlist.set_songs(songs, sorted)
804
def __filter_on(self, header, songs, player):
805
if not self.browser or not self.browser.can_filter(header):
808
if player.song: songs = [player.song]
812
if header.startswith("~#"):
813
values.update([song(header, 0) for song in songs])
815
for song in songs: values.update(song.list(header))
816
self.browser.filter(header, list(values))
818
def __hide_headers(self, activator=None):
819
for column in self.songlist.get_columns():
820
if self.browser.headers is None:
821
column.set_visible(True)
823
for tag in util.tagsplit(column.header_name):
824
if tag in self.browser.headers:
825
column.set_visible(True)
827
else: column.set_visible(False)
829
def __cols_changed(self, songlist):
830
headers = [col.header_name for col in songlist.get_columns()]
831
try: headers.remove('~current')
832
except ValueError: pass
833
if len(headers) == len(config.get("settings", "headers").split()):
834
# Not an addition or removal (handled separately)
835
config.set("settings", "headers", " ".join(headers))
836
SongList.headers = headers
838
def __make_query(self, query):
839
if self.browser.can_filter(None):
840
self.browser.set_text(query.encode('utf-8'))
841
self.browser.activate()
843
def __set_time(self, *args, **kwargs):
844
songs = kwargs.get("songs") or self.songlist.get_selected_songs()
845
if "songs" not in kwargs and len(songs) <= 1:
846
songs = self.songlist.get_songs()
848
length = sum([song["~#length"] for song in songs])
849
t = self.browser.statusbar(i) % {
850
'count': i, 'time': util.format_time_long(length)}
851
self.statusbar.count.set_text(t)