1 /***************************************************************************
2 * Copyright (C) 2008-2016 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 "song_list.h"
31 #include "utility/string.h"
32 #include "utility/type_conversions.h"
33 #include "utility/wide_string.h"
35 enum ReapplyFilter
{ Yes
, No
};
37 template <typename ItemT
>
38 struct ScopedUnfilteredMenu
40 ScopedUnfilteredMenu(ReapplyFilter reapply_filter
, NC::Menu
<ItemT
> &menu
)
41 : m_refresh(false), m_reapply_filter(reapply_filter
), m_menu(menu
)
43 m_is_filtered
= m_menu
.isFiltered();
45 m_menu
.showAllItems();
48 ~ScopedUnfilteredMenu()
52 switch (m_reapply_filter
)
54 case ReapplyFilter::Yes
:
55 m_menu
.reapplyFilter();
57 case ReapplyFilter::No
:
58 m_menu
.showFilteredItems();
66 void set(ReapplyFilter reapply_filter
, bool refresh
)
68 m_reapply_filter
= reapply_filter
;
75 ReapplyFilter m_reapply_filter
;
76 NC::Menu
<ItemT
> &m_menu
;
79 template <typename Iterator
, typename PredicateT
>
80 Iterator
wrappedSearch(Iterator begin
, Iterator current
, Iterator end
,
81 const PredicateT
&pred
, bool wrap
, bool skip_current
)
85 assert(current
== end
);
90 auto it
= std::find_if(current
, end
, pred
);
91 if (it
== end
&& wrap
)
93 it
= std::find_if(begin
, current
, pred
);
100 template <typename ItemT
, typename PredicateT
>
101 bool search(NC::Menu
<ItemT
> &m
, const PredicateT
&pred
,
102 SearchDirection direction
, bool wrap
, bool skip_current
)
109 case SearchDirection::Backward
:
111 auto it
= wrappedSearch(m
.rbegin(), m
.rcurrent(), m
.rend(),
112 pred
, wrap
, skip_current
116 m
.highlight(it
.base()-m
.begin()-1);
121 case SearchDirection::Forward
:
123 auto it
= wrappedSearch(m
.begin(), m
.current(), m
.end(),
124 pred
, wrap
, skip_current
128 m
.highlight(it
-m
.begin());
137 template <typename Iterator
>
138 bool hasSelected(Iterator first
, Iterator last
)
140 for (; first
!= last
; ++first
)
141 if (first
->isSelected())
146 template <typename Iterator
>
147 std::vector
<Iterator
> getSelected(Iterator first
, Iterator last
)
149 std::vector
<Iterator
> result
;
150 for (; first
!= last
; ++first
)
151 if (first
->isSelected())
152 result
.push_back(first
);
156 /// @return true if range that begins and ends with selected items was
157 /// found, false when there is no selected items (in which case first
159 template <typename Iterator
>
160 bool findRange(Iterator
&first
, Iterator
&last
)
162 for (; first
!= last
; ++first
)
164 if (first
->isSelected())
170 for (; first
!= last
; --last
)
172 if (last
->isSelected())
179 /// @return true if fully selected range was found or no selected
180 /// items were found, false otherwise.
181 template <typename Iterator
>
182 bool findSelectedRange(Iterator
&first
, Iterator
&last
)
184 auto orig_first
= first
;
185 if (!findRange(first
, last
))
187 // If no selected items were found, return original range.
196 // We have range, now check if it's filled with selected items.
197 for (auto it
= first
; it
!= last
; ++it
)
199 if (!it
->isSelected())
205 template <typename T
>
206 void selectCurrentIfNoneSelected(NC::Menu
<T
> &m
)
208 if (!hasSelected(m
.begin(), m
.end()))
209 m
.current()->setSelected(true);
212 template <typename Iterator
>
213 std::vector
<Iterator
> getSelectedOrCurrent(Iterator first
, Iterator last
, Iterator current
)
215 std::vector
<Iterator
> result
= getSelected(first
, last
);
217 result
.push_back(current
);
221 template <typename Iterator
>
222 void reverseSelectionHelper(Iterator first
, Iterator last
)
224 for (; first
!= last
; ++first
)
225 first
->setSelected(!first
->isSelected());
228 template <typename F
>
229 void moveSelectedItemsUp(NC::Menu
<MPD::Song
> &m
, F swap_fun
)
232 selectCurrentIfNoneSelected(m
);
233 auto list
= getSelected(m
.begin(), m
.end());
234 auto begin
= m
.begin();
235 if (!list
.empty() && list
.front() != m
.begin())
237 Mpd
.StartCommandsList();
238 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
239 swap_fun(&Mpd
, *it
- begin
, *it
- begin
- 1);
240 Mpd
.CommitCommandsList();
243 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
245 (*it
)->setSelected(false);
246 (*it
-1)->setSelected(true);
248 m
.highlight(list
[(list
.size())/2] - begin
- 1);
252 // if we move only one item, do not select it. however, if single item
253 // was selected prior to move, it'll deselect it. oh well.
254 list
[0]->setSelected(false);
255 m
.scroll(NC::Scroll::Up
);
260 template <typename F
>
261 void moveSelectedItemsDown(NC::Menu
<MPD::Song
> &m
, F swap_fun
)
263 if (m
.choice() < m
.size()-1)
264 selectCurrentIfNoneSelected(m
);
265 auto list
= getSelected(m
.rbegin(), m
.rend());
266 auto begin
= m
.begin() + 1; // reverse iterators add 1, so we need to cancel it
267 if (!list
.empty() && list
.front() != m
.rbegin())
269 Mpd
.StartCommandsList();
270 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
271 swap_fun(&Mpd
, it
->base() - begin
, it
->base() - begin
+ 1);
272 Mpd
.CommitCommandsList();
275 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
277 (*it
)->setSelected(false);
278 (*it
-1)->setSelected(true);
280 m
.highlight(list
[(list
.size())/2].base() - begin
+ 1);
284 // if we move only one item, do not select it. however, if single item
285 // was selected prior to move, it'll deselect it. oh well.
286 list
[0]->setSelected(false);
287 m
.scroll(NC::Scroll::Down
);
292 template <typename F
>
293 void moveSelectedItemsTo(NC::Menu
<MPD::Song
> &menu
, F
&&move_fun
)
295 auto cur_ptr
= &menu
.current()->value();
296 ScopedUnfilteredMenu
<MPD::Song
> sunfilter(ReapplyFilter::No
, menu
);
297 // this is kinda shitty, but there is no other way to know
298 // what position current item has in unfiltered menu.
300 for (auto it
= menu
.begin(); it
!= menu
.end(); ++it
, ++pos
)
301 if (&it
->value() == cur_ptr
)
303 auto begin
= menu
.begin();
304 auto list
= getSelected(menu
.begin(), menu
.end());
305 // we move only truly selected items
308 // we can't move to the middle of selected items
309 //(this also handles case when list.size() == 1)
310 if (pos
>= (list
.front() - begin
) && pos
<= (list
.back() - begin
))
312 int diff
= pos
- (list
.front() - begin
);
313 Mpd
.StartCommandsList();
314 if (diff
> 0) // move down
317 size_t i
= list
.size()-1;
318 for (auto it
= list
.rbegin(); it
!= list
.rend(); ++it
, --i
)
319 move_fun(&Mpd
, *it
- begin
, pos
+i
);
320 Mpd
.CommitCommandsList();
322 for (auto it
= list
.rbegin(); it
!= list
.rend(); ++it
, --i
)
324 (*it
)->setSelected(false);
325 menu
[pos
+i
].setSelected(true);
328 else if (diff
< 0) // move up
331 for (auto it
= list
.begin(); it
!= list
.end(); ++it
, ++i
)
332 move_fun(&Mpd
, *it
- begin
, pos
+i
);
333 Mpd
.CommitCommandsList();
335 for (auto it
= list
.begin(); it
!= list
.end(); ++it
, ++i
)
337 (*it
)->setSelected(false);
338 menu
[pos
+i
].setSelected(true);
343 template <typename F
>
344 void deleteSelectedSongs(NC::Menu
<MPD::Song
> &m
, F delete_fun
)
346 selectCurrentIfNoneSelected(m
);
347 // ok, this is tricky. we need to operate on whole playlist
348 // to get positions right, but at the same time we need to
349 // ignore all songs that are not filtered. we use the fact
350 // that both ranges share the same values, ie. we can compare
351 // pointers to check whether an item belongs to filtered range.
352 auto begin
= ++m
.begin();
353 Mpd
.StartCommandsList();
354 for (auto it
= m
.rbegin(); it
!= m
.rend(); ++it
)
356 if (it
->isSelected())
358 it
->setSelected(false);
359 delete_fun(Mpd
, it
.base() - begin
);
362 Mpd
.CommitCommandsList();
365 template <typename F
>
366 void cropPlaylist(NC::Menu
<MPD::Song
> &m
, F delete_fun
)
368 reverseSelectionHelper(m
.begin(), m
.end());
369 deleteSelectedSongs(m
, delete_fun
);
372 template <typename Iterator
>
373 std::string
getSharedDirectory(Iterator first
, Iterator last
)
375 assert(first
!= last
);
376 std::string result
= first
->getDirectory();
377 while (++first
!= last
)
379 result
= getSharedDirectory(result
, first
->getDirectory());
386 template <typename ListT
>
387 void markSongsInPlaylist(ListT
&list
)
389 ScopedUnfilteredMenu
<typename
ListT::Item::Type
> sunfilter(ReapplyFilter::No
, list
);
391 for (auto &p
: static_cast<SongList
&>(list
))
393 s
= p
.get
<Bit::Song
>();
395 p
.get
<Bit::Properties
>().setBold(myPlaylist
->checkForSong(*s
));
399 template <typename Iterator
>
400 bool addSongsToPlaylist(Iterator first
, Iterator last
, bool play
, int position
)
403 auto addSongNoError
= [&](Iterator it
) -> int {
406 return Mpd
.AddSong(*it
, position
);
408 catch (MPD::ServerError
&e
)
410 Status::handleServerError(e
);
421 id
= addSongNoError(first
);
432 for(; first
!= last
; ++first
)
433 addSongNoError(first
);
439 for (; first
!= last
; --last
)
440 addSongNoError(last
);
449 template <typename T
> void ShowTime(T
&buf
, size_t length
, bool short_names
)
451 const unsigned MINUTE
= 60;
452 const unsigned HOUR
= 60*MINUTE
;
453 const unsigned DAY
= 24*HOUR
;
454 const unsigned YEAR
= 365*DAY
;
456 unsigned years
= length
/YEAR
;
459 buf
<< years
<< (short_names
? "y" : (years
== 1 ? " year" : " years"));
460 length
-= years
*YEAR
;
464 unsigned days
= length
/DAY
;
467 buf
<< days
<< (short_names
? "d" : (days
== 1 ? " day" : " days"));
472 unsigned hours
= length
/HOUR
;
475 buf
<< hours
<< (short_names
? "h" : (hours
== 1 ? " hour" : " hours"));
476 length
-= hours
*HOUR
;
480 unsigned minutes
= length
/MINUTE
;
483 buf
<< minutes
<< (short_names
? "m" : (minutes
== 1 ? " minute" : " minutes"));
484 length
-= minutes
*MINUTE
;
489 buf
<< length
<< (short_names
? "s" : (length
== 1 ? " second" : " seconds"));
492 template <typename BufferT
> void ShowTag(BufferT
&buf
, const std::string
&tag
)
495 buf
<< Config
.empty_tags_color
<< Config
.empty_tag
<< NC::Color::End
;
500 inline const char *withErrors(bool success
)
502 return success
? "" : " " "(with errors)";
505 bool addSongToPlaylist(const MPD::Song
&s
, bool play
, int position
= -1);
507 const MPD::Song
*currentSong(const BaseScreen
*screen
);
509 MPD::SongIterator
getDatabaseIterator(MPD::Connection
&mpd
);
511 std::string
timeFormat(const char *format
, time_t t
);
513 std::string
Timestamp(time_t t
);
515 std::wstring
Scroller(const std::wstring
&str
, size_t &pos
, size_t width
);
516 void writeCyclicBuffer(const NC::WBuffer
&buf
, NC::Window
&w
, size_t &start_pos
,
517 size_t width
, const std::wstring
&separator
);
519 #endif // NCMPCPP_HELPERS_H