diff --git a/doc/keys b/doc/keys index 7b58233c..14d09909 100644 --- a/doc/keys +++ b/doc/keys @@ -82,6 +82,8 @@ # #key_update_db = 'u' # +#key_sort_playlist = 22 +# #key_apply_filter = 6 # #key_find_forward = '/' diff --git a/src/display.cpp b/src/display.cpp index df2ef7fb..5b2e6c3d 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -90,11 +90,6 @@ string Display::Columns(string st) return result; } -void Display::StringPairs(const string_pair &pair, void *, Menu *menu) -{ - *menu << pair.first; -} - void Display::SongsInColumns(const MPD::Song &s, void *s_template, Menu *menu) { string st = s_template ? *static_cast(s_template) : ""; @@ -115,7 +110,7 @@ void Display::SongsInColumns(const MPD::Song &s, void *s_template, Menu *menu) if (*it == '{') { prev_pos = it; - string (Song::*get)() const = 0; + Song::GetFunction get = 0; for (; *it != '}'; it++) { if (*it == '%') diff --git a/src/display.h b/src/display.h index b3c9ed6c..6bb2be7a 100644 --- a/src/display.h +++ b/src/display.h @@ -34,7 +34,10 @@ namespace Display *menu << t; } - void StringPairs(const string_pair &, void *, Menu *); + template void Pairs(const std::pair &pair, void *, Menu< std::pair > *menu) + { + *menu << pair.first; + } void SongsInColumns(const MPD::Song &, void *, Menu *); @@ -45,8 +48,6 @@ namespace Display void SearchEngine(const std::pair &, void *, Menu< std::pair > *); void Items(const MPD::Item &, void *, Menu *); - - void Clock(Window &, const tm *); } #endif diff --git a/src/help.cpp b/src/help.cpp index 97da3c67..32add389 100644 --- a/src/help.cpp +++ b/src/help.cpp @@ -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"; diff --git a/src/media_library.cpp b/src/media_library.cpp index e3167f3d..c82bf6c2 100644 --- a/src/media_library.cpp +++ b/src/media_library.cpp @@ -57,7 +57,7 @@ void MediaLibrary::Init() Albums = new Menu(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(itsRightColStartX, main_start_y, itsRightColWidth, main_height, "Songs", Config.main_color, brNone); diff --git a/src/mpdpp.cpp b/src/mpdpp.cpp index 4bc7b1bd..6ec52a20 100644 --- a/src/mpdpp.cpp +++ b/src/mpdpp.cpp @@ -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) diff --git a/src/mpdpp.h b/src/mpdpp.h index f919f8c8..882522c0 100644 --- a/src/mpdpp.h +++ b/src/mpdpp.h @@ -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; diff --git a/src/ncmpcpp.cpp b/src/ncmpcpp.cpp index 730d7b18..547a5fab 100644 --- a/src/ncmpcpp.cpp +++ b/src/ncmpcpp.cpp @@ -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(); diff --git a/src/playlist.cpp b/src/playlist.cpp index e88a4e3f..93cadeec 100644 --- a/src/playlist.cpp +++ b/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 > *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 >((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; diff --git a/src/playlist.h b/src/playlist.h index cbf0c045..4ded2375 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -53,6 +53,8 @@ class Playlist : public Screen< Menu > 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 > 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 > *SortDialog; + + static const size_t SortOptions; + static const size_t SortDialogWidth; + static const size_t SortDialogHeight; }; extern Playlist *myPlaylist; diff --git a/src/settings.cpp b/src/settings.cpp index 8ac91f9d..a430d067 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -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) diff --git a/src/settings.h b/src/settings.h index 1d76a6f8..b5916888 100644 --- a/src/settings.h +++ b/src/settings.h @@ -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]; diff --git a/src/song.cpp b/src/song.cpp index 6d6206eb..d3f387db 100644 --- a/src/song.cpp +++ b/src/song.cpp @@ -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 == '%') diff --git a/src/song.h b/src/song.h index 81bc1424..ffb7b221 100644 --- a/src/song.h +++ b/src/song.h @@ -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 &); diff --git a/src/status.cpp b/src/status.cpp index 3d7a4418..04c010f6 100644 --- a/src/status.cpp +++ b/src/status.cpp @@ -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); diff --git a/src/tag_editor.cpp b/src/tag_editor.cpp index 74aa7707..41f0dea6 100644 --- a/src/tag_editor.cpp +++ b/src/tag_editor.cpp @@ -303,13 +303,13 @@ void TagEditor::Init() Albums = new Menu(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(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) { diff --git a/src/tag_editor.h b/src/tag_editor.h index 93b99b67..24f31c91 100644 --- a/src/tag_editor.h +++ b/src/tag_editor.h @@ -117,13 +117,10 @@ class TagEditor : public Screen extern TagEditor *myTagEditor; -typedef void (MPD::Song::*SongSetFunction)(const std::string &); -typedef std::string (MPD::Song::*SongGetFunction)() const; - std::string FindSharedDir(Menu *); std::string FindSharedDir(const MPD::SongList &); -SongSetFunction IntoSetFunction(mpd_TagItems); +MPD::Song::SetFunction IntoSetFunction(mpd_TagItems); void DealWithFilenames(MPD::SongList &);