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 std::string
AlbumToString(const AlbumEntry
&ae
);
62 std::string
SongToString(const MPD::Song
&s
);
64 bool TagEntryMatcher(const boost::regex
&rx
, const MediaLibrary::PrimaryTag
&tagmtime
);
65 bool AlbumEntryMatcher(const boost::regex
&rx
, const NC::Menu
<AlbumEntry
>::Item
&item
, bool filter
);
66 bool SongEntryMatcher(const boost::regex
&rx
, const MPD::Song
&s
);
69 typedef NC::Menu
<MPD::Song
>::Item SongItem
;
71 static const std::array
<MPD::Song::GetFunction
, 3> GetFuns
;
73 LocaleStringComparison m_cmp
;
74 std::ptrdiff_t m_offset
;
77 SortSongs(bool disc_only
)
78 : m_cmp(std::locale(), Config
.ignore_leading_the
), m_offset(disc_only
? 2 : 0) { }
80 bool operator()(const SongItem
&a
, const SongItem
&b
) {
81 return (*this)(a
.value(), b
.value());
83 bool operator()(const MPD::Song
&a
, const MPD::Song
&b
) {
84 for (auto get
= GetFuns
.begin()+m_offset
; get
!= GetFuns
.end(); ++get
) {
85 int ret
= m_cmp(a
.getTags(*get
, Config
.tags_separator
),
86 b
.getTags(*get
, Config
.tags_separator
));
90 return a
.getTrack() < b
.getTrack();
94 const std::array
<MPD::Song::GetFunction
, 3> SortSongs::GetFuns
= {{
100 class SortAlbumEntries
{
101 typedef MediaLibrary::Album Album
;
103 LocaleStringComparison m_cmp
;
106 SortAlbumEntries() : m_cmp(std::locale(), Config
.ignore_leading_the
) { }
108 bool operator()(const AlbumEntry
&a
, const AlbumEntry
&b
) const {
109 return (*this)(a
.entry(), b
.entry());
112 bool operator()(const Album
&a
, const Album
&b
) const {
113 if (Config
.media_library_sort_by_mtime
)
114 return a
.mtime() > b
.mtime();
118 result
= m_cmp(a
.tag(), b
.tag());
121 result
= m_cmp(a
.date(), b
.date());
124 return m_cmp(a
.album(), b
.album()) < 0;
129 class SortPrimaryTags
{
130 typedef MediaLibrary::PrimaryTag PrimaryTag
;
132 LocaleStringComparison m_cmp
;
135 SortPrimaryTags() : m_cmp(std::locale(), Config
.ignore_leading_the
) { }
137 bool operator()(const PrimaryTag
&a
, const PrimaryTag
&b
) const {
138 if (Config
.media_library_sort_by_mtime
)
139 return a
.mtime() > b
.mtime();
141 return m_cmp(a
.tag(), b
.tag()) < 0;
147 MediaLibrary::MediaLibrary()
148 : m_timer(boost::posix_time::from_time_t(0))
149 , m_window_timeout(Config
.data_fetching_delay
? 250 : 500)
150 , m_fetching_delay(boost::posix_time::milliseconds(Config
.data_fetching_delay
? 250 : -1))
153 itsLeftColWidth
= COLS
/3-1;
154 itsMiddleColWidth
= COLS
/3;
155 itsMiddleColStartX
= itsLeftColWidth
+1;
156 itsRightColWidth
= COLS
-COLS
/3*2-1;
157 itsRightColStartX
= itsLeftColWidth
+itsMiddleColWidth
+2;
159 Tags
= NC::Menu
<PrimaryTag
>(0, MainStartY
, itsLeftColWidth
, MainHeight
, Config
.titles_visibility
? tagTypeToString(Config
.media_lib_primary_tag
) + "s" : "", Config
.main_color
, NC::Border::None
);
160 Tags
.setHighlightColor(Config
.active_column_color
);
161 Tags
.cyclicScrolling(Config
.use_cyclic_scrolling
);
162 Tags
.centeredCursor(Config
.centered_cursor
);
163 Tags
.setSelectedPrefix(Config
.selected_item_prefix
);
164 Tags
.setSelectedSuffix(Config
.selected_item_suffix
);
165 Tags
.setItemDisplayer([](NC::Menu
<PrimaryTag
> &menu
) {
166 const std::string
&tag
= menu
.drawn()->value().tag();
168 menu
<< Config
.empty_tag
;
170 menu
<< Charset::utf8ToLocale(tag
);
173 Albums
= NC::Menu
<AlbumEntry
>(itsMiddleColStartX
, MainStartY
, itsMiddleColWidth
, MainHeight
, Config
.titles_visibility
? "Albums" : "", Config
.main_color
, NC::Border::None
);
174 Albums
.setHighlightColor(Config
.main_highlight_color
);
175 Albums
.cyclicScrolling(Config
.use_cyclic_scrolling
);
176 Albums
.centeredCursor(Config
.centered_cursor
);
177 Albums
.setSelectedPrefix(Config
.selected_item_prefix
);
178 Albums
.setSelectedSuffix(Config
.selected_item_suffix
);
179 Albums
.setItemDisplayer([](NC::Menu
<AlbumEntry
> &menu
) {
180 menu
<< Charset::utf8ToLocale(AlbumToString(menu
.drawn()->value()));
183 Songs
= NC::Menu
<MPD::Song
>(itsRightColStartX
, MainStartY
, itsRightColWidth
, MainHeight
, Config
.titles_visibility
? "Songs" : "", Config
.main_color
, NC::Border::None
);
184 Songs
.setHighlightColor(Config
.main_highlight_color
);
185 Songs
.cyclicScrolling(Config
.use_cyclic_scrolling
);
186 Songs
.centeredCursor(Config
.centered_cursor
);
187 Songs
.setSelectedPrefix(Config
.selected_item_prefix
);
188 Songs
.setSelectedSuffix(Config
.selected_item_suffix
);
189 Songs
.setItemDisplayer(boost::bind(Display::Songs
, _1
, songsProxyList(), Config
.song_library_format
));
194 void MediaLibrary::resize()
196 size_t x_offset
, width
;
197 getWindowResizeParams(x_offset
, width
);
200 itsLeftColStartX
= x_offset
;
201 itsLeftColWidth
= width
/3-1;
202 itsMiddleColStartX
= itsLeftColStartX
+itsLeftColWidth
+1;
203 itsMiddleColWidth
= width
/3;
204 itsRightColStartX
= itsMiddleColStartX
+itsMiddleColWidth
+1;
205 itsRightColWidth
= width
-width
/3*2-1;
209 itsMiddleColStartX
= x_offset
;
210 itsMiddleColWidth
= width
/2;
211 itsRightColStartX
= x_offset
+itsMiddleColWidth
+1;
212 itsRightColWidth
= width
-itsMiddleColWidth
-1;
215 Tags
.resize(itsLeftColWidth
, MainHeight
);
216 Albums
.resize(itsMiddleColWidth
, MainHeight
);
217 Songs
.resize(itsRightColWidth
, MainHeight
);
219 Tags
.moveTo(itsLeftColStartX
, MainStartY
);
220 Albums
.moveTo(itsMiddleColStartX
, MainStartY
);
221 Songs
.moveTo(itsRightColStartX
, MainStartY
);
226 void MediaLibrary::refresh()
229 drawSeparator(itsMiddleColStartX
-1);
231 drawSeparator(itsRightColStartX
-1);
235 Albums
<< NC::XY(0, 0) << "No albums found.";
236 Albums
.Window::refresh();
240 void MediaLibrary::switchTo()
242 SwitchTo::execute(this);
243 markSongsInPlaylist(songsProxyList());
248 std::wstring
MediaLibrary::title()
250 return L
"Media library";
253 void MediaLibrary::update()
257 if (Albums
.reallyEmpty() || m_albums_update_request
)
259 Albums
.clearSearchResults();
260 m_albums_update_request
= false;
261 std::map
<std::tuple
<std::string
, std::string
, std::string
>, time_t> albums
;
262 Mpd
.GetDirectoryRecursive("/", [&albums
](MPD::Song s
) {
264 std::string tag
= s
.get(Config
.media_lib_primary_tag
, idx
);
267 auto key
= std::make_tuple(tag
, s
.getAlbum(), s
.getDate());
268 auto it
= albums
.find(key
);
269 if (it
== albums
.end())
270 albums
[key
] = s
.getMTime();
272 it
->second
= s
.getMTime();
274 while (!(tag
= s
.get(Config
.media_lib_primary_tag
, ++idx
)).empty());
276 withUnfilteredMenuReapplyFilter(Albums
, [this, &albums
]() {
278 for (auto it
= albums
.begin(); it
!= albums
.end(); ++it
, ++idx
)
280 auto &&entry
= AlbumEntry(Album(
281 std::move(std::get
<0>(it
->first
)),
282 std::move(std::get
<1>(it
->first
)),
283 std::move(std::get
<2>(it
->first
)),
285 if (idx
< Albums
.size())
286 Albums
[idx
].value() = entry
;
288 Albums
.addItem(entry
);
290 if (idx
< Albums
.size())
291 Albums
.resizeList(idx
);
292 std::sort(Albums
.beginV(), Albums
.endV(), SortAlbumEntries());
299 if (Tags
.reallyEmpty() || m_tags_update_request
)
301 Tags
.clearSearchResults();
302 m_tags_update_request
= false;
303 std::map
<std::string
, time_t> tags
;
304 if (Config
.media_library_sort_by_mtime
)
306 Mpd
.GetDirectoryRecursive("/", [&tags
](MPD::Song s
) {
308 std::string tag
= s
.get(Config
.media_lib_primary_tag
, idx
);
311 auto it
= tags
.find(tag
);
312 if (it
== tags
.end())
313 tags
[tag
] = s
.getMTime();
315 it
->second
= std::max(it
->second
, s
.getMTime());
317 while (!(tag
= s
.get(Config
.media_lib_primary_tag
, ++idx
)).empty());
322 Mpd
.GetList(Config
.media_lib_primary_tag
, [&tags
](std::string tag
) {
326 withUnfilteredMenuReapplyFilter(Tags
, [this, &tags
]() {
328 for (auto it
= tags
.begin(); it
!= tags
.end(); ++it
, ++idx
)
330 auto &&tag
= PrimaryTag(std::move(it
->first
), it
->second
);
331 if (idx
< Tags
.size())
332 Tags
[idx
].value() = tag
;
336 if (idx
< Tags
.size())
337 Tags
.resizeList(idx
);
338 std::sort(Tags
.beginV(), Tags
.endV(), SortPrimaryTags());
344 && ((Albums
.reallyEmpty() && Global::Timer
- m_timer
> m_fetching_delay
) || m_albums_update_request
)
347 Albums
.clearSearchResults();
348 m_albums_update_request
= false;
349 auto &primary_tag
= Tags
.current().value().tag();
350 Mpd
.StartSearch(true);
351 Mpd
.AddSearch(Config
.media_lib_primary_tag
, primary_tag
);
352 std::map
<std::tuple
<std::string
, std::string
>, time_t> albums
;
353 Mpd
.CommitSearchSongs([&albums
](MPD::Song s
) {
354 auto key
= std::make_tuple(s
.getAlbum(), s
.getDate());
355 auto it
= albums
.find(key
);
356 if (it
== albums
.end())
357 albums
[key
] = s
.getMTime();
359 it
->second
= s
.getMTime();
361 withUnfilteredMenuReapplyFilter(Albums
, [this, &albums
, &primary_tag
]() {
363 for (auto it
= albums
.begin(); it
!= albums
.end(); ++it
, ++idx
)
365 auto &&entry
= AlbumEntry(Album(
367 std::move(std::get
<0>(it
->first
)),
368 std::move(std::get
<1>(it
->first
)),
370 if (idx
< Albums
.size())
372 Albums
[idx
].value() = entry
;
373 Albums
[idx
].setSeparator(false);
376 Albums
.addItem(entry
);
378 if (idx
< Albums
.size())
379 Albums
.resizeList(idx
);
380 std::sort(Albums
.beginV(), Albums
.endV(), SortAlbumEntries());
381 if (albums
.size() > 1)
383 Albums
.addSeparator();
384 Albums
.addItem(AlbumEntry::mkAllTracksEntry(primary_tag
));
392 && ((Songs
.reallyEmpty() && Global::Timer
- m_timer
> m_fetching_delay
) || m_songs_update_request
)
395 Songs
.clearSearchResults();
396 m_songs_update_request
= false;
397 auto &album
= Albums
.current().value();
398 Mpd
.StartSearch(true);
399 Mpd
.AddSearch(Config
.media_lib_primary_tag
, album
.entry().tag());
400 if (!album
.isAllTracksEntry())
402 Mpd
.AddSearch(MPD_TAG_ALBUM
, album
.entry().album());
403 Mpd
.AddSearch(MPD_TAG_DATE
, album
.entry().date());
405 withUnfilteredMenuReapplyFilter(Songs
, [this, &album
]() {
407 Mpd
.CommitSearchSongs([this, &idx
](MPD::Song s
) {
408 bool is_playlist
= myPlaylist
->checkForSong(s
);
409 if (idx
< Songs
.size())
411 Songs
[idx
].value() = s
;
412 Songs
[idx
].setBold(is_playlist
);
415 Songs
.addItem(s
, is_playlist
);
418 if (idx
< Songs
.size())
419 Songs
.resizeList(idx
);
420 std::sort(Songs
.begin(), Songs
.end(), SortSongs(!album
.isAllTracksEntry()));
426 int MediaLibrary::windowTimeout()
428 if (Albums
.reallyEmpty() || Songs
.reallyEmpty())
429 return m_window_timeout
;
431 return Screen
<WindowType
>::windowTimeout();
434 void MediaLibrary::enterPressed()
439 void MediaLibrary::spacePressed()
441 if (Config
.space_selects
)
443 if (isActiveWindow(Tags
))
445 size_t idx
= Tags
.choice();
446 Tags
[idx
].setSelected(!Tags
[idx
].isSelected());
447 Tags
.scroll(NC::Scroll::Down
);
451 else if (isActiveWindow(Albums
))
453 if (!Albums
.current().value().isAllTracksEntry())
455 size_t idx
= Albums
.choice();
456 Albums
[idx
].setSelected(!Albums
[idx
].isSelected());
457 Albums
.scroll(NC::Scroll::Down
);
461 else if (isActiveWindow(Songs
))
463 size_t idx
= Songs
.choice();
464 Songs
[idx
].setSelected(!Songs
[idx
].isSelected());
465 Songs
.scroll(NC::Scroll::Down
);
469 AddToPlaylist(false);
472 void MediaLibrary::mouseButtonPressed(MEVENT me
)
474 auto tryNextColumn
= [this]() -> bool {
476 if (!isActiveWindow(Songs
))
478 if (nextColumnAvailable())
485 auto tryPreviousColumn
= [this]() -> bool {
487 if (!isActiveWindow(Tags
))
489 if (previousColumnAvailable())
496 if (!Tags
.empty() && Tags
.hasCoords(me
.x
, me
.y
))
498 if (!tryPreviousColumn() || !tryPreviousColumn())
500 if (size_t(me
.y
) < Tags
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
503 if (me
.bstate
& BUTTON3_PRESSED
)
505 size_t pos
= Tags
.choice();
507 if (pos
< Tags
.size()-1)
508 Tags
.scroll(NC::Scroll::Up
);
512 Screen
<WindowType
>::mouseButtonPressed(me
);
516 else if (!Albums
.empty() && Albums
.hasCoords(me
.x
, me
.y
))
518 if (!isActiveWindow(Albums
))
521 if (isActiveWindow(Tags
))
522 success
= tryNextColumn();
524 success
= tryPreviousColumn();
528 if (size_t(me
.y
) < Albums
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
531 if (me
.bstate
& BUTTON3_PRESSED
)
533 size_t pos
= Albums
.choice();
535 if (pos
< Albums
.size()-1)
536 Albums
.scroll(NC::Scroll::Up
);
540 Screen
<WindowType
>::mouseButtonPressed(me
);
543 else if (!Songs
.empty() && Songs
.hasCoords(me
.x
, me
.y
))
545 if (!tryNextColumn() || !tryNextColumn())
547 if (size_t(me
.y
) < Songs
.size() && (me
.bstate
& (BUTTON1_PRESSED
| BUTTON3_PRESSED
)))
550 if (me
.bstate
& BUTTON1_PRESSED
)
552 size_t pos
= Songs
.choice();
554 if (pos
< Songs
.size()-1)
555 Songs
.scroll(NC::Scroll::Up
);
561 Screen
<WindowType
>::mouseButtonPressed(me
);
565 /***********************************************************************/
567 bool MediaLibrary::allowsFiltering()
572 std::string
MediaLibrary::currentFilter()
575 if (isActiveWindow(Tags
))
576 filter
= RegexFilter
<PrimaryTag
>::currentFilter(Tags
);
577 else if (isActiveWindow(Albums
))
578 filter
= RegexItemFilter
<AlbumEntry
>::currentFilter(Albums
);
579 else if (isActiveWindow(Songs
))
580 filter
= RegexFilter
<MPD::Song
>::currentFilter(Songs
);
584 void MediaLibrary::applyFilter(const std::string
&filter
)
588 if (isActiveWindow(Tags
))
591 Tags
.clearFilterResults();
593 else if (isActiveWindow(Albums
))
595 Albums
.clearFilter();
596 Albums
.clearFilterResults();
598 else if (isActiveWindow(Songs
))
601 Songs
.clearFilterResults();
607 if (isActiveWindow(Tags
))
610 auto rx
= RegexFilter
<PrimaryTag
>(
611 boost::regex(filter
, Config
.regex_type
), TagEntryMatcher
);
612 Tags
.filter(Tags
.begin(), Tags
.end(), rx
);
614 else if (isActiveWindow(Albums
))
617 auto fun
= boost::bind(AlbumEntryMatcher
, _1
, _2
, true);
618 auto rx
= RegexItemFilter
<AlbumEntry
>(
619 boost::regex(filter
, Config
.regex_type
), fun
);
620 Albums
.filter(Albums
.begin(), Albums
.end(), rx
);
622 else if (isActiveWindow(Songs
))
625 auto rx
= RegexFilter
<MPD::Song
>(
626 boost::regex(filter
, Config
.regex_type
), SongEntryMatcher
);
627 Songs
.filter(Songs
.begin(), Songs
.end(), rx
);
630 catch (boost::bad_expression
&) { }
633 /***********************************************************************/
635 bool MediaLibrary::allowsSearching()
640 bool MediaLibrary::search(const std::string
&constraint
)
642 if (constraint
.empty())
644 if (isActiveWindow(Tags
))
645 Tags
.clearSearchResults();
646 else if (isActiveWindow(Albums
))
647 Albums
.clearSearchResults();
648 else if (isActiveWindow(Songs
))
649 Songs
.clearSearchResults();
655 if (isActiveWindow(Tags
))
657 auto rx
= RegexFilter
<PrimaryTag
>(
658 boost::regex(constraint
, Config
.regex_type
), TagEntryMatcher
);
659 result
= Tags
.search(Tags
.begin(), Tags
.end(), rx
);
661 else if (isActiveWindow(Albums
))
663 auto fun
= boost::bind(AlbumEntryMatcher
, _1
, _2
, false);
664 auto rx
= RegexItemFilter
<AlbumEntry
>(
665 boost::regex(constraint
, Config
.regex_type
), fun
);
666 result
= Albums
.search(Albums
.begin(), Albums
.end(), rx
);
668 else if (isActiveWindow(Songs
))
670 auto rx
= RegexFilter
<MPD::Song
>(
671 boost::regex(constraint
, Config
.regex_type
), SongEntryMatcher
);
672 result
= Songs
.search(Songs
.begin(), Songs
.end(), rx
);
676 catch (boost::bad_expression
&)
682 void MediaLibrary::nextFound(bool wrap
)
684 if (isActiveWindow(Tags
))
685 Tags
.nextFound(wrap
);
686 else if (isActiveWindow(Albums
))
687 Albums
.nextFound(wrap
);
688 else if (isActiveWindow(Songs
))
689 Songs
.nextFound(wrap
);
692 void MediaLibrary::prevFound(bool wrap
)
694 if (isActiveWindow(Tags
))
695 Tags
.prevFound(wrap
);
696 else if (isActiveWindow(Albums
))
697 Albums
.prevFound(wrap
);
698 else if (isActiveWindow(Songs
))
699 Songs
.prevFound(wrap
);
702 /***********************************************************************/
704 ProxySongList
MediaLibrary::proxySongList()
706 auto ptr
= ProxySongList();
707 if (isActiveWindow(Songs
))
708 ptr
= songsProxyList();
712 bool MediaLibrary::allowsSelection()
717 void MediaLibrary::reverseSelection()
719 if (isActiveWindow(Tags
))
720 reverseSelectionHelper(Tags
.begin(), Tags
.end());
721 else if (isActiveWindow(Albums
))
724 if (Albums
.size() > 1)
725 reverseSelectionHelper(Albums
.begin(), Albums
.end()-2);
727 reverseSelectionHelper(Albums
.begin(), Albums
.end());
729 else if (isActiveWindow(Songs
))
730 reverseSelectionHelper(Songs
.begin(), Songs
.end());
733 MPD::SongList
MediaLibrary::getSelectedSongs()
735 MPD::SongList result
;
736 if (isActiveWindow(Tags
))
738 auto tag_handler
= [&result
](const std::string
&tag
) {
739 Mpd
.StartSearch(true);
740 Mpd
.AddSearch(Config
.media_lib_primary_tag
, tag
);
741 Mpd
.CommitSearchSongs(vectorMoveInserter(result
));
743 bool any_selected
= false;
749 tag_handler(e
.value().tag());
752 // if no item is selected, add current one
753 if (!any_selected
&& !Tags
.empty())
754 tag_handler(Tags
.current().value().tag());
756 else if (isActiveWindow(Albums
))
758 bool any_selected
= false;
759 for (auto it
= Albums
.begin(); it
!= Albums
.end() && !it
->isSeparator(); ++it
)
761 if (it
->isSelected())
764 auto &sc
= it
->value();
765 Mpd
.StartSearch(true);
767 Mpd
.AddSearch(Config
.media_lib_primary_tag
, sc
.entry().tag());
769 Mpd
.AddSearch(Config
.media_lib_primary_tag
,
770 Tags
.current().value().tag());
771 Mpd
.AddSearch(MPD_TAG_ALBUM
, sc
.entry().album());
772 Mpd
.AddSearch(MPD_TAG_DATE
, sc
.entry().date());
773 size_t begin
= result
.size();
774 Mpd
.CommitSearchSongs(vectorMoveInserter(result
));
775 std::sort(result
.begin()+begin
, result
.end(), SortSongs(false));
778 // if no item is selected, add songs from right column
779 if (!any_selected
&& !Albums
.empty())
781 withUnfilteredMenu(Songs
, [this, &result
]() {
782 result
.insert(result
.end(), Songs
.beginV(), Songs
.endV());
786 else if (isActiveWindow(Songs
))
788 for (auto it
= Songs
.begin(); it
!= Songs
.end(); ++it
)
789 if (it
->isSelected())
790 result
.push_back(it
->value());
791 // if no item is selected, add current one
792 if (result
.empty() && !Songs
.empty())
793 result
.push_back(Songs
.current().value());
798 /***********************************************************************/
800 bool MediaLibrary::previousColumnAvailable()
802 assert(!hasTwoColumns
|| !isActiveWindow(Tags
));
803 if (isActiveWindow(Songs
))
805 if (!Albums
.reallyEmpty() && (hasTwoColumns
|| !Tags
.reallyEmpty()))
808 else if (isActiveWindow(Albums
))
810 if (!hasTwoColumns
&& !Tags
.reallyEmpty())
816 void MediaLibrary::previousColumn()
818 if (isActiveWindow(Songs
))
820 Songs
.setHighlightColor(Config
.main_highlight_color
);
823 Albums
.setHighlightColor(Config
.active_column_color
);
825 else if (isActiveWindow(Albums
) && !hasTwoColumns
)
827 Albums
.setHighlightColor(Config
.main_highlight_color
);
830 Tags
.setHighlightColor(Config
.active_column_color
);
834 bool MediaLibrary::nextColumnAvailable()
836 assert(!hasTwoColumns
|| !isActiveWindow(Tags
));
837 if (isActiveWindow(Tags
))
839 if (!Albums
.reallyEmpty() && !Songs
.reallyEmpty())
842 else if (isActiveWindow(Albums
))
844 if (!Songs
.reallyEmpty())
850 void MediaLibrary::nextColumn()
852 if (isActiveWindow(Tags
))
854 Tags
.setHighlightColor(Config
.main_highlight_color
);
857 Albums
.setHighlightColor(Config
.active_column_color
);
859 else if (isActiveWindow(Albums
))
861 Albums
.setHighlightColor(Config
.main_highlight_color
);
864 Songs
.setHighlightColor(Config
.active_column_color
);
868 /***********************************************************************/
870 void MediaLibrary::updateTimer()
872 m_timer
= Global::Timer
;
875 void MediaLibrary::toggleColumnsMode()
877 hasTwoColumns
= !hasTwoColumns
;
884 if (isActiveWindow(Tags
))
886 if (Config
.titles_visibility
)
888 std::string item_type
= boost::locale::to_lower(
889 tagTypeToString(Config
.media_lib_primary_tag
));
890 std::string and_mtime
= Config
.media_library_sort_by_mtime
? " and mtime" : "";
891 Albums
.setTitle("Albums (sorted by " + item_type
+ and_mtime
+ ")");
895 Albums
.setTitle(Config
.titles_visibility
? "Albums" : "");
899 int MediaLibrary::Columns()
907 ProxySongList
MediaLibrary::songsProxyList()
909 return ProxySongList(Songs
, [](NC::Menu
<MPD::Song
>::Item
&item
) {
910 return &item
.value();
914 void MediaLibrary::toggleSortMode()
916 Config
.media_library_sort_by_mtime
= !Config
.media_library_sort_by_mtime
;
917 Statusbar::printf("Sorting library by: %1%",
918 Config
.media_library_sort_by_mtime
? "modification time" : "name");
921 withUnfilteredMenuReapplyFilter(Albums
, [this]() {
922 std::sort(Albums
.beginV(), Albums
.endV(), SortAlbumEntries());
926 if (Config
.titles_visibility
)
928 std::string item_type
= boost::locale::to_lower(
929 tagTypeToString(Config
.media_lib_primary_tag
));
930 std::string and_mtime
= Config
.media_library_sort_by_mtime
? (" " "and mtime") : "";
931 Albums
.setTitle("Albums (sorted by " + item_type
+ and_mtime
+ ")");
936 withUnfilteredMenuReapplyFilter(Tags
, [this]() {
937 // if we already have modification times,
938 // just resort. otherwise refetch the list.
939 if (!Tags
.empty() && Tags
[0].value().mtime() > 0)
941 std::sort(Tags
.beginV(), Tags
.endV(), SortPrimaryTags());
953 void MediaLibrary::LocateSong(const MPD::Song
&s
)
955 std::string primary_tag
= s
.get(Config
.media_lib_primary_tag
);
956 if (primary_tag
.empty())
958 std::string item_type
= boost::locale::to_lower(
959 tagTypeToString(Config
.media_lib_primary_tag
));
960 Statusbar::printf("Can't use this function because the song has no %s tag", item_type
);
963 if (!s
.isFromDatabase())
965 Statusbar::print("Song is not from the database");
969 if (myScreen
!= this)
971 Statusbar::put() << "Jumping to song...";
972 Global::wFooter
->refresh();
979 if (primary_tag
!= Tags
.current().value().tag())
981 for (size_t i
= 0; i
< Tags
.size(); ++i
)
983 if (primary_tag
== Tags
[i
].value().tag())
998 std::string album
= s
.getAlbum();
999 std::string date
= s
.getDate();
1000 if ((hasTwoColumns
&& Albums
.current().value().entry().tag() != primary_tag
)
1001 || album
!= Albums
.current().value().entry().album()
1002 || date
!= Albums
.current().value().entry().date())
1004 for (size_t i
= 0; i
< Albums
.size(); ++i
)
1006 if ((!hasTwoColumns
|| Albums
[i
].value().entry().tag() == primary_tag
)
1007 && album
== Albums
[i
].value().entry().album()
1008 && date
== Albums
[i
].value().entry().date())
1010 Albums
.highlight(i
);
1021 if (s
!= Songs
.current().value())
1023 for (size_t i
= 0; i
< Songs
.size(); ++i
)
1025 if (s
== Songs
[i
].value())
1033 Tags
.setHighlightColor(Config
.main_highlight_color
);
1034 Albums
.setHighlightColor(Config
.main_highlight_color
);
1035 Songs
.setHighlightColor(Config
.active_column_color
);
1040 void MediaLibrary::AddToPlaylist(bool add_n_play
)
1042 if (isActiveWindow(Songs
) && !Songs
.empty())
1043 addSongToPlaylist(Songs
.current().value(), add_n_play
);
1046 if ((!Tags
.empty() && isActiveWindow(Tags
))
1047 || (isActiveWindow(Albums
) && Albums
.current().value().isAllTracksEntry()))
1050 Mpd
.StartSearch(true);
1051 Mpd
.AddSearch(Config
.media_lib_primary_tag
, Tags
.current().value().tag());
1052 Mpd
.CommitSearchSongs(vectorMoveInserter(list
));
1053 bool success
= addSongsToPlaylist(list
.begin(), list
.end(), add_n_play
, -1);
1054 std::string tag_type
= boost::locale::to_lower(
1055 tagTypeToString(Config
.media_lib_primary_tag
));
1056 Statusbar::printf("Songs with %1% \"%2%\" added%3%",
1057 tag_type
, Tags
.current().value().tag(), withErrors(success
)
1060 else if (isActiveWindow(Albums
))
1063 withUnfilteredMenu(Songs
, [&]() {
1064 success
= addSongsToPlaylist(Songs
.beginV(), Songs
.endV(), add_n_play
, -1);
1066 Statusbar::printf("Songs from album \"%1%\" added%2%",
1067 Albums
.current().value().entry().album(), withErrors(success
)
1074 w
->scroll(NC::Scroll::Down
);
1075 if (isActiveWindow(Tags
))
1080 else if (isActiveWindow(Albums
))
1087 std::string
AlbumToString(const AlbumEntry
&ae
)
1090 if (ae
.isAllTracksEntry())
1091 result
= "All tracks";
1096 if (ae
.entry().tag().empty())
1097 result
+= Config
.empty_tag
;
1099 result
+= ae
.entry().tag();
1102 if (Config
.media_lib_primary_tag
!= MPD_TAG_DATE
&& !ae
.entry().date().empty())
1103 result
+= "(" + ae
.entry().date() + ") ";
1104 result
+= ae
.entry().album().empty() ? "<no album>" : ae
.entry().album();
1109 std::string
SongToString(const MPD::Song
&s
)
1111 return s
.toString(Config
.song_library_format
, Config
.tags_separator
);
1114 bool TagEntryMatcher(const boost::regex
&rx
, const MediaLibrary::PrimaryTag
&pt
)
1116 return boost::regex_search(pt
.tag(), rx
);
1119 bool AlbumEntryMatcher(const boost::regex
&rx
, const NC::Menu
<AlbumEntry
>::Item
&item
, bool filter
)
1121 if (item
.isSeparator() || item
.value().isAllTracksEntry())
1123 return boost::regex_search(AlbumToString(item
.value()), rx
);
1126 bool SongEntryMatcher(const boost::regex
&rx
, const MPD::Song
&s
)
1128 return boost::regex_search(SongToString(s
), rx
);