check for emptiness in some more places
[ncmpcpp.git] / src / playlist.cpp
blob3dfcd678e7572b20875fb9638838c0bea320603b
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::ReadKey(int &key)
246 w->ReadKey(key);
247 UpdateTimer();
250 void Playlist::MouseButtonPressed(MEVENT me)
252 if (w == Items && !Items->Empty() && Items->hasCoords(me.x, me.y))
254 if (size_t(me.y) < Items->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
256 Items->Goto(me.y);
257 if (me.bstate & BUTTON3_PRESSED)
258 EnterPressed();
260 else
261 Screen<Window>::MouseButtonPressed(me);
263 else if (w == SortDialog && SortDialog->hasCoords(me.x, me.y))
265 if (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED))
267 SortDialog->Goto(me.y);
268 if (me.bstate & BUTTON3_PRESSED)
269 EnterPressed();
271 else
272 Screen<Window>::MouseButtonPressed(me);
276 MPD::Song *Playlist::CurrentSong()
278 return !Items->Empty() ? &Items->Current() : 0;
281 void Playlist::GetSelectedSongs(MPD::SongList &v)
283 if (myPlaylist->Items->Empty())
284 return;
285 std::vector<size_t> selected;
286 Items->GetSelected(selected);
287 if (selected.empty())
288 selected.push_back(Items->Choice());
289 for (std::vector<size_t>::const_iterator it = selected.begin(); it != selected.end(); ++it)
290 v.push_back(new MPD::Song(Items->at(*it)));
293 void Playlist::ApplyFilter(const std::string &s)
295 if (w == Items)
296 Items->ApplyFilter(s, 0, REG_ICASE | Config.regex_type);
299 void Playlist::Sort()
301 if (Items->GetWidth() < SortDialogWidth || MainHeight < 5)
302 ShowMessage("Screen is too small to display dialog window!");
303 else
305 SortDialog->Reset();
306 w = SortDialog;
310 void Playlist::AdjustSortOrder(int key)
312 if (Keypressed(key, Key.MvSongUp))
314 size_t pos = SortDialog->Choice();
315 if (pos > 0 && pos < SortOptions)
317 SortDialog->Swap(pos, pos-1);
318 SortDialog->Scroll(wUp);
321 else if (Keypressed(key, Key.MvSongDown))
323 size_t pos = SortDialog->Choice();
324 if (pos < SortOptions-1)
326 SortDialog->Swap(pos, pos+1);
327 SortDialog->Scroll(wDown);
332 void Playlist::FixPositions(size_t beginning)
334 bool was_filtered = Items->isFiltered();
335 Items->ShowAll();
336 for (size_t i = beginning; i < Items->Size(); ++i)
337 (*Items)[i].SetPosition(i);
338 if (was_filtered)
339 Items->ShowFiltered();
342 void Playlist::EnableHighlighting()
344 Items->Highlighting(1);
345 UpdateTimer();
348 bool Playlist::Sorting(MPD::Song *a, MPD::Song *b)
350 CaseInsensitiveStringComparison cmp;
351 for (size_t i = 0; i < SortOptions; ++i)
352 if (int ret = cmp(a->GetTags((*SortDialog)[i].second), b->GetTags((*SortDialog)[i].second)))
353 return ret < 0;
354 return a->GetPosition() < b->GetPosition();
357 std::string Playlist::TotalLength()
359 std::ostringstream result;
361 if (ReloadTotalLength)
363 itsTotalLength = 0;
364 for (size_t i = 0; i < Items->Size(); ++i)
365 itsTotalLength += (*Items)[i].GetTotalLength();
366 ReloadTotalLength = 0;
368 if (Config.playlist_show_remaining_time && ReloadRemaining && !Items->isFiltered())
370 itsRemainingTime = 0;
371 for (size_t i = NowPlaying; i < Items->Size(); ++i)
372 itsRemainingTime += (*Items)[i].GetTotalLength();
373 ReloadRemaining = 0;
376 result << '(' << Items->Size() << (Items->Size() == 1 ? " item" : " items");
378 if (Items->isFiltered())
380 Items->ShowAll();
381 size_t real_size = Items->Size();
382 Items->ShowFiltered();
383 if (Items->Size() != real_size)
384 result << " (out of " << Mpd.GetPlaylistLength() << ")";
387 if (itsTotalLength)
389 result << ", length: ";
390 ShowTime(result, itsTotalLength, 0);
392 if (Config.playlist_show_remaining_time && itsRemainingTime && !Items->isFiltered() && Items->Size() > 1)
394 result << " :: remaining: ";
395 ShowTime(result, itsRemainingTime, 0);
397 result << ')';
398 return result.str();
401 const MPD::Song *Playlist::NowPlayingSong()
403 bool was_filtered = Items->isFiltered();
404 Items->ShowAll();
405 const MPD::Song *s = isPlaying() ? &Items->at(NowPlaying) : 0;
406 if (was_filtered)
407 Items->ShowFiltered();
408 return s;
411 std::string Playlist::SongToString(const MPD::Song &s, void *data)
413 return s.toString(*static_cast<std::string *>(data));
416 std::string Playlist::SongInColumnsToString(const MPD::Song &s, void *)
418 std::string result = "{";
419 for (std::vector<Column>::const_iterator it = Config.columns.begin(); it != Config.columns.end(); ++it)
421 if (it->type == 't')
423 result += "{%t}|{%f}";
425 else
427 // tags should be put in additional braces as if they are not, the
428 // tag that is not present within 'main' braces discards them all.
429 result += "{%";
430 result += it->type;
431 result += "}";
433 result += " ";
435 result += "}";
436 return s.toString(result);
439 bool Playlist::Add(const MPD::Song &s, bool in_playlist, bool play, int position)
441 Global::BlockItemListUpdate = 1;
442 if (Config.ncmpc_like_songs_adding && in_playlist)
444 unsigned hash = s.GetHash();
445 if (play)
447 for (size_t i = 0; i < Items->Size(); ++i)
449 if (Items->at(i).GetHash() == hash)
451 Mpd.Play(i);
452 break;
455 return true;
457 else
459 Playlist::BlockUpdate = 1;
460 Mpd.StartCommandsList();
461 for (size_t i = 0; i < Items->Size(); ++i)
463 if ((*Items)[i].GetHash() == hash)
465 Mpd.Delete(i);
466 Items->DeleteOption(i);
467 i--;
470 Mpd.CommitCommandsList();
471 Playlist::BlockUpdate = 0;
472 return false;
475 else
477 int id = Mpd.AddSong(s, position);
478 if (id >= 0)
480 ShowMessage("Added to playlist: %s", s.toString(Config.song_status_format_no_colors).c_str());
481 if (play)
482 Mpd.PlayID(id);
483 return true;
485 else
486 return false;
490 bool Playlist::Add(const MPD::SongList &l, bool play, int position)
492 if (l.empty())
493 return false;
495 size_t old_playlist_size = Items->Size();
497 Mpd.StartCommandsList();
498 MPD::SongList::const_iterator it = l.begin();
499 if (position < 0)
501 for (; it != l.end(); ++it)
502 if (Mpd.AddSong(**it) < 0)
503 break;
505 else
507 MPD::SongList::const_reverse_iterator j = l.rbegin();
508 for (; j != l.rend(); ++j)
509 if (Mpd.AddSong(**j, position) < 0)
510 break;
512 Mpd.CommitCommandsList();
514 if (play && old_playlist_size < Items->Size())
515 Mpd.Play(old_playlist_size);
517 if (position < 0 && Items->Back().GetHash() != l.back()->GetHash())
519 if (it != l.begin())
520 ShowMessage("%s", MPD::Message::PartOfSongsAdded);
521 return false;
523 else
524 return true;