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 */
19 #include "config.h"
20 #include "i18n.h"
21 #include "options.h"
22 #include "charset.h"
23 #include "mpdclient.h"
24 #include "command.h"
25 #include "screen.h"
26 #include "screen_utils.h"
27 #include "screen_browser.h"
28 #include "screen_play.h"
30 #include <ctype.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <glib.h>
35 static struct screen_browser browser;
36 static char *current_path;
38 static void
39 browse_paint(void);
41 static void
42 file_repaint(void)
43 {
44 browse_paint();
45 wrefresh(browser.lw->w);
46 }
48 static void
49 file_repaint_if_active(void)
50 {
51 if (screen_is_visible(&screen_browse))
52 file_repaint();
53 }
55 static void
56 file_reload(struct mpdclient *c)
57 {
58 if (browser.filelist != NULL)
59 filelist_free(browser.filelist);
61 browser.filelist = mpdclient_filelist_get(c, current_path);
62 }
64 /* the db has changed -> update the filelist */
65 static void
66 file_changed_callback(mpdclient_t *c, G_GNUC_UNUSED int event,
67 G_GNUC_UNUSED gpointer data)
68 {
69 file_reload(c);
71 #ifndef NCMPC_MINI
72 sync_highlights(c, browser.filelist);
73 #endif
74 list_window_check_selected(browser.lw, filelist_length(browser.filelist));
76 file_repaint_if_active();
77 }
79 #ifndef NCMPC_MINI
80 /* the playlist has been updated -> fix highlights */
81 static void
82 playlist_changed_callback(mpdclient_t *c, int event, gpointer data)
83 {
84 browser_playlist_changed(&browser, c, event, data);
86 file_repaint_if_active();
87 }
88 #endif
90 static bool
91 file_change_directory(mpdclient_t *c, filelist_entry_t *entry,
92 const char *new_path)
93 {
94 mpd_InfoEntity *entity = NULL;
95 gchar *path = NULL;
96 char *old_path;
97 int idx;
99 if( entry!=NULL )
100 entity = entry->entity;
101 else if( new_path==NULL )
102 return false;
104 if( entity==NULL ) {
105 if( entry || 0==strcmp(new_path, "..") ) {
106 /* return to parent */
107 char *parent = g_path_get_dirname(current_path);
108 if( strcmp(parent, ".") == 0 )
109 parent[0] = '\0';
110 path = g_strdup(parent);
111 g_free(parent);
112 } else {
113 /* entry==NULL, then new_path ("" is root) */
114 path = g_strdup(new_path);
115 }
116 } else if( entity->type==MPD_INFO_ENTITY_TYPE_DIRECTORY) {
117 /* enter sub */
118 mpd_Directory *dir = entity->info.directory;
119 path = g_strdup(dir->path);
120 } else
121 return false;
123 old_path = current_path;
124 current_path = g_strdup(path);
126 file_reload(c);
128 #ifndef NCMPC_MINI
129 sync_highlights(c, browser.filelist);
130 #endif
132 idx = old_path != NULL
133 ? filelist_find_directory(browser.filelist, old_path)
134 : -1;
135 g_free(old_path);
137 list_window_reset(browser.lw);
138 if (idx >= 0) {
139 list_window_set_selected(browser.lw, idx);
140 list_window_center(browser.lw,
141 filelist_length(browser.filelist), idx);
142 }
144 g_free(path);
145 return true;
146 }
148 static bool
149 file_handle_enter(struct mpdclient *c)
150 {
151 struct filelist_entry *entry = browser_get_selected_entry(&browser);
152 struct mpd_InfoEntity *entity;
154 if (entry == NULL)
155 return false;
157 entity = entry->entity;
158 if (entity == NULL || entity->type == MPD_INFO_ENTITY_TYPE_DIRECTORY)
159 return file_change_directory(c, entry, NULL);
160 else
161 return false;
162 }
164 static int
165 handle_save(mpdclient_t *c)
166 {
167 filelist_entry_t *entry;
168 char *defaultname = NULL;
169 int ret;
170 unsigned selected;
172 if (browser.lw->selected >= filelist_length(browser.filelist))
173 return -1;
175 for(selected = browser.lw->selected_start; selected <= browser.lw->selected_end; ++selected)
176 {
177 entry = filelist_get(browser.filelist, selected);
178 if( entry && entry->entity ) {
179 mpd_InfoEntity *entity = entry->entity;
180 if( entity->type==MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
181 mpd_PlaylistFile *plf = entity->info.playlistFile;
182 defaultname = plf->path;
183 }
184 }
185 }
187 if(defaultname)
188 defaultname = utf8_to_locale(defaultname);
189 ret = playlist_save(c, NULL, defaultname);
190 g_free(defaultname);
192 return ret;
193 }
195 static int
196 handle_delete(mpdclient_t *c)
197 {
198 filelist_entry_t *entry;
199 mpd_InfoEntity *entity;
200 mpd_PlaylistFile *plf;
201 char *str, *buf;
202 int key;
203 unsigned selected;
205 for(selected = browser.lw->selected_start; selected <= browser.lw->selected_end; ++selected)
206 {
207 if (selected >= filelist_length(browser.filelist))
208 return -1;
210 entry = filelist_get(browser.filelist, selected);
211 if( entry==NULL || entry->entity==NULL )
212 continue;
214 entity = entry->entity;
216 if( entity->type!=MPD_INFO_ENTITY_TYPE_PLAYLISTFILE ) {
217 /* translators: the "delete" command is only possible
218 for playlists; the user attempted to delete a song
219 or a directory or something else */
220 screen_status_printf(_("Deleting this item is not possible"));
221 screen_bell();
222 continue;
223 }
225 plf = entity->info.playlistFile;
226 str = utf8_to_locale(g_basename(plf->path));
227 buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
228 g_free(str);
229 key = tolower(screen_getch(screen.status_window.w, buf));
230 g_free(buf);
231 if( key != YES[0] ) {
232 /* translators: a dialog was aborted by the user */
233 screen_status_printf(_("Aborted"));
234 return 0;
235 }
237 if( mpdclient_cmd_delete_playlist(c, plf->path) )
238 continue;
240 /* translators: MPD deleted the playlist, as requested by the
241 user */
242 screen_status_printf(_("Playlist deleted"));
243 }
244 return 0;
245 }
247 static void
248 browse_init(WINDOW *w, int cols, int rows)
249 {
250 current_path = g_strdup("");
252 browser.lw = list_window_init(w, cols, rows);
253 }
255 static void
256 browse_resize(int cols, int rows)
257 {
258 browser.lw->cols = cols;
259 browser.lw->rows = rows;
260 }
262 static void
263 browse_exit(void)
264 {
265 if (browser.filelist)
266 filelist_free(browser.filelist);
267 list_window_free(browser.lw);
269 g_free(current_path);
270 }
272 static void
273 browse_open(G_GNUC_UNUSED mpdclient_t *c)
274 {
275 if (browser.filelist == NULL) {
276 browser.filelist = mpdclient_filelist_get(c, "");
277 #ifndef NCMPC_MINI
278 mpdclient_install_playlist_callback(c, playlist_changed_callback);
279 #endif
280 mpdclient_install_browse_callback(c, file_changed_callback);
281 }
282 }
284 static const char *
285 browse_title(char *str, size_t size)
286 {
287 const char *path = NULL, *prev = NULL, *slash = current_path;
288 char *path_locale;
290 /* determine the last 2 parts of the path */
291 while ((slash = strchr(slash, '/')) != NULL) {
292 path = prev;
293 prev = ++slash;
294 }
296 if (path == NULL)
297 /* fall back to full path */
298 path = current_path;
300 path_locale = utf8_to_locale(path);
301 g_snprintf(str, size, "%s: %s",
302 /* translators: caption of the browser screen */
303 _("Browse"), path_locale);
304 g_free(path_locale);
305 return str;
306 }
308 static void
309 browse_paint(void)
310 {
311 list_window_paint(browser.lw, browser_lw_callback, browser.filelist);
312 }
314 static bool
315 browse_cmd(mpdclient_t *c, command_t cmd)
316 {
317 switch(cmd) {
318 case CMD_PLAY:
319 if (file_handle_enter(c)) {
320 file_repaint();
321 return true;
322 }
324 break;
326 case CMD_GO_ROOT_DIRECTORY:
327 file_change_directory(c, NULL, "");
328 file_repaint();
329 return true;
330 case CMD_GO_PARENT_DIRECTORY:
331 file_change_directory(c, NULL, "..");
332 file_repaint();
333 return true;
335 case CMD_LOCATE:
336 /* don't let browser_cmd() evaluate the locate command
337 - it's a no-op, and by the way, leads to a
338 segmentation fault in the current implementation */
339 return false;
341 case CMD_DELETE:
342 handle_delete(c);
343 file_repaint();
344 break;
345 case CMD_SAVE_PLAYLIST:
346 handle_save(c);
347 break;
348 case CMD_SCREEN_UPDATE:
349 file_reload(c);
350 #ifndef NCMPC_MINI
351 sync_highlights(c, browser.filelist);
352 #endif
353 list_window_check_selected(browser.lw,
354 filelist_length(browser.filelist));
355 file_repaint();
356 return false;
358 case CMD_DB_UPDATE:
359 if (c->status == NULL)
360 return true;
362 if (!c->status->updatingDb) {
363 if (mpdclient_cmd_db_update(c, current_path) == 0) {
364 if (strcmp(current_path, "") != 0) {
365 char *path_locale =
366 utf8_to_locale(current_path);
367 screen_status_printf(_("Database update of %s started"),
368 path_locale);
369 g_free(path_locale);
370 } else
371 screen_status_printf(_("Database update started"));
372 }
373 } else
374 screen_status_printf(_("Database update running..."));
375 return true;
377 default:
378 break;
379 }
381 if (browser_cmd(&browser, c, cmd)) {
382 if (screen_is_visible(&screen_browse))
383 file_repaint();
384 return true;
385 }
387 return false;
388 }
390 const struct screen_functions screen_browse = {
391 .init = browse_init,
392 .exit = browse_exit,
393 .open = browse_open,
394 .resize = browse_resize,
395 .paint = browse_paint,
396 .cmd = browse_cmd,
397 .get_title = browse_title,
398 };
400 bool
401 screen_file_goto_song(struct mpdclient *c, const struct mpd_song *song)
402 {
403 const char *slash, *parent;
404 char *allocated = NULL;
405 bool ret;
406 int i;
408 assert(song != NULL);
409 assert(song->file != NULL);
411 if (strstr(song->file, "//") != NULL)
412 /* an URL? */
413 return false;
415 /* determine the song's parent directory and go there */
417 slash = strrchr(song->file, '/');
418 if (slash != NULL)
419 parent = allocated = g_strndup(song->file, slash - song->file);
420 else
421 parent = "";
423 ret = file_change_directory(c, NULL, parent);
424 g_free(allocated);
425 if (!ret)
426 return false;
428 /* select the specified song */
430 i = filelist_find_song(browser.filelist, song);
431 if (i < 0)
432 i = 0;
434 list_window_set_selected(browser.lw, i);
436 /* finally, switch to the file screen */
437 screen_switch(&screen_browse, c);
438 return true;
439 }