fixes for db97a1a3186b2bbd9d36770e83aa5325db5ce77a
[ncmpcpp.git] / src / media_library.cpp
blobc402763ee619d1b831034c292fc02cb09eeab7c5
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->SetSelectPrefix(&Config.selected_item_prefix);
58 Artists->SetSelectSuffix(&Config.selected_item_suffix);
59 Artists->SetItemDisplayer(Display::Generic);
61 Albums = new Menu<SearchConstraints>(itsMiddleColStartX, MainStartY, itsMiddleColWidth, MainHeight, "Albums", Config.main_color, brNone);
62 Albums->HighlightColor(Config.main_highlight_color);
63 Albums->CyclicScrolling(Config.use_cyclic_scrolling);
64 Albums->SetSelectPrefix(&Config.selected_item_prefix);
65 Albums->SetSelectSuffix(&Config.selected_item_suffix);
66 Albums->SetItemDisplayer(DisplayAlbums);
67 Albums->SetGetStringFunction(AlbumToString);
68 Albums->SetGetStringFunctionUserData(this);
70 Songs = new Menu<MPD::Song>(itsRightColStartX, MainStartY, itsRightColWidth, MainHeight, "Songs", Config.main_color, brNone);
71 Songs->HighlightColor(Config.main_highlight_color);
72 Songs->CyclicScrolling(Config.use_cyclic_scrolling);
73 Songs->SetSelectPrefix(&Config.selected_item_prefix);
74 Songs->SetSelectSuffix(&Config.selected_item_suffix);
75 Songs->SetItemDisplayer(Display::Songs);
76 Songs->SetItemDisplayerUserData(&Config.song_library_format);
77 Songs->SetGetStringFunction(SongToString);
79 w = Artists;
80 isInitialized = 1;
83 void MediaLibrary::Resize()
85 if (!hasTwoColumns)
87 itsLeftColWidth = COLS/3-1;
88 itsMiddleColStartX = itsLeftColWidth+1;
89 itsMiddleColWidth = COLS/3;
90 itsRightColStartX = itsLeftColWidth+itsMiddleColWidth+2;
91 itsRightColWidth = COLS-COLS/3*2-1;
93 else
95 itsMiddleColStartX = 0;
96 itsMiddleColWidth = COLS/2;
97 itsRightColStartX = itsMiddleColWidth+1;
98 itsRightColWidth = COLS-itsMiddleColWidth-1;
101 Artists->Resize(itsLeftColWidth, MainHeight);
102 Albums->Resize(itsMiddleColWidth, MainHeight);
103 Songs->Resize(itsRightColWidth, MainHeight);
105 Artists->MoveTo(0, MainStartY);
106 Albums->MoveTo(itsMiddleColStartX, MainStartY);
107 Songs->MoveTo(itsRightColStartX, MainStartY);
109 hasToBeResized = 0;
112 void MediaLibrary::Refresh()
114 Artists->Display();
115 mvvline(MainStartY, itsMiddleColStartX-1, 0, MainHeight);
116 Albums->Display();
117 mvvline(MainStartY, itsRightColStartX-1, 0, MainHeight);
118 Songs->Display();
119 if (Albums->Empty())
121 *Albums << XY(0, 0) << "No albums found.";
122 Albums->Window::Refresh();
126 void MediaLibrary::SwitchTo()
128 if (myScreen == this)
130 if (Config.media_library_disable_two_column_mode)
131 return;
132 else
134 hasTwoColumns = !hasTwoColumns;
135 hasToBeResized = 1;
136 Artists->Clear();
137 Albums->Clear();
138 Albums->Reset();
139 Songs->Clear();
140 if (hasTwoColumns)
142 if (w == Artists)
143 NextColumn();
144 std::string item_type = IntoStr(Config.media_lib_primary_tag);
145 ToLower(item_type);
146 Albums->SetTitle("Albums (sorted by " + item_type + ")");
148 else
149 Albums->SetTitle("Albums");
153 if (!isInitialized)
154 Init();
156 if (hasToBeResized)
157 Resize();
159 if (myScreen != this && myScreen->isTabbable())
160 Global::myPrevScreen = myScreen;
161 myScreen = this;
162 Global::RedrawHeader = 1;
163 Refresh();
164 UpdateSongList(Songs);
167 std::basic_string<my_char_t> MediaLibrary::Title()
169 return U("Media library");
172 void MediaLibrary::Update()
174 if (!hasTwoColumns && Artists->Empty())
176 MPD::TagList list;
177 Albums->Clear();
178 Mpd.GetList(list, Config.media_lib_primary_tag);
179 sort(list.begin(), list.end(), CaseInsensitiveSorting());
180 for (MPD::TagList::iterator it = list.begin(); it != list.end(); ++it)
182 if (!it->empty())
184 utf_to_locale(*it);
185 Artists->AddOption(*it);
188 Artists->Window::Clear();
189 Artists->Refresh();
192 if (!hasTwoColumns && !Artists->Empty() && Albums->Empty())
194 Songs->Clear();
195 Albums->Reset();
196 MPD::TagList list;
197 locale_to_utf(Artists->Current());
198 Mpd.StartFieldSearch(MPD_TAG_ALBUM);
199 Mpd.AddSearch(Config.media_lib_primary_tag, Artists->Current());
200 Mpd.CommitSearch(list);
202 for (MPD::TagList::iterator it = list.begin(); it != list.end(); ++it)
204 if (Config.media_library_display_date)
206 MPD::TagList l;
207 Mpd.StartFieldSearch(MPD_TAG_DATE);
208 Mpd.AddSearch(Config.media_lib_primary_tag, Artists->Current());
209 Mpd.AddSearch(MPD_TAG_ALBUM, *it);
210 Mpd.CommitSearch(l);
211 utf_to_locale(*it);
212 for (MPD::TagList::iterator j = l.begin(); j != l.end(); ++j)
214 utf_to_locale(*j);
215 Albums->AddOption(SearchConstraints(*it, *j));
218 else
220 utf_to_locale(*it);
221 Albums->AddOption(SearchConstraints(*it, ""));
224 utf_to_locale(Artists->Current());
225 if (!Albums->Empty())
226 Albums->Sort<SearchConstraintsSorting>();
227 Albums->Refresh();
229 else if (hasTwoColumns && Albums->Empty())
231 Songs->Clear();
232 MPD::TagList artists;
233 *Albums << XY(0, 0) << "Fetching albums...";
234 Albums->Window::Refresh();
235 Mpd.GetList(artists, Config.media_lib_primary_tag);
236 for (MPD::TagList::iterator i = artists.begin(); i != artists.end(); ++i)
238 if (i->empty())
239 continue;
240 MPD::TagList albums;
241 Mpd.StartFieldSearch(MPD_TAG_ALBUM);
242 Mpd.AddSearch(Config.media_lib_primary_tag, *i);
243 Mpd.CommitSearch(albums);
244 for (MPD::TagList::iterator j = albums.begin(); j != albums.end(); ++j)
246 if (Config.media_library_display_date)
248 if (Config.media_lib_primary_tag != MPD_TAG_DATE)
250 MPD::TagList years;
251 Mpd.StartFieldSearch(MPD_TAG_DATE);
252 Mpd.AddSearch(Config.media_lib_primary_tag, *i);
253 Mpd.AddSearch(MPD_TAG_ALBUM, *j);
254 Mpd.CommitSearch(years);
255 utf_to_locale(*i);
256 utf_to_locale(*j);
257 for (MPD::TagList::iterator k = years.begin(); k != years.end(); ++k)
259 utf_to_locale(*k);
260 Albums->AddOption(SearchConstraints(*i, *j, *k));
263 else
265 utf_to_locale(*i);
266 utf_to_locale(*j);
267 Albums->AddOption(SearchConstraints(*i, *j, *i));
270 else
272 utf_to_locale(*i);
273 utf_to_locale(*j);
274 Albums->AddOption(SearchConstraints(*i, *j, ""));
278 if (!Albums->Empty())
279 Albums->Sort<SearchConstraintsSorting>();
280 Albums->Refresh();
283 if (!hasTwoColumns && !Artists->Empty() && w == Albums && Albums->Empty())
285 Albums->HighlightColor(Config.main_highlight_color);
286 Artists->HighlightColor(Config.active_column_color);
287 w = Artists;
290 if ((hasTwoColumns || !Artists->Empty()) && Songs->Empty())
292 Songs->Reset();
293 MPD::SongList list;
295 Mpd.StartSearch(1);
296 Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(hasTwoColumns ? Albums->Current().Artist : Artists->Current()));
297 if (Albums->Empty()) // left for compatibility with <mpd-0.14
299 *Albums << XY(0, 0) << "No albums found.";
300 Albums->Window::Refresh();
302 else
304 Mpd.AddSearch(MPD_TAG_ALBUM, locale_to_utf_cpy(Albums->Current().Album));
305 if (Config.media_library_display_date)
306 Mpd.AddSearch(MPD_TAG_DATE, locale_to_utf_cpy(Albums->Current().Year));
308 Mpd.CommitSearch(list);
310 sort(list.begin(), list.end(), SortSongsByTrack);
311 bool bold = 0;
313 for (MPD::SongList::const_iterator it = list.begin(); it != list.end(); ++it)
315 for (size_t j = 0; j < myPlaylist->Items->Size(); ++j)
317 if ((*it)->GetHash() == myPlaylist->Items->at(j).GetHash())
319 bold = 1;
320 break;
323 Songs->AddOption(**it, bold);
324 bold = 0;
326 FreeSongList(list);
327 Songs->Window::Clear();
328 Songs->Refresh();
332 void MediaLibrary::SpacePressed()
334 if (Config.space_selects)
336 if (w == Artists)
338 Artists->Select(Artists->Choice(), !Artists->isSelected());
339 Albums->Clear();
341 else if (w == Albums)
343 Albums->Select(Albums->Choice(), !Albums->isSelected());
344 Songs->Clear();
346 else if (w == Songs)
347 Songs->Select(Songs->Choice(), !Songs->isSelected());
348 w->Scroll(wDown);
350 else
351 AddToPlaylist(0);
354 void MediaLibrary::MouseButtonPressed(MEVENT me)
356 if (!Artists->Empty() && Artists->hasCoords(me.x, me.y))
358 if (w != Artists)
360 PrevColumn();
361 PrevColumn();
363 if (size_t(me.y) < Artists->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
365 Artists->Goto(me.y);
366 if (me.bstate & BUTTON3_PRESSED)
368 size_t pos = Artists->Choice();
369 SpacePressed();
370 if (pos < Artists->Size()-1)
371 Artists->Scroll(wUp);
374 else
375 Screen<Window>::MouseButtonPressed(me);
376 Albums->Clear();
377 Songs->Clear();
379 else if (!Albums->Empty() && Albums->hasCoords(me.x, me.y))
381 if (w != Albums)
382 w == Artists ? NextColumn() : PrevColumn();
383 if (size_t(me.y) < Albums->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
385 Albums->Goto(me.y);
386 if (me.bstate & BUTTON3_PRESSED)
388 size_t pos = Albums->Choice();
389 SpacePressed();
390 if (pos < Albums->Size()-1)
391 Albums->Scroll(wUp);
394 else
395 Screen<Window>::MouseButtonPressed(me);
396 Songs->Clear();
398 else if (!Songs->Empty() && Songs->hasCoords(me.x, me.y))
400 if (w != Songs)
402 NextColumn();
403 NextColumn();
405 if (size_t(me.y) < Songs->Size() && (me.bstate & (BUTTON1_PRESSED | BUTTON3_PRESSED)))
407 Songs->Goto(me.y);
408 if (me.bstate & BUTTON1_PRESSED)
410 size_t pos = Songs->Choice();
411 SpacePressed();
412 if (pos < Songs->Size()-1)
413 Songs->Scroll(wUp);
415 else
416 EnterPressed();
418 else
419 Screen<Window>::MouseButtonPressed(me);
423 MPD::Song *MediaLibrary::CurrentSong()
425 return w == Songs && !Songs->Empty() ? &Songs->Current() : 0;
428 List *MediaLibrary::GetList()
430 if (w == Artists)
431 return Artists;
432 else if (w == Albums)
433 return Albums;
434 else if (w == Songs)
435 return Songs;
436 else // silence compiler
437 return 0;
440 void MediaLibrary::ReverseSelection()
442 if (w == Artists)
443 Artists->ReverseSelection();
444 else if (w == Albums)
445 Albums->ReverseSelection();
446 else if (w == Songs)
447 Songs->ReverseSelection();
450 void MediaLibrary::GetSelectedSongs(MPD::SongList &v)
452 std::vector<size_t> selected;
453 if (w == Artists && !Artists->Empty())
455 Artists->GetSelected(selected);
456 if (selected.empty())
457 selected.push_back(Artists->Choice());
458 for (std::vector<size_t>::const_iterator it = selected.begin(); it != selected.end(); ++it)
460 MPD::SongList list;
461 Mpd.StartSearch(1);
462 Mpd.AddSearch(Config.media_lib_primary_tag, locale_to_utf_cpy(Artists->at(*it)));
463 Mpd.CommitSearch(list);
464 for (MPD::SongList::const_iterator sIt = list.begin(); sIt != list.end(); ++sIt)
465 v.push_back(new MPD::Song(**sIt));
466 FreeSongList(list);
469 else if (w == Albums && !Albums->Empty())
471 Albums->GetSelected(selected);
472 if (selected.empty())
474 // shortcut via the existing song list in right column
475 if (v.empty())
476 v.reserve(Songs->Size());
477 for (size_t i = 0; i < Songs->Size(); ++i)
478 v.push_back(new MPD::Song((*Songs)[i]));
480 else
482 for (std::vector<size_t>::const_iterator it = selected.begin(); it != selected.end(); ++it)
484 MPD::SongList list;
485 Mpd.StartSearch(1);
486 Mpd.AddSearch(Config.media_lib_primary_tag, hasTwoColumns
487 ? Albums->at(*it).Artist
488 : locale_to_utf_cpy(Artists->Current()));
489 Mpd.AddSearch(MPD_TAG_ALBUM, Albums->at(*it).Album);
490 Mpd.AddSearch(MPD_TAG_DATE, Albums->at(*it).Year);
491 Mpd.CommitSearch(list);
492 for (MPD::SongList::const_iterator sIt = list.begin(); sIt != list.end(); ++sIt)
493 v.push_back(new MPD::Song(**sIt));
494 FreeSongList(list);
498 else if (w == Songs && !Songs->Empty())
500 Songs->GetSelected(selected);
501 if (selected.empty())
502 selected.push_back(Songs->Choice());
503 for (std::vector<size_t>::const_iterator it = selected.begin(); it != selected.end(); ++it)
504 v.push_back(new MPD::Song(Songs->at(*it)));
508 void MediaLibrary::ApplyFilter(const std::string &s)
510 GetList()->ApplyFilter(s, 0, REG_ICASE | Config.regex_type);
513 void MediaLibrary::NextColumn()
515 if (w == Artists)
517 if (!hasTwoColumns && Songs->Empty())
518 return;
519 Artists->HighlightColor(Config.main_highlight_color);
520 w->Refresh();
521 w = Albums;
522 Albums->HighlightColor(Config.active_column_color);
523 if (!Albums->Empty())
524 return;
526 if (w == Albums && !Songs->Empty())
528 Albums->HighlightColor(Config.main_highlight_color);
529 w->Refresh();
530 w = Songs;
531 Songs->HighlightColor(Config.active_column_color);
535 void MediaLibrary::PrevColumn()
537 if (w == Songs)
539 Songs->HighlightColor(Config.main_highlight_color);
540 w->Refresh();
541 w = Albums;
542 Albums->HighlightColor(Config.active_column_color);
543 if (!Albums->Empty())
544 return;
546 if (w == Albums && !hasTwoColumns)
548 Albums->HighlightColor(Config.main_highlight_color);
549 w->Refresh();
550 w = Artists;
551 Artists->HighlightColor(Config.active_column_color);
555 void MediaLibrary::LocateSong(const MPD::Song &s)
557 std::string primary_tag;
558 switch (Config.media_lib_primary_tag)
560 case MPD_TAG_ARTIST:
561 primary_tag = s.GetArtist();
562 break;
563 case MPD_TAG_DATE:
564 primary_tag = s.GetDate();
565 break;
566 case MPD_TAG_GENRE:
567 primary_tag = s.GetGenre();
568 break;
569 case MPD_TAG_COMPOSER:
570 primary_tag = s.GetComposer();
571 break;
572 case MPD_TAG_PERFORMER:
573 primary_tag = s.GetPerformer();
574 break;
575 default:
576 ShowMessage("Invalid tag type in left column of the media library");
577 return;
579 if (primary_tag.empty())
581 std::string item_type = IntoStr(Config.media_lib_primary_tag);
582 ToLower(item_type);
583 ShowMessage("Can't jump to media library because the song has no %s tag set.", item_type.c_str());
584 return;
587 if (myScreen != this)
588 SwitchTo();
589 Statusbar() << "Jumping to song...";
590 Global::wFooter->Refresh();
592 if (!hasTwoColumns)
594 if (Artists->Empty())
595 Update();
596 if (primary_tag != Artists->Current())
598 for (size_t i = 0; i < Artists->Size(); ++i)
600 if (primary_tag == (*Artists)[i])
602 Artists->Highlight(i);
603 Albums->Clear();
604 break;
610 if (Albums->Empty())
611 Update();
613 std::string album = s.GetAlbum();
614 std::string date = s.GetDate();
615 if ((hasTwoColumns && Albums->Current().Artist != primary_tag)
616 || album != Albums->Current().Album
617 || date != Albums->Current().Year)
619 for (size_t i = 0; i < Albums->Size(); ++i)
621 if ((!hasTwoColumns || (*Albums)[i].Artist == primary_tag)
622 && album == (*Albums)[i].Album
623 && date == (*Albums)[i].Year)
625 Albums->Highlight(i);
626 Songs->Clear();
627 break;
632 if (Songs->Empty())
633 Update();
635 std::string song = s.GetTitle();
636 if (song != Songs->Current().GetTitle())
638 for (size_t i = 0; i < Songs->Size(); ++i)
640 if (song == (*Songs)[i].GetTitle())
642 Songs->Highlight(i);
643 break;
648 Artists->HighlightColor(Config.main_highlight_color);
649 Albums->HighlightColor(Config.main_highlight_color);
650 Songs->HighlightColor(Config.active_column_color);
651 w = Songs;
652 Refresh();
655 void MediaLibrary::AddToPlaylist(bool add_n_play)
657 if (w == Songs && !Songs->Empty())
658 Songs->Bold(Songs->Choice(), myPlaylist->Add(Songs->Current(), Songs->isBold(), add_n_play));
659 else
661 MPD::SongList list;
662 GetSelectedSongs(list);
664 if (myPlaylist->Add(list, add_n_play))
666 if (!Artists->Empty() && w == Artists)
668 std::string tag_type = IntoStr(Config.media_lib_primary_tag);
669 ToLower(tag_type);
670 ShowMessage("Adding songs of %s \"%s\"", tag_type.c_str(), Artists->Current().c_str());
672 else if (w == Albums)
673 ShowMessage("Adding songs from album \"%s\"", Albums->Current().Album.c_str());
677 if (!add_n_play)
679 w->Scroll(wDown);
680 if (w == Artists)
682 Albums->Clear();
683 Songs->Clear();
685 else if (w == Albums)
686 Songs->Clear();
690 std::string MediaLibrary::SongToString(const MPD::Song &s, void *)
692 return s.toString(Config.song_library_format);
695 std::string MediaLibrary::AlbumToString(const SearchConstraints &sc, void *ptr)
697 std::string result;
698 if (!sc.Artist.empty())
699 (result += sc.Artist) += " - ";
700 if ((!static_cast<MediaLibrary *>(ptr)->hasTwoColumns || Config.media_lib_primary_tag != MPD_TAG_DATE) && !sc.Year.empty())
701 ((result += "(") += sc.Year) += ") ";
702 result += sc.Album.empty() ? "<no album>" : sc.Album;
703 return result;
706 void MediaLibrary::DisplayAlbums(const SearchConstraints &sc, void *, Menu<SearchConstraints> *menu)
708 *menu << AlbumToString(sc, 0);
711 bool MediaLibrary::SearchConstraintsSorting::operator()(const SearchConstraints &a, const SearchConstraints &b) const
713 int result;
714 CaseInsensitiveStringComparison cmp;
715 if (!a.Artist.empty() || b.Artist.empty())
717 result = cmp(a.Artist, b.Artist);
718 if (result != 0)
719 return result < 0;
721 result = cmp(a.Year, b.Year);
722 return (result == 0 ? cmp(a.Album, b.Album) : result) < 0;
725 bool MediaLibrary::SortSongsByTrack(MPD::Song *a, MPD::Song *b)
727 if (a->GetDisc() == b->GetDisc())
728 return StrToInt(a->GetTrack()) < StrToInt(b->GetTrack());
729 else
730 return StrToInt(a->GetDisc()) < StrToInt(b->GetDisc());