~ubuntu-branches/debian/wheezy/quodlibet/wheezy

« back to all changes in this revision

Viewing changes to qltk/quodlibet.py

  • Committer: Bazaar Package Importer
  • Author(s): Luca Falavigna
  • Date: 2009-01-30 23:55:34 UTC
  • mto: (18.1.1 squeeze) (2.1.9 sid)
  • mto: This revision was merged to the branch mainline in revision 23.
  • Revision ID: james.westby@ubuntu.com-20090130235534-45857nfsgobw4apc
Tags: upstream-2.0
ImportĀ upstreamĀ versionĀ 2.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
# Copyright 2004-2005 Joe Wreschnig, Michael Urman, IƱigo Serna
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 version 2 as
6
 
# published by the Free Software Foundation
7
 
#
8
 
# $Id: quodlibet.py 4046 2007-04-29 18:07:23Z piman $
9
 
 
10
 
import os
11
 
import random
12
 
import sys
13
 
 
14
 
import gst
15
 
import gtk
16
 
import pango
17
 
 
18
 
import browsers
19
 
import config
20
 
import const
21
 
import formats
22
 
import player
23
 
import qltk
24
 
import qltk.about
25
 
import stock
26
 
import util
27
 
 
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
49
 
 
50
 
class MainSongList(SongList):
51
 
    # The SongList that represents the current playlist.
52
 
 
53
 
    class CurrentColumn(gtk.TreeViewColumn):
54
 
        # Displays the current song indicator, either a play or pause icon.
55
 
    
56
 
        _render = gtk.CellRendererPixbuf()
57
 
        _render.set_property('xalign', 0.5)
58
 
        header_name = "~current"
59
 
 
60
 
        def _cdf(self, column, cell, model, iter,
61
 
                 pixbuf=(gtk.STOCK_MEDIA_PLAY, gtk.STOCK_MEDIA_PAUSE)):
62
 
            try:
63
 
                if model.get_path(iter) == model.current_path:
64
 
                    if model.sourced:
65
 
                        stock = pixbuf[player.playlist.paused]
66
 
                    else:
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:
71
 
                    stock = ''
72
 
                cell.set_property('stock-id', stock)
73
 
            except AttributeError: pass
74
 
 
75
 
        def __init__(self):
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"
81
 
 
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)
89
 
 
90
 
    def __visibility(self, visible, event):
91
 
        visible.set_active(self.get_property('visible'))
92
 
 
93
 
    def __select_song(self, player, indices, col):
94
 
        iter = self.model.get_iter(indices)
95
 
        player.go_to(iter)
96
 
        if player.song: player.paused = False
97
 
 
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))
102
 
 
103
 
class StatusBar(gtk.HBox):
104
 
    def __init__(self):
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)
125
 
        self.pack_start(hb)
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)
129
 
        self.count.show()
130
 
 
131
 
    def __pause(self, pause):
132
 
        if pause.get_active():
133
 
            copool.pause("library")
134
 
        else:
135
 
            copool.resume("library")
136
 
 
137
 
    def __toggle(self, bar, property, pause, hb):
138
 
        if self.progress.props.visible:
139
 
            self.count.hide()
140
 
            pause.set_active(False)
141
 
            hb.show_all()
142
 
        else:
143
 
            self.count.show()
144
 
            hb.hide()
145
 
 
146
 
class QuodLibetWindow(gtk.Window):
147
 
    def __init__(self, library, player):
148
 
        super(QuodLibetWindow, self).__init__()
149
 
        self.last_dir = os.path.expanduser("~")
150
 
 
151
 
        tips = qltk.Tooltips(self)
152
 
        self.set_title("Quod Libet")
153
 
 
154
 
        self.set_default_size(
155
 
            *map(int, config.get('memory', 'size').split()))
156
 
        self.add(gtk.VBox())
157
 
 
158
 
        # create main menubar, load/restore accelerator groups
159
 
        self.__create_menu(tips, player)
160
 
        self.add_accel_group(self.ui.get_accel_group())
