center cursor directly in Menu class
[ncmpcpp.git] / src / media_library.cpp
blob48f7367628bd0e4735818c4134198572acc4a3a1
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 "charset.h"
24 #include "display.h"
25 #include "helpers.h"
26 #include "global.h"
27 #include "media_library.h"
28 #include "mpdpp.h"
29 #include "playlist.h"
30 #include "status.h"
32 using Global::MainHeight;
33 using Global::MainStartY;
34 using Global::myScreen;
36 MediaLibrary *myLibrary = new MediaLibrary;
38 bool MediaLibrary::hasTwoColumns;
39 size_t MediaLibrary::itsLeftColWidth;
40 size_t MediaLibrary::itsMiddleColWidth;
41 size_t MediaLibrary::itsMiddleColStartX;
42 size_t MediaLibrary::itsRightColWidth;
43 size_t MediaLibrary::itsRightColStartX;
45 void MediaLibrary::Init()
47 hasTwoColumns = 0;
48 itsLeftColWidth = COLS/3-1;
49 itsMiddleColWidth = COLS/3;
50 itsMiddleColStartX = itsLeftColWidth+1;
51 itsRightColWidth = COLS-COLS/3*2-1;
52 itsRightColStartX = itsLeftColWidth+itsMiddleColWidth+2;
54 Artists = new Menu<std::string>(0, MainStartY, itsLeftColWidth, MainHeight, IntoStr(Config.media_lib_primary_tag) + "s", Config.main_color, brNone);
55 Artists->HighlightColor(Config.active_column_color);
56 Artists->CyclicScrolling(Config.use_cyclic_scrolling);
57 Artists->CenteredCursor(Config.centered_cursor);
58 Artists->SetSelectPrefix(&Config.selected_item_prefix);
59 Artists->SetSelectSuffix(&Config.selected_item_suffix);
60 Artists->SetItemDisplayer(Display::Generic);
62 Albums = new Menu<SearchConstraints>(itsMiddleColStartX, MainStartY, itsMiddleColWidth, MainHeight, "Albums", Config.main_color, brNone);
63 Albums->HighlightColor(Config.main_highlight_color);
64 Albums->CyclicScrolling(Config.use_cyclic_scrolling);
65 Albums->CenteredCursor(Config.centered_cursor);
66 Albums->SetSelectPrefix(&Config.selected_item_prefix);
67 Albums->SetSelectSuffix(&Config.selected_item_suffix);
68 Albums->SetItemDisplayer(DisplayAlbums);
69 Albums->SetGetStringFunction(AlbumToString);
70 Albums->SetGetStringFunctionUserData(this);
72 Songs = new Menu<MPD::Song>(itsRightColStartX, MainStartY, itsRightColWidth, MainHeight, "Songs", Config.main_color, brNone);
73 Songs->HighlightColor(Config.main_highlight_color);
74 Songs->CyclicScrolling(Config.use_cyclic_scrolling);
75 Songs->CenteredCursor(Config.centered_cursor);
76 Songs->SetSelectPrefix(&Config.selected_item_prefix);
77 Songs->SetSelectSuffix(&Config.selected_item_suffix);
78 Songs->SetItemDisplayer(Display::Songs);
79 Songs->SetItemDisplayerUserData(&Config.song_library_format);
80 Songs->SetGetStringFunction(SongToString);
82 w = Artists;
83 isInitialized = 1;
86 void MediaLibrary::Resize()
88 if (!hasTwoColumns)
90 itsLeftColWidth = COLS/3-1;
91 itsMiddleColStartX = itsLeftColWidth+1;
92 itsMiddleColWidth = COLS/3;
93 itsRightColStartX = itsLeftColWidth+itsMiddleColWidth+2;
94 itsRightColWidth = COLS-COLS/3*2-1;
96 else
98 itsMiddleColStartX = 0;
99 itsMiddleColWidth = COLS/2;
100 itsRightColStartX = itsMiddleColWidth+1;
101 itsRightColWidth = COLS-itsMiddleColWidth-1;
104 Artists->Resize(itsLeftColWidth, MainHeight);
105 Albums->Resize(itsMiddleColWidth, MainHeight);
106 Songs->Resize(itsRightColWidth, MainHeight);
108 Artists->MoveTo(0, MainStartY);
109 Albums->MoveTo(itsMiddleColStartX, MainStartY);
110 Songs->MoveTo(itsRightColStartX, MainStartY);
112 hasToBeResized = 0;
115 void MediaLibrary::Refresh()
117 Artists->Display();
118 mvvline(MainStartY, itsMiddleColStartX-1, 0, MainHeight);
119 Albums->Display();
120 mvvline(MainStartY, itsRightColStartX-1, 0, MainHeight);
121 Songs->Display();
122 if (Albums->Empty())
124 *Albums << XY(0, 0) << "No albums found.";
125 Albums->Window::Refresh();
129 void MediaLibrary::SwitchTo()
131 if (myScreen == this)
133 if (Config.media_library_disable_two_column_mode)
134 return;
135 else
137 hasTwoColumns = !hasTwoColumns;
138 hasToBeResized = 1;
139 Artists->Clear();
140 Albums->Clear();
141 Albums->Reset();
142 Songs->Clear();
143 if (hasTwoColumns)
145 if (w == Artists)
146 NextColumn();
147 std::string item_type = IntoStr(Config.media_lib_primary_tag);
148 ToLower(item_type);
149 Albums->SetTitle("Albums (sorted by " + item_type + ")");
151 else
152 Albums->SetTitle("Albums");
156 if (!isInitialized)
157 Init();
159 if (hasToBeResized)
160 Resize();
162 if (myScreen != this && myScreen->isTabbable())
163 Global::myPrevScreen = myScreen;
164 myScreen = this;
165 Global::RedrawHeader = 1;
166 Refresh();
167 UpdateSongList(Songs);
170 std::basic_string<my_char_t> MediaLibrary::Title()
172 return U("Media library");
175 void MediaLibrary::Update()
177 if (!hasTwoColumns && Artists->Empty())
179 MPD::TagList list;
180 Albums->Clear();
181 Mpd.GetList(list, Config.media_lib_primary_tag);
182 sort(list.begin(), list.end(), CaseInsensitiveSorting());
183 for (MPD::TagList::iterator it = list.begin(); it != list.end(); ++it)
185 if (!it->empty())
187 utf_to_locale(*it);
188 Artists->AddOption(*it);
191 Artists->Window::Clear();
192 Artists->Refresh();
195 if (!hasTwoColumns && !Artists->Empty() && Albums->Empty())
197 Songs->Clear();
198 Albums->Reset();
199 MPD::TagList list;
200 locale_to_utf(Artists->Current());
201 Mpd.StartFieldSearch(MPD_TAG_ALBUM);
202 Mpd.AddSearch(Config.media_lib_primary_tag, Artists->Current());
203 Mpd.CommitSearch(list);
205 for (MPD::TagList::iterator it = list.begin(); it != list.end(); ++it)
207 if (Config.media_library_display_date)
209 MPD::TagList l;
210 Mpd.StartFieldSearch(MPD_TAG_DATE);
211 Mpd.AddSearch(Config.media_lib_primary_tag, Artists->Current());
212 Mpd.AddSearch(MPD_TAG_ALBUM, *it);
213 Mpd.CommitSearch(l);
214 utf_to_locale(*it);
215 for (MPD::TagList::iterator j = l.begin(); j != l.end(); ++j)
217 utf_to_locale(*j);
218 Albums->AddOption(SearchConstraints(*it, *j));
221 else
223 utf_to_locale(*it);
224 Albums->AddOption(SearchConstraints(*it, ""));
227 utf_to_locale(Artists->Current());
228 if (!Albums->Empty())
229 Albums->Sort<SearchConstraintsSorting>();
230 Albums->Refresh();
232 else if (hasTwoColumns && Albums->Empty())
234 Songs->Clear();
235 MPD::TagList artists;
236 *Albums << XY(0, 0) << "Fetching albums...";
237 Albums->Window::Refresh();
238 Mpd.GetList(artists, Config.media_lib_primary_tag);
239 for (MPD::TagList::iterator i = artists.begin(); i != artists.end(); ++i)
241 if (i->empty())
242 continue;
243 MPD::TagList albums;
244 Mpd.StartFieldSearch(MPD_TAG_ALBUM);
245 Mpd.AddSearch(Config.media_lib_primary_tag, *i);
246 Mpd.CommitSearch(albums);
247 for (MPD::TagList::iterator j = albums.begin(); j != albums.end(); ++j)
249 if (Config.media_library_display_date)
251 if (Config.media_lib_primary_tag != MPD_TAG_DATE)
253 MPD::TagList years;
254 Mpd.StartFieldSearch(MPD_TAG_DATE);
255 Mpd.AddSearch(Config.media_lib_primary_tag, *i);
256 Mpd.AddSearch(MPD_TAG_ALBUM, *j);
257 Mpd.CommitSearch(years);
258 utf_to_locale(*i);
259 utf_to_locale(*j);
260 for (MPD::TagList::iterator k = years.begin(); k != years.end(); ++k)
262 utf_to_locale(*k);
263 Albums->AddOption(SearchConstraints(*i, *j, *k));
266 else
268 utf_to_locale(*i);
269 utf_to_locale(*j);
270 Albums->AddOption(SearchConstraints(*i, *j, *i));
273 else
275 utf_to_locale(*i);
276 utf_to_locale(*j);
277 Albums->AddOption(SearchConstraints(*i, *j, ""));
281 if (!Albums->Empty())
282 Albums->Sort<SearchConstraintsSorting>();
283 Albums->Refresh();
286 if (!hasTwoColumns && !Artists->Empty() && w == Albums && Albums->Empty())
288 Albums->HighlightColor(Config.main_highlight_color);
289 Artists->HighlightColor(Config.active_column_color);
290 w = Artists;
293 if ((hasTwoColumns || !Artists->Empty()) && Songs->Empty())
295 Songs->Reset();
296 MPD::SongList list;
298 Mpd.StartSearch(1);
299 Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(hasTwoColumns ? Albums->Current().Artist : Artists->Current()));
300 if (Albums->Empty()) // left for compatibility with <mpd-0.14
302 *Albums << XY(0, 0) << "No albums found.";
303 Albums->Window::Refresh();
305 else
307 Mpd.AddSearch(MPD_TAG_ALBUM, locale_to_utf_cpy(Albums->Current().Album));
308 if (Config.media_library_display_date)
309 Mpd.AddSearch(MPD_TAG_DATE, locale_to_utf_cpy(Albums->Current().Year));
311 Mpd.CommitSearch(list);
313 sort(list.begin(), list.end(), SortSongsByTrack);
314 bool bold = 0;
316 for (MPD::SongList::const_iterator it = list.begin(); it != list.end(); ++it)
318 for (size_t j = 0; j < myPlaylist->Items->Size(); ++j)
320 if ((*it)->GetHash() == myPlaylist->Items->at(j).GetHash())
322 bold = 1;
323 break;
326 Songs->AddOption(**it, bold);
327 bold = 0;
329 FreeSongList(list);
330 Songs->Window::Clear();
331 Songs->Refresh();
335 void MediaLibrary::SpacePressed()
337 if (Config.space_selects)
339 if (w == Artists)
341 Artists->Select(Artists->Choice(), !Artists->isSelected());
342 Albums->Clear();
344 else if (w == Albums)
346 Albums->Select(Albums->Choice(), !Albums->isSelected());
347 Songs->Clear();
349 else if (w == Songs)
350 Songs->Select(Songs->Choice(), !Songs->isSelected());
351 w->Scroll(wDown);
353 else
354 AddToPlaylist(0);
357 void MediaLibrary::MouseButtonPressed(MEVENT me)
359 if (!Artists->Empty() && Artists->hasCoords(me.x, me.y))
361 if (w != Artists)
363 PrevColumn();
364 PrevColumn();
366 if (size_t(me.y) < Artists->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
368 Artists->Goto(me.y);
369 if (me.bstate & BUTTON3_PRESSED)
371 size_t pos = Artists->Choice();
372 SpacePressed();
373 if (pos < Artists->Size()-1)
374 Artists->Scroll(wUp);
377 else
378 Screen<Window>::MouseButtonPressed(me);
379 Albums->Clear();
380 Songs->Clear();
382 else if (!Albums->Empty() && Albums->hasCoords(me.x, me.y))
384 if (w != Albums)
385 w == Artists ? NextColumn() : PrevColumn();
386 if (size_t(me.y) < Albums->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
388 Albums->Goto(me.y);
389 if (me.bstate & BUTTON3_PRESSED)
391 size_t pos = Albums->Choice();
392 SpacePressed();
393 if (pos < Albums->Size()-1)
394 Albums->Scroll(wUp);
397 else
398 Screen<Window>::MouseButtonPressed(me);
399 Songs->Clear();
401 else if (!Songs->Empty() && Songs->hasCoords(me.x, me.y))
403 if (w != Songs)
405 NextColumn();
406 NextColumn();
408 if (size_t(me.y) < Songs->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
410 Songs->Goto(me.y);
411 if (me.bstate & BUTTON1_PRESSED)
413 size_t pos = Songs->Choice();
414 SpacePressed();
415 if (pos < Songs->Size()-1)
416 Songs->Scroll(wUp);
418 else
419 EnterPressed();
421 else
422 Screen<Window>::MouseButtonPressed(me);
426 MPD::Song *MediaLibrary::CurrentSong()
428 return w == Songs && !Songs->Empty() ? &Songs->Current() : 0;
431 List *MediaLibrary::GetList()
433 if (w == Artists)
434 return Artists;
435 else if (w == Albums)
436 return Albums;
437 else if (w == Songs)
438 return Songs;
439 else // silence compiler
440 return 0;
443 void MediaLibrary::ReverseSelection()
445 if (w == Artists)
446 Artists->ReverseSelection();
447 else if (w == Albums)
448 Albums->ReverseSelection();
449 else if (w == Songs)
450 Songs->ReverseSelection();
453 void MediaLibrary::GetSelectedSongs(MPD::SongList &v)
455 std::vector<size_t> selected;
456 if (w == Artists && !Artists->Empty())
458 Artists->GetSelected(selected);
459 if (selected.empty())
460 selected.push_back(Artists->Choice());
461 for (std::vector<size_t>::const_iterator it = selected.begin(); it != selected.end(); ++it)
463 MPD::SongList list;
464 Mpd.StartSearch(1);
465 Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(Artists->at(*it)));
466 Mpd.CommitSearch(list);
467 for (MPD::SongList::const_iterator sIt = list.begin(); sIt != list.end(); ++sIt)
468 v.push_back(new MPD::Song(**sIt));
469 FreeSongList(list);
472 else if (w == Albums && !Albums->Empty())
474 Albums->GetSelected(selected);
475 if (selected.empty())
477 // shortcut via the existing song list in right column
478 if (v.empty())
479 v.reserve(Songs->Size());
480 for (size_t i = 0; i < Songs->Size(); ++i)
481 v.push_back(new MPD::Song((*Songs)[i]));
483 else
485 for (std::vector<size_t>::const_iterator it = selected.begin(); it != selected.end(); ++it)
487 MPD::SongList list;
488 Mpd.StartSearch(1);
489 Mpd.AddSearch(Config.media_lib_primary_tag, hasTwoColumns
490 ? Albums->at(*it).Artist
491 : locale_to_utf_cpy(Artists->Current()));
492 Mpd.AddSearch(MPD_TAG_ALBUM, Albums->at(*it).Album);
493 Mpd.AddSearch(MPD_TAG_DATE, Albums->at(*it).Year);
494 Mpd.CommitSearch(list);
495 for (MPD::SongList::const_iterator sIt = list.begin(); sIt != list.end(); ++sIt)
496 v.push_back(new MPD::Song(**sIt));
497 FreeSongList(list);
501 else if (w == Songs && !Songs->Empty())
503 Songs->GetSelected(selected);
504 if (selected.empty())
505 selected.push_back(Songs->Choice());
506 for (std::vector<size_t>::const_iterator it = selected.begin(); it != selected.end(); ++it)
507 v.push_back(new MPD::Song(Songs->at(*it)));
511 void MediaLibrary::ApplyFilter(const std::string &s)
513 GetList()->ApplyFilter(s, 0, REG_ICASE | Config.regex_type);
516 void MediaLibrary::NextColumn()
518 if (w == Artists)
520 if (!hasTwoColumns && Songs->Empty())
521 return;
522 Artists->HighlightColor(Config.main_highlight_color);
523 w->Refresh();
524 w = Albums;
525 Albums->HighlightColor(Config.active_column_color);
526 if (!Albums->Empty())
527 return;
529 if (w == Albums && !Songs->Empty())
531 Albums->HighlightColor(Config.main_highlight_color);
532 w->Refresh();
533 w = Songs;
534 Songs->HighlightColor(Config.active_column_color);
538 void MediaLibrary::PrevColumn()
540 if (w == Songs)
542 Songs->HighlightColor(Config.main_highlight_color);
543 w->Refresh();
544 w = Albums;
545 Albums->HighlightColor(Config.active_column_color);
546 if (!Albums->Empty())
547 return;
549 if (w == Albums && !hasTwoColumns)
551 Albums->HighlightColor(Config.main_highlight_color);
552 w->Refresh();
553 w = Artists;
554 Artists->HighlightColor(Config.active_column_color);
558 void MediaLibrary::LocateSong(const MPD::Song &s)
560 std::string primary_tag;
561 switch (Config.media_lib_primary_tag)
563 case MPD_TAG_ARTIST:
564 primary_tag = s.GetArtist();
565 break;
566 case MPD_TAG_DATE:
567 primary_tag = s.GetDate();
568 break;
569 case MPD_TAG_GENRE:
570 primary_tag = s.GetGenre();
571 break;
572 case MPD_TAG_COMPOSER:
573 primary_tag = s.GetComposer();
574 break;
575 case MPD_TAG_PERFORMER:
576 primary_tag = s.GetPerformer();
577 break;
578 default:
579 ShowMessage("Invalid tag type in left column of the media library");
580 return;
582 if (primary_tag.empty())
584 std::string item_type = IntoStr(Config.media_lib_primary_tag);
585 ToLower(item_type);
586 ShowMessage("Can't jump to media library because the song has no %s tag set.", item_type.c_str());
587 return;
590 if (myScreen != this)
591 SwitchTo();
592 Statusbar() << "Jumping to song...";
593 Global::wFooter->Refresh();
595 if (!hasTwoColumns)
597 if (Artists->Empty())
598 Update();
599 if (primary_tag != Artists->Current())
601 for (size_t i = 0; i < Artists->Size(); ++i)
603 if (primary_tag == (*Artists)[i])
605 Artists->Highlight(i);
606 Albums->Clear();
607 break;
613 if (Albums->Empty())
614 Update();
616 std::string album = s.GetAlbum();
617 std::string date = s.GetDate();
618 if ((hasTwoColumns && Albums->Current().Artist != primary_tag)
619 || album != Albums->Current().Album
620 || date != Albums->Current().Year)
622 for (size_t i = 0; i < Albums->Size(); ++i)
624 if ((!hasTwoColumns || (*Albums)[i].Artist == primary_tag)
625 && album == (*Albums)[i].Album
626 && date == (*Albums)[i].Year)
628 Albums->Highlight(i);
629 Songs->Clear();
630 break;
635 if (Songs->Empty())
636 Update();
638 std::string song = s.GetTitle();
639 if (song != Songs->Current().GetTitle())
641 for (size_t i = 0; i < Songs->Size(); ++i)
643 if (song == (*Songs)[i].GetTitle())
645 Songs->Highlight(i);
646 break;
651 Artists->HighlightColor(Config.main_highlight_color);
652 Albums->HighlightColor(Config.main_highlight_color);
653 Songs->HighlightColor(Config.active_column_color);
654 w = Songs;
655 Refresh();
658 void MediaLibrary::AddToPlaylist(bool add_n_play)
660 if (w == Songs && !Songs->Empty())
661 Songs->Bold(Songs->Choice(), myPlaylist->Add(Songs->Current(), Songs->isBold(), add_n_play));
662 else
664 MPD::SongList list;
665 GetSelectedSongs(list);
667 if (myPlaylist->Add(list, add_n_play))
669 if (!Artists->Empty() && w == Artists)
671 std::string tag_type = IntoStr(Config.media_lib_primary_tag);
672 ToLower(tag_type);
673 ShowMessage("Adding songs of %s \"%s\"", tag_type.c_str(), Artists->Current().c_str());
675 else if (w == Albums)
676 ShowMessage("Adding songs from album \"%s\"", Albums->Current().Album.c_str());
680 if (!add_n_play)
682 w->Scroll(wDown);
683 if (w == Artists)
685 Albums->Clear();
686 Songs->Clear();
688 else if (w == Albums)
689 Songs->Clear();
693 std::string MediaLibrary::SongToString(const MPD::Song &s, void *)
695 return s.toString(Config.song_library_format);
698 std::string MediaLibrary::AlbumToString(const SearchConstraints &sc, void *ptr)
700 std::string result;
701 if (!sc.Artist.empty())
702 (result += sc.Artist) += " - ";
703 if ((!static_cast<MediaLibrary *>(ptr)->hasTwoColumns || Config.media_lib_primary_tag != MPD_TAG_DATE) && !sc.Year.empty())
704 ((result += "(") += sc.Year) += ") ";
705 result += sc.Album.empty() ? "<no album>" : sc.Album;
706 return result;
709 void MediaLibrary::DisplayAlbums(const SearchConstraints &sc, void *, Menu<SearchConstraints> *menu)
711 *menu << AlbumToString(sc, 0);
714 bool MediaLibrary::SearchConstraintsSorting::operator()(const SearchConstraints &a, const SearchConstraints &b) const
716 int result;
717 CaseInsensitiveStringComparison cmp;
718 if (!a.Artist.empty() || b.Artist.empty())
720 result = cmp(a.Artist, b.Artist);
721 if (result != 0)
722 return result < 0;
724 result = cmp(a.Year, b.Year);
725 return (result == 0 ? cmp(a.Album, b.Album) : result) < 0;
728 bool MediaLibrary::SortSongsByTrack(MPD::Song *a, MPD::Song *b)
730 if (a->GetDisc() == b->GetDisc())
731 return StrToInt(a->GetTrack()) < StrToInt(b->GetTrack());
732 else
733 return StrToInt(a->GetDisc()) < StrToInt(b->GetDisc());