actions: use unique_ptr for storing actions
[ncmpcpp.git] / src / helpers.h
blobe275504bf36f44d953e7b2d75d6c0317ec9d2ea8
1 /***************************************************************************
2 * Copyright (C) 2008-2016 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
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. *
9 * *
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. *
14 * *
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"
25 #include "mpdpp.h"
26 #include "playlist.h"
27 #include "screen.h"
28 #include "settings.h"
29 #include "song_list.h"
30 #include "status.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();
44 if (m_is_filtered)
45 m_menu.showAllItems();
48 ~ScopedUnfilteredMenu()
50 if (m_is_filtered)
52 switch (m_reapply_filter)
54 case ReapplyFilter::Yes:
55 m_menu.reapplyFilter();
56 break;
57 case ReapplyFilter::No:
58 m_menu.showFilteredItems();
59 break;
62 if (m_refresh)
63 m_menu.refresh();
66 void set(ReapplyFilter reapply_filter, bool refresh)
68 m_reapply_filter = reapply_filter;
69 m_refresh = refresh;
72 private:
73 bool m_is_filtered;
74 bool m_refresh;
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)
83 if (begin == end)
85 assert(current == end);
86 return begin;
88 if (skip_current)
89 ++current;
90 auto it = std::find_if(current, end, pred);
91 if (it == end && wrap)
93 it = std::find_if(begin, current, pred);
94 if (it == current)
95 it = end;
97 return it;
100 template <typename ItemT, typename PredicateT>
101 bool search(NC::Menu<ItemT> &m, const PredicateT &pred,
102 SearchDirection direction, bool wrap, bool skip_current)
104 bool result = false;
105 if (pred.defined())
107 switch (direction)
109 case SearchDirection::Backward:
111 auto it = wrappedSearch(m.rbegin(), m.rcurrent(), m.rend(),
112 pred, wrap, skip_current
114 if (it != m.rend())
116 m.highlight(it.base()-m.begin()-1);
117 result = true;
119 break;
121 case SearchDirection::Forward:
123 auto it = wrappedSearch(m.begin(), m.current(), m.end(),
124 pred, wrap, skip_current
126 if (it != m.end())
128 m.highlight(it-m.begin());
129 result = true;
134 return result;
137 template <typename Iterator>
138 bool hasSelected(Iterator first, Iterator last)
140 for (; first != last; ++first)
141 if (first->isSelected())
142 return true;
143 return false;
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);
153 return result;
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
158 /// == last).
159 template <typename Iterator>
160 bool findRange(Iterator &first, Iterator &last)
162 for (; first != last; ++first)
164 if (first->isSelected())
165 break;
167 if (first == last)
168 return false;
169 --last;
170 for (; first != last; --last)
172 if (last->isSelected())
173 break;
175 ++last;
176 return true;
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.
188 if (first == last)
190 first = orig_first;
191 return true;
193 else
194 return false;
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())
200 return false;
202 return true;
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);
216 if (result.empty())
217 result.push_back(current);
218 return result;
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)
231 if (m.choice() > 0)
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();
241 if (list.size() > 1)
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);
250 else
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();
273 if (list.size() > 1)
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);
282 else
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.
299 ptrdiff_t pos = 0;
300 for (auto it = menu.begin(); it != menu.end(); ++it, ++pos)
301 if (&it->value() == cur_ptr)
302 break;
303 auto begin = menu.begin();
304 auto list = getSelected(menu.begin(), menu.end());
305 // we move only truly selected items
306 if (list.empty())
307 return;
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))
311 return;
312 int diff = pos - (list.front() - begin);
313 Mpd.StartCommandsList();
314 if (diff > 0) // move down
316 pos -= list.size();
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();
321 i = list.size()-1;
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
330 size_t i = 0;
331 for (auto it = list.begin(); it != list.end(); ++it, ++i)
332 move_fun(&Mpd, *it - begin, pos+i);
333 Mpd.CommitCommandsList();
334 i = 0;
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());
380 if (result == "/")
381 break;
383 return result;
386 template <typename ListT>
387 void markSongsInPlaylist(ListT &list)
389 ScopedUnfilteredMenu<typename ListT::Item::Type> sunfilter(ReapplyFilter::No, list);
390 MPD::Song *s;
391 for (auto &p : static_cast<SongList &>(list))
393 s = p.get<Bit::Song>();
394 if (s != nullptr)
395 p.get<Bit::Properties>().setBold(myPlaylist->checkForSong(*s));
399 template <typename Iterator>
400 bool addSongsToPlaylist(Iterator first, Iterator last, bool play, int position)
402 bool result = true;
403 auto addSongNoError = [&](Iterator it) -> int {
406 return Mpd.AddSong(*it, position);
408 catch (MPD::ServerError &e)
410 Status::handleServerError(e);
411 result = false;
412 return -1;
416 if (last-first >= 1)
418 int id;
419 while (true)
421 id = addSongNoError(first);
422 if (id >= 0)
423 break;
424 ++first;
425 if (first == last)
426 return result;
429 if (position == -1)
431 ++first;
432 for(; first != last; ++first)
433 addSongNoError(first);
435 else
437 ++position;
438 --last;
439 for (; first != last; --last)
440 addSongNoError(last);
442 if (play)
443 Mpd.PlayID(id);
446 return result;
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;
457 if (years)
459 buf << years << (short_names ? "y" : (years == 1 ? " year" : " years"));
460 length -= years*YEAR;
461 if (length)
462 buf << ", ";
464 unsigned days = length/DAY;
465 if (days)
467 buf << days << (short_names ? "d" : (days == 1 ? " day" : " days"));
468 length -= days*DAY;
469 if (length)
470 buf << ", ";
472 unsigned hours = length/HOUR;
473 if (hours)
475 buf << hours << (short_names ? "h" : (hours == 1 ? " hour" : " hours"));
476 length -= hours*HOUR;
477 if (length)
478 buf << ", ";
480 unsigned minutes = length/MINUTE;
481 if (minutes)
483 buf << minutes << (short_names ? "m" : (minutes == 1 ? " minute" : " minutes"));
484 length -= minutes*MINUTE;
485 if (length)
486 buf << ", ";
488 if (length)
489 buf << length << (short_names ? "s" : (length == 1 ? " second" : " seconds"));
492 template <typename BufferT> void ShowTag(BufferT &buf, const std::string &tag)
494 if (tag.empty())
495 buf << Config.empty_tags_color << Config.empty_tag << NC::Color::End;
496 else
497 buf << tag;
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