a9d5e6a7ad3346e6939c3aae61345a1b2efc1237
1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2009 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
20 #include "config.h"
21 #include "i18n.h"
22 #include "options.h"
23 #include "charset.h"
24 #include "mpdclient.h"
25 #include "filelist.h"
26 #include "command.h"
27 #include "screen.h"
28 #include "screen_utils.h"
29 #include "screen_browser.h"
30 #include "screen_play.h"
31 #include "screen_client.h"
33 #include <mpd/client.h>
35 #include <ctype.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <glib.h>
40 static struct screen_browser browser;
41 static char *current_path;
43 static void
44 browse_paint(void);
46 static void
47 file_repaint(void)
48 {
49 browse_paint();
50 wrefresh(browser.lw->w);
51 }
53 static void
54 file_repaint_if_active(void)
55 {
56 if (screen_is_visible(&screen_browse))
57 file_repaint();
58 }
60 static void
61 file_reload(struct mpdclient *c)
62 {
63 if (browser.filelist != NULL)
64 filelist_free(browser.filelist);
66 browser.filelist = mpdclient_filelist_get(c, current_path);
67 }
69 /* the db has changed -> update the filelist */
70 static void
71 file_changed_callback(struct mpdclient *c, G_GNUC_UNUSED int event,
72 G_GNUC_UNUSED gpointer data)
73 {
74 file_reload(c);
76 #ifndef NCMPC_MINI
77 sync_highlights(c, browser.filelist);
78 #endif
79 list_window_check_selected(browser.lw, filelist_length(browser.filelist));
81 file_repaint_if_active();
82 }
84 #ifndef NCMPC_MINI
85 /* the playlist has been updated -> fix highlights */
86 static void
87 playlist_changed_callback(struct mpdclient *c, int event, gpointer data)
88 {
89 browser_playlist_changed(&browser, c, event, data);
91 file_repaint_if_active();
92 }
93 #endif
95 /**
96 * Change to the specified absolute directory.
97 */
98 static bool
99 file_change_directory(struct mpdclient *c, const char *new_path)
100 {
101 g_free(current_path);
102 current_path = g_strdup(new_path);
104 file_reload(c);
106 #ifndef NCMPC_MINI
107 sync_highlights(c, browser.filelist);
108 #endif
110 list_window_reset(browser.lw);
112 return browser.filelist != NULL;
113 }
115 /**
116 * Change to the parent directory of the current directory.
117 */
118 static bool
119 file_change_to_parent(struct mpdclient *c)
120 {
121 char *parent = g_path_get_dirname(current_path);
122 char *old_path;
123 int idx;
124 bool success;
126 if (strcmp(parent, ".") == 0)
127 parent[0] = '\0';
129 old_path = current_path;
130 current_path = NULL;
132 success = file_change_directory(c, parent);
133 g_free(parent);
135 idx = success
136 ? filelist_find_directory(browser.filelist, old_path)
137 : -1;
138 g_free(old_path);
140 if (success && idx >= 0) {
141 /* set the cursor on the previous working directory */
142 list_window_set_selected(browser.lw, idx);
143 list_window_center(browser.lw,
144 filelist_length(browser.filelist), idx);
145 }
147 return success;
148 }
150 /**
151 * Change to the directory referred by the specified #filelist_entry
152 * object.
153 */
154 static bool
155 file_change_to_entry(struct mpdclient *c, const struct filelist_entry *entry)
156 {
157 assert(entry != NULL);
159 if (entry->entity == NULL)
160 return file_change_to_parent(c);
161 else if (mpd_entity_get_type(entry->entity) == MPD_ENTITY_TYPE_DIRECTORY)
162 return file_change_directory(c, mpd_directory_get_path(mpd_entity_get_directory(entry->entity)));
163 else
164 return false;
165 }
167 static bool
168 file_handle_enter(struct mpdclient *c)
169 {
170 const struct filelist_entry *entry = browser_get_selected_entry(&browser);
172 if (entry == NULL)
173 return false;
175 return file_change_to_entry(c, entry);
176 }
178 static int
179 handle_save(struct mpdclient *c)
180 {
181 struct filelist_entry *entry;
182 const char *defaultname = NULL;
183 char *defaultname_utf8 = NULL;
184 int ret;
185 unsigned selected;
187 if (browser.lw->selected >= filelist_length(browser.filelist))
188 return -1;
190 for(selected = browser.lw->selected_start; selected <= browser.lw->selected_end; ++selected)
191 {
192 entry = filelist_get(browser.filelist, selected);
193 if( entry && entry->entity ) {
194 struct mpd_entity *entity = entry->entity;
195 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST) {
196 const struct mpd_playlist *playlist =
197 mpd_entity_get_playlist(entity);
198 defaultname = mpd_playlist_get_path(playlist);
199 }
200 }
201 }
203 if(defaultname)
204 defaultname_utf8 = utf8_to_locale(defaultname);
205 ret = playlist_save(c, NULL, defaultname_utf8);
206 g_free(defaultname_utf8);
208 return ret;
209 }
211 static int
212 handle_delete(struct mpdclient *c)
213 {
214 struct filelist_entry *entry;
215 struct mpd_entity *entity;
216 const struct mpd_playlist *playlist;
217 char *str, *buf;
218 int key;
219 unsigned selected;
221 for(selected = browser.lw->selected_start; selected <= browser.lw->selected_end; ++selected)
222 {
223 if (selected >= filelist_length(browser.filelist))
224 return -1;
226 entry = filelist_get(browser.filelist, selected);
227 if( entry==NULL || entry->entity==NULL )
228 continue;
230 entity = entry->entity;
232 if (mpd_entity_get_type(entity) != MPD_ENTITY_TYPE_PLAYLIST) {
233 /* translators: the "delete" command is only possible
234 for playlists; the user attempted to delete a song
235 or a directory or something else */
236 screen_status_printf(_("Deleting this item is not possible"));
237 screen_bell();
238 continue;
239 }
241 playlist = mpd_entity_get_playlist(entity);
242 str = utf8_to_locale(g_basename(mpd_playlist_get_path(playlist)));
243 buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
244 g_free(str);
245 key = tolower(screen_getch(screen.status_window.w, buf));
246 g_free(buf);
247 if( key != YES[0] ) {
248 /* translators: a dialog was aborted by the user */
249 screen_status_printf(_("Aborted"));
250 return 0;
251 }
253 if (mpdclient_cmd_delete_playlist(c, mpd_playlist_get_path(playlist)))
254 continue;
256 /* translators: MPD deleted the playlist, as requested by the
257 user */
258 screen_status_printf(_("Playlist deleted"));
259 }
260 return 0;
261 }
263 static void
264 browse_init(WINDOW *w, int cols, int rows)
265 {
266 current_path = g_strdup("");
268 browser.lw = list_window_init(w, cols, rows);
269 }
271 static void
272 browse_resize(int cols, int rows)
273 {
274 browser.lw->cols = cols;
275 browser.lw->rows = rows;
276 }
278 static void
279 browse_exit(void)
280 {
281 if (browser.filelist)
282 filelist_free(browser.filelist);
283 list_window_free(browser.lw);
285 g_free(current_path);
286 }
288 static void
289 browse_open(G_GNUC_UNUSED struct mpdclient *c)
290 {
291 if (browser.filelist == NULL) {
292 browser.filelist = mpdclient_filelist_get(c, "");
293 #ifndef NCMPC_MINI
294 mpdclient_install_playlist_callback(c, playlist_changed_callback);
295 #endif
296 mpdclient_install_browse_callback(c, file_changed_callback);
297 }
298 }
300 static const char *
301 browse_title(char *str, size_t size)
302 {
303 const char *path = NULL, *prev = NULL, *slash = current_path;
304 char *path_locale;
306 /* determine the last 2 parts of the path */
307 while ((slash = strchr(slash, '/')) != NULL) {
308 path = prev;
309 prev = ++slash;
310 }
312 if (path == NULL)
313 /* fall back to full path */
314 path = current_path;
316 path_locale = utf8_to_locale(path);
317 g_snprintf(str, size, "%s: %s",
318 /* translators: caption of the browser screen */
319 _("Browse"), path_locale);
320 g_free(path_locale);
321 return str;
322 }
324 static void
325 browse_paint(void)
326 {
327 list_window_paint(browser.lw, browser_lw_callback, browser.filelist);
328 }
330 static bool
331 browse_cmd(struct mpdclient *c, command_t cmd)
332 {
333 switch(cmd) {
334 case CMD_PLAY:
335 if (file_handle_enter(c)) {
336 file_repaint();
337 return true;
338 }
340 break;
342 case CMD_GO_ROOT_DIRECTORY:
343 file_change_directory(c, "");
344 file_repaint();
345 return true;
346 case CMD_GO_PARENT_DIRECTORY:
347 file_change_to_parent(c);
348 file_repaint();
349 return true;
351 case CMD_LOCATE:
352 /* don't let browser_cmd() evaluate the locate command
353 - it's a no-op, and by the way, leads to a
354 segmentation fault in the current implementation */
355 return false;
357 case CMD_DELETE:
358 handle_delete(c);
359 file_repaint();
360 break;
361 case CMD_SAVE_PLAYLIST:
362 handle_save(c);
363 break;
364 case CMD_SCREEN_UPDATE:
365 file_reload(c);
366 #ifndef NCMPC_MINI
367 sync_highlights(c, browser.filelist);
368 #endif
369 list_window_check_selected(browser.lw,
370 filelist_length(browser.filelist));
371 file_repaint();
372 return false;
374 case CMD_DB_UPDATE:
375 if (c->status == NULL)
376 return true;
378 screen_database_update(c, current_path);
379 return true;
381 default:
382 break;
383 }
385 if (browser_cmd(&browser, c, cmd)) {
386 if (screen_is_visible(&screen_browse))
387 file_repaint();
388 return true;
389 }
391 return false;
392 }
394 const struct screen_functions screen_browse = {
395 .init = browse_init,
396 .exit = browse_exit,
397 .open = browse_open,
398 .resize = browse_resize,
399 .paint = browse_paint,
400 .cmd = browse_cmd,
401 .get_title = browse_title,
402 };
404 bool
405 screen_file_goto_song(struct mpdclient *c, const struct mpd_song *song)
406 {
407 const char *uri, *slash, *parent;
408 char *allocated = NULL;
409 bool ret;
410 int i;
412 assert(song != NULL);
414 uri = mpd_song_get_uri(song);
416 if (strstr(uri, "//") != NULL)
417 /* an URL? */
418 return false;
420 /* determine the song's parent directory and go there */
422 slash = strrchr(uri, '/');
423 if (slash != NULL)
424 parent = allocated = g_strndup(uri, slash - uri);
425 else
426 parent = "";
428 ret = file_change_directory(c, parent);
429 g_free(allocated);
430 if (!ret)
431 return false;
433 /* select the specified song */
435 i = filelist_find_song(browser.filelist, song);
436 if (i < 0)
437 i = 0;
439 list_window_set_selected(browser.lw, i);
441 /* finally, switch to the file screen */
442 screen_switch(&screen_browse, c);
443 return true;
444 }