You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
403 lines
10 KiB
403 lines
10 KiB
/*************************************************************************** |
|
* Copyright (C) 2008-2012 by Andrzej Rybczak * |
|
* electricityispower@gmail.com * |
|
* * |
|
* This program is free software; you can redistribute it and/or modify * |
|
* it under the terms of the GNU General Public License as published by * |
|
* the Free Software Foundation; either version 2 of the License, or * |
|
* (at your option) any later version. * |
|
* * |
|
* This program is distributed in the hope that it will be useful, * |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of * |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * |
|
* GNU General Public License for more details. * |
|
* * |
|
* You should have received a copy of the GNU General Public License * |
|
* along with this program; if not, write to the * |
|
* Free Software Foundation, Inc., * |
|
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * |
|
***************************************************************************/ |
|
|
|
#ifndef _HELPERS_H |
|
#define _HELPERS_H |
|
|
|
#include "interfaces.h" |
|
#include "mpdpp.h" |
|
#include "screen.h" |
|
#include "settings.h" |
|
#include "status.h" |
|
|
|
inline HasSongs *hasSongs(BasicScreen *screen) |
|
{ |
|
return dynamic_cast<HasSongs *>(screen); |
|
} |
|
|
|
inline std::shared_ptr<ProxySongList> proxySongList(BasicScreen *screen) |
|
{ |
|
auto ptr = nullProxySongList(); |
|
auto hs = hasSongs(screen); |
|
if (hs) |
|
ptr = hs->getProxySongList(); |
|
return ptr; |
|
} |
|
|
|
inline MPD::Song *currentSong(BasicScreen *screen) |
|
{ |
|
MPD::Song *ptr = 0; |
|
auto pl = proxySongList(screen); |
|
if (pl) |
|
ptr = pl->currentSong(); |
|
return ptr; |
|
} |
|
|
|
template <typename Iterator> bool hasSelected(Iterator first, Iterator last) |
|
{ |
|
for (; first != last; ++first) |
|
if (first->isSelected()) |
|
return true; |
|
return false; |
|
} |
|
|
|
template <typename Iterator> std::vector<Iterator> getSelected(Iterator first, Iterator last) |
|
{ |
|
std::vector<Iterator> result; |
|
for (; first != last; ++first) |
|
if (first->isSelected()) |
|
result.push_back(first); |
|
return result; |
|
} |
|
|
|
template <typename T> void selectCurrentIfNoneSelected(NC::Menu<T> &m) |
|
{ |
|
if (!hasSelected(m.begin(), m.end())) |
|
m.current().setSelected(true); |
|
} |
|
|
|
template <typename Iterator> |
|
std::vector<Iterator> getSelectedOrCurrent(Iterator first, Iterator last, Iterator current) |
|
{ |
|
std::vector<Iterator> result = getSelected(first, last); |
|
if (result.empty()) |
|
result.push_back(current); |
|
return result; |
|
} |
|
|
|
template <typename T, typename F> void withUnfilteredMenu(NC::Menu<T> &m, F action) |
|
{ |
|
bool is_filtered = m.isFiltered(); |
|
m.showAll(); |
|
action(); |
|
if (is_filtered) |
|
m.showFiltered(); |
|
} |
|
|
|
template <typename T, typename F> |
|
void withUnfilteredMenuReapplyFilter(NC::Menu<T> &m, F action) |
|
{ |
|
m.showAll(); |
|
action(); |
|
if (m.getFilter()) |
|
{ |
|
m.applyCurrentFilter(m.begin(), m.end()); |
|
if (m.empty()) |
|
{ |
|
m.clearFilter(); |
|
m.clearFilterResults(); |
|
} |
|
} |
|
} |
|
|
|
template <typename T, typename F> |
|
void moveSelectedItemsUp(NC::Menu<T> &m, F swap_fun) |
|
{ |
|
if (m.choice() > 0) |
|
selectCurrentIfNoneSelected(m); |
|
auto list = getSelected(m.begin(), m.end()); |
|
auto begin = m.begin(); |
|
if (!list.empty() && list.front() != m.begin()) |
|
{ |
|
Mpd.StartCommandsList(); |
|
for (auto it = list.begin(); it != list.end(); ++it) |
|
swap_fun(Mpd, *it - begin, *it - begin - 1); |
|
if (Mpd.CommitCommandsList()) |
|
{ |
|
if (list.size() > 1) |
|
{ |
|
for (auto it = list.begin(); it != list.end(); ++it) |
|
{ |
|
(*it)->setSelected(false); |
|
(*it-1)->setSelected(true); |
|
} |
|
m.highlight(list[(list.size())/2] - begin - 1); |
|
} |
|
else |
|
{ |
|
// if we move only one item, do not select it. however, if single item |
|
// was selected prior to move, it'll deselect it. oh well. |
|
list[0]->setSelected(false); |
|
m.scroll(NC::wUp); |
|
} |
|
} |
|
} |
|
} |
|
|
|
template <typename T, typename F> |
|
void moveSelectedItemsDown(NC::Menu<T> &m, F swap_fun) |
|
{ |
|
if (m.choice() < m.size()-1) |
|
selectCurrentIfNoneSelected(m); |
|
auto list = getSelected(m.rbegin(), m.rend()); |
|
auto begin = m.begin() + 1; // reverse iterators add 1, so we need to cancel it |
|
if (!list.empty() && list.front() != m.rbegin()) |
|
{ |
|
Mpd.StartCommandsList(); |
|
for (auto it = list.begin(); it != list.end(); ++it) |
|
swap_fun(Mpd, it->base() - begin, it->base() - begin + 1); |
|
if (Mpd.CommitCommandsList()) |
|
{ |
|
if (list.size() > 1) |
|
{ |
|
for (auto it = list.begin(); it != list.end(); ++it) |
|
{ |
|
(*it)->setSelected(false); |
|
(*it-1)->setSelected(true); |
|
} |
|
m.highlight(list[(list.size())/2].base() - begin + 1); |
|
} |
|
else |
|
{ |
|
// if we move only one item, do not select it. however, if single item |
|
// was selected prior to move, it'll deselect it. oh well. |
|
list[0]->setSelected(false); |
|
m.scroll(NC::wDown); |
|
} |
|
} |
|
} |
|
} |
|
|
|
template <typename T, typename F> |
|
void moveSelectedItemsTo(NC::Menu<T> &m, F move_fun) |
|
{ |
|
if (m.empty()) |
|
return; |
|
auto cur_ptr = &m.current().value(); |
|
withUnfilteredMenu(m, [&]() { |
|
// this is kinda shitty, but there is no other way to know |
|
// what position current item has in unfiltered menu. |
|
ptrdiff_t pos = 0; |
|
for (auto it = m.begin(); it != m.end(); ++it, ++pos) |
|
if (&it->value() == cur_ptr) |
|
break; |
|
auto begin = m.begin(); |
|
auto list = getSelected(m.begin(), m.end()); |
|
// we move only truly selected items |
|
if (list.empty()) |
|
return; |
|
// we can't move to the middle of selected items |
|
//(this also handles case when list.size() == 1) |
|
if (pos >= (list.front() - begin) && pos <= (list.back() - begin)) |
|
return; |
|
int diff = pos - (list.front() - begin); |
|
Mpd.StartCommandsList(); |
|
if (diff > 0) // move down |
|
{ |
|
pos -= list.size(); |
|
size_t i = list.size()-1; |
|
for (auto it = list.rbegin(); it != list.rend(); ++it, --i) |
|
move_fun(Mpd, *it - begin, pos+i); |
|
if (Mpd.CommitCommandsList()) |
|
{ |
|
i = list.size()-1; |
|
for (auto it = list.rbegin(); it != list.rend(); ++it, --i) |
|
{ |
|
(*it)->setSelected(false); |
|
m[pos+i].setSelected(true); |
|
} |
|
} |
|
} |
|
else if (diff < 0) // move up |
|
{ |
|
size_t i = 0; |
|
for (auto it = list.begin(); it != list.end(); ++it, ++i) |
|
move_fun(Mpd, *it - begin, pos+i); |
|
if (Mpd.CommitCommandsList()) |
|
{ |
|
i = 0; |
|
for (auto it = list.begin(); it != list.end(); ++it, ++i) |
|
{ |
|
(*it)->setSelected(false); |
|
m[pos+i].setSelected(true); |
|
} |
|
} |
|
} |
|
}); |
|
} |
|
|
|
template <typename Iterator> void reverseSelectionHelper(Iterator first, Iterator last) |
|
{ |
|
for (; first != last; ++first) |
|
first->setSelected(!first->isSelected()); |
|
} |
|
|
|
template <typename Iterator> std::string getSharedDirectory(Iterator first, Iterator last) |
|
{ |
|
assert(first != last); |
|
std::string result = first->getDirectory(); |
|
while (++first != last) |
|
{ |
|
result = getSharedDirectory(result, first->getDirectory()); |
|
if (result == "/") |
|
break; |
|
} |
|
return result; |
|
} |
|
|
|
void ParseArgv(int, char **); |
|
|
|
template <typename T> struct StringConverter { |
|
const char *operator()(const char *s) { return s; } |
|
}; |
|
template <> struct StringConverter< NC::basic_buffer<wchar_t> > { |
|
std::wstring operator()(const char *s) { return ToWString(s); } |
|
}; |
|
template <> struct StringConverter<NC::Scrollpad> { |
|
std::basic_string<my_char_t> operator()(const char *s) { return TO_WSTRING(s); } |
|
}; |
|
|
|
template <typename C> void String2Buffer(const std::basic_string<C> &s, NC::basic_buffer<C> &buf) |
|
{ |
|
StringConverter< NC::basic_buffer<C> > cnv; |
|
for (auto it = s.begin(); it != s.end(); ++it) |
|
{ |
|
if (*it == '$') |
|
{ |
|
if (++it == s.end()) |
|
{ |
|
buf << '$'; |
|
break; |
|
} |
|
else if (isdigit(*it)) |
|
{ |
|
buf << NC::Color(*it-'0'); |
|
} |
|
else |
|
{ |
|
switch (*it) |
|
{ |
|
case 'b': |
|
buf << NC::fmtBold; |
|
break; |
|
case 'u': |
|
buf << NC::fmtUnderline; |
|
break; |
|
case 'a': |
|
buf << NC::fmtAltCharset; |
|
break; |
|
case 'r': |
|
buf << NC::fmtReverse; |
|
break; |
|
case '/': |
|
if (++it == s.end()) |
|
{ |
|
buf << cnv("$/"); |
|
break; |
|
} |
|
switch (*it) |
|
{ |
|
case 'b': |
|
buf << NC::fmtBoldEnd; |
|
break; |
|
case 'u': |
|
buf << NC::fmtUnderlineEnd; |
|
break; |
|
case 'a': |
|
buf << NC::fmtAltCharsetEnd; |
|
break; |
|
case 'r': |
|
buf << NC::fmtReverseEnd; |
|
break; |
|
default: |
|
buf << '$' << *--it; |
|
break; |
|
} |
|
break; |
|
default: |
|
buf << *--it; |
|
break; |
|
} |
|
} |
|
} |
|
else if (*it == MPD::Song::FormatEscapeCharacter) |
|
{ |
|
// treat '$' as a normal character if song format escape char is prepended to it |
|
if (++it == s.end() || *it != '$') |
|
--it; |
|
buf << *it; |
|
} |
|
else |
|
buf << *it; |
|
} |
|
} |
|
|
|
template <typename T> void ShowTime(T &buf, size_t length, bool short_names) |
|
{ |
|
StringConverter<T> cnv; |
|
|
|
const unsigned MINUTE = 60; |
|
const unsigned HOUR = 60*MINUTE; |
|
const unsigned DAY = 24*HOUR; |
|
const unsigned YEAR = 365*DAY; |
|
|
|
unsigned years = length/YEAR; |
|
if (years) |
|
{ |
|
buf << years << cnv(short_names ? "y" : (years == 1 ? " year" : " years")); |
|
length -= years*YEAR; |
|
if (length) |
|
buf << cnv(", "); |
|
} |
|
unsigned days = length/DAY; |
|
if (days) |
|
{ |
|
buf << days << cnv(short_names ? "d" : (days == 1 ? " day" : " days")); |
|
length -= days*DAY; |
|
if (length) |
|
buf << cnv(", "); |
|
} |
|
unsigned hours = length/HOUR; |
|
if (hours) |
|
{ |
|
buf << hours << cnv(short_names ? "h" : (hours == 1 ? " hour" : " hours")); |
|
length -= hours*HOUR; |
|
if (length) |
|
buf << cnv(", "); |
|
} |
|
unsigned minutes = length/MINUTE; |
|
if (minutes) |
|
{ |
|
buf << minutes << cnv(short_names ? "m" : (minutes == 1 ? " minute" : " minutes")); |
|
length -= minutes*MINUTE; |
|
if (length) |
|
buf << cnv(", "); |
|
} |
|
if (length) |
|
buf << length << cnv(short_names ? "s" : (length == 1 ? " second" : " seconds")); |
|
} |
|
|
|
template <typename T> void ShowTag(T &buf, const std::string &tag) |
|
{ |
|
if (tag.empty()) |
|
buf << Config.empty_tags_color << Config.empty_tag << NC::clEnd; |
|
else |
|
buf << tag; |
|
} |
|
|
|
std::string Timestamp(time_t t); |
|
|
|
void markSongsInPlaylist(std::shared_ptr<ProxySongList> pl); |
|
|
|
std::basic_string<my_char_t> Scroller(const std::basic_string<my_char_t> &str, size_t &pos, size_t width); |
|
|
|
std::string Shorten(const std::basic_string<my_char_t> &s, size_t max_length); |
|
|
|
#endif
|
|
|