1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
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. *
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. *
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 <boost/bind.hpp>
22 #include <boost/date_time/posix_time/posix_time.hpp>
23 #include <boost/locale/conversion.hpp>
32 #include "media_library.h"
35 #include "regex_filter.h"
37 #include "statusbar.h"
38 #include "utility/comparators.h"
39 #include "utility/type_conversions.h"
41 #include "screen_switcher.h"
43 using Global::MainHeight
;
44 using Global::MainStartY
;
45 using Global::myScreen
;
47 MediaLibrary
*myLibrary
;
52 size_t itsLeftColStartX
;
53 size_t itsLeftColWidth
;
54 size_t itsMiddleColWidth
;
55 size_t itsMiddleColStartX
;
56 size_t itsRightColWidth
;
57 size_t itsRightColStartX
;
59 typedef MediaLibrary::AlbumEntry AlbumEntry
;
61 template <typename FunT
>
62 void withSongsFromAlbum(const AlbumEntry
&album
, FunT
&&f
)
64 Mpd
.StartSearch(true);
65 Mpd
.AddSearch(Config
.media_lib_primary_tag
, album
.entry().tag());
66 if (!album
.isAllTracksEntry())
68 Mpd
.AddSearch(MPD_TAG_ALBUM
, album
.entry().album());
69 Mpd
.AddSearch(MPD_TAG_DATE
, album
.entry().date());
71 Mpd
.CommitSearchSongs(std::forward
<FunT
>(f
));
74 std::string
AlbumToString(const AlbumEntry
&ae
);
75 std::string
SongToString(const MPD::Song
&s
);
77 bool TagEntryMatcher(const boost::regex
&rx
, const MediaLibrary::PrimaryTag
&tagmtime
);
78 bool AlbumEntryMatcher(const boost::regex
&rx
, const NC::Menu
<AlbumEntry
>::Item
&item
, bool filter
);
79 bool SongEntryMatcher(const boost::regex
&rx
, const MPD::Song
&s
);
82 typedef NC::Menu
<MPD::Song
>::Item SongItem
;
84 static const std::array
<MPD::Song::GetFunction
, 3> GetFuns
;
86 LocaleStringComparison m_cmp
;
87 std::ptrdiff_t m_offset
;
90 SortSongs(bool disc_only
)
91 : m_cmp(std::locale(), Config
.ignore_leading_the
), m_offset(disc_only
? 2 : 0) { }
93 bool operator()(const SongItem
&a
, const SongItem
&b
) {
94 return (*this)(a
.value(), b
.value());
96 bool operator()(const MPD::Song
&a
, const MPD::Song
&b
) {
97 for (auto get
= GetFuns
.begin()+m_offset
; get
!= GetFuns
.end(); ++get
) {
98 int ret
= m_cmp(a
.getTags(*get
, Config
.tags_separator
),
99 b
.getTags(*get
, Config
.tags_separator
));
103 return a
.getTrack() < b
.getTrack();
107 const std::array
<MPD::Song::GetFunction
, 3> SortSongs::GetFuns
= {{
109 &MPD::Song::getAlbum
,
113 class SortAlbumEntries
{
114 typedef MediaLibrary::Album Album
;
116 LocaleStringComparison m_cmp
;
119 SortAlbumEntries() : m_cmp(std::locale(), Config
.ignore_leading_the
) { }
121 bool operator()(const AlbumEntry
&a
, const AlbumEntry
&b
) const {
122 return (*this)(a
.entry(), b
.entry());
125 bool operator()(const Album
&a
, const Album
&b
) const {
126 if (Config
.media_library_sort_by_mtime
)
127 return a
.mtime() > b
.mtime();
131 result
= m_cmp(a
.tag(), b
.tag());
134 result
= m_cmp(a
.date(), b
.date());
137 return m_cmp(a
.album(), b
.album()) < 0;
142 class SortPrimaryTags
{
143 typedef MediaLibrary::PrimaryTag PrimaryTag
;
145 LocaleStringComparison m_cmp
;
148 SortPrimaryTags() : m_cmp(std::locale(), Config
.ignore_leading_the
) { }
150 bool operator()(const PrimaryTag
&a
, const PrimaryTag
&b
) const {
151 if (Config
.media_library_sort_by_mtime
)
152 return a
.mtime() > b
.mtime();
154 return m_cmp(a
.tag(), b
.tag()) < 0;
160 MediaLibrary::MediaLibrary()
161 : m_timer(boost::posix_time::from_time_t(0))
162 , m_window_timeout(Config
.data_fetching_delay
? 250 : 500)
163 , m_fetching_delay(boost::posix_time::milliseconds(Config
.data_fetching_delay
? 250 : -1))
166 itsLeftColWidth
= COLS
/3-1;
167 itsMiddleColWidth
= COLS
/3;
168 itsMiddleColStartX
= itsLeftColWidth
+1;
169 itsRightColWidth
= COLS
-COLS
/3*2-1;
170 itsRightColStartX
= itsLeftColWidth
+itsMiddleColWidth
+2;
172 Tags
= NC::Menu
<PrimaryTag
>(0, MainStartY
, itsLeftColWidth
, MainHeight
, Config
.titles_visibility
? tagTypeToString(Config
.media_lib_primary_tag
) + "s" : "", Config
.main_color
, NC::Border::None
);
173 Tags
.setHighlightColor(Config
.active_column_color
);
174 Tags
.cyclicScrolling(Config
.use_cyclic_scrolling
);
175 Tags
.centeredCursor(Config
.centered_cursor
);
176 Tags
.setSelectedPrefix(Config
.selected_item_prefix
);
177 Tags
.setSelectedSuffix(Config
.selected_item_suffix
);
178 Tags
.setItemDisplayer([](NC::Menu
<PrimaryTag
> &menu
) {
179 const std::string
&tag
= menu
.drawn()->value().tag();
181 menu
<< Config
.empty_tag
;
183 menu
<< Charset::utf8ToLocale(tag
);
186 Albums
= NC::Menu
<AlbumEntry
>(itsMiddleColStartX
, MainStartY
, itsMiddleColWidth
, MainHeight
, Config
.titles_visibility
? "Albums" : "", Config
.main_color
, NC::Border::None
);
187 Albums
.setHighlightColor(Config
.main_highlight_color
);
188 Albums
.cyclicScrolling(Config
.use_cyclic_scrolling
);
189 Albums
.centeredCursor(Config
.centered_cursor
);
190 Albums
.setSelectedPrefix(Config
.selected_item_prefix
);
191 Albums
.setSelectedSuffix(Config
.selected_item_suffix
);
192 Albums
.setItemDisplayer([](NC::Menu
<AlbumEntry
> &menu
) {
193 menu
<< Charset::utf8ToLocale(AlbumToString(menu
.drawn()->value()));
196 Songs
= NC::Menu
<MPD::Song
>(itsRightColStartX
, MainStartY
, itsRightColWidth
, MainHeight
, Config
.titles_visibility
? "Songs" : "", Config
.main_color
, NC::Border::None
);
197 Songs
.setHighlightColor(Config
.main_highlight_color
);
198 Songs
.cyclicScrolling(Config
.use_cyclic_scrolling
);
199 Songs
.centeredCursor(Config
.centered_cursor
);
200 Songs
.setSelectedPrefix(Config
.selected_item_prefix
);
201 Songs
.setSelectedSuffix(Config
.selected_item_suffix
);
202 Songs
.setItemDisplayer(boost::bind(Display::Songs
, _1
, songsProxyList(), Config
.song_library_format
));
207 void MediaLibrary::resize()
209 size_t x_offset
, width
;
210 getWindowResizeParams(x_offset
, width
);
213 itsLeftColStartX
= x_offset
;
214 itsLeftColWidth
= width
/3-1;
215 itsMiddleColStartX
= itsLeftColStartX
+itsLeftColWidth
+1;
216 itsMiddleColWidth
= width
/3;
217 itsRightColStartX
= itsMiddleColStartX
+itsMiddleColWidth
+1;
218 itsRightColWidth
= width
-width
/3*2-1;
222 itsMiddleColStartX
= x_offset
;
223 itsMiddleColWidth
= width
/2;
224 itsRightColStartX
= x_offset
+itsMiddleColWidth
+1;
225 itsRightColWidth
= width
-itsMiddleColWidth
-1;
228 Tags
.resize(itsLeftColWidth
, MainHeight
);
229 Albums
.resize(itsMiddleColWidth
, MainHeight
);
230 Songs
.resize(itsRightColWidth
, MainHeight
);
232 Tags
.moveTo(itsLeftColStartX
, MainStartY
);
233 Albums
.moveTo(itsMiddleColStartX
, MainStartY
);
234 Songs
.moveTo(itsRightColStartX
, MainStartY
);
239 void MediaLibrary::refresh()
242 drawSeparator(itsMiddleColStartX
-1);
244 drawSeparator(itsRightColStartX
-1);
248 Albums
<< NC::XY(0, 0) << "No albums found.";
249 Albums
.Window::refresh();
253 void MediaLibrary::switchTo()
255 SwitchTo::execute(this);
256 markSongsInPlaylist(songsProxyList());
261 std::wstring
MediaLibrary::title()
263 return L
"Media library";
266 void MediaLibrary::update()
270 if (Albums
.reallyEmpty() || m_albums_update_request
)
272 Albums
.clearSearchResults();
273 m_albums_update_request
= false;
274 std::map
<std::tuple
<std::string
, std::string
, std::string
>, time_t> albums
;
275 Mpd
.GetDirectoryRecursive("/", [&albums
](MPD::Song s
) {
277 std::string tag
= s
.get(Config
.media_lib_primary_tag
, idx
);
280 auto key
= std::make_tuple(tag
, s
.getAlbum(), s
.getDate());
281 auto it
= albums
.find(key
);
282 if (it
== albums
.end())
283 albums
[key
] = s
.getMTime();
285 it
->second
= s
.getMTime();
287 while (!(tag
= s
.get(Config
.media_lib_primary_tag
, ++idx
)).empty());
289 withUnfilteredMenuReapplyFilter(Albums
, [this, &albums
]() {
291 for (auto it
= albums
.begin(); it
!= albums
.end(); ++it
, ++idx
)
293 auto &&entry
= AlbumEntry(Album(
294 std::move(std::get
<0>(it
->first
)),
295 std::move(std::get
<1>(it
->first
)),
296 std::move(std::get
<2>(it
->first
)),
298 if (idx
< Albums
.size())
299 Albums
[idx
].value() = entry
;
301 Albums
.addItem(entry
);
303 if (idx
< Albums
.size())
304 Albums
.resizeList(idx
);
305 std::sort(Albums
.beginV(), Albums
.endV(), SortAlbumEntries());
312 if (Tags
.reallyEmpty() || m_tags_update_request
)
314 Tags
.clearSearchResults();
315 m_tags_update_request
= false;
316 std::map
<std::string
, time_t> tags
;
317 if (Config
.media_library_sort_by_mtime
)
319 Mpd
.GetDirectoryRecursive("/", [&tags
](MPD::Song s
) {
321 std::string tag
= s
.get(Config
.media_lib_primary_tag
, idx
);
324 auto it
= tags
.find(tag
);
325 if (it
== tags
.end())
326 tags
[tag
] = s
.getMTime();
328 it
->second
= std::max(it
->second
, s
.getMTime());
330 while (!(tag
= s
.get(Config
.media_lib_primary_tag
, ++idx
)).empty());
335 Mpd
.GetList(Config
.media_lib_primary_tag
, [&tags
](std::string tag
) {
339 withUnfilteredMenuReapplyFilter(Tags
, [this, &tags
]() {
341 for (auto it
= tags
.begin(); it
!= tags
.end(); ++it
, ++idx
)
343 auto &&tag
= PrimaryTag(std::move(it
->first
), it
->second
);
344 if (idx
< Tags
.size())
345 Tags
[idx
].value() = tag
;
349 if (idx
< Tags
.size())
350 Tags
.resizeList(idx
);
351 std::sort(Tags
.beginV(), Tags
.endV(), SortPrimaryTags());
357 && ((Albums
.reallyEmpty() && Global::Timer
- m_timer
> m_fetching_delay
) || m_albums_update_request
)
360 Albums
.clearSearchResults();
361 m_albums_update_request
= false;
362 auto &primary_tag
= Tags
.current().value().tag();
363 Mpd
.StartSearch(true);
364 Mpd
.AddSearch(Config
.media_lib_primary_tag
, primary_tag
);
365 std::map
<std::tuple
<std::string
, std::string
>, time_t> albums
;
366 Mpd
.CommitSearchSongs([&albums
](MPD::Song s
) {
367 auto key
= std::make_tuple(s
.getAlbum(), s
.getDate());
368 auto it
= albums
.find(key
);
369 if (it
== albums
.end())
370 albums
[key
] = s
.getMTime();
372 it
->second
= s
.getMTime();
374 withUnfilteredMenuReapplyFilter(Albums
, [this, &albums
, &primary_tag
]() {
376 for (auto it
= albums
.begin(); it
!= albums
.end(); ++it
, ++idx
)
378 auto &&entry
= AlbumEntry(Album(
380 std::move(std::get
<0>(it
->first
)),
381 std::move(std::get
<1>(it
->first
)),
383 if (idx
< Albums
.size())
385 Albums
[idx
].value() = entry
;
386 Albums
[idx
].setSeparator(false);
389 Albums
.addItem(entry
);
391 if (idx
< Albums
.size())
392 Albums
.resizeList(idx
);
393 std::sort(Albums
.beginV(), Albums
.endV(), SortAlbumEntries());
394 if (albums
.size() > 1)
396 Albums
.addSeparator();
397 Albums
.addItem(AlbumEntry::mkAllTracksEntry(primary_tag
));
405 && ((Songs
.reallyEmpty() && Global::Timer
- m_timer
> m_fetching_delay
) || m_songs_update_request
)
408 Songs
.clearSearchResults();
409 m_songs_update_request
= false;
410 auto &album
= Albums
.current().value();
411 Mpd
.StartSearch(true);
412 Mpd
.AddSearch(Config
.media_lib_primary_tag
, album
.entry().tag());
413 if (!album
.isAllTracksEntry())
415 Mpd
.AddSearch(MPD_TAG_ALBUM
, album
.entry().album());
416 Mpd
.AddSearch(MPD_TAG_DATE
, album
.entry().date());
418 withUnfilteredMenuReapplyFilter(Songs
, [this, &album
]() {
420 Mpd
.CommitSearchSongs([this, &idx
](MPD::Song s
) {
421 bool is_playlist
= myPlaylist
->checkForSong(s
);
422 if (idx
< Songs
.size())
424 Songs
[idx
].value() = s
;
425 Songs
[idx
].setBold(is_playlist
);
428 Songs
.addItem(s
, is_playlist
);
431 if (idx
< Songs
.size())
432 Songs
.resizeList(idx
);
433 std::sort(Songs
.begin(), Songs
.end(), SortSongs(!album
.isAllTracksEntry()));
439 int MediaLibrary::windowTimeout()
441 if (Albums
.reallyEmpty() || Songs
.reallyEmpty())
442 return m_window_timeout
;
444 return Screen
<WindowType
>::windowTimeout();
447 void MediaLibrary::enterPressed()
452 void MediaLibrary::spacePressed()
454 if (Config
.space_selects
)
456 if (isActiveWindow(Tags
))
458 size_t idx
= Tags
.choice();
459 Tags
[idx
].setSelected(!Tags
[idx
].isSelected());
460 Tags
.scroll(NC::Scroll::Down
);
464 else if (isActiveWindow(Albums
))
466 if (!Albums
.current().value().isAllTracksEntry())
468 size_t idx
= Albums
.choice();
469 Albums
[idx
].setSelected(!Albums
[idx
].isSelected());
470 Albums
.scroll(NC::Scroll::Down
);
474 else if (isActiveWindow(Songs
))
476 size_t idx
= Songs
.choice();
477 Songs
[idx
].setSelected(!Songs
[idx
].isSelected());
478 Songs
.scroll(NC::Scroll::Down
);
482 AddToPlaylist(false);
485 void MediaLibrary::mouseButtonPressed(MEVENT me
)
487 auto tryNextColumn
= [this]() -> bool {
489 if (!isActiveWindow(Songs
))
491 if (nextColumnAvailable())
498 auto tryPreviousColumn
= [this]() -> bool {
500 if (!isActiveWindow(Tags
))
502 if (previousColumnAvailable())
509 if (!Tags
.empty() && Tags
.hasCoords(me
.x
, me
.y
))
511 if (!tryPreviousColumn() || !tryPreviousColumn())
513 if (size_t(me
.y
) < Tags
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
516 if (me
.bstate
& BUTTON3_PRESSED
)
518 size_t pos
= Tags
.choice();
520 if (pos
< Tags
.size()-1)
521 Tags
.scroll(NC::Scroll::Up
);
525 Screen
<WindowType
>::mouseButtonPressed(me
);
529 else if (!Albums
.empty() && Albums
.hasCoords(me
.x
, me
.y
))
531 if (!isActiveWindow(Albums
))
534 if (isActiveWindow(Tags
))
535 success
= tryNextColumn();
537 success
= tryPreviousColumn();
541 if (size_t(me
.y
) < Albums
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
544 if (me
.bstate
& BUTTON3_PRESSED
)
546 size_t pos
= Albums
.choice();
548 if (pos
< Albums
.size()-1)
549 Albums
.scroll(NC::Scroll::Up
);
553 Screen
<WindowType
>::mouseButtonPressed(me
);
556 else if (!Songs
.empty() && Songs
.hasCoords(me
.x
, me
.y
))
558 if (!tryNextColumn() || !tryNextColumn())
560 if (size_t(me
.y
) < Songs
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
563 if (me
.bstate
& BUTTON1_PRESSED
)
565 size_t pos
= Songs
.choice();
567 if (pos
< Songs
.size()-1)
568 Songs
.scroll(NC::Scroll::Up
);
574 Screen
<WindowType
>::mouseButtonPressed(me
);
578 /***********************************************************************/
580 bool MediaLibrary::allowsFiltering()
585 std::string
MediaLibrary::currentFilter()
588 if (isActiveWindow(Tags
))
589 filter
= RegexFilter
<PrimaryTag
>::currentFilter(Tags
);
590 else if (isActiveWindow(Albums
))
591 filter
= RegexItemFilter
<AlbumEntry
>::currentFilter(Albums
);
592 else if (isActiveWindow(Songs
))
593 filter
= RegexFilter
<MPD::Song
>::currentFilter(Songs
);
597 void MediaLibrary::applyFilter(const std::string
&filter
)
601 if (isActiveWindow(Tags
))
604 Tags
.clearFilterResults();
606 else if (isActiveWindow(Albums
))
608 Albums
.clearFilter();
609 Albums
.clearFilterResults();
611 else if (isActiveWindow(Songs
))
614 Songs
.clearFilterResults();
620 if (isActiveWindow(Tags
))
623 auto rx
= RegexFilter
<PrimaryTag
>(
624 boost::regex(filter
, Config
.regex_type
), TagEntryMatcher
);
625 Tags
.filter(Tags
.begin(), Tags
.end(), rx
);
627 else if (isActiveWindow(Albums
))
630 auto fun
= boost::bind(AlbumEntryMatcher
, _1
, _2
, true);
631 auto rx
= RegexItemFilter
<AlbumEntry
>(
632 boost::regex(filter
, Config
.regex_type
), fun
);
633 Albums
.filter(Albums
.begin(), Albums
.end(), rx
);
635 else if (isActiveWindow(Songs
))
638 auto rx
= RegexFilter
<MPD::Song
>(
639 boost::regex(filter
, Config
.regex_type
), SongEntryMatcher
);
640 Songs
.filter(Songs
.begin(), Songs
.end(), rx
);
643 catch (boost::bad_expression
&) { }
646 /***********************************************************************/
648 bool MediaLibrary::allowsSearching()
653 bool MediaLibrary::search(const std::string
&constraint
)
655 if (constraint
.empty())
657 if (isActiveWindow(Tags
))
658 Tags
.clearSearchResults();
659 else if (isActiveWindow(Albums
))
660 Albums
.clearSearchResults();
661 else if (isActiveWindow(Songs
))
662 Songs
.clearSearchResults();
668 if (isActiveWindow(Tags
))
670 auto rx
= RegexFilter
<PrimaryTag
>(
671 boost::regex(constraint
, Config
.regex_type
), TagEntryMatcher
);
672 result
= Tags
.search(Tags
.begin(), Tags
.end(), rx
);
674 else if (isActiveWindow(Albums
))
676 auto fun
= boost::bind(AlbumEntryMatcher
, _1
, _2
, false);
677 auto rx
= RegexItemFilter
<AlbumEntry
>(
678 boost::regex(constraint
, Config
.regex_type
), fun
);
679 result
= Albums
.search(Albums
.begin(), Albums
.end(), rx
);
681 else if (isActiveWindow(Songs
))
683 auto rx
= RegexFilter
<MPD::Song
>(
684 boost::regex(constraint
, Config
.regex_type
), SongEntryMatcher
);
685 result
= Songs
.search(Songs
.begin(), Songs
.end(), rx
);
689 catch (boost::bad_expression
&)
695 void MediaLibrary::nextFound(bool wrap
)
697 if (isActiveWindow(Tags
))
698 Tags
.nextFound(wrap
);
699 else if (isActiveWindow(Albums
))
700 Albums
.nextFound(wrap
);
701 else if (isActiveWindow(Songs
))
702 Songs
.nextFound(wrap
);
705 void MediaLibrary::prevFound(bool wrap
)
707 if (isActiveWindow(Tags
))
708 Tags
.prevFound(wrap
);
709 else if (isActiveWindow(Albums
))
710 Albums
.prevFound(wrap
);
711 else if (isActiveWindow(Songs
))
712 Songs
.prevFound(wrap
);
715 /***********************************************************************/
717 ProxySongList
MediaLibrary::proxySongList()
719 auto ptr
= ProxySongList();
720 if (isActiveWindow(Songs
))
721 ptr
= songsProxyList();
725 bool MediaLibrary::allowsSelection()
730 void MediaLibrary::reverseSelection()
732 if (isActiveWindow(Tags
))
733 reverseSelectionHelper(Tags
.begin(), Tags
.end());
734 else if (isActiveWindow(Albums
))
737 if (Albums
.size() > 1)
738 reverseSelectionHelper(Albums
.begin(), Albums
.end()-2);
740 reverseSelectionHelper(Albums
.begin(), Albums
.end());
742 else if (isActiveWindow(Songs
))
743 reverseSelectionHelper(Songs
.begin(), Songs
.end());
746 MPD::SongList
MediaLibrary::getSelectedSongs()
748 MPD::SongList result
;
749 if (isActiveWindow(Tags
))
751 auto tag_handler
= [&result
](const std::string
&tag
) {
752 Mpd
.StartSearch(true);
753 Mpd
.AddSearch(Config
.media_lib_primary_tag
, tag
);
754 Mpd
.CommitSearchSongs(vectorMoveInserter(result
));
756 bool any_selected
= false;
762 tag_handler(e
.value().tag());
765 // if no item is selected, add current one
766 if (!any_selected
&& !Tags
.empty())
767 tag_handler(Tags
.current().value().tag());
769 else if (isActiveWindow(Albums
))
771 bool any_selected
= false;
772 for (auto it
= Albums
.begin(); it
!= Albums
.end() && !it
->isSeparator(); ++it
)
774 if (it
->isSelected())
777 auto &sc
= it
->value();
778 Mpd
.StartSearch(true);
780 Mpd
.AddSearch(Config
.media_lib_primary_tag
, sc
.entry().tag());
782 Mpd
.AddSearch(Config
.media_lib_primary_tag
,
783 Tags
.current().value().tag());
784 Mpd
.AddSearch(MPD_TAG_ALBUM
, sc
.entry().album());
785 Mpd
.AddSearch(MPD_TAG_DATE
, sc
.entry().date());
786 size_t begin
= result
.size();
787 Mpd
.CommitSearchSongs(vectorMoveInserter(result
));
788 std::sort(result
.begin()+begin
, result
.end(), SortSongs(false));
791 // if no item is selected, add songs from right column
792 if (!any_selected
&& !Albums
.empty())
794 size_t begin
= result
.size();
795 withSongsFromAlbum(Albums
.current().value().entry(), vectorMoveInserter(result
));
796 std::sort(result
.begin()+begin
, result
.end(), SortSongs(false));
799 else if (isActiveWindow(Songs
))
801 for (auto it
= Songs
.begin(); it
!= Songs
.end(); ++it
)
802 if (it
->isSelected())
803 result
.push_back(it
->value());
804 // if no item is selected, add current one
805 if (result
.empty() && !Songs
.empty())
806 result
.push_back(Songs
.current().value());
811 /***********************************************************************/
813 bool MediaLibrary::previousColumnAvailable()
815 assert(!hasTwoColumns
|| !isActiveWindow(Tags
));
816 if (isActiveWindow(Songs
))
818 if (!Albums
.reallyEmpty() && (hasTwoColumns
|| !Tags
.reallyEmpty()))
821 else if (isActiveWindow(Albums
))
823 if (!hasTwoColumns
&& !Tags
.reallyEmpty())
829 void MediaLibrary::previousColumn()
831 if (isActiveWindow(Songs
))
833 Songs
.setHighlightColor(Config
.main_highlight_color
);
836 Albums
.setHighlightColor(Config
.active_column_color
);
838 else if (isActiveWindow(Albums
) && !hasTwoColumns
)
840 Albums
.setHighlightColor(Config
.main_highlight_color
);
843 Tags
.setHighlightColor(Config
.active_column_color
);
847 bool MediaLibrary::nextColumnAvailable()
849 assert(!hasTwoColumns
|| !isActiveWindow(Tags
));
850 if (isActiveWindow(Tags
))
852 if (!Albums
.reallyEmpty() && !Songs
.reallyEmpty())
855 else if (isActiveWindow(Albums
))
857 if (!Songs
.reallyEmpty())
863 void MediaLibrary::nextColumn()
865 if (isActiveWindow(Tags
))
867 Tags
.setHighlightColor(Config
.main_highlight_color
);
870 Albums
.setHighlightColor(Config
.active_column_color
);
872 else if (isActiveWindow(Albums
))
874 Albums
.setHighlightColor(Config
.main_highlight_color
);
877 Songs
.setHighlightColor(Config
.active_column_color
);
881 /***********************************************************************/
883 void MediaLibrary::updateTimer()
885 m_timer
= Global::Timer
;
888 void MediaLibrary::toggleColumnsMode()
890 hasTwoColumns
= !hasTwoColumns
;
897 if (isActiveWindow(Tags
))
899 if (Config
.titles_visibility
)
901 std::string item_type
= boost::locale::to_lower(
902 tagTypeToString(Config
.media_lib_primary_tag
));
903 std::string and_mtime
= Config
.media_library_sort_by_mtime
? " and mtime" : "";
904 Albums
.setTitle("Albums (sorted by " + item_type
+ and_mtime
+ ")");
908 Albums
.setTitle(Config
.titles_visibility
? "Albums" : "");
912 int MediaLibrary::Columns()
920 ProxySongList
MediaLibrary::songsProxyList()
922 return ProxySongList(Songs
, [](NC::Menu
<MPD::Song
>::Item
&item
) {
923 return &item
.value();
927 void MediaLibrary::toggleSortMode()
929 Config
.media_library_sort_by_mtime
= !Config
.media_library_sort_by_mtime
;
930 Statusbar::printf("Sorting library by: %1%",
931 Config
.media_library_sort_by_mtime
? "modification time" : "name");
934 withUnfilteredMenuReapplyFilter(Albums
, [this]() {
935 std::sort(Albums
.beginV(), Albums
.endV(), SortAlbumEntries());
939 if (Config
.titles_visibility
)
941 std::string item_type
= boost::locale::to_lower(
942 tagTypeToString(Config
.media_lib_primary_tag
));
943 std::string and_mtime
= Config
.media_library_sort_by_mtime
? (" " "and mtime") : "";
944 Albums
.setTitle("Albums (sorted by " + item_type
+ and_mtime
+ ")");
949 withUnfilteredMenuReapplyFilter(Tags
, [this]() {
950 // if we already have modification times,
951 // just resort. otherwise refetch the list.
952 if (!Tags
.empty() && Tags
[0].value().mtime() > 0)
954 std::sort(Tags
.beginV(), Tags
.endV(), SortPrimaryTags());
966 void MediaLibrary::LocateSong(const MPD::Song
&s
)
968 std::string primary_tag
= s
.get(Config
.media_lib_primary_tag
);
969 if (primary_tag
.empty())
971 std::string item_type
= boost::locale::to_lower(
972 tagTypeToString(Config
.media_lib_primary_tag
));
973 Statusbar::printf("Can't use this function because the song has no %s tag", item_type
);
976 if (!s
.isFromDatabase())
978 Statusbar::print("Song is not from the database");
982 if (myScreen
!= this)
984 Statusbar::put() << "Jumping to song...";
985 Global::wFooter
->refresh();
992 if (primary_tag
!= Tags
.current().value().tag())
994 for (size_t i
= 0; i
< Tags
.size(); ++i
)
996 if (primary_tag
== Tags
[i
].value().tag())
1011 std::string album
= s
.getAlbum();
1012 std::string date
= s
.getDate();
1013 if ((hasTwoColumns
&& Albums
.current().value().entry().tag() != primary_tag
)
1014 || album
!= Albums
.current().value().entry().album()
1015 || date
!= Albums
.current().value().entry().date())
1017 for (size_t i
= 0; i
< Albums
.size(); ++i
)
1019 if ((!hasTwoColumns
|| Albums
[i
].value().entry().tag() == primary_tag
)
1020 && album
== Albums
[i
].value().entry().album()
1021 && date
== Albums
[i
].value().entry().date())
1023 Albums
.highlight(i
);
1034 if (s
!= Songs
.current().value())
1036 for (size_t i
= 0; i
< Songs
.size(); ++i
)
1038 if (s
== Songs
[i
].value())
1046 Tags
.setHighlightColor(Config
.main_highlight_color
);
1047 Albums
.setHighlightColor(Config
.main_highlight_color
);
1048 Songs
.setHighlightColor(Config
.active_column_color
);
1053 void MediaLibrary::AddToPlaylist(bool add_n_play
)
1055 if (isActiveWindow(Songs
) && !Songs
.empty())
1056 addSongToPlaylist(Songs
.current().value(), add_n_play
);
1059 if ((!Tags
.empty() && isActiveWindow(Tags
))
1060 || (isActiveWindow(Albums
) && Albums
.current().value().isAllTracksEntry()))
1063 Mpd
.StartSearch(true);
1064 Mpd
.AddSearch(Config
.media_lib_primary_tag
, Tags
.current().value().tag());
1065 Mpd
.CommitSearchSongs(vectorMoveInserter(list
));
1066 bool success
= addSongsToPlaylist(list
.begin(), list
.end(), add_n_play
, -1);
1067 std::string tag_type
= boost::locale::to_lower(
1068 tagTypeToString(Config
.media_lib_primary_tag
));
1069 Statusbar::printf("Songs with %1% \"%2%\" added%3%",
1070 tag_type
, Tags
.current().value().tag(), withErrors(success
)
1073 else if (isActiveWindow(Albums
))
1076 withSongsFromAlbum(Albums
.current().value(), vectorMoveInserter(list
));
1077 bool success
= addSongsToPlaylist(list
.begin(), list
.end(), add_n_play
, -1);
1078 Statusbar::printf("Songs from album \"%1%\" added%2%",
1079 Albums
.current().value().entry().album(), withErrors(success
)
1086 w
->scroll(NC::Scroll::Down
);
1087 if (isActiveWindow(Tags
))
1092 else if (isActiveWindow(Albums
))
1099 std::string
AlbumToString(const AlbumEntry
&ae
)
1102 if (ae
.isAllTracksEntry())
1103 result
= "All tracks";
1108 if (ae
.entry().tag().empty())
1109 result
+= Config
.empty_tag
;
1111 result
+= ae
.entry().tag();
1114 if (Config
.media_lib_primary_tag
!= MPD_TAG_DATE
&& !ae
.entry().date().empty())
1115 result
+= "(" + ae
.entry().date() + ") ";
1116 result
+= ae
.entry().album().empty() ? "<no album>" : ae
.entry().album();
1121 std::string
SongToString(const MPD::Song
&s
)
1123 return s
.toString(Config
.song_library_format
, Config
.tags_separator
);
1126 bool TagEntryMatcher(const boost::regex
&rx
, const MediaLibrary::PrimaryTag
&pt
)
1128 return boost::regex_search(pt
.tag(), rx
);
1131 bool AlbumEntryMatcher(const boost::regex
&rx
, const NC::Menu
<AlbumEntry
>::Item
&item
, bool filter
)
1133 if (item
.isSeparator() || item
.value().isAllTracksEntry())
1135 return boost::regex_search(AlbumToString(item
.value()), rx
);
1138 bool SongEntryMatcher(const boost::regex
&rx
, const MPD::Song
&s
)
1140 return boost::regex_search(SongToString(s
), rx
);