From 9e071b074c2511e46920717d5a041ea39ac8d9f9 Mon Sep 17 00:00:00 2001 From: Andrzej Rybczak Date: Sun, 26 Aug 2012 20:45:54 +0200 Subject: [PATCH] display: rewrite Display::Columns and Display::SongsInColumns --- src/display.cpp | 243 ++++++++++++++++++++++++++--------------------- src/settings.cpp | 20 ++++ src/settings.h | 7 +- src/window.cpp | 26 ++++- src/window.h | 21 ++-- 5 files changed, 190 insertions(+), 127 deletions(-) diff --git a/src/display.cpp b/src/display.cpp index 64b6df17..860eb7ff 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -75,56 +75,66 @@ std::string Display::Columns(size_t list_width) if (Config.columns.empty()) return ""; - std::basic_string result; - size_t where = 0; + std::string result; + int width; - - std::vector::const_iterator next2last; - bool last_fixed = Config.columns.back().fixed; - if (Config.columns.size() > 1) - next2last = Config.columns.end()-2; - - for (std::vector::const_iterator it = Config.columns.begin(); it != Config.columns.end(); ++it) + int remained_width = list_width; + std::vector::const_iterator it, last = Config.columns.end() - 1; + for (it = Config.columns.begin(); it != Config.columns.end(); ++it) { - if (it == Config.columns.end()-1) - width = list_width-where; - else if (last_fixed && it == next2last) - width = list_width-where-1-(++next2last)->width; + // 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->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 tag; - if (it->type.length() >= 1 && it->name.empty()) + // if column doesn't fit into screen, discard it and any other after it. + if (remained_width-width < 0 || width < 0 /* this one may come from (*) */) + break; + + std::basic_string name; + if (it->name.empty()) { for (size_t j = 0; j < it->type.length(); ++j) { - tag += toColumnName(it->type[j]); - tag += '/'; + name += toColumnName(it->type[j]); + name += '/'; } - tag.resize(tag.length()-1); + name.resize(name.length()-1); } 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) { - long i = width-tag.length()-(it != Config.columns.begin()); - if (i > 0) - result.resize(result.length()+i, ' '); - } - - where += width; - result += tag; - - if (result.length() > where) - { - result.resize(where); - result += ' '; + result += std::string(x_off, KEY_SPACE); + result += TO_STRING(name); } 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 *menu) @@ -132,13 +142,6 @@ void Display::SongsInColumns(const MPD::Song &s, void *data, Menu *me if (!s.Localized()) const_cast(&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()) return; @@ -153,95 +156,115 @@ void Display::SongsInColumns(const MPD::Song &s, void *data, Menu *me if (separate_albums) *menu << fmtUnderline; - std::vector::const_iterator next2last, last, it; - size_t where = 0; + int song_pos = menu->isFiltered() ? s.GetPosition() : menu->CurrentlyDrawedPosition(); + 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; - - bool last_fixed = Config.columns.back().fixed; - if (Config.columns.size() > 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()); - + int y = menu->Y(); + int remained_width = menu->GetWidth(); + std::vector::const_iterator it, last = Config.columns.end() - 1; 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()); - *menu << ' '; - if (!discard_colors && (it-1)->color != clDefault) - *menu << clEnd; + // here comes the shitty part. if we applied now playing or selected + // prefix, first column's width needs to be properly modified, so + // next column is not affected by them. if prefixes fit, we just + // 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) - width = menu->GetWidth()-where; - else if (last_fixed && it == next2last) - width = menu->GetWidth()-where-1-(++next2last)->width; - else - width = it->width*(it->fixed ? 1 : menu->GetWidth()/100.0); + // if column doesn't fit into screen, discard it and any other after it. + if (remained_width-width < 0 || width < 0 /* this one may come from (*) */) + break; - MPD::Song::GetFunction get = 0; - - std::string tag; + std::basic_string tag; for (size_t i = 0; i < it->type.length(); ++i) { - get = toGetFunction(it->type[i]); - tag = get ? s.GetTags(get) : ""; + MPD::Song::GetFunction get = toGetFunction(it->type[i]); + tag = TO_WSTRING(get ? s.GetTags(get) : ""); if (!tag.empty()) break; } + if (tag.empty() && it->display_empty_tag) + tag = TO_WSTRING(Config.empty_tag); + Window::Cut(tag, width); + if (!discard_colors && it->color != clDefault) *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) + 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)) - { - int x, y; - menu->GetXY(x, y); - std::basic_string wtag = TO_WSTRING(tag.empty() ? Config.empty_tag : tag).substr(0, width-!!x); - *menu << XY(x+width-Window::Length(wtag)-!!x, y) << wtag; - } + // add missing width's part and restore the value. + *menu << ' '; + remained_width -= width+1; } - else - { - if (it == last) - { - if (width > 0) - { - std::basic_string 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; } - 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) + { + 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; + } + if (is_selected) + menu->GotoXY(menu->GetWidth() - Config.selected_item_suffix_length, y); + if (separate_albums) *menu << fmtUnderlineEnd; } diff --git a/src/settings.cpp b/src/settings.cpp index 4ea19045..34de53f1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -371,6 +371,7 @@ void Configuration::SetDefaults() search_engine_default_search_mode = 0; visualizer_sync_interval = 30; locked_screen_width_part = 0.5; + selected_item_prefix_length = 0; selected_item_suffix_length = 0; now_playing_suffix_length = 0; # ifdef HAVE_LANGINFO_H @@ -655,6 +656,7 @@ void Configuration::Read() { selected_item_prefix.Clear(); String2Buffer(v, selected_item_prefix); + selected_item_prefix_length = Window::Length(TO_WSTRING(selected_item_prefix.Str())); } } else if (name == "selected_item_suffix") @@ -672,6 +674,7 @@ void Configuration::Read() { now_playing_prefix.Clear(); String2Buffer(v, now_playing_prefix); + now_playing_prefix_length = Window::Length(TO_WSTRING(now_playing_prefix.Str())); } } else if (name == "now_playing_suffix") @@ -1121,6 +1124,23 @@ void Configuration::GenerateColumns() 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() char tag[] = "{% }|"; song_in_columns_to_string_format = "{"; diff --git a/src/settings.h b/src/settings.h index 0f9aaf80..874272b6 100644 --- a/src/settings.h +++ b/src/settings.h @@ -35,11 +35,12 @@ enum SortMode { smName, smMTime, smCustomFormat }; struct Column { - Column() : right_alignment(0), display_empty_tag(1) { } + Column() : stretch_limit(-1), right_alignment(0), display_empty_tag(1) { } std::basic_string name; std::string type; - unsigned width; + int width; + int stretch_limit; Color color; bool fixed; bool right_alignment; @@ -219,7 +220,9 @@ struct Configuration double locked_screen_width_part; + size_t selected_item_prefix_length; size_t selected_item_suffix_length; + size_t now_playing_prefix_length; size_t now_playing_suffix_length; BasicScreen *startup_screen; diff --git a/src/window.cpp b/src/window.cpp index 47038599..8c6f6ae4 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -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) { - getyx(itsWindow, y, x); - itsX = x; - itsY = y; + getyx(itsWindow, itsY, itsX); + x = itsX; + y = itsY; } void Window::GotoXY(int x, int y) @@ -666,13 +666,15 @@ void Window::GotoXY(int x, int y) itsY = y; } - int Window::X() const + int Window::X() { + getyx(itsWindow, itsY, itsX); return itsX; } -int Window::Y() const +int Window::Y() { + getyx(itsWindow, itsY, itsX); return itsY; } @@ -956,3 +958,17 @@ size_t Window::Length(const std::wstring &ws) # 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; + } + } +} diff --git a/src/window.h b/src/window.h index eeb5e6ec..e12bd806 100644 --- a/src/window.h +++ b/src/window.h @@ -298,21 +298,15 @@ namespace NCurses /// void GotoXY(int x, int y); - /// @return last X position that was set with GetXY(). Note that it most - /// likely won't report correct position unless called after GetXY() and - /// before any function that can print anything to window. + /// @return x window coordinate /// @see GetXY() /// - int X() const; + int X(); - /// @return last Y position that was set with GetXY(). Since Y position - /// 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. + /// @return y windows coordinate /// @see GetXY() /// - int Y() const; + int Y(); /// 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. @@ -526,6 +520,13 @@ namespace NCurses /// 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: /// Sets colors of window (interal use only) /// @param fg foregound color