format: implement generic format parser and printer
This commit is contained in:
@@ -189,8 +189,6 @@
|
|||||||
#
|
#
|
||||||
#song_library_format = {%n - }{%t}|{%f}
|
#song_library_format = {%n - }{%t}|{%f}
|
||||||
#
|
#
|
||||||
#tag_editor_album_format = {(%y) }%b
|
|
||||||
#
|
|
||||||
##
|
##
|
||||||
## Note: Below variables are used for sorting songs in browser.
|
## Note: Below variables are used for sorting songs in browser.
|
||||||
## The sort mode determines how songs are sorted, and can be used
|
## The sort mode determines how songs are sorted, and can be used
|
||||||
|
|||||||
@@ -115,9 +115,6 @@ Song's format for statusbar.
|
|||||||
.B song_library_format
|
.B song_library_format
|
||||||
Format for songs in Media library.
|
Format for songs in Media library.
|
||||||
.TP
|
.TP
|
||||||
.B tag_editor_album_format
|
|
||||||
Format for albums' list in Tag editor.
|
|
||||||
.TP
|
|
||||||
.B song_window_title_format
|
.B song_window_title_format
|
||||||
Song format for window title.
|
Song format for window title.
|
||||||
.TP
|
.TP
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ ncmpcpp_SOURCES = \
|
|||||||
display.cpp \
|
display.cpp \
|
||||||
enums.cpp \
|
enums.cpp \
|
||||||
error.cpp \
|
error.cpp \
|
||||||
|
format.cpp \
|
||||||
global.cpp \
|
global.cpp \
|
||||||
help.cpp \
|
help.cpp \
|
||||||
helpers.cpp \
|
helpers.cpp \
|
||||||
@@ -73,6 +74,7 @@ noinst_HEADERS = \
|
|||||||
display.h \
|
display.h \
|
||||||
enums.h \
|
enums.h \
|
||||||
error.h \
|
error.h \
|
||||||
|
format.h \
|
||||||
global.h \
|
global.h \
|
||||||
help.h \
|
help.h \
|
||||||
helpers.h \
|
helpers.h \
|
||||||
|
|||||||
@@ -1322,7 +1322,7 @@ void EditLibraryTag::run()
|
|||||||
for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
|
for (MPD::SongIterator s = Mpd.CommitSearchSongs(), end; s != end; ++s)
|
||||||
{
|
{
|
||||||
MPD::MutableSong ms = std::move(*s);
|
MPD::MutableSong ms = std::move(*s);
|
||||||
ms.setTags(set, new_tag, Config.tags_separator);
|
ms.setTags(set, new_tag);
|
||||||
Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
|
Statusbar::printf("Updating tags in \"%1%\"...", ms.getName());
|
||||||
std::string path = Config.mpd_music_dir + ms.getURI();
|
std::string path = Config.mpd_music_dir + ms.getURI();
|
||||||
if (!Tags::write(ms))
|
if (!Tags::write(ms))
|
||||||
|
|||||||
@@ -641,10 +641,10 @@ std::string itemToString(const MPD::Item &item)
|
|||||||
switch (Config.browser_display_mode)
|
switch (Config.browser_display_mode)
|
||||||
{
|
{
|
||||||
case DisplayMode::Classic:
|
case DisplayMode::Classic:
|
||||||
result = item.song().toString(Config.song_list_format_dollar_free, Config.tags_separator);
|
result = Format::stringify<char>(Config.song_list_format, &item.song());
|
||||||
break;
|
break;
|
||||||
case DisplayMode::Columns:
|
case DisplayMode::Columns:
|
||||||
result = item.song().toString(Config.song_in_columns_to_string_format, Config.tags_separator);
|
result = Format::stringify<char>(Config.song_columns_mode_format, &item.song());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -104,57 +104,25 @@ void setProperties(NC::Menu<T> &menu, const MPD::Song &s, const ProxySongList &p
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void showSongs(NC::Menu<T> &menu, const MPD::Song &s, const ProxySongList &pl, const std::string &format)
|
void showSongs(NC::Menu<T> &menu, const MPD::Song &s,
|
||||||
|
const ProxySongList &pl, const Format::AST<char> &ast)
|
||||||
{
|
{
|
||||||
bool separate_albums, is_now_playing, is_selected, discard_colors;
|
bool separate_albums, is_now_playing, is_selected, discard_colors;
|
||||||
setProperties(menu, s, pl, separate_albums, is_now_playing, is_selected, discard_colors);
|
setProperties(menu, s, pl, separate_albums, is_now_playing, is_selected, discard_colors);
|
||||||
|
|
||||||
size_t y = menu.getY();
|
size_t y = menu.getY();
|
||||||
std::string line = Charset::utf8ToLocale(s.toString(format, Config.tags_separator, "$"));
|
NC::Buffer right_aligned;
|
||||||
for (auto it = line.begin(); it != line.end(); ++it)
|
Format::print(ast, menu, &s, &right_aligned);
|
||||||
|
if (!right_aligned.str().empty())
|
||||||
{
|
{
|
||||||
if (*it == '$')
|
size_t x_off = menu.getWidth() - wideLength(ToWString(right_aligned.str()));
|
||||||
{
|
if (is_now_playing)
|
||||||
++it;
|
x_off -= Config.now_playing_suffix_length;
|
||||||
if (it == line.end()) // end of format
|
if (is_selected)
|
||||||
{
|
x_off -= Config.selected_item_suffix_length;
|
||||||
menu << '$';
|
menu << NC::XY(x_off, y) << right_aligned;
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (isdigit(*it)) // color
|
|
||||||
{
|
|
||||||
if (!discard_colors)
|
|
||||||
menu << charToColor(*it);
|
|
||||||
}
|
|
||||||
else if (*it == 'R') // right align
|
|
||||||
{
|
|
||||||
NC::Buffer buf;
|
|
||||||
buf << " ";
|
|
||||||
stringToBuffer(++it, line.end(), buf);
|
|
||||||
if (discard_colors)
|
|
||||||
buf.removeProperties();
|
|
||||||
size_t x_off = menu.getWidth() - wideLength(ToWString(buf.str()));
|
|
||||||
if (is_now_playing)
|
|
||||||
x_off -= Config.now_playing_suffix_length;
|
|
||||||
if (is_selected)
|
|
||||||
x_off -= Config.selected_item_suffix_length;
|
|
||||||
menu << NC::XY(x_off, y) << buf;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else // not a color nor right align, just a random character
|
|
||||||
menu << *--it;
|
|
||||||
}
|
|
||||||
else if (*it == MPD::Song::FormatEscapeCharacter)
|
|
||||||
{
|
|
||||||
++it;
|
|
||||||
// treat '$' as a normal character if song format escape char is prepended to it
|
|
||||||
if (it == line.end() || *it != '$')
|
|
||||||
--it;
|
|
||||||
menu << *it;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
menu << *it;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_now_playing)
|
if (is_now_playing)
|
||||||
menu << Config.now_playing_suffix;
|
menu << Config.now_playing_suffix;
|
||||||
if (separate_albums)
|
if (separate_albums)
|
||||||
@@ -225,7 +193,7 @@ void showSongsInColumns(NC::Menu<T> &menu, const MPD::Song &s, const ProxySongLi
|
|||||||
{
|
{
|
||||||
MPD::Song::GetFunction get = charToGetFunction(it->type[i]);
|
MPD::Song::GetFunction get = charToGetFunction(it->type[i]);
|
||||||
assert(get);
|
assert(get);
|
||||||
tag = ToWString(Charset::utf8ToLocale(s.getTags(get, Config.tags_separator)));
|
tag = ToWString(Charset::utf8ToLocale(s.getTags(get)));
|
||||||
if (!tag.empty())
|
if (!tag.empty())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -353,9 +321,10 @@ void Display::SongsInColumns(NC::Menu< MPD::Song >& menu, const ProxySongList &p
|
|||||||
showSongsInColumns(menu, menu.drawn()->value(), pl);
|
showSongsInColumns(menu, menu.drawn()->value(), pl);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Display::Songs(NC::Menu< MPD::Song >& menu, const ProxySongList &pl, const std::string &format)
|
void Display::Songs(NC::Menu< MPD::Song >& menu,
|
||||||
|
const ProxySongList &pl, const Format::AST<char> &ast)
|
||||||
{
|
{
|
||||||
showSongs(menu, menu.drawn()->value(), pl, format);
|
showSongs(menu, menu.drawn()->value(), pl, ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef HAVE_TAGLIB_H
|
#ifdef HAVE_TAGLIB_H
|
||||||
@@ -367,7 +336,7 @@ void Display::Tags(NC::Menu<MPD::MutableSong> &menu)
|
|||||||
size_t i = myTagEditor->TagTypes->choice();
|
size_t i = myTagEditor->TagTypes->choice();
|
||||||
if (i < 11)
|
if (i < 11)
|
||||||
{
|
{
|
||||||
ShowTag(menu, Charset::utf8ToLocale(s.getTags(SongInfo::Tags[i].Get, Config.tags_separator)));
|
ShowTag(menu, Charset::utf8ToLocale(s.getTags(SongInfo::Tags[i].Get)));
|
||||||
}
|
}
|
||||||
else if (i == 12)
|
else if (i == 12)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#define NCMPCPP_DISPLAY_H
|
#define NCMPCPP_DISPLAY_H
|
||||||
|
|
||||||
#include "interfaces.h"
|
#include "interfaces.h"
|
||||||
|
#include "format.h"
|
||||||
#include "menu.h"
|
#include "menu.h"
|
||||||
#include "mutable_song.h"
|
#include "mutable_song.h"
|
||||||
#include "search_engine.h"
|
#include "search_engine.h"
|
||||||
@@ -32,7 +33,7 @@ std::string Columns(size_t);
|
|||||||
|
|
||||||
void SongsInColumns(NC::Menu<MPD::Song> &menu, const ProxySongList &pl);
|
void SongsInColumns(NC::Menu<MPD::Song> &menu, const ProxySongList &pl);
|
||||||
|
|
||||||
void Songs(NC::Menu<MPD::Song> &menu, const ProxySongList &pl, const std::string &format);
|
void Songs(NC::Menu<MPD::Song> &menu, const ProxySongList &pl, const Format::AST<char> &ast);
|
||||||
|
|
||||||
void Tags(NC::Menu<MPD::MutableSong> &menu);
|
void Tags(NC::Menu<MPD::MutableSong> &menu);
|
||||||
|
|
||||||
|
|||||||
209
src/format.cpp
Normal file
209
src/format.cpp
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
* Copyright (C) 2008-2014 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. *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include "format.h"
|
||||||
|
#include "utility/type_conversions.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template <typename CharT> using string = std::basic_string<CharT>;
|
||||||
|
template <typename CharT> using iterator = typename std::basic_string<CharT>::const_iterator;
|
||||||
|
template <typename CharT> using expressions = std::vector<Format::Expression<CharT>>;
|
||||||
|
|
||||||
|
std::string invalidCharacter(char c)
|
||||||
|
{
|
||||||
|
return "invalid character '" + boost::lexical_cast<std::string>(c) + "'";
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename CharT>
|
||||||
|
void throwError(const string<CharT> &s, iterator<CharT> current, std::string msg)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(
|
||||||
|
std::move(msg) + " at position " + boost::lexical_cast<std::string>(current - s.begin())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename CharT>
|
||||||
|
void rangeCheck(const string<CharT> &s, iterator<CharT> current, iterator<CharT> end)
|
||||||
|
{
|
||||||
|
if (current >= end)
|
||||||
|
throwError(s, current, "unexpected end");
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename CharT>
|
||||||
|
expressions<CharT> parseBracket(const string<CharT> &s,
|
||||||
|
iterator<CharT> it, iterator<CharT> end)
|
||||||
|
{
|
||||||
|
string<CharT> token;
|
||||||
|
expressions<CharT> tmp, result;
|
||||||
|
auto push_token = [&] {
|
||||||
|
result.push_back(std::move(token));
|
||||||
|
};
|
||||||
|
for (; it != end; ++it)
|
||||||
|
{
|
||||||
|
if (*it == '{')
|
||||||
|
{
|
||||||
|
push_token();
|
||||||
|
bool done;
|
||||||
|
Format::Any<CharT> any;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
auto jt = it;
|
||||||
|
done = true;
|
||||||
|
// get to the corresponding closing bracket
|
||||||
|
unsigned brackets = 1;
|
||||||
|
while (brackets > 0)
|
||||||
|
{
|
||||||
|
if (++jt == end)
|
||||||
|
break;
|
||||||
|
if (*jt == '{')
|
||||||
|
++brackets;
|
||||||
|
else if (*jt == '}')
|
||||||
|
--brackets;
|
||||||
|
}
|
||||||
|
// check if we're still in range
|
||||||
|
rangeCheck(s, jt, end);
|
||||||
|
// skip the opening bracket
|
||||||
|
++it;
|
||||||
|
// recursively parse the bracket
|
||||||
|
tmp = parseBracket(s, it, jt);
|
||||||
|
// if the inner bracket contains only one expression,
|
||||||
|
// put it as is. otherwise require all of them.
|
||||||
|
if (tmp.size() == 1)
|
||||||
|
any.base().push_back(std::move(tmp[0]));
|
||||||
|
else
|
||||||
|
any.base().push_back(Format::All<CharT>(std::move(tmp)));
|
||||||
|
it = jt;
|
||||||
|
// check for the alternative
|
||||||
|
++jt;
|
||||||
|
if (jt != end && *jt == '|')
|
||||||
|
{
|
||||||
|
++jt;
|
||||||
|
rangeCheck(s, jt, end);
|
||||||
|
if (*jt != '{')
|
||||||
|
throwError(s, jt, invalidCharacter(*jt) + ", expected '{'");
|
||||||
|
it = jt;
|
||||||
|
done = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!done);
|
||||||
|
assert(!any.base().empty());
|
||||||
|
// if there was only one bracket, append empty branch
|
||||||
|
// so that it always evaluates to true. otherwise put
|
||||||
|
// it as is.
|
||||||
|
if (any.base().size() == 1)
|
||||||
|
any.base().push_back(string<CharT>());
|
||||||
|
result.push_back(std::move(any));
|
||||||
|
}
|
||||||
|
else if (*it == '%')
|
||||||
|
{
|
||||||
|
++it;
|
||||||
|
rangeCheck(s, it, end);
|
||||||
|
// %% is escaped %
|
||||||
|
if (*it == '%')
|
||||||
|
{
|
||||||
|
token += '%';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
push_token();
|
||||||
|
// check for tag delimiter
|
||||||
|
unsigned delimiter = 0;
|
||||||
|
if (isdigit(*it))
|
||||||
|
{
|
||||||
|
std::string sdelimiter;
|
||||||
|
do
|
||||||
|
sdelimiter += *it++;
|
||||||
|
while (it != end && isdigit(*it));
|
||||||
|
rangeCheck(s, it, end);
|
||||||
|
delimiter = boost::lexical_cast<unsigned>(sdelimiter);
|
||||||
|
}
|
||||||
|
auto f = charToGetFunction(*it);
|
||||||
|
if (f == nullptr)
|
||||||
|
throwError(s, it, invalidCharacter(*it));
|
||||||
|
result.push_back(Format::SongTag(f, delimiter));
|
||||||
|
}
|
||||||
|
else if (*it == '$')
|
||||||
|
{
|
||||||
|
++it;
|
||||||
|
rangeCheck(s, it, end);
|
||||||
|
// $$ is escaped $
|
||||||
|
if (*it == '$')
|
||||||
|
{
|
||||||
|
token += '$';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
push_token();
|
||||||
|
if (isdigit(*it))
|
||||||
|
{
|
||||||
|
auto color = charToColor(*it);
|
||||||
|
result.push_back(color);
|
||||||
|
}
|
||||||
|
else if (*it == 'R')
|
||||||
|
result.push_back(Format::AlignRight());
|
||||||
|
else if (*it == 'b')
|
||||||
|
result.push_back(NC::Format::Bold);
|
||||||
|
else if (*it == 'u')
|
||||||
|
result.push_back(NC::Format::Underline);
|
||||||
|
else if (*it == 'a')
|
||||||
|
result.push_back(NC::Format::AltCharset);
|
||||||
|
else if (*it == 'r')
|
||||||
|
result.push_back(NC::Format::Reverse);
|
||||||
|
else if (*it == '/')
|
||||||
|
{
|
||||||
|
++it;
|
||||||
|
rangeCheck(s, it, end);
|
||||||
|
if (*it == 'b')
|
||||||
|
result.push_back(NC::Format::NoBold);
|
||||||
|
else if (*it == 'u')
|
||||||
|
result.push_back(NC::Format::NoUnderline);
|
||||||
|
else if (*it == 'a')
|
||||||
|
result.push_back(NC::Format::NoAltCharset);
|
||||||
|
else if (*it == 'r')
|
||||||
|
result.push_back(NC::Format::NoReverse);
|
||||||
|
else
|
||||||
|
throwError(s, it, invalidCharacter(*it));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throwError(s, it, invalidCharacter(*it));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
token += *it;
|
||||||
|
}
|
||||||
|
push_token();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Format {
|
||||||
|
|
||||||
|
AST<char> parse(const std::string &s)
|
||||||
|
{
|
||||||
|
return AST<char>(parseBracket(s, s.begin(), s.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
AST<wchar_t> wparse(const std::wstring &s)
|
||||||
|
{
|
||||||
|
return AST<wchar_t>(parseBracket(s, s.begin(), s.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
279
src/format.h
Normal file
279
src/format.h
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
* Copyright (C) 2008-2014 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 NCMPCPP_HAVE_FORMAT_H
|
||||||
|
#define NCMPCPP_HAVE_FORMAT_H
|
||||||
|
|
||||||
|
#include <boost/locale/encoding_utf.hpp>
|
||||||
|
#include <boost/variant.hpp>
|
||||||
|
|
||||||
|
#include "menu.h"
|
||||||
|
#include "song.h"
|
||||||
|
#include "strbuffer.h"
|
||||||
|
#include "utility/wide_string.h"
|
||||||
|
|
||||||
|
namespace Format {
|
||||||
|
|
||||||
|
enum class ListType { All, Any, AST };
|
||||||
|
|
||||||
|
template <ListType, typename> struct List;
|
||||||
|
template <typename CharT> using All = List<ListType::All, CharT>;
|
||||||
|
template <typename CharT> using Any = List<ListType::Any, CharT>;
|
||||||
|
template <typename CharT> using AST = List<ListType::AST, CharT>;
|
||||||
|
|
||||||
|
struct AlignRight { };
|
||||||
|
|
||||||
|
struct SongTag
|
||||||
|
{
|
||||||
|
SongTag(MPD::Song::GetFunction function_, unsigned delimiter_ = 0)
|
||||||
|
: m_function(function_), m_delimiter(delimiter_)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
MPD::Song::GetFunction function() const { return m_function; }
|
||||||
|
unsigned delimiter() const { return m_delimiter; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
MPD::Song::GetFunction m_function;
|
||||||
|
unsigned m_delimiter;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename CharT>
|
||||||
|
using Expression = boost::variant<
|
||||||
|
std::basic_string<CharT>,
|
||||||
|
NC::Color,
|
||||||
|
NC::Format,
|
||||||
|
AlignRight,
|
||||||
|
SongTag,
|
||||||
|
boost::recursive_wrapper<Any<CharT>>,
|
||||||
|
boost::recursive_wrapper<All<CharT>>
|
||||||
|
>;
|
||||||
|
|
||||||
|
template <ListType Type, typename CharT>
|
||||||
|
struct List
|
||||||
|
{
|
||||||
|
typedef std::vector<Expression<CharT>> Base;
|
||||||
|
|
||||||
|
List() { }
|
||||||
|
List(Base &&base_)
|
||||||
|
: m_base(std::move(base_))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
Base &base() { return m_base; }
|
||||||
|
const Base &base() const { return m_base; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Base m_base;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename CharT, typename OutputT, typename OutputAfterAlignmentT = OutputT>
|
||||||
|
struct Printer: boost::static_visitor<bool>
|
||||||
|
{
|
||||||
|
typedef std::basic_string<CharT> StringT;
|
||||||
|
|
||||||
|
Printer(OutputT &os, const MPD::Song *song, OutputAfterAlignmentT *os_after_alignment)
|
||||||
|
: m_output(os)
|
||||||
|
, m_song(song)
|
||||||
|
, m_after_alignment(false)
|
||||||
|
, m_output_after_alignment(os_after_alignment)
|
||||||
|
, m_no_output(0)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
bool operator()(const StringT &s)
|
||||||
|
{
|
||||||
|
output(s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const NC::Color &c)
|
||||||
|
{
|
||||||
|
output(c);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(NC::Format fmt)
|
||||||
|
{
|
||||||
|
output(fmt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(AlignRight)
|
||||||
|
{
|
||||||
|
if (!m_no_output)
|
||||||
|
m_after_alignment = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const SongTag &st)
|
||||||
|
{
|
||||||
|
StringT tags;
|
||||||
|
if (m_song != nullptr)
|
||||||
|
{
|
||||||
|
tags = convertString<std::is_same<char, CharT>::value, std::string>::apply(
|
||||||
|
m_song->getTags(st.function())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!tags.empty())
|
||||||
|
{
|
||||||
|
if (st.delimiter() > 0)
|
||||||
|
{
|
||||||
|
// shorten length by chopping off the tail
|
||||||
|
if (st.function() == &MPD::Song::getLength)
|
||||||
|
tags.resize(st.delimiter());
|
||||||
|
else
|
||||||
|
tags = wideShorten(tags, st.delimiter());
|
||||||
|
}
|
||||||
|
output(tags);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const All<CharT> &all)
|
||||||
|
{
|
||||||
|
auto visit = [this, &all] {
|
||||||
|
return std::all_of(
|
||||||
|
all.base().begin(),
|
||||||
|
all.base().end(),
|
||||||
|
[this](const Expression<CharT> &ex) {
|
||||||
|
return boost::apply_visitor(*this, ex);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
++m_no_output;
|
||||||
|
bool all_ok = visit();
|
||||||
|
--m_no_output;
|
||||||
|
if (!m_no_output && all_ok)
|
||||||
|
visit();
|
||||||
|
return all_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const Any<CharT> &any)
|
||||||
|
{
|
||||||
|
return std::any_of(
|
||||||
|
any.base().begin(),
|
||||||
|
any.base().end(),
|
||||||
|
[this](const Expression<CharT> &ex) {
|
||||||
|
return boost::apply_visitor(*this, ex);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// convert string to appropriate type
|
||||||
|
template <bool AreSame, typename ValueT>
|
||||||
|
struct convertString {
|
||||||
|
static const StringT &apply(const ValueT &s) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template <typename ValueT>
|
||||||
|
struct convertString<false, ValueT> {
|
||||||
|
static StringT apply(const ValueT &s) {
|
||||||
|
return boost::locale::conv::utf_to_utf<CharT>(s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// generic version for streams (buffers, menus)
|
||||||
|
template <typename ValueT, typename OutputStreamT>
|
||||||
|
struct output_ {
|
||||||
|
static void exec(OutputStreamT &os, const ValueT &value) {
|
||||||
|
os << value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// specialization for strings (input/output)
|
||||||
|
template <typename SomeCharT, typename OtherCharT>
|
||||||
|
struct output_<std::basic_string<SomeCharT>, std::basic_string<OtherCharT>> {
|
||||||
|
typedef std::basic_string<SomeCharT> SomeString;
|
||||||
|
typedef std::basic_string<OtherCharT> OtherString;
|
||||||
|
|
||||||
|
// compile only if string types are the same
|
||||||
|
static typename std::enable_if<
|
||||||
|
std::is_same<SomeString, OtherString>::value,
|
||||||
|
void
|
||||||
|
>::type exec(SomeString &result, const OtherString &s) {
|
||||||
|
result += s;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// when writing to a string, ignore all properties
|
||||||
|
template <typename ValueT, typename SomeCharT>
|
||||||
|
struct output_<ValueT, std::basic_string<SomeCharT>>
|
||||||
|
{
|
||||||
|
static void exec(std::basic_string<CharT> &, const ValueT &) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ValueT>
|
||||||
|
void output(const ValueT &value) const
|
||||||
|
{
|
||||||
|
if (!m_no_output)
|
||||||
|
{
|
||||||
|
if (m_after_alignment && m_output_after_alignment != nullptr)
|
||||||
|
output_<ValueT, OutputAfterAlignmentT>::exec(*m_output_after_alignment, value);
|
||||||
|
else
|
||||||
|
output_<ValueT, OutputT>::exec(m_output, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputT &m_output;
|
||||||
|
const MPD::Song *m_song;
|
||||||
|
|
||||||
|
bool m_after_alignment;
|
||||||
|
OutputAfterAlignmentT *m_output_after_alignment;
|
||||||
|
|
||||||
|
unsigned m_no_output;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename CharT, typename VisitorT>
|
||||||
|
void visit(VisitorT &visitor, const AST<CharT> &ast)
|
||||||
|
{
|
||||||
|
for (const auto &ex : ast.base())
|
||||||
|
boost::apply_visitor(visitor, ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename CharT, typename ItemT>
|
||||||
|
void print(const AST<CharT> &ast, NC::Menu<ItemT> &menu,
|
||||||
|
const MPD::Song *song, NC::BasicBuffer<CharT> *buffer)
|
||||||
|
{
|
||||||
|
Printer<CharT, NC::Menu<ItemT>, NC::Buffer> printer(menu, song, buffer);
|
||||||
|
visit(printer, ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename CharT>
|
||||||
|
void print(const AST<CharT> &ast, NC::BasicBuffer<CharT> &buffer, const MPD::Song *song)
|
||||||
|
{
|
||||||
|
Printer<CharT, NC::BasicBuffer<CharT>> printer(buffer, song, &buffer);
|
||||||
|
visit(printer, ast);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename CharT>
|
||||||
|
std::basic_string<CharT> stringify(const AST<CharT> &ast, const MPD::Song *song)
|
||||||
|
{
|
||||||
|
std::basic_string<CharT> result;
|
||||||
|
Printer<CharT, std::basic_string<CharT>> printer(result, song, &result);
|
||||||
|
visit(printer, ast);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AST<char> parse(const std::string &s);
|
||||||
|
AST<wchar_t> wparse(const std::wstring &ws);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NCMPCPP_HAVE_FORMAT_H
|
||||||
@@ -54,7 +54,7 @@ bool addSongToPlaylist(const MPD::Song &s, bool play, int position)
|
|||||||
if (id >= 0)
|
if (id >= 0)
|
||||||
{
|
{
|
||||||
Statusbar::printf("Added to playlist: %s",
|
Statusbar::printf("Added to playlist: %s",
|
||||||
s.toString(Config.song_status_format_no_colors, Config.tags_separator)
|
Format::stringify<char>(Config.song_status_format, &s)
|
||||||
);
|
);
|
||||||
if (play)
|
if (play)
|
||||||
Mpd.PlayID(id);
|
Mpd.PlayID(id);
|
||||||
|
|||||||
@@ -331,95 +331,6 @@ template <typename Iterator> std::string getSharedDirectory(Iterator first, Iter
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Iterator>
|
|
||||||
void stringToBuffer(Iterator first, Iterator last, NC::BasicBuffer<typename Iterator::value_type> &buf)
|
|
||||||
{
|
|
||||||
for (auto it = first; it != last; ++it)
|
|
||||||
{
|
|
||||||
if (*it == '$')
|
|
||||||
{
|
|
||||||
if (++it == last)
|
|
||||||
{
|
|
||||||
buf << '$';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (isdigit(*it))
|
|
||||||
{
|
|
||||||
buf << charToColor(*it);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (*it)
|
|
||||||
{
|
|
||||||
case 'b':
|
|
||||||
buf << NC::Format::Bold;
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
buf << NC::Format::Underline;
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
buf << NC::Format::AltCharset;
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
buf << NC::Format::Reverse;
|
|
||||||
break;
|
|
||||||
case '/':
|
|
||||||
if (++it == last)
|
|
||||||
{
|
|
||||||
buf << '$' << '/';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
switch (*it)
|
|
||||||
{
|
|
||||||
case 'b':
|
|
||||||
buf << NC::Format::NoBold;
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
buf << NC::Format::NoUnderline;
|
|
||||||
break;
|
|
||||||
case 'a':
|
|
||||||
buf << NC::Format::NoAltCharset;
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
buf << NC::Format::NoReverse;
|
|
||||||
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 == last || *it != '$')
|
|
||||||
--it;
|
|
||||||
buf << *it;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
buf << *it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename CharT>
|
|
||||||
void stringToBuffer(const std::basic_string<CharT> &s, NC::BasicBuffer<CharT> &buf)
|
|
||||||
{
|
|
||||||
stringToBuffer(s.begin(), s.end(), buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename CharT>
|
|
||||||
NC::BasicBuffer<CharT> stringToBuffer(const std::basic_string<CharT> &s)
|
|
||||||
{
|
|
||||||
NC::BasicBuffer<CharT> result;
|
|
||||||
stringToBuffer(s, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T> void ShowTime(T &buf, size_t length, bool short_names)
|
template <typename T> void ShowTime(T &buf, size_t length, bool short_names)
|
||||||
{
|
{
|
||||||
const unsigned MINUTE = 60;
|
const unsigned MINUTE = 60;
|
||||||
|
|||||||
@@ -127,7 +127,12 @@ void Lyrics::switchTo()
|
|||||||
std::wstring Lyrics::title()
|
std::wstring Lyrics::title()
|
||||||
{
|
{
|
||||||
std::wstring result = L"Lyrics: ";
|
std::wstring result = L"Lyrics: ";
|
||||||
result += Scroller(ToWString(itsSong.toString("{%a - %t}", ", ")), itsScrollBegin, COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length()));
|
|
||||||
|
result += Scroller(
|
||||||
|
Format::stringify<wchar_t>(Format::wparse(L"{%a - %t}"), &itsSong),
|
||||||
|
itsScrollBegin,
|
||||||
|
COLS-result.length()-(Config.design == Design::Alternative ? 2 : Global::VolumeState.length())
|
||||||
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +158,7 @@ void Lyrics::DownloadInBackground(const MPD::Song &s)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Statusbar::printf("Fetching lyrics for \"%1%\"...",
|
Statusbar::printf("Fetching lyrics for \"%1%\"...",
|
||||||
s.toString(Config.song_status_format_no_colors, Config.tags_separator)
|
Format::stringify<char>(Config.song_status_format, &s)
|
||||||
);
|
);
|
||||||
|
|
||||||
MPD::Song *s_copy = new MPD::Song(s);
|
MPD::Song *s_copy = new MPD::Song(s);
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ public:
|
|||||||
}
|
}
|
||||||
bool operator()(const MPD::Song &a, const MPD::Song &b) {
|
bool operator()(const MPD::Song &a, const MPD::Song &b) {
|
||||||
for (auto get = GetFuns.begin()+m_offset; get != GetFuns.end(); ++get) {
|
for (auto get = GetFuns.begin()+m_offset; get != GetFuns.end(); ++get) {
|
||||||
int ret = m_cmp(a.getTags(*get, Config.tags_separator),
|
int ret = m_cmp(a.getTags(*get),
|
||||||
b.getTags(*get, Config.tags_separator));
|
b.getTags(*get));
|
||||||
if (ret != 0)
|
if (ret != 0)
|
||||||
return ret < 0;
|
return ret < 0;
|
||||||
}
|
}
|
||||||
@@ -197,7 +197,9 @@ MediaLibrary::MediaLibrary()
|
|||||||
Songs.centeredCursor(Config.centered_cursor);
|
Songs.centeredCursor(Config.centered_cursor);
|
||||||
Songs.setSelectedPrefix(Config.selected_item_prefix);
|
Songs.setSelectedPrefix(Config.selected_item_prefix);
|
||||||
Songs.setSelectedSuffix(Config.selected_item_suffix);
|
Songs.setSelectedSuffix(Config.selected_item_suffix);
|
||||||
Songs.setItemDisplayer(boost::bind(Display::Songs, _1, songsProxyList(), Config.song_library_format));
|
Songs.setItemDisplayer(boost::bind(
|
||||||
|
Display::Songs, _1, songsProxyList(), Config.song_library_format
|
||||||
|
));
|
||||||
|
|
||||||
w = &Tags;
|
w = &Tags;
|
||||||
}
|
}
|
||||||
@@ -1037,7 +1039,9 @@ std::string AlbumToString(const AlbumEntry &ae)
|
|||||||
|
|
||||||
std::string SongToString(const MPD::Song &s)
|
std::string SongToString(const MPD::Song &s)
|
||||||
{
|
{
|
||||||
return s.toString(Config.song_library_format, Config.tags_separator);
|
return Format::stringify<char>(
|
||||||
|
Config.song_library_format, &s//FIXME, Config.tags_separator
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TagEntryMatcher(const boost::regex &rx, const MediaLibrary::PrimaryTag &pt)
|
bool TagEntryMatcher(const boost::regex &rx, const MediaLibrary::PrimaryTag &pt)
|
||||||
|
|||||||
@@ -178,10 +178,10 @@ void MutableSong::setMTime(time_t mtime)
|
|||||||
m_mtime = mtime;
|
m_mtime = mtime;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MutableSong::setTags(SetFunction set, const std::string &value, const std::string &delimiter)
|
void MutableSong::setTags(SetFunction set, const std::string &value)
|
||||||
{
|
{
|
||||||
std::vector<std::string> tags;
|
std::vector<std::string> tags;
|
||||||
boost::iter_split(tags, value, boost::first_finder(delimiter));
|
boost::iter_split(tags, value, boost::first_finder(Song::TagsSeparator));
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
for (; i < tags.size(); ++i)
|
for (; i < tags.size(); ++i)
|
||||||
(this->*set)(tags[i], i);
|
(this->*set)(tags[i], i);
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ struct MutableSong : public Song
|
|||||||
void setDuration(unsigned duration);
|
void setDuration(unsigned duration);
|
||||||
void setMTime(time_t mtime);
|
void setMTime(time_t mtime);
|
||||||
|
|
||||||
void setTags(SetFunction set, const std::string &value, const std::string &delimiter);
|
void setTags(SetFunction set, const std::string &value);
|
||||||
|
|
||||||
bool isModified() const;
|
bool isModified() const;
|
||||||
void clearModifications();
|
void clearModifications();
|
||||||
|
|||||||
@@ -61,10 +61,14 @@ Playlist::Playlist()
|
|||||||
switch (Config.playlist_display_mode)
|
switch (Config.playlist_display_mode)
|
||||||
{
|
{
|
||||||
case DisplayMode::Classic:
|
case DisplayMode::Classic:
|
||||||
w.setItemDisplayer(boost::bind(Display::Songs, _1, proxySongList(), Config.song_list_format));
|
w.setItemDisplayer(boost::bind(
|
||||||
|
Display::Songs, _1, proxySongList(), Config.song_list_format
|
||||||
|
));
|
||||||
break;
|
break;
|
||||||
case DisplayMode::Columns:
|
case DisplayMode::Columns:
|
||||||
w.setItemDisplayer(boost::bind(Display::SongsInColumns, _1, proxySongList()));
|
w.setItemDisplayer(boost::bind(
|
||||||
|
Display::SongsInColumns, _1, proxySongList()
|
||||||
|
));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,10 +311,10 @@ std::string songToString(const MPD::Song &s)
|
|||||||
switch (Config.playlist_display_mode)
|
switch (Config.playlist_display_mode)
|
||||||
{
|
{
|
||||||
case DisplayMode::Classic:
|
case DisplayMode::Classic:
|
||||||
result = s.toString(Config.song_list_format_dollar_free, Config.tags_separator);
|
result = Format::stringify<char>(Config.song_list_format, &s);
|
||||||
break;
|
break;
|
||||||
case DisplayMode::Columns:
|
case DisplayMode::Columns:
|
||||||
result = s.toString(Config.song_in_columns_to_string_format, Config.tags_separator);
|
result = Format::stringify<char>(Config.song_columns_mode_format, &s);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,10 +83,14 @@ PlaylistEditor::PlaylistEditor()
|
|||||||
switch (Config.playlist_editor_display_mode)
|
switch (Config.playlist_editor_display_mode)
|
||||||
{
|
{
|
||||||
case DisplayMode::Classic:
|
case DisplayMode::Classic:
|
||||||
Content.setItemDisplayer(boost::bind(Display::Songs, _1, contentProxyList(), Config.song_list_format));
|
Content.setItemDisplayer(
|
||||||
|
boost::bind(Display::Songs, _1, contentProxyList(), Config.song_list_format
|
||||||
|
));
|
||||||
break;
|
break;
|
||||||
case DisplayMode::Columns:
|
case DisplayMode::Columns:
|
||||||
Content.setItemDisplayer(boost::bind(Display::SongsInColumns, _1, contentProxyList()));
|
Content.setItemDisplayer(
|
||||||
|
boost::bind(Display::SongsInColumns, _1, contentProxyList())
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,7 +495,7 @@ void PlaylistEditor::Locate(const MPD::Playlist &playlist)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {//
|
namespace {
|
||||||
|
|
||||||
std::string SongToString(const MPD::Song &s)
|
std::string SongToString(const MPD::Song &s)
|
||||||
{
|
{
|
||||||
@@ -499,10 +503,10 @@ std::string SongToString(const MPD::Song &s)
|
|||||||
switch (Config.playlist_display_mode)
|
switch (Config.playlist_display_mode)
|
||||||
{
|
{
|
||||||
case DisplayMode::Classic:
|
case DisplayMode::Classic:
|
||||||
result = s.toString(Config.song_list_format_dollar_free, Config.tags_separator);
|
result = Format::stringify<char>(Config.song_list_format, &s);
|
||||||
break;
|
break;
|
||||||
case DisplayMode::Columns:
|
case DisplayMode::Columns:
|
||||||
result = s.toString(Config.song_in_columns_to_string_format, Config.tags_separator);
|
result = Format::stringify<char>(Config.song_columns_mode_format, &s);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -591,10 +591,10 @@ std::string SEItemToString(const SEItem &ei)
|
|||||||
switch (Config.search_engine_display_mode)
|
switch (Config.search_engine_display_mode)
|
||||||
{
|
{
|
||||||
case DisplayMode::Classic:
|
case DisplayMode::Classic:
|
||||||
result = ei.song().toString(Config.song_list_format_dollar_free, Config.tags_separator);
|
result = Format::stringify<char>(Config.song_list_format, &ei.song());
|
||||||
break;
|
break;
|
||||||
case DisplayMode::Columns:
|
case DisplayMode::Columns:
|
||||||
result = ei.song().toString(Config.song_in_columns_to_string_format, Config.tags_separator);
|
result = Format::stringify<char>(Config.song_columns_mode_format, &ei.song());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
111
src/settings.cpp
111
src/settings.cpp
@@ -37,20 +37,7 @@ Configuration Config;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::string remove_dollar_formatting(const std::string &s)
|
std::vector<Column> generate_columns(const std::string &format)
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
for (size_t i = 0; i < s.size(); ++i)
|
|
||||||
{
|
|
||||||
if (s[i] != '$')
|
|
||||||
result += s[i];
|
|
||||||
else
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<std::vector<Column>, std::string> generate_columns(const std::string &format)
|
|
||||||
{
|
{
|
||||||
std::vector<Column> result;
|
std::vector<Column> result;
|
||||||
std::string width;
|
std::string width;
|
||||||
@@ -128,25 +115,32 @@ std::pair<std::vector<Column>, std::string> generate_columns(const std::string &
|
|||||||
it->stretch_limit = stretch_limit;
|
it->stretch_limit = stretch_limit;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string string_format;
|
return result;
|
||||||
// generate format for converting tags in columns to string for Playlist::SongInColumnsToString()
|
}
|
||||||
char tag[] = "{% }|";
|
|
||||||
string_format = "{";
|
|
||||||
for (auto it = result.begin(); it != result.end(); ++it)
|
|
||||||
{
|
|
||||||
for (std::string::const_iterator j = it->type.begin(); j != it->type.end(); ++j)
|
|
||||||
{
|
|
||||||
tag[2] = *j;
|
|
||||||
string_format += tag;
|
|
||||||
}
|
|
||||||
*string_format.rbegin() = ' ';
|
|
||||||
}
|
|
||||||
if (string_format.length() == 1) // only '{'
|
|
||||||
string_format += '}';
|
|
||||||
else
|
|
||||||
*string_format.rbegin() = '}';
|
|
||||||
|
|
||||||
return std::make_pair(std::move(result), std::move(string_format));
|
Format::AST<char> columns_to_format(const std::vector<Column> &columns)
|
||||||
|
{
|
||||||
|
std::vector<Format::Expression<char>> result;
|
||||||
|
|
||||||
|
auto column = columns.begin();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Format::Any<char> any;
|
||||||
|
for (const auto &type : column->type)
|
||||||
|
{
|
||||||
|
auto f = charToGetFunction(type);
|
||||||
|
assert(f != nullptr);
|
||||||
|
any.base().push_back(f);
|
||||||
|
}
|
||||||
|
result.push_back(std::move(any));
|
||||||
|
|
||||||
|
if (++column != columns.end())
|
||||||
|
result.push_back(" ");
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Format::AST<char>(std::move(result));
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_slash_at_the_end(std::string &s)
|
void add_slash_at_the_end(std::string &s)
|
||||||
@@ -165,19 +159,14 @@ std::string adjust_directory(std::string s)
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string adjust_and_validate_format(std::string format)
|
|
||||||
{
|
|
||||||
MPD::Song::validateFormat(format);
|
|
||||||
format = "{" + format + "}";
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
// parser worker for buffer
|
// parser worker for buffer
|
||||||
template <typename ValueT, typename TransformT>
|
template <typename ValueT, typename TransformT>
|
||||||
option_parser::worker buffer(NC::Buffer &arg, ValueT &&value, TransformT &&map)
|
option_parser::worker buffer(NC::Buffer &arg, ValueT &&value, TransformT &&map)
|
||||||
{
|
{
|
||||||
return option_parser::worker(assign<std::string>(arg, [&arg, map](std::string s) {
|
return option_parser::worker(assign<std::string>(arg, [&arg, map](std::string s) {
|
||||||
return map(stringToBuffer(std::move(s)));
|
NC::Buffer result;
|
||||||
|
Format::print(Format::parse(s), result, nullptr);
|
||||||
|
return result;
|
||||||
}), defaults_to(arg, map(std::forward<ValueT>(value))));
|
}), defaults_to(arg, map(std::forward<ValueT>(value))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,38 +271,31 @@ bool Configuration::read(const std::string &config_path)
|
|||||||
message_delay_time, 5
|
message_delay_time, 5
|
||||||
));
|
));
|
||||||
p.add("song_list_format", assign_default<std::string>(
|
p.add("song_list_format", assign_default<std::string>(
|
||||||
song_list_format, "{%a - }{%t}|{$8%f$9}$R{$3(%l)$9}", [this](std::string s) {
|
song_list_format, "{%a - }{%t}|{$8%f$9}$R{$3(%l)$9}", Format::parse
|
||||||
auto result = adjust_and_validate_format(std::move(s));
|
));
|
||||||
song_list_format_dollar_free = remove_dollar_formatting(result);
|
|
||||||
return result;
|
|
||||||
}));
|
|
||||||
p.add("song_status_format", assign_default<std::string>(
|
p.add("song_status_format", assign_default<std::string>(
|
||||||
song_status_format, "{{%a{ \"%b\"{ (%y)}} - }{%t}}|{%f}", [this](std::string s) {
|
song_status_format, "{{%a{ \"%b\"{ (%y)}} - }{%t}}|{%f}", [this](std::string v) {
|
||||||
auto result = adjust_and_validate_format(std::move(s));
|
// precompute wide format for status display
|
||||||
if (result.find("$") != std::string::npos)
|
song_status_wformat = Format::wparse(ToWString(v));
|
||||||
song_status_format_no_colors = stringToBuffer(result).str();
|
return Format::parse(v);
|
||||||
else
|
|
||||||
song_status_format_no_colors = result;
|
|
||||||
return result;
|
|
||||||
}));
|
}));
|
||||||
p.add("song_library_format", assign_default<std::string>(
|
p.add("song_library_format", assign_default<std::string>(
|
||||||
song_library_format, "{%n - }{%t}|{%f}", adjust_and_validate_format
|
song_library_format, "{%n - }{%t}|{%f}", Format::parse
|
||||||
));
|
|
||||||
p.add("tag_editor_album_format", assign_default<std::string>(
|
|
||||||
tag_editor_album_format, "{(%y) }%b", adjust_and_validate_format
|
|
||||||
));
|
));
|
||||||
p.add("browser_sort_mode", assign_default(
|
p.add("browser_sort_mode", assign_default(
|
||||||
browser_sort_mode, SortMode::Name
|
browser_sort_mode, SortMode::Name
|
||||||
));
|
));
|
||||||
p.add("browser_sort_format", assign_default<std::string>(
|
p.add("browser_sort_format", assign_default<std::string>(
|
||||||
browser_sort_format, "{%a - }{%t}|{%f} {(%l)}", adjust_and_validate_format
|
browser_sort_format, "{%a - }{%t}|{%f} {(%l)}", Format::parse
|
||||||
));
|
));
|
||||||
p.add("alternative_header_first_line_format", assign_default<std::string>(
|
p.add("alternative_header_first_line_format", assign_default<std::string>(
|
||||||
new_header_first_line, "$b$1$aqqu$/a$9 {%t}|{%f} $1$atqq$/a$9$/b", adjust_and_validate_format
|
new_header_first_line, "$b$1$aqqu$/a$9 {%t}|{%f} $1$atqq$/a$9$/b", [this](std::string v) {
|
||||||
));
|
return Format::wparse(ToWString(std::move(v)));
|
||||||
|
}));
|
||||||
p.add("alternative_header_second_line_format", assign_default<std::string>(
|
p.add("alternative_header_second_line_format", assign_default<std::string>(
|
||||||
new_header_second_line, "{{$4$b%a$/b$9}{ - $7%b$9}{ ($4%y$9)}}|{%D}", adjust_and_validate_format
|
new_header_second_line, "{{$4$b%a$/b$9}{ - $7%b$9}{ ($4%y$9)}}|{%D}", [this](std::string v) {
|
||||||
));
|
return Format::wparse(ToWString(std::move(v)));
|
||||||
|
}));
|
||||||
p.add("now_playing_prefix", buffer(
|
p.add("now_playing_prefix", buffer(
|
||||||
now_playing_prefix, NC::Buffer::init(NC::Format::Bold), [this](NC::Buffer buf) {
|
now_playing_prefix, NC::Buffer::init(NC::Format::Bold), [this](NC::Buffer buf) {
|
||||||
now_playing_prefix_length = wideLength(ToWString(buf.str()));
|
now_playing_prefix_length = wideLength(ToWString(buf.str()));
|
||||||
@@ -341,12 +323,13 @@ bool Configuration::read(const std::string &config_path)
|
|||||||
modified_item_prefix, NC::Buffer::init(NC::Color::Green, "> ", NC::Color::End), id_()
|
modified_item_prefix, NC::Buffer::init(NC::Color::Green, "> ", NC::Color::End), id_()
|
||||||
));
|
));
|
||||||
p.add("song_window_title_format", assign_default<std::string>(
|
p.add("song_window_title_format", assign_default<std::string>(
|
||||||
song_window_title_format, "{%a - }{%t}|{%f}", adjust_and_validate_format
|
song_window_title_format, "{%a - }{%t}|{%f}", Format::parse
|
||||||
));
|
));
|
||||||
p.add("song_columns_list_format", assign_default<std::string>(
|
p.add("song_columns_list_format", assign_default<std::string>(
|
||||||
columns_format, "(20)[]{a} (6f)[green]{NE} (50)[white]{t|f:Title} (20)[cyan]{b} (7f)[magenta]{l}",
|
columns_format, "(20)[]{a} (6f)[green]{NE} (50)[white]{t|f:Title} (20)[cyan]{b} (7f)[magenta]{l}",
|
||||||
[this](std::string v) {
|
[this](std::string v) {
|
||||||
boost::tie(columns, song_in_columns_to_string_format) = generate_columns(v);
|
columns = generate_columns(v);
|
||||||
|
song_columns_mode_format = columns_to_format(columns);
|
||||||
return v;
|
return v;
|
||||||
}));
|
}));
|
||||||
p.add("execute_on_song_change", assign_default(
|
p.add("execute_on_song_change", assign_default(
|
||||||
@@ -586,7 +569,7 @@ bool Configuration::read(const std::string &config_path)
|
|||||||
empty_tag, "<empty>"
|
empty_tag, "<empty>"
|
||||||
));
|
));
|
||||||
p.add("tags_separator", assign_default(
|
p.add("tags_separator", assign_default(
|
||||||
tags_separator, " | "
|
MPD::Song::TagsSeparator, " | "
|
||||||
));
|
));
|
||||||
p.add("tag_editor_extended_numeration", yes_no(
|
p.add("tag_editor_extended_numeration", yes_no(
|
||||||
tag_editor_extended_numeration, false
|
tag_editor_extended_numeration, false
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include <mpd/client.h>
|
#include <mpd/client.h>
|
||||||
|
|
||||||
#include "enums.h"
|
#include "enums.h"
|
||||||
|
#include "format.h"
|
||||||
#include "screen_type.h"
|
#include "screen_type.h"
|
||||||
#include "strbuffer.h"
|
#include "strbuffer.h"
|
||||||
|
|
||||||
@@ -61,21 +62,20 @@ struct Configuration
|
|||||||
std::string visualizer_fifo_path;
|
std::string visualizer_fifo_path;
|
||||||
std::string visualizer_output_name;
|
std::string visualizer_output_name;
|
||||||
std::string empty_tag;
|
std::string empty_tag;
|
||||||
std::string tags_separator;
|
|
||||||
std::string song_list_format;
|
Format::AST<char> song_list_format;
|
||||||
std::string song_list_format_dollar_free;
|
Format::AST<char> song_window_title_format;
|
||||||
std::string song_status_format;
|
Format::AST<char> song_library_format;
|
||||||
std::string song_status_format_no_colors;
|
Format::AST<char> song_columns_mode_format;
|
||||||
std::string song_window_title_format;
|
Format::AST<char> browser_sort_format;
|
||||||
std::string song_library_format;
|
Format::AST<char> song_status_format;
|
||||||
std::string tag_editor_album_format;
|
Format::AST<wchar_t> song_status_wformat;
|
||||||
std::string song_in_columns_to_string_format;
|
Format::AST<wchar_t> new_header_first_line;
|
||||||
std::string browser_sort_format;
|
Format::AST<wchar_t> new_header_second_line;
|
||||||
|
|
||||||
std::string external_editor;
|
std::string external_editor;
|
||||||
std::string system_encoding;
|
std::string system_encoding;
|
||||||
std::string execute_on_song_change;
|
std::string execute_on_song_change;
|
||||||
std::string new_header_first_line;
|
|
||||||
std::string new_header_second_line;
|
|
||||||
std::string lastfm_preferred_language;
|
std::string lastfm_preferred_language;
|
||||||
std::wstring progressbar;
|
std::wstring progressbar;
|
||||||
std::wstring visualizer_chars;
|
std::wstring visualizer_chars;
|
||||||
|
|||||||
137
src/song.cpp
137
src/song.cpp
@@ -42,7 +42,9 @@ size_t calc_hash(const char* s, unsigned seed = 0)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace MPD {//
|
namespace MPD {
|
||||||
|
|
||||||
|
std::string Song::TagsSeparator = " | ";
|
||||||
|
|
||||||
std::string Song::get(mpd_tag_type type, unsigned idx) const
|
std::string Song::get(mpd_tag_type type, unsigned idx) const
|
||||||
{
|
{
|
||||||
@@ -197,7 +199,7 @@ std::string Song::getPriority(unsigned idx) const
|
|||||||
return boost::lexical_cast<std::string>(getPrio());
|
return boost::lexical_cast<std::string>(getPrio());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string MPD::Song::getTags(GetFunction f, const std::string &tags_separator) const
|
std::string MPD::Song::getTags(GetFunction f) const
|
||||||
{
|
{
|
||||||
assert(m_song);
|
assert(m_song);
|
||||||
unsigned idx = 0;
|
unsigned idx = 0;
|
||||||
@@ -205,7 +207,7 @@ std::string MPD::Song::getTags(GetFunction f, const std::string &tags_separator)
|
|||||||
for (std::string tag; !(tag = (this->*f)(idx)).empty(); ++idx)
|
for (std::string tag; !(tag = (this->*f)(idx)).empty(); ++idx)
|
||||||
{
|
{
|
||||||
if (!result.empty())
|
if (!result.empty())
|
||||||
result += tags_separator;
|
result += TagsSeparator;
|
||||||
result += tag;
|
result += tag;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -259,13 +261,6 @@ bool Song::empty() const
|
|||||||
return m_song.get() == 0;
|
return m_song.get() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Song::toString(const std::string &fmt, const std::string &tags_separator, const std::string &escape_chars) const
|
|
||||||
{
|
|
||||||
assert(m_song);
|
|
||||||
std::string::const_iterator it = fmt.begin();
|
|
||||||
return ParseFormat(it, tags_separator, escape_chars);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Song::ShowTime(unsigned length)
|
std::string Song::ShowTime(unsigned length)
|
||||||
{
|
{
|
||||||
int hours = length/3600;
|
int hours = length/3600;
|
||||||
@@ -282,126 +277,4 @@ std::string Song::ShowTime(unsigned length)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MPD::Song::validateFormat(const std::string &fmt)
|
|
||||||
{
|
|
||||||
int braces = 0;
|
|
||||||
for (std::string::const_iterator it = fmt.begin(); it != fmt.end(); ++it)
|
|
||||||
{
|
|
||||||
if (*it == '{')
|
|
||||||
++braces;
|
|
||||||
else if (*it == '}')
|
|
||||||
--braces;
|
|
||||||
}
|
|
||||||
if (braces)
|
|
||||||
throw std::runtime_error("number of opening and closing braces is not equal");
|
|
||||||
|
|
||||||
for (size_t i = fmt.find('%'); i != std::string::npos; i = fmt.find('%', i))
|
|
||||||
{
|
|
||||||
if (isdigit(fmt[++i]))
|
|
||||||
while (isdigit(fmt[++i])) { }
|
|
||||||
if (!charToGetFunction(fmt[i]))
|
|
||||||
throw std::runtime_error(
|
|
||||||
(boost::format("invalid character at position %1%: %2%") % (i+1) % fmt[i]).str()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Song::ParseFormat(std::string::const_iterator &it, const std::string &tags_separator,
|
|
||||||
const std::string &escape_chars) const
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
bool has_some_tags = 0;
|
|
||||||
MPD::Song::GetFunction get_fun = 0;
|
|
||||||
while (*++it != '}')
|
|
||||||
{
|
|
||||||
while (*it == '{')
|
|
||||||
{
|
|
||||||
std::string tags = ParseFormat(it, tags_separator, escape_chars);
|
|
||||||
if (!tags.empty())
|
|
||||||
{
|
|
||||||
has_some_tags = 1;
|
|
||||||
result += tags;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (*it == '}')
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (*it == '%')
|
|
||||||
{
|
|
||||||
size_t delimiter = 0;
|
|
||||||
if (isdigit(*++it))
|
|
||||||
{
|
|
||||||
delimiter = atol(&*it);
|
|
||||||
while (isdigit(*++it)) { }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*it == '%')
|
|
||||||
{
|
|
||||||
result += *it;
|
|
||||||
get_fun = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
get_fun = charToGetFunction(*it);
|
|
||||||
|
|
||||||
if (get_fun)
|
|
||||||
{
|
|
||||||
std::string tag = getTags(get_fun, tags_separator);
|
|
||||||
if (!escape_chars.empty()) // prepend format escape character to all given chars to escape
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < escape_chars.length(); ++i)
|
|
||||||
for (size_t j = 0; (j = tag.find(escape_chars[i], j)) != std::string::npos; j += 2)
|
|
||||||
tag.replace(j, 1, std::string(1, FormatEscapeCharacter) + escape_chars[i]);
|
|
||||||
}
|
|
||||||
if (!tag.empty() && (get_fun != &MPD::Song::getLength || getDuration() > 0))
|
|
||||||
{
|
|
||||||
if (delimiter && tag.size() > delimiter)
|
|
||||||
{
|
|
||||||
// Shorten length string by just chopping off the tail
|
|
||||||
if (get_fun == &MPD::Song::getLength)
|
|
||||||
tag = tag.substr(0, delimiter);
|
|
||||||
else
|
|
||||||
tag = ToString(wideShorten(ToWString(tag), delimiter));
|
|
||||||
}
|
|
||||||
has_some_tags = 1;
|
|
||||||
result += tag;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
result += *it;
|
|
||||||
}
|
|
||||||
int brace_counter = 0;
|
|
||||||
if (*it != '}' || !has_some_tags)
|
|
||||||
{
|
|
||||||
for (; *it != '}' || brace_counter; ++it)
|
|
||||||
{
|
|
||||||
if (*it == '{')
|
|
||||||
++brace_counter;
|
|
||||||
else if (*it == '}')
|
|
||||||
--brace_counter;
|
|
||||||
}
|
|
||||||
if (*++it == '|')
|
|
||||||
return ParseFormat(++it, tags_separator, escape_chars);
|
|
||||||
else
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (*(it+1) == '|')
|
|
||||||
{
|
|
||||||
for (; *it != '}' || *(it+1) == '|' || brace_counter; ++it)
|
|
||||||
{
|
|
||||||
if (*it == '{')
|
|
||||||
++brace_counter;
|
|
||||||
else if (*it == '}')
|
|
||||||
--brace_counter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
++it;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/song.h
11
src/song.h
@@ -72,7 +72,7 @@ struct Song
|
|||||||
virtual std::string getLength(unsigned idx = 0) const;
|
virtual std::string getLength(unsigned idx = 0) const;
|
||||||
virtual std::string getPriority(unsigned idx = 0) const;
|
virtual std::string getPriority(unsigned idx = 0) const;
|
||||||
|
|
||||||
virtual std::string getTags(GetFunction f, const std::string &tags_separator) const;
|
virtual std::string getTags(GetFunction f) const;
|
||||||
|
|
||||||
virtual unsigned getDuration() const;
|
virtual unsigned getDuration() const;
|
||||||
virtual unsigned getPosition() const;
|
virtual unsigned getPosition() const;
|
||||||
@@ -85,9 +85,6 @@ struct Song
|
|||||||
|
|
||||||
virtual bool empty() const;
|
virtual bool empty() const;
|
||||||
|
|
||||||
virtual std::string toString(const std::string &fmt, const std::string &tags_separator,
|
|
||||||
const std::string &escape_chars = "") const;
|
|
||||||
|
|
||||||
bool operator==(const Song &rhs) const {
|
bool operator==(const Song &rhs) const {
|
||||||
if (m_hash != rhs.m_hash)
|
if (m_hash != rhs.m_hash)
|
||||||
return false;
|
return false;
|
||||||
@@ -102,14 +99,10 @@ struct Song
|
|||||||
const char *c_uri() const { return m_song ? mpd_song_get_uri(m_song.get()) : ""; }
|
const char *c_uri() const { return m_song ? mpd_song_get_uri(m_song.get()) : ""; }
|
||||||
|
|
||||||
static std::string ShowTime(unsigned length);
|
static std::string ShowTime(unsigned length);
|
||||||
static void validateFormat(const std::string &fmt);
|
|
||||||
|
|
||||||
static const char FormatEscapeCharacter = 1;
|
static std::string TagsSeparator;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string ParseFormat(std::string::const_iterator &it, const std::string &tags_separator,
|
|
||||||
const std::string &escape_chars) const;
|
|
||||||
|
|
||||||
std::shared_ptr<mpd_song> m_song;
|
std::shared_ptr<mpd_song> m_song;
|
||||||
size_t m_hash;
|
size_t m_hash;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -144,6 +144,6 @@ void SongInfo::PrepareSong(MPD::Song &s)
|
|||||||
for (const Metadata *m = Tags; m->Name; ++m)
|
for (const Metadata *m = Tags; m->Name; ++m)
|
||||||
{
|
{
|
||||||
w << NC::Format::Bold << '\n' << m->Name << ": " << NC::Format::NoBold;
|
w << NC::Format::Bold << '\n' << m->Name << ": " << NC::Format::NoBold;
|
||||||
ShowTag(w, s.getTags(m->Get, Config.tags_separator));
|
ShowTag(w, s.getTags(m->Get));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,8 +171,8 @@ void SortPlaylistDialog::sort() const
|
|||||||
auto song_cmp = [this, &cmp](const MPD::Song &a, const MPD::Song &b) -> bool {
|
auto song_cmp = [this, &cmp](const MPD::Song &a, const MPD::Song &b) -> bool {
|
||||||
for (auto it = w.beginV(); it->item().second; ++it)
|
for (auto it = w.beginV(); it->item().second; ++it)
|
||||||
{
|
{
|
||||||
int res = cmp(a.getTags(it->item().second, Config.tags_separator),
|
int res = cmp(a.getTags(it->item().second),
|
||||||
b.getTags(it->item().second, Config.tags_separator));
|
b.getTags(it->item().second));
|
||||||
if (res != 0)
|
if (res != 0)
|
||||||
return res < 0;
|
return res < 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ int m_volume;
|
|||||||
void drawTitle(const MPD::Song &np)
|
void drawTitle(const MPD::Song &np)
|
||||||
{
|
{
|
||||||
assert(!np.empty());
|
assert(!np.empty());
|
||||||
windowTitle(np.toString(Config.song_window_title_format, Config.tags_separator));
|
windowTitle(Format::stringify<char>(Config.song_window_title_format, &np));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string playerStateToString(MPD::PlayerState ps)
|
std::string playerStateToString(MPD::PlayerState ps)
|
||||||
@@ -591,7 +591,7 @@ void Status::Changes::elapsedTime(bool update_elapsed)
|
|||||||
tracklength += "]";
|
tracklength += "]";
|
||||||
}
|
}
|
||||||
NC::WBuffer np_song;
|
NC::WBuffer np_song;
|
||||||
stringToBuffer(ToWString(Charset::utf8ToLocale(np.toString(Config.song_status_format, Config.tags_separator, "$"))), np_song);
|
Format::print(Config.song_status_wformat, np_song, &np);
|
||||||
*wFooter << NC::XY(0, 1) << wclrtoeol << NC::Format::Bold << ps << NC::Format::NoBold;
|
*wFooter << NC::XY(0, 1) << wclrtoeol << NC::Format::Bold << ps << NC::Format::NoBold;
|
||||||
writeCyclicBuffer(np_song, *wFooter, playing_song_scroll_begin, wFooter->getWidth()-ps.length()-tracklength.length(), L" ** ");
|
writeCyclicBuffer(np_song, *wFooter, playing_song_scroll_begin, wFooter->getWidth()-ps.length()-tracklength.length(), L" ** ");
|
||||||
*wFooter << NC::Format::Bold << NC::XY(wFooter->getWidth()-tracklength.length(), 1) << tracklength << NC::Format::NoBold;
|
*wFooter << NC::Format::Bold << NC::XY(wFooter->getWidth()-tracklength.length(), 1) << tracklength << NC::Format::NoBold;
|
||||||
@@ -619,8 +619,8 @@ void Status::Changes::elapsedTime(bool update_elapsed)
|
|||||||
}
|
}
|
||||||
|
|
||||||
NC::WBuffer first, second;
|
NC::WBuffer first, second;
|
||||||
stringToBuffer(ToWString(Charset::utf8ToLocale(np.toString(Config.new_header_first_line, Config.tags_separator, "$"))), first);
|
Format::print(Config.new_header_first_line, first, &np);
|
||||||
stringToBuffer(ToWString(Charset::utf8ToLocale(np.toString(Config.new_header_second_line, Config.tags_separator, "$"))), second);
|
Format::print(Config.new_header_second_line, second, &np);
|
||||||
|
|
||||||
size_t first_len = wideLength(first.str());
|
size_t first_len = wideLength(first.str());
|
||||||
size_t first_margin = (std::max(tracklength.length()+1, VolumeState.length()))*2;
|
size_t first_margin = (std::max(tracklength.length()+1, VolumeState.length()))*2;
|
||||||
|
|||||||
@@ -490,17 +490,17 @@ void TagEditor::enterPressed()
|
|||||||
{
|
{
|
||||||
Statusbar::ScopedLock slock;
|
Statusbar::ScopedLock slock;
|
||||||
Statusbar::put() << NC::Format::Bold << TagTypes->current()->value() << NC::Format::NoBold << ": ";
|
Statusbar::put() << NC::Format::Bold << TagTypes->current()->value() << NC::Format::NoBold << ": ";
|
||||||
std::string new_tag = wFooter->prompt(Tags->current()->value().getTags(get, Config.tags_separator));
|
std::string new_tag = wFooter->prompt(Tags->current()->value().getTags(get));
|
||||||
for (auto it = EditedSongs.begin(); it != EditedSongs.end(); ++it)
|
for (auto it = EditedSongs.begin(); it != EditedSongs.end(); ++it)
|
||||||
(*it)->setTags(set, new_tag, Config.tags_separator);
|
(*it)->setTags(set, new_tag);
|
||||||
}
|
}
|
||||||
else if (w == Tags)
|
else if (w == Tags)
|
||||||
{
|
{
|
||||||
Statusbar::ScopedLock slock;
|
Statusbar::ScopedLock slock;
|
||||||
Statusbar::put() << NC::Format::Bold << TagTypes->current()->value() << NC::Format::NoBold << ": ";
|
Statusbar::put() << NC::Format::Bold << TagTypes->current()->value() << NC::Format::NoBold << ": ";
|
||||||
std::string new_tag = wFooter->prompt(Tags->current()->value().getTags(get, Config.tags_separator));
|
std::string new_tag = wFooter->prompt(Tags->current()->value().getTags(get));
|
||||||
if (new_tag != Tags->current()->value().getTags(get, Config.tags_separator))
|
if (new_tag != Tags->current()->value().getTags(get))
|
||||||
Tags->current()->value().setTags(set, new_tag, Config.tags_separator);
|
Tags->current()->value().setTags(set, new_tag);
|
||||||
Tags->scroll(NC::Scroll::Down);
|
Tags->scroll(NC::Scroll::Down);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1053,7 +1053,7 @@ MPD::MutableSong::SetFunction IntoSetFunction(char c)
|
|||||||
|
|
||||||
std::string GenerateFilename(const MPD::MutableSong &s, const std::string &pattern)
|
std::string GenerateFilename(const MPD::MutableSong &s, const std::string &pattern)
|
||||||
{
|
{
|
||||||
std::string result = s.toString(pattern, Config.tags_separator);
|
std::string result = Format::stringify<char>(Format::parse(pattern), &s);
|
||||||
removeInvalidCharsFromFilename(result, Config.generate_win32_compatible_filenames);
|
removeInvalidCharsFromFilename(result, Config.generate_win32_compatible_filenames);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -1105,7 +1105,7 @@ std::string ParseFilename(MPD::MutableSong &s, std::string mask, bool preview)
|
|||||||
{
|
{
|
||||||
MPD::MutableSong::SetFunction set = IntoSetFunction(it->first);
|
MPD::MutableSong::SetFunction set = IntoSetFunction(it->first);
|
||||||
if (set)
|
if (set)
|
||||||
s.setTags(set, it->second, Config.tags_separator);
|
s.setTags(set, it->second);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
result << "%" << it->first << ": " << it->second << "\n";
|
result << "%" << it->first << ": " << it->second << "\n";
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ void writeID3v2Tags(const MPD::MutableSong &s, TagLib::ID3v2::Tag *tag)
|
|||||||
auto frame = new TagLib::ID3v2::CommentsFrame(TagLib::String::UTF8);
|
auto frame = new TagLib::ID3v2::CommentsFrame(TagLib::String::UTF8);
|
||||||
// apparently there can't be multiple comments,
|
// apparently there can't be multiple comments,
|
||||||
// so if there is more than one, join them.
|
// so if there is more than one, join them.
|
||||||
frame->setText(join(list, TagLib::String(Config.tags_separator, TagLib::String::UTF8)));
|
frame->setText(join(list, TagLib::String(MPD::Song::TagsSeparator, TagLib::String::UTF8)));
|
||||||
tag->addFrame(frame);
|
tag->addFrame(frame);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -107,10 +107,10 @@ void TinyTagEditor::enterPressed()
|
|||||||
size_t pos = option-8;
|
size_t pos = option-8;
|
||||||
Statusbar::put() << NC::Format::Bold << SongInfo::Tags[pos].Name << ": " << NC::Format::NoBold;
|
Statusbar::put() << NC::Format::Bold << SongInfo::Tags[pos].Name << ": " << NC::Format::NoBold;
|
||||||
itsEdited.setTags(SongInfo::Tags[pos].Set, Global::wFooter->prompt(
|
itsEdited.setTags(SongInfo::Tags[pos].Set, Global::wFooter->prompt(
|
||||||
itsEdited.getTags(SongInfo::Tags[pos].Get, Config.tags_separator)), Config.tags_separator);
|
itsEdited.getTags(SongInfo::Tags[pos].Get)));
|
||||||
w.at(option).value().clear();
|
w.at(option).value().clear();
|
||||||
w.at(option).value() << NC::Format::Bold << SongInfo::Tags[pos].Name << ':' << NC::Format::NoBold << ' ';
|
w.at(option).value() << NC::Format::Bold << SongInfo::Tags[pos].Name << ':' << NC::Format::NoBold << ' ';
|
||||||
ShowTag(w.at(option).value(), itsEdited.getTags(SongInfo::Tags[pos].Get, Config.tags_separator));
|
ShowTag(w.at(option).value(), itsEdited.getTags(SongInfo::Tags[pos].Get));
|
||||||
}
|
}
|
||||||
else if (option == 20)
|
else if (option == 20)
|
||||||
{
|
{
|
||||||
@@ -226,7 +226,7 @@ bool TinyTagEditor::getTags()
|
|||||||
for (const SongInfo::Metadata *m = SongInfo::Tags; m->Name; ++m, ++pos)
|
for (const SongInfo::Metadata *m = SongInfo::Tags; m->Name; ++m, ++pos)
|
||||||
{
|
{
|
||||||
w.at(pos).value() << NC::Format::Bold << m->Name << ":" << NC::Format::NoBold << ' ';
|
w.at(pos).value() << NC::Format::Bold << m->Name << ":" << NC::Format::NoBold << ' ';
|
||||||
ShowTag(w.at(pos).value(), itsEdited.getTags(m->Get, Config.tags_separator));
|
ShowTag(w.at(pos).value(), itsEdited.getTags(m->Get));
|
||||||
}
|
}
|
||||||
|
|
||||||
w.at(20).value() << NC::Format::Bold << "Filename:" << NC::Format::NoBold << ' ' << itsEdited.getName();
|
w.at(20).value() << NC::Format::Bold << "Filename:" << NC::Format::NoBold << ' ' << itsEdited.getName();
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ bool LocaleBasedItemSorting::operator()(const MPD::Item &a, const MPD::Item &b)
|
|||||||
result = m_cmp(a.playlist().path(), b.playlist().path());
|
result = m_cmp(a.playlist().path(), b.playlist().path());
|
||||||
break;
|
break;
|
||||||
case MPD::Item::Type::Song:
|
case MPD::Item::Type::Song:
|
||||||
result = m_cmp(a.song().toString(Config.browser_sort_format, Config.tags_separator),
|
result = m_cmp(Format::stringify<char>(Config.browser_sort_format, &a.song()),
|
||||||
b.song().toString(Config.browser_sort_format, Config.tags_separator));
|
Format::stringify<char>(Config.browser_sort_format, &b.song()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ MPD::Song::GetFunction charToGetFunction(char c)
|
|||||||
case 'P':
|
case 'P':
|
||||||
return &MPD::Song::getPriority;
|
return &MPD::Song::getPriority;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
#include <boost/locale/encoding.hpp>
|
#include <boost/locale/encoding_utf.hpp>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include "utility/wide_string.h"
|
#include "utility/wide_string.h"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user