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