# HG changeset patch # User Teemu Piippo # Date 1418616432 -7200 # Node ID 3caf69e7350b6c92e72568b4a3b9c30806b6b4ac # Parent bb209480d0ecc86b87a927a7838da657b1e0c974 - added line-wrapping to the output widget: lines to output are now first compiled into renderer-friendly format. the output view then uses these pre-processed lines to determine what lines to render. Phew! What a monster of a commit. diff -r bb209480d0ec -r 3caf69e7350b sources/interface.cpp --- a/sources/interface.cpp Mon Dec 15 04:10:27 2014 +0200 +++ b/sources/interface.cpp Mon Dec 15 06:07:12 2014 +0200 @@ -33,7 +33,52 @@ #include "network/rconsession.h" #include "network/ipaddress.h" -enum { PAGE_SIZE = 10 }; +enum +{ + RLINE_ON_BLACK = 256, + RLINE_ON_RED, + RLINE_ON_GREEN, + RLINE_ON_YELLOW, + RLINE_ON_BLUE, + RLINE_ON_MAGENTA, + RLINE_ON_CYAN, + RLINE_ON_WHITE, + RLINE_ON_BOLD, + RLINE_OFF_BLACK, + RLINE_OFF_RED, + RLINE_OFF_GREEN, + RLINE_OFF_YELLOW, + RLINE_OFF_BLUE, + RLINE_OFF_MAGENTA, + RLINE_OFF_CYAN, + RLINE_OFF_WHITE, + RLINE_OFF_BOLD, +}; + +class RendererLine +{ +public: + RendererLine() {} + + METHOD data() const -> const Vector& { return m_data; } + METHOD length() const -> int { return m_length; } + METHOD add_char (char ch) -> void; + METHOD finalize() -> void; + METHOD rows (int cols) const -> int; + +private: + METHOD set_color (Color a, bool on) -> void; + + Vector m_data; + int m_length = 0; + bool m_final = false; + Color m_activeColor = DEFAULT; + bool m_boldActive = false; + int m_colorCodeStage = 0; + String m_string; +}; + +static const int g_pageSize = 10; enum InputState { @@ -52,7 +97,7 @@ static bool g_needInputRender = false; static bool g_needOutputRender = false; static struct { char ch; int x; } g_cursorChar; -static Vector g_output = {""}; +static Vector g_output;; static int g_outputScroll = 0; static String g_title; static InputState g_inputState = INPUTSTATE_NORMAL; @@ -209,6 +254,8 @@ ::use_default_colors(); g_input.clear(); g_input << ""; + g_output.clear(); + g_output << RendererLine(); g_title = format (APPNAME " %1 (%2)", full_version_string(), changeset_date_string()); for (int i = 0; i < NUM_COLORS; ++i) @@ -272,67 +319,120 @@ static FUNCTION interface_render_output() -> void { + if (g_output.size() == 1) + return; + + g_outputScroll = clamp (g_outputScroll, 0, g_output.size() - 1); + int height = LINES - 3; + int printOffset = 0; + int end = g_output.size() - 1 - g_outputScroll; + int start = end; + int usedHeight = 0; + int y = 1; + bool tightFit = false; + + // Where to start? + while (start > 0) + { + int rows = g_output[start - 1].rows (COLS); + + if (usedHeight + rows > height) + { + // This line won't fit anymore. + tightFit = true; + break; + } + + start--; + usedHeight += rows; + } - // ensure we're within bounds - if (g_outputScroll + height >= g_output.size()) - g_outputScroll = g_output.size() - height - 1; - else if (g_outputScroll < 0) - g_outputScroll = 0; + // See if there's any more rows to use (end may be too small) + if (not tightFit) + { + while (end < g_output.size()) + { + int rows = g_output[end].rows (COLS); + + if (usedHeight + rows > height) + { + tightFit = true; + break; + } - int start = max (0, g_output.size() - height - 1 - g_outputScroll); - int end = min (g_output.size(), start + height); - int y = 1; - assert (end - start <= height); + end++; + usedHeight += rows; + } + } + + if (start > 0) + printOffset = height - usedHeight; + + g_outputScroll = g_output.size() - 1 - end; + + if (start < 0 or start == end or printOffset >= height) + return; + + assert (start <= end and start - end <= height); + + // Clear the display + for (int i = y; i < y + height; ++i) + mvhline (i, 0, ' ', COLS); + + // Print the lines + y += printOffset; for (int i = start; i < end; ++i) { - mvhline (y, 0, ' ', COLS); - int activeColor = -1; - bool boldActive = false; int x = 0; - for (int j = 0; j < g_output[i].length(); ++j) + for (int byte : g_output[i].data()) { - char ch = g_output[i][j]; - - if (ch == '\x1C' and j + 1 < g_output[i].length()) + if (x == COLS) { - if (activeColor != -1) - attroff (activeColor); - - if (boldActive) - attroff (A_BOLD); - - char colorChar = g_output[i][j + 1]; - - if (colorChar >= 'a' and colorChar <= 'v' and colorChar != 'l') - { - auto colorInfo = g_colorCodes[colorChar - 'a']; - activeColor = interface_color_pair (colorInfo.color, DEFAULT); - boldActive = colorInfo.bold; - attron (activeColor); - - if (boldActive) - attron (A_BOLD); - } - - j++; - continue; + x = 0; + ++y; } - mvaddch (y, x++, g_output[i][j]); - } + if (isprint (byte)) + mvaddch (y, x++, char (byte)); + else switch (byte) + { + case RLINE_ON_BLACK: + case RLINE_ON_GREEN: + case RLINE_ON_YELLOW: + case RLINE_ON_BLUE: + case RLINE_ON_MAGENTA: + case RLINE_ON_CYAN: + case RLINE_ON_WHITE: + attron (interface_color_pair (Color (byte - RLINE_ON_BLACK), DEFAULT)); + break; - if (activeColor != -1) - attroff (activeColor); + case RLINE_OFF_BLACK: + case RLINE_OFF_GREEN: + case RLINE_OFF_YELLOW: + case RLINE_OFF_BLUE: + case RLINE_OFF_MAGENTA: + case RLINE_OFF_CYAN: + case RLINE_OFF_WHITE: + attroff (interface_color_pair (Color (byte - RLINE_OFF_BLACK), DEFAULT)); + break; - if (boldActive) - attroff (A_BOLD); + case RLINE_ON_BOLD: + attron (A_BOLD); + break; + + case RLINE_OFF_BOLD: + attroff (A_BOLD); + break; + } + } ++y; } + g_needOutputRender = false; g_needRefresh = true; } @@ -610,12 +710,12 @@ break; case KEY_PPAGE: - g_outputScroll += PAGE_SIZE; + g_outputScroll += min (g_pageSize, LINES / 2); g_needOutputRender = true; break; case KEY_NPAGE: - g_outputScroll -= PAGE_SIZE; + g_outputScroll -= min (g_pageSize, LINES / 2); g_needOutputRender = true; break; @@ -699,11 +799,12 @@ { if (ch == '\n') { - g_output << ""; + g_output[g_output.size() - 1].finalize(); + g_output << RendererLine(); continue; } - g_output[g_output.size() - 1] += ch; + g_output[g_output.size() - 1].add_char (ch); } g_needOutputRender = true; @@ -731,3 +832,97 @@ session->set_password (password); session->connect (g_address); } + +// ------------------------------------------------------------------------------------------------- +// +METHOD +RendererLine::finalize() -> void +{ + if (m_activeColor != DEFAULT) + this->set_color (m_activeColor, false); + + if (m_boldActive) + m_data << RLINE_OFF_BOLD; + + m_final = true; +} + +// ------------------------------------------------------------------------------------------------- +// +METHOD +RendererLine::add_char (char ch) -> void +{ + if (m_final) + return; // Don't touch finalized lines. + + if (ch == '\x1C' and m_colorCodeStage == 0) + { + m_colorCodeStage = 1; + return; + } + + if (m_colorCodeStage == 1) + { + if (m_activeColor != DEFAULT) + this->set_color (m_activeColor, false); + + if (m_boldActive) + m_data << RLINE_OFF_BOLD; + + if (ch >= 'a' and ch <= 'v' and ch != 'l') + { + auto colorInfo = g_colorCodes[ch - 'a']; + m_activeColor = colorInfo.color; + m_boldActive = colorInfo.bold; + assert (m_activeColor < 8); + this->set_color (m_activeColor, true); + + if (m_boldActive) + m_data << RLINE_ON_BOLD; + } + + m_colorCodeStage = 0; + return; + } + + if (isprint (ch)) + { + m_string += ch; + m_data << int (ch); + ++m_length; + } +} + +// ------------------------------------------------------------------------------------------------- +// +METHOD +RendererLine::set_color (Color a, bool on) -> void +{ + switch (a) + { + case BLACK: m_data << (on ? RLINE_ON_BLACK : RLINE_OFF_BLACK); break; + case RED: m_data << (on ? RLINE_ON_RED : RLINE_OFF_RED); break; + case GREEN: m_data << (on ? RLINE_ON_GREEN : RLINE_OFF_GREEN); break; + case YELLOW: m_data << (on ? RLINE_ON_YELLOW : RLINE_OFF_YELLOW); break; + case BLUE: m_data << (on ? RLINE_ON_BLUE : RLINE_OFF_BLUE); break; + case MAGENTA: m_data << (on ? RLINE_ON_MAGENTA : RLINE_OFF_MAGENTA); break; + case CYAN: m_data << (on ? RLINE_ON_CYAN : RLINE_OFF_CYAN); break; + case WHITE: m_data << (on ? RLINE_ON_WHITE : RLINE_OFF_WHITE); break; + case NUM_COLORS: + case DEFAULT: assert (false); break; + } +} + +// ------------------------------------------------------------------------------------------------- +// How many rows does this line take up? +// +METHOD +RendererLine::rows (int cols) const -> int +{ + int rows = length() / cols; + + if (length() % cols != 0) + rows++; + + return max (rows, 1); +}