browser: Do not modify the items name if we're loading a playlist
[ncmpcpp.git] / src / browser.cpp
blobd09b78f025919c3ac0cd6e0df7e00181409a3bda
1 /***************************************************************************
2 * Copyright (C) 2008-2011 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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 *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include <dirent.h>
22 #include <sys/stat.h>
23 #include <cerrno>
24 #include <cstring>
25 #include <algorithm>
27 #include "browser.h"
28 #include "charset.h"
29 #include "display.h"
30 #include "global.h"
31 #include "helpers.h"
32 #include "playlist.h"
33 #include "settings.h"
34 #include "status.h"
35 #ifdef HAVE_TAGLIB_H
36 # include "tag_editor.h"
37 #endif // HAVE_TAGLIB_H
39 using Global::MainHeight;
40 using Global::MainStartY;
41 using Global::myScreen;
42 using Global::RedrawHeader;
44 using MPD::itDirectory;
45 using MPD::itSong;
46 using MPD::itPlaylist;
48 Browser *myBrowser = new Browser;
50 const char *Browser::SupportedExtensions[] =
52 "wma", "asf", "rm", "mp1", "mp2", "mp3",
53 "mp4", "m4a", "flac", "ogg", "wav", "au",
54 "aiff", "aif", "ac3", "aac", "mpc", "it",
55 "mod", "s3m", "xm", "wv", 0
58 void Browser::Init()
60 static Display::ScreenFormat sf = { this, &Config.song_list_format };
62 w = new Menu<MPD::Item>(0, MainStartY, COLS, MainHeight, Config.columns_in_browser && Config.titles_visibility ? Display::Columns(COLS) : "", Config.main_color, brNone);
63 w->HighlightColor(Config.main_highlight_color);
64 w->CyclicScrolling(Config.use_cyclic_scrolling);
65 w->CenteredCursor(Config.centered_cursor);
66 w->SetSelectPrefix(&Config.selected_item_prefix);
67 w->SetSelectSuffix(&Config.selected_item_suffix);
68 w->SetItemDisplayer(Display::Items);
69 w->SetItemDisplayerUserData(&sf);
70 w->SetGetStringFunction(ItemToString);
71 isInitialized = 1;
74 void Browser::Resize()
76 size_t x_offset, width;
77 GetWindowResizeParams(x_offset, width);
78 w->Resize(width, MainHeight);
79 w->MoveTo(x_offset, MainStartY);
80 w->SetTitle(Config.columns_in_browser && Config.titles_visibility ? Display::Columns(w->GetWidth()) : "");
81 hasToBeResized = 0;
84 void Browser::SwitchTo()
86 using Global::myLockedScreen;
87 using Global::myInactiveScreen;
89 if (myScreen == this)
91 # ifndef WIN32
92 myBrowser->ChangeBrowseMode();
93 # endif // !WIN32
96 if (!isInitialized)
97 Init();
99 if (myLockedScreen)
100 UpdateInactiveScreen(this);
102 if (hasToBeResized || myLockedScreen)
103 Resize();
105 if (isLocal()) // local browser doesn't support sorting by mtime
106 Config.browser_sort_by_mtime = 0;
108 w->Empty() ? myBrowser->GetDirectory(itsBrowsedDir) : myBrowser->UpdateItemList();
110 if (myScreen != this && myScreen->isTabbable())
111 Global::myPrevScreen = myScreen;
112 myScreen = this;
113 RedrawHeader = 1;
116 std::basic_string<my_char_t> Browser::Title()
118 std::basic_string<my_char_t> result = U("Browse: ");
119 result += Scroller(TO_WSTRING(itsBrowsedDir), itsScrollBeginning, COLS-result.length()-(Config.new_design ? 2 : Global::VolumeState.length()));
120 return result;
123 void Browser::EnterPressed()
125 if (w->Empty())
126 return;
128 const MPD::Item &item = w->Current();
129 switch (item.type)
131 case itDirectory:
133 GetDirectory(item.name, itsBrowsedDir);
134 RedrawHeader = 1;
135 break;
137 case itSong:
139 w->Bold(w->Choice(), myPlaylist->Add(*item.song, w->isBold(), 1));
140 break;
142 case itPlaylist:
144 if (itsBrowsedDir == "/")
146 MPD::SongList list;
147 Mpd.GetPlaylistContent(locale_to_utf_cpy(item.name), list);
148 if (myPlaylist->Add(list, 1))
149 ShowMessage("Loading and playing playlist %s...", item.name.c_str());
150 FreeSongList(list);
152 else
154 std::string name = item.name;
155 ShowMessage("Loading playlist %s...", name.c_str());
156 locale_to_utf(name);
157 if (Mpd.LoadPlaylist(name))
158 ShowMessage("Playlist loaded.");
160 break;
165 void Browser::SpacePressed()
167 if (w->Empty())
168 return;
170 if (Config.space_selects && w->Choice() >= (itsBrowsedDir != "/" ? 1 : 0))
172 w->Select(w->Choice(), !w->isSelected());
173 w->Scroll(wDown);
174 return;
177 if (itsBrowsedDir != "/" && w->Choice() == 0 /* parent dir */)
178 return;
180 const MPD::Item &item = w->Current();
181 switch (item.type)
183 case itDirectory:
185 if (itsBrowsedDir != "/" && !w->Choice())
186 break; // do not let add parent dir.
188 MPD::SongList list;
189 # ifndef WIN32
190 if (isLocal())
192 MPD::ItemList items;
193 ShowMessage("Scanning \"%s\"...", item.name.c_str());
194 myBrowser->GetLocalDirectory(items, item.name, 1);
195 list.reserve(items.size());
196 for (MPD::ItemList::const_iterator it = items.begin(); it != items.end(); ++it)
197 list.push_back(it->song);
199 else
200 # endif // !WIN32
201 Mpd.GetDirectoryRecursive(locale_to_utf_cpy(item.name), list);
203 if (myPlaylist->Add(list, 0))
204 ShowMessage("Added folder: %s", item.name.c_str());
206 FreeSongList(list);
207 break;
209 case itSong:
211 w->Bold(w->Choice(), myPlaylist->Add(*item.song, w->isBold(), 0));
212 break;
214 case itPlaylist:
216 std::string name = item.name;
217 ShowMessage("Loading playlist %s...", name.c_str());
218 locale_to_utf(name);
219 if (Mpd.LoadPlaylist(name))
220 ShowMessage("Playlist loaded.");
221 break;
224 w->Scroll(wDown);
227 void Browser::MouseButtonPressed(MEVENT me)
229 if (w->Empty() || !w->hasCoords(me.x, me.y) || size_t(me.y) >= w->Size())
230 return;
231 if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
233 w->Goto(me.y);
234 switch (w->Current().type)
236 case itDirectory:
237 if (me.bstate & BUTTON1_PRESSED)
239 GetDirectory(w->Current().name);
240 RedrawHeader = 1;
242 else
244 size_t pos = w->Choice();
245 SpacePressed();
246 if (pos < w->Size()-1)
247 w->Scroll(wUp);
249 break;
250 case itPlaylist:
251 case itSong:
252 if (me.bstate & BUTTON1_PRESSED)
254 size_t pos = w->Choice();
255 SpacePressed();
256 if (pos < w->Size()-1)
257 w->Scroll(wUp);
259 else
260 EnterPressed();
261 break;
264 else
265 Screen< Menu<MPD::Item> >::MouseButtonPressed(me);
268 MPD::Song *Browser::CurrentSong()
270 return !w->Empty() && w->Current().type == itSong ? w->Current().song : 0;
273 void Browser::ReverseSelection()
275 w->ReverseSelection(itsBrowsedDir == "/" ? 0 : 1);
278 void Browser::GetSelectedSongs(MPD::SongList &v)
280 if (w->Empty())
281 return;
282 std::vector<size_t> selected;
283 w->GetSelected(selected);
284 if (selected.empty())
285 selected.push_back(w->Choice());
286 for (std::vector<size_t>::const_iterator it = selected.begin(); it != selected.end(); ++it)
288 const MPD::Item &item = w->at(*it);
289 switch (item.type)
291 case itDirectory:
293 # ifndef WIN32
294 if (isLocal())
296 MPD::ItemList list;
297 GetLocalDirectory(list, item.name, 1);
298 for (MPD::ItemList::const_iterator j = list.begin(); j != list.end(); ++j)
299 v.push_back(j->song);
301 else
302 # endif // !WIN32
303 Mpd.GetDirectoryRecursive(locale_to_utf_cpy(item.name), v);
304 break;
306 case itSong:
308 v.push_back(new MPD::Song(*item.song));
309 break;
311 case itPlaylist:
313 Mpd.GetPlaylistContent(locale_to_utf_cpy(item.name), v);
314 break;
320 void Browser::ApplyFilter(const std::string &s)
322 w->ApplyFilter(s, itsBrowsedDir == "/" ? 0 : 1, REG_ICASE | Config.regex_type);
325 bool Browser::hasSupportedExtension(const std::string &file)
327 size_t last_dot = file.rfind(".");
328 if (last_dot > file.length())
329 return false;
331 std::string ext = file.substr(last_dot+1);
332 ToLower(ext);
333 for (int i = 0; SupportedExtensions[i]; ++i)
334 if (strcmp(ext.c_str(), SupportedExtensions[i]) == 0)
335 return true;
337 return false;
340 void Browser::LocateSong(const MPD::Song &s)
342 if (s.GetDirectory().empty())
343 return;
345 itsBrowseLocally = !s.isFromDB();
347 if (myScreen != this)
348 SwitchTo();
350 if (itsBrowsedDir != s.GetDirectory())
351 GetDirectory(s.GetDirectory());
352 for (size_t i = 0; i < w->Size(); ++i)
354 if ((*w)[i].type == itSong && s.GetHash() == (*w)[i].song->GetHash())
356 w->Highlight(i);
357 break;
362 void Browser::GetDirectory(std::string dir, std::string subdir)
364 if (dir.empty())
365 dir = "/";
367 int highlightme = -1;
368 itsScrollBeginning = 0;
369 if (itsBrowsedDir != dir)
370 w->Reset();
371 itsBrowsedDir = dir;
373 locale_to_utf(dir);
375 for (size_t i = 0; i < w->Size(); ++i)
376 if (w->at(i).type == itSong)
377 delete w->at(i).song;
379 w->Clear();
381 if (dir != "/")
383 MPD::Item parent;
384 size_t slash = dir.rfind("/");
385 parent.song = reinterpret_cast<MPD::Song *>(1); // in that way we assume that's really parent dir
386 parent.name = slash != std::string::npos ? dir.substr(0, slash) : "/";
387 parent.type = itDirectory;
388 utf_to_locale(parent.name);
389 w->AddOption(parent);
392 MPD::ItemList list;
393 # ifndef WIN32
394 isLocal() ? GetLocalDirectory(list) : Mpd.GetDirectory(dir, list);
395 # else
396 Mpd.GetDirectory(dir, list);
397 # endif // !WIN32
398 if (!isLocal()) // local directory is already sorted
399 sort(list.begin(), list.end(), CaseInsensitiveSorting());
401 for (MPD::ItemList::iterator it = list.begin(); it != list.end(); ++it)
403 switch (it->type)
405 case itPlaylist:
407 utf_to_locale(it->name);
408 w->AddOption(*it);
409 break;
411 case itDirectory:
413 utf_to_locale(it->name);
414 if (it->name == subdir)
415 highlightme = w->Size();
416 w->AddOption(*it);
417 break;
419 case itSong:
421 bool bold = 0;
422 for (size_t i = 0; i < myPlaylist->Items->Size(); ++i)
424 if (myPlaylist->Items->at(i).GetHash() == it->song->GetHash())
426 bold = 1;
427 break;
430 w->AddOption(*it, bold);
431 break;
435 if (highlightme >= 0)
436 w->Highlight(highlightme);
439 #ifndef WIN32
440 void Browser::GetLocalDirectory(MPD::ItemList &v, const std::string &directory, bool recursively) const
442 DIR *dir = opendir((directory.empty() ? itsBrowsedDir : directory).c_str());
444 if (!dir)
445 return;
447 dirent *file;
449 struct stat file_stat;
450 std::string full_path;
452 size_t old_size = v.size();
453 while ((file = readdir(dir)))
455 // omit . and ..
456 if (file->d_name[0] == '.' && (file->d_name[1] == '\0' || (file->d_name[1] == '.' && file->d_name[2] == '\0')))
457 continue;
459 if (!Config.local_browser_show_hidden_files && file->d_name[0] == '.')
460 continue;
461 MPD::Item new_item;
462 full_path = directory.empty() ? itsBrowsedDir : directory;
463 if (itsBrowsedDir != "/")
464 full_path += "/";
465 full_path += file->d_name;
466 stat(full_path.c_str(), &file_stat);
467 if (S_ISDIR(file_stat.st_mode))
469 if (recursively)
471 GetLocalDirectory(v, full_path, 1);
472 old_size = v.size();
474 else
476 new_item.type = itDirectory;
477 new_item.name = full_path;
478 v.push_back(new_item);
481 else if (hasSupportedExtension(file->d_name))
483 new_item.type = itSong;
484 mpd_pair file_pair = { "file", full_path.c_str() };
485 new_item.song = new MPD::Song(mpd_song_begin(&file_pair));
486 # ifdef HAVE_TAGLIB_H
487 if (!recursively)
488 TagEditor::ReadTags(*new_item.song);
489 # endif // HAVE_TAGLIB_H
490 v.push_back(new_item);
493 closedir(dir);
494 std::sort(v.begin()+old_size, v.end(), CaseInsensitiveSorting());
497 void Browser::ClearDirectory(const std::string &path) const
499 DIR *dir = opendir(path.c_str());
500 if (!dir)
501 return;
503 dirent *file;
504 struct stat file_stat;
505 std::string full_path;
507 while ((file = readdir(dir)))
509 // omit . and ..
510 if (file->d_name[0] == '.' && (file->d_name[1] == '\0' || (file->d_name[1] == '.' && file->d_name[2] == '\0')))
511 continue;
513 full_path = path;
514 if (*full_path.rbegin() != '/')
515 full_path += '/';
516 full_path += file->d_name;
517 lstat(full_path.c_str(), &file_stat);
518 if (S_ISDIR(file_stat.st_mode))
519 ClearDirectory(full_path);
520 if (remove(full_path.c_str()) == 0)
522 static const char msg[] = "Deleting \"%s\"...";
523 ShowMessage(msg, Shorten(TO_WSTRING(full_path), COLS-static_strlen(msg)).c_str());
525 else
527 static const char msg[] = "Couldn't remove \"%s\": %s";
528 ShowMessage(msg, Shorten(TO_WSTRING(full_path), COLS-static_strlen(msg)-25).c_str(), strerror(errno));
531 closedir(dir);
534 void Browser::ChangeBrowseMode()
536 if (Mpd.GetHostname()[0] != '/')
537 return;
539 itsBrowseLocally = !itsBrowseLocally;
540 ShowMessage("Browse mode: %s", itsBrowseLocally ? "Local filesystem" : "MPD music dir");
541 itsBrowsedDir = itsBrowseLocally ? Config.GetHomeDirectory() : "/";
542 if (itsBrowseLocally && *itsBrowsedDir.rbegin() == '/')
543 itsBrowsedDir.resize(itsBrowsedDir.length()-1);
544 w->Reset();
545 GetDirectory(itsBrowsedDir);
546 RedrawHeader = 1;
549 bool Browser::DeleteItem(const MPD::Item &item)
551 // parent dir
552 if (item.type == itDirectory && item.song)
553 return false;
555 // playlist creatd by mpd
556 if (!isLocal() && item.type == itPlaylist && CurrentDir() == "/")
557 return Mpd.DeletePlaylist(locale_to_utf_cpy(item.name));
559 std::string path;
560 if (!isLocal())
561 path = Config.mpd_music_dir;
562 path += item.type == itSong ? item.song->GetFile() : item.name;
564 if (item.type == itDirectory)
565 ClearDirectory(path);
567 return remove(path.c_str()) == 0;
569 #endif // !WIN32
571 void Browser::UpdateItemList()
573 bool bold = 0;
574 for (size_t i = 0; i < w->Size(); ++i)
576 if (w->at(i).type == itSong)
578 for (size_t j = 0; j < myPlaylist->Items->Size(); ++j)
580 if (myPlaylist->Items->at(j).GetHash() == w->at(i).song->GetHash())
582 bold = 1;
583 break;
586 w->Bold(i, bold);
587 bold = 0;
590 w->Refresh();
593 std::string Browser::ItemToString(const MPD::Item &item, void *)
595 switch (item.type)
597 case MPD::itDirectory:
599 if (item.song)
600 return "[..]";
601 return "[" + ExtractTopName(item.name) + "]";
603 case MPD::itSong:
605 if (!Config.columns_in_browser)
606 return item.song->toString(Config.song_list_format_dollar_free);
607 else
608 return Playlist::SongInColumnsToString(*item.song, 0);
610 case MPD::itPlaylist:
612 return Config.browser_playlist_prefix.Str() + ExtractTopName(item.name);
614 default:
616 return "";