tag editor: add support for numerating tracks using xx/xx format
[ncmpcpp.git] / src / playlist.cpp
blob367ac9c5e4a7fd12bf445ab15f33dbe2ca9dcfb3
1 /***************************************************************************
2 * Copyright (C) 2008-2009 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
21 #include <algorithm>
23 #include "display.h"
24 #include "global.h"
25 #include "helpers.h"
26 #include "menu.h"
27 #include "playlist.h"
28 #include "song.h"
29 #include "status.h"
31 using namespace Global;
33 Playlist *myPlaylist = new Playlist;
35 bool Playlist::ReloadTotalLength = 0;
36 bool Playlist::ReloadRemaining = 0;
38 bool Playlist::BlockNowPlayingUpdate = 0;
39 bool Playlist::BlockUpdate = 0;
41 const size_t Playlist::SortOptions = 10;
42 const size_t Playlist::SortDialogWidth = 30;
43 size_t Playlist::SortDialogHeight;
45 Menu< std::pair<std::string, MPD::Song::GetFunction> > *Playlist::SortDialog = 0;
47 void Playlist::Init()
49 Items = new Menu<MPD::Song>(0, MainStartY, COLS, MainHeight, Config.columns_in_playlist ? Display::Columns() : "", Config.main_color, brNone);
50 Items->CyclicScrolling(Config.use_cyclic_scrolling);
51 Items->HighlightColor(Config.main_highlight_color);
52 Items->SetSelectPrefix(&Config.selected_item_prefix);
53 Items->SetSelectSuffix(&Config.selected_item_suffix);
54 Items->SetItemDisplayer(Config.columns_in_playlist ? Display::SongsInColumns : Display::Songs);
55 Items->SetItemDisplayerUserData(&Config.song_list_format);
56 Items->SetGetStringFunction(Config.columns_in_playlist ? SongInColumnsToString : SongToString);
57 Items->SetGetStringFunctionUserData(&Config.song_list_format);
59 if (!SortDialog)
61 SortDialogHeight = std::min(int(MainHeight), 18);
63 SortDialog = new Menu< std::pair<std::string, MPD::Song::GetFunction> >((COLS-SortDialogWidth)/2, (MainHeight-SortDialogHeight)/2+MainStartY, SortDialogWidth, SortDialogHeight, "Sort songs by...", Config.main_color, Config.window_border);
64 SortDialog->CyclicScrolling(Config.use_cyclic_scrolling);
65 SortDialog->SetItemDisplayer(Display::Pairs);
67 SortDialog->AddOption(std::make_pair("Artist", &MPD::Song::GetArtist));
68 SortDialog->AddOption(std::make_pair("Album", &MPD::Song::GetAlbum));
69 SortDialog->AddOption(std::make_pair("Disc", &MPD::Song::GetDisc));
70 SortDialog->AddOption(std::make_pair("Track", &MPD::Song::GetTrack));
71 SortDialog->AddOption(std::make_pair("Genre", &MPD::Song::GetGenre));
72 SortDialog->AddOption(std::make_pair("Year", &MPD::Song::GetDate));
73 SortDialog->AddOption(std::make_pair("Composer", &MPD::Song::GetComposer));
74 SortDialog->AddOption(std::make_pair("Performer", &MPD::Song::GetPerformer));
75 SortDialog->AddOption(std::make_pair("Title", &MPD::Song::GetTitle));
76 SortDialog->AddOption(std::make_pair("Filename", &MPD::Song::GetFile));
77 SortDialog->AddSeparator();
78 SortDialog->AddOption(std::make_pair("Sort", static_cast<MPD::Song::GetFunction>(0)));
79 SortDialog->AddOption(std::make_pair("Reverse", static_cast<MPD::Song::GetFunction>(0)));
80 SortDialog->AddOption(std::make_pair("Cancel", static_cast<MPD::Song::GetFunction>(0)));
83 w = Items;
84 isInitialized = 1;
87 void Playlist::SwitchTo()
89 if (myScreen == this)
90 return;
92 if (!isInitialized)
93 Init();
95 itsScrollBegin = 0;
97 if (hasToBeResized)
98 Resize();
100 myScreen = this;
101 Items->Window::Clear();
102 EnableHighlighting();
103 if (w != Items) // even if sorting window is active, background has to be refreshed anyway
104 Items->Display();
105 RedrawHeader = 1;
108 void Playlist::Resize()
110 Items->Resize(COLS, MainHeight);
111 Items->MoveTo(0, MainStartY);
112 Items->SetTitle(Config.columns_in_playlist ? Display::Columns() : "");
113 if (w == SortDialog) // if sorting window is active, playlist needs refreshing
114 Items->Display();
116 SortDialogHeight = std::min(int(MainHeight), 18);
117 if (Items->GetWidth() >= SortDialogWidth && MainHeight >= 5)
119 SortDialog->Resize(SortDialogWidth, SortDialogHeight);
120 SortDialog->MoveTo((COLS-SortDialogWidth)/2, (MainHeight-SortDialogHeight)/2+MainStartY);
122 else // if screen is too low to display sorting window, fall back to items list
123 w = Items;
125 hasToBeResized = 0;
128 std::basic_string<my_char_t> Playlist::Title()
130 std::basic_string<my_char_t> result = U("Playlist ");
131 if (ReloadTotalLength || ReloadRemaining)
132 itsBufferedStats = TotalLength();
133 result += Scroller(TO_WSTRING(itsBufferedStats), itsScrollBegin, Items->GetWidth()-result.length()-(Config.new_design ? 2 : VolumeState.length()));
134 return result;
137 void Playlist::EnterPressed()
139 if (w == Items)
141 if (!Items->Empty())
143 Mpd.PlayID(Items->Current().GetID());
144 UpdateStatusImmediately = 1;
147 else if (w == SortDialog)
149 size_t pos = SortDialog->Choice();
151 size_t beginning = 0;
152 size_t end = Items->Size();
153 if (Items->hasSelected())
155 std::vector<size_t> list;
156 Items->GetSelected(list);
157 beginning = *list.begin();
158 end = *list.rbegin()+1;
161 if (pos > SortOptions)
163 if (pos == SortOptions+2) // reverse
165 BlockUpdate = 1;
166 ShowMessage("Reversing playlist order...");
167 Mpd.StartCommandsList();
168 for (size_t i = beginning, j = end-1; i < (beginning+end)/2; ++i, --j)
170 Mpd.Swap(i, j);
171 Items->Swap(i, j);
173 ShowMessage(Mpd.CommitCommandsList() ? "Playlist reversed!" : "Error while reversing playlist!");
174 w = Items;
175 return;
177 else if (pos == SortOptions+3) // cancel
179 w = Items;
180 return;
183 else
185 ShowMessage("Move tag types up and down to adjust sort order");
186 return;
189 ShowMessage("Sorting playlist...");
190 MPD::SongList playlist, cmp;
192 playlist.reserve(end-beginning);
193 for (size_t i = beginning; i < end; ++i)
195 (*Items)[i].SetPosition(i);
196 playlist.push_back(&(*Items)[i]);
198 cmp = playlist;
199 sort(playlist.begin(), playlist.end(), Playlist::Sorting);
201 if (playlist == cmp)
203 ShowMessage("Playlist is already sorted");
204 return;
207 BlockUpdate = 1;
208 Mpd.StartCommandsList();
211 for (size_t i = 0, j = beginning; i < playlist.size(); ++i, ++j)
213 if (playlist[i]->GetPosition() > j)
215 Mpd.Swap(playlist[i]->GetPosition(), j);
216 std::swap(cmp[playlist[i]->GetPosition()-beginning], cmp[i]);
217 Items->Swap(playlist[i]->GetPosition(), j);
219 cmp[i]->SetPosition(j);
222 while (playlist != cmp);
223 ShowMessage(Mpd.CommitCommandsList() ? "Playlist sorted!" : "Error while sorting playlist!");
224 w = Items;
228 void Playlist::SpacePressed()
230 if (w == Items)
232 Items->Select(Items->Choice(), !Items->isSelected());
233 Items->Scroll(wDown);
237 void Playlist::MouseButtonPressed(MEVENT me)
239 if (w == Items && !Items->Empty() && Items->hasCoords(me.x, me.y))
241 if (size_t(me.y) < Items->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
243 Items->Goto(me.y);
244 if (me.bstate & BUTTON3_PRESSED)
245 EnterPressed();
247 else
248 Screen<Window>::MouseButtonPressed(me);
250 else if (w == SortDialog && SortDialog->hasCoords(me.x, me.y))
252 if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
254 SortDialog->Goto(me.y);
255 if (me.bstate & BUTTON3_PRESSED)
256 EnterPressed();
258 else
259 Screen<Window>::MouseButtonPressed(me);
263 MPD::Song *Playlist::CurrentSong()
265 return !Items->Empty() ? &Items->Current() : 0;
268 void Playlist::GetSelectedSongs(MPD::SongList &v)
270 std::vector<size_t> selected;
271 Items->GetSelected(selected);
272 for (std::vector<size_t>::const_iterator it = selected.begin(); it != selected.end(); ++it)
273 v.push_back(new MPD::Song(Items->at(*it)));
276 void Playlist::ApplyFilter(const std::string &s)
278 if (w == Items)
279 Items->ApplyFilter(s, 0, REG_ICASE | Config.regex_type);
282 void Playlist::Sort()
284 if (Items->GetWidth() < SortDialogWidth || MainHeight < 5)
285 ShowMessage("Screen is too small to display dialog window!");
286 else
288 SortDialog->Reset();
289 w = SortDialog;
293 void Playlist::AdjustSortOrder(int key)
295 if (Keypressed(key, Key.MvSongUp))
297 size_t pos = SortDialog->Choice();
298 if (pos > 0 && pos < SortOptions)
300 SortDialog->Swap(pos, pos-1);
301 SortDialog->Scroll(wUp);
304 else if (Keypressed(key, Key.MvSongDown))
306 size_t pos = SortDialog->Choice();
307 if (pos < SortOptions-1)
309 SortDialog->Swap(pos, pos+1);
310 SortDialog->Scroll(wDown);
315 void Playlist::FixPositions(size_t beginning)
317 bool was_filtered = Items->isFiltered();
318 Items->ShowAll();
319 for (size_t i = beginning; i < Items->Size(); ++i)
320 (*Items)[i].SetPosition(i);
321 if (was_filtered)
322 Items->ShowFiltered();
325 void Playlist::EnableHighlighting()
327 Items->Highlighting(1);
328 UpdateTimer();
331 bool Playlist::Sorting(MPD::Song *a, MPD::Song *b)
333 CaseInsensitiveStringComparison cmp;
334 for (size_t i = 0; i < SortOptions; ++i)
335 if (int ret = cmp(a->GetTags((*SortDialog)[i].second), b->GetTags((*SortDialog)[i].second)))
336 return ret < 0;
337 return a->GetPosition() < b->GetPosition();
340 std::string Playlist::TotalLength()
342 std::ostringstream result;
344 if (ReloadTotalLength)
346 itsTotalLength = 0;
347 for (size_t i = 0; i < Items->Size(); ++i)
348 itsTotalLength += (*Items)[i].GetTotalLength();
349 ReloadTotalLength = 0;
351 if (Config.playlist_show_remaining_time && ReloadRemaining && !Items->isFiltered())
353 itsRemainingTime = 0;
354 for (size_t i = NowPlaying; i < Items->Size(); ++i)
355 itsRemainingTime += (*Items)[i].GetTotalLength();
356 ReloadRemaining = 0;
359 result << '(' << Items->Size() << (Items->Size() == 1 ? " item" : " items");
361 if (Items->isFiltered())
363 Items->ShowAll();
364 size_t real_size = Items->Size();
365 Items->ShowFiltered();
366 if (Items->Size() != real_size)
367 result << " (out of " << Mpd.GetPlaylistLength() << ")";
370 if (itsTotalLength)
372 result << ", length: ";
373 ShowTime(result, itsTotalLength, 0);
375 if (Config.playlist_show_remaining_time && itsRemainingTime && !Items->isFiltered() && Items->Size() > 1)
377 result << " :: remaining: ";
378 ShowTime(result, itsRemainingTime, 0);
380 result << ')';
381 return result.str();
384 const MPD::Song *Playlist::NowPlayingSong()
386 bool was_filtered = Items->isFiltered();
387 Items->ShowAll();
388 const MPD::Song *s = isPlaying() ? &Items->at(NowPlaying) : 0;
389 if (was_filtered)
390 Items->ShowFiltered();
391 return s;
394 std::string Playlist::SongToString(const MPD::Song &s, void *data)
396 return s.toString(*static_cast<std::string *>(data));
399 std::string Playlist::SongInColumnsToString(const MPD::Song &s, void *)
401 std::string result = "{";
402 for (std::vector<Column>::const_iterator it = Config.columns.begin(); it != Config.columns.end(); ++it)
404 if (it->type == 't')
406 result += "{%t}|{%f}";
408 else
410 // tags should be put in additional braces as if they are not, the
411 // tag that is not present within 'main' braces discards them all.
412 result += "{%";
413 result += it->type;
414 result += "}";
416 result += " ";
418 result += "}";
419 return s.toString(result);
422 bool Playlist::Add(const MPD::Song &s, bool in_playlist, bool play)
424 BlockItemListUpdate = 1;
425 if (Config.ncmpc_like_songs_adding && in_playlist)
427 unsigned hash = s.GetHash();
428 if (play)
430 for (size_t i = 0; i < Items->Size(); ++i)
432 if (Items->at(i).GetHash() == hash)
434 Mpd.Play(i);
435 break;
438 return true;
440 else
442 Playlist::BlockUpdate = 1;
443 Mpd.StartCommandsList();
444 for (size_t i = 0; i < Items->Size(); ++i)
446 if ((*Items)[i].GetHash() == hash)
448 Mpd.Delete(i);
449 Items->DeleteOption(i);
450 i--;
453 Mpd.CommitCommandsList();
454 Playlist::BlockUpdate = 0;
455 return false;
458 else
460 int id = Mpd.AddSong(s);
461 if (id >= 0)
463 ShowMessage("Added to playlist: %s", s.toString(Config.song_status_format_no_colors).c_str());
464 if (play)
465 Mpd.PlayID(id);
466 return true;
468 else
469 return false;
473 bool Playlist::Add(const MPD::SongList &l, bool play)
475 if (l.empty())
476 return false;
478 size_t old_playlist_size = Items->Size();
480 Mpd.StartCommandsList();
481 MPD::SongList::const_iterator it = l.begin();
482 for (; it != l.end(); ++it)
483 if (Mpd.AddSong(**it) < 0)
484 break;
485 Mpd.CommitCommandsList();
487 if (play && old_playlist_size < Items->Size())
488 Mpd.Play(old_playlist_size);
490 if (Items->Back().GetHash() != l.back()->GetHash())
492 if (it != l.begin())
493 ShowMessage("%s", MPD::Message::PartOfSongsAdded);
494 return false;
496 else
497 return true;