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 "screen_file.h"
21 #include "screen_browser.h"
22 #include "screen_interface.h"
23 #include "config.h"
24 #include "i18n.h"
25 #include "charset.h"
26 #include "mpdclient.h"
27 #include "filelist.h"
28 #include "screen_utils.h"
29 #include "screen_play.h"
30 #include "screen_client.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 screen_file_paint(void);
45 static void
46 screen_file_repaint(void)
47 {
48 screen_file_paint();
49 wrefresh(browser.lw->w);
50 }
52 static void
53 screen_file_reload(struct mpdclient *c)
54 {
55 if (browser.filelist != NULL)
56 filelist_free(browser.filelist);
58 browser.filelist = mpdclient_filelist_get(c, current_path);
59 if (browser.filelist == NULL)
60 browser.filelist = filelist_new();
62 if (*current_path != 0)
63 /* add a dummy entry for ./.. */
64 filelist_prepend(browser.filelist, NULL);
65 }
67 /**
68 * Change to the specified absolute directory.
69 */
70 static bool
71 change_directory(struct mpdclient *c, const char *new_path)
72 {
73 g_free(current_path);
74 current_path = g_strdup(new_path);
76 screen_file_reload(c);
78 #ifndef NCMPC_MINI
79 screen_browser_sync_highlights(browser.filelist, &c->playlist);
80 #endif
82 list_window_reset(browser.lw);
84 return browser.filelist != NULL;
85 }
87 /**
88 * Change to the parent directory of the current directory.
89 */
90 static bool
91 change_to_parent(struct mpdclient *c)
92 {
93 char *parent = g_path_get_dirname(current_path);
94 char *old_path;
95 int idx;
96 bool success;
98 if (strcmp(parent, ".") == 0)
99 parent[0] = '\0';
101 old_path = current_path;
102 current_path = NULL;
104 success = change_directory(c, parent);
105 g_free(parent);
107 idx = success
108 ? filelist_find_directory(browser.filelist, old_path)
109 : -1;
110 g_free(old_path);
112 if (success && idx >= 0) {
113 /* set the cursor on the previous working directory */
114 list_window_set_selected(browser.lw, idx);
115 list_window_center(browser.lw,
116 filelist_length(browser.filelist), idx);
117 }
119 return success;
120 }
122 /**
123 * Change to the directory referred by the specified #filelist_entry
124 * object.
125 */
126 static bool
127 change_to_entry(struct mpdclient *c, const struct filelist_entry *entry)
128 {
129 assert(entry != NULL);
131 if (entry->entity == NULL)
132 return change_to_parent(c);
133 else if (mpd_entity_get_type(entry->entity) == MPD_ENTITY_TYPE_DIRECTORY)
134 return change_directory(c, mpd_directory_get_path(mpd_entity_get_directory(entry->entity)));
135 else
136 return false;
137 }
139 static bool
140 screen_file_handle_enter(struct mpdclient *c)
141 {
142 const struct filelist_entry *entry = browser_get_selected_entry(&browser);
144 if (entry == NULL)
145 return false;
147 return change_to_entry(c, entry);
148 }
150 static int
151 handle_save(struct mpdclient *c)
152 {
153 struct filelist_entry *entry;
154 const char *defaultname = NULL;
155 char *defaultname_utf8 = NULL;
156 int ret;
157 unsigned selected;
159 if (browser.lw->selected >= filelist_length(browser.filelist))
160 return -1;
162 for(selected = browser.lw->selected_start; selected <= browser.lw->selected_end; ++selected)
163 {
164 entry = filelist_get(browser.filelist, selected);
165 if( entry && entry->entity ) {
166 struct mpd_entity *entity = entry->entity;
167 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST) {
168 const struct mpd_playlist *playlist =
169 mpd_entity_get_playlist(entity);
170 defaultname = mpd_playlist_get_path(playlist);
171 }
172 }
173 }
175 if(defaultname)
176 defaultname_utf8 = utf8_to_locale(defaultname);
177 ret = playlist_save(c, NULL, defaultname_utf8);
178 g_free(defaultname_utf8);
180 return ret;
181 }
183 static int
184 handle_delete(struct mpdclient *c)
185 {
186 struct filelist_entry *entry;
187 struct mpd_entity *entity;
188 const struct mpd_playlist *playlist;
189 char *str, *buf;
190 int key;
191 unsigned selected;
193 for(selected = browser.lw->selected_start; selected <= browser.lw->selected_end; ++selected)
194 {
195 if (selected >= filelist_length(browser.filelist))
196 return -1;
198 entry = filelist_get(browser.filelist, selected);
199 if( entry==NULL || entry->entity==NULL )
200 continue;
202 entity = entry->entity;
204 if (mpd_entity_get_type(entity) != MPD_ENTITY_TYPE_PLAYLIST) {
205 /* translators: the "delete" command is only possible
206 for playlists; the user attempted to delete a song
207 or a directory or something else */
208 screen_status_printf(_("Deleting this item is not possible"));
209 screen_bell();
210 continue;
211 }
213 playlist = mpd_entity_get_playlist(entity);
214 str = utf8_to_locale(g_basename(mpd_playlist_get_path(playlist)));
215 buf = g_strdup_printf(_("Delete playlist %s [%s/%s] ? "), str, YES, NO);
216 g_free(str);
217 key = tolower(screen_getch(buf));
218 g_free(buf);
219 if( key != YES[0] ) {
220 /* translators: a dialog was aborted by the user */
221 screen_status_printf(_("Aborted"));
222 return 0;
223 }
225 if (mpdclient_cmd_delete_playlist(c, mpd_playlist_get_path(playlist)))
226 continue;
228 /* translators: MPD deleted the playlist, as requested by the
229 user */
230 screen_status_printf(_("Playlist deleted"));
231 }
232 return 0;
233 }
235 static void
236 screen_file_init(WINDOW *w, int cols, int rows)
237 {
238 current_path = g_strdup("");
240 browser.lw = list_window_init(w, cols, rows);
241 }
243 static void
244 screen_file_resize(int cols, int rows)
245 {
246 browser.lw->cols = cols;
247 browser.lw->rows = rows;
248 }
250 static void
251 screen_file_exit(void)
252 {
253 if (browser.filelist)
254 filelist_free(browser.filelist);
255 list_window_free(browser.lw);
257 g_free(current_path);
258 }
260 static void
261 screen_file_open(struct mpdclient *c)
262 {
263 screen_file_reload(c);
264 }
266 static const char *
267 screen_file_get_title(char *str, size_t size)
268 {
269 const char *path = NULL, *prev = NULL, *slash = current_path;
270 char *path_locale;
272 /* determine the last 2 parts of the path */
273 while ((slash = strchr(slash, '/')) != NULL) {
274 path = prev;
275 prev = ++slash;
276 }
278 if (path == NULL)
279 /* fall back to full path */
280 path = current_path;
282 path_locale = utf8_to_locale(path);
283 g_snprintf(str, size, "%s: %s",
284 /* translators: caption of the browser screen */
285 _("Browse"), path_locale);
286 g_free(path_locale);
287 return str;
288 }
290 static void
291 screen_file_paint(void)
292 {
293 list_window_paint(browser.lw, browser_lw_callback, browser.filelist);
294 }
296 static void
297 screen_file_update(struct mpdclient *c)
298 {
299 if (c->events & (MPD_IDLE_DATABASE | MPD_IDLE_STORED_PLAYLIST)) {
300 /* the db has changed -> update the filelist */
301 screen_file_reload(c);
302 list_window_check_selected(browser.lw,
303 filelist_length(browser.filelist));
304 }
306 #ifndef NCMPC_MINI
307 if (c->events & (MPD_IDLE_DATABASE | MPD_IDLE_STORED_PLAYLIST |
308 MPD_IDLE_PLAYLIST))
309 screen_browser_sync_highlights(browser.filelist, &c->playlist);
310 #endif
312 if (c->events & (MPD_IDLE_DATABASE | MPD_IDLE_STORED_PLAYLIST
313 #ifndef NCMPC_MINI
314 | MPD_IDLE_PLAYLIST
315 #endif
316 ))
317 screen_file_repaint();
318 }
320 static bool
321 screen_file_cmd(struct mpdclient *c, command_t cmd)
322 {
323 switch(cmd) {
324 case CMD_PLAY:
325 if (screen_file_handle_enter(c)) {
326 screen_file_repaint();
327 return true;
328 }
330 break;
332 case CMD_GO_ROOT_DIRECTORY:
333 change_directory(c, "");
334 screen_file_repaint();
335 return true;
336 case CMD_GO_PARENT_DIRECTORY:
337 change_to_parent(c);
338 screen_file_repaint();
339 return true;
341 case CMD_LOCATE:
342 /* don't let browser_cmd() evaluate the locate command
343 - it's a no-op, and by the way, leads to a
344 segmentation fault in the current implementation */
345 return false;
347 case CMD_DELETE:
348 handle_delete(c);
349 screen_file_repaint();
350 break;
351 case CMD_SAVE_PLAYLIST:
352 handle_save(c);
353 break;
354 case CMD_SCREEN_UPDATE:
355 screen_file_reload(c);
356 #ifndef NCMPC_MINI
357 screen_browser_sync_highlights(browser.filelist, &c->playlist);
358 #endif
359 list_window_check_selected(browser.lw,
360 filelist_length(browser.filelist));
361 screen_file_repaint();
362 return false;
364 case CMD_DB_UPDATE:
365 if (c->status == NULL)
366 return true;
368 screen_database_update(c, current_path);
369 return true;
371 default:
372 break;
373 }
375 if (browser_cmd(&browser, c, cmd)) {
376 if (screen_is_visible(&screen_browse))
377 screen_file_repaint();
378 return true;
379 }
381 return false;
382 }
384 const struct screen_functions screen_browse = {
385 .init = screen_file_init,
386 .exit = screen_file_exit,
387 .open = screen_file_open,
388 .resize = screen_file_resize,
389 .paint = screen_file_paint,
390 .update = screen_file_update,
391 .cmd = screen_file_cmd,
392 .get_title = screen_file_get_title,
393 };
395 bool
396 screen_file_goto_song(struct mpdclient *c, const struct mpd_song *song)
397 {
398 const char *uri, *slash, *parent;
399 char *allocated = NULL;
400 bool ret;
401 int i;
403 assert(song != NULL);
405 uri = mpd_song_get_uri(song);
407 if (strstr(uri, "//") != NULL)
408 /* an URL? */
409 return false;
411 /* determine the song's parent directory and go there */
413 slash = strrchr(uri, '/');
414 if (slash != NULL)
415 parent = allocated = g_strndup(uri, slash - uri);
416 else
417 parent = "";
419 ret = change_directory(c, parent);
420 g_free(allocated);
421 if (!ret)
422 return false;
424 /* select the specified song */
426 i = filelist_find_song(browser.filelist, song);
427 if (i < 0)
428 i = 0;
430 list_window_set_selected(browser.lw, i);
432 /* finally, switch to the file screen */
433 screen_switch(&screen_browse, c);
434 return true;
435 }