new feature: playlist sorting (Ctrl-V by default)
This commit is contained in:
2
doc/keys
2
doc/keys
@@ -82,6 +82,8 @@
|
||||
#
|
||||
#key_update_db = 'u'
|
||||
#
|
||||
#key_sort_playlist = 22
|
||||
#
|
||||
#key_apply_filter = 6
|
||||
#
|
||||
#key_find_forward = '/'
|
||||
|
||||
@@ -90,11 +90,6 @@ string Display::Columns(string st)
|
||||
return result;
|
||||
}
|
||||
|
||||
void Display::StringPairs(const string_pair &pair, void *, Menu<string_pair> *menu)
|
||||
{
|
||||
*menu << pair.first;
|
||||
}
|
||||
|
||||
void Display::SongsInColumns(const MPD::Song &s, void *s_template, Menu<MPD::Song> *menu)
|
||||
{
|
||||
string st = s_template ? *static_cast<string *>(s_template) : "";
|
||||
@@ -115,7 +110,7 @@ void Display::SongsInColumns(const MPD::Song &s, void *s_template, Menu<MPD::Son
|
||||
color = IntoColor(GetLineValue(st, '[', ']', 1));
|
||||
char type = GetLineValue(st, '{', '}', 1)[0];
|
||||
|
||||
string (Song::*get)() const = 0;
|
||||
Song::GetFunction get = 0;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
@@ -195,7 +190,7 @@ void Display::Songs(const MPD::Song &s, void *data, Menu<MPD::Song> *menu)
|
||||
if (*it == '{')
|
||||
{
|
||||
prev_pos = it;
|
||||
string (Song::*get)() const = 0;
|
||||
Song::GetFunction get = 0;
|
||||
for (; *it != '}'; it++)
|
||||
{
|
||||
if (*it == '%')
|
||||
|
||||
@@ -34,7 +34,10 @@ namespace Display
|
||||
*menu << t;
|
||||
}
|
||||
|
||||
void StringPairs(const string_pair &, void *, Menu<string_pair> *);
|
||||
template <class A, class B> void Pairs(const std::pair<A, B> &pair, void *, Menu< std::pair<A, B> > *menu)
|
||||
{
|
||||
*menu << pair.first;
|
||||
}
|
||||
|
||||
void SongsInColumns(const MPD::Song &, void *, Menu<MPD::Song> *);
|
||||
|
||||
@@ -45,8 +48,6 @@ namespace Display
|
||||
void SearchEngine(const std::pair<Buffer *, MPD::Song *> &, void *, Menu< std::pair<Buffer *, MPD::Song *> > *);
|
||||
|
||||
void Items(const MPD::Item &, void *, Menu<MPD::Item> *);
|
||||
|
||||
void Clock(Window &, const tm *);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -199,6 +199,7 @@ void Help::GetKeybindings()
|
||||
*w << DisplayKeys(Key.MvSongDown) << "Move item/group of items down\n";
|
||||
*w << DisplayKeys(Key.Add) << "Add url/file/directory to playlist\n";
|
||||
*w << DisplayKeys(Key.SavePlaylist) << "Save playlist\n";
|
||||
*w << DisplayKeys(Key.SortPlaylist) << "Sort playlist\n";
|
||||
*w << DisplayKeys(Key.GoToNowPlaying) << "Go to currently playing position\n";
|
||||
*w << DisplayKeys(Key.ToggleAutoCenter) << "Toggle auto center mode\n\n\n";
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ void MediaLibrary::Init()
|
||||
Albums = new Menu<string_pair>(itsMiddleColStartX, main_start_y, itsMiddleColWidth, main_height, "Albums", Config.main_color, brNone);
|
||||
Albums->HighlightColor(Config.main_highlight_color);
|
||||
Albums->SetTimeout(ncmpcpp_window_timeout);
|
||||
Albums->SetItemDisplayer(Display::StringPairs);
|
||||
Albums->SetItemDisplayer(Display::Pairs);
|
||||
Albums->SetGetStringFunction(StringPairToString);
|
||||
|
||||
Songs = new Menu<Song>(itsRightColStartX, main_start_y, itsRightColWidth, main_height, "Songs", Config.main_color, brNone);
|
||||
|
||||
@@ -290,6 +290,15 @@ void Connection::Move(int from, int to) const
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::Swap(int from, int to) const
|
||||
{
|
||||
if (isConnected)
|
||||
{
|
||||
mpd_sendSwapCommand(itsConnection, from, to);
|
||||
mpd_finishCommand(itsConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::Seek(int where) const
|
||||
{
|
||||
if (isConnected)
|
||||
|
||||
@@ -119,6 +119,7 @@ namespace MPD
|
||||
void Next() const;
|
||||
void Prev() const;
|
||||
void Move(int, int) const;
|
||||
void Swap(int, int) const;
|
||||
void Seek(int) const;
|
||||
void Shuffle() const;
|
||||
void ClearPlaylist() const;
|
||||
|
||||
@@ -1042,7 +1042,7 @@ int main(int argc, char *argv[])
|
||||
Mpd->StartSearch(1);
|
||||
Mpd->AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(myLibrary->Artists->Current()));
|
||||
Mpd->CommitSearch(list);
|
||||
SongSetFunction set = IntoSetFunction(Config.media_lib_primary_tag);
|
||||
Song::SetFunction set = IntoSetFunction(Config.media_lib_primary_tag);
|
||||
if (!set)
|
||||
continue;
|
||||
for (SongList::iterator it = list.begin(); it != list.end(); it++)
|
||||
@@ -1290,9 +1290,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
mDialog->Display();
|
||||
|
||||
myOldScreen = myScreen;
|
||||
myScreen = myHelp; // temp hack, prevent playlist from updating
|
||||
|
||||
Playlist::BlockRefreshing = 1;
|
||||
while (!Keypressed(input, Key.Enter))
|
||||
{
|
||||
TraceMpdStatus();
|
||||
@@ -1312,8 +1310,7 @@ int main(int argc, char *argv[])
|
||||
else if (Keypressed(input, Key.End))
|
||||
mDialog->Scroll(wEnd);
|
||||
}
|
||||
|
||||
myScreen = myOldScreen;
|
||||
Playlist::BlockRefreshing = 0;
|
||||
|
||||
size_t id = mDialog->Choice();
|
||||
|
||||
@@ -1412,6 +1409,12 @@ int main(int argc, char *argv[])
|
||||
Mpd->ClearPlaylist();
|
||||
ShowMessage("Cleared playlist!");
|
||||
}
|
||||
else if (Keypressed(input, Key.SortPlaylist) && myScreen == myPlaylist)
|
||||
{
|
||||
myPlaylist->Sort();
|
||||
myPlaylist->Main()->Highlighting(1);
|
||||
time(&timer);
|
||||
}
|
||||
else if (Keypressed(input, Key.ApplyFilter))
|
||||
{
|
||||
List *mList = myScreen->GetList();
|
||||
|
||||
156
src/playlist.cpp
156
src/playlist.cpp
@@ -34,6 +34,14 @@ Playlist *myPlaylist = new Playlist;
|
||||
|
||||
bool Playlist::BlockNowPlayingUpdate = 0;
|
||||
bool Playlist::BlockUpdate = 0;
|
||||
bool Playlist::BlockRefreshing = 0;
|
||||
|
||||
Menu< std::pair<std::string, MPD::Song::GetFunction> > *Playlist::SortDialog;
|
||||
|
||||
const size_t Playlist::SortOptions = 10;
|
||||
|
||||
const size_t Playlist::SortDialogWidth = 30;
|
||||
const size_t Playlist::SortDialogHeight = 17;
|
||||
|
||||
void Playlist::Init()
|
||||
{
|
||||
@@ -46,6 +54,24 @@ void Playlist::Init()
|
||||
w->SetItemDisplayerUserData(Config.columns_in_playlist ? &Config.song_columns_list_format : &Config.song_list_format);
|
||||
w->SetGetStringFunction(Config.columns_in_playlist ? SongInColumnsToString : SongToString);
|
||||
w->SetGetStringFunctionUserData(Config.columns_in_playlist ? &Config.song_columns_list_format : &Config.song_list_format);
|
||||
|
||||
SortDialog = new Menu< std::pair<std::string, MPD::Song::GetFunction> >((COLS-SortDialogWidth)/2, (LINES-SortDialogHeight)/2, SortDialogWidth, SortDialogHeight, "Sort songs by...", Config.main_color, Config.window_border);
|
||||
SortDialog->SetTimeout(ncmpcpp_window_timeout);
|
||||
SortDialog->SetItemDisplayer(Display::Pairs);
|
||||
|
||||
SortDialog->AddOption(std::make_pair("Artist", &MPD::Song::GetArtist));
|
||||
SortDialog->AddOption(std::make_pair("Album", &MPD::Song::GetAlbum));
|
||||
SortDialog->AddOption(std::make_pair("Disc", &MPD::Song::GetDisc));
|
||||
SortDialog->AddOption(std::make_pair("Track", &MPD::Song::GetTrack));
|
||||
SortDialog->AddOption(std::make_pair("Genre", &MPD::Song::GetGenre));
|
||||
SortDialog->AddOption(std::make_pair("Year", &MPD::Song::GetYear));
|
||||
SortDialog->AddOption(std::make_pair("Composer", &MPD::Song::GetComposer));
|
||||
SortDialog->AddOption(std::make_pair("Performer", &MPD::Song::GetPerformer));
|
||||
SortDialog->AddOption(std::make_pair("Title", &MPD::Song::GetTitle));
|
||||
SortDialog->AddOption(std::make_pair("Filename", &MPD::Song::GetFile));
|
||||
SortDialog->AddSeparator();
|
||||
SortDialog->AddOption(std::make_pair("Sort", (MPD::Song::GetFunction)0));
|
||||
SortDialog->AddOption(std::make_pair("Cancel", (MPD::Song::GetFunction)0));
|
||||
}
|
||||
|
||||
void Playlist::SwitchTo()
|
||||
@@ -66,6 +92,7 @@ void Playlist::Resize()
|
||||
{
|
||||
w->Resize(COLS, main_height);
|
||||
w->SetTitle(Config.columns_in_playlist ? Display::Columns(Config.song_columns_list_format) : "");
|
||||
SortDialog->MoveTo((COLS-SortDialogWidth)/2, (LINES-SortDialogHeight)/2);
|
||||
hasToBeResized = 0;
|
||||
}
|
||||
|
||||
@@ -103,6 +130,135 @@ void Playlist::GetSelectedSongs(MPD::SongList &v)
|
||||
}
|
||||
}
|
||||
|
||||
void Playlist::Sort()
|
||||
{
|
||||
if (w->GetWidth() < SortDialogWidth || w->GetHeight() < SortDialogHeight)
|
||||
{
|
||||
ShowMessage("Screen is too small to display dialog window!");
|
||||
return;
|
||||
}
|
||||
|
||||
int input;
|
||||
|
||||
SortDialog->Reset();
|
||||
SortDialog->Display();
|
||||
|
||||
BlockRefreshing = 1;
|
||||
while (1)
|
||||
{
|
||||
TraceMpdStatus();
|
||||
SortDialog->Refresh();
|
||||
SortDialog->ReadKey(input);
|
||||
|
||||
if (Keypressed(input, Key.Up))
|
||||
{
|
||||
SortDialog->Scroll(wUp);
|
||||
}
|
||||
else if (Keypressed(input, Key.Down))
|
||||
{
|
||||
SortDialog->Scroll(wDown);
|
||||
}
|
||||
else if (Keypressed(input, Key.PageUp))
|
||||
{
|
||||
SortDialog->Scroll(wPageUp);
|
||||
}
|
||||
else if (Keypressed(input, Key.PageDown))
|
||||
{
|
||||
SortDialog->Scroll(wPageDown);
|
||||
}
|
||||
else if (Keypressed(input, Key.Home))
|
||||
{
|
||||
SortDialog->Scroll(wHome);
|
||||
}
|
||||
else if (Keypressed(input, Key.End))
|
||||
{
|
||||
SortDialog->Scroll(wEnd);
|
||||
}
|
||||
else if (Keypressed(input, Key.MvSongUp))
|
||||
{
|
||||
size_t pos = SortDialog->Choice();
|
||||
if (pos > 0 && pos < SortOptions)
|
||||
{
|
||||
SortDialog->Swap(pos, pos-1);
|
||||
SortDialog->Scroll(wUp);
|
||||
}
|
||||
}
|
||||
else if (Keypressed(input, Key.MvSongDown))
|
||||
{
|
||||
size_t pos = SortDialog->Choice();
|
||||
if (pos < SortOptions-1)
|
||||
{
|
||||
SortDialog->Swap(pos, pos+1);
|
||||
SortDialog->Scroll(wDown);
|
||||
}
|
||||
}
|
||||
else if (Keypressed(input, Key.Enter))
|
||||
{
|
||||
size_t pos = SortDialog->Choice();
|
||||
if (pos > SortOptions)
|
||||
{
|
||||
BlockRefreshing = 0;
|
||||
if (pos == SortOptions+1) // sort
|
||||
break;
|
||||
else if (pos == SortOptions+2) // cancel
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowMessage("Move tag types up and down to adjust sort order");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MPD::SongList playlist, cmp;
|
||||
|
||||
playlist.reserve(w->Size());
|
||||
|
||||
for (size_t i = 0; i < w->Size(); i++)
|
||||
{
|
||||
(*w)[i].SetPosition(i);
|
||||
playlist.push_back(&(*w)[i]);
|
||||
}
|
||||
|
||||
cmp = playlist;
|
||||
sort(playlist.begin(), playlist.end(), Playlist::Sorting);
|
||||
|
||||
if (playlist == cmp)
|
||||
{
|
||||
ShowMessage("Playlist is already sorted");
|
||||
return;
|
||||
}
|
||||
|
||||
ShowMessage("Sorting playlist...");
|
||||
do
|
||||
{
|
||||
for (size_t i = 0; i < playlist.size(); i++)
|
||||
{
|
||||
if (playlist[i]->GetPosition() > int(i))
|
||||
{
|
||||
Mpd->Swap(playlist[i]->GetPosition(), i);
|
||||
std::swap(cmp[playlist[i]->GetPosition()], cmp[i]);
|
||||
}
|
||||
cmp[i]->SetPosition(i);
|
||||
}
|
||||
}
|
||||
while (playlist != cmp);
|
||||
ShowMessage("Playlist sorted!");
|
||||
}
|
||||
|
||||
bool Playlist::Sorting(MPD::Song *a, MPD::Song *b)
|
||||
{
|
||||
for (size_t i = 0; i < SortOptions; i++)
|
||||
{
|
||||
MPD::Song::GetFunction get = (*SortDialog)[i].second;
|
||||
if ((a->*get)() != (b->*get)())
|
||||
{
|
||||
return (a->*get)() < (b->*get)();
|
||||
}
|
||||
}
|
||||
return a->GetPosition() < b->GetPosition();
|
||||
}
|
||||
|
||||
std::string Playlist::TotalLength()
|
||||
{
|
||||
std::ostringstream result;
|
||||
|
||||
@@ -53,6 +53,8 @@ class Playlist : public Screen< Menu<MPD::Song> >
|
||||
bool isPlaying() { return NowPlaying >= 0 && !w->Empty(); }
|
||||
const MPD::Song &NowPlayingSong();
|
||||
|
||||
void Sort();
|
||||
|
||||
static std::string SongToString(const MPD::Song &, void *);
|
||||
static std::string SongInColumnsToString(const MPD::Song &, void *);
|
||||
|
||||
@@ -61,9 +63,18 @@ class Playlist : public Screen< Menu<MPD::Song> >
|
||||
|
||||
static bool BlockNowPlayingUpdate;
|
||||
static bool BlockUpdate;
|
||||
static bool BlockRefreshing;
|
||||
|
||||
protected:
|
||||
std::string TotalLength();
|
||||
|
||||
static bool Sorting(MPD::Song *a, MPD::Song *b);
|
||||
|
||||
static Menu< std::pair<std::string, MPD::Song::GetFunction> > *SortDialog;
|
||||
|
||||
static const size_t SortOptions;
|
||||
static const size_t SortDialogWidth;
|
||||
static const size_t SortDialogHeight;
|
||||
};
|
||||
|
||||
extern Playlist *myPlaylist;
|
||||
|
||||
@@ -117,6 +117,7 @@ void DefaultKeys(ncmpcpp_keys &keys)
|
||||
keys.ToggleCrossfade[0] = 'x';
|
||||
keys.SetCrossfade[0] = 'X';
|
||||
keys.UpdateDB[0] = 'u';
|
||||
keys.SortPlaylist[0] = 22;
|
||||
keys.ApplyFilter[0] = 6;
|
||||
keys.FindForward[0] = '/';
|
||||
keys.FindBackward[0] = '?';
|
||||
@@ -182,6 +183,7 @@ void DefaultKeys(ncmpcpp_keys &keys)
|
||||
keys.ToggleCrossfade[1] = null_key;
|
||||
keys.SetCrossfade[1] = null_key;
|
||||
keys.UpdateDB[1] = null_key;
|
||||
keys.SortPlaylist[1] = null_key;
|
||||
keys.ApplyFilter[1] = null_key;
|
||||
keys.FindForward[1] = null_key;
|
||||
keys.FindBackward[1] = null_key;
|
||||
@@ -356,6 +358,8 @@ void ReadKeys(ncmpcpp_keys &keys)
|
||||
GetKeys(key, keys.SetCrossfade);
|
||||
else if (key.find("key_update_db ") != string::npos)
|
||||
GetKeys(key, keys.UpdateDB);
|
||||
else if (key.find("key_sort_playlist ") != string::npos)
|
||||
GetKeys(key, keys.SortPlaylist);
|
||||
else if (key.find("key_apply_filter ") != string::npos)
|
||||
GetKeys(key, keys.ApplyFilter);
|
||||
else if (key.find("key_find_forward ") != string::npos)
|
||||
|
||||
@@ -66,6 +66,7 @@ struct ncmpcpp_keys
|
||||
int ToggleCrossfade[2];
|
||||
int SetCrossfade[2];
|
||||
int UpdateDB[2];
|
||||
int SortPlaylist[2];
|
||||
int ApplyFilter[2];
|
||||
int FindForward[2];
|
||||
int FindBackward[2];
|
||||
|
||||
@@ -299,7 +299,7 @@ string Song::toString(const std::string &format) const
|
||||
if (*it == '{')
|
||||
{
|
||||
prev_pos = it;
|
||||
string (Song::*get)() const = 0;
|
||||
GetFunction get = 0;
|
||||
for (; *it != '}'; it++)
|
||||
{
|
||||
if (*it == '%')
|
||||
|
||||
@@ -31,6 +31,10 @@ namespace MPD
|
||||
class Song
|
||||
{
|
||||
public:
|
||||
|
||||
typedef void (Song::*SetFunction)(const std::string &);
|
||||
typedef std::string (Song::*GetFunction)() const;
|
||||
|
||||
Song() : itsSlash(std::string::npos), itsHash(0), copyPtr(0), isStream(0), isLocalised(0) { itsSong = mpd_newSong(); }
|
||||
Song(mpd_Song *, bool = 0);
|
||||
Song(const Song &);
|
||||
|
||||
@@ -470,7 +470,7 @@ void NcmpcppStatusChanged(Connection *Mpd, StatusChanges changed, void *)
|
||||
wHeader->SetColor(Config.header_color);
|
||||
wHeader->Refresh();
|
||||
}
|
||||
if (myScreen == myPlaylist)
|
||||
if (myScreen == myPlaylist && !Playlist::BlockRefreshing)
|
||||
myPlaylist->Main()->Refresh();
|
||||
wFooter->Bold(0);
|
||||
wFooter->GotoXY(sx, sy);
|
||||
|
||||
@@ -303,13 +303,13 @@ void TagEditor::Init()
|
||||
Albums = new Menu<string_pair>(0, main_start_y, LeftColumnWidth, main_height, "Albums", Config.main_color, brNone);
|
||||
Albums->HighlightColor(Config.active_column_color);
|
||||
Albums->SetTimeout(ncmpcpp_window_timeout);
|
||||
Albums->SetItemDisplayer(Display::StringPairs);
|
||||
Albums->SetItemDisplayer(Display::Pairs);
|
||||
Albums->SetGetStringFunction(MediaLibrary::StringPairToString);
|
||||
|
||||
Dirs = new Menu<string_pair>(0, main_start_y, LeftColumnWidth, main_height, "Directories", Config.main_color, brNone);
|
||||
Dirs->HighlightColor(Config.active_column_color);
|
||||
Dirs->SetTimeout(ncmpcpp_window_timeout);
|
||||
Dirs->SetItemDisplayer(Display::StringPairs);
|
||||
Dirs->SetItemDisplayer(Display::Pairs);
|
||||
Dirs->SetGetStringFunction(MediaLibrary::StringPairToString);
|
||||
|
||||
LeftColumn = Config.albums_in_tag_editor ? Albums : Dirs;
|
||||
@@ -537,8 +537,8 @@ void TagEditor::EnterPressed()
|
||||
for (size_t i = 0; i < Tags->Size(); i++)
|
||||
list.push_back(&Tags->at(i));
|
||||
|
||||
SongGetFunction get = 0;
|
||||
SongSetFunction set = 0;
|
||||
Song::GetFunction get = 0;
|
||||
Song::SetFunction set = 0;
|
||||
|
||||
size_t id = TagTypes->RealChoice();
|
||||
switch (id)
|
||||
@@ -1057,7 +1057,7 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
SongSetFunction IntoSetFunction(char c)
|
||||
Song::SetFunction IntoSetFunction(char c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
@@ -1133,7 +1133,7 @@ namespace
|
||||
|
||||
if (!preview)
|
||||
{
|
||||
SongSetFunction set = IntoSetFunction(it->first);
|
||||
Song::SetFunction set = IntoSetFunction(it->first);
|
||||
if (set)
|
||||
(s.*set)(it->second);
|
||||
}
|
||||
@@ -1144,7 +1144,7 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
SongSetFunction IntoSetFunction(mpd_TagItems tag)
|
||||
Song::SetFunction IntoSetFunction(mpd_TagItems tag)
|
||||
{
|
||||
switch (tag)
|
||||
{
|
||||
|
||||
@@ -117,13 +117,10 @@ class TagEditor : public Screen<Window>
|
||||
|
||||
extern TagEditor *myTagEditor;
|
||||
|
||||
typedef void (MPD::Song::*SongSetFunction)(const std::string &);
|
||||
typedef std::string (MPD::Song::*SongGetFunction)() const;
|
||||
|
||||
std::string FindSharedDir(Menu<MPD::Song> *);
|
||||
std::string FindSharedDir(const MPD::SongList &);
|
||||
|
||||
SongSetFunction IntoSetFunction(mpd_TagItems);
|
||||
MPD::Song::SetFunction IntoSetFunction(mpd_TagItems);
|
||||
|
||||
void DealWithFilenames(MPD::SongList &);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user