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 #ifndef NCMPCPP_HELPERS_H
22 #define NCMPCPP_HELPERS_H
24 #include "interfaces.h"
29 #include "utility/string.h"
30 #include "utility/wide_string.h"
32 inline HasColumns
*hasColumns(BaseScreen
*screen
)
34 return dynamic_cast<HasColumns
*>(screen
);
37 inline HasSongs
*hasSongs(BaseScreen
*screen
)
39 return dynamic_cast<HasSongs
*>(screen
);
42 inline ProxySongList
proxySongList(BaseScreen
*screen
)
44 auto pl
= ProxySongList();
45 auto hs
= hasSongs(screen
);
47 pl
= hs
->proxySongList();
51 inline MPD::Song
*currentSong(BaseScreen
*screen
)
54 auto pl
= proxySongList(screen
);
56 ptr
= pl
.currentSong();
60 template <typename Iterator
>
61 bool hasSelected(Iterator first
, Iterator last
)
63 for (; first
!= last
; ++first
)
64 if (first
->isSelected())
69 template <typename Iterator
>
70 std::vector
<Iterator
> getSelected(Iterator first
, Iterator last
)
72 std::vector
<Iterator
> result
;
73 for (; first
!= last
; ++first
)
74 if (first
->isSelected())
75 result
.push_back(first
);
79 /// @return selected range within given range or original range if no item is selected
80 template <typename Iterator
>
81 std::pair
<Iterator
, Iterator
> getSelectedRange(Iterator first
, Iterator second
)
83 auto result
= std::make_pair(first
, second
);
84 if (hasSelected(first
, second
))
86 while (!result
.first
->isSelected())
88 while (!(result
.second
-1)->isSelected())
95 void selectCurrentIfNoneSelected(NC::Menu
<T
> &m
)
97 if (!hasSelected(m
.begin(), m
.end()))
98 m
.current().setSelected(true);
101 template <typename Iterator
>
102 std::vector
<Iterator
> getSelectedOrCurrent(Iterator first
, Iterator last
, Iterator current
)
104 std::vector
<Iterator
> result
= getSelected(first
, last
);
106 result
.push_back(current
);
110 template <typename Iterator
>
111 void reverseSelectionHelper(Iterator first
, Iterator last
)
113 for (; first
!= last
; ++first
)
114 first
->setSelected(!first
->isSelected());
117 template <typename T
, typename F
>
118 void withUnfilteredMenu(NC::Menu
<T
> &m
, F action
)
120 bool is_filtered
= m
.isFiltered();
121 auto cleanup
= [&]() {
138 template <typename T
, typename F
>
139 void withUnfilteredMenuReapplyFilter(NC::Menu
<T
> &m
, F action
)
141 auto cleanup
= [&]() {
144 m
.applyCurrentFilter(m
.begin(), m
.end());
148 m
.clearFilterResults();
165 template <typename F
>
166 void moveSelectedItemsUp(NC::Menu
<MPD::Song
> &m
, F swap_fun
)
169 selectCurrentIfNoneSelected(m
);
170 auto list
= getSelected(m
.begin(), m
.end());
171 auto begin
= m
.begin();
172 if (!list
.empty() && list
.front() != m
.begin())
174 Mpd
.StartCommandsList();
175 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
176 swap_fun(&Mpd
, *it
- begin
, *it
- begin
- 1);
177 Mpd
.CommitCommandsList();
180 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
182 (*it
)->setSelected(false);
183 (*it
-1)->setSelected(true);
185 m
.highlight(list
[(list
.size())/2] - begin
- 1);
189 // if we move only one item, do not select it. however, if single item
190 // was selected prior to move, it'll deselect it. oh well.
191 list
[0]->setSelected(false);
192 m
.scroll(NC::Scroll::Up
);
197 template <typename F
>
198 void moveSelectedItemsDown(NC::Menu
<MPD::Song
> &m
, F swap_fun
)
200 if (m
.choice() < m
.size()-1)
201 selectCurrentIfNoneSelected(m
);
202 auto list
= getSelected(m
.rbegin(), m
.rend());
203 auto begin
= m
.begin() + 1; // reverse iterators add 1, so we need to cancel it
204 if (!list
.empty() && list
.front() != m
.rbegin())
206 Mpd
.StartCommandsList();
207 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
208 swap_fun(&Mpd
, it
->base() - begin
, it
->base() - begin
+ 1);
209 Mpd
.CommitCommandsList();
212 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
214 (*it
)->setSelected(false);
215 (*it
-1)->setSelected(true);
217 m
.highlight(list
[(list
.size())/2].base() - begin
+ 1);
221 // if we move only one item, do not select it. however, if single item
222 // was selected prior to move, it'll deselect it. oh well.
223 list
[0]->setSelected(false);
224 m
.scroll(NC::Scroll::Down
);
229 template <typename F
>
230 void moveSelectedItemsTo(NC::Menu
<MPD::Song
> &m
, F move_fun
)
232 auto cur_ptr
= &m
.current().value();
233 withUnfilteredMenu(m
, [&]() {
234 // this is kinda shitty, but there is no other way to know
235 // what position current item has in unfiltered menu.
237 for (auto it
= m
.begin(); it
!= m
.end(); ++it
, ++pos
)
238 if (&it
->value() == cur_ptr
)
240 auto begin
= m
.begin();
241 auto list
= getSelected(m
.begin(), m
.end());
242 // we move only truly selected items
245 // we can't move to the middle of selected items
246 //(this also handles case when list.size() == 1)
247 if (pos
>= (list
.front() - begin
) && pos
<= (list
.back() - begin
))
249 int diff
= pos
- (list
.front() - begin
);
250 Mpd
.StartCommandsList();
251 if (diff
> 0) // move down
254 size_t i
= list
.size()-1;
255 for (auto it
= list
.rbegin(); it
!= list
.rend(); ++it
, --i
)
256 move_fun(&Mpd
, *it
- begin
, pos
+i
);
257 Mpd
.CommitCommandsList();
259 for (auto it
= list
.rbegin(); it
!= list
.rend(); ++it
, --i
)
261 (*it
)->setSelected(false);
262 m
[pos
+i
].setSelected(true);
265 else if (diff
< 0) // move up
268 for (auto it
= list
.begin(); it
!= list
.end(); ++it
, ++i
)
269 move_fun(&Mpd
, *it
- begin
, pos
+i
);
270 Mpd
.CommitCommandsList();
272 for (auto it
= list
.begin(); it
!= list
.end(); ++it
, ++i
)
274 (*it
)->setSelected(false);
275 m
[pos
+i
].setSelected(true);
281 template <typename F
>
282 void deleteSelectedSongs(NC::Menu
<MPD::Song
> &m
, F delete_fun
)
284 selectCurrentIfNoneSelected(m
);
285 // ok, this is tricky. we need to operate on whole playlist
286 // to get positions right, but at the same time we need to
287 // ignore all songs that are not filtered. we use the fact
288 // that both ranges share the same values, ie. we can compare
289 // pointers to check whether an item belongs to filtered range.
290 NC::Menu
<MPD::Song
>::Iterator begin
;
291 NC::Menu
<MPD::Song
>::ReverseIterator real_begin
, real_end
;
292 withUnfilteredMenu(m
, [&]() {
293 // obtain iterators for unfiltered range
294 begin
= m
.begin() + 1; // cancel reverse iterator's offset
295 real_begin
= m
.rbegin();
298 // get iterator to filtered range
299 auto cur_filtered
= m
.rbegin();
300 Mpd
.StartCommandsList();
301 for (auto it
= real_begin
; it
!= real_end
; ++it
)
303 // current iterator belongs to filtered range, proceed
304 if (&*it
== &*cur_filtered
)
306 if (it
->isSelected())
308 it
->setSelected(false);
309 delete_fun(Mpd
, it
.base() - begin
);
314 Mpd
.CommitCommandsList();
317 template <typename F
>
318 void cropPlaylist(NC::Menu
<MPD::Song
> &m
, F delete_fun
)
320 reverseSelectionHelper(m
.begin(), m
.end());
321 deleteSelectedSongs(m
, delete_fun
);
324 template <typename F
, typename G
>
325 void clearPlaylist(NC::Menu
<MPD::Song
> &m
, F delete_fun
, G clear_fun
)
329 for (auto it
= m
.begin(); it
!= m
.end(); ++it
)
330 it
->setSelected(true);
331 deleteSelectedSongs(m
, delete_fun
);
337 template <typename ItemT
>
338 std::function
<void (ItemT
)> vectorMoveInserter(std::vector
<ItemT
> &v
)
340 return [&](ItemT item
) { v
.push_back(std::move(item
)); };
343 template <typename Iterator
> std::string
getSharedDirectory(Iterator first
, Iterator last
)
345 assert(first
!= last
);
346 std::string result
= first
->getDirectory();
347 while (++first
!= last
)
349 result
= getSharedDirectory(result
, first
->getDirectory());
356 template <typename Iterator
>
357 void stringToBuffer(Iterator first
, Iterator last
, NC::BasicBuffer
<typename
Iterator::value_type
> &buf
)
359 for (auto it
= first
; it
!= last
; ++it
)
368 else if (isdigit(*it
))
370 buf
<< NC::Color(*it
-'0');
377 buf
<< NC::Format::Bold
;
380 buf
<< NC::Format::Underline
;
383 buf
<< NC::Format::AltCharset
;
386 buf
<< NC::Format::Reverse
;
397 buf
<< NC::Format::NoBold
;
400 buf
<< NC::Format::NoUnderline
;
403 buf
<< NC::Format::NoAltCharset
;
406 buf
<< NC::Format::NoReverse
;
419 else if (*it
== MPD::Song::FormatEscapeCharacter
)
421 // treat '$' as a normal character if song format escape char is prepended to it
422 if (++it
== last
|| *it
!= '$')
431 template <typename CharT
>
432 void stringToBuffer(const std::basic_string
<CharT
> &s
, NC::BasicBuffer
<CharT
> &buf
)
434 stringToBuffer(s
.begin(), s
.end(), buf
);
437 template <typename CharT
>
438 NC::BasicBuffer
<CharT
> stringToBuffer(const std::basic_string
<CharT
> &s
)
440 NC::BasicBuffer
<CharT
> result
;
441 stringToBuffer(s
, result
);
445 template <typename T
> void ShowTime(T
&buf
, size_t length
, bool short_names
)
447 const unsigned MINUTE
= 60;
448 const unsigned HOUR
= 60*MINUTE
;
449 const unsigned DAY
= 24*HOUR
;
450 const unsigned YEAR
= 365*DAY
;
452 unsigned years
= length
/YEAR
;
455 buf
<< years
<< (short_names
? "y" : (years
== 1 ? " year" : " years"));
456 length
-= years
*YEAR
;
460 unsigned days
= length
/DAY
;
463 buf
<< days
<< (short_names
? "d" : (days
== 1 ? " day" : " days"));
468 unsigned hours
= length
/HOUR
;
471 buf
<< hours
<< (short_names
? "h" : (hours
== 1 ? " hour" : " hours"));
472 length
-= hours
*HOUR
;
476 unsigned minutes
= length
/MINUTE
;
479 buf
<< minutes
<< (short_names
? "m" : (minutes
== 1 ? " minute" : " minutes"));
480 length
-= minutes
*MINUTE
;
485 buf
<< length
<< (short_names
? "s" : (length
== 1 ? " second" : " seconds"));
488 template <typename BufferT
> void ShowTag(BufferT
&buf
, const std::string
&tag
)
491 buf
<< Config
.empty_tags_color
<< Config
.empty_tag
<< NC::Color::End
;
496 template <typename SongIterator
>
497 bool addSongsToPlaylist(SongIterator first
, SongIterator last
, bool play
, int position
)
500 auto addSongNoError
= [&](SongIterator song
) -> int {
503 return Mpd
.AddSong(*song
, position
);
505 catch (MPD::ServerError
&e
)
507 Status::handleServerError(e
);
518 id
= addSongNoError(first
);
529 for(; first
!= last
; ++first
)
530 addSongNoError(first
);
536 for (; first
!= last
; --last
)
537 addSongNoError(last
);
546 inline const char *withErrors(bool success
)
548 return success
? "" : " " "(with errors)";
551 bool addSongToPlaylist(const MPD::Song
&s
, bool play
, int position
= -1);
553 std::string
Timestamp(time_t t
);
555 void markSongsInPlaylist(ProxySongList pl
);
557 std::wstring
Scroller(const std::wstring
&str
, size_t &pos
, size_t width
);
558 void writeCyclicBuffer(const NC::WBuffer
&buf
, NC::Window
&w
, size_t &start_pos
,
559 size_t width
, const std::wstring
&separator
);
561 #endif // NCMPCPP_HELPERS_H