center cursor directly in Menu class
[ncmpcpp.git] / src / playlist.cpp
blob1982886030d462f85de99fbb9d37fdeb4465df4f
1 /***************************************************************************
2 * Copyright (C) 2008-2010 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 Global::MainHeight;
32 using Global::MainStartY;
34 Playlist *myPlaylist = new Playlist;
36 bool Playlist::ReloadTotalLength = 0;
37 bool Playlist::ReloadRemaining = 0;
39 bool Playlist::BlockNowPlayingUpdate = 0;
40 bool Playlist::BlockUpdate = 0;
42 const size_t Playlist::SortOptions = 10;
43 const size_t Playlist::SortDialogWidth = 30;
44 size_t Playlist::SortDialogHeight;
46 Menu< std::pair<std::string, MPD::Song::GetFunction> > *Playlist::SortDialog = 0;
48 void Playlist::Init()
50 Items = new Menu<MPD::Song>(0, MainStartY, COLS, MainHeight, Config.columns_in_playlist ? Display::Columns() : "", Config.main_color, brNone);
51 Items->CyclicScrolling(Config.use_cyclic_scrolling);
52 Items->CenteredCursor(Config.centered_cursor);
53 Items->HighlightColor(Config.main_highlight_color);
54 Items->SetSelectPrefix(&Config.selected_item_prefix);
55 Items->SetSelectSuffix(&Config.selected_item_suffix);
56 Items->SetItemDisplayer(Config.columns_in_playlist ? Display::SongsInColumns : Display::Songs);
57 Items->SetItemDisplayerUserData(&Config.song_list_format);
58 Items->SetGetStringFunction(Config.columns_in_playlist ? SongInColumnsToString : SongToString);
59 Items->SetGetStringFunctionUserData(&Config.song_list_format);
61 if (!SortDialog)
63 SortDialogHeight = std::min(int(MainHeight), 18);
65 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);
66 SortDialog->CyclicScrolling(Config.use_cyclic_scrolling);
67 SortDialog->CenteredCursor(Config.centered_cursor);
68 SortDialog->SetItemDisplayer(Display::Pairs);
70 SortDialog->AddOption(std::make_pair("Artist", &MPD::Song::GetArtist));
71 SortDialog->AddOption(std::make_pair("Album", &MPD::Song::GetAlbum));
72 SortDialog->AddOption(std::make_pair("Disc", &MPD::Song::GetDisc));
73 SortDialog->AddOption(std::make_pair("Track", &MPD::Song::GetTrack));
74 SortDialog->AddOption(std::make_pair("Genre", &MPD::Song::GetGenre));
75 SortDialog->AddOption(std::make_pair("Year", &MPD::Song::GetDate));
76 SortDialog->AddOption(std::make_pair("Composer", &MPD::Song::GetComposer));
77 SortDialog->AddOption(std::make_pair("Performer", &MPD::Song::GetPerformer));
78 SortDialog->AddOption(std::make_pair("Title", &MPD::Song::GetTitle));
79 SortDialog->AddOption(std::make_pair("Filename", &MPD::Song::GetFile));
80 SortDialog->AddSeparator();
81 SortDialog->AddOption(std::make_pair("Sort", static_cast<MPD::Song::GetFunction>(0)));
82 SortDialog->AddOption(std::make_pair("Reverse", static_cast<MPD::Song::GetFunction>(0)));
83 SortDialog->AddOption(std::make_pair("Cancel", static_cast<MPD::Song::GetFunction>(0)));
86 w = Items;
87 isInitialized = 1;
90 void Playlist::SwitchTo()
92 using Global::myScreen;
94 if (myScreen == this)
95 return;
97 if (!isInitialized)
98 Init();
100 itsScrollBegin = 0;
102 if (hasToBeResized)
103 Resize();
105 if (myScreen != this && myScreen->isTabbable())
106 Global::myPrevScreen = myScreen;
107 myScreen = this;
108 Items->Window::Clear();
109 EnableHighlighting();
110 if (w != Items) // even if sorting window is active, background has to be refreshed anyway
111 Items->Display();
112 Global::RedrawHeader = 1;
115 void Playlist::Resize()
117 Items->Resize(COLS, MainHeight);
118 Items->MoveTo(0, MainStartY);
119 Items->SetTitle(Config.columns_in_playlist ? Display::Columns() : "");
120 if (w == SortDialog) // if sorting window is active, playlist needs refreshing
121 Items->Display();
123 SortDialogHeight = std::min(int(MainHeight), 18);
124 if (Items->GetWidth() >= SortDialogWidth && MainHeight >= 5)
126 SortDialog->Resize(SortDialogWidth, SortDialogHeight);
127 SortDialog->MoveTo((COLS-SortDialogWidth)/2, (MainHeight-SortDialogHeight)/2+MainStartY);
129 else // if screen is too low to display sorting window, fall back to items list
130 w = Items;
132 hasToBeResized = 0;
135 std::basic_string<my_char_t> Playlist::Title()
137 std::basic_string<my_char_t> result = U("Playlist ");
138 if (ReloadTotalLength || ReloadRemaining)
139 itsBufferedStats = TotalLength();
140 result += Scroller(TO_WSTRING(itsBufferedStats), itsScrollBegin, Items->GetWidth()-result.length()-(Config.new_design ? 2 : Global::VolumeState.length()));
141 return result;
144 void Playlist::EnterPressed()
146 if (w == Items)
148 if (!Items->Empty())
150 Mpd.PlayID(Items->Current().GetID());
151 Global::UpdateStatusImmediately = 1;
154 else if (w == SortDialog)
156 size_t pos = SortDialog->Choice();
158 size_t beginning = 0;
159 size_t end = Items->Size();
160 if (Items->hasSelected())
162 std::vector<size_t> list;
163 Items->GetSelected(list);
164 beginning = *list.begin();
165 end = *list.rbegin()+1;
168 if (pos > SortOptions)
170 if (pos == SortOptions+2) // reverse
172 BlockUpdate = 1;
173 ShowMessage("Reversing playlist order...");
174 Mpd.StartCommandsList();
175 for (size_t i = beginning, j = end-1; i < (beginning+end)/2; ++i, --j)
177 Mpd.Swap(i, j);
178 Items->Swap(i, j);
180 ShowMessage(Mpd.CommitCommandsList() ? "Playlist reversed!" : "Error while reversing playlist!");
181 w = Items;
182 return;
184 else if (pos == SortOptions+3) // cancel
186 w = Items;
187 return;
190 else
192 ShowMessage("Move tag types up and down to adjust sort order");
193 return;
196 ShowMessage("Sorting playlist...");
197 MPD::SongList playlist, cmp;
199 playlist.reserve(end-beginning);
200 for (size_t i = beginning; i < end; ++i)
202 (*Items)[i].SetPosition(i);
203 playlist.push_back(&(*Items)[i]);
205 cmp = playlist;
206 sort(playlist.begin(), playlist.end(), Playlist::Sorting);
208 if (playlist == cmp)
210 ShowMessage("Playlist is already sorted");
211 return;
214 BlockUpdate = 1;
215 Mpd.StartCommandsList();
218 for (size_t i = 0, j = beginning; i < playlist.size(); ++i, ++j)
220 if (playlist[i]->GetPosition() > j)
222 Mpd.Swap(playlist[i]->GetPosition(), j);
223 std::swap(cmp[playlist[i]->GetPosition()-beginning], cmp[i]);
224 Items->Swap(playlist[i]->GetPosition(), j);
226 cmp[i]->SetPosition(j);
229 while (playlist != cmp);
230 ShowMessage(Mpd.CommitCommandsList() ? "Playlist sorted!" : "Error while sorting playlist!");
231 w = Items;
235 void Playlist::SpacePressed()
237 if (w == Items && !Items->Empty())
239 Items->Select(Items->Choice(), !Items->isSelected());
240 Items->Scroll(wDown);
244 void Playlist::MouseButtonPressed(MEVENT me)
246 if (w == Items && !Items->Empty() && Items->hasCoords(me.x, me.y))
248 if (size_t(me.y) < Items->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
250 Items->Goto(me.y);
251 if (me.bstate & BUTTON3_PRESSED)
252 EnterPressed();
254 else
255 Screen<Window>::MouseButtonPressed(me);
257 else if (w == SortDialog && SortDialog->hasCoords(me.x, me.y))
259 if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
261 SortDialog->Goto(me.y);
262 if (me.bstate & BUTTON3_PRESSED)
263 EnterPressed();
265 else
266 Screen<Window>::MouseButtonPressed(me);
270 MPD::Song *Playlist::CurrentSong()
272 return !Items->Empty() ? &Items->Current() : 0;
275 void Playlist::GetSelectedSongs(MPD::SongList &v)
277 std::vector<size_t> selected;
278 Items->GetSelected(selected);
279 if (selected.empty())
280 selected.push_back(Items->Choice());
281 for (std::vector<size_t>::const_iterator it = selected.begin(); it != selected.end(); ++it)
282 v.push_back(new MPD::Song(Items->at(*it)));
285 void Playlist::ApplyFilter(const std::string &s)
287 if (w == Items)
288 Items->ApplyFilter(s, 0, REG_ICASE | Config.regex_type);
291 void Playlist::Sort()
293 if (Items->GetWidth() < SortDialogWidth || MainHeight < 5)
294 ShowMessage("Screen is too small to display dialog window!");
295 else
297 SortDialog->Reset();
298 w = SortDialog;
302 void Playlist::AdjustSortOrder(int key)
304 if (Keypressed(key, Key.MvSongUp))
306 size_t pos = SortDialog->Choice();
307 if (pos > 0 && pos < SortOptions)
309 SortDialog->Swap(pos, pos-1);
310 SortDialog->Scroll(wUp);
313 else if (Keypressed(key, Key.MvSongDown))
315 size_t pos = SortDialog->Choice();
316 if (pos < SortOptions-1)
318 SortDialog->Swap(pos, pos+1);
319 SortDialog->Scroll(wDown);
324 void Playlist::FixPositions(size_t beginning)
326 bool was_filtered = Items->isFiltered();
327 Items->ShowAll();
328 for (size_t i = beginning; i < Items->Size(); ++i)
329 (*Items)[i].SetPosition(i);
330 if (was_filtered)
331 Items->ShowFiltered();
334 void Playlist::EnableHighlighting()
336 Items->Highlighting(1);
337 UpdateTimer();
340 bool Playlist::Sorting(MPD::Song *a, MPD::Song *b)
342 CaseInsensitiveStringComparison cmp;
343 for (size_t i = 0; i < SortOptions; ++i)
344 if (int ret = cmp(a->GetTags((*SortDialog)[i].second), b->GetTags((*SortDialog)[i].second)))
345 return ret < 0;
346 return a->GetPosition() < b->GetPosition();
349 std::string Playlist::TotalLength()
351 std::ostringstream result;
353 if (ReloadTotalLength)
355 itsTotalLength = 0;
356 for (size_t i = 0; i < Items->Size(); ++i)
357 itsTotalLength += (*Items)[i].GetTotalLength();
358 ReloadTotalLength = 0;
360 if (Config.playlist_show_remaining_time && ReloadRemaining && !Items->isFiltered())
362 itsRemainingTime = 0;
363 for (size_t i = NowPlaying; i < Items->Size(); ++i)
364 itsRemainingTime += (*Items)[i].GetTotalLength();
365 ReloadRemaining = 0;
368 result << '(' << Items->Size() << (Items->Size() == 1 ? " item" : " items");
370 if (Items->isFiltered())
372 Items->ShowAll();
373 size_t real_size = Items->Size();
374 Items->ShowFiltered();
375 if (Items->Size() != real_size)
376 result << " (out of " << Mpd.GetPlaylistLength() << ")";
379 if (itsTotalLength)
381 result << ", length: ";
382 ShowTime(result, itsTotalLength, 0);
384 if (Config.playlist_show_remaining_time && itsRemainingTime && !Items->isFiltered() && Items->Size() > 1)
386 result << " :: remaining: ";
387 ShowTime(result, itsRemainingTime, 0);
389 result << ')';
390 return result.str();
393 const MPD::Song *Playlist::NowPlayingSong()
395 bool was_filtered = Items->isFiltered();
396 Items->ShowAll();
397 const MPD::Song *s = isPlaying() ? &Items->at(NowPlaying) : 0;
398 if (was_filtered)
399 Items->ShowFiltered();
400 return s;
403 std::string Playlist::SongToString(const MPD::Song &s, void *data)
405 return s.toString(*static_cast<std::string *>(data));
408 std::string Playlist::SongInColumnsToString(const MPD::Song &s, void *)
410 std::string result = "{";
411 for (std::vector<Column>::const_iterator it = Config.columns.begin(); it != Config.columns.end(); ++it)
413 if (it->type == 't')
415 result += "{%t}|{%f}";
417 else
419 // tags should be put in additional braces as if they are not, the
420 // tag that is not present within 'main' braces discards them all.
421 result += "{%";
422 result += it->type;
423 result += "}";
425 result += " ";
427 result += "}";
428 return s.toString(result);
431 bool Playlist::Add(const MPD::Song &s, bool in_playlist, bool play, int position)
433 Global::BlockItemListUpdate = 1;
434 if (Config.ncmpc_like_songs_adding && in_playlist)
436 unsigned hash = s.GetHash();
437 if (play)
439 for (size_t i = 0; i < Items->Size(); ++i)
441 if (Items->at(i).GetHash() == hash)
443 Mpd.Play(i);
444 break;
447 return true;
449 else
451 Playlist::BlockUpdate = 1;
452 Mpd.StartCommandsList();
453 for (size_t i = 0; i < Items->Size(); ++i)
455 if ((*Items)[i].GetHash() == hash)
457 Mpd.Delete(i);
458 Items->DeleteOption(i);
459 i--;
462 Mpd.CommitCommandsList();
463 Playlist::BlockUpdate = 0;
464 return false;
467 else
469 int id = Mpd.AddSong(s, position);
470 if (id >= 0)
472 ShowMessage("Added to playlist: %s", s.toString(Config.song_status_format_no_colors).c_str());
473 if (play)
474 Mpd.PlayID(id);
475 return true;
477 else
478 return false;
482 bool Playlist::Add(const MPD::SongList &l, bool play, int position)
484 if (l.empty())
485 return false;
487 size_t old_playlist_size = Items->Size();
489 Mpd.StartCommandsList();
490 MPD::SongList::const_iterator it = l.begin();
491 if (position < 0)
493 for (; it != l.end(); ++it)
494 if (Mpd.AddSong(**it) < 0)
495 break;
497 else
499 MPD::SongList::const_reverse_iterator j = l.rbegin();
500 for (; j != l.rend(); ++j)
501 if (Mpd.AddSong(**j, position) < 0)
502 break;
504 Mpd.CommitCommandsList();
506 if (play && old_playlist_size < Items->Size())
507 Mpd.Play(old_playlist_size);
509 if (position < 0 && Items->Back().GetHash() != l.back()->GetHash())
511 if (it != l.begin())
512 ShowMessage("%s", MPD::Message::PartOfSongsAdded);
513 return false;
515 else
516 return true;