161
 
 
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)
168
 
 
169
 
        self.__vbox = realvbox = gtk.VBox(spacing=6)
170
 
        realvbox.set_border_width(6)
171
 
        self.child.pack_start(realvbox)
172
 
 
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)
183
 
 
184
 
        # song info (top part of window)
185
 
        hbox = gtk.HBox(spacing=6)
186
 
 
187
 
        # play controls
188
 
        t = PlayControls(player, library.librarian)
189
 
        self.volume = t.volume
190
 
        hbox.pack_start(t, expand=False, fill=False)
191
 
 
192
 
        # song text
193
 
        text = SongInfo(library.librarian, player)
194
 
        hbox.pack_start(text)
195
 
 
196
 
        # cover image
197
 
        self.image = CoverImage()
198
 
        player.connect('song-started', self.image.set_song)
199
 
        hbox.pack_start(self.image, expand=False)
200
 
 
201
 
        realvbox.pack_start(hbox, expand=False)
202
 
 
203
 
        # status area
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)
213
 
        hb.pack_start(label)
214
 
        hb.pack_start(order)
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)
222
 
        align.add(hbox)
223
 
        self.child.pack_end(align, expand=False)
224
 
 
225
 
        # song list
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)
230
 
 
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)
237
 
 
238
 
        sort = config.get('memory', 'sortby')
239
 
        self.songlist.set_sort_by(None, sort[1:], order=int(sort[0]))
240
 
 
241
 
        self.inter = gtk.VBox()
242
 
 
243
 
        self.browser = None
244
 
 
245
 
        self.__keys = MmKeys(player)
246
 
 
247
 
        self.child.show_all()
248
 
        sw.show_all()
249
 
        self.select_browser(
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"))
255
 
 
256
 
        repeat.connect('toggled', self.__repeat, self.songlist.model)
257
 
        repeat.set_active(config.getboolean('settings', 'repeat'))
258
 
 
259
 
        self.connect('configure-event', QuodLibetWindow.__save_size)
260
 
        self.connect('window-state-event', self.__window_state_changed)
261
 
        self.__hidden_state = 0
262
 
 
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)
267
 
 
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)
275
 
 
276
 
        targets = [("text/uri-list", 0, 1)]
277
 
        self.drag_dest_set(
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)
281
 
        self.connect_object(
282
 
            'drag-data-received', QuodLibetWindow.__drag_data_received, self)
283
 
 
284
 
        self.resize(*map(int, config.get("memory", "size").split()))
285
 
        self.__rebuild(None, False)
286
 
 
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()
291
 
            return True
292
 
        else: return False
293
 
 
294
 
    def __drag_leave(self, ctx, time):
295
 
        self.drag_unhighlight()
296
 
 
297
 
    def __drag_data_received(self, ctx, x, y, sel, tid, etime):
298
 
        if tid == 1: uris = sel.get_uris()
299
 
        if tid == 2:
300
 
            uri = sel.data.decode('utf16', 'replace').split('\n')[0]
301
 
            uris = [uri.encode('ascii', 'replace')]
302
 
 
303
 
        dirs = []
304
 
        files = []
305
 
        error = False
306
 
        for uri in uris:
307
 
            try: uri = URI(uri)
308
 
            except ValueError: continue
309
 
 
310
 
            if uri.is_filename:
311
 
                loc = os.path.normpath(uri.filename)
312
 
                if os.path.isdir(loc): dirs.append(loc)
313
 
                else:
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]])
322
 
            else:
323
 
                error = True
324
 
                break
325
 
        ctx.finish(not error, False, etime)
326
 
        if error:
327
 
            ErrorMessage(
328
 
                self, _("Unable to add songs"),
329
 
                _("<b>%s</b> uses an unsupported protocol.") % uri).run()
330
 
        else:
331
 
            if dirs:
332
 
                copool.add(library.scan, dirs, self.__status.bar.progress,
333
 
                           funcid="library")
334
 
 
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)
338
 
 
339
 
    def __window_state_changed(self, window, event):
340
 
        assert window is self
341
 
        self.__window_state = event.new_window_state
342
 
 
343
 
    def hide(self):
