1 /***************************************************************************
2 * Copyright (C) 2008-2009 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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 *
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 ***************************************************************************/
36 # include "tag_editor.h"
37 #endif // HAVE_TAGLIB_H
39 using namespace Global
;
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
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
);
64 void Browser::Resize()
66 w
->Resize(COLS
, MainHeight
);
67 w
->MoveTo(0, MainStartY
);
71 void Browser::SwitchTo()
76 myBrowser
->ChangeBrowseMode();
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();
94 std::basic_string
<my_char_t
> Browser::Title()
96 std::basic_string
<my_char_t
> result
= U("Browse: ");
97 result
+= Scroller(TO_WSTRING(itsBrowsedDir
), itsScrollBeginning
, w
->GetWidth()-result
.length()-(Config
.new_design
? 2 : VolumeState
.length()));
101 void Browser::EnterPressed()
106 const Item
&item
= w
->Current();
111 GetDirectory(item
.name
, itsBrowsedDir
);
117 w
->Bold(w
->Choice(), myPlaylist
->Add(*item
.song
, w
->isBold(), 1));
123 Mpd
.GetPlaylistContent(locale_to_utf_cpy(item
.name
), list
);
124 if (myPlaylist
->Add(list
, 1))
125 ShowMessage("Loading and playing playlist %s...", item
.name
.c_str());
132 void Browser::SpacePressed()
137 if (Config
.space_selects
&& w
->Choice() >= (itsBrowsedDir
!= "/" ? 1 : 0))
139 w
->Select(w
->Choice(), !w
->isSelected());
144 if (itsBrowsedDir
!= "/" && w
->Choice() == 0 /* parent dir */)
147 const Item
&item
= w
->Current();
152 if (itsBrowsedDir
!= "/" && !w
->Choice())
153 break; // do not let add parent dir.
160 ShowMessage("Scanning \"%s\"...", item
.name
.c_str());
161 myBrowser
->GetLocalDirectory(items
, item
.name
, 1);
162 list
.reserve(items
.size());
163 for (MPD::ItemList::const_iterator it
= items
.begin(); it
!= items
.end(); ++it
)
164 list
.push_back(it
->song
);
168 Mpd
.GetDirectoryRecursive(locale_to_utf_cpy(item
.name
), list
);
170 if (myPlaylist
->Add(list
, 0))
171 ShowMessage("Added folder: %s", item
.name
.c_str());
178 w
->Bold(w
->Choice(), myPlaylist
->Add(*item
.song
, w
->isBold(), 0));
184 Mpd
.GetPlaylistContent(locale_to_utf_cpy(item
.name
), list
);
185 if (myPlaylist
->Add(list
, 0))
186 ShowMessage("Loading playlist %s...", item
.name
.c_str());
194 void Browser::MouseButtonPressed(MEVENT me
)
196 if (w
->Empty() || !w
->hasCoords(me
.x
, me
.y
) || size_t(me
.y
) >= w
->Size())
198 if (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
))
201 switch (w
->Current().type
)
204 if (me
.bstate
& BUTTON1_PRESSED
)
206 GetDirectory(w
->Current().name
);
211 size_t pos
= w
->Choice();
213 if (pos
< w
->Size()-1)
219 if (me
.bstate
& BUTTON1_PRESSED
)
221 size_t pos
= w
->Choice();
223 if (pos
< w
->Size()-1)
232 Screen
< Menu
<MPD::Item
> >::MouseButtonPressed(me
);
235 MPD::Song
*Browser::CurrentSong()
237 return !w
->Empty() && w
->Current().type
== itSong
? w
->Current().song
: 0;
240 void Browser::ReverseSelection()
242 w
->ReverseSelection(itsBrowsedDir
== "/" ? 0 : 1);
245 void Browser::GetSelectedSongs(MPD::SongList
&v
)
247 std::vector
<size_t> selected
;
248 w
->GetSelected(selected
);
249 for (std::vector
<size_t>::const_iterator it
= selected
.begin(); it
!= selected
.end(); ++it
)
251 const Item
&item
= w
->at(*it
);
256 Mpd
.GetDirectoryRecursive(locale_to_utf_cpy(item
.name
), v
);
261 v
.push_back(new Song(*item
.song
));
266 Mpd
.GetPlaylistContent(locale_to_utf_cpy(item
.name
), v
);
273 void Browser::ApplyFilter(const std::string
&s
)
275 w
->ApplyFilter(s
, itsBrowsedDir
== "/" ? 0 : 1, REG_ICASE
| Config
.regex_type
);
278 bool Browser::hasSupportedExtension(const std::string
&file
)
280 size_t last_dot
= file
.rfind(".");
281 if (last_dot
> file
.length())
284 std::string ext
= file
.substr(last_dot
+1);
286 for (int i
= 0; SupportedExtensions
[i
]; ++i
)
287 if (strcmp(ext
.c_str(), SupportedExtensions
[i
]) == 0)
293 void Browser::LocateSong(const MPD::Song
&s
)
295 if (s
.GetDirectory().empty())
298 itsBrowseLocally
= !s
.isFromDB();
300 if (myScreen
!= this)
303 std::string option
= s
.toString(Config
.song_status_format
);
304 locale_to_utf(option
);
305 if (itsBrowsedDir
!= s
.GetDirectory())
306 GetDirectory(s
.GetDirectory());
307 for (size_t i
= 0; i
< w
->Size(); ++i
)
309 if (w
->at(i
).type
== itSong
&& option
== w
->at(i
).song
->toString(Config
.song_status_format
))
317 void Browser::GetDirectory(std::string dir
, std::string subdir
)
322 int highlightme
= -1;
323 itsScrollBeginning
= 0;
324 if (itsBrowsedDir
!= dir
)
330 for (size_t i
= 0; i
< w
->Size(); ++i
)
331 if (w
->at(i
).type
== itSong
)
332 delete w
->at(i
).song
;
339 size_t slash
= dir
.rfind("/");
340 parent
.song
= reinterpret_cast<Song
*>(1); // in that way we assume that's really parent dir
341 parent
.name
= slash
!= std::string::npos
? dir
.substr(0, slash
) : "/";
342 parent
.type
= itDirectory
;
343 utf_to_locale(parent
.name
);
344 w
->AddOption(parent
);
349 isLocal() ? GetLocalDirectory(list
) : Mpd
.GetDirectory(dir
, list
);
351 Mpd
.GetDirectory(dir
, list
);
353 if (!isLocal()) // local directory is already sorted
354 sort(list
.begin(), list
.end(), CaseInsensitiveSorting());
356 for (ItemList::iterator it
= list
.begin(); it
!= list
.end(); ++it
)
362 utf_to_locale(it
->name
);
368 utf_to_locale(it
->name
);
369 if (it
->name
== subdir
)
370 highlightme
= w
->Size();
377 for (size_t i
= 0; i
< myPlaylist
->Items
->Size(); ++i
)
379 if (myPlaylist
->Items
->at(i
).GetHash() == it
->song
->GetHash())
385 w
->AddOption(*it
, bold
);
390 if (highlightme
>= 0)
391 w
->Highlight(highlightme
);
395 void Browser::GetLocalDirectory(ItemList
&v
, const std::string
&directory
, bool recursively
) const
397 DIR *dir
= opendir((directory
.empty() ? itsBrowsedDir
: directory
).c_str());
404 struct stat file_stat
;
405 std::string full_path
;
408 for (int i
= 0; i
< 2; ++i
)
418 size_t old_size
= v
.size();
419 while ((file
= readdir(dir
)))
421 if (!Config
.local_browser_show_hidden_files
&& file
->d_name
[0] == '.')
424 full_path
= directory
.empty() ? itsBrowsedDir
: directory
;
425 if (itsBrowsedDir
!= "/")
427 full_path
+= file
->d_name
;
428 stat(full_path
.c_str(), &file_stat
);
429 if (S_ISDIR(file_stat
.st_mode
))
433 GetLocalDirectory(v
, full_path
, 1);
438 new_item
.type
= itDirectory
;
439 new_item
.name
= full_path
;
440 v
.push_back(new_item
);
443 else if (hasSupportedExtension(file
->d_name
))
445 new_item
.type
= itSong
;
446 mpd_pair file_pair
= { "file", full_path
.c_str() };
447 new_item
.song
= new Song(mpd_song_begin(&file_pair
));
448 # ifdef HAVE_TAGLIB_H
450 TagEditor::ReadTags(*new_item
.song
);
451 # endif // HAVE_TAGLIB_H
452 v
.push_back(new_item
);
456 std::sort(v
.begin()+old_size
, v
.end(), CaseInsensitiveSorting());
459 void Browser::ClearDirectory(const std::string
&path
) const
461 DIR *dir
= opendir(path
.c_str());
466 struct stat file_stat
;
467 std::string full_path
;
470 for (int i
= 0; i
< 2; ++i
)
480 while ((file
= readdir(dir
)))
483 if (*full_path
.rbegin() != '/')
485 full_path
+= file
->d_name
;
486 lstat(full_path
.c_str(), &file_stat
);
487 if (S_ISDIR(file_stat
.st_mode
))
488 ClearDirectory(full_path
);
489 if (remove(full_path
.c_str()) == 0)
491 static const char msg
[] = "Deleting \"%s\"...";
492 ShowMessage(msg
, Shorten(TO_WSTRING(full_path
), COLS
-static_strlen(msg
)).c_str());
496 static const char msg
[] = "Couldn't remove \"%s\": %s";
497 ShowMessage(msg
, Shorten(TO_WSTRING(full_path
), COLS
-static_strlen(msg
)-25).c_str(), strerror(errno
));
503 void Browser::ChangeBrowseMode()
505 if (Mpd
.GetHostname()[0] != '/')
508 itsBrowseLocally
= !itsBrowseLocally
;
509 ShowMessage("Browse mode: %s", itsBrowseLocally
? "Local filesystem" : "MPD music dir");
510 itsBrowsedDir
= itsBrowseLocally
? home_path
: "/";
512 GetDirectory(itsBrowsedDir
);
517 void Browser::UpdateItemList()
520 for (size_t i
= 0; i
< w
->Size(); ++i
)
522 if (w
->at(i
).type
== itSong
)
524 for (size_t j
= 0; j
< myPlaylist
->Items
->Size(); ++j
)
526 if (myPlaylist
->Items
->at(j
).GetHash() == w
->at(i
).song
->GetHash())
539 std::string
Browser::ItemToString(const MPD::Item
&item
, void *)
543 case MPD::itDirectory
:
547 return "[" + ExtractTopDirectory(item
.name
) + "]";
551 if (!Config
.columns_in_browser
)
552 return item
.song
->toString(Config
.song_list_format
);
554 return Playlist::SongInColumnsToString(*item
.song
, 0);
556 case MPD::itPlaylist
:
558 return Config
.browser_playlist_prefix
.Str() + item
.name
;