1 /***************************************************************************
2 * Copyright (C) 2008-2010 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 Global::MainHeight
;
40 using Global::MainStartY
;
41 using Global::myScreen
;
42 using Global::RedrawHeader
;
44 using MPD::itDirectory
;
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
60 w
= new Menu
<MPD::Item
>(0, MainStartY
, COLS
, MainHeight
, Config
.columns_in_browser
? Display::Columns() : "", Config
.main_color
, brNone
);
61 w
->HighlightColor(Config
.main_highlight_color
);
62 w
->CyclicScrolling(Config
.use_cyclic_scrolling
);
63 w
->CenteredCursor(Config
.centered_cursor
);
64 w
->SetSelectPrefix(&Config
.selected_item_prefix
);
65 w
->SetSelectSuffix(&Config
.selected_item_suffix
);
66 w
->SetItemDisplayer(Display::Items
);
67 w
->SetGetStringFunction(ItemToString
);
71 void Browser::Resize()
73 w
->Resize(COLS
, MainHeight
);
74 w
->MoveTo(0, MainStartY
);
78 void Browser::SwitchTo()
83 myBrowser
->ChangeBrowseMode();
93 if (isLocal()) // local browser doesn't support sorting by mtime
94 Config
.browser_sort_by_mtime
= 0;
96 w
->Empty() ? myBrowser
->GetDirectory(itsBrowsedDir
) : myBrowser
->UpdateItemList();
98 if (myScreen
!= this && myScreen
->isTabbable())
99 Global::myPrevScreen
= myScreen
;
104 std::basic_string
<my_char_t
> Browser::Title()
106 std::basic_string
<my_char_t
> result
= U("Browse: ");
107 result
+= Scroller(TO_WSTRING(itsBrowsedDir
), itsScrollBeginning
, w
->GetWidth()-result
.length()-(Config
.new_design
? 2 : Global::VolumeState
.length()));
111 void Browser::EnterPressed()
116 const MPD::Item
&item
= w
->Current();
121 GetDirectory(item
.name
, itsBrowsedDir
);
127 w
->Bold(w
->Choice(), myPlaylist
->Add(*item
.song
, w
->isBold(), 1));
133 Mpd
.GetPlaylistContent(locale_to_utf_cpy(item
.name
), list
);
134 if (myPlaylist
->Add(list
, 1))
135 ShowMessage("Loading and playing playlist %s...", item
.name
.c_str());
142 void Browser::SpacePressed()
147 if (Config
.space_selects
&& w
->Choice() >= (itsBrowsedDir
!= "/" ? 1 : 0))
149 w
->Select(w
->Choice(), !w
->isSelected());
154 if (itsBrowsedDir
!= "/" && w
->Choice() == 0 /* parent dir */)
157 const MPD::Item
&item
= w
->Current();
162 if (itsBrowsedDir
!= "/" && !w
->Choice())
163 break; // do not let add parent dir.
170 ShowMessage("Scanning \"%s\"...", item
.name
.c_str());
171 myBrowser
->GetLocalDirectory(items
, item
.name
, 1);
172 list
.reserve(items
.size());
173 for (MPD::ItemList::const_iterator it
= items
.begin(); it
!= items
.end(); ++it
)
174 list
.push_back(it
->song
);
178 Mpd
.GetDirectoryRecursive(locale_to_utf_cpy(item
.name
), list
);
180 if (myPlaylist
->Add(list
, 0))
181 ShowMessage("Added folder: %s", item
.name
.c_str());
188 w
->Bold(w
->Choice(), myPlaylist
->Add(*item
.song
, w
->isBold(), 0));
194 Mpd
.GetPlaylistContent(locale_to_utf_cpy(item
.name
), list
);
195 if (myPlaylist
->Add(list
, 0))
196 ShowMessage("Loading playlist %s...", item
.name
.c_str());
204 void Browser::MouseButtonPressed(MEVENT me
)
206 if (w
->Empty() || !w
->hasCoords(me
.x
, me
.y
) || size_t(me
.y
) >= w
->Size())
208 if (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
))
211 switch (w
->Current().type
)
214 if (me
.bstate
& BUTTON1_PRESSED
)
216 GetDirectory(w
->Current().name
);
221 size_t pos
= w
->Choice();
223 if (pos
< w
->Size()-1)
229 if (me
.bstate
& BUTTON1_PRESSED
)
231 size_t pos
= w
->Choice();
233 if (pos
< w
->Size()-1)
242 Screen
< Menu
<MPD::Item
> >::MouseButtonPressed(me
);
245 MPD::Song
*Browser::CurrentSong()
247 return !w
->Empty() && w
->Current().type
== itSong
? w
->Current().song
: 0;
250 void Browser::ReverseSelection()
252 w
->ReverseSelection(itsBrowsedDir
== "/" ? 0 : 1);
255 void Browser::GetSelectedSongs(MPD::SongList
&v
)
257 std::vector
<size_t> selected
;
258 w
->GetSelected(selected
);
259 if (selected
.empty())
260 selected
.push_back(w
->Choice());
261 for (std::vector
<size_t>::const_iterator it
= selected
.begin(); it
!= selected
.end(); ++it
)
263 const MPD::Item
&item
= w
->at(*it
);
272 GetLocalDirectory(list
, item
.name
, 1);
273 for (MPD::ItemList::const_iterator j
= list
.begin(); j
!= list
.end(); ++j
)
274 v
.push_back(j
->song
);
278 Mpd
.GetDirectoryRecursive(locale_to_utf_cpy(item
.name
), v
);
283 v
.push_back(new MPD::Song(*item
.song
));
288 Mpd
.GetPlaylistContent(locale_to_utf_cpy(item
.name
), v
);
295 void Browser::ApplyFilter(const std::string
&s
)
297 w
->ApplyFilter(s
, itsBrowsedDir
== "/" ? 0 : 1, REG_ICASE
| Config
.regex_type
);
300 bool Browser::hasSupportedExtension(const std::string
&file
)
302 size_t last_dot
= file
.rfind(".");
303 if (last_dot
> file
.length())
306 std::string ext
= file
.substr(last_dot
+1);
308 for (int i
= 0; SupportedExtensions
[i
]; ++i
)
309 if (strcmp(ext
.c_str(), SupportedExtensions
[i
]) == 0)
315 void Browser::LocateSong(const MPD::Song
&s
)
317 if (s
.GetDirectory().empty())
320 itsBrowseLocally
= !s
.isFromDB();
322 if (myScreen
!= this)
325 std::string option
= s
.toString(Config
.song_status_format
);
326 locale_to_utf(option
);
327 if (itsBrowsedDir
!= s
.GetDirectory())
328 GetDirectory(s
.GetDirectory());
329 for (size_t i
= 0; i
< w
->Size(); ++i
)
331 if (w
->at(i
).type
== itSong
&& option
== w
->at(i
).song
->toString(Config
.song_status_format
))
339 void Browser::GetDirectory(std::string dir
, std::string subdir
)
344 int highlightme
= -1;
345 itsScrollBeginning
= 0;
346 if (itsBrowsedDir
!= dir
)
352 for (size_t i
= 0; i
< w
->Size(); ++i
)
353 if (w
->at(i
).type
== itSong
)
354 delete w
->at(i
).song
;
361 size_t slash
= dir
.rfind("/");
362 parent
.song
= reinterpret_cast<MPD::Song
*>(1); // in that way we assume that's really parent dir
363 parent
.name
= slash
!= std::string::npos
? dir
.substr(0, slash
) : "/";
364 parent
.type
= itDirectory
;
365 utf_to_locale(parent
.name
);
366 w
->AddOption(parent
);
371 isLocal() ? GetLocalDirectory(list
) : Mpd
.GetDirectory(dir
, list
);
373 Mpd
.GetDirectory(dir
, list
);
375 if (!isLocal()) // local directory is already sorted
376 sort(list
.begin(), list
.end(), CaseInsensitiveSorting());
378 for (MPD::ItemList::iterator it
= list
.begin(); it
!= list
.end(); ++it
)
384 utf_to_locale(it
->name
);
390 utf_to_locale(it
->name
);
391 if (it
->name
== subdir
)
392 highlightme
= w
->Size();
399 for (size_t i
= 0; i
< myPlaylist
->Items
->Size(); ++i
)
401 if (myPlaylist
->Items
->at(i
).GetHash() == it
->song
->GetHash())
407 w
->AddOption(*it
, bold
);
412 if (highlightme
>= 0)
413 w
->Highlight(highlightme
);
417 void Browser::GetLocalDirectory(MPD::ItemList
&v
, const std::string
&directory
, bool recursively
) const
419 DIR *dir
= opendir((directory
.empty() ? itsBrowsedDir
: directory
).c_str());
426 struct stat file_stat
;
427 std::string full_path
;
430 for (int i
= 0; i
< 2; ++i
)
440 size_t old_size
= v
.size();
441 while ((file
= readdir(dir
)))
443 if (!Config
.local_browser_show_hidden_files
&& file
->d_name
[0] == '.')
446 full_path
= directory
.empty() ? itsBrowsedDir
: directory
;
447 if (itsBrowsedDir
!= "/")
449 full_path
+= file
->d_name
;
450 stat(full_path
.c_str(), &file_stat
);
451 if (S_ISDIR(file_stat
.st_mode
))
455 GetLocalDirectory(v
, full_path
, 1);
460 new_item
.type
= itDirectory
;
461 new_item
.name
= full_path
;
462 v
.push_back(new_item
);
465 else if (hasSupportedExtension(file
->d_name
))
467 new_item
.type
= itSong
;
468 mpd_pair file_pair
= { "file", full_path
.c_str() };
469 new_item
.song
= new MPD::Song(mpd_song_begin(&file_pair
));
470 # ifdef HAVE_TAGLIB_H
472 TagEditor::ReadTags(*new_item
.song
);
473 # endif // HAVE_TAGLIB_H
474 v
.push_back(new_item
);
478 std::sort(v
.begin()+old_size
, v
.end(), CaseInsensitiveSorting());
481 void Browser::ClearDirectory(const std::string
&path
) const
483 DIR *dir
= opendir(path
.c_str());
488 struct stat file_stat
;
489 std::string full_path
;
492 for (int i
= 0; i
< 2; ++i
)
502 while ((file
= readdir(dir
)))
505 if (*full_path
.rbegin() != '/')
507 full_path
+= file
->d_name
;
508 lstat(full_path
.c_str(), &file_stat
);
509 if (S_ISDIR(file_stat
.st_mode
))
510 ClearDirectory(full_path
);
511 if (remove(full_path
.c_str()) == 0)
513 static const char msg
[] = "Deleting \"%s\"...";
514 ShowMessage(msg
, Shorten(TO_WSTRING(full_path
), COLS
-static_strlen(msg
)).c_str());
518 static const char msg
[] = "Couldn't remove \"%s\": %s";
519 ShowMessage(msg
, Shorten(TO_WSTRING(full_path
), COLS
-static_strlen(msg
)-25).c_str(), strerror(errno
));
525 void Browser::ChangeBrowseMode()
527 if (Mpd
.GetHostname()[0] != '/')
530 itsBrowseLocally
= !itsBrowseLocally
;
531 ShowMessage("Browse mode: %s", itsBrowseLocally
? "Local filesystem" : "MPD music dir");
532 itsBrowsedDir
= itsBrowseLocally
? home_path
: "/";
534 GetDirectory(itsBrowsedDir
);
539 void Browser::UpdateItemList()
542 for (size_t i
= 0; i
< w
->Size(); ++i
)
544 if (w
->at(i
).type
== itSong
)
546 for (size_t j
= 0; j
< myPlaylist
->Items
->Size(); ++j
)
548 if (myPlaylist
->Items
->at(j
).GetHash() == w
->at(i
).song
->GetHash())
561 std::string
Browser::ItemToString(const MPD::Item
&item
, void *)
565 case MPD::itDirectory
:
569 return "[" + ExtractTopDirectory(item
.name
) + "]";
573 if (!Config
.columns_in_browser
)
574 return item
.song
->toString(Config
.song_list_format
);
576 return Playlist::SongInColumnsToString(*item
.song
, 0);
578 case MPD::itPlaylist
:
580 return Config
.browser_playlist_prefix
.Str() + item
.name
;