1# Copyright 2006-2009 Scott Horowitz <stonecrest@gmail.com>
2# Copyright 2009-2014 Jonathan Ballet <jon@multani.info>
3#
4# This file is part of Sonata.
5#
6# Sonata is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# Sonata is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with Sonata.  If not, see <http://www.gnu.org/licenses/>.
18
19"""
20This module implements a user interface for mpd playlists.
21
22Example usage:
23import playlists
24self.playlists = playlists.Playlists(self.config, self.window,
25self.client, lambda:self.UIManager, self.update_menu_visibility,
26self.iterate_now, self.on_add_item, self.on_playlists_button_press,
27self.connected, self.TAB_PLAYLISTS)
28playlistswindow, playlistsevbox = self.playlists.get_widgets()
29...
30self.playlists.populate()
31...
32"""
33
34import os
35
36from gi.repository import Gtk, Pango, Gdk
37
38from sonata import ui, misc, mpdhelper as mpdh
39
40from sonata.pluginsystem import pluginsystem, BuiltinPlugin
41
42
43class Playlists:
44
45    def __init__(self, config, window, mpd, UIManager,
46                 update_menu_visibility, iterate_now, on_add_item,
47                 on_playlists_button_press, connected,
48                 add_selected_to_playlist, TAB_PLAYLISTS, add_tab):
49        self.config = config
50        self.window = window
51        self.mpd = mpd
52        self.UIManager = UIManager
53        self.update_menu_visibility = update_menu_visibility
54        self.iterate_now = iterate_now # XXX Do we really need this?
55        self.on_add_item = on_add_item
56        self.on_playlists_button_press = on_playlists_button_press
57        self.add_selected_to_playlist = add_selected_to_playlist
58        self.connected = connected
59
60        self.mergepl_id = None
61        self.actionGroupPlaylists = None
62        self.playlist_name_dialog = None
63
64        self.builder = ui.builder('playlists')
65
66        # Playlists tab
67        self.playlists = self.builder.get_object('playlists_page_treeview')
68        self.playlists_selection = self.playlists.get_selection()
69        self.playlistswindow = self.builder.get_object('playlists_page_scrolledwindow')
70
71        self.tab_label = self.builder.get_object('playlists_tab_label')
72        self.tab_label.set_text(TAB_PLAYLISTS)
73
74        self.tab_widget = self.builder.get_object('playlists_tab_eventbox')
75        self.tab = add_tab(self.playlistswindow, self.tab_widget, TAB_PLAYLISTS,
76                           self.playlists)
77
78        self.playlists.connect('button_press_event',
79                               self.on_playlists_button_press)
80        self.playlists.connect('row_activated', self.playlists_activated)
81        self.playlists.connect('key-press-event', self.playlists_key_press)
82
83        # Initialize playlist data and widget
84        self.playlistsdata = self.builder.get_object('playlists_liststore')
85        self.playlists.set_search_column(1)
86
87    def get_model(self):
88        return self.playlistsdata
89
90    def get_widgets(self):
91        return self.playlistswindow
92
93    def get_treeview(self):
94        return self.playlists
95
96    def get_selection(self):
97        return self.playlists_selection
98
99    def populate_playlists_for_menu(self, playlistinfo):
100        if self.mergepl_id:
101            self.UIManager().remove_ui(self.mergepl_id)
102        if self.actionGroupPlaylists:
103            self.UIManager().remove_action_group(self.actionGroupPlaylists)
104            self.actionGroupPlaylists = None
105        self.actionGroupPlaylists = Gtk.ActionGroup('MPDPlaylists')
106        self.UIManager().ensure_update()
107        actions = [
108            ("Playlist: %s" % playlist.replace("&", ""),
109             Gtk.STOCK_JUSTIFY_CENTER,
110             ui.quote_label(misc.unescape_html(playlist)),
111             None, None,
112             self.on_playlist_menu_click)
113            for playlist in playlistinfo]
114        self.actionGroupPlaylists.add_actions(actions)
115        uiDescription = """
116            <ui>
117              <popup name="mainmenu">
118                  <menu action="plmenu">
119            """
120        uiDescription += "".join('<menuitem action=\"%s\"/>' % action[0]
121                        for action in actions)
122        uiDescription += '</menu></popup></ui>'
123        self.mergepl_id = self.UIManager().add_ui_from_string(uiDescription)
124        self.UIManager().insert_action_group(self.actionGroupPlaylists, 0)
125        self.UIManager().get_widget('/hidden').set_property('visible', False)
126        # If we're not on the Current tab, prevent additional menu items
127        # from displaying:
128        self.update_menu_visibility()
129
130    def on_playlist_save(self, _action):
131        plname = self.prompt_for_playlist_name(_("Save Playlist"),
132                                               'savePlaylist')
133        if plname:
134            if self.playlist_name_exists(_("Save Playlist"),
135                                         'savePlaylistError', plname):
136                return
137            self.playlist_create(plname)
138            self.mpd.playlistclear(plname)
139            self.add_selected_to_playlist(plname)
140
141    def playlist_create(self, playlistname, oldname=None):
142        self.mpd.rm(playlistname)
143        if oldname is not None:
144            self.mpd.rename(oldname, playlistname)
145        else:
146            self.mpd.save(playlistname)
147        self.populate()
148        self.iterate_now()
149
150    def on_playlist_menu_click(self, action):
151        plname = misc.unescape_html(action.get_name().replace("Playlist: ",
152                                                              ""))
153        text = ('Would you like to replace the existing playlist or append'
154                'these songs?')
155        response = ui.show_msg(self.window,
156                               _(text), _("Existing Playlist"),
157                               "existingPlaylist", (_("Replace playlist"),
158                                                    1, _("Append songs"), 2),
159                               default=self.config.existing_playlist_option)
160        if response == 1: # Overwrite
161            self.config.existing_playlist_option = response
162            self.mpd.playlistclear(plname)
163            self.add_selected_to_playlist(plname)
164        elif response == 2: # Append songs:
165            self.config.existing_playlist_option = response
166            self.add_selected_to_playlist(plname)
167
168    def playlist_name_exists(self, title, role, plname, skip_plname=""):
169        # If the playlist already exists, and the user does not want to
170        # replace it, return True; In all other cases, return False
171        playlists = self.mpd.listplaylists()
172        if playlists is None:
173            playlists = self.mpd.lsinfo()
174        for item in playlists:
175            if 'playlist' in item:
176                if item['playlist'] == plname and \
177                   plname != skip_plname:
178                    if ui.show_msg(self.window,
179                                   _(('A playlist with this name already '
180                                     'exists. Would you like to replace it?')),
181                                   title, role, Gtk.ButtonsType.YES_NO) == \
182                       Gtk.ResponseType.YES:
183                        return False
184                    else:
185                        return True
186        return False
187
188    def prompt_for_playlist_name(self, title, role, oldname=None):
189        """Prompt user for playlist name"""
190        plname = None
191        if self.connected():
192            if not self.playlist_name_dialog:
193                self.playlist_name_dialog = self.builder.get_object(
194                    'playlist_name_dialog')
195            self.playlist_name_dialog.set_transient_for(self.window)
196            self.playlist_name_dialog.set_title(title)
197            self.playlist_name_dialog.set_role(role)
198            entry = self.builder.get_object('playlist_name_entry')
199            if oldname:
200                entry.set_text(oldname)
201            else:
202                entry.set_text("")
203            self.playlist_name_dialog.show_all()
204            response = self.playlist_name_dialog.run()
205            if response == Gtk.ResponseType.ACCEPT:
206                plname = misc.strip_all_slashes(entry.get_text())
207            self.playlist_name_dialog.hide()
208        return plname
209
210    def populate(self):
211        if self.connected():
212            self.playlistsdata.clear()
213            playlistinfo = []
214            playlists = self.mpd.listplaylists()
215            if playlists is None:
216                playlists = self.mpd.lsinfo()
217            for item in playlists:
218                if 'playlist' in item:
219                    playlistinfo.append(misc.escape_html(item['playlist']))
220
221            # Remove case sensitivity
222            playlistinfo.sort(key=lambda x: x.lower())
223            for item in playlistinfo:
224                self.playlistsdata.append([Gtk.STOCK_DIRECTORY, item])
225
226            self.populate_playlists_for_menu(playlistinfo)
227
228    def on_playlist_rename(self, _action):
229        model, selected = self.playlists_selection.get_selected_rows()
230        oldname = misc.unescape_html(
231            model.get_value(model.get_iter(selected[0]), 1))
232        plname = self.prompt_for_playlist_name(_("Rename Playlist"),
233                                               'renamePlaylist', oldname)
234        if plname:
235            if self.playlist_name_exists(_("Rename Playlist"),
236                                         'renamePlaylistError',
237                                         plname, oldname):
238                return
239            self.playlist_create(plname, oldname)
240            # Re-select item:
241            row = 0
242            for pl in self.playlistsdata:
243                if pl[1] == plname:
244                    self.playlists_selection.select_path((row,))
245                    return
246                row = row + 1
247
248    def playlists_key_press(self, widget, event):
249        if event.keyval == Gdk.keyval_from_name('Return'):
250            self.playlists_activated(widget, widget.get_cursor()[0])
251            return True
252
253    def playlists_activated(self, _treeview, _path, _column=0):
254        self.on_add_item(None)
255