make MPD::Connection::Toggle() run playback if player is stopped
[ncmpcpp.git] / src / browser.cpp
blobb8b0e84f3d011f30eebe2311e1185628497d4220
1 /***************************************************************************
2 * Copyright (C) 2008-2009 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 namespace Global;
40 using namespace MPD;
42 Browser *myBrowser = new Browser;
44 const char *Browser::SupportedExtensions[] =
46 "wma", "asf", "rm", "mp1", "mp2", "mp3",
47 "mp4", "m4a", "flac", "ogg", "wav", "au",
48 "aiff", "aif", "ac3", "aac", "mpc", "it",
49 "mod", "s3m", "xm", "wv", 0
52 void Browser::Init()
54 w = new Menu<Item>(0, MainStartY, COLS, MainHeight, Config.columns_in_browser ? Display::Columns() : "", Config.main_color, brNone);
55 w->HighlightColor(Config.main_highlight_color);
56 w->CyclicScrolling(Config.use_cyclic_scrolling);
57 w->SetSelectPrefix(&Config.selected_item_prefix);
58 w->SetSelectSuffix(&Config.selected_item_suffix);
59 w->SetItemDisplayer(Display::Items);
60 w->SetGetStringFunction(ItemToString);
61 isInitialized = 1;
64 void Browser::Resize()
66 w->Resize(COLS, MainHeight);
67 w->MoveTo(0, MainStartY);
68 hasToBeResized = 0;
71 void Browser::SwitchTo()
73 if (myScreen == this)
75 # ifndef WIN32
76 myBrowser->ChangeBrowseMode();
77 # endif // !WIN32
80 if (!isInitialized)
81 Init();
83 if (hasToBeResized)
84 Resize();
86 if (isLocal()) // local browser doesn't support sorting by mtime
87 Config.browser_sort_by_mtime = 0;
89 w->Empty() ? myBrowser->GetDirectory(itsBrowsedDir) : myBrowser->UpdateItemList();
91 if (myScreen != this && myScreen->isTabbable())
92 myPrevScreen = myScreen;
93 myScreen = this;
94 RedrawHeader = 1;
97 std::basic_string<my_char_t> Browser::Title()
99 std::basic_string<my_char_t> result = U("Browse: ");
100 result += Scroller(TO_WSTRING(itsBrowsedDir), itsScrollBeginning, w->GetWidth()-result.length()-(Config.new_design ? 2 : VolumeState.length()));
101 return result;
104 void Browser::EnterPressed()
106 if (w->Empty())
107 return;
109 const Item &item = w->Current();
110 switch (item.type)
112 case itDirectory:
114 GetDirectory(item.name, itsBrowsedDir);
115 RedrawHeader = 1;
116 break;
118 case itSong:
120 w->Bold(w->Choice(), myPlaylist->Add(*item.song, w->isBold(), 1));
121 break;
123 case itPlaylist:
125 SongList list;
126 Mpd.GetPlaylistContent(locale_to_utf_cpy(item.name), list);
127 if (myPlaylist->Add(list, 1))
128 ShowMessage("Loading and playing playlist %s...", item.name.c_str());
129 FreeSongList(list);
130 break;
135 void Browser::SpacePressed()
137 if (w->Empty())
138 return;
140 if (Config.space_selects && w->Choice() >= (itsBrowsedDir != "/" ? 1 : 0))
142 w->Select(w->Choice(), !w->isSelected());
143 w->Scroll(wDown);
144 return;
147 if (itsBrowsedDir != "/" && w->Choice() == 0 /* parent dir */)
148 return;
150 const Item &item = w->Current();
151 switch (item.type)
153 case itDirectory:
155 if (itsBrowsedDir != "/" && !w->Choice())
156 break; // do not let add parent dir.
158 SongList list;
159 # ifndef WIN32
160 if (isLocal())
162 ItemList items;
163 ShowMessage("Scanning \"%s\"...", item.name.c_str());
164 myBrowser->GetLocalDirectory(items, item.name, 1);
165 list.reserve(items.size());
166 for (MPD::ItemList::const_iterator it = items.begin(); it != items.end(); ++it)
167 list.push_back(it->song);
169 else
170 # endif // !WIN32
171 Mpd.GetDirectoryRecursive(locale_to_utf_cpy(item.name), list);
173 if (myPlaylist->Add(list, 0))
174 ShowMessage("Added folder: %s", item.name.c_str());
176 FreeSongList(list);
177 break;
179 case itSong:
181 w->Bold(w->Choice(), myPlaylist->Add(*item.song, w->isBold(), 0));
182 break;
184 case itPlaylist:
186 SongList list;
187 Mpd.GetPlaylistContent(locale_to_utf_cpy(item.name), list);
188 if (myPlaylist->Add(list, 0))
189 ShowMessage("Loading playlist %s...", item.name.c_str());
190 FreeSongList(list);
191 break;
194 w->Scroll(wDown);
197 void Browser::MouseButtonPressed(MEVENT me)
199 if (w->Empty() || !w->hasCoords(me.x, me.y) || size_t(me.y) >= w->Size())
200 return;
201 if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
203 w->Goto(me.y);
204 switch (w->Current().type)
206 case itDirectory:
207 if (me.bstate & BUTTON1_PRESSED)
209 GetDirectory(w->Current().name);
210 RedrawHeader = 1;
212 else
214 size_t pos = w->Choice();
215 SpacePressed();
216 if (pos < w->Size()-1)
217 w->Scroll(wUp);
219 break;
220 case itPlaylist:
221 case itSong:
222 if (me.bstate & BUTTON1_PRESSED)
224 size_t pos = w->Choice();
225 SpacePressed();
226 if (pos < w->Size()-1)
227 w->Scroll(wUp);
229 else
230 EnterPressed();
231 break;
234 else
235 Screen< Menu<MPD::Item> >::MouseButtonPressed(me);
238 MPD::Song *Browser::CurrentSong()
240 return !w->Empty() && w->Current().type == itSong ? w->Current().song : 0;
243 void Browser::ReverseSelection()
245 w->ReverseSelection(itsBrowsedDir == "/" ? 0 : 1);
248 void Browser::GetSelectedSongs(MPD::SongList &v)
250 std::vector<size_t> selected;
251 w->GetSelected(selected);
252 if (selected.empty())
253 selected.push_back(w->Choice());
254 for (std::vector<size_t>::const_iterator it = selected.begin(); it != selected.end(); ++it)
256 const Item &item = w->at(*it);
257 switch (item.type)
259 case itDirectory:
261 # ifndef WIN32
262 if (isLocal())
264 MPD::ItemList list;
265 GetLocalDirectory(list, item.name, 1);
266 for (MPD::ItemList::const_iterator j = list.begin(); j != list.end(); ++j)
267 v.push_back(j->song);
269 else
270 # endif // !WIN32
271 Mpd.GetDirectoryRecursive(locale_to_utf_cpy(item.name), v);
272 break;
274 case itSong:
276 v.push_back(new Song(*item.song));
277 break;
279 case itPlaylist:
281 Mpd.GetPlaylistContent(locale_to_utf_cpy(item.name), v);
282 break;
288 void Browser::ApplyFilter(const std::string &s)
290 w->ApplyFilter(s, itsBrowsedDir == "/" ? 0 : 1, REG_ICASE | Config.regex_type);
293 bool Browser::hasSupportedExtension(const std::string &file)
295 size_t last_dot = file.rfind(".");
296 if (last_dot > file.length())
297 return false;
299 std::string ext = file.substr(last_dot+1);
300 ToLower(ext);
301 for (int i = 0; SupportedExtensions[i]; ++i)
302 if (strcmp(ext.c_str(), SupportedExtensions[i]) == 0)
303 return true;
305 return false;
308 void Browser::LocateSong(const MPD::Song &s)
310 if (s.GetDirectory().empty())
311 return;
313 itsBrowseLocally = !s.isFromDB();
315 if (myScreen != this)
316 SwitchTo();
318 std::string option = s.toString(Config.song_status_format);
319 locale_to_utf(option);
320 if (itsBrowsedDir != s.GetDirectory())
321 GetDirectory(s.GetDirectory());
322 for (size_t i = 0; i < w->Size(); ++i)
324 if (w->at(i).type == itSong && option == w->at(i).song->toString(Config.song_status_format))
326 w->Highlight(i);
327 break;
332 void Browser::GetDirectory(std::string dir, std::string subdir)
334 if (dir.empty())
335 dir = "/";
337 int highlightme = -1;
338 itsScrollBeginning = 0;
339 if (itsBrowsedDir != dir)
340 w->Reset();
341 itsBrowsedDir = dir;
343 locale_to_utf(dir);
345 for (size_t i = 0; i < w->Size(); ++i)
346 if (w->at(i).type == itSong)
347 delete w->at(i).song;
349 w->Clear();
351 if (dir != "/")
353 Item parent;
354 size_t slash = dir.rfind("/");
355 parent.song = reinterpret_cast<Song *>(1); // in that way we assume that's really parent dir
356 parent.name = slash != std::string::npos ? dir.substr(0, slash) : "/";
357 parent.type = itDirectory;
358 utf_to_locale(parent.name);
359 w->AddOption(parent);
362 ItemList list;
363 # ifndef WIN32
364 isLocal() ? GetLocalDirectory(list) : Mpd.GetDirectory(dir, list);
365 # else
366 Mpd.GetDirectory(dir, list);
367 # endif // !WIN32
368 if (!isLocal()) // local directory is already sorted
369 sort(list.begin(), list.end(), CaseInsensitiveSorting());
371 for (ItemList::iterator it = list.begin(); it != list.end(); ++it)
373 switch (it->type)
375 case itPlaylist:
377 utf_to_locale(it->name);
378 w->AddOption(*it);
379 break;
381 case itDirectory:
383 utf_to_locale(it->name);
384 if (it->name == subdir)
385 highlightme = w->Size();
386 w->AddOption(*it);
387 break;
389 case itSong:
391 bool bold = 0;
392 for (size_t i = 0; i < myPlaylist->Items->Size(); ++i)
394 if (myPlaylist->Items->at(i).GetHash() == it->song->GetHash())
396 bold = 1;
397 break;
400 w->AddOption(*it, bold);
401 break;
405 if (highlightme >= 0)
406 w->Highlight(highlightme);
409 #ifndef WIN32
410 void Browser::GetLocalDirectory(ItemList &v, const std::string &directory, bool recursively) const
412 DIR *dir = opendir((directory.empty() ? itsBrowsedDir : directory).c_str());
414 if (!dir)
415 return;
417 dirent *file;
419 struct stat file_stat;
420 std::string full_path;
422 // omit . and ..
423 for (int i = 0; i < 2; ++i)
425 file = readdir(dir);
426 if (!file)
428 closedir(dir);
429 return;
433 size_t old_size = v.size();
434 while ((file = readdir(dir)))
436 if (!Config.local_browser_show_hidden_files && file->d_name[0] == '.')
437 continue;
438 Item new_item;
439 full_path = directory.empty() ? itsBrowsedDir : directory;
440 if (itsBrowsedDir != "/")
441 full_path += "/";
442 full_path += file->d_name;
443 stat(full_path.c_str(), &file_stat);
444 if (S_ISDIR(file_stat.st_mode))
446 if (recursively)
448 GetLocalDirectory(v, full_path, 1);
449 old_size = v.size();
451 else
453 new_item.type = itDirectory;
454 new_item.name = full_path;
455 v.push_back(new_item);
458 else if (hasSupportedExtension(file->d_name))
460 new_item.type = itSong;
461 mpd_pair file_pair = { "file", full_path.c_str() };
462 new_item.song = new Song(mpd_song_begin(&file_pair));
463 # ifdef HAVE_TAGLIB_H
464 if (!recursively)
465 TagEditor::ReadTags(*new_item.song);
466 # endif // HAVE_TAGLIB_H
467 v.push_back(new_item);
470 closedir(dir);
471 std::sort(v.begin()+old_size, v.end(), CaseInsensitiveSorting());
474 void Browser::ClearDirectory(const std::string &path) const
476 DIR *dir = opendir(path.c_str());
477 if (!dir)
478 return;
480 dirent *file;
481 struct stat file_stat;
482 std::string full_path;
484 // omit . and ..
485 for (int i = 0; i < 2; ++i)
487 file = readdir(dir);
488 if (!file)
490 closedir(dir);
491 return;
495 while ((file = readdir(dir)))
497 full_path = path;
498 if (*full_path.rbegin() != '/')
499 full_path += '/';
500 full_path += file->d_name;
501 lstat(full_path.c_str(), &file_stat);
502 if (S_ISDIR(file_stat.st_mode))
503 ClearDirectory(full_path);
504 if (remove(full_path.c_str()) == 0)
506 static const char msg[] = "Deleting \"%s\"...";
507 ShowMessage(msg, Shorten(TO_WSTRING(full_path), COLS-static_strlen(msg)).c_str());
509 else
511 static const char msg[] = "Couldn't remove \"%s\": %s";
512 ShowMessage(msg, Shorten(TO_WSTRING(full_path), COLS-static_strlen(msg)-25).c_str(), strerror(errno));
515 closedir(dir);
518 void Browser::ChangeBrowseMode()
520 if (Mpd.GetHostname()[0] != '/')
521 return;
523 itsBrowseLocally = !itsBrowseLocally;
524 ShowMessage("Browse mode: %s", itsBrowseLocally ? "Local filesystem" : "MPD music dir");
525 itsBrowsedDir = itsBrowseLocally ? home_path : "/";
526 w->Reset();
527 GetDirectory(itsBrowsedDir);
528 RedrawHeader = 1;
530 #endif // !WIN32
532 void Browser::UpdateItemList()
534 bool bold = 0;
535 for (size_t i = 0; i < w->Size(); ++i)
537 if (w->at(i).type == itSong)
539 for (size_t j = 0; j < myPlaylist->Items->Size(); ++j)
541 if (myPlaylist->Items->at(j).GetHash() == w->at(i).song->GetHash())
543 bold = 1;
544 break;
547 w->Bold(i, bold);
548 bold = 0;
551 w->Refresh();
554 std::string Browser::ItemToString(const MPD::Item &item, void *)
556 switch (item.type)
558 case MPD::itDirectory:
560 if (item.song)
561 return "[..]";
562 return "[" + ExtractTopDirectory(item.name) + "]";
564 case MPD::itSong:
566 if (!Config.columns_in_browser)
567 return item.song->toString(Config.song_list_format);
568 else
569 return Playlist::SongInColumnsToString(*item.song, 0);
571 case MPD::itPlaylist:
573 return Config.browser_playlist_prefix.Str() + item.name;
575 default:
577 return "";