344
 
        self.__hidden_state = self.__window_state
345
 
        if self.__hidden_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
346
 
            self.unmaximize()
347
 
        super(QuodLibetWindow, self).hide()
348
 
 
349
 
    def present(self):
350
 
        super(QuodLibetWindow, self).present()
351
 
        if self.__hidden_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
352
 
            self.maximize()
353
 
 
354
 
    def show(self):
355
 
        super(QuodLibetWindow, self).show()
356
 
        if self.__hidden_state & gtk.gdk.WINDOW_STATE_MAXIMIZED:
357
 
            self.maximize()
358
 
 
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)
366
 
        if not ssv:
367
 
            self.qexpander.set_expanded(True)
368
 
 
369
 
    def __create_menu(self, tips, player):
370
 
        ag = gtk.ActionGroup('QuodLibetWindowActions')
371
 
 
372
 
        actions = [
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,
382
 
             self.__preferences),
383
 
            ("Plugins", stock.PLUGINS, None, None, None,
384
 
             self.__plugins),
385
 
            ("Quit", gtk.STOCK_QUIT, None, None, None, gtk.main_quit),
386
 
            ('Filters', None, _("_Filters")),
387
 
 
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),
404
 
 
405
 
            ("Jump", gtk.STOCK_JUMP_TO, _("_Jump to Playing Song"),
406
 
             "<control>J", None, self.__jump_to_current),
407
 
 
408
 
            ("View", None, _("_View")),
409
 
            ("Help", None, _("_Help")),
410
 
            ]
411
 
 
412
 
        actions.append(("Previous", gtk.STOCK_MEDIA_PREVIOUS, None,
413
 
                        "<control>comma", None, self.__previous_song))
414
 
 
415
 
        actions.append(("PlayPause", gtk.STOCK_MEDIA_PLAY, None,
416
 
                        "<control>space", None, self.__play_pause))
417
 
 
418
 
        actions.append(("Next", gtk.STOCK_MEDIA_NEXT, None,
419
 
                        "<control>period", None, self.__next_song))
420
 
 
421
 
        ag.add_actions(actions)
422
 
 
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)
426
 
 
427
 
        act = gtk.Action(
428
 
            "RefreshLibrary", _("Re_fresh Library"), None, gtk.STOCK_REFRESH)
429
 
        act.connect('activate', self.__rebuild, False)
430
 
        ag.add_action_with_accel(act, None)
431
 
        act = gtk.Action(
432
 
            "ReloadLibrary", _("Re_load Library"), None, gtk.STOCK_REFRESH)
433
 
        act.connect('activate', self.__rebuild, True)
434
 
        ag.add_action_with_accel(act, None)
435
 
 
436
 
        for tag_, lab in [
437
 
            ("genre", _("Filter on _Genre")),
438
 
            ("artist", _("Filter on _Artist")),
439
 
            ("album", _("Filter on Al_bum"))]:
440
 
            act = gtk.Action(
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)
444
 
 
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)
453
 
 
454
 
        ag.add_toggle_actions([
455
 
            ("SongList", None, _("Song _List"), None, None,
456
 
             self.showhide_playlist,
457
 
             config.getboolean("memory", "songlist"))])
458
 
 
459
 
        ag.add_toggle_actions([
460
 
            ("Queue", None, _("_Queue"), None, None,
461
 
             self.showhide_playqueue,
462
 
             config.getboolean("memory", "queue"))])
463
 
 
464
 
        view_actions = []
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))
472
 
 
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)
480
 
 
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)
485
 
 
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
488
 
        # attach them.
489
 
        tips.set_tip(
490
 
            self.ui.get_widget("/Menu/Music/RefreshLibrary"),
491
 
            _("Check for changes in your library"))
492
 
        tips.set_tip(
493
 
            self.ui.get_widget("/Menu/Music/ReloadLibrary"),
494
 
            _("Reload all songs in your library (this can take a long time)"))
