~ubuntu-branches/ubuntu/karmic/quodlibet/karmic

« back to all changes in this revision

Viewing changes to quodlibet/browsers/playlists.py

  • Committer: Bazaar Package Importer
  • Author(s): Luca Falavigna
  • Date: 2009-01-30 23:55:34 UTC
  • mfrom: (1.1.12 upstream)
  • Revision ID: james.westby@ubuntu.com-20090130235534-l4e72ulw0vqfo17w
Tags: 2.0-1ubuntu1
* Merge from Debian experimental (LP: #276856), remaining Ubuntu changes:
  + debian/patches/40-use-music-profile.patch:
    - Use the "Music and Movies" pipeline per default.
* Refresh the above patch for new upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
# Copyright 2005 Joe Wreschnig
 
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: playlists.py 4330 2008-09-14 03:19:26Z piman $
 
9
 
 
10
import os
 
11
import urllib
 
12
 
 
13
import gobject
 
14
import gtk
 
15
import pango
 
16
 
 
17
from quodlibet import config
 
18
from quodlibet import const
 
19
from quodlibet import formats
 
20
from quodlibet import qltk
 
21
from quodlibet import stock
 
22
from quodlibet import util
 
23
 
 
24
from tempfile import NamedTemporaryFile
 
25
 
 
26
from quodlibet.browsers._base import Browser
 
27
from quodlibet.formats._audio import AudioFile
 
28
from quodlibet.qltk.songsmenu import SongsMenu
 
29
from quodlibet.qltk.views import RCMHintedTreeView
 
30
from quodlibet.qltk.wlw import WaitLoadWindow
 
31
from quodlibet.util.uri import URI
 
32
 
 
33
PLAYLISTS = os.path.join(const.USERDIR, "playlists")
 
34
if not os.path.isdir(PLAYLISTS): util.mkdir(PLAYLISTS)
 
35
 
 
36
def ParseM3U(filename, library=None):
 
37
    plname = util.fsdecode(os.path.basename(
 
38
        os.path.splitext(filename)[0])).encode('utf-8')
 
39
    filenames = []
 
40
    for line in file(filename):
 
41
        line = line.strip()
 
42
        if line.startswith("#"): continue
 
43
        else: filenames.append(line)
 
44
    return __ParsePlaylist(plname, filename, filenames, library)
 
45
 
 
46
def ParsePLS(filename, name="", library=None):
 
47
    plname = util.fsdecode(os.path.basename(
 
48
        os.path.splitext(filename)[0])).encode('utf-8')
 
49
    filenames = []
 
50
    for line in file(filename):
 
51
        line = line.strip()
 
52
        if not line.lower().startswith("file"): continue
 
53
        else:
 
54
            try: line = line[line.index("=")+1:].strip()
 
55
            except ValueError: pass
 
56
            else: filenames.append(line)
 
57
    return __ParsePlaylist(plname, filename, filenames, library)
 
58
 
 
59
def __ParsePlaylist(name, plfilename, files, library):
 
60
    playlist = Playlist.new(name, library=library)
 
61
    songs = []
 
62
    win = WaitLoadWindow(
 
63
        None, len(files),
 
64
        _("Importing playlist.\n\n%(current)d/%(total)d songs added."))
 
65
    for i, filename in enumerate(files):
 
66
        try: uri = URI(filename)
 
67
        except ValueError:
 
68
            # Plain filename.
 
69
            filename = os.path.realpath(os.path.join(
 
70
                os.path.dirname(plfilename), filename))
 
71
            if library and filename in library:
 
72
                songs.append(library[filename])
 
73
            else:
 
74
                songs.append(formats.MusicFile(filename))
 
75
        else:
 
76
            if uri.scheme == "file":
 
77
                # URI-encoded local filename.
 
78
                filename = os.path.realpath(os.path.join(
 
79
                    os.path.dirname(plfilename), uri.filename))
 
80
                if library and filename in library:
 
81
                    songs.append(library[filename])
 
82
                else:
 
83
                    songs.append(formats.MusicFile(filename))
 
84
            else:
 
85
                # Who knows! Hand it off to GStreamer.
 
86
                songs.append(formats.remote.RemoteFile(uri))
 
87
        if win.step(): break
 
88
    win.destroy()
 
89
    playlist.extend(filter(None, songs))
 
90
    return playlist
 
91
 
 
92
class Playlist(list):
 
93
    quote = staticmethod(lambda text: urllib.quote(text, safe=""))
 
94
    unquote = staticmethod(urllib.unquote)
 
95
 
 
96
    def new(klass, base=_("New Playlist"), library={}):
 
97
        p = Playlist("", library=library)
 
98
        i = 0
 
99
        try: p.rename(base)
 
100
        except ValueError:
 
101
            while not p.name:
 
102
                i += 1
 
103
                try: p.rename("%s %d" % (base, i))
 
104
                except ValueError: pass
 
105
        return p
 
106
    new = classmethod(new)
 
107
 
 
108
    def fromsongs(klass, songs, library={}):
 
109
        if len(songs) == 1:
 
110
            title = songs[0].comma("title")
 
111
        else:
 
112
            title = ngettext(
 
113
                "%(title)s and %(count)d more",
 
114
                "%(title)s and %(count)d more",
 
115
                len(songs) - 1) % (
 
116
                {'title': songs[0].comma("title"), 'count': len(songs) - 1})
 
117
        playlist = klass.new(title, library=library)
 
118
        playlist.extend(songs)
 
119
        return playlist
 
120
    fromsongs = classmethod(fromsongs)
 
121
 
 
122
    def __init__(self, name, library=None):
 
123
        super(Playlist, self).__init__()
 
124
        if isinstance(name, unicode): name = name.encode('utf-8')
 
125
        self.name = name
 
126
        basename = self.quote(name)
 
127
        try:
 
128
            for line in file(os.path.join(PLAYLISTS, basename), "r"):
 
129
                line = line.rstrip()
 
130
                if line in library:
 
131
                    self.append(library[line])
 
132
                elif library and library.masked(line):
 
133
                    self.append(line)
 
134
        except IOError:
 
135
            if self.name: self.write()
 
136
 
 
137
    def rename(self, newname):
 
138
        if isinstance(newname, unicode): newname = newname.encode('utf-8')
 
139
        if newname == self.name: return
 
140
        elif os.path.exists(os.path.join(PLAYLISTS, self.quote(newname))):
 
141
            raise ValueError(
 
142
                _("A playlist named %s already exists.") % newname)
 
143
        else:
 
144
            try: os.unlink(os.path.join(PLAYLISTS, self.quote(self.name)))
 
145
            except EnvironmentError: pass
 
146
            self.name = newname
 
147
            self.write()
 
148
 
 
149
    def add_songs(self, filenames, library):
 
150
        changed = False
 
151
        for i in range(len(self)):
 
152
            if isinstance(self[i], basestring) and self[i] in filenames:
 
153
                self[i] = library[self[i]]
 
154
                changed = True
 
155
        return changed
 
156
 
 
157
    def remove_songs(self, songs, library):
 
158
        changed = False
 
159
        # TODO: document the "library.masked" business
 
160
        for song in songs:
 
161
            if library.masked(song("~filename")):
 
162
                while True:
 
163
                    try: self[self.index(song)] = song("~filename")
 
164
                    except ValueError: break
 
165
                    else: changed = True
 
166
            else:
 
167
                while song in self: self.remove(song)
 
168
                else: changed = True
 
169
        return changed
 
170
 
 
171
    def has_songs(self, songs):
 
172
        # TODO(rm): consider the "library.masked" business
 
173
        some, all = False, True
 
174
        for song in songs:
 
175
            found = song in self
 
176
            some = some or found
 
177
            all = all and found
 
178
            if some and not all:
 
179
                break
 
180
        return some, all
 
181
 
 
182
    def delete(self):
 
183
        del(self[:])
 
184
        try: os.unlink(os.path.join(PLAYLISTS, self.quote(self.name)))
 
185
        except EnvironmentError: pass
 
186
 
 
187
    def write(self):
 
188
        basename = self.quote(self.name)
 
189
        f = file(os.path.join(PLAYLISTS, basename), "w")
 
190
        for song in self:
 
191
            try: f.write(song("~filename") + "\n")
 
192
            except TypeError: f.write(song + "\n")
 
193
        f.close()
 
194
 
 
195
    def format(self):
 
196
        return "<b>%s</b>\n<small>%s (%s)</small>" % (
 
197
            util.escape(self.name),
 
198
            ngettext("%d song", "%d songs", len(self)) % len(self),
 
199
            util.format_time(sum([t.get("~#length") for t in self
 
200
                                  if isinstance(t, AudioFile)])))
 
201
 
 
202
    def __cmp__(self, other):
 
203
        try: return cmp(self.name, other.name)
 
204
        except AttributeError: return -1
 
205
 
 
206
class Menu(gtk.Menu):
 
207
    def __init__(self, songs):
 
208
        super(Menu, self).__init__()
 
209
        i = gtk.MenuItem(_("_New Playlist"))
 
210
        i.connect_object('activate', self.__add_to_playlist, None, songs)
 
211
        self.append(i)
 
212
        self.append(gtk.SeparatorMenuItem())
 
213
        self.set_size_request(int(i.size_request()[0] * 2), -1)
 
214
 
 
215
        for playlist in Playlists.playlists():
 
216
            name = playlist.name
 
217
            i = gtk.CheckMenuItem(name)
 
218
            some, all = playlist.has_songs(songs)
 
219
            i.set_active(some)
 
220
            i.set_inconsistent(some and not all)
 
221
            i.child.set_ellipsize(pango.ELLIPSIZE_END)
 
222
            i.connect_object(
 
223
                'activate', self.__add_to_playlist, playlist, songs)
 
224
            self.append(i)
 
225
 
 
226
    def __add_to_playlist(playlist, songs):
 
227
        if playlist is None:
 
228
            if len(songs) == 1:
 
229
                title = songs[0].comma("title")
 
230
            else:
 
231
                title = ngettext(
 
232
                    "%(title)s and %(count)d more",
 
233
                    "%(title)s and %(count)d more",
 
234
                    len(songs) - 1) % (
 
235
                    {'title': songs[0].comma("title"), 'count': len(songs) - 1})
 
236
            playlist = Playlist.new(title)
 
237
        playlist.extend(songs)
 
238
        Playlists.changed(playlist)
 
239
    __add_to_playlist = staticmethod(__add_to_playlist)
 
240
 
 
241
class Playlists(gtk.VBox, Browser):
 
242
    __gsignals__ = Browser.__gsignals__
 
243
    expand = qltk.RHPaned
 
244
 
 
245
    name = _("Playlists")
 
246
    accelerated_name = _("_Playlists")
 
247
    priority = 2
 
248
    replaygain_profiles = ["track"]
 
249
 
 
250
    def init(klass, library):
 
251
        model = klass.__lists.get_model()
 
252
        for playlist in os.listdir(PLAYLISTS):
 
253
            try:
 
254
                playlist = Playlist(Playlist.unquote(playlist), library=library)
 
255
                model.append(row=[playlist])
 
256
            except EnvironmentError:
 
257
                pass
 
258
        library.connect('removed', klass.__removed)
 
259
        library.connect('added', klass.__added)
 
260
        library.connect('changed', klass.__changed)
 
261
    init = classmethod(init)
 
262
 
 
263
    def playlists(klass): return [row[0] for row in klass.__lists]
 
264
    playlists = classmethod(playlists)
 
265
 
 
266
    def changed(klass, playlist, refresh=True):
 
267
        model = klass.__lists
 
268
        for row in model:
 
269
            if row[0] is playlist:
 
270
                if refresh:
 
271
                    klass.__lists.row_changed(row.path, row.iter)
 
272
                playlist.write()
 
273
                break
 
274
        else:
 
275
            model.get_model().append(row=[playlist])
 
276
            playlist.write()
 
277
    changed = classmethod(changed)
 
278
 
 
279
    def __removed(klass, library, songs):
 
280
        for playlist in klass.playlists():
 
281
            if playlist.remove_songs(songs, library):
 
282
                Playlists.changed(playlist)
 
283
    __removed = classmethod(__removed)
 
284
 
 
285
    def __added(klass, library, songs):
 
286
        filenames = set([song("~filename") for song in songs])
 
287
        for playlist in klass.playlists():
 
288
            if playlist.add_songs(filenames, library):
 
289
                Playlists.changed(playlist)
 
290
    __added = classmethod(__added)
 
291
 
 
292
    def __changed(klass, library, songs):
 
293
        for playlist in klass.playlists():
 
294
            for song in songs:
 
295
                if song in playlist:
 
296
                    Playlists.changed(playlist, refresh=False)
 
297
                    break
 
298
    __changed = classmethod(__changed)
 
299
 
 
300
    def cell_data(col, render, model, iter):
 
301
        render.markup = model[iter][0].format()
 
302
        render.set_property('markup', render.markup)
 
303
    cell_data = staticmethod(cell_data)
 
304
 
 
305
    def Menu(self, songs, songlist, library):
 
306
        menu = super(Playlists, self).Menu(songs, songlist, library)
 
307
        model, rows = songlist.get_selection().get_selected_rows()
 
308
        iters = map(model.get_iter, rows)
 
309
        i = qltk.MenuItem(_("_Remove from Playlist"), gtk.STOCK_REMOVE)
 
310
        i.connect_object('activate', self.__remove, iters, model)
 
311
        i.set_sensitive(bool(self.__view.get_selection().get_selected()[1]))
 
312
        menu.preseparate()
 
313
        menu.prepend(i)
 
314
        return menu
 
315
 
 
316
    __lists = gtk.TreeModelSort(gtk.ListStore(object))
 
317
    __lists.set_default_sort_func(lambda m, a, b: cmp(m[a][0], m[b][0]))
 
318
 
 
319
    def __init__(self, library, player):
 
320
        super(Playlists, self).__init__(spacing=6)
 
321
        self.__main = bool(player)
 
322
        self.__view = view = RCMHintedTreeView()
 
323
        self.__view.set_enable_search(True)
 
324
        self.__view.set_search_column(0)
 
325
        self.__view.set_search_equal_func(
 
326
            lambda model, col, key, iter:
 
327
            not model[iter][col].name.lower().startswith(key.lower()))
 
328
        self.__render = render = gtk.CellRendererText()
 
329
        render.set_property('ellipsize', pango.ELLIPSIZE_END)
 
330
        render.connect('editing-started', self.__start_editing)
 
331
        render.connect('edited', self.__edited)
 
332
        col = gtk.TreeViewColumn("Playlists", render)
 
333
        col.set_cell_data_func(render, Playlists.cell_data)
 
334
        view.append_column(col)
 
335
        view.set_model(self.__lists)
 
336
        view.set_rules_hint(True)
 
337
        view.set_headers_visible(False)
 
338
        swin = gtk.ScrolledWindow()
 
339
        swin.set_shadow_type(gtk.SHADOW_IN)
 
340
        swin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
 
341
        swin.add(view)
 
342
        self.pack_start(swin)
 
343
 
 
344
        newpl = gtk.Button(stock=gtk.STOCK_NEW)
 
345
        newpl.connect('clicked', self.__new_playlist)
 
346
        importpl = qltk.Button(_("_Import"), gtk.STOCK_ADD)
 
347
        importpl.connect('clicked', self.__import, library)
 
348
        hb = gtk.HBox(spacing=6)
 
349
        hb.set_homogeneous(True)
 
350
        hb.pack_start(newpl)
 
351
        hb.pack_start(importpl)
 
352
        self.pack_start(hb, expand=False)
 
353
 
 
354
        view.connect('popup-menu', self.__popup_menu, library)
 
355
 
 
356
        targets = [("text/x-quodlibet-songs", gtk.TARGET_SAME_APP, 0),
 
357
                   ("text/uri-list", 0, 1),
 
358
                   ("text/x-moz-url", 0, 2)]
 
359
        view.drag_dest_set(gtk.DEST_DEFAULT_ALL, targets,
 
360
                           gtk.gdk.ACTION_COPY|gtk.gdk.ACTION_DEFAULT)
 
361
        view.connect('drag-data-received', self.__drag_data_received, library)
 
362
        view.connect('drag-motion', self.__drag_motion)
 
363
        view.connect('drag-leave', self.__drag_leave)
 
364
        if player: view.connect('row-activated', self.__play, player)
 
365
        else: render.set_property('editable', True)
 
366
        view.get_selection().connect('changed', self.activate)
 
367
 
 
368
        s = view.get_model().connect('row-changed', self.__check_current)
 
369
        self.connect_object('destroy', view.get_model().disconnect, s)
 
370
 
 
371
        self.accelerators = gtk.AccelGroup()
 
372
        keyval, mod = gtk.accelerator_parse("F2")
 
373
        self.accelerators.connect_group(keyval, mod, 0, self.__rename)
 
374
 
 
375
        self.show_all()
 
376
 
 
377
    def __rename(self, group, acceleratable, keyval, modifier):
 
378
        model, iter = self.__view.get_selection().get_selected()
 
379
        if iter:
 
380
            self.__render.set_property('editable', True)
 
381
            self.__view.set_cursor(model.get_path(iter),
 
382
                                   self.__view.get_columns()[0],
 
383
                                   start_editing=True)
 
384
 
 
385
    def __play(self, view, path, column, player):
 
386
        player.reset()
 
387
 
 
388
    def __check_current(self, model, path, iter):
 
389
        model, citer = self.__view.get_selection().get_selected()
 
390
        if citer and model.get_path(citer) == path:
 
391
            songlist = qltk.get_top_parent(self).songlist
 
392
            self.activate(resort=not songlist.is_sorted())
 
393
 
 
394
    def __drag_motion(self, view, ctx, x, y, time):
 
395
        if "text/x-quodlibet-songs" in ctx.targets:
 
396
            try: path = view.get_dest_row_at_pos(x, y)[0]
 
397
            except TypeError:
 
398
                path = (len(view.get_model()) - 1,)
 
399
                pos = gtk.TREE_VIEW_DROP_AFTER
 
400
            else: pos = gtk.TREE_VIEW_DROP_INTO_OR_AFTER
 
401
            if path > (-1,): view.set_drag_dest_row(path, pos)
 
402
            return True
 
403
        else:
 
404
            # Highlighting the view itself doesn't work.
 
405
            view.parent.drag_highlight()
 
406
            return True
 
407
 
 
408
    def __drag_leave(self, view, ctx, time):
 
409
        view.parent.drag_unhighlight()
 
410
 
 
411
    def __remove(self, iters, smodel):
 
412
        model, iter = self.__view.get_selection().get_selected()
 
413
        if iter:
 
414
            map(smodel.remove, iters)
 
415
            playlist = model[iter][0]
 
416
            del(playlist[:])
 
417
            for row in smodel: playlist.append(row[0])
 
418
            Playlists.changed(playlist)
 
419
            self.activate()
 
420
 
 
421
    def __drag_data_received(self, view, ctx, x, y, sel, tid, etime, library):
 
422
        # TreeModelSort doesn't support GtkTreeDragDestDrop.
 
423
        view.emit_stop_by_name('drag-data-received')
 
424
        model = view.get_model()
 
425
        if tid == 0:
 
426
            filenames = sel.data.split("\x00")
 
427
            songs = filter(None, map(library.get, filenames))
 
428
            if not songs: return True
 
429
            try: path, pos = view.get_dest_row_at_pos(x, y)
 
430
            except TypeError:
 
431
                playlist = Playlist.fromsongs(songs, library)
 
432
                gobject.idle_add(self.__select_playlist, playlist)
 
433
            else:
 
434
                playlist = model[path][0]
 
435
                playlist.extend(songs)
 
436
            Playlists.changed(playlist)
 
437
            ctx.finish(True, False, etime)
 
438
        else:
 
439
            if tid == 1:
 
440
                uri = sel.get_uris()[0]
 
441
                name = os.path.basename(uri)
 
442
            elif tid == 2:
 
443
                uri, name = sel.data.decode('utf16', 'replace').split('\n')
 
444
            else:
 
445
                ctx.finish(False, False, etime)
 
446
                return
 
447
            name = name or os.path.basename(uri) or _("New Playlist")
 
448
            uri = uri.encode('utf-8')
 
449
            sock = urllib.urlopen(uri)
 
450
            f = NamedTemporaryFile()
 
451
            f.write(sock.read()); f.flush()
 
452
            if uri.lower().endswith('.pls'):
 
453
                playlist = ParsePLS(f.name, library=library)
 
454
            elif uri.lower().endswith('.m3u'):
 
455
                playlist = ParseM3U(f.name, library=library)
 
456
            else: playlist = None
 
457
            if playlist:
 
458
                library.add_filename(playlist)
 
459
                if name: playlist.rename(name)
 
460
                Playlists.changed(playlist)
 
461
                ctx.finish(True, False, etime)
 
462
            else:
 
463
                ctx.finish(False, False, etime)
 
464
                qltk.ErrorMessage(
 
465
                    qltk.get_top_parent(self),
 
466
                    _("Unable to import playlist"),
 
467
                    _("Quod Libet can only import playlists in the M3U "
 
468
                      "and PLS formats.")).run()
 
469
 
 
470
    def __select_playlist(self, playlist):
 
471
        view = self.__view
 
472
        model = view.get_model()
 
473
        for row in model:
 
474
            if row[0] is playlist:
 
475
                view.get_selection().select_iter(row.iter)
 
476
 
 
477
    def __popup_menu(self, view, library):
 
478
        model, iter = view.get_selection().get_selected()
 
479
        if iter is None:
 
480
            return
 
481
        songs = list(model[iter][0])
 
482
        menu = SongsMenu(library, songs, playlists=False, remove=False)
 
483
        menu.preseparate()
 
484
 
 
485
        rem = gtk.ImageMenuItem(gtk.STOCK_DELETE)
 
486
        def remove(model, iter):
 
487
            model[iter][0].delete()
 
488
            model.get_model().remove(
 
489
                model.convert_iter_to_child_iter(None, iter))
 
490
        rem.connect_object('activate', remove, model, iter)
 
491
        menu.prepend(rem)
 
492
 
 
493
        ren = gtk.ImageMenuItem(stock.RENAME)
 
494
        keyval, mod = gtk.accelerator_parse("F2")
 
495
        ren.add_accelerator(
 
496
            'activate', self.accelerators, keyval, mod, gtk.ACCEL_VISIBLE)
 
497
        def rename(path):
 
498
            self.__render.set_property('editable', True)
 
499
            view.set_cursor(path, view.get_columns()[0], start_editing=True)
 
500
        ren.connect_object('activate', rename, model.get_path(iter))
 
501
        menu.prepend(ren)
 
502
 
 
503
        menu.show_all()
 
504
        return view.popup_menu(menu, 0, gtk.get_current_event_time())
 
505
 
 
506
    def activate(self, widget=None, resort=True):
 
507
        model, iter = self.__view.get_selection().get_selected()
 
508
        songs = iter and list(model[iter][0]) or []
 
509
        songs = filter(lambda s: isinstance(s, AudioFile), songs)
 
510
        name = iter and model[iter][0].name or ""
 
511
        if self.__main: config.set("browsers", "playlist", name)
 
512
        self.emit('songs-selected', songs, resort)
 
513
 
 
514
    def __new_playlist(self, activator):
 
515
        playlist = Playlist.new()
 
516
        self.__lists.get_model().append(row=[playlist])
 
517
        self.__select_playlist(playlist)
 
518
 
 
519
    def __start_editing(self, render, editable, path):
 
520
        editable.set_text(self.__lists[path][0].name)
 
521
 
 
522
    def __edited(self, render, path, newname):
 
523
        try: self.__lists[path][0].rename(newname)
 
524
        except ValueError, s:
 
525
            qltk.ErrorMessage(
 
526
                None, _("Unable to rename playlist"), s).run()
 
527
        else: self.__lists[path] = self.__lists[path]
 
528
        render.set_property('editable', not self.__main)
 
529
 
 
530
    def __import(self, activator, library):
 
531
        filt = lambda fn: fn.endswith(".pls") or fn.endswith(".m3u")
 
532
        from qltk.chooser import FileChooser
 
533
        chooser = FileChooser(self, _("Import Playlist"), filt, const.HOME)
 
534
        files = chooser.run()
 
535
        chooser.destroy()
 
536
        for filename in files:
 
537
            if filename.endswith(".m3u"):
 
538
                playlist = ParseM3U(filename, library=library)
 
539
            elif filename.endswith(".pls"):
 
540
                playlist = ParsePLS(filename, library=library)
 
541
            else:
 
542
                qltk.ErrorMessage(
 
543
                    qltk.get_top_parent(self),
 
544
                    _("Unable to import playlist"),
 
545
                    _("Quod Libet can only import playlists in the M3U "
 
546
                      "and PLS formats.")).run()
 
547
                return
 
548
            Playlists.changed(playlist)
 
549
            library.add(playlist)
 
550
 
 
551
    def restore(self):
 
552
        try: name = config.get("browsers", "playlist")
 
553
        except: pass
 
554
        else:
 
555
            for i, row in enumerate(self.__lists):
 
556
                if row[0].name == name:
 
557
                    self.__view.get_selection().select_path((i,))
 
558
                    break
 
559
 
 
560
    def reordered(self, songlist):
 
561
        songs = songlist.get_songs()
 
562
        model, iter = self.__view.get_selection().get_selected()
 
563
        if iter:
 
564
            playlist = model[iter][0]
 
565
            playlist[:] = songs
 
566
        else:
 
567
            playlist = Playlist.fromsongs(songs)
 
568
            gobject.idle_add(self.__select_playlist, playlist)
 
569
        Playlists.changed(playlist, refresh=False)
 
570
 
 
571
browsers = [Playlists]