1 /***************************************************************************
2 * Copyright (C) 2008-2012 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 ***************************************************************************/
24 #include "interfaces.h"
29 #include "utility/wide_string.h"
31 inline HasSongs
*hasSongs(BasicScreen
*screen
)
33 return dynamic_cast<HasSongs
*>(screen
);
36 inline std::shared_ptr
<ProxySongList
> proxySongList(BasicScreen
*screen
)
38 auto ptr
= nullProxySongList();
39 auto hs
= hasSongs(screen
);
41 ptr
= hs
->getProxySongList();
45 inline MPD::Song
*currentSong(BasicScreen
*screen
)
48 auto pl
= proxySongList(screen
);
50 ptr
= pl
->currentSong();
54 template <typename Iterator
>
55 bool hasSelected(Iterator first
, Iterator last
)
57 for (; first
!= last
; ++first
)
58 if (first
->isSelected())
63 template <typename Iterator
>
64 std::vector
<Iterator
> getSelected(Iterator first
, Iterator last
)
66 std::vector
<Iterator
> result
;
67 for (; first
!= last
; ++first
)
68 if (first
->isSelected())
69 result
.push_back(first
);
74 void selectCurrentIfNoneSelected(NC::Menu
<T
> &m
)
76 if (!hasSelected(m
.begin(), m
.end()))
77 m
.current().setSelected(true);
80 template <typename Iterator
>
81 std::vector
<Iterator
> getSelectedOrCurrent(Iterator first
, Iterator last
, Iterator current
)
83 std::vector
<Iterator
> result
= getSelected(first
, last
);
85 result
.push_back(current
);
89 template <typename Iterator
>
90 void reverseSelectionHelper(Iterator first
, Iterator last
)
92 for (; first
!= last
; ++first
)
93 first
->setSelected(!first
->isSelected());
96 template <typename T
, typename F
>
97 void withUnfilteredMenu(NC::Menu
<T
> &m
, F action
)
99 bool is_filtered
= m
.isFiltered();
106 template <typename T
, typename F
>
107 void withUnfilteredMenuReapplyFilter(NC::Menu
<T
> &m
, F action
)
113 m
.applyCurrentFilter(m
.begin(), m
.end());
117 m
.clearFilterResults();
122 template <typename F
>
123 void moveSelectedItemsUp(NC::Menu
<MPD::Song
> &m
, F swap_fun
)
126 selectCurrentIfNoneSelected(m
);
127 auto list
= getSelected(m
.begin(), m
.end());
128 auto begin
= m
.begin();
129 if (!list
.empty() && list
.front() != m
.begin())
131 Mpd
.StartCommandsList();
132 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
133 swap_fun(Mpd
, *it
- begin
, *it
- begin
- 1);
134 if (Mpd
.CommitCommandsList())
138 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
140 (*it
)->setSelected(false);
141 (*it
-1)->setSelected(true);
143 m
.highlight(list
[(list
.size())/2] - begin
- 1);
147 // if we move only one item, do not select it. however, if single item
148 // was selected prior to move, it'll deselect it. oh well.
149 list
[0]->setSelected(false);
156 template <typename F
>
157 void moveSelectedItemsDown(NC::Menu
<MPD::Song
> &m
, F swap_fun
)
159 if (m
.choice() < m
.size()-1)
160 selectCurrentIfNoneSelected(m
);
161 auto list
= getSelected(m
.rbegin(), m
.rend());
162 auto begin
= m
.begin() + 1; // reverse iterators add 1, so we need to cancel it
163 if (!list
.empty() && list
.front() != m
.rbegin())
165 Mpd
.StartCommandsList();
166 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
167 swap_fun(Mpd
, it
->base() - begin
, it
->base() - begin
+ 1);
168 if (Mpd
.CommitCommandsList())
172 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
174 (*it
)->setSelected(false);
175 (*it
-1)->setSelected(true);
177 m
.highlight(list
[(list
.size())/2].base() - begin
+ 1);
181 // if we move only one item, do not select it. however, if single item
182 // was selected prior to move, it'll deselect it. oh well.
183 list
[0]->setSelected(false);
190 template <typename F
>
191 void moveSelectedItemsTo(NC::Menu
<MPD::Song
> &m
, F move_fun
)
193 auto cur_ptr
= &m
.current().value();
194 withUnfilteredMenu(m
, [&]() {
195 // this is kinda shitty, but there is no other way to know
196 // what position current item has in unfiltered menu.
198 for (auto it
= m
.begin(); it
!= m
.end(); ++it
, ++pos
)
199 if (&it
->value() == cur_ptr
)
201 auto begin
= m
.begin();
202 auto list
= getSelected(m
.begin(), m
.end());
203 // we move only truly selected items
206 // we can't move to the middle of selected items
207 //(this also handles case when list.size() == 1)
208 if (pos
>= (list
.front() - begin
) && pos
<= (list
.back() - begin
))
210 int diff
= pos
- (list
.front() - begin
);
211 Mpd
.StartCommandsList();
212 if (diff
> 0) // move down
215 size_t i
= list
.size()-1;
216 for (auto it
= list
.rbegin(); it
!= list
.rend(); ++it
, --i
)
217 move_fun(Mpd
, *it
- begin
, pos
+i
);
218 if (Mpd
.CommitCommandsList())
221 for (auto it
= list
.rbegin(); it
!= list
.rend(); ++it
, --i
)
223 (*it
)->setSelected(false);
224 m
[pos
+i
].setSelected(true);
228 else if (diff
< 0) // move up
231 for (auto it
= list
.begin(); it
!= list
.end(); ++it
, ++i
)
232 move_fun(Mpd
, *it
- begin
, pos
+i
);
233 if (Mpd
.CommitCommandsList())
236 for (auto it
= list
.begin(); it
!= list
.end(); ++it
, ++i
)
238 (*it
)->setSelected(false);
239 m
[pos
+i
].setSelected(true);
246 template <typename F
>
247 bool deleteSelectedSongs(NC::Menu
<MPD::Song
> &m
, F delete_fun
)
250 selectCurrentIfNoneSelected(m
);
251 // ok, this is tricky. we need to operate on whole playlist
252 // to get positions right, but at the same time we need to
253 // ignore all songs that are not filtered. we use the fact
254 // that both ranges share the same values, ie. we can compare
255 // pointers to check whether an item belongs to filtered range.
256 NC::Menu
<MPD::Song
>::Iterator begin
;
257 NC::Menu
<MPD::Song
>::ReverseIterator real_begin
, real_end
;
258 withUnfilteredMenu(m
, [&]() {
259 // obtain iterators for unfiltered range
260 begin
= m
.begin() + 1; // cancel reverse iterator's offset
261 real_begin
= m
.rbegin();
264 // get iterator to filtered range
265 auto cur_filtered
= m
.rbegin();
266 Mpd
.StartCommandsList();
267 for (auto it
= real_begin
; it
!= real_end
; ++it
)
269 // current iterator belongs to filtered range, proceed
270 if (&*it
== &*cur_filtered
)
272 if (it
->isSelected())
274 it
->setSelected(false);
275 delete_fun(Mpd
, it
.base() - begin
);
280 if (Mpd
.CommitCommandsList())
285 template <typename F
>
286 bool cropPlaylist(NC::Menu
<MPD::Song
> &m
, F delete_fun
)
288 reverseSelectionHelper(m
.begin(), m
.end());
289 return deleteSelectedSongs(m
, delete_fun
);
292 template <typename F
, typename G
>
293 bool clearPlaylist(NC::Menu
<MPD::Song
> &m
, F delete_fun
, G clear_fun
)
298 for (auto it
= m
.begin(); it
!= m
.end(); ++it
)
299 it
->setSelected(true);
300 result
= deleteSelectedSongs(m
, delete_fun
);
303 result
= clear_fun(Mpd
);
307 template <typename Iterator
> std::string
getSharedDirectory(Iterator first
, Iterator last
)
309 assert(first
!= last
);
310 std::string result
= first
->getDirectory();
311 while (++first
!= last
)
313 result
= getSharedDirectory(result
, first
->getDirectory());
320 template <typename T
> struct StringConverter
{
321 const char *operator()(const char *s
) { return s
; }
323 template <> struct StringConverter
<NC::WBuffer
> {
324 std::wstring
operator()(const char *s
) { return ToWString(s
); }
326 template <> struct StringConverter
<NC::Scrollpad
> {
327 std::wstring
operator()(const char *s
) { return ToWString(s
); }
330 template <typename Iterator
>
331 void stringToBuffer(Iterator first
, Iterator last
, NC::BasicBuffer
<typename
Iterator::value_type
> &buf
)
333 for (auto it
= first
; it
!= last
; ++it
)
342 else if (isdigit(*it
))
344 buf
<< NC::Color(*it
-'0');
354 buf
<< NC::fmtUnderline
;
357 buf
<< NC::fmtAltCharset
;
360 buf
<< NC::fmtReverse
;
371 buf
<< NC::fmtBoldEnd
;
374 buf
<< NC::fmtUnderlineEnd
;
377 buf
<< NC::fmtAltCharsetEnd
;
380 buf
<< NC::fmtReverseEnd
;
393 else if (*it
== MPD::Song::FormatEscapeCharacter
)
395 // treat '$' as a normal character if song format escape char is prepended to it
396 if (++it
== last
|| *it
!= '$')
405 template <typename CharT
>
406 void stringToBuffer(const std::basic_string
<CharT
> &s
, NC::BasicBuffer
<CharT
> &buf
)
408 stringToBuffer(s
.begin(), s
.end(), buf
);
411 template <typename T
> void ShowTime(T
&buf
, size_t length
, bool short_names
)
413 StringConverter
<T
> cnv
;
415 const unsigned MINUTE
= 60;
416 const unsigned HOUR
= 60*MINUTE
;
417 const unsigned DAY
= 24*HOUR
;
418 const unsigned YEAR
= 365*DAY
;
420 unsigned years
= length
/YEAR
;
423 buf
<< years
<< cnv(short_names
? "y" : (years
== 1 ? " year" : " years"));
424 length
-= years
*YEAR
;
428 unsigned days
= length
/DAY
;
431 buf
<< days
<< cnv(short_names
? "d" : (days
== 1 ? " day" : " days"));
436 unsigned hours
= length
/HOUR
;
439 buf
<< hours
<< cnv(short_names
? "h" : (hours
== 1 ? " hour" : " hours"));
440 length
-= hours
*HOUR
;
444 unsigned minutes
= length
/MINUTE
;
447 buf
<< minutes
<< cnv(short_names
? "m" : (minutes
== 1 ? " minute" : " minutes"));
448 length
-= minutes
*MINUTE
;
453 buf
<< length
<< cnv(short_names
? "s" : (length
== 1 ? " second" : " seconds"));
456 template <typename T
> void ShowTag(T
&buf
, const std::string
&tag
)
459 buf
<< Config
.empty_tags_color
<< Config
.empty_tag
<< NC::clEnd
;
464 std::string
Timestamp(time_t t
);
466 void markSongsInPlaylist(std::shared_ptr
<ProxySongList
> pl
);
468 std::wstring
Scroller(const std::wstring
&str
, size_t &pos
, size_t width
);