495
 
        tips.set_tip(
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)"))
499
 
        tips.set_tip(
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)"))
503
 
 
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()))
508
 
 
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__)
514
 
        if self.browser:
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)
519
 
            container.destroy()
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)
530
 
 
531
 
        container = self.browser.__container = self.browser.pack(self.songpane)
532
 
        # Save position if container is a RPaned
533
 
        if isinstance(container, RPaned):
534
 
            try:
535
 
                key = "%s_pos" % self.browser.__class__.__name__
536
 
                val = config.getfloat("browsers", key)
537
 
            except: val = 0.4
538
 
            container.connect(
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.
544
 
                del(paned._size_sig)
545
 
            sig = container.connect('size-allocate', set_size, val)
546
 
            container._size_sig = sig
547
 
 
548
 
        player.replaygain_profiles[0] = self.browser.replaygain_profiles
549
 
        player.volume = player.volume
550
 
        self.__vbox.pack_end(container)
551
 
        container.show()
552
 
        self.__hide_menus()
553
 
        self.__hide_headers()
554
 
        self.__refresh_size()
555
 
 
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)
564
 
 
565
 
    def __song_ended(self, player, song, stopped):
566
 
        if song is None: return
567
 
        if not self.browser.dynamic(song):
568
 
            player.remove(song)
569
 
            iter = self.songlist.model.find(song)
570
 
            if iter:
571
 
                self.songlist.model.remove(iter)
572
 
                self.__set_time()
573
 
 
574
 
    def __update_title(self, player, songs):
575
 
        song = player.info
576
 
        if song:
577
 
            self.set_title("Quod Libet - " + song.comma("~title~version"))
578
 
        else: self.set_title("Quod Libet")
579
 
 
580
 
    def __song_started(self, player, song):
581
 
        if song is None:
582
 
            self.__update_title(player, [song])
583
 
 
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))
588
 
        if song:
589
 
            for h in ['genre', 'artist', 'album']:
590
 
                self.ui.get_widget(
591
 
                    "/Menu/Filters/Filter%s" % h.capitalize()).set_sensitive(
592
 
                    h in song)
593
 
        if song and config.getboolean("settings", "jump"):
594
 
            self.__jump_to_current(False)
595
 
 
596
 
    def __save_size(self, event):
597
 
        config.set("memory", "size", "%d %d" % (event.width, event.height))
598
 
 
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)
606
 
        else:
607
 
            self.set_geometry_hints(None, max_height=-1, max_width=-1)
608
 
 
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()
613
 
 
614
 
    def showhide_playqueue(self, toggle):
615
 
        self.qexpander.set_property('visible', toggle.get_active())
616
 
        self.__refresh_size()
617
 
 
618
 
    def __play_pause(self, *args):
619
 
        if player.playlist.song is None:
620
 
            player.playlist.reset()
621
 
        else: player.playlist.paused ^= True
622
 
 
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)
629
 
            if explicit:
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)
635
 
 
636
 
    def __next_song(self, *args): player.playlist.next()
637
 
    def __previous_song(self, *args): player.playlist.previous()
638
 
 
639
 
    def __repeat(self, button, model):
640
 
        model.repeat = button.get_active()
641
 
 
642
 
    def __random(self, item, key):
643
 
        if self.browser.can_filter(key):
644
 
            values = self.browser.list(key)
645
 
            if values:
646
 
                value = random.choice(values)
647
 
                self.browser.filter(key, [value])
648
 
 
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)")
657
 
 
658
 
    def __top40(self, menuitem):
659
 
        songs = [song["~#playcount"] for song in library]
660
 
        if len(songs) == 0: return
661
 
        songs.sort()
662
 
        if len(songs) < 40:
663
 
            self.__make_query("#(playcount > %d)" % (songs[0] - 1))
664
 
        else:
665
 
            self.__make_query("#(playcount > %d)" % (songs[-40] - 1))
666
 
 
667
 
    def __bottom40(self, menuitem):
668
 
        songs = [song["~#playcount"] for song in library]
669
 
        if len(songs) == 0: return
670
 
        songs.sort()
671
 
        if len(songs) < 40:
672
 
            self.__make_query("#(playcount < %d)" % (songs[0] + 1))
