implement Searchable interface / make use of unnamed namespaces

This commit is contained in:
Andrzej Rybczak
2012-09-01 16:10:52 +02:00
parent 12ca003350
commit 0811e30319
19 changed files with 904 additions and 639 deletions

View File

@@ -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;
@@ -360,9 +359,9 @@ void Action::FindItem(const FindDirection fd)
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();

View File

@@ -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();
}; };

View File

@@ -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));
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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,7 +921,7 @@ 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;
@@ -845,3 +931,17 @@ bool MediaLibrary::SortAllTracks(const MPD::Song &a, const MPD::Song &b)
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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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())
@@ -317,10 +348,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)
{ {
case mUp: case mUp:
@@ -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));
}
}

View File

@@ -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;

View File

@@ -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));
}
} }

View File

@@ -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;

View File

@@ -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 &regex_, int cflags, FilterFunction custom_filter = 0) RegexFilter(const std::string &regex_, 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 &regex_, 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

View File

@@ -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()));
}
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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())
{ {
@@ -1202,7 +1256,7 @@ void TagEditor::GetPatternList()
} }
} }
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,7 +1342,7 @@ 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 == '_')
@@ -1307,5 +1360,35 @@ std::string TagEditor::ParseFilename(MPD::MutableSong &s, std::string mask, bool
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

View File

@@ -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;