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;
List *mList = myScreen->GetList();
if (!mList)
return;
Searchable *w = dynamic_cast<Searchable *>(myScreen);
assert(w);
LockStatusbar();
Statusbar() << "Find " << (fd == fdForward ? "forward" : "backward") << ": ";
@@ -349,7 +348,7 @@ void Action::FindItem(const FindDirection fd)
if (!findme.empty())
ShowMessage("Searching...");
bool success = mList->Search(findme, 0, REG_ICASE | Config.regex_type);
bool success = w->search(findme);
if (findme.empty())
return;
@@ -359,10 +358,10 @@ void Action::FindItem(const FindDirection fd)
else
ShowMessage("Unable to find \"%s\"", findme.c_str());
if (fd == fdForward)
mList->NextFound(Config.wrapped_search);
else
mList->PrevFound(Config.wrapped_search);
if (fd == fdForward)
w->nextFound(Config.wrapped_search);
else
w->prevFound(Config.wrapped_search);
if (myScreen == myPlaylist)
myPlaylist->EnableHighlighting();
@@ -377,7 +376,7 @@ void Action::ListsChangeFinisher()
# endif // HAVE_TAGLIB_H
)
{
if (myScreen->ActiveWindow() == myLibrary->Artists)
if (myScreen->ActiveWindow() == myLibrary->Tags)
{
myLibrary->Albums->Clear();
myLibrary->Songs->Clear();
@@ -816,7 +815,7 @@ void Delete::Run()
ShowMessage("Deleting directories is disabled by default, see man page for more details");
return;
}
if (myBrowser->isParentDir(myBrowser->Main()->Choice()))
if (myBrowser->isParentDirectory(item))
return;
std::string name = item.type == itSong ? item.song->getName() : item.name;
@@ -1038,9 +1037,6 @@ void MoveSelectedItemsTo::Run()
ShowMessage("No selected items to move");
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();
std::vector<size_t> list;
myPlaylist->Items->GetSelected(list);
@@ -1163,13 +1159,11 @@ void ToggleDisplayMode::Run()
{
myPlaylist->Items->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *myPlaylist));
myPlaylist->Items->SetTitle(Config.titles_visibility ? Display::Columns(myPlaylist->Items->GetWidth()) : "");
myPlaylist->Items->SetItemStringifier(Playlist::SongInColumnsToString);
}
else
{
myPlaylist->Items->setItemDisplayer(std::bind(Display::Songs, _1, *myPlaylist, Config.song_list_format));
myPlaylist->Items->SetTitle("");
myPlaylist->Items->SetItemStringifier(Playlist::SongToString);
}
}
else if (myScreen == myBrowser)
@@ -1192,12 +1186,10 @@ void ToggleDisplayMode::Run()
if (Config.columns_in_playlist_editor)
{
myPlaylistEditor->Content->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *myPlaylistEditor));
myPlaylistEditor->Content->SetItemStringifier(Playlist::SongInColumnsToString);
}
else
{
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
{
# ifdef HAVE_TAGLIB_H
return myScreen->ActiveWindow() == myLibrary->Artists
&& !myLibrary->Artists->Empty();
return myScreen->ActiveWindow() == myLibrary->Tags
&& !myLibrary->Tags->Empty();
# else
return false;
# endif // HAVE_TAGLIB_H
@@ -1422,13 +1414,13 @@ void EditLibraryTag::Run()
return;
LockStatusbar();
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();
if (!new_tag.empty() && new_tag != myLibrary->Artists->Current().value())
if (!new_tag.empty() && new_tag != myLibrary->Tags->Current().value())
{
ShowMessage("Updating tags...");
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);
assert(set);
bool success = true;
@@ -2064,33 +2056,51 @@ void Find::Run()
s->Main()->Flush();
}
bool FindItemBackward::canBeRun() const
{
return dynamic_cast<Searchable *>(myScreen);
}
void FindItemForward::Run()
{
FindItem(fdForward);
ListsChangeFinisher();
}
bool FindItemForward::canBeRun() const
{
return dynamic_cast<Searchable *>(myScreen);
}
void FindItemBackward::Run()
{
FindItem(fdBackward);
ListsChangeFinisher();
}
bool NextFoundItem::canBeRun() const
{
return dynamic_cast<Searchable *>(myScreen);
}
void NextFoundItem::Run()
{
List *mList = myScreen->GetList();
if (!mList)
return;
mList->NextFound(Config.wrapped_search);
Searchable *w = dynamic_cast<Searchable *>(myScreen);
assert(w);
w->nextFound(Config.wrapped_search);
ListsChangeFinisher();
}
bool PreviousFoundItem::canBeRun() const
{
return dynamic_cast<Searchable *>(myScreen);
}
void PreviousFoundItem::Run()
{
List *mList = myScreen->GetList();
if (!mList)
return;
mList->PrevFound(Config.wrapped_search);
Searchable *w = dynamic_cast<Searchable *>(myScreen);
assert(w);
w->prevFound(Config.wrapped_search);
ListsChangeFinisher();
}
@@ -2209,7 +2219,7 @@ void ToggleBrowserSortMode::Run()
bool ToggleLibraryTagType::canBeRun() const
{
return (myScreen->ActiveWindow() == myLibrary->Artists)
return (myScreen->ActiveWindow() == myLibrary->Tags)
|| (myLibrary->Columns() == 2 && myScreen->ActiveWindow() == myLibrary->Albums);
}
@@ -2233,8 +2243,8 @@ void ToggleLibraryTagType::Run()
{
Config.media_lib_primary_tag = new_tagitem;
std::string item_type = tagTypeToString(Config.media_lib_primary_tag);
myLibrary->Artists->SetTitle(Config.titles_visibility ? item_type + "s" : "");
myLibrary->Artists->Reset();
myLibrary->Tags->SetTitle(Config.titles_visibility ? item_type + "s" : "");
myLibrary->Tags->Reset();
lowercase(item_type);
if (myLibrary->Columns() == 2)
{
@@ -2246,8 +2256,8 @@ void ToggleLibraryTagType::Run()
}
else
{
myLibrary->Artists->Clear();
myLibrary->Artists->Display();
myLibrary->Tags->Clear();
myLibrary->Tags->Display();
}
ShowMessage("Switched to list of %s tag", item_type.c_str());
}
@@ -2345,8 +2355,8 @@ void ShowArtistInfo::Run()
if (s)
artist = s->getArtist();
else if (myScreen == myLibrary && myLibrary->Main() == myLibrary->Artists && !myLibrary->Artists->Empty())
artist = myLibrary->Artists->Current().value();
else if (myScreen == myLibrary && myLibrary->Main() == myLibrary->Tags && !myLibrary->Tags->Empty())
artist = myLibrary->Tags->Current().value();
if (!artist.empty() && myLastfm->SetArtistInfoArgs(artist, Config.lastfm_preferred_language))
myLastfm->SwitchTo();

View File

@@ -655,24 +655,28 @@ struct Find : public Action
struct FindItemForward : public Action
{
FindItemForward() : Action(aFindItemForward, "find_item_forward") { }
virtual bool canBeRun() const;
virtual void Run();
};
struct FindItemBackward : public Action
{
FindItemBackward() : Action(aFindItemBackward, "find_item_backward") { }
virtual bool canBeRun() const;
virtual void Run();
};
struct NextFoundItem : public Action
{
NextFoundItem() : Action(aNextFoundItem, "next_found_item") { }
virtual bool canBeRun() const;
virtual void Run();
};
struct PreviousFoundItem : public Action
{
PreviousFoundItem() : Action(aPreviousFoundItem, "previous_found_item") { }
virtual bool canBeRun() const;
virtual void Run();
};

View File

@@ -32,10 +32,8 @@
#include "playlist.h"
#include "settings.h"
#include "status.h"
#include "tag_editor.h"
#include "utility/comparators.h"
#ifdef HAVE_TAGLIB_H
# include "tag_editor.h"
#endif // HAVE_TAGLIB_H
using Global::MainHeight;
using Global::MainStartY;
@@ -48,7 +46,15 @@ using MPD::itPlaylist;
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()
{
@@ -59,7 +65,6 @@ void Browser::Init()
w->SetSelectPrefix(Config.selected_item_prefix);
w->SetSelectSuffix(Config.selected_item_suffix);
w->setItemDisplayer(Display::Items);
w->SetItemStringifier(ItemToString);
if (SupportedExtensions.empty())
Mpd.GetSupportedExtensions(SupportedExtensions);
@@ -126,7 +131,7 @@ void Browser::EnterPressed()
{
case itDirectory:
{
if (isParentDir(w->Choice()))
if (isParentDirectory(item))
{
size_t slash = itsBrowsedDir.rfind("/");
if (slash != std::string::npos)
@@ -171,10 +176,11 @@ void Browser::SpacePressed()
return;
}
if (isParentDir(w->Choice()))
const MPD::Item &item = w->Current().value();
if (isParentDirectory(item))
return;
const MPD::Item &item = w->Current().value();
switch (item.type)
{
case itDirectory:
@@ -319,6 +325,8 @@ void Browser::GetSelectedSongs(MPD::SongList &v)
}
}
/***********************************************************************/
std::string Browser::currentFilter()
{
return RegexFilter<MPD::Item>::currentFilter(*w);
@@ -327,26 +335,32 @@ std::string Browser::currentFilter()
void Browser::applyFilter(const std::string &filter)
{
w->ShowAll();
auto fun = [](const Regex &rx, Menu<MPD::Item> &menu, const Menu<MPD::Item>::Item &item) {
if (item.value().type == MPD::itDirectory && item.value().name == "..")
return true;
return rx.match(menu.Stringify(item));
};
auto fun = std::bind(BrowserEntryMatcher, _1, _2, true);
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(".");
if (last_dot > file.length())
return false;
std::string ext = file.substr(last_dot+1);
lowercase(ext);
return SupportedExtensions.find(ext) != SupportedExtensions.end();
auto fun = std::bind(BrowserEntryMatcher, _1, _2, false);
auto rx = RegexFilter<MPD::Item>(constraint, Config.regex_type, fun);
return w->search(w->Begin(), w->End(), rx);
}
void Browser::nextFound(bool wrap)
{
w->NextFound(wrap);
}
void Browser::prevFound(bool wrap)
{
w->PrevFound(wrap);
}
/***********************************************************************/
void Browser::LocateSong(const MPD::Song &s)
{
if (s.getDirectory().empty())
@@ -592,7 +606,20 @@ void Browser::UpdateItemList()
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;
switch (item.type)
@@ -601,13 +628,23 @@ std::string Browser::ItemToString(const MPD::Item &item)
result = "[" + getBasename(item.name) + "]";
break;
case MPD::itSong:
if (!Config.columns_in_browser)
result = item.song->toString(Config.song_list_format_dollar_free);
if (Config.columns_in_browser)
result = item.song->toString(Config.song_in_columns_to_string_format);
else
result = Playlist::SongInColumnsToString(*item.song);
result = item.song->toString(Config.song_list_format_dollar_free);
break;
case MPD::itPlaylist:
result = Config.browser_playlist_prefix.Str() + getBasename(item.name);
break;
}
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 "screen.h"
class Browser : public Screen< Menu<MPD::Item> >, public Filterable
class Browser : public Screen< Menu<MPD::Item> >, public Filterable, public Searchable
{
public:
Browser() : itsBrowseLocally(0), itsScrollBeginning(0), itsBrowsedDir("/") { }
@@ -48,9 +48,15 @@ class Browser : public Screen< Menu<MPD::Item> >, public Filterable
virtual void ReverseSelection();
virtual void GetSelectedSongs(MPD::SongList &);
/// Filterable implementation
virtual std::string currentFilter();
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 bool isMergable() { return true; }
@@ -68,18 +74,15 @@ class Browser : public Screen< Menu<MPD::Item> >, public Filterable
# endif // !WIN32
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:
virtual void Init();
virtual bool isLockable() { return true; }
private:
static bool hasSupportedExtension(const std::string &);
static std::string ItemToString(const MPD::Item &item);
static std::set<std::string> SupportedExtensions;
bool itsBrowseLocally;
size_t itsScrollBeginning;
std::string itsBrowsedDir;

View File

@@ -30,4 +30,11 @@ struct Filterable
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

View File

@@ -28,6 +28,7 @@
#include "media_library.h"
#include "mpdpp.h"
#include "playlist.h"
#include "regex_filter.h"
#include "status.h"
#include "utility/comparators.h"
#include "utility/type_conversions.h"
@@ -38,19 +39,39 @@ using Global::myScreen;
MediaLibrary *myLibrary = new MediaLibrary;
bool MediaLibrary::hasTwoColumns;
size_t MediaLibrary::itsLeftColStartX;
size_t MediaLibrary::itsLeftColWidth;
size_t MediaLibrary::itsMiddleColWidth;
size_t MediaLibrary::itsMiddleColStartX;
size_t MediaLibrary::itsRightColWidth;
size_t MediaLibrary::itsRightColStartX;
namespace {//
bool hasTwoColumns;
size_t itsLeftColStartX;
size_t itsLeftColWidth;
size_t itsMiddleColWidth;
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
// 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
// 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()
{
@@ -61,13 +82,13 @@ void MediaLibrary::Init()
itsRightColWidth = COLS-COLS/3*2-1;
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);
Artists->HighlightColor(Config.active_column_color);
Artists->CyclicScrolling(Config.use_cyclic_scrolling);
Artists->CenteredCursor(Config.centered_cursor);
Artists->SetSelectPrefix(Config.selected_item_prefix);
Artists->SetSelectSuffix(Config.selected_item_suffix);
Artists->setItemDisplayer(DisplayPrimaryTags);
Tags = new Menu<std::string>(0, MainStartY, itsLeftColWidth, MainHeight, Config.titles_visibility ? tagTypeToString(Config.media_lib_primary_tag) + "s" : "", Config.main_color, brNone);
Tags->HighlightColor(Config.active_column_color);
Tags->CyclicScrolling(Config.use_cyclic_scrolling);
Tags->CenteredCursor(Config.centered_cursor);
Tags->SetSelectPrefix(Config.selected_item_prefix);
Tags->SetSelectSuffix(Config.selected_item_suffix);
Tags->setItemDisplayer(DisplayPrimaryTags);
Albums = new Menu<SearchConstraints>(itsMiddleColStartX, MainStartY, itsMiddleColWidth, MainHeight, Config.titles_visibility ? "Albums" : "", Config.main_color, brNone);
Albums->HighlightColor(Config.main_highlight_color);
@@ -76,7 +97,6 @@ void MediaLibrary::Init()
Albums->SetSelectPrefix(Config.selected_item_prefix);
Albums->SetSelectSuffix(Config.selected_item_suffix);
Albums->setItemDisplayer(DisplayAlbums);
Albums->SetItemStringifier(AlbumToString);
Songs = new Menu<MPD::Song>(itsRightColStartX, MainStartY, itsRightColWidth, MainHeight, Config.titles_visibility ? "Songs" : "", Config.main_color, brNone);
Songs->HighlightColor(Config.main_highlight_color);
@@ -85,9 +105,8 @@ void MediaLibrary::Init()
Songs->SetSelectPrefix(Config.selected_item_prefix);
Songs->SetSelectSuffix(Config.selected_item_suffix);
Songs->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_library_format));
Songs->SetItemStringifier(SongToString);
w = Artists;
w = Tags;
isInitialized = 1;
}
@@ -112,11 +131,11 @@ void MediaLibrary::Resize()
itsRightColWidth = width-itsMiddleColWidth-1;
}
Artists->Resize(itsLeftColWidth, MainHeight);
Tags->Resize(itsLeftColWidth, MainHeight);
Albums->Resize(itsMiddleColWidth, MainHeight);
Songs->Resize(itsRightColWidth, MainHeight);
Artists->MoveTo(itsLeftColStartX, MainStartY);
Tags->MoveTo(itsLeftColStartX, MainStartY);
Albums->MoveTo(itsMiddleColStartX, MainStartY);
Songs->MoveTo(itsRightColStartX, MainStartY);
@@ -125,7 +144,7 @@ void MediaLibrary::Resize()
void MediaLibrary::Refresh()
{
Artists->Display();
Tags->Display();
mvvline(MainStartY, itsMiddleColStartX-1, 0, MainHeight);
Albums->Display();
mvvline(MainStartY, itsRightColStartX-1, 0, MainHeight);
@@ -149,13 +168,13 @@ void MediaLibrary::SwitchTo()
{
hasTwoColumns = !hasTwoColumns;
hasToBeResized = 1;
Artists->Clear();
Tags->Clear();
Albums->Clear();
Albums->Reset();
Songs->Clear();
if (hasTwoColumns)
{
if (w == Artists)
if (w == Tags)
NextColumn();
if (Config.titles_visibility)
{
@@ -195,7 +214,7 @@ std::basic_string<my_char_t> MediaLibrary::Title()
void MediaLibrary::Update()
{
if (!hasTwoColumns && Artists->ReallyEmpty())
if (!hasTwoColumns && Tags->ReallyEmpty())
{
MPD::TagList list;
Albums->Clear();
@@ -207,13 +226,13 @@ void MediaLibrary::Update()
if (it->empty() && !Config.media_library_display_empty_tag)
continue;
utf_to_locale(*it);
Artists->AddItem(*it);
Tags->AddItem(*it);
}
Artists->Window::Clear();
Artists->Refresh();
Tags->Window::Clear();
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
// disabled a few times by each mpd command, which makes no sense
@@ -221,9 +240,9 @@ void MediaLibrary::Update()
Mpd.BlockIdle(1);
Albums->Reset();
MPD::TagList list;
locale_to_utf(Artists->Current().value());
locale_to_utf(Tags->Current().value());
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) {
list.push_back(album);
});
@@ -232,7 +251,7 @@ void MediaLibrary::Update()
if (Config.media_library_display_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);
utf_to_locale(*album);
Mpd.CommitSearchTags([this, &album](std::string &&date) {
@@ -246,9 +265,9 @@ void MediaLibrary::Update()
Albums->AddItem(SearchConstraints(*album, ""));
}
}
utf_to_locale(Artists->Current().value());
utf_to_locale(Tags->Current().value());
if (!Albums->Empty())
std::sort(Albums->BeginV(), Albums->EndV(), SearchConstraintsSorting());
std::sort(Albums->BeginV(), Albums->EndV(), SortSearchConstraints);
if (Albums->Size() > 1)
{
Albums->AddSeparator();
@@ -306,23 +325,23 @@ void MediaLibrary::Update()
}
Mpd.BlockIdle(0);
if (!Albums->Empty())
std::sort(Albums->BeginV(), Albums->EndV(), SearchConstraintsSorting());
std::sort(Albums->BeginV(), Albums->EndV(), SortSearchConstraints);
Albums->Refresh();
}
if (!hasTwoColumns && !Artists->Empty() && w == Albums && Albums->ReallyEmpty())
if (!hasTwoColumns && !Tags->Empty() && w == Albums && Albums->ReallyEmpty())
{
Albums->HighlightColor(Config.main_highlight_color);
Artists->HighlightColor(Config.active_column_color);
w = Artists;
Tags->HighlightColor(Config.active_column_color);
w = Tags;
}
if (!(hasTwoColumns ? Albums->Empty() : Artists->Empty()) && Songs->ReallyEmpty())
if (!(hasTwoColumns ? Albums->Empty() : Tags->Empty()) && Songs->ReallyEmpty())
{
Songs->Reset();
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)
{
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 (w == Artists)
if (w == Tags)
{
size_t i = Artists->Choice();
Artists->at(i).setSelected(!Artists->at(i).isSelected());
size_t i = Tags->Choice();
Tags->at(i).setSelected(!Tags->at(i).isSelected());
Albums->Clear();
Songs->Clear();
}
@@ -376,22 +395,22 @@ void MediaLibrary::SpacePressed()
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();
}
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)
{
size_t pos = Artists->Choice();
size_t pos = Tags->Choice();
SpacePressed();
if (pos < Artists->Size()-1)
Artists->Scroll(wUp);
if (pos < Tags->Size()-1)
Tags->Scroll(wUp);
}
}
else
@@ -402,7 +421,7 @@ void MediaLibrary::MouseButtonPressed(MEVENT me)
else if (!Albums->Empty() && Albums->hasCoords(me.x, me.y))
{
if (w != Albums)
w == Artists ? NextColumn() : PrevColumn();
w == Tags ? NextColumn() : PrevColumn();
if (size_t(me.y) < Albums->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
{
Albums->Goto(me.y);
@@ -450,8 +469,8 @@ MPD::Song *MediaLibrary::CurrentSong()
List *MediaLibrary::GetList()
{
if (w == Artists)
return Artists;
if (w == Tags)
return Tags;
else if (w == Albums)
return Albums;
else if (w == Songs)
@@ -462,8 +481,8 @@ List *MediaLibrary::GetList()
void MediaLibrary::ReverseSelection()
{
if (w == Artists)
Artists->ReverseSelection();
if (w == Tags)
Tags->ReverseSelection();
else if (w == Albums)
Albums->ReverseSelection();
else if (w == Songs)
@@ -473,16 +492,16 @@ void MediaLibrary::ReverseSelection()
void MediaLibrary::GetSelectedSongs(MPD::SongList &v)
{
std::vector<size_t> selected;
if (w == Artists && !Artists->Empty())
if (w == Tags && !Tags->Empty())
{
Artists->GetSelected(selected);
Tags->GetSelected(selected);
if (selected.empty())
selected.push_back(Artists->Choice());
selected.push_back(Tags->Choice());
for (auto it = selected.begin(); it != selected.end(); ++it)
{
MPD::SongList list;
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) {
list.push_back(s);
});
@@ -508,7 +527,7 @@ void MediaLibrary::GetSelectedSongs(MPD::SongList &v)
Mpd.StartSearch(1);
Mpd.AddSearch(Config.media_lib_primary_tag, hasTwoColumns
? 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_DATE, Albums->at(*it).value().Date);
Mpd.CommitSearchSongs([&v](MPD::Song &&s) {
@@ -527,13 +546,15 @@ void MediaLibrary::GetSelectedSongs(MPD::SongList &v)
}
}
/***********************************************************************/
std::string MediaLibrary::currentFilter()
{
std::string filter;
if (w == Artists)
filter = RegexFilter<std::string>::currentFilter(*Artists);
if (w == Tags)
filter = RegexFilter<std::string>::currentFilter(*Tags);
else if (w == Albums)
filter = RegexFilter<SearchConstraints>::currentFilter(*Albums);
filter = RegexItemFilter<SearchConstraints>::currentFilter(*Albums);
else if (w == Songs)
filter = RegexFilter<MPD::Song>::currentFilter(*Songs);
return filter;
@@ -541,35 +562,85 @@ std::string MediaLibrary::currentFilter()
void MediaLibrary::applyFilter(const std::string &filter)
{
if (w == Artists)
if (w == Tags)
{
Artists->ShowAll();
auto rx = RegexFilter<std::string>(filter, Config.regex_type);
Artists->Filter(Artists->Begin(), Artists->End(), rx);
Tags->ShowAll();
auto rx = RegexFilter<std::string>(filter, Config.regex_type, TagEntryMatcher);
Tags->filter(Tags->Begin(), Tags->End(), rx);
}
else if (w == Albums)
{
Albums->ShowAll();
auto fun = [](const Regex &rx, Menu<SearchConstraints> &menu, const Menu<SearchConstraints>::Item &item) {
if (item.isSeparator() || item.value().Date == AllTracksMarker)
return true;
return rx.match(menu.Stringify(item));
};
auto rx = RegexFilter<SearchConstraints>(filter, Config.regex_type, fun);
Albums->Filter(Albums->Begin(), Albums->End(), rx);
auto fun = std::bind(AlbumEntryMatcher, _1, _2, true);
auto rx = RegexItemFilter<SearchConstraints>(filter, Config.regex_type, fun);
Albums->filter(Albums->Begin(), Albums->End(), rx);
}
else if (w == Songs)
{
Songs->ShowAll();
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type);
Songs->Filter(Songs->Begin(), Songs->End(), rx);
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type, SongEntryMatcher);
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()
{
assert(!hasTwoColumns || w != Artists);
if (w == Artists)
assert(!hasTwoColumns || w != Tags);
if (w == Tags)
{
if (!Albums->ReallyEmpty() && !Songs->ReallyEmpty())
return true;
@@ -584,9 +655,9 @@ bool MediaLibrary::isNextColumnAvailable()
void MediaLibrary::NextColumn()
{
if (w == Artists)
if (w == Tags)
{
Artists->HighlightColor(Config.main_highlight_color);
Tags->HighlightColor(Config.main_highlight_color);
w->Refresh();
w = Albums;
Albums->HighlightColor(Config.active_column_color);
@@ -602,15 +673,15 @@ void MediaLibrary::NextColumn()
bool MediaLibrary::isPrevColumnAvailable()
{
assert(!hasTwoColumns || w != Artists);
assert(!hasTwoColumns || w != Tags);
if (w == Songs)
{
if (!Albums->ReallyEmpty() && (hasTwoColumns || !Artists->ReallyEmpty()))
if (!Albums->ReallyEmpty() && (hasTwoColumns || !Tags->ReallyEmpty()))
return true;
}
else if (w == Albums)
{
if (!hasTwoColumns && !Artists->ReallyEmpty())
if (!hasTwoColumns && !Tags->ReallyEmpty())
return true;
}
return false;
@@ -629,21 +700,13 @@ void MediaLibrary::PrevColumn()
{
Albums->HighlightColor(Config.main_highlight_color);
w->Refresh();
w = Artists;
Artists->HighlightColor(Config.active_column_color);
w = Tags;
Tags->HighlightColor(Config.active_column_color);
}
}
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;
switch (Config.media_lib_primary_tag)
{
@@ -681,16 +744,16 @@ void MediaLibrary::LocateSong(const MPD::Song &s)
if (!hasTwoColumns)
{
Artists->ShowAll();
if (Artists->Empty())
Tags->ShowAll();
if (Tags->Empty())
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();
Songs->Clear();
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);
Songs->HighlightColor(Config.active_column_color);
w = Songs;
@@ -759,12 +822,12 @@ void MediaLibrary::AddToPlaylist(bool 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))
{
std::string tag_type = tagTypeToString(Config.media_lib_primary_tag);
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)
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)
{
w->Scroll(wDown);
if (w == Artists)
if (w == Tags)
{
Albums->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);
}
std::string MediaLibrary::AlbumToString(const SearchConstraints &sc)
/***********************************************************************/
bool TagEntryMatcher(const Regex &rx, const std::string &tag)
{
if (sc.Date == AllTracksMarker)
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;
return rx.match(tag);
}
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());
}
void MediaLibrary::DisplayPrimaryTags(Menu<std::string> &menu)
void DisplayPrimaryTags(Menu<std::string> &menu)
{
const std::string &tag = menu.Drawn().value();
if (tag.empty())
@@ -816,18 +911,9 @@ void MediaLibrary::DisplayPrimaryTags(Menu<std::string> &menu)
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())
return stringToInt(a.getTrack()) < stringToInt(b.getTrack());
@@ -835,13 +921,27 @@ bool MediaLibrary::SortSongsByTrack(const MPD::Song &a, const MPD::Song &b)
return stringToInt(a.getDisc()) < stringToInt(b.getDisc());
}
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 };
CaseInsensitiveStringComparison cmp;
for (MPD::Song::GetFunction *get = gets; *get; ++get)
if (int ret = cmp(a.getTags(*get), b.getTags(*get)))
return ret < 0;
return a.getTrack() < b.getTrack();
return a.getTrack() < b.getTrack();
}
bool SortSearchConstraints(const SearchConstraints &a, const SearchConstraints &b)
{
int result;
CaseInsensitiveStringComparison cmp;
result = cmp(a.PrimaryTag, b.PrimaryTag);
if (result != 0)
return result < 0;
result = cmp(a.Date, b.Date);
if (result != 0)
return result < 0;
return cmp(a.Album, b.Album) < 0;
}
}

View File

@@ -23,27 +23,10 @@
#include "interfaces.h"
#include "ncmpcpp.h"
#include "regex_filter.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:
virtual void SwitchTo();
virtual void Resize();
@@ -65,14 +48,20 @@ class MediaLibrary : public Screen<Window>, public Filterable
virtual void ReverseSelection();
virtual void GetSelectedSongs(MPD::SongList &);
/// Filterable implementation
virtual std::string currentFilter();
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 bool isMergable() { return true; }
int Columns() { return hasTwoColumns ? 2 : 3; }
int Columns();
bool isNextColumnAvailable();
void NextColumn();
bool isPrevColumnAvailable();
@@ -80,7 +69,20 @@ class MediaLibrary : public Screen<Window>, public Filterable
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<MPD::Song> *Songs;
@@ -90,24 +92,6 @@ class MediaLibrary : public Screen<Window>, public Filterable
private:
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;

View File

@@ -17,32 +17,3 @@
* Free Software Foundation, Inc., *
* 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 {
/// List class is an interface for Menu class
///
struct List
{
/// @return currently highlighted position
///
virtual size_t Choice() const = 0;
/// @see Menu::Empty()
///
virtual bool Empty() const = 0;
/// @see Menu::Size()
///
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
/// holding any std::vector compatible values.
///
template <typename T> struct Menu : public Window, public List
{
struct Item
@@ -116,10 +95,12 @@ template <typename T> struct Menu : public Window, public List
BaseIterator m_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<
ValueT, typename std::remove_pointer<
typename BaseIterator::value_type
>::type
typename std::remove_const<ValueT>::type,
typename std::remove_pointer<typename BaseIterator::value_type>::type
>::value;
template <typename Result, bool referenceValue> struct getObject { };
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.
/// If not set by setItemDisplayer(), menu won't display anything.
/// @see setItemDisplayer()
///
typedef std::function<void(Menu<T> &)> ItemDisplayer;
/// Function helper prototype used for converting items to strings.
/// 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;
typedef std::function<bool(const Item &)> FilterFunction;
/// Constructs an empty menu with given parameters
/// @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 color base color of constructed menu
/// @param border border of constructed menu
///
Menu(size_t startx, size_t starty, size_t width, size_t height,
const std::string &title, Color color, Border border);
/// Destroys the object and frees memory
///
virtual ~Menu();
/// Sets helper function that is responsible for displaying items
/// @param ptr function pointer that matches the ItemDisplayer prototype
///
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())
/// @param size requested size
///
void Reserve(size_t size);
/// Resizes the list to given size (adequate to std::vector::resize())
/// @param size requested size
///
void ResizeList(size_t size);
/// Adds new option to list
/// @param item object that has to be added
/// @param is_bold defines the initial state of bold attribute
/// @param is_static defines the initial state of static attribute
///
void AddItem(const T &item, bool is_bold = 0, bool is_static = 0);
/// Adds separator to list
///
void AddSeparator();
/// 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 is_bold defines the initial state of bold 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);
/// Inserts separator to list at given position
/// @param pos initial position of inserted separator
///
void InsertSeparator(size_t pos);
/// Deletes item from given position
/// @param pos given position of item to be deleted
///
void DeleteItem(size_t pos);
/// Swaps the content of two items
/// @param one position of first item
/// @param two position of second item
///
void Swap(size_t one, size_t two);
/// Moves the highlighted position to the given line of window
/// @param y Y position of menu window to be highlighted
/// @return true if the position is reachable, false otherwise
///
bool Goto(size_t y);
/// Checks whether list contains selected positions
/// @return true if it contains them, false otherwise
///
bool hasSelected() const;
/// Gets positions of items that are selected
/// @param v vector to be filled with selected positions numbers
///
void GetSelected(std::vector<size_t> &v) const;
/// Reverses selection of all items in list
/// @param beginning beginning of range that has to be reversed
///
void ReverseSelection(size_t beginning = 0);
/// Highlights given position
/// @param pos position to be highlighted
///
void Highlight(size_t pos);
/// @return currently highlighted position
///
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
/// 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);
bool search(ConstIterator first, ConstIterator last, const FilterFunction &f);
/// @return const reference to currently used search constraint
///
virtual const std::string &GetSearchConstraint() { return m_search_constraint; }
/// Clears filter results
void clearFilterResults();
/// Clears search results
void clearSearchResults();
/// Moves current position in the list to the next found one
/// @param wrap if true, this function will go to the first
/// found pos after the last one, otherwise it'll do nothing.
///
virtual void NextFound(bool wrap);
/// Moves current position in the list to the previous found one
/// @param wrap if true, this function will go to the last
/// found pos after the first one, otherwise it'll do nothing.
///
virtual void PrevFound(bool wrap);
/// @return const reference to currently used filter function
///
const FilterFunction &getFilter() { return m_filter; }
/// @return true if list is currently filtered, false otherwise
///
bool isFiltered() { return m_options_ptr == &m_filtered_options; }
/// Turns off filtering
///
void ShowAll() { m_options_ptr = &m_options; }
/// Turns on filtering
///
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
/// @see Window::Refresh()
///
virtual void Refresh();
/// Scrolls by given amount of lines
/// @param where indicated where exactly one wants to go
/// @see Window::Scroll()
///
virtual void Scroll(Where where);
/// Cleares all options, used filters etc. It doesn't reset highlighted position though.
/// @see Reset()
///
virtual void Clear();
/// Sets highlighted position to 0
///
void Reset();
/// 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.
/// @param b pointer to buffer that contains the prefix
///
void SetSelectPrefix(const Buffer &b) { m_selected_prefix = b; }
/// 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.
/// @param b pointer to buffer that contains the suffix
///
void SetSelectSuffix(const Buffer &b) { m_selected_suffix = b; }
/// Sets custom color of highlighted position
/// @param col custom color
///
void HighlightColor(Color color) { m_highlight_color = color; }
/// @return state of highlighting
///
bool isHighlighted() { return m_highlight_enabled; }
/// Turns on/off highlighting
/// @param state state of hihglighting
///
void Highlighting(bool state) { m_highlight_enabled = state; }
/// Turns on/off cyclic scrolling
/// @param state state of cyclic scrolling
///
void CyclicScrolling(bool state) { m_cyclic_scroll_enabled = state; }
/// Turns on/off centered cursor
/// @param state state of centered cursor
///
void CenteredCursor(bool state) { m_autocenter_cursor = state; }
/// Checks if list is empty
/// @return true if list is empty, false otherwise
/// @see ReallyEmpty()
///
virtual bool Empty() const { return m_options_ptr->empty(); }
/// Checks if list is really empty since Empty() may not
/// be accurate if filter is set)
/// @return true if list is empty, false otherwise
/// @see Empty()
///
virtual bool ReallyEmpty() const { return m_options.empty(); }
/// @return size of the list
///
virtual size_t Size() const;
/// @return currently drawn item. The result is defined only within
/// drawing function that is called by Refresh()
/// @see Refresh()
///
const Item &Drawn() const { return *(*m_options_ptr)[m_drawn_position]; }
/// @return position of currently drawn item. The result is defined
/// only within drawing function that is called by Refresh()
/// @see Refresh()
///
size_t DrawnPosition() const { return m_drawn_position; }
/// @return reference to last item on the list
/// @throw List::InvalidItem if requested item is separator
///
Menu<T>::Item &Back();
/// @return const reference to last item on the list
/// @throw List::InvalidItem if requested item is separator
///
const Menu<T>::Item &Back() const;
/// @return reference to curently highlighted object
/// @throw List::InvalidItem if requested item is separator
///
Menu<T>::Item &Current();
/// @return const reference to curently highlighted object
/// @throw List::InvalidItem if requested item is separator
///
const Menu<T>::Item &Current() const;
/// @param pos requested position
/// @return reference to item at given position
/// @throw std::out_of_range if given position is out of range
/// @throw List::InvalidItem if requested item is separator
///
Menu<T>::Item &at(size_t pos);
/// @param pos requested position
/// @return const reference to item at given position
/// @throw std::out_of_range if given position is out of range
/// @throw List::InvalidItem if requested item is separator
///
const Menu<T>::Item &at(size_t pos) const;
/// @param pos requested position
/// @return const reference to item at given position
/// @throw List::InvalidItem if requested item is separator
///
const Menu<T>::Item &operator[](size_t pos) const;
/// @param pos requested position
/// @return const reference to item at given position
/// @throw List::InvalidItem if requested item is separator
///
Menu<T>::Item &operator[](size_t pos);
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()); }
private:
/// Clears filter, filtered data etc.
///
void clearFiltered();
bool isHighlightable(size_t pos)
{
return !(*m_options_ptr)[pos]->isSeparator() && !(*m_options_ptr)[pos]->isInactive();
}
ItemDisplayer m_item_displayer;
ItemStringifier m_item_stringifier;
FilterFunction m_filter;
std::string m_search_constraint;
FilterFunction m_searcher;
std::vector<Item *> *m_options_ptr;
std::vector<Item *> m_options;
@@ -551,11 +452,6 @@ private:
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,
size_t starty,
size_t width,
@@ -565,7 +461,6 @@ template <typename T> Menu<T>::Menu(size_t startx,
Border border)
: Window(startx, starty, width, height, title, color, border),
m_item_displayer(0),
m_item_stringifier(0),
m_options_ptr(&m_options),
m_beginning(0),
m_highlight(0),
@@ -806,18 +701,11 @@ template <typename T> void Menu<T>::Reset()
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()
{
for (auto it = m_options.begin(); it != m_options.end(); ++it)
delete *it;
clearFiltered();
clearFilterResults();
m_options.clear();
m_found_positions.clear();
m_options_ptr = &m_options;
@@ -858,15 +746,15 @@ template <typename T> size_t Menu<T>::Choice() const
return m_highlight;
}
template <typename T> template <typename Iterator_>
void Menu<T>::Filter(Iterator_ first, Iterator_ last, const FilterFunction &f)
template <typename T>
void Menu<T>::filter(ConstIterator first, ConstIterator last, const FilterFunction &f)
{
assert(m_options_ptr != &m_filtered_options);
clearFiltered();
clearFilterResults();
m_filter = f;
for (auto it = first; it != last; ++it)
{
if (m_filter(*this, *it))
if (m_filter(*it))
{
size_t pos = it-Begin();
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;
}
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)
{
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());
}
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)
{
if (m_found_positions.empty())
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())
Highlight(*next);
else if (wrap)
@@ -917,29 +814,13 @@ template <typename T> void Menu<T>::PrevFound(bool wrap)
{
if (m_found_positions.empty())
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())
Highlight(*--prev);
else if (wrap)
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()
{
return *m_options_ptr->back();

View File

@@ -26,6 +26,7 @@
#include "helpers.h"
#include "menu.h"
#include "playlist.h"
#include "regex_filter.h"
#include "song.h"
#include "status.h"
#include "utility/comparators.h"
@@ -38,11 +39,18 @@ Playlist *myPlaylist = new Playlist;
bool Playlist::ReloadTotalLength = 0;
bool Playlist::ReloadRemaining = false;
const size_t Playlist::SortOptions = 10;
const size_t Playlist::SortDialogWidth = 30;
size_t Playlist::SortDialogHeight;
namespace {//
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()
{
@@ -53,15 +61,9 @@ void Playlist::Init()
Items->SetSelectPrefix(Config.selected_item_prefix);
Items->SetSelectSuffix(Config.selected_item_suffix);
if (Config.columns_in_playlist)
{
Items->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *this));
Items->SetItemStringifier(SongInColumnsToString);
}
else
{
Items->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_list_format));
Items->SetItemStringifier(SongToString);
}
if (!SortDialog)
{
@@ -284,6 +286,8 @@ void Playlist::GetSelectedSongs(MPD::SongList &v)
v.push_back(Items->at(*it).value());
}
/***********************************************************************/
std::string Playlist::currentFilter()
{
std::string filter;
@@ -297,11 +301,38 @@ void Playlist::applyFilter(const std::string &filter)
if (w == Items)
{
Items->ShowAll();
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type);
Items->Filter(Items->Begin(), Items->End(), rx);
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type, playlistEntryMatcher);
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()
{
if (Items->isFiltered())
@@ -316,10 +347,6 @@ void Playlist::MoveSelectedItems(Movement where)
{
if (Items->Empty() || isFiltered())
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)
{
@@ -462,6 +489,11 @@ void Playlist::EnableHighlighting()
UpdateTimer();
}
bool Playlist::SortingInProgress()
{
return w == SortDialog;
}
std::string Playlist::TotalLength()
{
std::ostringstream result;
@@ -516,16 +548,6 @@ const MPD::Song *Playlist::NowPlayingSong()
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)
{
if (Config.ncmpc_like_songs_adding && in_playlist)
@@ -631,3 +653,22 @@ bool Playlist::checkForSong (const MPD::Song &s)
return true;
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 "song.h"
class Playlist : public Screen<Window>, public Filterable
class Playlist : public Screen<Window>, public Filterable, public Searchable
{
public:
enum Movement { mUp, mDown };
@@ -51,9 +51,15 @@ class Playlist : public Screen<Window>, public Filterable
virtual void ReverseSelection() { Items->ReverseSelection(); }
virtual void GetSelectedSongs(MPD::SongList &);
/// Filterable implementation
virtual std::string currentFilter();
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 bool isMergable() { return true; }
@@ -67,7 +73,7 @@ class Playlist : public Screen<Window>, public Filterable
void Sort();
void Reverse();
void AdjustSortOrder(Movement where);
bool SortingInProgress() { return w == SortDialog; }
bool SortingInProgress();
void EnableHighlighting();
void UpdateTimer() { time(&itsTimer); }
@@ -81,8 +87,8 @@ class Playlist : public Screen<Window>, public Filterable
bool checkForSong(const MPD::Song &s);
static std::string SongToString(const MPD::Song &s);
static std::string SongInColumnsToString(const MPD::Song &s);
//static std::string SongToString(const MPD::Song &s);
//static std::string SongInColumnsToString(const MPD::Song &s);
Menu< MPD::Song > *Items;
@@ -97,7 +103,6 @@ class Playlist : public Screen<Window>, public Filterable
private:
std::string TotalLength();
std::string itsBufferedStats;
size_t itsTotalLength;
@@ -105,11 +110,6 @@ class Playlist : public Screen<Window>, public Filterable
size_t itsScrollBegin;
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;

View File

@@ -37,10 +37,18 @@ using Global::MainStartY;
PlaylistEditor *myPlaylistEditor = new PlaylistEditor;
size_t PlaylistEditor::LeftColumnStartX;
size_t PlaylistEditor::LeftColumnWidth;
size_t PlaylistEditor::RightColumnStartX;
size_t PlaylistEditor::RightColumnWidth;
namespace {//
size_t LeftColumnStartX;
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()
{
@@ -61,15 +69,9 @@ void PlaylistEditor::Init()
Content->SetSelectPrefix(Config.selected_item_prefix);
Content->SetSelectSuffix(Config.selected_item_suffix);
if (Config.columns_in_playlist_editor)
{
Content->setItemDisplayer(std::bind(Display::SongsInColumns, _1, *this));
Content->SetItemStringifier(Playlist::SongInColumnsToString);
}
else
{
Content->setItemDisplayer(std::bind(Display::Songs, _1, *this, Config.song_list_format));
Content->SetItemStringifier(Playlist::SongToString);
}
w = Playlists;
isInitialized = 1;
@@ -190,7 +192,7 @@ void PlaylistEditor::MoveSelectedItems(Playlist::Movement where)
// remove search results as we may move them to different positions, but
// search rememebers positions and may point to wrong ones after that.
Content->Search("");
Content->clearSearchResults();
switch (where)
{
@@ -416,6 +418,8 @@ void PlaylistEditor::GetSelectedSongs(MPD::SongList &v)
v.push_back(Content->at(*it).value());
}
/***********************************************************************/
std::string PlaylistEditor::currentFilter()
{
std::string filter;
@@ -431,17 +435,53 @@ void PlaylistEditor::applyFilter(const std::string &filter)
if (w == Playlists)
{
Playlists->ShowAll();
auto rx = RegexFilter<std::string>(filter, Config.regex_type);
Playlists->Filter(Playlists->Begin(), Playlists->End(), rx);
auto rx = RegexFilter<std::string>(filter, Config.regex_type, PlaylistEntryMatcher);
Playlists->filter(Playlists->Begin(), Playlists->End(), rx);
}
else if (w == Content)
{
Content->ShowAll();
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type);
Content->Filter(Content->Begin(), Content->End(), rx);
auto rx = RegexFilter<MPD::Song>(filter, Config.regex_type, SongEntryMatcher);
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)
{
if (!isInitialized)
@@ -466,5 +506,32 @@ List *PlaylistEditor::GetList()
else if (w == Content)
return Content;
else // silence compiler
{
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 "ncmpcpp.h"
class PlaylistEditor : public Screen<Window>, public Filterable
class PlaylistEditor : public Screen<Window>, public Filterable, public Searchable
{
public:
virtual void SwitchTo();
@@ -47,9 +47,15 @@ class PlaylistEditor : public Screen<Window>, public Filterable
virtual void ReverseSelection() { Content->ReverseSelection(); }
virtual void GetSelectedSongs(MPD::SongList &);
/// Filterable implementation
virtual std::string currentFilter();
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 List *GetList();
@@ -73,11 +79,6 @@ class PlaylistEditor : public Screen<Window>, public Filterable
private:
void AddToPlaylist(bool);
static size_t LeftColumnStartX;
static size_t LeftColumnWidth;
static size_t RightColumnStartX;
static size_t RightColumnWidth;
};
extern PlaylistEditor *myPlaylistEditor;

View File

@@ -26,20 +26,18 @@
template <typename T> struct RegexFilter
{
typedef NCurses::Menu<T> MenuT;
typedef typename NCurses::Menu<T>::Item MenuItem;
typedef std::function<bool(const Regex &, MenuT &menu, const MenuItem &)> FilterFunction;
typedef typename NCurses::Menu<T>::Item Item;
typedef std::function<bool(const Regex &, const T &)> FilterFunction;
RegexFilter(const std::string &regex_, int cflags, FilterFunction custom_filter = 0)
: m_rx(regex_, cflags), m_custom_filter(custom_filter) { }
RegexFilter(const std::string &regex_, int cflags, FilterFunction 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())
return true;
if (!m_rx.error().empty())
return false;
if (m_custom_filter)
return m_custom_filter(m_rx, menu, item);
return m_rx.match(menu.Stringify(item));
return m_filter(m_rx, item.value());
}
static std::string currentFilter(MenuT &menu)
@@ -53,7 +51,38 @@ template <typename T> struct RegexFilter
private:
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

View File

@@ -18,12 +18,14 @@
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include <array>
#include <iomanip>
#include "display.h"
#include "global.h"
#include "helpers.h"
#include "playlist.h"
#include "regex_filter.h"
#include "search_engine.h"
#include "settings.h"
#include "status.h"
@@ -34,6 +36,40 @@ using Global::MainStartY;
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[] =
{
"Any",
@@ -70,7 +106,6 @@ void SearchEngine::Init()
w->setItemDisplayer(Display::SearchEngine);
w->SetSelectPrefix(Config.selected_item_prefix);
w->SetSelectSuffix(Config.selected_item_suffix);
w->SetItemStringifier(SearchEngineOptionToString);
SearchMode = &SearchModes[Config.search_engine_default_search_mode];
isInitialized = 1;
}
@@ -261,23 +296,42 @@ void SearchEngine::GetSelectedSongs(MPD::SongList &v)
}
}
/***********************************************************************/
std::string SearchEngine::currentFilter()
{
return RegexFilter<SEItem>::currentFilter(*w);
return RegexItemFilter<SEItem>::currentFilter(*w);
}
void SearchEngine::applyFilter(const std::string &filter)
{
w->ShowAll();
auto fun = [](const Regex &rx, Menu<SEItem> &menu, const Menu<SEItem>::Item &item) {
if (item.isSeparator() || !item.value().isSong())
return true;
return rx.match(menu.Stringify(item));
};
auto rx = RegexFilter<SEItem>(filter, Config.regex_type, fun);
w->Filter(w->Begin(), w->End(), rx);
auto fun = std::bind(SEItemEntryMatcher, _1, _2, true);
auto rx = RegexItemFilter<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()
{
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;
if (ei.isSong())
{
if (Config.columns_in_search_engine)
result = Playlist::SongInColumnsToString(ei.song());
result = ei.song().toString(Config.song_in_columns_to_string_format);
else
result = ei.song().toString(Config.song_list_format_dollar_free);
}
else
result = ei.buffer().Str();
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 "regex_filter.h"
#include "interfaces.h"
#include "mpdpp.h"
#include "ncmpcpp.h"
@@ -74,7 +73,7 @@ struct SEItem
MPD::Song itsSong;
};
class SearchEngine : public Screen< Menu<SEItem> >, public Filterable
class SearchEngine : public Screen< Menu<SEItem> >, public Filterable, public Searchable
{
public:
virtual void Resize();
@@ -94,9 +93,15 @@ class SearchEngine : public Screen< Menu<SEItem> >, public Filterable
virtual void ReverseSelection() { w->ReverseSelection(StaticOptions); }
virtual void GetSelectedSongs(MPD::SongList &);
/// Filterable implementation
virtual std::string currentFilter();
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 bool isMergable() { return true; }
@@ -118,8 +123,6 @@ class SearchEngine : public Screen< Menu<SEItem> >, public Filterable
const char **SearchMode;
static std::string SearchEngineOptionToString(const SEItem &);
static const char *SearchModes[];
static const size_t ConstraintsNumber = 11;

View File

@@ -227,6 +227,8 @@ void NcmpcppStatusChanged(MPD::Connection *, MPD::StatusChanges changed, void *)
if (!np.empty())
WindowTitle(np.toString(Config.song_window_title_format));
myPlaylist->Items->clearSearchResults();
bool is_filtered = myPlaylist->Items->isFiltered();
myPlaylist->Items->ShowAll();
@@ -302,7 +304,7 @@ void NcmpcppStatusChanged(MPD::Connection *, MPD::StatusChanges changed, void *)
if (myLibrary->Columns() == 2)
myLibrary->Albums->Clear();
else
myLibrary->Artists->Clear();
myLibrary->Tags->Clear();
}
if (myPlaylistEditor->Main())
myPlaylistEditor->Content->Clear();

View File

@@ -47,22 +47,47 @@ using Global::MainStartY;
TagEditor *myTagEditor = new TagEditor;
std::string TagEditor::PatternsFile = "patterns.list";
std::list<std::string> TagEditor::Patterns;
namespace {//
size_t TagEditor::LeftColumnWidth;
size_t TagEditor::LeftColumnStartX;
size_t TagEditor::MiddleColumnWidth;
size_t TagEditor::MiddleColumnStartX;
size_t TagEditor::RightColumnWidth;
size_t TagEditor::RightColumnStartX;
size_t LeftColumnWidth;
size_t LeftColumnStartX;
size_t MiddleColumnWidth;
size_t MiddleColumnStartX;
size_t RightColumnWidth;
size_t RightColumnStartX;
size_t TagEditor::FParserDialogWidth;
size_t TagEditor::FParserDialogHeight;
size_t TagEditor::FParserWidth;
size_t TagEditor::FParserWidthOne;
size_t TagEditor::FParserWidthTwo;
size_t TagEditor::FParserHeight;
size_t FParserDialogWidth;
size_t FParserDialogHeight;
size_t FParserWidth;
size_t FParserWidthOne;
size_t FParserWidthTwo;
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()
{
@@ -74,14 +99,12 @@ void TagEditor::Init()
Albums->CyclicScrolling(Config.use_cyclic_scrolling);
Albums->CenteredCursor(Config.centered_cursor);
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->HighlightColor(Config.active_column_color);
Dirs->CyclicScrolling(Config.use_cyclic_scrolling);
Dirs->CenteredCursor(Config.centered_cursor);
Dirs->setItemDisplayer(Display::Pair<std::string, std::string>);
Dirs->SetItemStringifier(StringPairToString);
LeftColumn = Config.albums_in_tag_editor ? Albums : Dirs;
@@ -112,7 +135,6 @@ void TagEditor::Init()
Tags->SetSelectPrefix(Config.selected_item_prefix);
Tags->SetSelectSuffix(Config.selected_item_suffix);
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->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()));
}
/***********************************************************************/
std::string TagEditor::currentFilter()
{
std::string filter;
@@ -789,28 +813,70 @@ void TagEditor::applyFilter(const std::string &filter)
if (w == Dirs)
{
Dirs->ShowAll();
auto fun = [](const Regex &rx, Menu<string_pair> &menu, const Menu<string_pair>::Item &item) {
if (item.value().first == "." || item.value().first == "..")
return true;
return rx.match(menu.Stringify(item));
};
auto fun = std::bind(DirEntryMatcher, _1, _2, true);
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)
{
Albums->ShowAll();
auto rx = RegexFilter<string_pair>(filter, Config.regex_type);
Albums->Filter(Albums->Begin(), Albums->End(), rx);
auto rx = RegexFilter<string_pair>(filter, Config.regex_type, AlbumEntryMatcher);
Albums->filter(Albums->Begin(), Albums->End(), rx);
}
else if (w == Tags)
{
Tags->ShowAll();
auto rx = RegexFilter<MPD::MutableSong>(filter, Config.regex_type);
Tags->Filter(Tags->Begin(), Tags->End(), rx);
auto rx = RegexFilter<MPD::MutableSong>(filter, Config.regex_type, SongEntryMatcher);
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()
{
if (w == LeftColumn)
@@ -1008,40 +1074,6 @@ void TagEditor::ReadTags(MPD::MutableSong &s)
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)
{
std::string path_to_file;
@@ -1129,7 +1161,9 @@ bool TagEditor::WriteTags(MPD::MutableSong &s)
return false;
}
std::string TagEditor::CapitalizeFirstLetters(const std::string &s)
namespace {//
std::string CapitalizeFirstLetters(const std::string &s)
{
if (s.empty())
return "";
@@ -1144,7 +1178,7 @@ std::string TagEditor::CapitalizeFirstLetters(const std::string &s)
return result;
}
void TagEditor::CapitalizeFirstLetters(MPD::MutableSong &s)
void CapitalizeFirstLetters(MPD::MutableSong &s)
{
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)
{
@@ -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();
unsigned pos = 0;
@@ -1175,18 +1209,38 @@ void TagEditor::GetTagList(TagLib::StringList &list, const MPD::MutableSong &s,
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;
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;
using TagLib::ID3v2::TextIdentificationFrame;
tag->removeFrames(type);
TextIdentificationFrame *frame = new TextIdentificationFrame(type, TagLib::String::UTF8);
frame->setText(list);
tag->addFrame(frame);
}
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())
{
@@ -1197,12 +1251,12 @@ void TagEditor::GetPatternList()
while (getline(input, line))
if (!line.empty())
Patterns.push_back(line);
input.close();
input.close();
}
}
}
void TagEditor::SavePatternList()
void SavePatternList()
{
std::ofstream output(PatternsFile.c_str());
if (output.is_open())
@@ -1213,8 +1267,7 @@ void TagEditor::SavePatternList()
output.close();
}
}
MPD::MutableSong::SetFunction TagEditor::IntoSetFunction(char c)
MPD::MutableSong::SetFunction IntoSetFunction(char 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);
removeInvalidCharsFromFilename(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::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));
}
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);
tags.at(i).second = file.substr(0, j);
@@ -1289,23 +1342,53 @@ std::string TagEditor::ParseFilename(MPD::MutableSong &s, std::string mask, bool
return "Error while parsing filename!\n";
}
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)
if (*j == '_')
*j = ' ';
if (!preview)
{
MPD::MutableSong::SetFunction set = IntoSetFunction(it->first);
if (set)
s.setTag(set, it->second);
}
else
result << "%" << it->first << ": " << it->second << "\n";
if (!preview)
{
MPD::MutableSong::SetFunction set = IntoSetFunction(it->first);
if (set)
s.setTag(set, it->second);
}
else
result << "%" << it->first << ": " << it->second << "\n";
}
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

View File

@@ -37,7 +37,7 @@
#include "regex_filter.h"
#include "screen.h"
class TagEditor : public Screen<Window>, public Filterable
class TagEditor : public Screen<Window>, public Filterable, public Searchable
{
public:
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 GetSelectedSongs(MPD::SongList &);
/// Filterable implementation
virtual std::string currentFilter();
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 bool isMergable() { return true; }
@@ -103,39 +109,8 @@ class TagEditor : public Screen<Window>, public Filterable
Scrollpad *FParserPreview;
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 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;