673
 
        else:
674
 
            self.__make_query("#(playcount < %d)" % (songs[-40] + 1))
675
 
 
676
 
    def __rebuild(self, activator, force):
677
 
        paths = config.get("settings", "scan").split(":")
678
 
        copool.add(library.rebuild, paths, self.statusbar.progress, force,
679
 
                   funcid="library")
680
 
 
681
 
    # Set up the preferences window.
682
 
    def __preferences(self, activator):
683
 
        PreferencesWindow(self)
684
 
 
685
 
    def __plugins(self, activator):
686
 
        PluginWindow(self)
687
 
 
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()
692
 
        if name:
693
 
            if not gst.uri_is_valid(name):
694
 
                ErrorMessage(
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, ""):
699
 
                ErrorMessage(
700
 
                    self, _("Unable to add location"),
701
 
                    _("<b>%s</b> uses an unsupported protocol.") %(
702
 
                    util.escape(name))).run()
703
 
            else:
704
 
                if name not in library:
705
 
                    song = library.add([RemoteFile(name)])
706
 
 
707
 
    def open_chooser(self, action):
708
 
        if not os.path.exists(self.last_dir):
709
 
            self.last_dir = const.HOME
710
 
 
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"))
715
 
            cb.show()
716
 
            chooser.set_extra_widget(cb)
717
 
        else:
718
 
            chooser = FileChooser(
719
 
                self, _("Add Music"), formats.filter, self.last_dir)
720
 
            cb = None
721
 
        
722
 
        fns = chooser.run()
723
 
        chooser.destroy()
724
 
        if fns:
725
 
            if action.get_name() == "AddFolders":
726
 
                self.last_dir = fns[0]
727
 
                copool.add(library.scan, fns, self.statusbar.progress,
728
 
                           funcid="library")
729
 
            else:
730
 
                added = []
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)
736
 
                    else:
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)
746
 
                        d.run()
747
 
                        continue
748
 
                if added:
749
 
                    self.browser.activate()
750
 
 
751
 
        if cb and cb.get_active():
752
 
            dirs = config.get("settings", "scan").split(":")
753
 
            for fn in fns:
754
 
                if fn not in dirs: dirs.append(fn)
755
 
            dirs = ":".join(dirs)
756
 
            config.set("settings", "scan", dirs)
757
 
 
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)
762
 
        if menu is not None:
763
 
            return self.songlist.popup_menu(menu, 0,
764
 
                    gtk.get_current_event_time())
765
 
 
766
 
    def __current_song_prop(self, *args):
767
 
        song = player.playlist.song
768
 
        if song: SongProperties(librarian, [song])
769
 
 
770
 
    def __current_song_info(self, *args):
771
 
        song = player.playlist.song
772
 
        if song: Information(librarian, [song])
773
 
 
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",
785
 
                        "/Menu/Filters/Top",
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)
791
 
 
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 = ""
796
 
            if bg:
797
 
                try: search = Query(bg, SongList.star).search
798
 
                except Query.error: pass
799
 
                else: songs = filter(search, songs)
800
 
 
801
 
        self.__set_time(songs=songs)
802
 
        self.songlist.set_songs(songs, sorted)
803
 
 
804
 
    def __filter_on(self, header, songs, player):
805
 
        if not self.browser or not self.browser.can_filter(header):
806
 
            return
807
 
        if songs is None:
808
 
            if player.song: songs = [player.song]
809
 
            else: return
810
 
 
811
 
        values = set()
812
 
        if header.startswith("~#"):
813
 
            values.update([song(header, 0) for song in songs])
814
 
        else:
815
 
            for song in songs: values.update(song.list(header))
816
 
        self.browser.filter(header, list(values))
817
 
 
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)
822
 
            else:
823
 
                for tag in util.tagsplit(column.header_name):
824
 
                    if tag in self.browser.headers:
825
 
                        column.set_visible(True)
826
 
                        break
827
 
                else: column.set_visible(False)
828
 
 
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
837
 
 
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()
842
 
 
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()
847
 
        i = len(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)