option parser: adjust functors so no object copies are made
[ncmpcpp.git] / src / helpers.h
blobff8abd225b57cd6cfd6d611451b558cf9f9f6cc4
1 /***************************************************************************
2 * Copyright (C) 2008-2014 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 "screen.h"
27 #include "settings.h"
28 #include "status.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);
46 if (hs)
47 pl = hs->proxySongList();
48 return pl;
51 inline MPD::Song *currentSong(BaseScreen *screen)
53 MPD::Song *ptr = 0;
54 auto pl = proxySongList(screen);
55 if (pl)
56 ptr = pl.currentSong();
57 return ptr;
60 template <typename Iterator>
61 bool hasSelected(Iterator first, Iterator last)
63 for (; first != last; ++first)
64 if (first->isSelected())
65 return true;
66 return false;
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);
76 return result;
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())
87 ++result.first;
88 while (!(result.second-1)->isSelected())
89 --result.second;
91 return result;
94 template <typename T>
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);
105 if (result.empty())
106 result.push_back(current);
107 return result;
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 = [&]() {
122 if (is_filtered)
123 m.showFiltered();
125 m.showAll();
128 action();
130 catch (...)
132 cleanup();
133 throw;
135 cleanup();
138 template <typename T, typename F>
139 void withUnfilteredMenuReapplyFilter(NC::Menu<T> &m, F action)
141 auto cleanup = [&]() {
142 if (m.getFilter())
144 m.applyCurrentFilter(m.begin(), m.end());
145 if (m.empty())
147 m.clearFilter();
148 m.clearFilterResults();
152 m.showAll();
155 action();
157 catch (...)
159 cleanup();
160 throw;
162 cleanup();
165 template <typename F>
166 void moveSelectedItemsUp(NC::Menu<MPD::Song> &m, F swap_fun)
168 if (m.choice() > 0)
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();
178 if (list.size() > 1)
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);
187 else
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();
210 if (list.size() > 1)
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);
219 else
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.
236 ptrdiff_t pos = 0;
237 for (auto it = m.begin(); it != m.end(); ++it, ++pos)
238 if (&it->value() == cur_ptr)
239 break;
240 auto begin = m.begin();
241 auto list = getSelected(m.begin(), m.end());
242 // we move only truly selected items
243 if (list.empty())
244 return;
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))
248 return;
249 int diff = pos - (list.front() - begin);
250 Mpd.StartCommandsList();
251 if (diff > 0) // move down
253 pos -= list.size();
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();
258 i = list.size()-1;
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
267 size_t i = 0;
268 for (auto it = list.begin(); it != list.end(); ++it, ++i)
269 move_fun(&Mpd, *it - begin, pos+i);
270 Mpd.CommitCommandsList();
271 i = 0;
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();
296 real_end = m.rend();
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);
311 ++cur_filtered;
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)
327 if (m.isFiltered())
329 for (auto it = m.begin(); it != m.end(); ++it)
330 it->setSelected(true);
331 deleteSelectedSongs(m, delete_fun);
333 else
334 clear_fun(Mpd);
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());
350 if (result == "/")
351 break;
353 return result;
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)
361 if (*it == '$')
363 if (++it == last)
365 buf << '$';
366 break;
368 else if (isdigit(*it))
370 buf << NC::Color(*it-'0');
372 else
374 switch (*it)
376 case 'b':
377 buf << NC::Format::Bold;
378 break;
379 case 'u':
380 buf << NC::Format::Underline;
381 break;
382 case 'a':
383 buf << NC::Format::AltCharset;
384 break;
385 case 'r':
386 buf << NC::Format::Reverse;
387 break;
388 case '/':
389 if (++it == last)
391 buf << '$' << '/';
392 break;
394 switch (*it)
396 case 'b':
397 buf << NC::Format::NoBold;
398 break;
399 case 'u':
400 buf << NC::Format::NoUnderline;
401 break;
402 case 'a':
403 buf << NC::Format::NoAltCharset;
404 break;
405 case 'r':
406 buf << NC::Format::NoReverse;
407 break;
408 default:
409 buf << '$' << *--it;
410 break;
412 break;
413 default:
414 buf << *--it;
415 break;
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 != '$')
423 --it;
424 buf << *it;
426 else
427 buf << *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);
442 return 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;
453 if (years)
455 buf << years << (short_names ? "y" : (years == 1 ? " year" : " years"));
456 length -= years*YEAR;
457 if (length)
458 buf << ", ";
460 unsigned days = length/DAY;
461 if (days)
463 buf << days << (short_names ? "d" : (days == 1 ? " day" : " days"));
464 length -= days*DAY;
465 if (length)
466 buf << ", ";
468 unsigned hours = length/HOUR;
469 if (hours)
471 buf << hours << (short_names ? "h" : (hours == 1 ? " hour" : " hours"));
472 length -= hours*HOUR;
473 if (length)
474 buf << ", ";
476 unsigned minutes = length/MINUTE;
477 if (minutes)
479 buf << minutes << (short_names ? "m" : (minutes == 1 ? " minute" : " minutes"));
480 length -= minutes*MINUTE;
481 if (length)
482 buf << ", ";
484 if (length)
485 buf << length << (short_names ? "s" : (length == 1 ? " second" : " seconds"));
488 template <typename BufferT> void ShowTag(BufferT &buf, const std::string &tag)
490 if (tag.empty())
491 buf << Config.empty_tags_color << Config.empty_tag << NC::Color::End;
492 else
493 buf << tag;
496 template <typename SongIterator>
497 bool addSongsToPlaylist(SongIterator first, SongIterator last, bool play, int position)
499 bool result = true;
500 auto addSongNoError = [&](SongIterator song) -> int {
503 return Mpd.AddSong(*song, position);
505 catch (MPD::ServerError &e)
507 Status::handleServerError(e);
508 result = false;
509 return -1;
513 if (last-first >= 1)
515 int id;
516 while (true)
518 id = addSongNoError(first);
519 if (id >= 0)
520 break;
521 ++first;
522 if (first == last)
523 return result;
526 if (position == -1)
528 ++first;
529 for(; first != last; ++first)
530 addSongNoError(first);
532 else
534 ++position;
535 --last;
536 for (; first != last; --last)
537 addSongNoError(last);
539 if (play)
540 Mpd.PlayID(id);
543 return result;
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