charset: add class Utf8ToLocale
[ncmpc-debian.git] / src / screen_file.cxx
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2018 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
4  *
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.
9  *
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.
14  *
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
20 #include "screen_file.hxx"
21 #include "screen_interface.hxx"
22 #include "screen_browser.hxx"
23 #include "screen_status.hxx"
24 #include "save_playlist.hxx"
25 #include "screen.hxx"
26 #include "config.h"
27 #include "i18n.h"
28 #include "charset.hxx"
29 #include "mpdclient.hxx"
30 #include "filelist.hxx"
31 #include "screen_utils.hxx"
32 #include "screen_client.hxx"
33 #include "options.hxx"
34
35 #include <mpd/client.h>
36
37 #include <ctype.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <glib.h>
41
42 class FileBrowserPage final : public FileListPage {
43         char *current_path = g_strdup("");
44
45 public:
46         FileBrowserPage(ScreenManager &_screen, WINDOW *_w,
47                         Size size)
48                 :FileListPage(_screen, _w, size,
49                               options.list_format) {}
50
51         ~FileBrowserPage() override {
52                 g_free(current_path);
53         }
54
55         bool GotoSong(struct mpdclient &c, const struct mpd_song &song);
56
57 private:
58         void Reload(struct mpdclient &c);
59
60         /**
61          * Change to the specified absolute directory.
62          */
63         bool ChangeDirectory(struct mpdclient &c, const char *new_path);
64
65         /**
66          * Change to the parent directory of the current directory.
67          */
68         bool ChangeToParent(struct mpdclient &c);
69
70         /**
71          * Change to the directory referred by the specified
72          * #FileListEntry object.
73          */
74         bool ChangeToEntry(struct mpdclient &c, const FileListEntry &entry);
75
76         bool HandleEnter(struct mpdclient &c);
77         void HandleSave(struct mpdclient &c);
78         void HandleDelete(struct mpdclient &c);
79
80 public:
81         /* virtual methods from class Page */
82         void Update(struct mpdclient &c, unsigned events) override;
83         bool OnCommand(struct mpdclient &c, command_t cmd) override;
84         const char *GetTitle(char *s, size_t size) const override;
85 };
86
87 static void
88 screen_file_load_list(struct mpdclient *c, const char *current_path,
89                       FileList *filelist)
90 {
91         auto *connection = mpdclient_get_connection(c);
92         if (connection == nullptr)
93                 return;
94
95         mpd_send_list_meta(connection, current_path);
96         filelist->Receive(*connection);
97
98         if (mpdclient_finish_command(c))
99                 filelist->Sort();
100 }
101
102 void
103 FileBrowserPage::Reload(struct mpdclient &c)
104 {
105         delete filelist;
106
107         filelist = new FileList();
108         if (*current_path != 0)
109                 /* add a dummy entry for ./.. */
110                 filelist->emplace_back(nullptr);
111
112         screen_file_load_list(&c, current_path, filelist);
113
114         lw.SetLength(filelist->size());
115
116         SetDirty();
117 }
118
119 bool
120 FileBrowserPage::ChangeDirectory(struct mpdclient &c, const char *new_path)
121 {
122         g_free(current_path);
123         current_path = g_strdup(new_path);
124
125         Reload(c);
126
127         screen_browser_sync_highlights(filelist, &c.playlist);
128
129         lw.Reset();
130
131         return filelist != nullptr;
132 }
133
134 bool
135 FileBrowserPage::ChangeToParent(struct mpdclient &c)
136 {
137         char *parent = g_path_get_dirname(current_path);
138         if (strcmp(parent, ".") == 0)
139                 parent[0] = '\0';
140
141         char *old_path = current_path;
142         current_path = nullptr;
143
144         bool success = ChangeDirectory(c, parent);
145         g_free(parent);
146
147         int idx = success
148                 ? filelist->FindDirectory(old_path)
149                 : -1;
150         g_free(old_path);
151
152         if (success && idx >= 0) {
153                 /* set the cursor on the previous working directory */
154                 lw.SetCursor(idx);
155                 lw.Center(idx);
156         }
157
158         return success;
159 }
160
161 /**
162  * Change to the directory referred by the specified #FileListEntry
163  * object.
164  */
165 bool
166 FileBrowserPage::ChangeToEntry(struct mpdclient &c, const FileListEntry &entry)
167 {
168         if (entry.entity == nullptr)
169                 return ChangeToParent(c);
170         else if (mpd_entity_get_type(entry.entity) == MPD_ENTITY_TYPE_DIRECTORY)
171                 return ChangeDirectory(c, mpd_directory_get_path(mpd_entity_get_directory(entry.entity)));
172         else
173                 return false;
174 }
175
176 bool
177 FileBrowserPage::GotoSong(struct mpdclient &c, const struct mpd_song &song)
178 {
179         const char *uri = mpd_song_get_uri(&song);
180         if (strstr(uri, "//") != nullptr)
181                 /* an URL? */
182                 return false;
183
184         /* determine the song's parent directory and go there */
185
186         const char *slash = strrchr(uri, '/');
187         char *allocated = nullptr;
188         const char *parent = slash != nullptr
189                 ? (allocated = g_strndup(uri, slash - uri))
190                 : "";
191
192         bool ret = ChangeDirectory(c, parent);
193         g_free(allocated);
194         if (!ret)
195                 return false;
196
197         /* select the specified song */
198
199         int i = filelist->FindSong(song);
200         if (i < 0)
201                 i = 0;
202
203         lw.SetCursor(i);
204         SetDirty();
205         return true;
206 }
207
208 bool
209 FileBrowserPage::HandleEnter(struct mpdclient &c)
210 {
211         const auto *entry = GetSelectedEntry();
212         if (entry == nullptr)
213                 return false;
214
215         return ChangeToEntry(c, *entry);
216 }
217
218 void
219 FileBrowserPage::HandleSave(struct mpdclient &c)
220 {
221         const char *defaultname = nullptr;
222
223         const auto range = lw.GetRange();
224         if (range.start_index == range.end_index)
225                 return;
226
227         for (const unsigned i : range) {
228                 auto &entry = (*filelist)[i];
229                 if (entry.entity) {
230                         struct mpd_entity *entity = entry.entity;
231                         if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_PLAYLIST) {
232                                 const struct mpd_playlist *playlist =
233                                         mpd_entity_get_playlist(entity);
234                                 defaultname = mpd_playlist_get_path(playlist);
235                         }
236                 }
237         }
238
239         char *defaultname_utf8 = nullptr;
240         if(defaultname)
241                 defaultname_utf8 = utf8_to_locale(defaultname);
242         playlist_save(&c, nullptr, defaultname_utf8);
243         g_free(defaultname_utf8);
244 }
245
246 void
247 FileBrowserPage::HandleDelete(struct mpdclient &c)
248 {
249         auto *connection = mpdclient_get_connection(&c);
250
251         if (connection == nullptr)
252                 return;
253
254         const auto range = lw.GetRange();
255         for (const unsigned i : range) {
256                 auto &entry = (*filelist)[i];
257                 if (entry.entity == nullptr)
258                         continue;
259
260                 const auto *entity = entry.entity;
261
262                 if (mpd_entity_get_type(entity) != MPD_ENTITY_TYPE_PLAYLIST) {
263                         /* translators: the "delete" command is only possible
264                            for playlists; the user attempted to delete a song
265                            or a directory or something else */
266                         screen_status_printf(_("Deleting this item is not possible"));
267                         screen_bell();
268                         continue;
269                 }
270
271                 const auto *playlist = mpd_entity_get_playlist(entity);
272                 char *buf = g_strdup_printf(_("Delete playlist %s?"),
273                                             Utf8ToLocale(g_basename(mpd_playlist_get_path(playlist))).c_str());
274                 bool confirmed = screen_get_yesno(buf, false);
275                 g_free(buf);
276
277                 if (!confirmed) {
278                         /* translators: a dialog was aborted by the user */
279                         screen_status_printf(_("Aborted"));
280                         return;
281                 }
282
283                 if (!mpd_run_rm(connection, mpd_playlist_get_path(playlist))) {
284                         mpdclient_handle_error(&c);
285                         break;
286                 }
287
288                 c.events |= MPD_IDLE_STORED_PLAYLIST;
289
290                 /* translators: MPD deleted the playlist, as requested by the
291                    user */
292                 screen_status_printf(_("Playlist deleted"));
293         }
294 }
295
296 static Page *
297 screen_file_init(ScreenManager &_screen, WINDOW *w, Size size)
298 {
299         return new FileBrowserPage(_screen, w, size);
300 }
301
302 const char *
303 FileBrowserPage::GetTitle(char *str, size_t size) const
304 {
305         const char *path = nullptr, *prev = nullptr, *slash = current_path;
306
307         /* determine the last 2 parts of the path */
308         while ((slash = strchr(slash, '/')) != nullptr) {
309                 path = prev;
310                 prev = ++slash;
311         }
312
313         if (path == nullptr)
314                 /* fall back to full path */
315                 path = current_path;
316
317         g_snprintf(str, size, "%s: %s",
318                    /* translators: caption of the browser screen */
319                    _("Browse"), Utf8ToLocale(path).c_str());
320         return str;
321 }
322
323 void
324 FileBrowserPage::Update(struct mpdclient &c, unsigned events)
325 {
326         if (events & (MPD_IDLE_DATABASE | MPD_IDLE_STORED_PLAYLIST)) {
327                 /* the db has changed -> update the filelist */
328                 Reload(c);
329         }
330
331         if (events & (MPD_IDLE_DATABASE | MPD_IDLE_STORED_PLAYLIST
332 #ifndef NCMPC_MINI
333                       | MPD_IDLE_QUEUE
334 #endif
335                       )) {
336                 screen_browser_sync_highlights(filelist, &c.playlist);
337                 SetDirty();
338         }
339 }
340
341 bool
342 FileBrowserPage::OnCommand(struct mpdclient &c, command_t cmd)
343 {
344         switch(cmd) {
345         case CMD_PLAY:
346                 if (HandleEnter(c))
347                         return true;
348
349                 break;
350
351         case CMD_GO_ROOT_DIRECTORY:
352                 ChangeDirectory(c, "");
353                 return true;
354         case CMD_GO_PARENT_DIRECTORY:
355                 ChangeToParent(c);
356                 return true;
357
358         case CMD_LOCATE:
359                 /* don't let browser_cmd() evaluate the locate command
360                    - it's a no-op, and by the way, leads to a
361                    segmentation fault in the current implementation */
362                 return false;
363
364         case CMD_SCREEN_UPDATE:
365                 Reload(c);
366                 screen_browser_sync_highlights(filelist, &c.playlist);
367                 return false;
368
369         default:
370                 break;
371         }
372
373         if (FileListPage::OnCommand(c, cmd))
374                 return true;
375
376         if (!c.IsConnected())
377                 return false;
378
379         switch(cmd) {
380         case CMD_DELETE:
381                 HandleDelete(c);
382                 break;
383
384         case CMD_SAVE_PLAYLIST:
385                 HandleSave(c);
386                 break;
387
388         case CMD_DB_UPDATE:
389                 screen_database_update(&c, current_path);
390                 return true;
391
392         default:
393                 break;
394         }
395
396         return false;
397 }
398
399 const struct screen_functions screen_browse = {
400         "browse",
401         screen_file_init,
402 };
403
404 bool
405 screen_file_goto_song(ScreenManager &_screen, struct mpdclient &c,
406                       const struct mpd_song &song)
407 {
408         auto pi = _screen.MakePage(screen_browse);
409         auto &page = (FileBrowserPage &)*pi->second;
410         if (!page.GotoSong(c, song))
411                 return false;
412
413         /* finally, switch to the file screen */
414         _screen.Switch(screen_browse, c);
415         return true;
416 }