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"
32 #include <mpd/client.h>
34 #include <ctype.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <glib.h>
39 static struct screen_browser browser;
40 static char *current_path;
42 static void
43 browse_paint(void);
45 static void
46 file_repaint(void)
47 {
48 browse_paint();
49 wrefresh(browser.lw->w);
50 }
52 static void
53 file_repaint_if_active(void)
54 {
55 if (screen_is_visible(&screen_browse))
56 file_repaint();
57 }
59 static void
60 file_reload(struct mpdclient *c)
61 {
62 if (browser.filelist != NULL)
63 filelist_free(browser.filelist);
65 browser.filelist = mpdclient_filelist_get(c, current_path);
66 }
68 /* the db has changed -> update the filelist */
69 static void
70 file_changed_callback(struct mpdclient *c, G_GNUC_UNUSED int event,
71 G_GNUC_UNUSED gpointer data)
72 {
73 file_reload(c);
75 #ifndef NCMPC_MINI
76 sync_highlights(c, browser.filelist);
77 #endif
78 list_window_check_selected(browser.lw, filelist_length(browser.filelist));
80 file_repaint_if_active();
81 }
83 #ifndef NCMPC_MINI
84 /* the playlist has been updated -> fix highlights */
85 static void
86 playlist_changed_callback(struct mpdclient *c, int event, gpointer data)
87 {
88 browser_playlist_changed(&browser, c, event, data);
90 file_repaint_if_active();
91 }
92 #endif
94 /**
95 * Change to the specified absolute directory.
96 */
97 static bool
98 file_change_directory(struct mpdclient *c, const char *new_path)
99 {
100 g_free(current_path);
101 current_path = g_strdup(new_path);
103 file_reload(c);
105 #ifndef NCMPC_MINI
106 sync_highlights(c, browser.filelist);
107 #endif
109 list_window_reset(browser.lw);
111 return browser.filelist != NULL;
112 }
114 /**
115 * Change to the parent directory of the current directory.
116 */
117 static bool
118 file_change_to_parent(struct mpdclient *c)
119 {
120 char *parent = g_path_get_dirname(current_path);
121 char *old_path;
122 int idx;
123 bool success;
125 if (strcmp(parent, ".") == 0)
126 parent[0] = '\0';
128 old_path = current_path;
129 current_path = NULL;
131 success = file_change_directory(c, parent);
132 g_free(parent);
134 idx = success
135 ? filelist_find_directory(browser.filelist, old_path)
136 : -1;
137 g_free(old_path);
139 if (success && idx >= 0) {
140 /* set the cursor on the previous working directory */
141 list_window_set_selected(browser.lw, idx);
142 list_window_center(browser.lw,
143 filelist_length(browser.filelist), idx);
144 }
146 return success;
147 }
149 /**
150 * Change to the directory referred by the specified #filelist_entry
151 * object.
152 */
153 static bool
154 file_change_to_entry(struct mpdclient *c, const struct filelist_entry *entry)
155 {
156 assert(entry != NULL);
158 if (entry->entity == NULL)
159 return file_change_to_parent(c);
160 else if (mpd_entity_get_type(entry->entity) == MPD_ENTITY_TYPE_DIRECTORY)
161 return file_change_directory(c, mpd_directory_get_path(mpd_entity_get_directory(entry->entity)));
162 else
163 return false;
164 }
166 static bool
167 file_handle_enter(struct mpdclient *c)
168 {
169 const struct filelist_entry *entry = browser_get_selected_entry(&browser);
171 if (entry == NULL)
172 return false;
174 return file_change_to_entry(c, entry);
175 }
177 static int
178 handle_save(struct mpdclient *c)
179 {
180 struct filelist_entry *entry;
181 const char *defaultname = NULL;
182 char *defaultname_utf8 = NULL;
183 int ret;
184 unsigned selected;
186 if (browser.lw->selected >= filelist_length(browser.filelist))
187 return -1;
189 for(selected = browser.lw->selected_start; selected <= browser.lw->selected_end; ++selected)
190 {
191 entry = filelist_get(browser.filelist, selected);
192 if( entry && entry->entity ) {
193 struct mpd_entity *entity = entry->entity;
194 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST) {
195 const struct mpd_playlist *playlist =
196 mpd_entity_get_playlist(entity);
197 defaultname = mpd_playlist_get_path(playlist);
198 }
199 }
200 }
202 if(defaultname)
203 defaultname_utf8 = utf8_to_locale(defaultname);
204 ret = playlist_save(c, NULL, defaultname_utf8);
205 g_free(defaultname_utf8);
207 return ret;
208 }
210 static int
211 handle_delete(struct mpdclient *c)
212 {
213 struct filelist_entry *entry;
214 struct mpd_entity *entity;
215 const struct mpd_playlist *playlist;
216 char *str, *buf;
217 int key;
218 unsigned selected;
220 for(selected = browser.lw->selected_start; selected <= browser.lw->selected_end; ++selected)
221 {
222 if (selected >= filelist_length(browser.filelist))
223 return -1;
225 entry = filelist_get(browser.filelist, selected);
226 if( entry==NULL || entry->entity==NULL )
227 continue;
229 entity = entry->entity;
231 if (mpd_entity_get_type(entity) != MPD_ENTITY_TYPE_PLAYLIST) {
232 /* translators: the "delete" command is only possible
233 for playlists; the user attempted to delete a song
234 or a directory or something else */
235 screen_status_printf(_("Deleting this item is not possible"));
236 screen_bell();
237 continue;
238 }
240 playlist = mpd_entity_get_playlist(entity);
241 str = utf8_to_locale(g_basename(mpd_playlist_get_path(playlist)));
242 buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
243 g_free(str);
244 key = tolower(screen_getch(screen.status_window.w, buf));
245 g_free(buf);
246 if( key != YES[0] ) {
247 /* translators: a dialog was aborted by the user */
248 screen_status_printf(_("Aborted"));
249 return 0;
250 }
252 if (mpdclient_cmd_delete_playlist(c, mpd_playlist_get_path(playlist)))
253 continue;
255 /* translators: MPD deleted the playlist, as requested by the
256 user */
257 screen_status_printf(_("Playlist deleted"));
258 }
259 return 0;
260 }
262 static void
263 browse_init(WINDOW *w, int cols, int rows)
264 {
265 current_path = g_strdup("");
267 browser.lw = list_window_init(w, cols, rows);
268 }
270 static void
271 browse_resize(int cols, int rows)
272 {
273 browser.lw->cols = cols;
274 browser.lw->rows = rows;
275 }
277 static void
278 browse_exit(void)
279 {
280 if (browser.filelist)
281 filelist_free(browser.filelist);
282 list_window_free(browser.lw);
284 g_free(current_path);
285 }
287 static void
288 browse_open(G_GNUC_UNUSED struct mpdclient *c)
289 {
290 if (browser.filelist == NULL) {
291 browser.filelist = mpdclient_filelist_get(c, "");
292 #ifndef NCMPC_MINI
293 mpdclient_install_playlist_callback(c, playlist_changed_callback);
294 #endif
295 mpdclient_install_browse_callback(c, file_changed_callback);
296 }
297 }
299 static const char *
300 browse_title(char *str, size_t size)
301 {
302 const char *path = NULL, *prev = NULL, *slash = current_path;
303 char *path_locale;
305 /* determine the last 2 parts of the path */
306 while ((slash = strchr(slash, '/')) != NULL) {
307 path = prev;
308 prev = ++slash;
309 }
311 if (path == NULL)
312 /* fall back to full path */
313 path = current_path;
315 path_locale = utf8_to_locale(path);
316 g_snprintf(str, size, "%s: %s",
317 /* translators: caption of the browser screen */
318 _("Browse"), path_locale);
319 g_free(path_locale);
320 return str;
321 }
323 static void
324 browse_paint(void)
325 {
326 list_window_paint(browser.lw, browser_lw_callback, browser.filelist);
327 }
329 static bool
330 browse_cmd(struct mpdclient *c, command_t cmd)
331 {
332 switch(cmd) {
333 case CMD_PLAY:
334 if (file_handle_enter(c)) {
335 file_repaint();
336 return true;
337 }
339 break;
341 case CMD_GO_ROOT_DIRECTORY:
342 file_change_directory(c, "");
343 file_repaint();
344 return true;
345 case CMD_GO_PARENT_DIRECTORY:
346 file_change_to_parent(c);
347 file_repaint();
348 return true;
350 case CMD_LOCATE:
351 /* don't let browser_cmd() evaluate the locate command
352 - it's a no-op, and by the way, leads to a
353 segmentation fault in the current implementation */
354 return false;
356 case CMD_DELETE:
357 handle_delete(c);
358 file_repaint();
359 break;
360 case CMD_SAVE_PLAYLIST:
361 handle_save(c);
362 break;
363 case CMD_SCREEN_UPDATE:
364 file_reload(c);
365 #ifndef NCMPC_MINI
366 sync_highlights(c, browser.filelist);
367 #endif
368 list_window_check_selected(browser.lw,
369 filelist_length(browser.filelist));
370 file_repaint();
371 return false;
373 case CMD_DB_UPDATE:
374 if (c->status == NULL)
375 return true;
377 if (mpd_status_get_update_id(c->status) == 0) {
378 if (mpdclient_cmd_db_update(c, current_path) == 0) {
379 if (strcmp(current_path, "")) {
380 char *path_locale =
381 utf8_to_locale(current_path);
382 screen_status_printf(_("Database update of %s started"),
383 path_locale);
384 g_free(path_locale);
385 } else
386 screen_status_printf(_("Database update started"));
387 }
388 } else
389 screen_status_printf(_("Database update running..."));
390 return true;
392 default:
393 break;
394 }
396 if (browser_cmd(&browser, c, cmd)) {
397 if (screen_is_visible(&screen_browse))
398 file_repaint();
399 return true;
400 }
402 return false;
403 }
405 const struct screen_functions screen_browse = {
406 .init = browse_init,
407 .exit = browse_exit,
408 .open = browse_open,
409 .resize = browse_resize,
410 .paint = browse_paint,
411 .cmd = browse_cmd,
412 .get_title = browse_title,
413 };
415 bool
416 screen_file_goto_song(struct mpdclient *c, const struct mpd_song *song)
417 {
418 const char *uri, *slash, *parent;
419 char *allocated = NULL;
420 bool ret;
421 int i;
423 assert(song != NULL);
425 uri = mpd_song_get_uri(song);
427 if (strstr(uri, "//") != NULL)
428 /* an URL? */
429 return false;
431 /* determine the song's parent directory and go there */
433 slash = strrchr(uri, '/');
434 if (slash != NULL)
435 parent = allocated = g_strndup(uri, slash - uri);
436 else
437 parent = "";
439 ret = file_change_directory(c, parent);
440 g_free(allocated);
441 if (!ret)
442 return false;
444 /* select the specified song */
446 i = filelist_find_song(browser.filelist, song);
447 if (i < 0)
448 i = 0;
450 list_window_set_selected(browser.lw, i);
452 /* finally, switch to the file screen */
453 screen_switch(&screen_browse, c);
454 return true;
455 }