- 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.

Mon, 15 Dec 2014 06:07:12 +0200

author
Teemu Piippo <crimsondusk64@gmail.com>
date
Mon, 15 Dec 2014 06:07:12 +0200
changeset 34
3caf69e7350b
parent 33
bb209480d0ec
child 35
413a7de58398

- 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.

sources/interface.cpp file | annotate | diff | comparison | revisions
--- 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<int>& { 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<int> 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<String> g_output = {""};
+static Vector<RendererLine> 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);
+}

mercurial