strbuffer: change basic_buffer to BasicBuffer
[ncmpcpp.git] / src / helpers.h
blob3df02b5ba623c1e51ae171ca1d43ba141f3e5fc2
1 /***************************************************************************
2 * Copyright (C) 2008-2012 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 _HELPERS_H
22 #define _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/wide_string.h"
31 inline HasSongs *hasSongs(BasicScreen *screen)
33 return dynamic_cast<HasSongs *>(screen);
36 inline std::shared_ptr<ProxySongList> proxySongList(BasicScreen *screen)
38 auto ptr = nullProxySongList();
39 auto hs = hasSongs(screen);
40 if (hs)
41 ptr = hs->getProxySongList();
42 return ptr;
45 inline MPD::Song *currentSong(BasicScreen *screen)
47 MPD::Song *ptr = 0;
48 auto pl = proxySongList(screen);
49 if (pl)
50 ptr = pl->currentSong();
51 return ptr;
54 template <typename Iterator>
55 bool hasSelected(Iterator first, Iterator last)
57 for (; first != last; ++first)
58 if (first->isSelected())
59 return true;
60 return false;
63 template <typename Iterator>
64 std::vector<Iterator> getSelected(Iterator first, Iterator last)
66 std::vector<Iterator> result;
67 for (; first != last; ++first)
68 if (first->isSelected())
69 result.push_back(first);
70 return result;
73 template <typename T>
74 void selectCurrentIfNoneSelected(NC::Menu<T> &m)
76 if (!hasSelected(m.begin(), m.end()))
77 m.current().setSelected(true);
80 template <typename Iterator>
81 std::vector<Iterator> getSelectedOrCurrent(Iterator first, Iterator last, Iterator current)
83 std::vector<Iterator> result = getSelected(first, last);
84 if (result.empty())
85 result.push_back(current);
86 return result;
89 template <typename Iterator>
90 void reverseSelectionHelper(Iterator first, Iterator last)
92 for (; first != last; ++first)
93 first->setSelected(!first->isSelected());
96 template <typename T, typename F>
97 void withUnfilteredMenu(NC::Menu<T> &m, F action)
99 bool is_filtered = m.isFiltered();
100 m.showAll();
101 action();
102 if (is_filtered)
103 m.showFiltered();
106 template <typename T, typename F>
107 void withUnfilteredMenuReapplyFilter(NC::Menu<T> &m, F action)
109 m.showAll();
110 action();
111 if (m.getFilter())
113 m.applyCurrentFilter(m.begin(), m.end());
114 if (m.empty())
116 m.clearFilter();
117 m.clearFilterResults();
122 template <typename F>
123 void moveSelectedItemsUp(NC::Menu<MPD::Song> &m, F swap_fun)
125 if (m.choice() > 0)
126 selectCurrentIfNoneSelected(m);
127 auto list = getSelected(m.begin(), m.end());
128 auto begin = m.begin();
129 if (!list.empty() && list.front() != m.begin())
131 Mpd.StartCommandsList();
132 for (auto it = list.begin(); it != list.end(); ++it)
133 swap_fun(Mpd, *it - begin, *it - begin - 1);
134 if (Mpd.CommitCommandsList())
136 if (list.size() > 1)
138 for (auto it = list.begin(); it != list.end(); ++it)
140 (*it)->setSelected(false);
141 (*it-1)->setSelected(true);
143 m.highlight(list[(list.size())/2] - begin - 1);
145 else
147 // if we move only one item, do not select it. however, if single item
148 // was selected prior to move, it'll deselect it. oh well.
149 list[0]->setSelected(false);
150 m.scroll(NC::wUp);
156 template <typename F>
157 void moveSelectedItemsDown(NC::Menu<MPD::Song> &m, F swap_fun)
159 if (m.choice() < m.size()-1)
160 selectCurrentIfNoneSelected(m);
161 auto list = getSelected(m.rbegin(), m.rend());
162 auto begin = m.begin() + 1; // reverse iterators add 1, so we need to cancel it
163 if (!list.empty() && list.front() != m.rbegin())
165 Mpd.StartCommandsList();
166 for (auto it = list.begin(); it != list.end(); ++it)
167 swap_fun(Mpd, it->base() - begin, it->base() - begin + 1);
168 if (Mpd.CommitCommandsList())
170 if (list.size() > 1)
172 for (auto it = list.begin(); it != list.end(); ++it)
174 (*it)->setSelected(false);
175 (*it-1)->setSelected(true);
177 m.highlight(list[(list.size())/2].base() - begin + 1);
179 else
181 // if we move only one item, do not select it. however, if single item
182 // was selected prior to move, it'll deselect it. oh well.
183 list[0]->setSelected(false);
184 m.scroll(NC::wDown);
190 template <typename F>
191 void moveSelectedItemsTo(NC::Menu<MPD::Song> &m, F move_fun)
193 auto cur_ptr = &m.current().value();
194 withUnfilteredMenu(m, [&]() {
195 // this is kinda shitty, but there is no other way to know
196 // what position current item has in unfiltered menu.
197 ptrdiff_t pos = 0;
198 for (auto it = m.begin(); it != m.end(); ++it, ++pos)
199 if (&it->value() == cur_ptr)
200 break;
201 auto begin = m.begin();
202 auto list = getSelected(m.begin(), m.end());
203 // we move only truly selected items
204 if (list.empty())
205 return;
206 // we can't move to the middle of selected items
207 //(this also handles case when list.size() == 1)
208 if (pos >= (list.front() - begin) && pos <= (list.back() - begin))
209 return;
210 int diff = pos - (list.front() - begin);
211 Mpd.StartCommandsList();
212 if (diff > 0) // move down
214 pos -= list.size();
215 size_t i = list.size()-1;
216 for (auto it = list.rbegin(); it != list.rend(); ++it, --i)
217 move_fun(Mpd, *it - begin, pos+i);
218 if (Mpd.CommitCommandsList())
220 i = list.size()-1;
221 for (auto it = list.rbegin(); it != list.rend(); ++it, --i)
223 (*it)->setSelected(false);
224 m[pos+i].setSelected(true);
228 else if (diff < 0) // move up
230 size_t i = 0;
231 for (auto it = list.begin(); it != list.end(); ++it, ++i)
232 move_fun(Mpd, *it - begin, pos+i);
233 if (Mpd.CommitCommandsList())
235 i = 0;
236 for (auto it = list.begin(); it != list.end(); ++it, ++i)
238 (*it)->setSelected(false);
239 m[pos+i].setSelected(true);
246 template <typename F>
247 bool deleteSelectedSongs(NC::Menu<MPD::Song> &m, F delete_fun)
249 bool result = false;
250 selectCurrentIfNoneSelected(m);
251 // ok, this is tricky. we need to operate on whole playlist
252 // to get positions right, but at the same time we need to
253 // ignore all songs that are not filtered. we use the fact
254 // that both ranges share the same values, ie. we can compare
255 // pointers to check whether an item belongs to filtered range.
256 NC::Menu<MPD::Song>::Iterator begin;
257 NC::Menu<MPD::Song>::ReverseIterator real_begin, real_end;
258 withUnfilteredMenu(m, [&]() {
259 // obtain iterators for unfiltered range
260 begin = m.begin() + 1; // cancel reverse iterator's offset
261 real_begin = m.rbegin();
262 real_end = m.rend();
264 // get iterator to filtered range
265 auto cur_filtered = m.rbegin();
266 Mpd.StartCommandsList();
267 for (auto it = real_begin; it != real_end; ++it)
269 // current iterator belongs to filtered range, proceed
270 if (&*it == &*cur_filtered)
272 if (it->isSelected())
274 it->setSelected(false);
275 delete_fun(Mpd, it.base() - begin);
277 ++cur_filtered;
280 if (Mpd.CommitCommandsList())
281 result = true;
282 return result;
285 template <typename F>
286 bool cropPlaylist(NC::Menu<MPD::Song> &m, F delete_fun)
288 reverseSelectionHelper(m.begin(), m.end());
289 return deleteSelectedSongs(m, delete_fun);
292 template <typename F, typename G>
293 bool clearPlaylist(NC::Menu<MPD::Song> &m, F delete_fun, G clear_fun)
295 bool result = false;
296 if (m.isFiltered())
298 for (auto it = m.begin(); it != m.end(); ++it)
299 it->setSelected(true);
300 result = deleteSelectedSongs(m, delete_fun);
302 else
303 result = clear_fun(Mpd);
304 return result;
307 template <typename Iterator> std::string getSharedDirectory(Iterator first, Iterator last)
309 assert(first != last);
310 std::string result = first->getDirectory();
311 while (++first != last)
313 result = getSharedDirectory(result, first->getDirectory());
314 if (result == "/")
315 break;
317 return result;
320 template <typename T> struct StringConverter {
321 const char *operator()(const char *s) { return s; }
323 template <> struct StringConverter<NC::WBuffer> {
324 std::wstring operator()(const char *s) { return ToWString(s); }
326 template <> struct StringConverter<NC::Scrollpad> {
327 std::wstring operator()(const char *s) { return ToWString(s); }
330 template <typename Iterator>
331 void stringToBuffer(Iterator first, Iterator last, NC::BasicBuffer<typename Iterator::value_type> &buf)
333 for (auto it = first; it != last; ++it)
335 if (*it == '$')
337 if (++it == last)
339 buf << '$';
340 break;
342 else if (isdigit(*it))
344 buf << NC::Color(*it-'0');
346 else
348 switch (*it)
350 case 'b':
351 buf << NC::fmtBold;
352 break;
353 case 'u':
354 buf << NC::fmtUnderline;
355 break;
356 case 'a':
357 buf << NC::fmtAltCharset;
358 break;
359 case 'r':
360 buf << NC::fmtReverse;
361 break;
362 case '/':
363 if (++it == last)
365 buf << '$' << '/';
366 break;
368 switch (*it)
370 case 'b':
371 buf << NC::fmtBoldEnd;
372 break;
373 case 'u':
374 buf << NC::fmtUnderlineEnd;
375 break;
376 case 'a':
377 buf << NC::fmtAltCharsetEnd;
378 break;
379 case 'r':
380 buf << NC::fmtReverseEnd;
381 break;
382 default:
383 buf << '$' << *--it;
384 break;
386 break;
387 default:
388 buf << *--it;
389 break;
393 else if (*it == MPD::Song::FormatEscapeCharacter)
395 // treat '$' as a normal character if song format escape char is prepended to it
396 if (++it == last || *it != '$')
397 --it;
398 buf << *it;
400 else
401 buf << *it;
405 template <typename CharT>
406 void stringToBuffer(const std::basic_string<CharT> &s, NC::BasicBuffer<CharT> &buf)
408 stringToBuffer(s.begin(), s.end(), buf);
411 template <typename T> void ShowTime(T &buf, size_t length, bool short_names)
413 StringConverter<T> cnv;
415 const unsigned MINUTE = 60;
416 const unsigned HOUR = 60*MINUTE;
417 const unsigned DAY = 24*HOUR;
418 const unsigned YEAR = 365*DAY;
420 unsigned years = length/YEAR;
421 if (years)
423 buf << years << cnv(short_names ? "y" : (years == 1 ? " year" : " years"));
424 length -= years*YEAR;
425 if (length)
426 buf << cnv(", ");
428 unsigned days = length/DAY;
429 if (days)
431 buf << days << cnv(short_names ? "d" : (days == 1 ? " day" : " days"));
432 length -= days*DAY;
433 if (length)
434 buf << cnv(", ");
436 unsigned hours = length/HOUR;
437 if (hours)
439 buf << hours << cnv(short_names ? "h" : (hours == 1 ? " hour" : " hours"));
440 length -= hours*HOUR;
441 if (length)
442 buf << cnv(", ");
444 unsigned minutes = length/MINUTE;
445 if (minutes)
447 buf << minutes << cnv(short_names ? "m" : (minutes == 1 ? " minute" : " minutes"));
448 length -= minutes*MINUTE;
449 if (length)
450 buf << cnv(", ");
452 if (length)
453 buf << length << cnv(short_names ? "s" : (length == 1 ? " second" : " seconds"));
456 template <typename T> void ShowTag(T &buf, const std::string &tag)
458 if (tag.empty())
459 buf << Config.empty_tags_color << Config.empty_tag << NC::clEnd;
460 else
461 buf << tag;
464 std::string Timestamp(time_t t);
466 void markSongsInPlaylist(std::shared_ptr<ProxySongList> pl);
468 std::wstring Scroller(const std::wstring &str, size_t &pos, size_t width);
470 #endif