implement Searchable interface / make use of unnamed namespaces
This commit is contained in:
@@ -337,9 +337,8 @@ void Action::FindItem(const FindDirection fd)
|
|||||||
{
|
{
|
||||||
using Global::wFooter;
|
using Global::wFooter;
|
||||||
|
|
||||||
List *mList = myScreen->GetList();
|
Searchable *w = dynamic_cast<Searchable *>(myScreen);
|
||||||
if (!mList)
|
assert(w);
|
||||||
return;
|
|
||||||
|
|
||||||
LockStatusbar();
|
LockStatusbar();
|
||||||
Statusbar() << "Find " << (fd == fdForward ? "forward" : "backward") << ": ";
|
Statusbar() << "Find " << (fd == fdForward ? "forward" : "backward") << ": ";
|
||||||
@@ -349,7 +348,7 @@ void Action::FindItem(const FindDirection fd)
|
|||||||
if (!findme.empty())
|
if (!findme.empty())
|
||||||
ShowMessage("Searching...");
|
ShowMessage("Searching...");
|
||||||
|
|
||||||
bool success = mList->Search(findme, 0, REG_ICASE | Config.regex_type);
|
bool success = w->search(findme);
|
||||||
|
|
||||||
if (findme.empty())
|
if (findme.empty())
|
||||||
return;
|
return;
|
||||||
@@ -359,10 +358,10 @@ void Action::FindItem(const FindDirection fd)
|
|||||||
else
|
else
|
||||||
ShowMessage("Unable to find \"%s\"", findme.c_str());
|
ShowMessage("Unable to find \"%s\"", findme.c_str());
|
||||||
|
|
||||||
if (fd == fdForward)
|
if (fd == fdForward)
|
||||||
mList->NextFound(Config.wrapped_search);
|
w->nextFound(Config.wrapped_search);
|
||||||
else
|
else
|
||||||
mList->PrevFound(Config.wrapped_search);
|
w->prevFound(Config.wrapped_search);
|
||||||
|
|
||||||
if (myScreen == myPlaylist)
|
if (myScreen == myPlaylist)
|
||||||
myPlaylist->EnableHighlighting();
|
myPlaylist->EnableHighlighting();
|
||||||
@@ -377,7 +376,7 @@ void Action::ListsChangeFinisher()
|
|||||||
# endif // HAVE_TAGLIB_H
|
# endif // HAVE_TAGLIB_H
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (myScreen->ActiveWindow() == myLibrary->Artists)
|
if (myScreen->ActiveWindow() == myLibrary->Tags)
|
||||||
{
|
{
|
||||||
myLibrary->Albums->Clear();
|
myLibrary->Albums->Clear();
|
||||||
myLibrary->Songs->Clear();
|
myLibrary->Songs->Clear();
|
||||||
@@ -816,7 +815,7 @@ void Delete::Run()
|
|||||||
ShowMessage("Deleting directories is disabled by default, see man page for more details");
|
ShowMessage("Deleting directories is disabled by default, see man page for more details");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (myBrowser->isParentDir(myBrowser->Main()->Choice()))
|
if (myBrowser->isParentDirectory(item))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
std::string name = item.type == itSong ? item.song->getName() : item.name;
|
std::string name = item.type == itSong ? item.song->getName() : item.name;
|
||||||
@@ -1038,9 +1037,6 @@ void MoveSelectedItemsTo::Run()
|
|||||||
ShowMessage("No selected items to move");
|
ShowMessage("No selected items to move");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// remove search results as we may move them to different positions, but
|
|
||||||
// search rememebers positions and may point to wrong ones after that.
|
|
||||||
myPlaylist->Items->Search("");
|
|
||||||
size_t pos = myPlaylist->Items->Choice();
|
size_t pos = myPlaylist->Items->Choice();
|
||||||
std::vector<size_t> list;
|
std::vector<size_t> list;
|
||||||
myPlaylist->Items->GetSelected(list);
|
myPlaylist->Items->GetSelected(list);
|
||||||
@@ -1163,13 +1159,11 @@ void ToggleDisplayMode::Run()
|
|||||||
{
|
{
|
||||||
myPlaylist->Items->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *myPlaylist));
|
myPlaylist->Items->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *myPlaylist));
|
||||||
myPlaylist->Items->SetTitle(Config.titles_visibility ? Display::Columns(myPlaylist->Items->GetWidth()) : "");
|
myPlaylist->Items->SetTitle(Config.titles_visibility ? Display::Columns(myPlaylist->Items->GetWidth()) : "");
|
||||||
myPlaylist->Items->SetItemStringifier(Playlist::SongInColumnsToString);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
myPlaylist->Items->setItemDisplayer(std::bind(Display::Songs, _1, *myPlaylist, Config.song_list_format));
|
myPlaylist->Items->setItemDisplayer(std::bind(Display::Songs, _1, *myPlaylist, Config.song_list_format));
|
||||||
myPlaylist->Items->SetTitle("");
|
myPlaylist->Items->SetTitle("");
|
||||||
myPlaylist->Items->SetItemStringifier(Playlist::SongToString);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (myScreen == myBrowser)
|
else if (myScreen == myBrowser)
|
||||||
@@ -1192,12 +1186,10 @@ void ToggleDisplayMode::Run()
|
|||||||
if (Config.columns_in_playlist_editor)
|
if (Config.columns_in_playlist_editor)
|
||||||
{
|
{
|
||||||
myPlaylistEditor->Content->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *myPlaylistEditor));
|
myPlaylistEditor->Content->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *myPlaylistEditor));
|
||||||
myPlaylistEditor->Content->SetItemStringifier(Playlist::SongInColumnsToString);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
myPlaylistEditor->Content->setItemDisplayer(std::bind(Display::Songs, _1, *myPlaylistEditor, Config.song_list_format));
|
myPlaylistEditor->Content->setItemDisplayer(std::bind(Display::Songs, _1, *myPlaylistEditor, Config.song_list_format));
|
||||||
myPlaylistEditor->Content->SetItemStringifier(Playlist::SongToString);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1406,8 +1398,8 @@ void EditSong::Run()
|
|||||||
bool EditLibraryTag::canBeRun() const
|
bool EditLibraryTag::canBeRun() const
|
||||||
{
|
{
|
||||||
# ifdef HAVE_TAGLIB_H
|
# ifdef HAVE_TAGLIB_H
|
||||||
return myScreen->ActiveWindow() == myLibrary->Artists
|
return myScreen->ActiveWindow() == myLibrary->Tags
|
||||||
&& !myLibrary->Artists->Empty();
|
&& !myLibrary->Tags->Empty();
|
||||||
# else
|
# else
|
||||||
return false;
|
return false;
|
||||||
# endif // HAVE_TAGLIB_H
|
# endif // HAVE_TAGLIB_H
|
||||||
@@ -1422,13 +1414,13 @@ void EditLibraryTag::Run()
|
|||||||
return;
|
return;
|
||||||
LockStatusbar();
|
LockStatusbar();
|
||||||
Statusbar() << fmtBold << tagTypeToString(Config.media_lib_primary_tag) << fmtBoldEnd << ": ";
|
Statusbar() << fmtBold << tagTypeToString(Config.media_lib_primary_tag) << fmtBoldEnd << ": ";
|
||||||
std::string new_tag = wFooter->GetString(myLibrary->Artists->Current().value());
|
std::string new_tag = wFooter->GetString(myLibrary->Tags->Current().value());
|
||||||
UnlockStatusbar();
|
UnlockStatusbar();
|
||||||
if (!new_tag.empty() && new_tag != myLibrary->Artists->Current().value())
|
if (!new_tag.empty() && new_tag != myLibrary->Tags->Current().value())
|
||||||
{
|
{
|
||||||
ShowMessage("Updating tags...");
|
ShowMessage("Updating tags...");
|
||||||
Mpd.StartSearch(1);
|
Mpd.StartSearch(1);
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(myLibrary->Artists->Current().value()));
|
Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(myLibrary->Tags->Current().value()));
|
||||||
MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
|
MPD::MutableSong::SetFunction set = tagTypeToSetFunction(Config.media_lib_primary_tag);
|
||||||
assert(set);
|
assert(set);
|
||||||
bool success = true;
|
bool success = true;
|
||||||
@@ -2064,33 +2056,51 @@ void Find::Run()
|
|||||||
s->Main()->Flush();
|
s->Main()->Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FindItemBackward::canBeRun() const
|
||||||
|
{
|
||||||
|
return dynamic_cast<Searchable *>(myScreen);
|
||||||
|
}
|
||||||
|
|
||||||
void FindItemForward::Run()
|
void FindItemForward::Run()
|
||||||
{
|
{
|
||||||
FindItem(fdForward);
|
FindItem(fdForward);
|
||||||
ListsChangeFinisher();
|
ListsChangeFinisher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FindItemForward::canBeRun() const
|
||||||
|
{
|
||||||
|
return dynamic_cast<Searchable *>(myScreen);
|
||||||
|
}
|
||||||
|
|
||||||
void FindItemBackward::Run()
|
void FindItemBackward::Run()
|
||||||
{
|
{
|
||||||
FindItem(fdBackward);
|
FindItem(fdBackward);
|
||||||
ListsChangeFinisher();
|
ListsChangeFinisher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NextFoundItem::canBeRun() const
|
||||||
|
{
|
||||||
|
return dynamic_cast<Searchable *>(myScreen);
|
||||||
|
}
|
||||||
|
|
||||||
void NextFoundItem::Run()
|
void NextFoundItem::Run()
|
||||||
{
|
{
|
||||||
List *mList = myScreen->GetList();
|
Searchable *w = dynamic_cast<Searchable *>(myScreen);
|
||||||
if (!mList)
|
assert(w);
|
||||||
return;
|
w->nextFound(Config.wrapped_search);
|
||||||
mList->NextFound(Config.wrapped_search);
|
|
||||||
ListsChangeFinisher();
|
ListsChangeFinisher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PreviousFoundItem::canBeRun() const
|
||||||
|
{
|
||||||
|
return dynamic_cast<Searchable *>(myScreen);
|
||||||
|
}
|
||||||
|
|
||||||
void PreviousFoundItem::Run()
|
void PreviousFoundItem::Run()
|
||||||
{
|
{
|
||||||
List *mList = myScreen->GetList();
|
Searchable *w = dynamic_cast<Searchable *>(myScreen);
|
||||||
if (!mList)
|
assert(w);
|
||||||
return;
|
w->prevFound(Config.wrapped_search);
|
||||||
mList->PrevFound(Config.wrapped_search);
|
|
||||||
ListsChangeFinisher();
|
ListsChangeFinisher();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2209,7 +2219,7 @@ void ToggleBrowserSortMode::Run()
|
|||||||
|
|
||||||
bool ToggleLibraryTagType::canBeRun() const
|
bool ToggleLibraryTagType::canBeRun() const
|
||||||
{
|
{
|
||||||
return (myScreen->ActiveWindow() == myLibrary->Artists)
|
return (myScreen->ActiveWindow() == myLibrary->Tags)
|
||||||
|| (myLibrary->Columns() == 2 && myScreen->ActiveWindow() == myLibrary->Albums);
|
|| (myLibrary->Columns() == 2 && myScreen->ActiveWindow() == myLibrary->Albums);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2233,8 +2243,8 @@ void ToggleLibraryTagType::Run()
|
|||||||
{
|
{
|
||||||
Config.media_lib_primary_tag = new_tagitem;
|
Config.media_lib_primary_tag = new_tagitem;
|
||||||
std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
|
std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
|
||||||
myLibrary->Artists->SetTitle(Config.titles_visibility ? item_type + "s" : "");
|
myLibrary->Tags->SetTitle(Config.titles_visibility ? item_type + "s" : "");
|
||||||
myLibrary->Artists->Reset();
|
myLibrary->Tags->Reset();
|
||||||
lowercase(item_type);
|
lowercase(item_type);
|
||||||
if (myLibrary->Columns() == 2)
|
if (myLibrary->Columns() == 2)
|
||||||
{
|
{
|
||||||
@@ -2246,8 +2256,8 @@ void ToggleLibraryTagType::Run()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
myLibrary->Artists->Clear();
|
myLibrary->Tags->Clear();
|
||||||
myLibrary->Artists->Display();
|
myLibrary->Tags->Display();
|
||||||
}
|
}
|
||||||
ShowMessage("Switched to list of %s tag", item_type.c_str());
|
ShowMessage("Switched to list of %s tag", item_type.c_str());
|
||||||
}
|
}
|
||||||
@@ -2345,8 +2355,8 @@ void ShowArtistInfo::Run()
|
|||||||
|
|
||||||
if (s)
|
if (s)
|
||||||
artist = s->getArtist();
|
artist = s->getArtist();
|
||||||
else if (myScreen == myLibrary && myLibrary->Main() == myLibrary->Artists && !myLibrary->Artists->Empty())
|
else if (myScreen == myLibrary && myLibrary->Main() == myLibrary->Tags && !myLibrary->Tags->Empty())
|
||||||
artist = myLibrary->Artists->Current().value();
|
artist = myLibrary->Tags->Current().value();
|
||||||
|
|
||||||
if (!artist.empty() && myLastfm->SetArtistInfoArgs(artist, Config.lastfm_preferred_language))
|
if (!artist.empty() && myLastfm->SetArtistInfoArgs(artist, Config.lastfm_preferred_language))
|
||||||
myLastfm->SwitchTo();
|
myLastfm->SwitchTo();
|
||||||
|
|||||||
@@ -655,24 +655,28 @@ struct Find : public Action
|
|||||||
struct FindItemForward : public Action
|
struct FindItemForward : public Action
|
||||||
{
|
{
|
||||||
FindItemForward() : Action(aFindItemForward, "find_item_forward") { }
|
FindItemForward() : Action(aFindItemForward, "find_item_forward") { }
|
||||||
|
virtual bool canBeRun() const;
|
||||||
virtual void Run();
|
virtual void Run();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FindItemBackward : public Action
|
struct FindItemBackward : public Action
|
||||||
{
|
{
|
||||||
FindItemBackward() : Action(aFindItemBackward, "find_item_backward") { }
|
FindItemBackward() : Action(aFindItemBackward, "find_item_backward") { }
|
||||||
|
virtual bool canBeRun() const;
|
||||||
virtual void Run();
|
virtual void Run();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NextFoundItem : public Action
|
struct NextFoundItem : public Action
|
||||||
{
|
{
|
||||||
NextFoundItem() : Action(aNextFoundItem, "next_found_item") { }
|
NextFoundItem() : Action(aNextFoundItem, "next_found_item") { }
|
||||||
|
virtual bool canBeRun() const;
|
||||||
virtual void Run();
|
virtual void Run();
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PreviousFoundItem : public Action
|
struct PreviousFoundItem : public Action
|
||||||
{
|
{
|
||||||
PreviousFoundItem() : Action(aPreviousFoundItem, "previous_found_item") { }
|
PreviousFoundItem() : Action(aPreviousFoundItem, "previous_found_item") { }
|
||||||
|
virtual bool canBeRun() const;
|
||||||
virtual void Run();
|
virtual void Run();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -32,10 +32,8 @@
|
|||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "status.h"
|
#include "status.h"
|
||||||
|
#include "tag_editor.h"
|
||||||
#include "utility/comparators.h"
|
#include "utility/comparators.h"
|
||||||
#ifdef HAVE_TAGLIB_H
|
|
||||||
# include "tag_editor.h"
|
|
||||||
#endif // HAVE_TAGLIB_H
|
|
||||||
|
|
||||||
using Global::MainHeight;
|
using Global::MainHeight;
|
||||||
using Global::MainStartY;
|
using Global::MainStartY;
|
||||||
@@ -48,7 +46,15 @@ using MPD::itPlaylist;
|
|||||||
|
|
||||||
Browser *myBrowser = new Browser;
|
Browser *myBrowser = new Browser;
|
||||||
|
|
||||||
std::set<std::string> Browser::SupportedExtensions;
|
namespace {//
|
||||||
|
|
||||||
|
std::set<std::string> SupportedExtensions;
|
||||||
|
bool hasSupportedExtension(const std::string &file);
|
||||||
|
|
||||||
|
std::string ItemToString(const MPD::Item &item);
|
||||||
|
bool BrowserEntryMatcher(const Regex &rx, const MPD::Item &item, bool filter);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void Browser::Init()
|
void Browser::Init()
|
||||||
{
|
{
|
||||||
@@ -59,7 +65,6 @@ void Browser::Init()
|
|||||||
w->SetSelectPrefix(Config.selected_item_prefix);
|
w->SetSelectPrefix(Config.selected_item_prefix);
|
||||||
w->SetSelectSuffix(Config.selected_item_suffix);
|
w->SetSelectSuffix(Config.selected_item_suffix);
|
||||||
w->setItemDisplayer(Display::Items);
|
w->setItemDisplayer(Display::Items);
|
||||||
w->SetItemStringifier(ItemToString);
|
|
||||||
|
|
||||||
if (SupportedExtensions.empty())
|
if (SupportedExtensions.empty())
|
||||||
Mpd.GetSupportedExtensions(SupportedExtensions);
|
Mpd.GetSupportedExtensions(SupportedExtensions);
|
||||||
@@ -126,7 +131,7 @@ void Browser::EnterPressed()
|
|||||||
{
|
{
|
||||||
case itDirectory:
|
case itDirectory:
|
||||||
{
|
{
|
||||||
if (isParentDir(w->Choice()))
|
if (isParentDirectory(item))
|
||||||
{
|
{
|
||||||
size_t slash = itsBrowsedDir.rfind("/");
|
size_t slash = itsBrowsedDir.rfind("/");
|
||||||
if (slash != std::string::npos)
|
if (slash != std::string::npos)
|
||||||
@@ -171,10 +176,11 @@ void Browser::SpacePressed()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isParentDir(w->Choice()))
|
const MPD::Item &item = w->Current().value();
|
||||||
|
|
||||||
|
if (isParentDirectory(item))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const MPD::Item &item = w->Current().value();
|
|
||||||
switch (item.type)
|
switch (item.type)
|
||||||
{
|
{
|
||||||
case itDirectory:
|
case itDirectory:
|
||||||
@@ -319,6 +325,8 @@ void Browser::GetSelectedSongs(MPD::SongList &v)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
std::string Browser::currentFilter()
|
std::string Browser::currentFilter()
|
||||||
{
|
{
|
||||||
return RegexFilter<MPD::Item>::currentFilter(*w);
|
return RegexFilter<MPD::Item>::currentFilter(*w);
|
||||||
@@ -327,26 +335,32 @@ std::string Browser::currentFilter()
|
|||||||
void Browser::applyFilter(const std::string &filter)
|
void Browser::applyFilter(const std::string &filter)
|
||||||
{
|
{
|
||||||
w->ShowAll();
|
w->ShowAll();
|
||||||
auto fun = [](const Regex &rx, Menu<MPD::Item> &menu, const Menu<MPD::Item>::Item &item) {
|
auto fun = std::bind(BrowserEntryMatcher, _1, _2, true);
|
||||||
if (item.value().type == MPD::itDirectory && item.value().name == "..")
|
|
||||||
return true;
|
|
||||||
return rx.match(menu.Stringify(item));
|
|
||||||
};
|
|
||||||
auto rx = RegexFilter<MPD::Item>(filter, Config.regex_type, fun);
|
auto rx = RegexFilter<MPD::Item>(filter, Config.regex_type, fun);
|
||||||
w->Filter(w->Begin(), w->End(), rx);
|
w->filter(w->Begin(), w->End(), rx);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Browser::hasSupportedExtension(const std::string &file)
|
/***********************************************************************/
|
||||||
|
|
||||||
|
bool Browser::search(const std::string &constraint)
|
||||||
{
|
{
|
||||||
size_t last_dot = file.rfind(".");
|
auto fun = std::bind(BrowserEntryMatcher, _1, _2, false);
|
||||||
if (last_dot > file.length())
|
auto rx = RegexFilter<MPD::Item>(constraint, Config.regex_type, fun);
|
||||||
return false;
|
return w->search(w->Begin(), w->End(), rx);
|
||||||
|
|
||||||
std::string ext = file.substr(last_dot+1);
|
|
||||||
lowercase(ext);
|
|
||||||
return SupportedExtensions.find(ext) != SupportedExtensions.end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Browser::nextFound(bool wrap)
|
||||||
|
{
|
||||||
|
w->NextFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Browser::prevFound(bool wrap)
|
||||||
|
{
|
||||||
|
w->PrevFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
void Browser::LocateSong(const MPD::Song &s)
|
void Browser::LocateSong(const MPD::Song &s)
|
||||||
{
|
{
|
||||||
if (s.getDirectory().empty())
|
if (s.getDirectory().empty())
|
||||||
@@ -592,7 +606,20 @@ void Browser::UpdateItemList()
|
|||||||
w->Refresh();
|
w->Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Browser::ItemToString(const MPD::Item &item)
|
namespace {//
|
||||||
|
|
||||||
|
bool hasSupportedExtension(const std::string &file)
|
||||||
|
{
|
||||||
|
size_t last_dot = file.rfind(".");
|
||||||
|
if (last_dot > file.length())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string ext = file.substr(last_dot+1);
|
||||||
|
lowercase(ext);
|
||||||
|
return SupportedExtensions.find(ext) != SupportedExtensions.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ItemToString(const MPD::Item &item)
|
||||||
{
|
{
|
||||||
std::string result;
|
std::string result;
|
||||||
switch (item.type)
|
switch (item.type)
|
||||||
@@ -601,13 +628,23 @@ std::string Browser::ItemToString(const MPD::Item &item)
|
|||||||
result = "[" + getBasename(item.name) + "]";
|
result = "[" + getBasename(item.name) + "]";
|
||||||
break;
|
break;
|
||||||
case MPD::itSong:
|
case MPD::itSong:
|
||||||
if (!Config.columns_in_browser)
|
if (Config.columns_in_browser)
|
||||||
result = item.song->toString(Config.song_list_format_dollar_free);
|
result = item.song->toString(Config.song_in_columns_to_string_format);
|
||||||
else
|
else
|
||||||
result = Playlist::SongInColumnsToString(*item.song);
|
result = item.song->toString(Config.song_list_format_dollar_free);
|
||||||
|
break;
|
||||||
case MPD::itPlaylist:
|
case MPD::itPlaylist:
|
||||||
result = Config.browser_playlist_prefix.Str() + getBasename(item.name);
|
result = Config.browser_playlist_prefix.Str() + getBasename(item.name);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool BrowserEntryMatcher(const Regex &rx, const MPD::Item &item, bool filter)
|
||||||
|
{
|
||||||
|
if (Browser::isParentDirectory(item))
|
||||||
|
return filter;
|
||||||
|
return rx.match(ItemToString(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
#include "regex_filter.h"
|
#include "regex_filter.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
|
||||||
class Browser : public Screen< Menu<MPD::Item> >, public Filterable
|
class Browser : public Screen< Menu<MPD::Item> >, public Filterable, public Searchable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Browser() : itsBrowseLocally(0), itsScrollBeginning(0), itsBrowsedDir("/") { }
|
Browser() : itsBrowseLocally(0), itsScrollBeginning(0), itsBrowsedDir("/") { }
|
||||||
@@ -48,9 +48,15 @@ class Browser : public Screen< Menu<MPD::Item> >, public Filterable
|
|||||||
virtual void ReverseSelection();
|
virtual void ReverseSelection();
|
||||||
virtual void GetSelectedSongs(MPD::SongList &);
|
virtual void GetSelectedSongs(MPD::SongList &);
|
||||||
|
|
||||||
|
/// Filterable implementation
|
||||||
virtual std::string currentFilter();
|
virtual std::string currentFilter();
|
||||||
virtual void applyFilter(const std::string &filter);
|
virtual void applyFilter(const std::string &filter);
|
||||||
|
|
||||||
|
/// Searchable implementation
|
||||||
|
virtual bool search(const std::string &constraint);
|
||||||
|
virtual void nextFound(bool wrap);
|
||||||
|
virtual void prevFound(bool wrap);
|
||||||
|
|
||||||
virtual List *GetList() { return w; }
|
virtual List *GetList() { return w; }
|
||||||
|
|
||||||
virtual bool isMergable() { return true; }
|
virtual bool isMergable() { return true; }
|
||||||
@@ -68,18 +74,15 @@ class Browser : public Screen< Menu<MPD::Item> >, public Filterable
|
|||||||
# endif // !WIN32
|
# endif // !WIN32
|
||||||
void UpdateItemList();
|
void UpdateItemList();
|
||||||
|
|
||||||
bool isParentDir(size_t pos) { return itsBrowsedDir != "/" && pos == 0; }
|
static bool isParentDirectory(const MPD::Item &item) {
|
||||||
|
return item.type == MPD::itDirectory && item.name == "..";
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void Init();
|
virtual void Init();
|
||||||
virtual bool isLockable() { return true; }
|
virtual bool isLockable() { return true; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool hasSupportedExtension(const std::string &);
|
|
||||||
static std::string ItemToString(const MPD::Item &item);
|
|
||||||
|
|
||||||
static std::set<std::string> SupportedExtensions;
|
|
||||||
|
|
||||||
bool itsBrowseLocally;
|
bool itsBrowseLocally;
|
||||||
size_t itsScrollBeginning;
|
size_t itsScrollBeginning;
|
||||||
std::string itsBrowsedDir;
|
std::string itsBrowsedDir;
|
||||||
|
|||||||
@@ -30,4 +30,11 @@ struct Filterable
|
|||||||
virtual void applyFilter(const std::string &filter) = 0;
|
virtual void applyFilter(const std::string &filter) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Searchable
|
||||||
|
{
|
||||||
|
virtual bool search(const std::string &constraint) = 0;
|
||||||
|
virtual void nextFound(bool wrap) = 0;
|
||||||
|
virtual void prevFound(bool wrap) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
#endif // _INTERFACES_H
|
#endif // _INTERFACES_H
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "media_library.h"
|
#include "media_library.h"
|
||||||
#include "mpdpp.h"
|
#include "mpdpp.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
|
#include "regex_filter.h"
|
||||||
#include "status.h"
|
#include "status.h"
|
||||||
#include "utility/comparators.h"
|
#include "utility/comparators.h"
|
||||||
#include "utility/type_conversions.h"
|
#include "utility/type_conversions.h"
|
||||||
@@ -38,19 +39,39 @@ using Global::myScreen;
|
|||||||
|
|
||||||
MediaLibrary *myLibrary = new MediaLibrary;
|
MediaLibrary *myLibrary = new MediaLibrary;
|
||||||
|
|
||||||
bool MediaLibrary::hasTwoColumns;
|
namespace {//
|
||||||
size_t MediaLibrary::itsLeftColStartX;
|
|
||||||
size_t MediaLibrary::itsLeftColWidth;
|
bool hasTwoColumns;
|
||||||
size_t MediaLibrary::itsMiddleColWidth;
|
size_t itsLeftColStartX;
|
||||||
size_t MediaLibrary::itsMiddleColStartX;
|
size_t itsLeftColWidth;
|
||||||
size_t MediaLibrary::itsRightColWidth;
|
size_t itsMiddleColWidth;
|
||||||
size_t MediaLibrary::itsRightColStartX;
|
size_t itsMiddleColStartX;
|
||||||
|
size_t itsRightColWidth;
|
||||||
|
size_t itsRightColStartX;
|
||||||
|
|
||||||
// this string marks the position in middle column that works as "All tracks" option. it's
|
// this string marks the position in middle column that works as "All tracks" option. it's
|
||||||
// assigned to Date in SearchConstraint class since date normally cannot contain other chars
|
// assigned to Date in SearchConstraint class since date normally cannot contain other chars
|
||||||
// than ciphers and -'s (0x7f is interpreted as backspace keycode, so it's quite safe to assume
|
// than ciphers and -'s (0x7f is interpreted as backspace keycode, so it's quite safe to assume
|
||||||
// that it won't appear in any tag, let alone date).
|
// that it won't appear in any tag, let alone date).
|
||||||
const char MediaLibrary::AllTracksMarker[] = "\x7f";
|
const std::string AllTracksMarker = "\x7f_\x7f_\x7f";
|
||||||
|
|
||||||
|
typedef MediaLibrary::SearchConstraints SearchConstraints;
|
||||||
|
|
||||||
|
std::string AlbumToString(const SearchConstraints &sc);
|
||||||
|
std::string SongToString(const MPD::Song &s);
|
||||||
|
|
||||||
|
bool TagEntryMatcher(const Regex &rx, const std::string &tag);
|
||||||
|
bool AlbumEntryMatcher(const Regex &rx, const Menu<SearchConstraints>::Item &item, bool filter);
|
||||||
|
bool SongEntryMatcher(const Regex &rx, const MPD::Song &s);
|
||||||
|
|
||||||
|
void DisplayAlbums(Menu<SearchConstraints> &menu);
|
||||||
|
void DisplayPrimaryTags(Menu<std::string> &menu);
|
||||||
|
|
||||||
|
bool SortSongsByTrack(const MPD::Song &a, const MPD::Song &b);
|
||||||
|
bool SortAllTracks(const MPD::Song &a, const MPD::Song &b);
|
||||||
|
bool SortSearchConstraints(const SearchConstraints &a, const SearchConstraints &b);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void MediaLibrary::Init()
|
void MediaLibrary::Init()
|
||||||
{
|
{
|
||||||
@@ -61,13 +82,13 @@ void MediaLibrary::Init()
|
|||||||
itsRightColWidth = COLS-COLS/3*2-1;
|
itsRightColWidth = COLS-COLS/3*2-1;
|
||||||
itsRightColStartX = itsLeftColWidth+itsMiddleColWidth+2;
|
itsRightColStartX = itsLeftColWidth+itsMiddleColWidth+2;
|
||||||
|
|
||||||
Artists = new Menu<std::string>(0, MainStartY, itsLeftColWidth, MainHeight, Config.titles_visibility ? tagTypeToString(Config.media_lib_primary_tag) + "s" : "", Config.main_color, brNone);
|
Tags = new Menu<std::string>(0, MainStartY, itsLeftColWidth, MainHeight, Config.titles_visibility ? tagTypeToString(Config.media_lib_primary_tag) + "s" : "", Config.main_color, brNone);
|
||||||
Artists->HighlightColor(Config.active_column_color);
|
Tags->HighlightColor(Config.active_column_color);
|
||||||
Artists->CyclicScrolling(Config.use_cyclic_scrolling);
|
Tags->CyclicScrolling(Config.use_cyclic_scrolling);
|
||||||
Artists->CenteredCursor(Config.centered_cursor);
|
Tags->CenteredCursor(Config.centered_cursor);
|
||||||
Artists->SetSelectPrefix(Config.selected_item_prefix);
|
Tags->SetSelectPrefix(Config.selected_item_prefix);
|
||||||
Artists->SetSelectSuffix(Config.selected_item_suffix);
|
Tags->SetSelectSuffix(Config.selected_item_suffix);
|
||||||
Artists->setItemDisplayer(DisplayPrimaryTags);
|
Tags->setItemDisplayer(DisplayPrimaryTags);
|
||||||
|
|
||||||
Albums = new Menu<SearchConstraints>(itsMiddleColStartX, MainStartY, itsMiddleColWidth, MainHeight, Config.titles_visibility ? "Albums" : "", Config.main_color, brNone);
|
Albums = new Menu<SearchConstraints>(itsMiddleColStartX, MainStartY, itsMiddleColWidth, MainHeight, Config.titles_visibility ? "Albums" : "", Config.main_color, brNone);
|
||||||
Albums->HighlightColor(Config.main_highlight_color);
|
Albums->HighlightColor(Config.main_highlight_color);
|
||||||
@@ -76,7 +97,6 @@ void MediaLibrary::Init()
|
|||||||
Albums->SetSelectPrefix(Config.selected_item_prefix);
|
Albums->SetSelectPrefix(Config.selected_item_prefix);
|
||||||
Albums->SetSelectSuffix(Config.selected_item_suffix);
|
Albums->SetSelectSuffix(Config.selected_item_suffix);
|
||||||
Albums->setItemDisplayer(DisplayAlbums);
|
Albums->setItemDisplayer(DisplayAlbums);
|
||||||
Albums->SetItemStringifier(AlbumToString);
|
|
||||||
|
|
||||||
Songs = new Menu<MPD::Song>(itsRightColStartX, MainStartY, itsRightColWidth, MainHeight, Config.titles_visibility ? "Songs" : "", Config.main_color, brNone);
|
Songs = new Menu<MPD::Song>(itsRightColStartX, MainStartY, itsRightColWidth, MainHeight, Config.titles_visibility ? "Songs" : "", Config.main_color, brNone);
|
||||||
Songs->HighlightColor(Config.main_highlight_color);
|
Songs->HighlightColor(Config.main_highlight_color);
|
||||||
@@ -85,9 +105,8 @@ void MediaLibrary::Init()
|
|||||||
Songs->SetSelectPrefix(Config.selected_item_prefix);
|
Songs->SetSelectPrefix(Config.selected_item_prefix);
|
||||||
Songs->SetSelectSuffix(Config.selected_item_suffix);
|
Songs->SetSelectSuffix(Config.selected_item_suffix);
|
||||||
Songs->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_library_format));
|
Songs->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_library_format));
|
||||||
Songs->SetItemStringifier(SongToString);
|
|
||||||
|
|
||||||
w = Artists;
|
w = Tags;
|
||||||
isInitialized = 1;
|
isInitialized = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +131,11 @@ void MediaLibrary::Resize()
|
|||||||
itsRightColWidth = width-itsMiddleColWidth-1;
|
itsRightColWidth = width-itsMiddleColWidth-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Artists->Resize(itsLeftColWidth, MainHeight);
|
Tags->Resize(itsLeftColWidth, MainHeight);
|
||||||
Albums->Resize(itsMiddleColWidth, MainHeight);
|
Albums->Resize(itsMiddleColWidth, MainHeight);
|
||||||
Songs->Resize(itsRightColWidth, MainHeight);
|
Songs->Resize(itsRightColWidth, MainHeight);
|
||||||
|
|
||||||
Artists->MoveTo(itsLeftColStartX, MainStartY);
|
Tags->MoveTo(itsLeftColStartX, MainStartY);
|
||||||
Albums->MoveTo(itsMiddleColStartX, MainStartY);
|
Albums->MoveTo(itsMiddleColStartX, MainStartY);
|
||||||
Songs->MoveTo(itsRightColStartX, MainStartY);
|
Songs->MoveTo(itsRightColStartX, MainStartY);
|
||||||
|
|
||||||
@@ -125,7 +144,7 @@ void MediaLibrary::Resize()
|
|||||||
|
|
||||||
void MediaLibrary::Refresh()
|
void MediaLibrary::Refresh()
|
||||||
{
|
{
|
||||||
Artists->Display();
|
Tags->Display();
|
||||||
mvvline(MainStartY, itsMiddleColStartX-1, 0, MainHeight);
|
mvvline(MainStartY, itsMiddleColStartX-1, 0, MainHeight);
|
||||||
Albums->Display();
|
Albums->Display();
|
||||||
mvvline(MainStartY, itsRightColStartX-1, 0, MainHeight);
|
mvvline(MainStartY, itsRightColStartX-1, 0, MainHeight);
|
||||||
@@ -149,13 +168,13 @@ void MediaLibrary::SwitchTo()
|
|||||||
{
|
{
|
||||||
hasTwoColumns = !hasTwoColumns;
|
hasTwoColumns = !hasTwoColumns;
|
||||||
hasToBeResized = 1;
|
hasToBeResized = 1;
|
||||||
Artists->Clear();
|
Tags->Clear();
|
||||||
Albums->Clear();
|
Albums->Clear();
|
||||||
Albums->Reset();
|
Albums->Reset();
|
||||||
Songs->Clear();
|
Songs->Clear();
|
||||||
if (hasTwoColumns)
|
if (hasTwoColumns)
|
||||||
{
|
{
|
||||||
if (w == Artists)
|
if (w == Tags)
|
||||||
NextColumn();
|
NextColumn();
|
||||||
if (Config.titles_visibility)
|
if (Config.titles_visibility)
|
||||||
{
|
{
|
||||||
@@ -195,7 +214,7 @@ std::basic_string<my_char_t> MediaLibrary::Title()
|
|||||||
|
|
||||||
void MediaLibrary::Update()
|
void MediaLibrary::Update()
|
||||||
{
|
{
|
||||||
if (!hasTwoColumns && Artists->ReallyEmpty())
|
if (!hasTwoColumns && Tags->ReallyEmpty())
|
||||||
{
|
{
|
||||||
MPD::TagList list;
|
MPD::TagList list;
|
||||||
Albums->Clear();
|
Albums->Clear();
|
||||||
@@ -207,13 +226,13 @@ void MediaLibrary::Update()
|
|||||||
if (it->empty() && !Config.media_library_display_empty_tag)
|
if (it->empty() && !Config.media_library_display_empty_tag)
|
||||||
continue;
|
continue;
|
||||||
utf_to_locale(*it);
|
utf_to_locale(*it);
|
||||||
Artists->AddItem(*it);
|
Tags->AddItem(*it);
|
||||||
}
|
}
|
||||||
Artists->Window::Clear();
|
Tags->Window::Clear();
|
||||||
Artists->Refresh();
|
Tags->Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasTwoColumns && !Artists->Empty() && Albums->ReallyEmpty() && Songs->ReallyEmpty())
|
if (!hasTwoColumns && !Tags->Empty() && Albums->ReallyEmpty() && Songs->ReallyEmpty())
|
||||||
{
|
{
|
||||||
// idle has to be blocked for now since it would be enabled and
|
// idle has to be blocked for now since it would be enabled and
|
||||||
// disabled a few times by each mpd command, which makes no sense
|
// disabled a few times by each mpd command, which makes no sense
|
||||||
@@ -221,9 +240,9 @@ void MediaLibrary::Update()
|
|||||||
Mpd.BlockIdle(1);
|
Mpd.BlockIdle(1);
|
||||||
Albums->Reset();
|
Albums->Reset();
|
||||||
MPD::TagList list;
|
MPD::TagList list;
|
||||||
locale_to_utf(Artists->Current().value());
|
locale_to_utf(Tags->Current().value());
|
||||||
Mpd.StartFieldSearch(MPD_TAG_ALBUM);
|
Mpd.StartFieldSearch(MPD_TAG_ALBUM);
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag, Artists->Current().value());
|
Mpd.AddSearch(Config.media_lib_primary_tag, Tags->Current().value());
|
||||||
Mpd.CommitSearchTags([&list](std::string &&album) {
|
Mpd.CommitSearchTags([&list](std::string &&album) {
|
||||||
list.push_back(album);
|
list.push_back(album);
|
||||||
});
|
});
|
||||||
@@ -232,7 +251,7 @@ void MediaLibrary::Update()
|
|||||||
if (Config.media_library_display_date)
|
if (Config.media_library_display_date)
|
||||||
{
|
{
|
||||||
Mpd.StartFieldSearch(MPD_TAG_DATE);
|
Mpd.StartFieldSearch(MPD_TAG_DATE);
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag, Artists->Current().value());
|
Mpd.AddSearch(Config.media_lib_primary_tag, Tags->Current().value());
|
||||||
Mpd.AddSearch(MPD_TAG_ALBUM, *album);
|
Mpd.AddSearch(MPD_TAG_ALBUM, *album);
|
||||||
utf_to_locale(*album);
|
utf_to_locale(*album);
|
||||||
Mpd.CommitSearchTags([this, &album](std::string &&date) {
|
Mpd.CommitSearchTags([this, &album](std::string &&date) {
|
||||||
@@ -246,9 +265,9 @@ void MediaLibrary::Update()
|
|||||||
Albums->AddItem(SearchConstraints(*album, ""));
|
Albums->AddItem(SearchConstraints(*album, ""));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
utf_to_locale(Artists->Current().value());
|
utf_to_locale(Tags->Current().value());
|
||||||
if (!Albums->Empty())
|
if (!Albums->Empty())
|
||||||
std::sort(Albums->BeginV(), Albums->EndV(), SearchConstraintsSorting());
|
std::sort(Albums->BeginV(), Albums->EndV(), SortSearchConstraints);
|
||||||
if (Albums->Size() > 1)
|
if (Albums->Size() > 1)
|
||||||
{
|
{
|
||||||
Albums->AddSeparator();
|
Albums->AddSeparator();
|
||||||
@@ -306,23 +325,23 @@ void MediaLibrary::Update()
|
|||||||
}
|
}
|
||||||
Mpd.BlockIdle(0);
|
Mpd.BlockIdle(0);
|
||||||
if (!Albums->Empty())
|
if (!Albums->Empty())
|
||||||
std::sort(Albums->BeginV(), Albums->EndV(), SearchConstraintsSorting());
|
std::sort(Albums->BeginV(), Albums->EndV(), SortSearchConstraints);
|
||||||
Albums->Refresh();
|
Albums->Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasTwoColumns && !Artists->Empty() && w == Albums && Albums->ReallyEmpty())
|
if (!hasTwoColumns && !Tags->Empty() && w == Albums && Albums->ReallyEmpty())
|
||||||
{
|
{
|
||||||
Albums->HighlightColor(Config.main_highlight_color);
|
Albums->HighlightColor(Config.main_highlight_color);
|
||||||
Artists->HighlightColor(Config.active_column_color);
|
Tags->HighlightColor(Config.active_column_color);
|
||||||
w = Artists;
|
w = Tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(hasTwoColumns ? Albums->Empty() : Artists->Empty()) && Songs->ReallyEmpty())
|
if (!(hasTwoColumns ? Albums->Empty() : Tags->Empty()) && Songs->ReallyEmpty())
|
||||||
{
|
{
|
||||||
Songs->Reset();
|
Songs->Reset();
|
||||||
|
|
||||||
Mpd.StartSearch(1);
|
Mpd.StartSearch(1);
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(hasTwoColumns ? Albums->Current().value().PrimaryTag : Artists->Current().value()));
|
Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(hasTwoColumns ? Albums->Current().value().PrimaryTag : Tags->Current().value()));
|
||||||
if (Albums->Current().value().Date != AllTracksMarker)
|
if (Albums->Current().value().Date != AllTracksMarker)
|
||||||
{
|
{
|
||||||
Mpd.AddSearch(MPD_TAG_ALBUM, locale_to_utf_cpy(Albums->Current().value().Album));
|
Mpd.AddSearch(MPD_TAG_ALBUM, locale_to_utf_cpy(Albums->Current().value().Album));
|
||||||
@@ -347,10 +366,10 @@ void MediaLibrary::SpacePressed()
|
|||||||
{
|
{
|
||||||
if (Config.space_selects)
|
if (Config.space_selects)
|
||||||
{
|
{
|
||||||
if (w == Artists)
|
if (w == Tags)
|
||||||
{
|
{
|
||||||
size_t i = Artists->Choice();
|
size_t i = Tags->Choice();
|
||||||
Artists->at(i).setSelected(!Artists->at(i).isSelected());
|
Tags->at(i).setSelected(!Tags->at(i).isSelected());
|
||||||
Albums->Clear();
|
Albums->Clear();
|
||||||
Songs->Clear();
|
Songs->Clear();
|
||||||
}
|
}
|
||||||
@@ -376,22 +395,22 @@ void MediaLibrary::SpacePressed()
|
|||||||
|
|
||||||
void MediaLibrary::MouseButtonPressed(MEVENT me)
|
void MediaLibrary::MouseButtonPressed(MEVENT me)
|
||||||
{
|
{
|
||||||
if (!Artists->Empty() && Artists->hasCoords(me.x, me.y))
|
if (!Tags->Empty() && Tags->hasCoords(me.x, me.y))
|
||||||
{
|
{
|
||||||
if (w != Artists)
|
if (w != Tags)
|
||||||
{
|
{
|
||||||
PrevColumn();
|
PrevColumn();
|
||||||
PrevColumn();
|
PrevColumn();
|
||||||
}
|
}
|
||||||
if (size_t(me.y) < Artists->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
|
if (size_t(me.y) < Tags->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
|
||||||
{
|
{
|
||||||
Artists->Goto(me.y);
|
Tags->Goto(me.y);
|
||||||
if (me.bstate & BUTTON3_PRESSED)
|
if (me.bstate & BUTTON3_PRESSED)
|
||||||
{
|
{
|
||||||
size_t pos = Artists->Choice();
|
size_t pos = Tags->Choice();
|
||||||
SpacePressed();
|
SpacePressed();
|
||||||
if (pos < Artists->Size()-1)
|
if (pos < Tags->Size()-1)
|
||||||
Artists->Scroll(wUp);
|
Tags->Scroll(wUp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -402,7 +421,7 @@ void MediaLibrary::MouseButtonPressed(MEVENT me)
|
|||||||
else if (!Albums->Empty() && Albums->hasCoords(me.x, me.y))
|
else if (!Albums->Empty() && Albums->hasCoords(me.x, me.y))
|
||||||
{
|
{
|
||||||
if (w != Albums)
|
if (w != Albums)
|
||||||
w == Artists ? NextColumn() : PrevColumn();
|
w == Tags ? NextColumn() : PrevColumn();
|
||||||
if (size_t(me.y) < Albums->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
|
if (size_t(me.y) < Albums->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
|
||||||
{
|
{
|
||||||
Albums->Goto(me.y);
|
Albums->Goto(me.y);
|
||||||
@@ -450,8 +469,8 @@ MPD::Song *MediaLibrary::CurrentSong()
|
|||||||
|
|
||||||
List *MediaLibrary::GetList()
|
List *MediaLibrary::GetList()
|
||||||
{
|
{
|
||||||
if (w == Artists)
|
if (w == Tags)
|
||||||
return Artists;
|
return Tags;
|
||||||
else if (w == Albums)
|
else if (w == Albums)
|
||||||
return Albums;
|
return Albums;
|
||||||
else if (w == Songs)
|
else if (w == Songs)
|
||||||
@@ -462,8 +481,8 @@ List *MediaLibrary::GetList()
|
|||||||
|
|
||||||
void MediaLibrary::ReverseSelection()
|
void MediaLibrary::ReverseSelection()
|
||||||
{
|
{
|
||||||
if (w == Artists)
|
if (w == Tags)
|
||||||
Artists->ReverseSelection();
|
Tags->ReverseSelection();
|
||||||
else if (w == Albums)
|
else if (w == Albums)
|
||||||
Albums->ReverseSelection();
|
Albums->ReverseSelection();
|
||||||
else if (w == Songs)
|
else if (w == Songs)
|
||||||
@@ -473,16 +492,16 @@ void MediaLibrary::ReverseSelection()
|
|||||||
void MediaLibrary::GetSelectedSongs(MPD::SongList &v)
|
void MediaLibrary::GetSelectedSongs(MPD::SongList &v)
|
||||||
{
|
{
|
||||||
std::vector<size_t> selected;
|
std::vector<size_t> selected;
|
||||||
if (w == Artists && !Artists->Empty())
|
if (w == Tags && !Tags->Empty())
|
||||||
{
|
{
|
||||||
Artists->GetSelected(selected);
|
Tags->GetSelected(selected);
|
||||||
if (selected.empty())
|
if (selected.empty())
|
||||||
selected.push_back(Artists->Choice());
|
selected.push_back(Tags->Choice());
|
||||||
for (auto it = selected.begin(); it != selected.end(); ++it)
|
for (auto it = selected.begin(); it != selected.end(); ++it)
|
||||||
{
|
{
|
||||||
MPD::SongList list;
|
MPD::SongList list;
|
||||||
Mpd.StartSearch(1);
|
Mpd.StartSearch(1);
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(Artists->at(*it).value()));
|
Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(Tags->at(*it).value()));
|
||||||
Mpd.CommitSearchSongs([&list](MPD::Song &&s) {
|
Mpd.CommitSearchSongs([&list](MPD::Song &&s) {
|
||||||
list.push_back(s);
|
list.push_back(s);
|
||||||
});
|
});
|
||||||
@@ -508,7 +527,7 @@ void MediaLibrary::GetSelectedSongs(MPD::SongList &v)
|
|||||||
Mpd.StartSearch(1);
|
Mpd.StartSearch(1);
|
||||||
Mpd.AddSearch(Config.media_lib_primary_tag, hasTwoColumns
|
Mpd.AddSearch(Config.media_lib_primary_tag, hasTwoColumns
|
||||||
? Albums->at(*it).value().PrimaryTag
|
? Albums->at(*it).value().PrimaryTag
|
||||||
: locale_to_utf_cpy(Artists->Current().value()));
|
: locale_to_utf_cpy(Tags->Current().value()));
|
||||||
Mpd.AddSearch(MPD_TAG_ALBUM, Albums->at(*it).value().Album);
|
Mpd.AddSearch(MPD_TAG_ALBUM, Albums->at(*it).value().Album);
|
||||||
Mpd.AddSearch(MPD_TAG_DATE, Albums->at(*it).value().Date);
|
Mpd.AddSearch(MPD_TAG_DATE, Albums->at(*it).value().Date);
|
||||||
Mpd.CommitSearchSongs([&v](MPD::Song &&s) {
|
Mpd.CommitSearchSongs([&v](MPD::Song &&s) {
|
||||||
@@ -527,13 +546,15 @@ void MediaLibrary::GetSelectedSongs(MPD::SongList &v)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
std::string MediaLibrary::currentFilter()
|
std::string MediaLibrary::currentFilter()
|
||||||
{
|
{
|
||||||
std::string filter;
|
std::string filter;
|
||||||
if (w == Artists)
|
if (w == Tags)
|
||||||
filter = RegexFilter<std::string>::currentFilter(*Artists);
|
filter = RegexFilter<std::string>::currentFilter(*Tags);
|
||||||
else if (w == Albums)
|
else if (w == Albums)
|
||||||
filter = RegexFilter<SearchConstraints>::currentFilter(*Albums);
|
filter = RegexItemFilter<SearchConstraints>::currentFilter(*Albums);
|
||||||
else if (w == Songs)
|
else if (w == Songs)
|
||||||
filter = RegexFilter<MPD::Song>::currentFilter(*Songs);
|
filter = RegexFilter<MPD::Song>::currentFilter(*Songs);
|
||||||
return filter;
|
return filter;
|
||||||
@@ -541,35 +562,85 @@ std::string MediaLibrary::currentFilter()
|
|||||||
|
|
||||||
void MediaLibrary::applyFilter(const std::string &filter)
|
void MediaLibrary::applyFilter(const std::string &filter)
|
||||||
{
|
{
|
||||||
if (w == Artists)
|
if (w == Tags)
|
||||||
{
|
{
|
||||||
Artists->ShowAll();
|
Tags->ShowAll();
|
||||||
auto rx = RegexFilter<std::string>(filter, Config.regex_type);
|
auto rx = RegexFilter<std::string>(filter, Config.regex_type, TagEntryMatcher);
|
||||||
Artists->Filter(Artists->Begin(), Artists->End(), rx);
|
Tags->filter(Tags->Begin(), Tags->End(), rx);
|
||||||
}
|
}
|
||||||
else if (w == Albums)
|
else if (w == Albums)
|
||||||
{
|
{
|
||||||
Albums->ShowAll();
|
Albums->ShowAll();
|
||||||
auto fun = [](const Regex &rx, Menu<SearchConstraints> &menu, const Menu<SearchConstraints>::Item &item) {
|
auto fun = std::bind(AlbumEntryMatcher, _1, _2, true);
|
||||||
if (item.isSeparator() || item.value().Date == AllTracksMarker)
|
auto rx = RegexItemFilter<SearchConstraints>(filter, Config.regex_type, fun);
|
||||||
return true;
|
Albums->filter(Albums->Begin(), Albums->End(), rx);
|
||||||
return rx.match(menu.Stringify(item));
|
|
||||||
};
|
|
||||||
auto rx = RegexFilter<SearchConstraints>(filter, Config.regex_type, fun);
|
|
||||||
Albums->Filter(Albums->Begin(), Albums->End(), rx);
|
|
||||||
}
|
}
|
||||||
else if (w == Songs)
|
else if (w == Songs)
|
||||||
{
|
{
|
||||||
Songs->ShowAll();
|
Songs->ShowAll();
|
||||||
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type);
|
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type, SongEntryMatcher);
|
||||||
Songs->Filter(Songs->Begin(), Songs->End(), rx);
|
Songs->filter(Songs->Begin(), Songs->End(), rx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
|
bool MediaLibrary::search(const std::string &constraint)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
if (w == Tags)
|
||||||
|
{
|
||||||
|
auto rx = RegexFilter<std::string>(constraint, Config.regex_type, TagEntryMatcher);
|
||||||
|
result = Tags->search(Tags->Begin(), Tags->End(), rx);
|
||||||
|
}
|
||||||
|
else if (w == Albums)
|
||||||
|
{
|
||||||
|
auto fun = std::bind(AlbumEntryMatcher, _1, _2, false);
|
||||||
|
auto rx = RegexItemFilter<SearchConstraints>(constraint, Config.regex_type, fun);
|
||||||
|
result = Albums->search(Albums->Begin(), Albums->End(), rx);
|
||||||
|
}
|
||||||
|
else if (w == Songs)
|
||||||
|
{
|
||||||
|
auto rx = RegexFilter<MPD::Song>(constraint, Config.regex_type, SongEntryMatcher);
|
||||||
|
result = Songs->search(Songs->Begin(), Songs->End(), rx);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaLibrary::nextFound(bool wrap)
|
||||||
|
{
|
||||||
|
if (w == Tags)
|
||||||
|
Tags->NextFound(wrap);
|
||||||
|
else if (w == Albums)
|
||||||
|
Albums->NextFound(wrap);
|
||||||
|
else if (w == Songs)
|
||||||
|
Songs->NextFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaLibrary::prevFound(bool wrap)
|
||||||
|
{
|
||||||
|
if (w == Tags)
|
||||||
|
Tags->PrevFound(wrap);
|
||||||
|
else if (w == Albums)
|
||||||
|
Albums->PrevFound(wrap);
|
||||||
|
else if (w == Songs)
|
||||||
|
Songs->PrevFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
|
int MediaLibrary::Columns()
|
||||||
|
{
|
||||||
|
if (hasTwoColumns)
|
||||||
|
return 2;
|
||||||
|
else
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
bool MediaLibrary::isNextColumnAvailable()
|
bool MediaLibrary::isNextColumnAvailable()
|
||||||
{
|
{
|
||||||
assert(!hasTwoColumns || w != Artists);
|
assert(!hasTwoColumns || w != Tags);
|
||||||
if (w == Artists)
|
if (w == Tags)
|
||||||
{
|
{
|
||||||
if (!Albums->ReallyEmpty() && !Songs->ReallyEmpty())
|
if (!Albums->ReallyEmpty() && !Songs->ReallyEmpty())
|
||||||
return true;
|
return true;
|
||||||
@@ -584,9 +655,9 @@ bool MediaLibrary::isNextColumnAvailable()
|
|||||||
|
|
||||||
void MediaLibrary::NextColumn()
|
void MediaLibrary::NextColumn()
|
||||||
{
|
{
|
||||||
if (w == Artists)
|
if (w == Tags)
|
||||||
{
|
{
|
||||||
Artists->HighlightColor(Config.main_highlight_color);
|
Tags->HighlightColor(Config.main_highlight_color);
|
||||||
w->Refresh();
|
w->Refresh();
|
||||||
w = Albums;
|
w = Albums;
|
||||||
Albums->HighlightColor(Config.active_column_color);
|
Albums->HighlightColor(Config.active_column_color);
|
||||||
@@ -602,15 +673,15 @@ void MediaLibrary::NextColumn()
|
|||||||
|
|
||||||
bool MediaLibrary::isPrevColumnAvailable()
|
bool MediaLibrary::isPrevColumnAvailable()
|
||||||
{
|
{
|
||||||
assert(!hasTwoColumns || w != Artists);
|
assert(!hasTwoColumns || w != Tags);
|
||||||
if (w == Songs)
|
if (w == Songs)
|
||||||
{
|
{
|
||||||
if (!Albums->ReallyEmpty() && (hasTwoColumns || !Artists->ReallyEmpty()))
|
if (!Albums->ReallyEmpty() && (hasTwoColumns || !Tags->ReallyEmpty()))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (w == Albums)
|
else if (w == Albums)
|
||||||
{
|
{
|
||||||
if (!hasTwoColumns && !Artists->ReallyEmpty())
|
if (!hasTwoColumns && !Tags->ReallyEmpty())
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -629,21 +700,13 @@ void MediaLibrary::PrevColumn()
|
|||||||
{
|
{
|
||||||
Albums->HighlightColor(Config.main_highlight_color);
|
Albums->HighlightColor(Config.main_highlight_color);
|
||||||
w->Refresh();
|
w->Refresh();
|
||||||
w = Artists;
|
w = Tags;
|
||||||
Artists->HighlightColor(Config.active_column_color);
|
Tags->HighlightColor(Config.active_column_color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaLibrary::LocateSong(const MPD::Song &s)
|
void MediaLibrary::LocateSong(const MPD::Song &s)
|
||||||
{
|
{
|
||||||
if (Mpd.Version() < 14)
|
|
||||||
{
|
|
||||||
// <mpd-0.14.* has no ability to search for empty tags, which sometimes
|
|
||||||
// leaves albums column empty. since this function relies on this column
|
|
||||||
// being non-empty, it has to be disabled for these versions.
|
|
||||||
ShowMessage("This function is supported in MPD >= 0.14.0");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::string primary_tag;
|
std::string primary_tag;
|
||||||
switch (Config.media_lib_primary_tag)
|
switch (Config.media_lib_primary_tag)
|
||||||
{
|
{
|
||||||
@@ -681,16 +744,16 @@ void MediaLibrary::LocateSong(const MPD::Song &s)
|
|||||||
|
|
||||||
if (!hasTwoColumns)
|
if (!hasTwoColumns)
|
||||||
{
|
{
|
||||||
Artists->ShowAll();
|
Tags->ShowAll();
|
||||||
if (Artists->Empty())
|
if (Tags->Empty())
|
||||||
Update();
|
Update();
|
||||||
if (primary_tag != Artists->Current().value())
|
if (primary_tag != Tags->Current().value())
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < Artists->Size(); ++i)
|
for (size_t i = 0; i < Tags->Size(); ++i)
|
||||||
{
|
{
|
||||||
if (primary_tag == (*Artists)[i].value())
|
if (primary_tag == (*Tags)[i].value())
|
||||||
{
|
{
|
||||||
Artists->Highlight(i);
|
Tags->Highlight(i);
|
||||||
Albums->Clear();
|
Albums->Clear();
|
||||||
Songs->Clear();
|
Songs->Clear();
|
||||||
break;
|
break;
|
||||||
@@ -738,7 +801,7 @@ void MediaLibrary::LocateSong(const MPD::Song &s)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Artists->HighlightColor(Config.main_highlight_color);
|
Tags->HighlightColor(Config.main_highlight_color);
|
||||||
Albums->HighlightColor(Config.main_highlight_color);
|
Albums->HighlightColor(Config.main_highlight_color);
|
||||||
Songs->HighlightColor(Config.active_column_color);
|
Songs->HighlightColor(Config.active_column_color);
|
||||||
w = Songs;
|
w = Songs;
|
||||||
@@ -759,12 +822,12 @@ void MediaLibrary::AddToPlaylist(bool add_n_play)
|
|||||||
|
|
||||||
if (myPlaylist->Add(list, add_n_play))
|
if (myPlaylist->Add(list, add_n_play))
|
||||||
{
|
{
|
||||||
if ((!Artists->Empty() && w == Artists)
|
if ((!Tags->Empty() && w == Tags)
|
||||||
|| (w == Albums && Albums->Current().value().Date == AllTracksMarker))
|
|| (w == Albums && Albums->Current().value().Date == AllTracksMarker))
|
||||||
{
|
{
|
||||||
std::string tag_type = tagTypeToString(Config.media_lib_primary_tag);
|
std::string tag_type = tagTypeToString(Config.media_lib_primary_tag);
|
||||||
lowercase(tag_type);
|
lowercase(tag_type);
|
||||||
ShowMessage("Songs with %s = \"%s\" added", tag_type.c_str(), Artists->Current().value().c_str());
|
ShowMessage("Songs with %s = \"%s\" added", tag_type.c_str(), Tags->Current().value().c_str());
|
||||||
}
|
}
|
||||||
else if (w == Albums)
|
else if (w == Albums)
|
||||||
ShowMessage("Songs from album \"%s\" added", Albums->Current().value().Album.c_str());
|
ShowMessage("Songs from album \"%s\" added", Albums->Current().value().Album.c_str());
|
||||||
@@ -774,7 +837,7 @@ void MediaLibrary::AddToPlaylist(bool add_n_play)
|
|||||||
if (!add_n_play)
|
if (!add_n_play)
|
||||||
{
|
{
|
||||||
w->Scroll(wDown);
|
w->Scroll(wDown);
|
||||||
if (w == Artists)
|
if (w == Tags)
|
||||||
{
|
{
|
||||||
Albums->Clear();
|
Albums->Clear();
|
||||||
Songs->Clear();
|
Songs->Clear();
|
||||||
@@ -784,30 +847,62 @@ void MediaLibrary::AddToPlaylist(bool add_n_play)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string MediaLibrary::SongToString(const MPD::Song &s)
|
namespace {//
|
||||||
|
|
||||||
|
std::string AlbumToString(const SearchConstraints &sc)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
if (sc.Date == AllTracksMarker)
|
||||||
|
result = "All tracks";
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (hasTwoColumns)
|
||||||
|
{
|
||||||
|
if (sc.PrimaryTag.empty())
|
||||||
|
result += Config.empty_tag;
|
||||||
|
else
|
||||||
|
result += sc.PrimaryTag;
|
||||||
|
result += " - ";
|
||||||
|
}
|
||||||
|
if (Config.media_lib_primary_tag != MPD_TAG_DATE && !sc.Date.empty())
|
||||||
|
result += "(" + sc.Date + ") ";
|
||||||
|
result += sc.Album.empty() ? "<no album>" : sc.Album;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SongToString(const MPD::Song &s)
|
||||||
{
|
{
|
||||||
return s.toString(Config.song_library_format);
|
return s.toString(Config.song_library_format);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string MediaLibrary::AlbumToString(const SearchConstraints &sc)
|
/***********************************************************************/
|
||||||
|
|
||||||
|
bool TagEntryMatcher(const Regex &rx, const std::string &tag)
|
||||||
{
|
{
|
||||||
if (sc.Date == AllTracksMarker)
|
return rx.match(tag);
|
||||||
return "All tracks";
|
|
||||||
std::string result;
|
|
||||||
if (myLibrary->hasTwoColumns)
|
|
||||||
(result += sc.PrimaryTag.empty() ? Config.empty_tag : sc.PrimaryTag) += " - ";
|
|
||||||
if ((!myLibrary || Config.media_lib_primary_tag != MPD_TAG_DATE) && !sc.Date.empty())
|
|
||||||
((result += "(") += sc.Date) += ") ";
|
|
||||||
result += sc.Album.empty() ? "<no album>" : sc.Album;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaLibrary::DisplayAlbums(Menu<SearchConstraints> &menu)
|
bool AlbumEntryMatcher(const Regex &rx, const Menu<SearchConstraints>::Item &item, bool filter)
|
||||||
|
{
|
||||||
|
if (item.isSeparator() || item.value().Date == AllTracksMarker)
|
||||||
|
return filter;
|
||||||
|
return rx.match(AlbumToString(item.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SongEntryMatcher(const Regex &rx, const MPD::Song &s)
|
||||||
|
{
|
||||||
|
return rx.match(SongToString(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
|
void DisplayAlbums(Menu<SearchConstraints> &menu)
|
||||||
{
|
{
|
||||||
menu << AlbumToString(menu.Drawn().value());
|
menu << AlbumToString(menu.Drawn().value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaLibrary::DisplayPrimaryTags(Menu<std::string> &menu)
|
void DisplayPrimaryTags(Menu<std::string> &menu)
|
||||||
{
|
{
|
||||||
const std::string &tag = menu.Drawn().value();
|
const std::string &tag = menu.Drawn().value();
|
||||||
if (tag.empty())
|
if (tag.empty())
|
||||||
@@ -816,18 +911,9 @@ void MediaLibrary::DisplayPrimaryTags(Menu<std::string> &menu)
|
|||||||
menu << tag;
|
menu << tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaLibrary::SearchConstraintsSorting::operator()(const SearchConstraints &a, const SearchConstraints &b) const
|
/***********************************************************************/
|
||||||
{
|
|
||||||
int result;
|
|
||||||
CaseInsensitiveStringComparison cmp;
|
|
||||||
result = cmp(a.PrimaryTag, b.PrimaryTag);
|
|
||||||
if (result != 0)
|
|
||||||
return result < 0;
|
|
||||||
result = cmp(a.Date, b.Date);
|
|
||||||
return (result == 0 ? cmp(a.Album, b.Album) : result) < 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MediaLibrary::SortSongsByTrack(const MPD::Song &a, const MPD::Song &b)
|
bool SortSongsByTrack(const MPD::Song &a, const MPD::Song &b)
|
||||||
{
|
{
|
||||||
if (a.getDisc() == b.getDisc())
|
if (a.getDisc() == b.getDisc())
|
||||||
return stringToInt(a.getTrack()) < stringToInt(b.getTrack());
|
return stringToInt(a.getTrack()) < stringToInt(b.getTrack());
|
||||||
@@ -835,13 +921,27 @@ bool MediaLibrary::SortSongsByTrack(const MPD::Song &a, const MPD::Song &b)
|
|||||||
return stringToInt(a.getDisc()) < stringToInt(b.getDisc());
|
return stringToInt(a.getDisc()) < stringToInt(b.getDisc());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaLibrary::SortAllTracks(const MPD::Song &a, const MPD::Song &b)
|
bool SortAllTracks(const MPD::Song &a, const MPD::Song &b)
|
||||||
{
|
{
|
||||||
static MPD::Song::GetFunction gets[] = { &MPD::Song::getDate, &MPD::Song::getAlbum, &MPD::Song::getDisc, 0 };
|
static MPD::Song::GetFunction gets[] = { &MPD::Song::getDate, &MPD::Song::getAlbum, &MPD::Song::getDisc, 0 };
|
||||||
CaseInsensitiveStringComparison cmp;
|
CaseInsensitiveStringComparison cmp;
|
||||||
for (MPD::Song::GetFunction *get = gets; *get; ++get)
|
for (MPD::Song::GetFunction *get = gets; *get; ++get)
|
||||||
if (int ret = cmp(a.getTags(*get), b.getTags(*get)))
|
if (int ret = cmp(a.getTags(*get), b.getTags(*get)))
|
||||||
return ret < 0;
|
return ret < 0;
|
||||||
return a.getTrack() < b.getTrack();
|
return a.getTrack() < b.getTrack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SortSearchConstraints(const SearchConstraints &a, const SearchConstraints &b)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
CaseInsensitiveStringComparison cmp;
|
||||||
|
result = cmp(a.PrimaryTag, b.PrimaryTag);
|
||||||
|
if (result != 0)
|
||||||
|
return result < 0;
|
||||||
|
result = cmp(a.Date, b.Date);
|
||||||
|
if (result != 0)
|
||||||
|
return result < 0;
|
||||||
|
return cmp(a.Album, b.Album) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,27 +23,10 @@
|
|||||||
|
|
||||||
#include "interfaces.h"
|
#include "interfaces.h"
|
||||||
#include "ncmpcpp.h"
|
#include "ncmpcpp.h"
|
||||||
#include "regex_filter.h"
|
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
|
||||||
class MediaLibrary : public Screen<Window>, public Filterable
|
class MediaLibrary : public Screen<Window>, public Filterable, public Searchable
|
||||||
{
|
{
|
||||||
struct SearchConstraints
|
|
||||||
{
|
|
||||||
SearchConstraints() { }
|
|
||||||
SearchConstraints(const std::string &tag, const std::string &album, const std::string &date) : PrimaryTag(tag), Album(album), Date(date) { }
|
|
||||||
SearchConstraints(const std::string &album, const std::string &date) : Album(album), Date(date) { }
|
|
||||||
|
|
||||||
std::string PrimaryTag;
|
|
||||||
std::string Album;
|
|
||||||
std::string Date;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SearchConstraintsSorting
|
|
||||||
{
|
|
||||||
bool operator()(const SearchConstraints &a, const SearchConstraints &b) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual void SwitchTo();
|
virtual void SwitchTo();
|
||||||
virtual void Resize();
|
virtual void Resize();
|
||||||
@@ -65,14 +48,20 @@ class MediaLibrary : public Screen<Window>, public Filterable
|
|||||||
virtual void ReverseSelection();
|
virtual void ReverseSelection();
|
||||||
virtual void GetSelectedSongs(MPD::SongList &);
|
virtual void GetSelectedSongs(MPD::SongList &);
|
||||||
|
|
||||||
|
/// Filterable implementation
|
||||||
virtual std::string currentFilter();
|
virtual std::string currentFilter();
|
||||||
virtual void applyFilter(const std::string &filter);
|
virtual void applyFilter(const std::string &filter);
|
||||||
|
|
||||||
|
/// Searchable implementation
|
||||||
|
virtual bool search(const std::string &constraint);
|
||||||
|
virtual void nextFound(bool wrap);
|
||||||
|
virtual void prevFound(bool wrap);
|
||||||
|
|
||||||
virtual List *GetList();
|
virtual List *GetList();
|
||||||
|
|
||||||
virtual bool isMergable() { return true; }
|
virtual bool isMergable() { return true; }
|
||||||
|
|
||||||
int Columns() { return hasTwoColumns ? 2 : 3; }
|
int Columns();
|
||||||
bool isNextColumnAvailable();
|
bool isNextColumnAvailable();
|
||||||
void NextColumn();
|
void NextColumn();
|
||||||
bool isPrevColumnAvailable();
|
bool isPrevColumnAvailable();
|
||||||
@@ -80,7 +69,20 @@ class MediaLibrary : public Screen<Window>, public Filterable
|
|||||||
|
|
||||||
void LocateSong(const MPD::Song &);
|
void LocateSong(const MPD::Song &);
|
||||||
|
|
||||||
Menu<std::string> *Artists;
|
struct SearchConstraints
|
||||||
|
{
|
||||||
|
SearchConstraints() { }
|
||||||
|
SearchConstraints(const std::string &tag, const std::string &album, const std::string &date)
|
||||||
|
: PrimaryTag(tag), Album(album), Date(date) { }
|
||||||
|
SearchConstraints(const std::string &album, const std::string &date)
|
||||||
|
: Album(album), Date(date) { }
|
||||||
|
|
||||||
|
std::string PrimaryTag;
|
||||||
|
std::string Album;
|
||||||
|
std::string Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
Menu<std::string> *Tags;
|
||||||
Menu<SearchConstraints> *Albums;
|
Menu<SearchConstraints> *Albums;
|
||||||
Menu<MPD::Song> *Songs;
|
Menu<MPD::Song> *Songs;
|
||||||
|
|
||||||
@@ -90,24 +92,6 @@ class MediaLibrary : public Screen<Window>, public Filterable
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void AddToPlaylist(bool);
|
void AddToPlaylist(bool);
|
||||||
|
|
||||||
static std::string SongToString(const MPD::Song &s);
|
|
||||||
static std::string AlbumToString(const SearchConstraints &);
|
|
||||||
static void DisplayAlbums(Menu<SearchConstraints> &menu);
|
|
||||||
static void DisplayPrimaryTags(Menu<std::string> &menu);
|
|
||||||
|
|
||||||
static bool SortSongsByTrack(const MPD::Song &, const MPD::Song &);
|
|
||||||
static bool SortAllTracks(const MPD::Song &, const MPD::Song &);
|
|
||||||
|
|
||||||
static bool hasTwoColumns;
|
|
||||||
static size_t itsLeftColStartX;
|
|
||||||
static size_t itsLeftColWidth;
|
|
||||||
static size_t itsMiddleColWidth;
|
|
||||||
static size_t itsMiddleColStartX;
|
|
||||||
static size_t itsRightColWidth;
|
|
||||||
static size_t itsRightColStartX;
|
|
||||||
|
|
||||||
static const char AllTracksMarker[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MediaLibrary *myLibrary;
|
extern MediaLibrary *myLibrary;
|
||||||
|
|||||||
29
src/menu.cpp
29
src/menu.cpp
@@ -17,32 +17,3 @@
|
|||||||
* Free Software Foundation, Inc., *
|
* Free Software Foundation, Inc., *
|
||||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
#include "menu.h"
|
|
||||||
|
|
||||||
namespace NCurses {
|
|
||||||
|
|
||||||
template <> std::string Menu<std::string>::GetItem(size_t pos)
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
if (m_options_ptr->at(pos))
|
|
||||||
{
|
|
||||||
if (m_item_stringifier)
|
|
||||||
result = m_item_stringifier((*m_options_ptr)[pos]->value());
|
|
||||||
else
|
|
||||||
result = (*m_options_ptr)[pos]->value();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <> std::string Menu<std::string>::Stringify(const Menu<std::string>::Item &item) const
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
if (m_item_stringifier)
|
|
||||||
result = m_item_stringifier(item.value());
|
|
||||||
else
|
|
||||||
result = item.value();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
217
src/menu.h
217
src/menu.h
@@ -34,41 +34,20 @@
|
|||||||
namespace NCurses {
|
namespace NCurses {
|
||||||
|
|
||||||
/// List class is an interface for Menu class
|
/// List class is an interface for Menu class
|
||||||
///
|
|
||||||
struct List
|
struct List
|
||||||
{
|
{
|
||||||
/// @return currently highlighted position
|
/// @return currently highlighted position
|
||||||
///
|
|
||||||
virtual size_t Choice() const = 0;
|
virtual size_t Choice() const = 0;
|
||||||
|
|
||||||
/// @see Menu::Empty()
|
/// @see Menu::Empty()
|
||||||
///
|
|
||||||
virtual bool Empty() const = 0;
|
virtual bool Empty() const = 0;
|
||||||
|
|
||||||
/// @see Menu::Size()
|
/// @see Menu::Size()
|
||||||
///
|
|
||||||
virtual size_t Size() const = 0;
|
virtual size_t Size() const = 0;
|
||||||
|
|
||||||
/// @see Menu::Search()
|
|
||||||
///
|
|
||||||
virtual bool Search(const std::string &constraint, size_t beginning = 0, int flags = 0) = 0;
|
|
||||||
|
|
||||||
/// @see Menu::GetSearchConstraint()
|
|
||||||
///
|
|
||||||
virtual const std::string &GetSearchConstraint() = 0;
|
|
||||||
|
|
||||||
/// @see Menu::NextFound()
|
|
||||||
///
|
|
||||||
virtual void NextFound(bool wrap) = 0;
|
|
||||||
|
|
||||||
/// @see Menu::PrevFound()
|
|
||||||
///
|
|
||||||
virtual void PrevFound(bool wrap) = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// This template class is generic menu capable of
|
/// This template class is generic menu capable of
|
||||||
/// holding any std::vector compatible values.
|
/// holding any std::vector compatible values.
|
||||||
///
|
|
||||||
template <typename T> struct Menu : public Window, public List
|
template <typename T> struct Menu : public Window, public List
|
||||||
{
|
{
|
||||||
struct Item
|
struct Item
|
||||||
@@ -116,10 +95,12 @@ template <typename T> struct Menu : public Window, public List
|
|||||||
BaseIterator m_it;
|
BaseIterator m_it;
|
||||||
explicit ItemIterator(BaseIterator it) : m_it(it) { }
|
explicit ItemIterator(BaseIterator it) : m_it(it) { }
|
||||||
|
|
||||||
|
// base iterator's value_type doesn't change between const and non-const
|
||||||
|
// version, so we need to strip const off ValueT too for proper template
|
||||||
|
// version to be instantiated.
|
||||||
static const bool referenceValue = !std::is_same<
|
static const bool referenceValue = !std::is_same<
|
||||||
ValueT, typename std::remove_pointer<
|
typename std::remove_const<ValueT>::type,
|
||||||
typename BaseIterator::value_type
|
typename std::remove_pointer<typename BaseIterator::value_type>::type
|
||||||
>::type
|
|
||||||
>::value;
|
>::value;
|
||||||
template <typename Result, bool referenceValue> struct getObject { };
|
template <typename Result, bool referenceValue> struct getObject { };
|
||||||
template <typename Result> struct getObject<Result, true> {
|
template <typename Result> struct getObject<Result, true> {
|
||||||
@@ -197,17 +178,9 @@ template <typename T> struct Menu : public Window, public List
|
|||||||
/// Function helper prototype used to display each option on the screen.
|
/// Function helper prototype used to display each option on the screen.
|
||||||
/// If not set by setItemDisplayer(), menu won't display anything.
|
/// If not set by setItemDisplayer(), menu won't display anything.
|
||||||
/// @see setItemDisplayer()
|
/// @see setItemDisplayer()
|
||||||
///
|
|
||||||
typedef std::function<void(Menu<T> &)> ItemDisplayer;
|
typedef std::function<void(Menu<T> &)> ItemDisplayer;
|
||||||
|
|
||||||
/// Function helper prototype used for converting items to strings.
|
typedef std::function<bool(const Item &)> FilterFunction;
|
||||||
/// If not set by SetItemStringifier(), searching and filtering
|
|
||||||
/// won't work (note that Menu<std::string> doesn't need this)
|
|
||||||
/// @see SetItemStringifier()
|
|
||||||
///
|
|
||||||
typedef std::function<std::string(const T &)> ItemStringifier;
|
|
||||||
|
|
||||||
typedef std::function<bool(Menu<T> &, const Item &)> FilterFunction;
|
|
||||||
|
|
||||||
/// Constructs an empty menu with given parameters
|
/// Constructs an empty menu with given parameters
|
||||||
/// @param startx X position of left upper corner of constructed menu
|
/// @param startx X position of left upper corner of constructed menu
|
||||||
@@ -217,43 +190,31 @@ template <typename T> struct Menu : public Window, public List
|
|||||||
/// @param title title of constructed menu
|
/// @param title title of constructed menu
|
||||||
/// @param color base color of constructed menu
|
/// @param color base color of constructed menu
|
||||||
/// @param border border of constructed menu
|
/// @param border border of constructed menu
|
||||||
///
|
|
||||||
Menu(size_t startx, size_t starty, size_t width, size_t height,
|
Menu(size_t startx, size_t starty, size_t width, size_t height,
|
||||||
const std::string &title, Color color, Border border);
|
const std::string &title, Color color, Border border);
|
||||||
|
|
||||||
/// Destroys the object and frees memory
|
/// Destroys the object and frees memory
|
||||||
///
|
|
||||||
virtual ~Menu();
|
virtual ~Menu();
|
||||||
|
|
||||||
/// Sets helper function that is responsible for displaying items
|
/// Sets helper function that is responsible for displaying items
|
||||||
/// @param ptr function pointer that matches the ItemDisplayer prototype
|
/// @param ptr function pointer that matches the ItemDisplayer prototype
|
||||||
///
|
|
||||||
void setItemDisplayer(const ItemDisplayer &f) { m_item_displayer = f; }
|
void setItemDisplayer(const ItemDisplayer &f) { m_item_displayer = f; }
|
||||||
|
|
||||||
/// Sets helper function that is responsible for converting items to strings
|
|
||||||
/// @param f function pointer that matches the ItemStringifier prototype
|
|
||||||
///
|
|
||||||
void SetItemStringifier(const ItemStringifier &f) { m_item_stringifier = f; }
|
|
||||||
|
|
||||||
/// Reserves the size for internal container (this just calls std::vector::reserve())
|
/// Reserves the size for internal container (this just calls std::vector::reserve())
|
||||||
/// @param size requested size
|
/// @param size requested size
|
||||||
///
|
|
||||||
void Reserve(size_t size);
|
void Reserve(size_t size);
|
||||||
|
|
||||||
/// Resizes the list to given size (adequate to std::vector::resize())
|
/// Resizes the list to given size (adequate to std::vector::resize())
|
||||||
/// @param size requested size
|
/// @param size requested size
|
||||||
///
|
|
||||||
void ResizeList(size_t size);
|
void ResizeList(size_t size);
|
||||||
|
|
||||||
/// Adds new option to list
|
/// Adds new option to list
|
||||||
/// @param item object that has to be added
|
/// @param item object that has to be added
|
||||||
/// @param is_bold defines the initial state of bold attribute
|
/// @param is_bold defines the initial state of bold attribute
|
||||||
/// @param is_static defines the initial state of static attribute
|
/// @param is_static defines the initial state of static attribute
|
||||||
///
|
|
||||||
void AddItem(const T &item, bool is_bold = 0, bool is_static = 0);
|
void AddItem(const T &item, bool is_bold = 0, bool is_static = 0);
|
||||||
|
|
||||||
/// Adds separator to list
|
/// Adds separator to list
|
||||||
///
|
|
||||||
void AddSeparator();
|
void AddSeparator();
|
||||||
|
|
||||||
/// Inserts new option to list at given position
|
/// Inserts new option to list at given position
|
||||||
@@ -261,237 +222,182 @@ template <typename T> struct Menu : public Window, public List
|
|||||||
/// @param item object that has to be inserted
|
/// @param item object that has to be inserted
|
||||||
/// @param is_bold defines the initial state of bold attribute
|
/// @param is_bold defines the initial state of bold attribute
|
||||||
/// @param is_static defines the initial state of static attribute
|
/// @param is_static defines the initial state of static attribute
|
||||||
///
|
|
||||||
void InsertItem(size_t pos, const T &Item, bool is_bold = 0, bool is_static = 0);
|
void InsertItem(size_t pos, const T &Item, bool is_bold = 0, bool is_static = 0);
|
||||||
|
|
||||||
/// Inserts separator to list at given position
|
/// Inserts separator to list at given position
|
||||||
/// @param pos initial position of inserted separator
|
/// @param pos initial position of inserted separator
|
||||||
///
|
|
||||||
void InsertSeparator(size_t pos);
|
void InsertSeparator(size_t pos);
|
||||||
|
|
||||||
/// Deletes item from given position
|
/// Deletes item from given position
|
||||||
/// @param pos given position of item to be deleted
|
/// @param pos given position of item to be deleted
|
||||||
///
|
|
||||||
void DeleteItem(size_t pos);
|
void DeleteItem(size_t pos);
|
||||||
|
|
||||||
/// Swaps the content of two items
|
/// Swaps the content of two items
|
||||||
/// @param one position of first item
|
/// @param one position of first item
|
||||||
/// @param two position of second item
|
/// @param two position of second item
|
||||||
///
|
|
||||||
void Swap(size_t one, size_t two);
|
void Swap(size_t one, size_t two);
|
||||||
|
|
||||||
/// Moves the highlighted position to the given line of window
|
/// Moves the highlighted position to the given line of window
|
||||||
/// @param y Y position of menu window to be highlighted
|
/// @param y Y position of menu window to be highlighted
|
||||||
/// @return true if the position is reachable, false otherwise
|
/// @return true if the position is reachable, false otherwise
|
||||||
///
|
|
||||||
bool Goto(size_t y);
|
bool Goto(size_t y);
|
||||||
|
|
||||||
/// Checks whether list contains selected positions
|
/// Checks whether list contains selected positions
|
||||||
/// @return true if it contains them, false otherwise
|
/// @return true if it contains them, false otherwise
|
||||||
///
|
|
||||||
bool hasSelected() const;
|
bool hasSelected() const;
|
||||||
|
|
||||||
/// Gets positions of items that are selected
|
/// Gets positions of items that are selected
|
||||||
/// @param v vector to be filled with selected positions numbers
|
/// @param v vector to be filled with selected positions numbers
|
||||||
///
|
|
||||||
void GetSelected(std::vector<size_t> &v) const;
|
void GetSelected(std::vector<size_t> &v) const;
|
||||||
|
|
||||||
/// Reverses selection of all items in list
|
/// Reverses selection of all items in list
|
||||||
/// @param beginning beginning of range that has to be reversed
|
/// @param beginning beginning of range that has to be reversed
|
||||||
///
|
|
||||||
void ReverseSelection(size_t beginning = 0);
|
void ReverseSelection(size_t beginning = 0);
|
||||||
|
|
||||||
/// Highlights given position
|
/// Highlights given position
|
||||||
/// @param pos position to be highlighted
|
/// @param pos position to be highlighted
|
||||||
///
|
|
||||||
void Highlight(size_t pos);
|
void Highlight(size_t pos);
|
||||||
|
|
||||||
/// @return currently highlighted position
|
/// @return currently highlighted position
|
||||||
///
|
|
||||||
size_t Choice() const;
|
size_t Choice() const;
|
||||||
|
|
||||||
template <typename Iterator> void Filter(Iterator first, Iterator last, const FilterFunction &f);
|
void filter(ConstIterator first, ConstIterator last, const FilterFunction &f);
|
||||||
|
|
||||||
/// Searches the list for a given contraint. It uses ItemStringifier to convert stored items
|
bool search(ConstIterator first, ConstIterator last, const FilterFunction &f);
|
||||||
/// into strings and then performs pattern matching. Note that this supports regular expressions.
|
|
||||||
/// @param constraint a search constraint to be used
|
|
||||||
/// @param beginning beginning of range that has to be searched through
|
|
||||||
/// @param flags regex flags (REG_EXTENDED, REG_ICASE, REG_NOSUB, REG_NEWLINE)
|
|
||||||
/// @return true if at least one item matched the given pattern, false otherwise
|
|
||||||
///
|
|
||||||
virtual bool Search(const std::string &constraint, size_t beginning = 0, int flags = 0);
|
|
||||||
|
|
||||||
/// @return const reference to currently used search constraint
|
/// Clears filter results
|
||||||
///
|
void clearFilterResults();
|
||||||
virtual const std::string &GetSearchConstraint() { return m_search_constraint; }
|
|
||||||
|
/// Clears search results
|
||||||
|
void clearSearchResults();
|
||||||
|
|
||||||
/// Moves current position in the list to the next found one
|
/// Moves current position in the list to the next found one
|
||||||
/// @param wrap if true, this function will go to the first
|
/// @param wrap if true, this function will go to the first
|
||||||
/// found pos after the last one, otherwise it'll do nothing.
|
/// found pos after the last one, otherwise it'll do nothing.
|
||||||
///
|
|
||||||
virtual void NextFound(bool wrap);
|
virtual void NextFound(bool wrap);
|
||||||
|
|
||||||
/// Moves current position in the list to the previous found one
|
/// Moves current position in the list to the previous found one
|
||||||
/// @param wrap if true, this function will go to the last
|
/// @param wrap if true, this function will go to the last
|
||||||
/// found pos after the first one, otherwise it'll do nothing.
|
/// found pos after the first one, otherwise it'll do nothing.
|
||||||
///
|
|
||||||
virtual void PrevFound(bool wrap);
|
virtual void PrevFound(bool wrap);
|
||||||
|
|
||||||
/// @return const reference to currently used filter function
|
/// @return const reference to currently used filter function
|
||||||
///
|
|
||||||
const FilterFunction &getFilter() { return m_filter; }
|
const FilterFunction &getFilter() { return m_filter; }
|
||||||
|
|
||||||
/// @return true if list is currently filtered, false otherwise
|
/// @return true if list is currently filtered, false otherwise
|
||||||
///
|
|
||||||
bool isFiltered() { return m_options_ptr == &m_filtered_options; }
|
bool isFiltered() { return m_options_ptr == &m_filtered_options; }
|
||||||
|
|
||||||
/// Turns off filtering
|
/// Turns off filtering
|
||||||
///
|
|
||||||
void ShowAll() { m_options_ptr = &m_options; }
|
void ShowAll() { m_options_ptr = &m_options; }
|
||||||
|
|
||||||
/// Turns on filtering
|
/// Turns on filtering
|
||||||
///
|
|
||||||
void ShowFiltered() { m_options_ptr = &m_filtered_options; }
|
void ShowFiltered() { m_options_ptr = &m_filtered_options; }
|
||||||
|
|
||||||
/// Converts given position in list to string using ItemStringifier
|
|
||||||
/// if specified and an empty string otherwise
|
|
||||||
/// @param pos position to be converted
|
|
||||||
/// @return item converted to string
|
|
||||||
/// @see setItemDisplayer()
|
|
||||||
///
|
|
||||||
std::string GetItem(size_t pos);
|
|
||||||
|
|
||||||
std::string Stringify(const Item &item) const;
|
|
||||||
|
|
||||||
/// Refreshes the menu window
|
/// Refreshes the menu window
|
||||||
/// @see Window::Refresh()
|
/// @see Window::Refresh()
|
||||||
///
|
|
||||||
virtual void Refresh();
|
virtual void Refresh();
|
||||||
|
|
||||||
/// Scrolls by given amount of lines
|
/// Scrolls by given amount of lines
|
||||||
/// @param where indicated where exactly one wants to go
|
/// @param where indicated where exactly one wants to go
|
||||||
/// @see Window::Scroll()
|
/// @see Window::Scroll()
|
||||||
///
|
|
||||||
virtual void Scroll(Where where);
|
virtual void Scroll(Where where);
|
||||||
|
|
||||||
/// Cleares all options, used filters etc. It doesn't reset highlighted position though.
|
/// Cleares all options, used filters etc. It doesn't reset highlighted position though.
|
||||||
/// @see Reset()
|
/// @see Reset()
|
||||||
///
|
|
||||||
virtual void Clear();
|
virtual void Clear();
|
||||||
|
|
||||||
/// Sets highlighted position to 0
|
/// Sets highlighted position to 0
|
||||||
///
|
|
||||||
void Reset();
|
void Reset();
|
||||||
|
|
||||||
/// Sets prefix, that is put before each selected item to indicate its selection
|
/// Sets prefix, that is put before each selected item to indicate its selection
|
||||||
/// Note that the passed variable is not deleted along with menu object.
|
/// Note that the passed variable is not deleted along with menu object.
|
||||||
/// @param b pointer to buffer that contains the prefix
|
/// @param b pointer to buffer that contains the prefix
|
||||||
///
|
|
||||||
void SetSelectPrefix(const Buffer &b) { m_selected_prefix = b; }
|
void SetSelectPrefix(const Buffer &b) { m_selected_prefix = b; }
|
||||||
|
|
||||||
/// Sets suffix, that is put after each selected item to indicate its selection
|
/// Sets suffix, that is put after each selected item to indicate its selection
|
||||||
/// Note that the passed variable is not deleted along with menu object.
|
/// Note that the passed variable is not deleted along with menu object.
|
||||||
/// @param b pointer to buffer that contains the suffix
|
/// @param b pointer to buffer that contains the suffix
|
||||||
///
|
|
||||||
void SetSelectSuffix(const Buffer &b) { m_selected_suffix = b; }
|
void SetSelectSuffix(const Buffer &b) { m_selected_suffix = b; }
|
||||||
|
|
||||||
/// Sets custom color of highlighted position
|
/// Sets custom color of highlighted position
|
||||||
/// @param col custom color
|
/// @param col custom color
|
||||||
///
|
|
||||||
void HighlightColor(Color color) { m_highlight_color = color; }
|
void HighlightColor(Color color) { m_highlight_color = color; }
|
||||||
|
|
||||||
/// @return state of highlighting
|
/// @return state of highlighting
|
||||||
///
|
|
||||||
bool isHighlighted() { return m_highlight_enabled; }
|
bool isHighlighted() { return m_highlight_enabled; }
|
||||||
|
|
||||||
/// Turns on/off highlighting
|
/// Turns on/off highlighting
|
||||||
/// @param state state of hihglighting
|
/// @param state state of hihglighting
|
||||||
///
|
|
||||||
void Highlighting(bool state) { m_highlight_enabled = state; }
|
void Highlighting(bool state) { m_highlight_enabled = state; }
|
||||||
|
|
||||||
/// Turns on/off cyclic scrolling
|
/// Turns on/off cyclic scrolling
|
||||||
/// @param state state of cyclic scrolling
|
/// @param state state of cyclic scrolling
|
||||||
///
|
|
||||||
void CyclicScrolling(bool state) { m_cyclic_scroll_enabled = state; }
|
void CyclicScrolling(bool state) { m_cyclic_scroll_enabled = state; }
|
||||||
|
|
||||||
/// Turns on/off centered cursor
|
/// Turns on/off centered cursor
|
||||||
/// @param state state of centered cursor
|
/// @param state state of centered cursor
|
||||||
///
|
|
||||||
void CenteredCursor(bool state) { m_autocenter_cursor = state; }
|
void CenteredCursor(bool state) { m_autocenter_cursor = state; }
|
||||||
|
|
||||||
/// Checks if list is empty
|
/// Checks if list is empty
|
||||||
/// @return true if list is empty, false otherwise
|
/// @return true if list is empty, false otherwise
|
||||||
/// @see ReallyEmpty()
|
/// @see ReallyEmpty()
|
||||||
///
|
|
||||||
virtual bool Empty() const { return m_options_ptr->empty(); }
|
virtual bool Empty() const { return m_options_ptr->empty(); }
|
||||||
|
|
||||||
/// Checks if list is really empty since Empty() may not
|
/// Checks if list is really empty since Empty() may not
|
||||||
/// be accurate if filter is set)
|
/// be accurate if filter is set)
|
||||||
/// @return true if list is empty, false otherwise
|
/// @return true if list is empty, false otherwise
|
||||||
/// @see Empty()
|
/// @see Empty()
|
||||||
///
|
|
||||||
virtual bool ReallyEmpty() const { return m_options.empty(); }
|
virtual bool ReallyEmpty() const { return m_options.empty(); }
|
||||||
|
|
||||||
/// @return size of the list
|
/// @return size of the list
|
||||||
///
|
|
||||||
virtual size_t Size() const;
|
virtual size_t Size() const;
|
||||||
|
|
||||||
/// @return currently drawn item. The result is defined only within
|
/// @return currently drawn item. The result is defined only within
|
||||||
/// drawing function that is called by Refresh()
|
/// drawing function that is called by Refresh()
|
||||||
/// @see Refresh()
|
/// @see Refresh()
|
||||||
///
|
|
||||||
const Item &Drawn() const { return *(*m_options_ptr)[m_drawn_position]; }
|
const Item &Drawn() const { return *(*m_options_ptr)[m_drawn_position]; }
|
||||||
|
|
||||||
/// @return position of currently drawn item. The result is defined
|
/// @return position of currently drawn item. The result is defined
|
||||||
/// only within drawing function that is called by Refresh()
|
/// only within drawing function that is called by Refresh()
|
||||||
/// @see Refresh()
|
/// @see Refresh()
|
||||||
///
|
|
||||||
size_t DrawnPosition() const { return m_drawn_position; }
|
size_t DrawnPosition() const { return m_drawn_position; }
|
||||||
|
|
||||||
/// @return reference to last item on the list
|
/// @return reference to last item on the list
|
||||||
/// @throw List::InvalidItem if requested item is separator
|
/// @throw List::InvalidItem if requested item is separator
|
||||||
///
|
|
||||||
Menu<T>::Item &Back();
|
Menu<T>::Item &Back();
|
||||||
|
|
||||||
/// @return const reference to last item on the list
|
/// @return const reference to last item on the list
|
||||||
/// @throw List::InvalidItem if requested item is separator
|
/// @throw List::InvalidItem if requested item is separator
|
||||||
///
|
|
||||||
const Menu<T>::Item &Back() const;
|
const Menu<T>::Item &Back() const;
|
||||||
|
|
||||||
/// @return reference to curently highlighted object
|
/// @return reference to curently highlighted object
|
||||||
/// @throw List::InvalidItem if requested item is separator
|
/// @throw List::InvalidItem if requested item is separator
|
||||||
///
|
|
||||||
Menu<T>::Item &Current();
|
Menu<T>::Item &Current();
|
||||||
|
|
||||||
/// @return const reference to curently highlighted object
|
/// @return const reference to curently highlighted object
|
||||||
/// @throw List::InvalidItem if requested item is separator
|
/// @throw List::InvalidItem if requested item is separator
|
||||||
///
|
|
||||||
const Menu<T>::Item &Current() const;
|
const Menu<T>::Item &Current() const;
|
||||||
|
|
||||||
/// @param pos requested position
|
/// @param pos requested position
|
||||||
/// @return reference to item at given position
|
/// @return reference to item at given position
|
||||||
/// @throw std::out_of_range if given position is out of range
|
/// @throw std::out_of_range if given position is out of range
|
||||||
/// @throw List::InvalidItem if requested item is separator
|
/// @throw List::InvalidItem if requested item is separator
|
||||||
///
|
|
||||||
Menu<T>::Item &at(size_t pos);
|
Menu<T>::Item &at(size_t pos);
|
||||||
|
|
||||||
/// @param pos requested position
|
/// @param pos requested position
|
||||||
/// @return const reference to item at given position
|
/// @return const reference to item at given position
|
||||||
/// @throw std::out_of_range if given position is out of range
|
/// @throw std::out_of_range if given position is out of range
|
||||||
/// @throw List::InvalidItem if requested item is separator
|
/// @throw List::InvalidItem if requested item is separator
|
||||||
///
|
|
||||||
const Menu<T>::Item &at(size_t pos) const;
|
const Menu<T>::Item &at(size_t pos) const;
|
||||||
|
|
||||||
/// @param pos requested position
|
/// @param pos requested position
|
||||||
/// @return const reference to item at given position
|
/// @return const reference to item at given position
|
||||||
/// @throw List::InvalidItem if requested item is separator
|
/// @throw List::InvalidItem if requested item is separator
|
||||||
///
|
|
||||||
const Menu<T>::Item &operator[](size_t pos) const;
|
const Menu<T>::Item &operator[](size_t pos) const;
|
||||||
|
|
||||||
/// @param pos requested position
|
/// @param pos requested position
|
||||||
/// @return const reference to item at given position
|
/// @return const reference to item at given position
|
||||||
/// @throw List::InvalidItem if requested item is separator
|
/// @throw List::InvalidItem if requested item is separator
|
||||||
///
|
|
||||||
Menu<T>::Item &operator[](size_t pos);
|
Menu<T>::Item &operator[](size_t pos);
|
||||||
|
|
||||||
Iterator Begin() { return Iterator(m_options_ptr->begin()); }
|
Iterator Begin() { return Iterator(m_options_ptr->begin()); }
|
||||||
@@ -515,20 +421,15 @@ template <typename T> struct Menu : public Window, public List
|
|||||||
ConstReverseValueIterator RendV() const { return ConstReverseValueIterator(BeginV()); }
|
ConstReverseValueIterator RendV() const { return ConstReverseValueIterator(BeginV()); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Clears filter, filtered data etc.
|
|
||||||
///
|
|
||||||
void clearFiltered();
|
|
||||||
|
|
||||||
bool isHighlightable(size_t pos)
|
bool isHighlightable(size_t pos)
|
||||||
{
|
{
|
||||||
return !(*m_options_ptr)[pos]->isSeparator() && !(*m_options_ptr)[pos]->isInactive();
|
return !(*m_options_ptr)[pos]->isSeparator() && !(*m_options_ptr)[pos]->isInactive();
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemDisplayer m_item_displayer;
|
ItemDisplayer m_item_displayer;
|
||||||
ItemStringifier m_item_stringifier;
|
|
||||||
|
|
||||||
FilterFunction m_filter;
|
FilterFunction m_filter;
|
||||||
std::string m_search_constraint;
|
FilterFunction m_searcher;
|
||||||
|
|
||||||
std::vector<Item *> *m_options_ptr;
|
std::vector<Item *> *m_options_ptr;
|
||||||
std::vector<Item *> m_options;
|
std::vector<Item *> m_options;
|
||||||
@@ -551,11 +452,6 @@ private:
|
|||||||
Buffer m_selected_suffix;
|
Buffer m_selected_suffix;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Specialization for T = std::string. It's obvious that if strings are stored,
|
|
||||||
/// we don't need extra function to convert them to strings by default
|
|
||||||
template <> std::string Menu<std::string>::GetItem(size_t pos);
|
|
||||||
template <> std::string Menu<std::string>::Stringify(const Menu<std::string>::Item &item) const;
|
|
||||||
|
|
||||||
template <typename T> Menu<T>::Menu(size_t startx,
|
template <typename T> Menu<T>::Menu(size_t startx,
|
||||||
size_t starty,
|
size_t starty,
|
||||||
size_t width,
|
size_t width,
|
||||||
@@ -565,7 +461,6 @@ template <typename T> Menu<T>::Menu(size_t startx,
|
|||||||
Border border)
|
Border border)
|
||||||
: Window(startx, starty, width, height, title, color, border),
|
: Window(startx, starty, width, height, title, color, border),
|
||||||
m_item_displayer(0),
|
m_item_displayer(0),
|
||||||
m_item_stringifier(0),
|
|
||||||
m_options_ptr(&m_options),
|
m_options_ptr(&m_options),
|
||||||
m_beginning(0),
|
m_beginning(0),
|
||||||
m_highlight(0),
|
m_highlight(0),
|
||||||
@@ -806,18 +701,11 @@ template <typename T> void Menu<T>::Reset()
|
|||||||
m_beginning = 0;
|
m_beginning = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> void Menu<T>::clearFiltered()
|
|
||||||
{
|
|
||||||
m_filtered_options.clear();
|
|
||||||
m_filtered_positions.clear();
|
|
||||||
m_options_ptr = &m_options;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T> void Menu<T>::Clear()
|
template <typename T> void Menu<T>::Clear()
|
||||||
{
|
{
|
||||||
for (auto it = m_options.begin(); it != m_options.end(); ++it)
|
for (auto it = m_options.begin(); it != m_options.end(); ++it)
|
||||||
delete *it;
|
delete *it;
|
||||||
clearFiltered();
|
clearFilterResults();
|
||||||
m_options.clear();
|
m_options.clear();
|
||||||
m_found_positions.clear();
|
m_found_positions.clear();
|
||||||
m_options_ptr = &m_options;
|
m_options_ptr = &m_options;
|
||||||
@@ -858,15 +746,15 @@ template <typename T> size_t Menu<T>::Choice() const
|
|||||||
return m_highlight;
|
return m_highlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> template <typename Iterator_>
|
template <typename T>
|
||||||
void Menu<T>::Filter(Iterator_ first, Iterator_ last, const FilterFunction &f)
|
void Menu<T>::filter(ConstIterator first, ConstIterator last, const FilterFunction &f)
|
||||||
{
|
{
|
||||||
assert(m_options_ptr != &m_filtered_options);
|
assert(m_options_ptr != &m_filtered_options);
|
||||||
clearFiltered();
|
clearFilterResults();
|
||||||
m_filter = f;
|
m_filter = f;
|
||||||
for (auto it = first; it != last; ++it)
|
for (auto it = first; it != last; ++it)
|
||||||
{
|
{
|
||||||
if (m_filter(*this, *it))
|
if (m_filter(*it))
|
||||||
{
|
{
|
||||||
size_t pos = it-Begin();
|
size_t pos = it-Begin();
|
||||||
m_filtered_positions.push_back(pos);
|
m_filtered_positions.push_back(pos);
|
||||||
@@ -876,6 +764,34 @@ void Menu<T>::Filter(Iterator_ first, Iterator_ last, const FilterFunction &f)
|
|||||||
m_options_ptr = &m_filtered_options;
|
m_options_ptr = &m_filtered_options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> void Menu<T>::clearFilterResults()
|
||||||
|
{
|
||||||
|
m_filtered_options.clear();
|
||||||
|
m_filtered_positions.clear();
|
||||||
|
m_options_ptr = &m_options;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool Menu<T>::search(ConstIterator first, ConstIterator last, const FilterFunction &f)
|
||||||
|
{
|
||||||
|
m_found_positions.clear();
|
||||||
|
m_searcher = f;
|
||||||
|
for (auto it = first; it != last; ++it)
|
||||||
|
{
|
||||||
|
if (m_searcher(*it))
|
||||||
|
{
|
||||||
|
size_t pos = it-Begin();
|
||||||
|
m_found_positions.insert(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !m_found_positions.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> void Menu<T>::clearSearchResults()
|
||||||
|
{
|
||||||
|
m_found_positions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T> void Menu<T>::ReverseSelection(size_t beginning)
|
template <typename T> void Menu<T>::ReverseSelection(size_t beginning)
|
||||||
{
|
{
|
||||||
auto it = m_options_ptr->begin()+beginning;
|
auto it = m_options_ptr->begin()+beginning;
|
||||||
@@ -883,30 +799,11 @@ template <typename T> void Menu<T>::ReverseSelection(size_t beginning)
|
|||||||
(*it)->setSelected(!(*it)->isSelected() && !(*it)->isInactive());
|
(*it)->setSelected(!(*it)->isSelected() && !(*it)->isInactive());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> bool Menu<T>::Search(const std::string &constraint, size_t beginning, int flags)
|
|
||||||
{
|
|
||||||
m_found_positions.clear();
|
|
||||||
m_search_constraint.clear();
|
|
||||||
if (constraint.empty())
|
|
||||||
return false;
|
|
||||||
m_search_constraint = constraint;
|
|
||||||
Regex rx;
|
|
||||||
if (rx.compile(m_search_constraint, flags))
|
|
||||||
{
|
|
||||||
for (size_t i = beginning; i < m_options_ptr->size(); ++i)
|
|
||||||
{
|
|
||||||
if (rx.match(GetItem(i)))
|
|
||||||
m_found_positions.insert(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return !m_found_positions.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T> void Menu<T>::NextFound(bool wrap)
|
template <typename T> void Menu<T>::NextFound(bool wrap)
|
||||||
{
|
{
|
||||||
if (m_found_positions.empty())
|
if (m_found_positions.empty())
|
||||||
return;
|
return;
|
||||||
std::set<size_t>::iterator next = m_found_positions.upper_bound(m_highlight);
|
auto next = m_found_positions.upper_bound(m_highlight);
|
||||||
if (next != m_found_positions.end())
|
if (next != m_found_positions.end())
|
||||||
Highlight(*next);
|
Highlight(*next);
|
||||||
else if (wrap)
|
else if (wrap)
|
||||||
@@ -917,29 +814,13 @@ template <typename T> void Menu<T>::PrevFound(bool wrap)
|
|||||||
{
|
{
|
||||||
if (m_found_positions.empty())
|
if (m_found_positions.empty())
|
||||||
return;
|
return;
|
||||||
std::set<size_t>::iterator prev = m_found_positions.lower_bound(m_highlight);
|
auto prev = m_found_positions.lower_bound(m_highlight);
|
||||||
if (prev != m_found_positions.begin())
|
if (prev != m_found_positions.begin())
|
||||||
Highlight(*--prev);
|
Highlight(*--prev);
|
||||||
else if (wrap)
|
else if (wrap)
|
||||||
Highlight(*m_found_positions.rbegin());
|
Highlight(*m_found_positions.rbegin());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T> std::string Menu<T>::GetItem(size_t pos)
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
if (m_item_stringifier)
|
|
||||||
result = m_item_stringifier((*m_options_ptr)[pos]->value());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T> std::string Menu<T>::Stringify(const Item &item) const
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
if (m_item_stringifier)
|
|
||||||
result = m_item_stringifier(item.value());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T> typename Menu<T>::Item &Menu<T>::Back()
|
template <typename T> typename Menu<T>::Item &Menu<T>::Back()
|
||||||
{
|
{
|
||||||
return *m_options_ptr->back();
|
return *m_options_ptr->back();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
|
#include "regex_filter.h"
|
||||||
#include "song.h"
|
#include "song.h"
|
||||||
#include "status.h"
|
#include "status.h"
|
||||||
#include "utility/comparators.h"
|
#include "utility/comparators.h"
|
||||||
@@ -38,11 +39,18 @@ Playlist *myPlaylist = new Playlist;
|
|||||||
bool Playlist::ReloadTotalLength = 0;
|
bool Playlist::ReloadTotalLength = 0;
|
||||||
bool Playlist::ReloadRemaining = false;
|
bool Playlist::ReloadRemaining = false;
|
||||||
|
|
||||||
const size_t Playlist::SortOptions = 10;
|
namespace {//
|
||||||
const size_t Playlist::SortDialogWidth = 30;
|
|
||||||
size_t Playlist::SortDialogHeight;
|
|
||||||
|
|
||||||
Menu< std::pair<std::string, MPD::Song::GetFunction> > *Playlist::SortDialog = 0;
|
Menu< std::pair<std::string, MPD::Song::GetFunction> > *SortDialog = 0;
|
||||||
|
size_t SortDialogHeight;
|
||||||
|
|
||||||
|
const size_t SortOptions = 10;
|
||||||
|
const size_t SortDialogWidth = 30;
|
||||||
|
|
||||||
|
std::string songToString(const MPD::Song &s);
|
||||||
|
bool playlistEntryMatcher(const Regex &rx, const MPD::Song &s);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void Playlist::Init()
|
void Playlist::Init()
|
||||||
{
|
{
|
||||||
@@ -53,15 +61,9 @@ void Playlist::Init()
|
|||||||
Items->SetSelectPrefix(Config.selected_item_prefix);
|
Items->SetSelectPrefix(Config.selected_item_prefix);
|
||||||
Items->SetSelectSuffix(Config.selected_item_suffix);
|
Items->SetSelectSuffix(Config.selected_item_suffix);
|
||||||
if (Config.columns_in_playlist)
|
if (Config.columns_in_playlist)
|
||||||
{
|
|
||||||
Items->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *this));
|
Items->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *this));
|
||||||
Items->SetItemStringifier(SongInColumnsToString);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
Items->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_list_format));
|
Items->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_list_format));
|
||||||
Items->SetItemStringifier(SongToString);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!SortDialog)
|
if (!SortDialog)
|
||||||
{
|
{
|
||||||
@@ -284,6 +286,8 @@ void Playlist::GetSelectedSongs(MPD::SongList &v)
|
|||||||
v.push_back(Items->at(*it).value());
|
v.push_back(Items->at(*it).value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
std::string Playlist::currentFilter()
|
std::string Playlist::currentFilter()
|
||||||
{
|
{
|
||||||
std::string filter;
|
std::string filter;
|
||||||
@@ -297,11 +301,38 @@ void Playlist::applyFilter(const std::string &filter)
|
|||||||
if (w == Items)
|
if (w == Items)
|
||||||
{
|
{
|
||||||
Items->ShowAll();
|
Items->ShowAll();
|
||||||
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type);
|
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type, playlistEntryMatcher);
|
||||||
Items->Filter(Items->Begin(), Items->End(), rx);
|
Items->filter(Items->Begin(), Items->End(), rx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
|
bool Playlist::search(const std::string &constraint)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
if (w == Items)
|
||||||
|
{
|
||||||
|
auto rx = RegexFilter<MPD::Song>(constraint, Config.regex_type, playlistEntryMatcher);
|
||||||
|
result = Items->search(Items->Begin(), Items->End(), rx);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Playlist::nextFound(bool wrap)
|
||||||
|
{
|
||||||
|
if (w == Items)
|
||||||
|
Items->NextFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Playlist::prevFound(bool wrap)
|
||||||
|
{
|
||||||
|
if (w == Items)
|
||||||
|
Items->PrevFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
bool Playlist::isFiltered()
|
bool Playlist::isFiltered()
|
||||||
{
|
{
|
||||||
if (Items->isFiltered())
|
if (Items->isFiltered())
|
||||||
@@ -316,10 +347,6 @@ void Playlist::MoveSelectedItems(Movement where)
|
|||||||
{
|
{
|
||||||
if (Items->Empty() || isFiltered())
|
if (Items->Empty() || isFiltered())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// remove search results as we may move them to different positions, but
|
|
||||||
// search rememebers positions and may point to wrong ones after that.
|
|
||||||
Items->Search("");
|
|
||||||
|
|
||||||
switch (where)
|
switch (where)
|
||||||
{
|
{
|
||||||
@@ -462,6 +489,11 @@ void Playlist::EnableHighlighting()
|
|||||||
UpdateTimer();
|
UpdateTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Playlist::SortingInProgress()
|
||||||
|
{
|
||||||
|
return w == SortDialog;
|
||||||
|
}
|
||||||
|
|
||||||
std::string Playlist::TotalLength()
|
std::string Playlist::TotalLength()
|
||||||
{
|
{
|
||||||
std::ostringstream result;
|
std::ostringstream result;
|
||||||
@@ -516,16 +548,6 @@ const MPD::Song *Playlist::NowPlayingSong()
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Playlist::SongToString(const MPD::Song &s)
|
|
||||||
{
|
|
||||||
return s.toString(Config.song_list_format_dollar_free);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Playlist::SongInColumnsToString(const MPD::Song &s)
|
|
||||||
{
|
|
||||||
return s.toString(Config.song_in_columns_to_string_format);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Playlist::Add(const MPD::Song &s, bool in_playlist, bool play, int position)
|
bool Playlist::Add(const MPD::Song &s, bool in_playlist, bool play, int position)
|
||||||
{
|
{
|
||||||
if (Config.ncmpc_like_songs_adding && in_playlist)
|
if (Config.ncmpc_like_songs_adding && in_playlist)
|
||||||
@@ -631,3 +653,22 @@ bool Playlist::checkForSong (const MPD::Song &s)
|
|||||||
return true;
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {//
|
||||||
|
|
||||||
|
std::string songToString(const MPD::Song &s)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
if (Config.columns_in_playlist)
|
||||||
|
result = s.toString(Config.song_in_columns_to_string_format);
|
||||||
|
else
|
||||||
|
result = s.toString(Config.song_list_format_dollar_free);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool playlistEntryMatcher(const Regex &rx, const MPD::Song &s)
|
||||||
|
{
|
||||||
|
return rx.match(songToString(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
#include "song.h"
|
#include "song.h"
|
||||||
|
|
||||||
class Playlist : public Screen<Window>, public Filterable
|
class Playlist : public Screen<Window>, public Filterable, public Searchable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum Movement { mUp, mDown };
|
enum Movement { mUp, mDown };
|
||||||
@@ -51,9 +51,15 @@ class Playlist : public Screen<Window>, public Filterable
|
|||||||
virtual void ReverseSelection() { Items->ReverseSelection(); }
|
virtual void ReverseSelection() { Items->ReverseSelection(); }
|
||||||
virtual void GetSelectedSongs(MPD::SongList &);
|
virtual void GetSelectedSongs(MPD::SongList &);
|
||||||
|
|
||||||
|
/// Filterable implementation
|
||||||
virtual std::string currentFilter();
|
virtual std::string currentFilter();
|
||||||
virtual void applyFilter(const std::string &filter);
|
virtual void applyFilter(const std::string &filter);
|
||||||
|
|
||||||
|
/// Searchable implementation
|
||||||
|
virtual bool search(const std::string &constraint);
|
||||||
|
virtual void nextFound(bool wrap);
|
||||||
|
virtual void prevFound(bool wrap);
|
||||||
|
|
||||||
virtual List *GetList() { return w == Items ? Items : 0; }
|
virtual List *GetList() { return w == Items ? Items : 0; }
|
||||||
|
|
||||||
virtual bool isMergable() { return true; }
|
virtual bool isMergable() { return true; }
|
||||||
@@ -67,7 +73,7 @@ class Playlist : public Screen<Window>, public Filterable
|
|||||||
void Sort();
|
void Sort();
|
||||||
void Reverse();
|
void Reverse();
|
||||||
void AdjustSortOrder(Movement where);
|
void AdjustSortOrder(Movement where);
|
||||||
bool SortingInProgress() { return w == SortDialog; }
|
bool SortingInProgress();
|
||||||
|
|
||||||
void EnableHighlighting();
|
void EnableHighlighting();
|
||||||
void UpdateTimer() { time(&itsTimer); }
|
void UpdateTimer() { time(&itsTimer); }
|
||||||
@@ -81,8 +87,8 @@ class Playlist : public Screen<Window>, public Filterable
|
|||||||
|
|
||||||
bool checkForSong(const MPD::Song &s);
|
bool checkForSong(const MPD::Song &s);
|
||||||
|
|
||||||
static std::string SongToString(const MPD::Song &s);
|
//static std::string SongToString(const MPD::Song &s);
|
||||||
static std::string SongInColumnsToString(const MPD::Song &s);
|
//static std::string SongInColumnsToString(const MPD::Song &s);
|
||||||
|
|
||||||
Menu< MPD::Song > *Items;
|
Menu< MPD::Song > *Items;
|
||||||
|
|
||||||
@@ -97,7 +103,6 @@ class Playlist : public Screen<Window>, public Filterable
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::string TotalLength();
|
std::string TotalLength();
|
||||||
|
|
||||||
std::string itsBufferedStats;
|
std::string itsBufferedStats;
|
||||||
|
|
||||||
size_t itsTotalLength;
|
size_t itsTotalLength;
|
||||||
@@ -105,11 +110,6 @@ class Playlist : public Screen<Window>, public Filterable
|
|||||||
size_t itsScrollBegin;
|
size_t itsScrollBegin;
|
||||||
|
|
||||||
time_t itsTimer;
|
time_t itsTimer;
|
||||||
|
|
||||||
static Menu< std::pair<std::string, MPD::Song::GetFunction> > *SortDialog;
|
|
||||||
static const size_t SortOptions;
|
|
||||||
static const size_t SortDialogWidth;
|
|
||||||
static size_t SortDialogHeight;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Playlist *myPlaylist;
|
extern Playlist *myPlaylist;
|
||||||
|
|||||||
@@ -37,10 +37,18 @@ using Global::MainStartY;
|
|||||||
|
|
||||||
PlaylistEditor *myPlaylistEditor = new PlaylistEditor;
|
PlaylistEditor *myPlaylistEditor = new PlaylistEditor;
|
||||||
|
|
||||||
size_t PlaylistEditor::LeftColumnStartX;
|
namespace {//
|
||||||
size_t PlaylistEditor::LeftColumnWidth;
|
|
||||||
size_t PlaylistEditor::RightColumnStartX;
|
size_t LeftColumnStartX;
|
||||||
size_t PlaylistEditor::RightColumnWidth;
|
size_t LeftColumnWidth;
|
||||||
|
size_t RightColumnStartX;
|
||||||
|
size_t RightColumnWidth;
|
||||||
|
|
||||||
|
std::string SongToString(const MPD::Song &s);
|
||||||
|
bool PlaylistEntryMatcher(const Regex &rx, const std::string &playlist);
|
||||||
|
bool SongEntryMatcher(const Regex &rx, const MPD::Song &s);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void PlaylistEditor::Init()
|
void PlaylistEditor::Init()
|
||||||
{
|
{
|
||||||
@@ -61,15 +69,9 @@ void PlaylistEditor::Init()
|
|||||||
Content->SetSelectPrefix(Config.selected_item_prefix);
|
Content->SetSelectPrefix(Config.selected_item_prefix);
|
||||||
Content->SetSelectSuffix(Config.selected_item_suffix);
|
Content->SetSelectSuffix(Config.selected_item_suffix);
|
||||||
if (Config.columns_in_playlist_editor)
|
if (Config.columns_in_playlist_editor)
|
||||||
{
|
|
||||||
Content->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *this));
|
Content->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *this));
|
||||||
Content->SetItemStringifier(Playlist::SongInColumnsToString);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
Content->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_list_format));
|
Content->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_list_format));
|
||||||
Content->SetItemStringifier(Playlist::SongToString);
|
|
||||||
}
|
|
||||||
|
|
||||||
w = Playlists;
|
w = Playlists;
|
||||||
isInitialized = 1;
|
isInitialized = 1;
|
||||||
@@ -190,7 +192,7 @@ void PlaylistEditor::MoveSelectedItems(Playlist::Movement where)
|
|||||||
|
|
||||||
// remove search results as we may move them to different positions, but
|
// remove search results as we may move them to different positions, but
|
||||||
// search rememebers positions and may point to wrong ones after that.
|
// search rememebers positions and may point to wrong ones after that.
|
||||||
Content->Search("");
|
Content->clearSearchResults();
|
||||||
|
|
||||||
switch (where)
|
switch (where)
|
||||||
{
|
{
|
||||||
@@ -416,6 +418,8 @@ void PlaylistEditor::GetSelectedSongs(MPD::SongList &v)
|
|||||||
v.push_back(Content->at(*it).value());
|
v.push_back(Content->at(*it).value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
std::string PlaylistEditor::currentFilter()
|
std::string PlaylistEditor::currentFilter()
|
||||||
{
|
{
|
||||||
std::string filter;
|
std::string filter;
|
||||||
@@ -431,17 +435,53 @@ void PlaylistEditor::applyFilter(const std::string &filter)
|
|||||||
if (w == Playlists)
|
if (w == Playlists)
|
||||||
{
|
{
|
||||||
Playlists->ShowAll();
|
Playlists->ShowAll();
|
||||||
auto rx = RegexFilter<std::string>(filter, Config.regex_type);
|
auto rx = RegexFilter<std::string>(filter, Config.regex_type, PlaylistEntryMatcher);
|
||||||
Playlists->Filter(Playlists->Begin(), Playlists->End(), rx);
|
Playlists->filter(Playlists->Begin(), Playlists->End(), rx);
|
||||||
}
|
}
|
||||||
else if (w == Content)
|
else if (w == Content)
|
||||||
{
|
{
|
||||||
Content->ShowAll();
|
Content->ShowAll();
|
||||||
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type);
|
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type, SongEntryMatcher);
|
||||||
Content->Filter(Content->Begin(), Content->End(), rx);
|
Content->filter(Content->Begin(), Content->End(), rx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
|
bool PlaylistEditor::search(const std::string &constraint)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
if (w == Playlists)
|
||||||
|
{
|
||||||
|
auto rx = RegexFilter<std::string>(constraint, Config.regex_type, PlaylistEntryMatcher);
|
||||||
|
result = Playlists->search(Playlists->Begin(), Playlists->End(), rx);
|
||||||
|
}
|
||||||
|
else if (w == Content)
|
||||||
|
{
|
||||||
|
auto rx = RegexFilter<MPD::Song>(constraint, Config.regex_type, SongEntryMatcher);
|
||||||
|
result = Content->search(Content->Begin(), Content->End(), rx);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaylistEditor::nextFound(bool wrap)
|
||||||
|
{
|
||||||
|
if (w == Playlists)
|
||||||
|
Playlists->NextFound(wrap);
|
||||||
|
else if (w == Content)
|
||||||
|
Content->NextFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlaylistEditor::prevFound(bool wrap)
|
||||||
|
{
|
||||||
|
if (w == Playlists)
|
||||||
|
Playlists->PrevFound(wrap);
|
||||||
|
else if (w == Content)
|
||||||
|
Content->PrevFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
void PlaylistEditor::Locate(const std::string &name)
|
void PlaylistEditor::Locate(const std::string &name)
|
||||||
{
|
{
|
||||||
if (!isInitialized)
|
if (!isInitialized)
|
||||||
@@ -466,5 +506,32 @@ List *PlaylistEditor::GetList()
|
|||||||
else if (w == Content)
|
else if (w == Content)
|
||||||
return Content;
|
return Content;
|
||||||
else // silence compiler
|
else // silence compiler
|
||||||
|
{
|
||||||
assert(false);
|
assert(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {//
|
||||||
|
|
||||||
|
std::string SongToString(const MPD::Song &s)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
if (Config.columns_in_playlist_editor)
|
||||||
|
result = s.toString(Config.song_in_columns_to_string_format);
|
||||||
|
else
|
||||||
|
result = s.toString(Config.song_list_format_dollar_free);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlaylistEntryMatcher(const Regex &rx, const std::string &playlist)
|
||||||
|
{
|
||||||
|
return rx.match(playlist);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SongEntryMatcher(const Regex &rx, const MPD::Song &s)
|
||||||
|
{
|
||||||
|
return rx.match(SongToString(s));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
#include "ncmpcpp.h"
|
#include "ncmpcpp.h"
|
||||||
|
|
||||||
class PlaylistEditor : public Screen<Window>, public Filterable
|
class PlaylistEditor : public Screen<Window>, public Filterable, public Searchable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual void SwitchTo();
|
virtual void SwitchTo();
|
||||||
@@ -47,9 +47,15 @@ class PlaylistEditor : public Screen<Window>, public Filterable
|
|||||||
virtual void ReverseSelection() { Content->ReverseSelection(); }
|
virtual void ReverseSelection() { Content->ReverseSelection(); }
|
||||||
virtual void GetSelectedSongs(MPD::SongList &);
|
virtual void GetSelectedSongs(MPD::SongList &);
|
||||||
|
|
||||||
|
/// Filterable implementation
|
||||||
virtual std::string currentFilter();
|
virtual std::string currentFilter();
|
||||||
virtual void applyFilter(const std::string &filter);
|
virtual void applyFilter(const std::string &filter);
|
||||||
|
|
||||||
|
/// Searchable implementation
|
||||||
|
virtual bool search(const std::string &constraint);
|
||||||
|
virtual void nextFound(bool wrap);
|
||||||
|
virtual void prevFound(bool wrap);
|
||||||
|
|
||||||
virtual void Locate(const std::string &);
|
virtual void Locate(const std::string &);
|
||||||
|
|
||||||
virtual List *GetList();
|
virtual List *GetList();
|
||||||
@@ -73,11 +79,6 @@ class PlaylistEditor : public Screen<Window>, public Filterable
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void AddToPlaylist(bool);
|
void AddToPlaylist(bool);
|
||||||
|
|
||||||
static size_t LeftColumnStartX;
|
|
||||||
static size_t LeftColumnWidth;
|
|
||||||
static size_t RightColumnStartX;
|
|
||||||
static size_t RightColumnWidth;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern PlaylistEditor *myPlaylistEditor;
|
extern PlaylistEditor *myPlaylistEditor;
|
||||||
|
|||||||
@@ -26,20 +26,18 @@
|
|||||||
template <typename T> struct RegexFilter
|
template <typename T> struct RegexFilter
|
||||||
{
|
{
|
||||||
typedef NCurses::Menu<T> MenuT;
|
typedef NCurses::Menu<T> MenuT;
|
||||||
typedef typename NCurses::Menu<T>::Item MenuItem;
|
typedef typename NCurses::Menu<T>::Item Item;
|
||||||
typedef std::function<bool(const Regex &, MenuT &menu, const MenuItem &)> FilterFunction;
|
typedef std::function<bool(const Regex &, const T &)> FilterFunction;
|
||||||
|
|
||||||
RegexFilter(const std::string ®ex_, int cflags, FilterFunction custom_filter = 0)
|
RegexFilter(const std::string ®ex_, int cflags, FilterFunction filter)
|
||||||
: m_rx(regex_, cflags), m_custom_filter(custom_filter) { }
|
: m_rx(regex_, cflags), m_filter(filter) { }
|
||||||
|
|
||||||
bool operator()(MenuT &menu, const MenuItem &item) {
|
bool operator()(const Item &item) {
|
||||||
if (m_rx.regex().empty())
|
if (m_rx.regex().empty())
|
||||||
return true;
|
return true;
|
||||||
if (!m_rx.error().empty())
|
if (!m_rx.error().empty())
|
||||||
return false;
|
return false;
|
||||||
if (m_custom_filter)
|
return m_filter(m_rx, item.value());
|
||||||
return m_custom_filter(m_rx, menu, item);
|
|
||||||
return m_rx.match(menu.Stringify(item));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string currentFilter(MenuT &menu)
|
static std::string currentFilter(MenuT &menu)
|
||||||
@@ -53,7 +51,38 @@ template <typename T> struct RegexFilter
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Regex m_rx;
|
Regex m_rx;
|
||||||
FilterFunction m_custom_filter;
|
FilterFunction m_filter;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T> struct RegexItemFilter
|
||||||
|
{
|
||||||
|
typedef NCurses::Menu<T> MenuT;
|
||||||
|
typedef typename NCurses::Menu<T>::Item Item;
|
||||||
|
typedef std::function<bool(const Regex &, const Item &)> FilterFunction;
|
||||||
|
|
||||||
|
RegexItemFilter(const std::string ®ex_, int cflags, FilterFunction filter)
|
||||||
|
: m_rx(regex_, cflags), m_filter(filter) { }
|
||||||
|
|
||||||
|
bool operator()(const Item &item) {
|
||||||
|
if (m_rx.regex().empty())
|
||||||
|
return true;
|
||||||
|
if (!m_rx.error().empty())
|
||||||
|
return false;
|
||||||
|
return m_filter(m_rx, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string currentFilter(MenuT &menu)
|
||||||
|
{
|
||||||
|
std::string filter;
|
||||||
|
auto rf = menu.getFilter().template target< RegexItemFilter<T> >();
|
||||||
|
if (rf)
|
||||||
|
filter = rf->m_rx.regex();
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Regex m_rx;
|
||||||
|
FilterFunction m_filter;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -18,12 +18,14 @@
|
|||||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "global.h"
|
#include "global.h"
|
||||||
#include "helpers.h"
|
#include "helpers.h"
|
||||||
#include "playlist.h"
|
#include "playlist.h"
|
||||||
|
#include "regex_filter.h"
|
||||||
#include "search_engine.h"
|
#include "search_engine.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "status.h"
|
#include "status.h"
|
||||||
@@ -34,6 +36,40 @@ using Global::MainStartY;
|
|||||||
|
|
||||||
SearchEngine *mySearcher = new SearchEngine;
|
SearchEngine *mySearcher = new SearchEngine;
|
||||||
|
|
||||||
|
namespace {//
|
||||||
|
|
||||||
|
/*const std::array<const std::string, 11> constraintsNames = {{
|
||||||
|
"Any",
|
||||||
|
"Artist",
|
||||||
|
"Album Artist",
|
||||||
|
"Title",
|
||||||
|
"Album",
|
||||||
|
"Filename",
|
||||||
|
"Composer",
|
||||||
|
"Performer",
|
||||||
|
"Genre",
|
||||||
|
"Date",
|
||||||
|
"Comment"
|
||||||
|
}};
|
||||||
|
|
||||||
|
const std::array<const char *, 3> searchModes = {{
|
||||||
|
"Match if tag contains searched phrase (no regexes)",
|
||||||
|
"Match if tag contains searched phrase (regexes supported)",
|
||||||
|
"Match only if both values are the same"
|
||||||
|
}};
|
||||||
|
|
||||||
|
namespace pos {//
|
||||||
|
const size_t searchIn = constraintsNames.size()-1+1+1; // separated
|
||||||
|
const size_t searchMode = searchIn+1;
|
||||||
|
const size_t search = searchMode+1+1; // separated
|
||||||
|
const size_t reset = search+1;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
std::string SEItemToString(const SEItem &ei);
|
||||||
|
bool SEItemEntryMatcher(const Regex &rx, const Menu<SEItem>::Item &item, bool filter);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const char *SearchEngine::ConstraintsNames[] =
|
const char *SearchEngine::ConstraintsNames[] =
|
||||||
{
|
{
|
||||||
"Any",
|
"Any",
|
||||||
@@ -70,7 +106,6 @@ void SearchEngine::Init()
|
|||||||
w->setItemDisplayer(Display::SearchEngine);
|
w->setItemDisplayer(Display::SearchEngine);
|
||||||
w->SetSelectPrefix(Config.selected_item_prefix);
|
w->SetSelectPrefix(Config.selected_item_prefix);
|
||||||
w->SetSelectSuffix(Config.selected_item_suffix);
|
w->SetSelectSuffix(Config.selected_item_suffix);
|
||||||
w->SetItemStringifier(SearchEngineOptionToString);
|
|
||||||
SearchMode = &SearchModes[Config.search_engine_default_search_mode];
|
SearchMode = &SearchModes[Config.search_engine_default_search_mode];
|
||||||
isInitialized = 1;
|
isInitialized = 1;
|
||||||
}
|
}
|
||||||
@@ -261,23 +296,42 @@ void SearchEngine::GetSelectedSongs(MPD::SongList &v)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
std::string SearchEngine::currentFilter()
|
std::string SearchEngine::currentFilter()
|
||||||
{
|
{
|
||||||
return RegexFilter<SEItem>::currentFilter(*w);
|
return RegexItemFilter<SEItem>::currentFilter(*w);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SearchEngine::applyFilter(const std::string &filter)
|
void SearchEngine::applyFilter(const std::string &filter)
|
||||||
{
|
{
|
||||||
w->ShowAll();
|
w->ShowAll();
|
||||||
auto fun = [](const Regex &rx, Menu<SEItem> &menu, const Menu<SEItem>::Item &item) {
|
auto fun = std::bind(SEItemEntryMatcher, _1, _2, true);
|
||||||
if (item.isSeparator() || !item.value().isSong())
|
auto rx = RegexItemFilter<SEItem>(filter, Config.regex_type, fun);
|
||||||
return true;
|
w->filter(w->Begin(), w->End(), rx);
|
||||||
return rx.match(menu.Stringify(item));
|
|
||||||
};
|
|
||||||
auto rx = RegexFilter<SEItem>(filter, Config.regex_type, fun);
|
|
||||||
w->Filter(w->Begin(), w->End(), rx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
|
bool SearchEngine::search(const std::string &constraint)
|
||||||
|
{
|
||||||
|
auto fun = std::bind(SEItemEntryMatcher, _1, _2, false);
|
||||||
|
auto rx = RegexItemFilter<SEItem>(constraint, Config.regex_type, fun);
|
||||||
|
return w->search(w->Begin(), w->End(), rx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchEngine::nextFound(bool wrap)
|
||||||
|
{
|
||||||
|
w->NextFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SearchEngine::prevFound(bool wrap)
|
||||||
|
{
|
||||||
|
w->PrevFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
void SearchEngine::UpdateFoundList()
|
void SearchEngine::UpdateFoundList()
|
||||||
{
|
{
|
||||||
bool bold = 0;
|
bool bold = 0;
|
||||||
@@ -525,15 +579,28 @@ void SearchEngine::Search()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string SearchEngine::SearchEngineOptionToString(const SEItem &ei)
|
namespace {//
|
||||||
|
|
||||||
|
std::string SEItemToString(const SEItem &ei)
|
||||||
{
|
{
|
||||||
std::string result;
|
std::string result;
|
||||||
if (ei.isSong())
|
if (ei.isSong())
|
||||||
{
|
{
|
||||||
if (Config.columns_in_search_engine)
|
if (Config.columns_in_search_engine)
|
||||||
result = Playlist::SongInColumnsToString(ei.song());
|
result = ei.song().toString(Config.song_in_columns_to_string_format);
|
||||||
else
|
else
|
||||||
result = ei.song().toString(Config.song_list_format_dollar_free);
|
result = ei.song().toString(Config.song_list_format_dollar_free);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
result = ei.buffer().Str();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SEItemEntryMatcher(const Regex &rx, const Menu<SEItem>::Item &item, bool filter)
|
||||||
|
{
|
||||||
|
if (item.isSeparator() || !item.value().isSong())
|
||||||
|
return filter;
|
||||||
|
return rx.match(SEItemToString(item.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
#include "regex_filter.h"
|
|
||||||
#include "interfaces.h"
|
#include "interfaces.h"
|
||||||
#include "mpdpp.h"
|
#include "mpdpp.h"
|
||||||
#include "ncmpcpp.h"
|
#include "ncmpcpp.h"
|
||||||
@@ -74,7 +73,7 @@ struct SEItem
|
|||||||
MPD::Song itsSong;
|
MPD::Song itsSong;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SearchEngine : public Screen< Menu<SEItem> >, public Filterable
|
class SearchEngine : public Screen< Menu<SEItem> >, public Filterable, public Searchable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual void Resize();
|
virtual void Resize();
|
||||||
@@ -94,9 +93,15 @@ class SearchEngine : public Screen< Menu<SEItem> >, public Filterable
|
|||||||
virtual void ReverseSelection() { w->ReverseSelection(StaticOptions); }
|
virtual void ReverseSelection() { w->ReverseSelection(StaticOptions); }
|
||||||
virtual void GetSelectedSongs(MPD::SongList &);
|
virtual void GetSelectedSongs(MPD::SongList &);
|
||||||
|
|
||||||
|
/// Filterable implementation
|
||||||
virtual std::string currentFilter();
|
virtual std::string currentFilter();
|
||||||
virtual void applyFilter(const std::string &filter);
|
virtual void applyFilter(const std::string &filter);
|
||||||
|
|
||||||
|
/// Searchable implementation
|
||||||
|
virtual bool search(const std::string &constraint);
|
||||||
|
virtual void nextFound(bool wrap);
|
||||||
|
virtual void prevFound(bool wrap);
|
||||||
|
|
||||||
virtual List *GetList() { return w->Size() >= StaticOptions ? w : 0; }
|
virtual List *GetList() { return w->Size() >= StaticOptions ? w : 0; }
|
||||||
|
|
||||||
virtual bool isMergable() { return true; }
|
virtual bool isMergable() { return true; }
|
||||||
@@ -118,8 +123,6 @@ class SearchEngine : public Screen< Menu<SEItem> >, public Filterable
|
|||||||
|
|
||||||
const char **SearchMode;
|
const char **SearchMode;
|
||||||
|
|
||||||
static std::string SearchEngineOptionToString(const SEItem &);
|
|
||||||
|
|
||||||
static const char *SearchModes[];
|
static const char *SearchModes[];
|
||||||
|
|
||||||
static const size_t ConstraintsNumber = 11;
|
static const size_t ConstraintsNumber = 11;
|
||||||
|
|||||||
@@ -227,6 +227,8 @@ void NcmpcppStatusChanged(MPD::Connection *, MPD::StatusChanges changed, void *)
|
|||||||
if (!np.empty())
|
if (!np.empty())
|
||||||
WindowTitle(np.toString(Config.song_window_title_format));
|
WindowTitle(np.toString(Config.song_window_title_format));
|
||||||
|
|
||||||
|
myPlaylist->Items->clearSearchResults();
|
||||||
|
|
||||||
bool is_filtered = myPlaylist->Items->isFiltered();
|
bool is_filtered = myPlaylist->Items->isFiltered();
|
||||||
myPlaylist->Items->ShowAll();
|
myPlaylist->Items->ShowAll();
|
||||||
|
|
||||||
@@ -302,7 +304,7 @@ void NcmpcppStatusChanged(MPD::Connection *, MPD::StatusChanges changed, void *)
|
|||||||
if (myLibrary->Columns() == 2)
|
if (myLibrary->Columns() == 2)
|
||||||
myLibrary->Albums->Clear();
|
myLibrary->Albums->Clear();
|
||||||
else
|
else
|
||||||
myLibrary->Artists->Clear();
|
myLibrary->Tags->Clear();
|
||||||
}
|
}
|
||||||
if (myPlaylistEditor->Main())
|
if (myPlaylistEditor->Main())
|
||||||
myPlaylistEditor->Content->Clear();
|
myPlaylistEditor->Content->Clear();
|
||||||
|
|||||||
@@ -47,22 +47,47 @@ using Global::MainStartY;
|
|||||||
|
|
||||||
TagEditor *myTagEditor = new TagEditor;
|
TagEditor *myTagEditor = new TagEditor;
|
||||||
|
|
||||||
std::string TagEditor::PatternsFile = "patterns.list";
|
namespace {//
|
||||||
std::list<std::string> TagEditor::Patterns;
|
|
||||||
|
|
||||||
size_t TagEditor::LeftColumnWidth;
|
size_t LeftColumnWidth;
|
||||||
size_t TagEditor::LeftColumnStartX;
|
size_t LeftColumnStartX;
|
||||||
size_t TagEditor::MiddleColumnWidth;
|
size_t MiddleColumnWidth;
|
||||||
size_t TagEditor::MiddleColumnStartX;
|
size_t MiddleColumnStartX;
|
||||||
size_t TagEditor::RightColumnWidth;
|
size_t RightColumnWidth;
|
||||||
size_t TagEditor::RightColumnStartX;
|
size_t RightColumnStartX;
|
||||||
|
|
||||||
size_t TagEditor::FParserDialogWidth;
|
size_t FParserDialogWidth;
|
||||||
size_t TagEditor::FParserDialogHeight;
|
size_t FParserDialogHeight;
|
||||||
size_t TagEditor::FParserWidth;
|
size_t FParserWidth;
|
||||||
size_t TagEditor::FParserWidthOne;
|
size_t FParserWidthOne;
|
||||||
size_t TagEditor::FParserWidthTwo;
|
size_t FParserWidthTwo;
|
||||||
size_t TagEditor::FParserHeight;
|
size_t FParserHeight;
|
||||||
|
|
||||||
|
std::list<std::string> Patterns;
|
||||||
|
std::string PatternsFile = "patterns.list";
|
||||||
|
|
||||||
|
std::string CapitalizeFirstLetters(const std::string &s);
|
||||||
|
void CapitalizeFirstLetters(MPD::MutableSong &s);
|
||||||
|
void LowerAllLetters(MPD::MutableSong &s);
|
||||||
|
void GetTagList(TagLib::StringList &list, const MPD::MutableSong &s, MPD::Song::GetFunction f);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
void WriteID3v2(const TagLib::ByteVector &type, TagLib::ID3v2::Tag *tag, const T &list);
|
||||||
|
void WriteXiphComments(const MPD::MutableSong &s, TagLib::Ogg::XiphComment *tag);
|
||||||
|
|
||||||
|
void GetPatternList();
|
||||||
|
void SavePatternList();
|
||||||
|
|
||||||
|
MPD::MutableSong::SetFunction IntoSetFunction(char c);
|
||||||
|
std::string GenerateFilename(const MPD::MutableSong &s, const std::string &pattern);
|
||||||
|
std::string ParseFilename(MPD::MutableSong &s, std::string mask, bool preview);
|
||||||
|
|
||||||
|
std::string SongToString(const MPD::MutableSong &s);
|
||||||
|
bool DirEntryMatcher(const Regex &rx, const string_pair &dir, bool filter);
|
||||||
|
bool AlbumEntryMatcher(const Regex &rx, const string_pair &dir);
|
||||||
|
bool SongEntryMatcher(const Regex &rx, const MPD::MutableSong &s);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void TagEditor::Init()
|
void TagEditor::Init()
|
||||||
{
|
{
|
||||||
@@ -74,14 +99,12 @@ void TagEditor::Init()
|
|||||||
Albums->CyclicScrolling(Config.use_cyclic_scrolling);
|
Albums->CyclicScrolling(Config.use_cyclic_scrolling);
|
||||||
Albums->CenteredCursor(Config.centered_cursor);
|
Albums->CenteredCursor(Config.centered_cursor);
|
||||||
Albums->setItemDisplayer(Display::Pair<std::string, std::string>);
|
Albums->setItemDisplayer(Display::Pair<std::string, std::string>);
|
||||||
Albums->SetItemStringifier(StringPairToString);
|
|
||||||
|
|
||||||
Dirs = new Menu<string_pair>(0, MainStartY, LeftColumnWidth, MainHeight, Config.titles_visibility ? "Directories" : "", Config.main_color, brNone);
|
Dirs = new Menu<string_pair>(0, MainStartY, LeftColumnWidth, MainHeight, Config.titles_visibility ? "Directories" : "", Config.main_color, brNone);
|
||||||
Dirs->HighlightColor(Config.active_column_color);
|
Dirs->HighlightColor(Config.active_column_color);
|
||||||
Dirs->CyclicScrolling(Config.use_cyclic_scrolling);
|
Dirs->CyclicScrolling(Config.use_cyclic_scrolling);
|
||||||
Dirs->CenteredCursor(Config.centered_cursor);
|
Dirs->CenteredCursor(Config.centered_cursor);
|
||||||
Dirs->setItemDisplayer(Display::Pair<std::string, std::string>);
|
Dirs->setItemDisplayer(Display::Pair<std::string, std::string>);
|
||||||
Dirs->SetItemStringifier(StringPairToString);
|
|
||||||
|
|
||||||
LeftColumn = Config.albums_in_tag_editor ? Albums : Dirs;
|
LeftColumn = Config.albums_in_tag_editor ? Albums : Dirs;
|
||||||
|
|
||||||
@@ -112,7 +135,6 @@ void TagEditor::Init()
|
|||||||
Tags->SetSelectPrefix(Config.selected_item_prefix);
|
Tags->SetSelectPrefix(Config.selected_item_prefix);
|
||||||
Tags->SetSelectSuffix(Config.selected_item_suffix);
|
Tags->SetSelectSuffix(Config.selected_item_suffix);
|
||||||
Tags->setItemDisplayer(Display::Tags);
|
Tags->setItemDisplayer(Display::Tags);
|
||||||
Tags->SetItemStringifier(TagToString);
|
|
||||||
|
|
||||||
FParserDialog = new Menu<std::string>((COLS-FParserDialogWidth)/2, (MainHeight-FParserDialogHeight)/2+MainStartY, FParserDialogWidth, FParserDialogHeight, "", Config.main_color, Config.window_border);
|
FParserDialog = new Menu<std::string>((COLS-FParserDialogWidth)/2, (MainHeight-FParserDialogHeight)/2+MainStartY, FParserDialogWidth, FParserDialogHeight, "", Config.main_color, Config.window_border);
|
||||||
FParserDialog->CyclicScrolling(Config.use_cyclic_scrolling);
|
FParserDialog->CyclicScrolling(Config.use_cyclic_scrolling);
|
||||||
@@ -772,6 +794,8 @@ void TagEditor::GetSelectedSongs(MPD::SongList &v)
|
|||||||
v.push_back(static_cast<MPD::Song>((*Tags)[*it].value()));
|
v.push_back(static_cast<MPD::Song>((*Tags)[*it].value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
std::string TagEditor::currentFilter()
|
std::string TagEditor::currentFilter()
|
||||||
{
|
{
|
||||||
std::string filter;
|
std::string filter;
|
||||||
@@ -789,28 +813,70 @@ void TagEditor::applyFilter(const std::string &filter)
|
|||||||
if (w == Dirs)
|
if (w == Dirs)
|
||||||
{
|
{
|
||||||
Dirs->ShowAll();
|
Dirs->ShowAll();
|
||||||
auto fun = [](const Regex &rx, Menu<string_pair> &menu, const Menu<string_pair>::Item &item) {
|
auto fun = std::bind(DirEntryMatcher, _1, _2, true);
|
||||||
if (item.value().first == "." || item.value().first == "..")
|
|
||||||
return true;
|
|
||||||
return rx.match(menu.Stringify(item));
|
|
||||||
};
|
|
||||||
auto rx = RegexFilter<string_pair>(filter, Config.regex_type, fun);
|
auto rx = RegexFilter<string_pair>(filter, Config.regex_type, fun);
|
||||||
Dirs->Filter(Dirs->Begin(), Dirs->End(), rx);
|
Dirs->filter(Dirs->Begin(), Dirs->End(), rx);
|
||||||
}
|
}
|
||||||
else if (w == Albums)
|
else if (w == Albums)
|
||||||
{
|
{
|
||||||
Albums->ShowAll();
|
Albums->ShowAll();
|
||||||
auto rx = RegexFilter<string_pair>(filter, Config.regex_type);
|
auto rx = RegexFilter<string_pair>(filter, Config.regex_type, AlbumEntryMatcher);
|
||||||
Albums->Filter(Albums->Begin(), Albums->End(), rx);
|
Albums->filter(Albums->Begin(), Albums->End(), rx);
|
||||||
}
|
}
|
||||||
else if (w == Tags)
|
else if (w == Tags)
|
||||||
{
|
{
|
||||||
Tags->ShowAll();
|
Tags->ShowAll();
|
||||||
auto rx = RegexFilter<MPD::MutableSong>(filter, Config.regex_type);
|
auto rx = RegexFilter<MPD::MutableSong>(filter, Config.regex_type, SongEntryMatcher);
|
||||||
Tags->Filter(Tags->Begin(), Tags->End(), rx);
|
Tags->filter(Tags->Begin(), Tags->End(), rx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
|
bool TagEditor::search(const std::string &constraint)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
if (w == Dirs)
|
||||||
|
{
|
||||||
|
auto fun = std::bind(DirEntryMatcher, _1, _2, false);
|
||||||
|
auto rx = RegexFilter<string_pair>(constraint, Config.regex_type, fun);
|
||||||
|
result = Dirs->search(Dirs->Begin(), Dirs->End(), rx);
|
||||||
|
}
|
||||||
|
else if (w == Albums)
|
||||||
|
{
|
||||||
|
auto rx = RegexFilter<string_pair>(constraint, Config.regex_type, AlbumEntryMatcher);
|
||||||
|
result = Albums->search(Albums->Begin(), Albums->End(), rx);
|
||||||
|
}
|
||||||
|
else if (w == Tags)
|
||||||
|
{
|
||||||
|
auto rx = RegexFilter<MPD::MutableSong>(constraint, Config.regex_type, SongEntryMatcher);
|
||||||
|
result = Tags->search(Tags->Begin(), Tags->End(), rx);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagEditor::nextFound(bool wrap)
|
||||||
|
{
|
||||||
|
if (w == Dirs)
|
||||||
|
Dirs->NextFound(wrap);
|
||||||
|
else if (w == Albums)
|
||||||
|
Albums->NextFound(wrap);
|
||||||
|
else if (w == Tags)
|
||||||
|
Tags->NextFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagEditor::prevFound(bool wrap)
|
||||||
|
{
|
||||||
|
if (w == Dirs)
|
||||||
|
Dirs->PrevFound(wrap);
|
||||||
|
else if (w == Albums)
|
||||||
|
Albums->PrevFound(wrap);
|
||||||
|
else if (w == Tags)
|
||||||
|
Tags->PrevFound(wrap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************************************************************/
|
||||||
|
|
||||||
List *TagEditor::GetList()
|
List *TagEditor::GetList()
|
||||||
{
|
{
|
||||||
if (w == LeftColumn)
|
if (w == LeftColumn)
|
||||||
@@ -1008,40 +1074,6 @@ void TagEditor::ReadTags(MPD::MutableSong &s)
|
|||||||
s.setComment(f.tag()->comment().to8Bit(1));
|
s.setComment(f.tag()->comment().to8Bit(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
template <typename T> void WriteID3v2(const TagLib::ByteVector &type, TagLib::ID3v2::Tag *tag, const T &list)
|
|
||||||
{
|
|
||||||
using TagLib::ID3v2::TextIdentificationFrame;
|
|
||||||
tag->removeFrames(type);
|
|
||||||
TextIdentificationFrame *frame = new TextIdentificationFrame(type, TagLib::String::UTF8);
|
|
||||||
frame->setText(list);
|
|
||||||
tag->addFrame(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TagEditor::WriteXiphComments(const MPD::MutableSong &s, TagLib::Ogg::XiphComment *tag)
|
|
||||||
{
|
|
||||||
TagLib::StringList list;
|
|
||||||
|
|
||||||
tag->addField("DISCNUMBER", ToWString(s.getDisc())); // disc
|
|
||||||
|
|
||||||
tag->removeField("ALBUM ARTIST"); // album artist
|
|
||||||
GetTagList(list, s, &MPD::Song::getAlbumArtist);
|
|
||||||
for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it)
|
|
||||||
tag->addField("ALBUM ARTIST", *it, 0);
|
|
||||||
|
|
||||||
tag->removeField("COMPOSER"); // composer
|
|
||||||
GetTagList(list, s, &MPD::Song::getComposer);
|
|
||||||
for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it)
|
|
||||||
tag->addField("COMPOSER", *it, 0);
|
|
||||||
|
|
||||||
tag->removeField("PERFORMER"); // performer
|
|
||||||
GetTagList(list, s, &MPD::Song::getPerformer);
|
|
||||||
for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it)
|
|
||||||
tag->addField("PERFORMER", *it, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TagEditor::WriteTags(MPD::MutableSong &s)
|
bool TagEditor::WriteTags(MPD::MutableSong &s)
|
||||||
{
|
{
|
||||||
std::string path_to_file;
|
std::string path_to_file;
|
||||||
@@ -1129,7 +1161,9 @@ bool TagEditor::WriteTags(MPD::MutableSong &s)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TagEditor::CapitalizeFirstLetters(const std::string &s)
|
namespace {//
|
||||||
|
|
||||||
|
std::string CapitalizeFirstLetters(const std::string &s)
|
||||||
{
|
{
|
||||||
if (s.empty())
|
if (s.empty())
|
||||||
return "";
|
return "";
|
||||||
@@ -1144,7 +1178,7 @@ std::string TagEditor::CapitalizeFirstLetters(const std::string &s)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagEditor::CapitalizeFirstLetters(MPD::MutableSong &s)
|
void CapitalizeFirstLetters(MPD::MutableSong &s)
|
||||||
{
|
{
|
||||||
for (const SongInfo::Metadata *m = SongInfo::Tags; m->Name; ++m)
|
for (const SongInfo::Metadata *m = SongInfo::Tags; m->Name; ++m)
|
||||||
{
|
{
|
||||||
@@ -1154,7 +1188,7 @@ void TagEditor::CapitalizeFirstLetters(MPD::MutableSong &s)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagEditor::LowerAllLetters(MPD::MutableSong &s)
|
void LowerAllLetters(MPD::MutableSong &s)
|
||||||
{
|
{
|
||||||
for (const SongInfo::Metadata *m = SongInfo::Tags; m->Name; ++m)
|
for (const SongInfo::Metadata *m = SongInfo::Tags; m->Name; ++m)
|
||||||
{
|
{
|
||||||
@@ -1167,7 +1201,7 @@ void TagEditor::LowerAllLetters(MPD::MutableSong &s)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagEditor::GetTagList(TagLib::StringList &list, const MPD::MutableSong &s, MPD::Song::GetFunction f)
|
void GetTagList(TagLib::StringList &list, const MPD::MutableSong &s, MPD::Song::GetFunction f)
|
||||||
{
|
{
|
||||||
list.clear();
|
list.clear();
|
||||||
unsigned pos = 0;
|
unsigned pos = 0;
|
||||||
@@ -1175,18 +1209,38 @@ void TagEditor::GetTagList(TagLib::StringList &list, const MPD::MutableSong &s,
|
|||||||
list.append(ToWString(value));
|
list.append(ToWString(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TagEditor::TagToString(const MPD::MutableSong &s)
|
template <typename T> void WriteID3v2(const TagLib::ByteVector &type, TagLib::ID3v2::Tag *tag, const T &list)
|
||||||
{
|
{
|
||||||
std::string result;
|
using TagLib::ID3v2::TextIdentificationFrame;
|
||||||
size_t i = myTagEditor->TagTypes->Choice();
|
tag->removeFrames(type);
|
||||||
if (i < 11)
|
TextIdentificationFrame *frame = new TextIdentificationFrame(type, TagLib::String::UTF8);
|
||||||
result = (s.*SongInfo::Tags[i].Get)(0);
|
frame->setText(list);
|
||||||
else if (i == 12)
|
tag->addFrame(frame);
|
||||||
result = s.getNewURI().empty() ? s.getName() : s.getName() + " -> " + s.getNewURI();
|
|
||||||
return result.empty() ? Config.empty_tag : result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagEditor::GetPatternList()
|
void WriteXiphComments(const MPD::MutableSong &s, TagLib::Ogg::XiphComment *tag)
|
||||||
|
{
|
||||||
|
TagLib::StringList list;
|
||||||
|
|
||||||
|
tag->addField("DISCNUMBER", ToWString(s.getDisc())); // disc
|
||||||
|
|
||||||
|
tag->removeField("ALBUM ARTIST"); // album artist
|
||||||
|
GetTagList(list, s, &MPD::Song::getAlbumArtist);
|
||||||
|
for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it)
|
||||||
|
tag->addField("ALBUM ARTIST", *it, 0);
|
||||||
|
|
||||||
|
tag->removeField("COMPOSER"); // composer
|
||||||
|
GetTagList(list, s, &MPD::Song::getComposer);
|
||||||
|
for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it)
|
||||||
|
tag->addField("COMPOSER", *it, 0);
|
||||||
|
|
||||||
|
tag->removeField("PERFORMER"); // performer
|
||||||
|
GetTagList(list, s, &MPD::Song::getPerformer);
|
||||||
|
for (TagLib::StringList::ConstIterator it = list.begin(); it != list.end(); ++it)
|
||||||
|
tag->addField("PERFORMER", *it, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetPatternList()
|
||||||
{
|
{
|
||||||
if (Patterns.empty())
|
if (Patterns.empty())
|
||||||
{
|
{
|
||||||
@@ -1197,12 +1251,12 @@ void TagEditor::GetPatternList()
|
|||||||
while (getline(input, line))
|
while (getline(input, line))
|
||||||
if (!line.empty())
|
if (!line.empty())
|
||||||
Patterns.push_back(line);
|
Patterns.push_back(line);
|
||||||
input.close();
|
input.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagEditor::SavePatternList()
|
void SavePatternList()
|
||||||
{
|
{
|
||||||
std::ofstream output(PatternsFile.c_str());
|
std::ofstream output(PatternsFile.c_str());
|
||||||
if (output.is_open())
|
if (output.is_open())
|
||||||
@@ -1213,8 +1267,7 @@ void TagEditor::SavePatternList()
|
|||||||
output.close();
|
output.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MPD::MutableSong::SetFunction IntoSetFunction(char c)
|
||||||
MPD::MutableSong::SetFunction TagEditor::IntoSetFunction(char c)
|
|
||||||
{
|
{
|
||||||
switch (c)
|
switch (c)
|
||||||
{
|
{
|
||||||
@@ -1245,14 +1298,14 @@ MPD::MutableSong::SetFunction TagEditor::IntoSetFunction(char c)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TagEditor::GenerateFilename(const MPD::MutableSong &s, const std::string &pattern)
|
std::string GenerateFilename(const MPD::MutableSong &s, const std::string &pattern)
|
||||||
{
|
{
|
||||||
std::string result = s.toString(pattern);
|
std::string result = s.toString(pattern);
|
||||||
removeInvalidCharsFromFilename(result);
|
removeInvalidCharsFromFilename(result);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string TagEditor::ParseFilename(MPD::MutableSong &s, std::string mask, bool preview)
|
std::string ParseFilename(MPD::MutableSong &s, std::string mask, bool preview)
|
||||||
{
|
{
|
||||||
std::ostringstream result;
|
std::ostringstream result;
|
||||||
std::vector<std::string> separators;
|
std::vector<std::string> separators;
|
||||||
@@ -1268,7 +1321,7 @@ std::string TagEditor::ParseFilename(MPD::MutableSong &s, std::string mask, bool
|
|||||||
separators.push_back(mask.substr(0, i));
|
separators.push_back(mask.substr(0, i));
|
||||||
}
|
}
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (std::vector<std::string>::const_iterator it = separators.begin(); it != separators.end(); ++it, ++i)
|
for (auto it = separators.begin(); it != separators.end(); ++it, ++i)
|
||||||
{
|
{
|
||||||
size_t j = file.find(*it);
|
size_t j = file.find(*it);
|
||||||
tags.at(i).second = file.substr(0, j);
|
tags.at(i).second = file.substr(0, j);
|
||||||
@@ -1289,23 +1342,53 @@ std::string TagEditor::ParseFilename(MPD::MutableSong &s, std::string mask, bool
|
|||||||
return "Error while parsing filename!\n";
|
return "Error while parsing filename!\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::vector< std::pair<char, std::string> >::iterator it = tags.begin(); it != tags.end(); ++it)
|
for (auto it = tags.begin(); it != tags.end(); ++it)
|
||||||
{
|
{
|
||||||
for (std::string::iterator j = it->second.begin(); j != it->second.end(); ++j)
|
for (std::string::iterator j = it->second.begin(); j != it->second.end(); ++j)
|
||||||
if (*j == '_')
|
if (*j == '_')
|
||||||
*j = ' ';
|
*j = ' ';
|
||||||
|
|
||||||
if (!preview)
|
if (!preview)
|
||||||
{
|
{
|
||||||
MPD::MutableSong::SetFunction set = IntoSetFunction(it->first);
|
MPD::MutableSong::SetFunction set = IntoSetFunction(it->first);
|
||||||
if (set)
|
if (set)
|
||||||
s.setTag(set, it->second);
|
s.setTag(set, it->second);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
result << "%" << it->first << ": " << it->second << "\n";
|
result << "%" << it->first << ": " << it->second << "\n";
|
||||||
}
|
}
|
||||||
return result.str();
|
return result.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string SongToString(const MPD::MutableSong &s)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
size_t i = myTagEditor->TagTypes->Choice();
|
||||||
|
if (i < 11)
|
||||||
|
result = (s.*SongInfo::Tags[i].Get)(0);
|
||||||
|
else if (i == 12)
|
||||||
|
result = s.getNewURI().empty() ? s.getName() : s.getName() + " -> " + s.getNewURI();
|
||||||
|
return result.empty() ? Config.empty_tag : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DirEntryMatcher(const Regex &rx, const string_pair &dir, bool filter)
|
||||||
|
{
|
||||||
|
if (dir.first == "." || dir.first == "..")
|
||||||
|
return filter;
|
||||||
|
return rx.match(dir.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AlbumEntryMatcher(const Regex &rx, const string_pair &dir)
|
||||||
|
{
|
||||||
|
return rx.match(dir.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SongEntryMatcher(const Regex &rx, const MPD::MutableSong &s)
|
||||||
|
{
|
||||||
|
return rx.match(SongToString(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
#include "regex_filter.h"
|
#include "regex_filter.h"
|
||||||
#include "screen.h"
|
#include "screen.h"
|
||||||
|
|
||||||
class TagEditor : public Screen<Window>, public Filterable
|
class TagEditor : public Screen<Window>, public Filterable, public Searchable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TagEditor() : FParser(0), FParserHelper(0), FParserLegend(0), FParserPreview(0), itsBrowsedDir("/") { }
|
TagEditor() : FParser(0), FParserHelper(0), FParserLegend(0), FParserPreview(0), itsBrowsedDir("/") { }
|
||||||
@@ -62,9 +62,15 @@ class TagEditor : public Screen<Window>, public Filterable
|
|||||||
virtual void ReverseSelection() { Tags->ReverseSelection(); }
|
virtual void ReverseSelection() { Tags->ReverseSelection(); }
|
||||||
virtual void GetSelectedSongs(MPD::SongList &);
|
virtual void GetSelectedSongs(MPD::SongList &);
|
||||||
|
|
||||||
|
/// Filterable implementation
|
||||||
virtual std::string currentFilter();
|
virtual std::string currentFilter();
|
||||||
virtual void applyFilter(const std::string &filter);
|
virtual void applyFilter(const std::string &filter);
|
||||||
|
|
||||||
|
/// Searchable implementation
|
||||||
|
virtual bool search(const std::string &constraint);
|
||||||
|
virtual void nextFound(bool wrap);
|
||||||
|
virtual void prevFound(bool wrap);
|
||||||
|
|
||||||
virtual List *GetList();
|
virtual List *GetList();
|
||||||
|
|
||||||
virtual bool isMergable() { return true; }
|
virtual bool isMergable() { return true; }
|
||||||
@@ -103,39 +109,8 @@ class TagEditor : public Screen<Window>, public Filterable
|
|||||||
Scrollpad *FParserPreview;
|
Scrollpad *FParserPreview;
|
||||||
bool FParserUsePreview;
|
bool FParserUsePreview;
|
||||||
|
|
||||||
static std::string CapitalizeFirstLetters(const std::string &);
|
|
||||||
static void CapitalizeFirstLetters(MPD::MutableSong &);
|
|
||||||
static void LowerAllLetters(MPD::MutableSong &);
|
|
||||||
static void GetTagList(TagLib::StringList &, const MPD::MutableSong &, MPD::Song::GetFunction);
|
|
||||||
static void WriteXiphComments(const MPD::MutableSong &, TagLib::Ogg::XiphComment *);
|
|
||||||
|
|
||||||
static void GetPatternList();
|
|
||||||
static void SavePatternList();
|
|
||||||
static MPD::MutableSong::SetFunction IntoSetFunction(char);
|
|
||||||
static std::string GenerateFilename(const MPD::MutableSong &, const std::string &);
|
|
||||||
static std::string ParseFilename(MPD::MutableSong &, std::string, bool);
|
|
||||||
|
|
||||||
static std::string TagToString(const MPD::MutableSong &);
|
|
||||||
|
|
||||||
std::string itsBrowsedDir;
|
std::string itsBrowsedDir;
|
||||||
std::string itsHighlightedDir;
|
std::string itsHighlightedDir;
|
||||||
|
|
||||||
static std::string PatternsFile;
|
|
||||||
static std::list<std::string> Patterns;
|
|
||||||
|
|
||||||
static size_t MiddleColumnWidth;
|
|
||||||
static size_t LeftColumnStartX;
|
|
||||||
static size_t LeftColumnWidth;
|
|
||||||
static size_t MiddleColumnStartX;
|
|
||||||
static size_t RightColumnWidth;
|
|
||||||
static size_t RightColumnStartX;
|
|
||||||
|
|
||||||
static size_t FParserDialogWidth;
|
|
||||||
static size_t FParserDialogHeight;
|
|
||||||
static size_t FParserWidth;
|
|
||||||
static size_t FParserWidthOne;
|
|
||||||
static size_t FParserWidthTwo;
|
|
||||||
static size_t FParserHeight;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern TagEditor *myTagEditor;
|
extern TagEditor *myTagEditor;
|
||||||
|
|||||||
Reference in New Issue
Block a user