Fix InternetLyricsFetcher
[ncmpcpp.git] / src / helpers.h
blob5cef8f067693078fd14729576deb26a3208e9c76
1 /***************************************************************************
2 * Copyright (C) 2008-2017 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 "screens/playlist.h"
27 #include "screens/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> &menu, F &&delete_fun)
346 selectCurrentIfNoneSelected(menu);
347 // We need to operate on the whole playlist to get positions right, but at the
348 // same time we need to ignore all songs that are not filtered. We abuse the
349 // fact that both ranges share the same values, i.e. we can compare addresses
350 // of item values to check whether an item belongs to filtered range. TODO: do
351 // something more sane here.
352 NC::Menu<MPD::Song>::Iterator begin;
353 NC::Menu<MPD::Song>::ReverseIterator real_begin, real_end;
355 ScopedUnfilteredMenu<MPD::Song> sunfilter(ReapplyFilter::No, menu);
356 // obtain iterators for unfiltered range
357 begin = menu.begin() + 1; // cancel reverse iterator's offset
358 real_begin = menu.rbegin();
359 real_end = menu.rend();
361 // get iterator to filtered range
362 auto cur_filtered = menu.rbegin();
363 Mpd.StartCommandsList();
364 for (auto it = real_begin; it != real_end; ++it)
366 // current iterator belongs to filtered range, proceed
367 if (&it->value() == &cur_filtered->value())
369 if (it->isSelected())
371 it->setSelected(false);
372 delete_fun(Mpd, it.base() - begin);
374 ++cur_filtered;
377 Mpd.CommitCommandsList();
380 template <typename F>
381 void cropPlaylist(NC::Menu<MPD::Song> &m, F delete_fun)
383 reverseSelectionHelper(m.begin(), m.end());
384 deleteSelectedSongs(m, delete_fun);
387 template <typename Iterator>
388 std::string getSharedDirectory(Iterator first, Iterator last)
390 assert(first != last);
391 std::string result = first->getDirectory();
392 while (++first != last)
394 result = getSharedDirectory(result, first->getDirectory());
395 if (result == "/")
396 break;
398 return result;
401 template <typename Iterator>
402 bool addSongsToPlaylist(Iterator first, Iterator last, bool play, int position)
404 bool result = true;
405 auto addSongNoError = [&](Iterator it) -> int {
408 return Mpd.AddSong(*it, position);
410 catch (MPD::ServerError &e)
412 Status::handleServerError(e);
413 result = false;
414 return -1;
418 if (last-first >= 1)
420 int id;
421 while (true)
423 id = addSongNoError(first);
424 if (id >= 0)
425 break;
426 ++first;
427 if (first == last)
428 return result;
431 if (position == -1)
433 ++first;
434 for(; first != last; ++first)
435 addSongNoError(first);
437 else
439 ++position;
440 --last;
441 for (; first != last; --last)
442 addSongNoError(last);
444 if (play)
445 Mpd.PlayID(id);
448 return result;
451 template <typename T> void ShowTime(T &buf, size_t length, bool short_names)
453 const unsigned MINUTE = 60;
454 const unsigned HOUR = 60*MINUTE;
455 const unsigned DAY = 24*HOUR;
456 const unsigned YEAR = 365*DAY;
458 unsigned years = length/YEAR;
459 if (years)
461 buf << years << (short_names ? "y" : (years == 1 ? " year" : " years"));
462 length -= years*YEAR;
463 if (length)
464 buf << ", ";
466 unsigned days = length/DAY;
467 if (days)
469 buf << days << (short_names ? "d" : (days == 1 ? " day" : " days"));
470 length -= days*DAY;
471 if (length)
472 buf << ", ";
474 unsigned hours = length/HOUR;
475 if (hours)
477 buf << hours << (short_names ? "h" : (hours == 1 ? " hour" : " hours"));
478 length -= hours*HOUR;
479 if (length)
480 buf << ", ";
482 unsigned minutes = length/MINUTE;
483 if (minutes)
485 buf << minutes << (short_names ? "m" : (minutes == 1 ? " minute" : " minutes"));
486 length -= minutes*MINUTE;
487 if (length)
488 buf << ", ";
490 if (length)
491 buf << length << (short_names ? "s" : (length == 1 ? " second" : " seconds"));
494 template <typename BufferT>
495 void ShowTag(BufferT &buf, const std::string &tag)
497 if (tag.empty())
498 buf << Config.empty_tags_color
499 << Config.empty_tag
500 << NC::FormattedColor::End(Config.empty_tags_color);
501 else
502 buf << tag;
505 inline NC::Buffer ShowTag(const std::string &tag)
507 NC::Buffer result;
508 ShowTag(result, tag);
509 return result;
512 inline const char *withErrors(bool success)
514 return success ? "" : " " "(with errors)";
517 bool addSongToPlaylist(const MPD::Song &s, bool play, int position = -1);
519 const MPD::Song *currentSong(const BaseScreen *screen);
521 MPD::SongIterator getDatabaseIterator(MPD::Connection &mpd);
523 std::string timeFormat(const char *format, time_t t);
525 std::string Timestamp(time_t t);
527 std::wstring Scroller(const std::wstring &str, size_t &pos, size_t width);
528 void writeCyclicBuffer(const NC::WBuffer &buf, NC::Window &w, size_t &start_pos,
529 size_t width, const std::wstring &separator);
531 #endif // NCMPCPP_HELPERS_H