display: rewrite Display::Columns and Display::SongsInColumns
This commit is contained in:
243
src/display.cpp
243
src/display.cpp
@@ -75,56 +75,66 @@ std::string Display::Columns(size_t list_width)
|
|||||||
if (Config.columns.empty())
|
if (Config.columns.empty())
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
std::basic_string<my_char_t> result;
|
std::string result;
|
||||||
size_t where = 0;
|
|
||||||
int width;
|
int width;
|
||||||
|
int remained_width = list_width;
|
||||||
std::vector<Column>::const_iterator next2last;
|
std::vector<Column>::const_iterator it, last = Config.columns.end() - 1;
|
||||||
bool last_fixed = Config.columns.back().fixed;
|
for (it = Config.columns.begin(); it != Config.columns.end(); ++it)
|
||||||
if (Config.columns.size() > 1)
|
|
||||||
next2last = Config.columns.end()-2;
|
|
||||||
|
|
||||||
for (std::vector<Column>::const_iterator it = Config.columns.begin(); it != Config.columns.end(); ++it)
|
|
||||||
{
|
{
|
||||||
if (it == Config.columns.end()-1)
|
// column has relative width and all after it have fixed width,
|
||||||
width = list_width-where;
|
// so stretch it so it fills whole screen along with these after.
|
||||||
else if (last_fixed && it == next2last)
|
if (it->stretch_limit >= 0) // (*)
|
||||||
width = list_width-where-1-(++next2last)->width;
|
width = remained_width - it->stretch_limit;
|
||||||
else
|
else
|
||||||
width = it->width*(it->fixed ? 1 : list_width/100.0);
|
width = it->fixed ? it->width : it->width * list_width * 0.01;
|
||||||
|
// columns with relative width may shrink to 0, omit them
|
||||||
|
if (width == 0)
|
||||||
|
continue;
|
||||||
|
// if column is not last, we need to have spacing between it
|
||||||
|
// and next column, so we substract it now and restore later.
|
||||||
|
if (it != last)
|
||||||
|
--width;
|
||||||
|
|
||||||
std::basic_string<my_char_t> tag;
|
// if column doesn't fit into screen, discard it and any other after it.
|
||||||
if (it->type.length() >= 1 && it->name.empty())
|
if (remained_width-width < 0 || width < 0 /* this one may come from (*) */)
|
||||||
|
break;
|
||||||
|
|
||||||
|
std::basic_string<my_char_t> name;
|
||||||
|
if (it->name.empty())
|
||||||
{
|
{
|
||||||
for (size_t j = 0; j < it->type.length(); ++j)
|
for (size_t j = 0; j < it->type.length(); ++j)
|
||||||
{
|
{
|
||||||
tag += toColumnName(it->type[j]);
|
name += toColumnName(it->type[j]);
|
||||||
tag += '/';
|
name += '/';
|
||||||
}
|
}
|
||||||
tag.resize(tag.length()-1);
|
name.resize(name.length()-1);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
tag = it->name;
|
name = it->name;
|
||||||
|
Window::Cut(name, width);
|
||||||
|
|
||||||
|
int x_off = std::max(0, width - int(Window::Length(name)));
|
||||||
if (it->right_alignment)
|
if (it->right_alignment)
|
||||||
{
|
{
|
||||||
long i = width-tag.length()-(it != Config.columns.begin());
|
result += std::string(x_off, KEY_SPACE);
|
||||||
if (i > 0)
|
result += TO_STRING(name);
|
||||||
result.resize(result.length()+i, ' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
where += width;
|
|
||||||
result += tag;
|
|
||||||
|
|
||||||
if (result.length() > where)
|
|
||||||
{
|
|
||||||
result.resize(where);
|
|
||||||
result += ' ';
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
result.resize(std::min(where+1, size_t(COLS)), ' ');
|
{
|
||||||
|
result += TO_STRING(name);
|
||||||
|
result += std::string(x_off, KEY_SPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it != last)
|
||||||
|
{
|
||||||
|
// add missing width's part and restore the value.
|
||||||
|
remained_width -= width+1;
|
||||||
|
result += ' ';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result.resize(list_width);
|
|
||||||
return TO_STRING(result);
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Display::SongsInColumns(const MPD::Song &s, void *data, Menu<MPD::Song> *menu)
|
void Display::SongsInColumns(const MPD::Song &s, void *data, Menu<MPD::Song> *menu)
|
||||||
@@ -132,13 +142,6 @@ void Display::SongsInColumns(const MPD::Song &s, void *data, Menu<MPD::Song> *me
|
|||||||
if (!s.Localized())
|
if (!s.Localized())
|
||||||
const_cast<MPD::Song *>(&s)->Localize();
|
const_cast<MPD::Song *>(&s)->Localize();
|
||||||
|
|
||||||
/// FIXME: This function is pure mess, it needs to be
|
|
||||||
/// rewritten and unified with Display::Columns() a bit.
|
|
||||||
|
|
||||||
bool is_now_playing = menu == myPlaylist->Items && (menu->isFiltered() ? s.GetPosition() : menu->CurrentlyDrawedPosition()) == size_t(myPlaylist->NowPlaying);
|
|
||||||
if (is_now_playing)
|
|
||||||
*menu << Config.now_playing_prefix;
|
|
||||||
|
|
||||||
if (Config.columns.empty())
|
if (Config.columns.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -153,95 +156,115 @@ void Display::SongsInColumns(const MPD::Song &s, void *data, Menu<MPD::Song> *me
|
|||||||
if (separate_albums)
|
if (separate_albums)
|
||||||
*menu << fmtUnderline;
|
*menu << fmtUnderline;
|
||||||
|
|
||||||
std::vector<Column>::const_iterator next2last, last, it;
|
int song_pos = menu->isFiltered() ? s.GetPosition() : menu->CurrentlyDrawedPosition();
|
||||||
size_t where = 0;
|
bool is_now_playing = menu == myPlaylist->Items && song_pos == myPlaylist->NowPlaying;
|
||||||
|
bool is_selected = menu->isSelected(menu->CurrentlyDrawedPosition());
|
||||||
|
bool discard_colors = Config.discard_colors_if_item_is_selected && is_selected;
|
||||||
|
|
||||||
|
if (is_now_playing)
|
||||||
|
*menu << Config.now_playing_prefix;
|
||||||
|
|
||||||
int width;
|
int width;
|
||||||
|
int y = menu->Y();
|
||||||
bool last_fixed = Config.columns.back().fixed;
|
int remained_width = menu->GetWidth();
|
||||||
if (Config.columns.size() > 1)
|
std::vector<Column>::const_iterator it, last = Config.columns.end() - 1;
|
||||||
next2last = Config.columns.end()-2;
|
|
||||||
last = Config.columns.end()-1;
|
|
||||||
|
|
||||||
bool discard_colors = Config.discard_colors_if_item_is_selected && menu->isSelected(menu->CurrentlyDrawedPosition());
|
|
||||||
|
|
||||||
for (it = Config.columns.begin(); it != Config.columns.end(); ++it)
|
for (it = Config.columns.begin(); it != Config.columns.end(); ++it)
|
||||||
{
|
{
|
||||||
if (where)
|
// check current X coordinate
|
||||||
|
int x = menu->X();
|
||||||
|
// column has relative width and all after it have fixed width,
|
||||||
|
// so stretch it so it fills whole screen along with these after.
|
||||||
|
if (it->stretch_limit >= 0) // (*)
|
||||||
|
width = remained_width - it->stretch_limit;
|
||||||
|
else
|
||||||
|
width = it->fixed ? it->width : it->width * menu->GetWidth() * 0.01;
|
||||||
|
// columns with relative width may shrink to 0, omit them
|
||||||
|
if (width == 0)
|
||||||
|
continue;
|
||||||
|
// if column is not last, we need to have spacing between it
|
||||||
|
// and next column, so we substract it now and restore later.
|
||||||
|
if (it != last)
|
||||||
|
--width;
|
||||||
|
|
||||||
|
if (it == Config.columns.begin() && (is_now_playing || is_selected))
|
||||||
{
|
{
|
||||||
menu->GotoXY(where, menu->Y());
|
// here comes the shitty part. if we applied now playing or selected
|
||||||
*menu << ' ';
|
// prefix, first column's width needs to be properly modified, so
|
||||||
if (!discard_colors && (it-1)->color != clDefault)
|
// next column is not affected by them. if prefixes fit, we just
|
||||||
*menu << clEnd;
|
// subtract their width from allowed column's width. if they don't,
|
||||||
|
// then we pretend that they do, but we adjust current cursor position
|
||||||
|
// so part of them will be overwritten by next column.
|
||||||
|
int offset = 0;
|
||||||
|
if (is_now_playing)
|
||||||
|
offset += Config.now_playing_prefix_length;
|
||||||
|
if (is_selected)
|
||||||
|
offset += Config.selected_item_prefix_length;
|
||||||
|
if (width-offset < 0)
|
||||||
|
{
|
||||||
|
remained_width -= width + 1;
|
||||||
|
menu->GotoXY(width, y);
|
||||||
|
*menu << ' ';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
width -= offset;
|
||||||
|
remained_width -= offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it == Config.columns.end()-1)
|
// if column doesn't fit into screen, discard it and any other after it.
|
||||||
width = menu->GetWidth()-where;
|
if (remained_width-width < 0 || width < 0 /* this one may come from (*) */)
|
||||||
else if (last_fixed && it == next2last)
|
break;
|
||||||
width = menu->GetWidth()-where-1-(++next2last)->width;
|
|
||||||
else
|
|
||||||
width = it->width*(it->fixed ? 1 : menu->GetWidth()/100.0);
|
|
||||||
|
|
||||||
MPD::Song::GetFunction get = 0;
|
std::basic_string<my_char_t> tag;
|
||||||
|
|
||||||
std::string tag;
|
|
||||||
for (size_t i = 0; i < it->type.length(); ++i)
|
for (size_t i = 0; i < it->type.length(); ++i)
|
||||||
{
|
{
|
||||||
get = toGetFunction(it->type[i]);
|
MPD::Song::GetFunction get = toGetFunction(it->type[i]);
|
||||||
tag = get ? s.GetTags(get) : "";
|
tag = TO_WSTRING(get ? s.GetTags(get) : "");
|
||||||
if (!tag.empty())
|
if (!tag.empty())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (tag.empty() && it->display_empty_tag)
|
||||||
|
tag = TO_WSTRING(Config.empty_tag);
|
||||||
|
Window::Cut(tag, width);
|
||||||
|
|
||||||
if (!discard_colors && it->color != clDefault)
|
if (!discard_colors && it->color != clDefault)
|
||||||
*menu << it->color;
|
*menu << it->color;
|
||||||
whline(menu->Raw(), 32, menu->GetWidth()-where);
|
|
||||||
|
|
||||||
// last column might need to be shrinked to make space for np/sel suffixes
|
|
||||||
if (it == last)
|
|
||||||
{
|
|
||||||
if (menu->isSelected(menu->CurrentlyDrawedPosition()))
|
|
||||||
width -= Config.selected_item_suffix_length;
|
|
||||||
if (is_now_playing)
|
|
||||||
width -= Config.now_playing_suffix_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
int x_off = 0;
|
||||||
|
// if column uses right alignment, calculate proper offset.
|
||||||
|
// otherwise just assume offset is 0, ie. we start from the left.
|
||||||
if (it->right_alignment)
|
if (it->right_alignment)
|
||||||
|
x_off = std::max(0, width - int(Window::Length(tag)));
|
||||||
|
|
||||||
|
whline(menu->Raw(), KEY_SPACE, width);
|
||||||
|
menu->GotoXY(x + x_off, y);
|
||||||
|
*menu << tag;
|
||||||
|
menu->GotoXY(x + width, y);
|
||||||
|
if (it != last)
|
||||||
{
|
{
|
||||||
if (width > 0 && (!tag.empty() || it->display_empty_tag))
|
// add missing width's part and restore the value.
|
||||||
{
|
*menu << ' ';
|
||||||
int x, y;
|
remained_width -= width+1;
|
||||||
menu->GetXY(x, y);
|
|
||||||
std::basic_string<my_char_t> wtag = TO_WSTRING(tag.empty() ? Config.empty_tag : tag).substr(0, width-!!x);
|
|
||||||
*menu << XY(x+width-Window::Length(wtag)-!!x, y) << wtag;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
if (!discard_colors && it->color != clDefault)
|
||||||
if (it == last)
|
*menu << clEnd;
|
||||||
{
|
|
||||||
if (width > 0)
|
|
||||||
{
|
|
||||||
std::basic_string<my_char_t> str;
|
|
||||||
if (!tag.empty())
|
|
||||||
str = TO_WSTRING(tag).substr(0, width-1);
|
|
||||||
else if (it->display_empty_tag)
|
|
||||||
str = TO_WSTRING(Config.empty_tag).substr(0, width-1);
|
|
||||||
*menu << str;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!tag.empty())
|
|
||||||
*menu << tag;
|
|
||||||
else if (it->display_empty_tag)
|
|
||||||
*menu << Config.empty_tag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
where += width;
|
|
||||||
}
|
}
|
||||||
if (!discard_colors && (--it)->color != clDefault)
|
|
||||||
*menu << clEnd;
|
// here comes the shitty part, second chapter. here we apply
|
||||||
|
// now playing suffix or/and make room for selected suffix
|
||||||
|
// (as it will be applied in Menu::Refresh when this function
|
||||||
|
// returns there).
|
||||||
if (is_now_playing)
|
if (is_now_playing)
|
||||||
|
{
|
||||||
|
int np_x = menu->GetWidth() - Config.now_playing_suffix_length;
|
||||||
|
if (is_selected)
|
||||||
|
np_x -= Config.selected_item_suffix_length;
|
||||||
|
menu->GotoXY(np_x, y);
|
||||||
*menu << Config.now_playing_suffix;
|
*menu << Config.now_playing_suffix;
|
||||||
|
}
|
||||||
|
if (is_selected)
|
||||||
|
menu->GotoXY(menu->GetWidth() - Config.selected_item_suffix_length, y);
|
||||||
|
|
||||||
if (separate_albums)
|
if (separate_albums)
|
||||||
*menu << fmtUnderlineEnd;
|
*menu << fmtUnderlineEnd;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -371,6 +371,7 @@ void Configuration::SetDefaults()
|
|||||||
search_engine_default_search_mode = 0;
|
search_engine_default_search_mode = 0;
|
||||||
visualizer_sync_interval = 30;
|
visualizer_sync_interval = 30;
|
||||||
locked_screen_width_part = 0.5;
|
locked_screen_width_part = 0.5;
|
||||||
|
selected_item_prefix_length = 0;
|
||||||
selected_item_suffix_length = 0;
|
selected_item_suffix_length = 0;
|
||||||
now_playing_suffix_length = 0;
|
now_playing_suffix_length = 0;
|
||||||
# ifdef HAVE_LANGINFO_H
|
# ifdef HAVE_LANGINFO_H
|
||||||
@@ -655,6 +656,7 @@ void Configuration::Read()
|
|||||||
{
|
{
|
||||||
selected_item_prefix.Clear();
|
selected_item_prefix.Clear();
|
||||||
String2Buffer(v, selected_item_prefix);
|
String2Buffer(v, selected_item_prefix);
|
||||||
|
selected_item_prefix_length = Window::Length(TO_WSTRING(selected_item_prefix.Str()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (name == "selected_item_suffix")
|
else if (name == "selected_item_suffix")
|
||||||
@@ -672,6 +674,7 @@ void Configuration::Read()
|
|||||||
{
|
{
|
||||||
now_playing_prefix.Clear();
|
now_playing_prefix.Clear();
|
||||||
String2Buffer(v, now_playing_prefix);
|
String2Buffer(v, now_playing_prefix);
|
||||||
|
now_playing_prefix_length = Window::Length(TO_WSTRING(now_playing_prefix.Str()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (name == "now_playing_suffix")
|
else if (name == "now_playing_suffix")
|
||||||
@@ -1121,6 +1124,23 @@ void Configuration::GenerateColumns()
|
|||||||
columns.push_back(col);
|
columns.push_back(col);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// calculate which column is the last one to have relative width and stretch it accordingly
|
||||||
|
if (!columns.empty())
|
||||||
|
{
|
||||||
|
int stretch_limit = 0;
|
||||||
|
auto it = columns.rbegin();
|
||||||
|
for (; it != columns.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (it->fixed)
|
||||||
|
stretch_limit += it->width;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// if it's false, all columns are fixed
|
||||||
|
if (it != columns.rend())
|
||||||
|
it->stretch_limit = stretch_limit;
|
||||||
|
}
|
||||||
|
|
||||||
// generate format for converting tags in columns to string for Playlist::SongInColumnsToString()
|
// generate format for converting tags in columns to string for Playlist::SongInColumnsToString()
|
||||||
char tag[] = "{% }|";
|
char tag[] = "{% }|";
|
||||||
song_in_columns_to_string_format = "{";
|
song_in_columns_to_string_format = "{";
|
||||||
|
|||||||
@@ -35,11 +35,12 @@ enum SortMode { smName, smMTime, smCustomFormat };
|
|||||||
|
|
||||||
struct Column
|
struct Column
|
||||||
{
|
{
|
||||||
Column() : right_alignment(0), display_empty_tag(1) { }
|
Column() : stretch_limit(-1), right_alignment(0), display_empty_tag(1) { }
|
||||||
|
|
||||||
std::basic_string<my_char_t> name;
|
std::basic_string<my_char_t> name;
|
||||||
std::string type;
|
std::string type;
|
||||||
unsigned width;
|
int width;
|
||||||
|
int stretch_limit;
|
||||||
Color color;
|
Color color;
|
||||||
bool fixed;
|
bool fixed;
|
||||||
bool right_alignment;
|
bool right_alignment;
|
||||||
@@ -219,7 +220,9 @@ struct Configuration
|
|||||||
|
|
||||||
double locked_screen_width_part;
|
double locked_screen_width_part;
|
||||||
|
|
||||||
|
size_t selected_item_prefix_length;
|
||||||
size_t selected_item_suffix_length;
|
size_t selected_item_suffix_length;
|
||||||
|
size_t now_playing_prefix_length;
|
||||||
size_t now_playing_suffix_length;
|
size_t now_playing_suffix_length;
|
||||||
|
|
||||||
BasicScreen *startup_screen;
|
BasicScreen *startup_screen;
|
||||||
|
|||||||
@@ -654,9 +654,9 @@ std::string Window::GetString(const std::string &base, size_t length, size_t wid
|
|||||||
|
|
||||||
void Window::GetXY(int &x, int &y)
|
void Window::GetXY(int &x, int &y)
|
||||||
{
|
{
|
||||||
getyx(itsWindow, y, x);
|
getyx(itsWindow, itsY, itsX);
|
||||||
itsX = x;
|
x = itsX;
|
||||||
itsY = y;
|
y = itsY;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Window::GotoXY(int x, int y)
|
void Window::GotoXY(int x, int y)
|
||||||
@@ -666,13 +666,15 @@ void Window::GotoXY(int x, int y)
|
|||||||
itsY = y;
|
itsY = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Window::X() const
|
int Window::X()
|
||||||
{
|
{
|
||||||
|
getyx(itsWindow, itsY, itsX);
|
||||||
return itsX;
|
return itsX;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Window::Y() const
|
int Window::Y()
|
||||||
{
|
{
|
||||||
|
getyx(itsWindow, itsY, itsX);
|
||||||
return itsY;
|
return itsY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -956,3 +958,17 @@ size_t Window::Length(const std::wstring &ws)
|
|||||||
# endif // WIN32
|
# endif // WIN32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::Cut (std::wstring &ws, size_t max_len)
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
int remained_len = max_len;
|
||||||
|
for (; i < ws.length(); ++i)
|
||||||
|
{
|
||||||
|
remained_len -= wcwidth(ws[i]);
|
||||||
|
if (remained_len < 0)
|
||||||
|
{
|
||||||
|
ws.resize(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
21
src/window.h
21
src/window.h
@@ -298,21 +298,15 @@ namespace NCurses
|
|||||||
///
|
///
|
||||||
void GotoXY(int x, int y);
|
void GotoXY(int x, int y);
|
||||||
|
|
||||||
/// @return last X position that was set with GetXY(). Note that it most
|
/// @return x window coordinate
|
||||||
/// likely won't report correct position unless called after GetXY() and
|
|
||||||
/// before any function that can print anything to window.
|
|
||||||
/// @see GetXY()
|
/// @see GetXY()
|
||||||
///
|
///
|
||||||
int X() const;
|
int X();
|
||||||
|
|
||||||
/// @return last Y position that was set with GetXY(). Since Y position
|
/// @return y windows coordinate
|
||||||
/// doesn't change so frequently as X does, it can be called (with some
|
|
||||||
/// caution) after something was printed to window and still may report
|
|
||||||
/// correct position, but if you're not sure, obtain Y pos with GetXY()
|
|
||||||
/// instead.
|
|
||||||
/// @see GetXY()
|
/// @see GetXY()
|
||||||
///
|
///
|
||||||
int Y() const;
|
int Y();
|
||||||
|
|
||||||
/// Used to indicate whether given coordinates of main screen lies within
|
/// Used to indicate whether given coordinates of main screen lies within
|
||||||
/// window area or not and if they do, transform them into in-window coords.
|
/// window area or not and if they do, transform them into in-window coords.
|
||||||
@@ -526,6 +520,13 @@ namespace NCurses
|
|||||||
///
|
///
|
||||||
static size_t Length(const std::wstring &ws);
|
static size_t Length(const std::wstring &ws);
|
||||||
|
|
||||||
|
/// Cuts string so it fits desired length on the screen. Note that it uses
|
||||||
|
/// wcwidth to check real width of all characters it contains. If string
|
||||||
|
/// fits requested length it's not modified at all.
|
||||||
|
/// @param ws wide string to be cut
|
||||||
|
/// @param max_len maximal length of string
|
||||||
|
static void Cut(std::wstring &ws, size_t max_len);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Sets colors of window (interal use only)
|
/// Sets colors of window (interal use only)
|
||||||
/// @param fg foregound color
|
/// @param fg foregound color
|
||||||
|
|||||||
Reference in New Issue
Block a user