1 /***************************************************************************
2 * Copyright (C) 2008-2011 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 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
);
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()) : "");
84 void Browser::SwitchTo()
86 using Global::myLockedScreen
;
87 using Global::myInactiveScreen
;
92 myBrowser
->ChangeBrowseMode();
100 UpdateInactiveScreen(this);
102 if (hasToBeResized
|| myLockedScreen
)
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
;
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()));
123 void Browser::EnterPressed()
128 const MPD::Item
&item
= w
->Current();
133 GetDirectory(item
.name
, itsBrowsedDir
);
139 w
->Bold(w
->Choice(), myPlaylist
->Add(*item
.song
, w
->isBold(), 1));
144 if (itsBrowsedDir
== "/")
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());
154 std::string name
= item
.name
;
155 ShowMessage("Loading playlist %s...", name
.c_str());
157 if (Mpd
.LoadPlaylist(name
))
158 ShowMessage("Playlist loaded.");
165 void Browser::SpacePressed()
170 if (Config
.space_selects
&& w
->Choice() >= (itsBrowsedDir
!= "/" ? 1 : 0))
172 w
->Select(w
->Choice(), !w
->isSelected());
177 if (itsBrowsedDir
!= "/" && w
->Choice() == 0 /* parent dir */)
180 const MPD::Item
&item
= w
->Current();
185 if (itsBrowsedDir
!= "/" && !w
->Choice())
186 break; // do not let add parent dir.
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
);
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());
211 w
->Bold(w
->Choice(), myPlaylist
->Add(*item
.song
, w
->isBold(), 0));
216 std::string name
= item
.name
;
217 ShowMessage("Loading playlist %s...", name
.c_str());
219 if (Mpd
.LoadPlaylist(name
))
220 ShowMessage("Playlist loaded.");
227 void Browser::MouseButtonPressed(MEVENT me
)
229 if (w
->Empty() || !w
->hasCoords(me
.x
, me
.y
) || size_t(me
.y
) >= w
->Size())
231 if (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
))
234 switch (w
->Current().type
)
237 if (me
.bstate
& BUTTON1_PRESSED
)
239 GetDirectory(w
->Current().name
);
244 size_t pos
= w
->Choice();
246 if (pos
< w
->Size()-1)
252 if (me
.bstate
& BUTTON1_PRESSED
)
254 size_t pos
= w
->Choice();
256 if (pos
< w
->Size()-1)
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
)
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
);
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
);
303 Mpd
.GetDirectoryRecursive(locale_to_utf_cpy(item
.name
), v
);
308 v
.push_back(new MPD::Song(*item
.song
));
313 Mpd
.GetPlaylistContent(locale_to_utf_cpy(item
.name
), v
);
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())
331 std::string ext
= file
.substr(last_dot
+1);
333 for (int i
= 0; SupportedExtensions
[i
]; ++i
)
334 if (strcmp(ext
.c_str(), SupportedExtensions
[i
]) == 0)
340 void Browser::LocateSong(const MPD::Song
&s
)
342 if (s
.GetDirectory().empty())
345 itsBrowseLocally
= !s
.isFromDB();
347 if (myScreen
!= this)
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())
362 void Browser::GetDirectory(std::string dir
, std::string subdir
)
367 int highlightme
= -1;
368 itsScrollBeginning
= 0;
369 if (itsBrowsedDir
!= dir
)
375 for (size_t i
= 0; i
< w
->Size(); ++i
)
376 if (w
->at(i
).type
== itSong
)
377 delete w
->at(i
).song
;
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
);
394 isLocal() ? GetLocalDirectory(list
) : Mpd
.GetDirectory(dir
, list
);
396 Mpd
.GetDirectory(dir
, list
);
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
)
407 utf_to_locale(it
->name
);
413 utf_to_locale(it
->name
);
414 if (it
->name
== subdir
)
415 highlightme
= w
->Size();
422 for (size_t i
= 0; i
< myPlaylist
->Items
->Size(); ++i
)
424 if (myPlaylist
->Items
->at(i
).GetHash() == it
->song
->GetHash())
430 w
->AddOption(*it
, bold
);
435 if (highlightme
>= 0)
436 w
->Highlight(highlightme
);
440 void Browser::GetLocalDirectory(MPD::ItemList
&v
, const std::string
&directory
, bool recursively
) const
442 DIR *dir
= opendir((directory
.empty() ? itsBrowsedDir
: directory
).c_str());
449 struct stat file_stat
;
450 std::string full_path
;
452 size_t old_size
= v
.size();
453 while ((file
= readdir(dir
)))
456 if (file
->d_name
[0] == '.' && (file
->d_name
[1] == '\0' || (file
->d_name
[1] == '.' && file
->d_name
[2] == '\0')))
459 if (!Config
.local_browser_show_hidden_files
&& file
->d_name
[0] == '.')
462 full_path
= directory
.empty() ? itsBrowsedDir
: directory
;
463 if (itsBrowsedDir
!= "/")
465 full_path
+= file
->d_name
;
466 stat(full_path
.c_str(), &file_stat
);
467 if (S_ISDIR(file_stat
.st_mode
))
471 GetLocalDirectory(v
, full_path
, 1);
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
488 TagEditor::ReadTags(*new_item
.song
);
489 # endif // HAVE_TAGLIB_H
490 v
.push_back(new_item
);
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());
504 struct stat file_stat
;
505 std::string full_path
;
507 while ((file
= readdir(dir
)))
510 if (file
->d_name
[0] == '.' && (file
->d_name
[1] == '\0' || (file
->d_name
[1] == '.' && file
->d_name
[2] == '\0')))
514 if (*full_path
.rbegin() != '/')
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());
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
));
534 void Browser::ChangeBrowseMode()
536 if (Mpd
.GetHostname()[0] != '/')
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);
545 GetDirectory(itsBrowsedDir
);
549 bool Browser::DeleteItem(const MPD::Item
&item
)
552 if (item
.type
== itDirectory
&& item
.song
)
555 // playlist creatd by mpd
556 if (!isLocal() && item
.type
== itPlaylist
&& CurrentDir() == "/")
557 return Mpd
.DeletePlaylist(locale_to_utf_cpy(item
.name
));
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;
571 void Browser::UpdateItemList()
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())
593 std::string
Browser::ItemToString(const MPD::Item
&item
, void *)
597 case MPD::itDirectory
:
601 return "[" + ExtractTopName(item
.name
) + "]";
605 if (!Config
.columns_in_browser
)
606 return item
.song
->toString(Config
.song_list_format_dollar_free
);
608 return Playlist::SongInColumnsToString(*item
.song
, 0);
610 case MPD::itPlaylist
:
612 return Config
.browser_playlist_prefix
.Str() + ExtractTopName(item
.name
);