sources/interface.cpp

branch
protocol5
changeset 195
be953e1621d9
parent 160
cf514fa0f1cc
parent 194
0c7e44e1078a
--- a/sources/interface.cpp	Wed Jan 27 12:41:50 2021 +0200
+++ b/sources/interface.cpp	Wed Jan 27 19:48:41 2021 +0200
@@ -1,5 +1,5 @@
 /*
-	Copyright 2014 - 2016 Teemu Piippo
+	Copyright 2014 - 2021 Teemu Piippo
 	All rights reserved.
 
 	Redistribution and use in source and binary forms, with or without
@@ -51,9 +51,9 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-const String& Interface::getCurrentInput()
+const std::string& Interface::getCurrentInput()
 {
-	return m_inputHistory[m_inputCursor];
+	return this->m_inputHistory[this->m_inputCursor];
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -62,20 +62,20 @@
 //
 void Interface::detachInput()
 {
-	if (m_inputCursor > 0)
+	if (this->m_inputCursor > 0)
 	{
-		m_inputHistory[0] = getCurrentInput();
-		m_inputCursor = 0;
+		this->m_inputHistory[0] = getCurrentInput();
+		this->m_inputCursor = 0;
 	}
 }
 
 // -------------------------------------------------------------------------------------------------
 // A version of current_input() that allows changing the contents of it.
 //
-String& Interface::getEditableInput()
+std::string& Interface::getEditableInput()
 {
 	detachInput();
-	return m_inputHistory[m_inputCursor];
+	return this->m_inputHistory[this->m_inputCursor];
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -83,29 +83,29 @@
 void Interface::moveInputCursor(int delta)
 {
 	// No input history when inputting addresses or passwords
-	if (m_inputState != INPUTSTATE_NORMAL)
+	if (this->m_inputState != INPUTSTATE_NORMAL)
 	{
-		m_inputCursor = 0;
+		this->m_inputCursor = 0;
 		return;
 	}
 
-	int oldcursor = m_inputCursor;
-	m_inputCursor = clamp(m_inputCursor + delta, 0, m_inputHistory.size() - 1);
+	int oldcursor = this->m_inputCursor;
+	this->m_inputCursor = clamp(this->m_inputCursor + delta, 0, static_cast<int>(this->m_inputHistory.size() - 1));
 
-	if (m_inputCursor != oldcursor)
+	if (this->m_inputCursor != oldcursor)
 	{
-		m_cursorPosition = getCurrentInput().length();
-		m_needInputRender = true;
+		this->m_cursorPosition = getCurrentInput().length();
+		this->m_needInputRender = true;
 	}
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-String Interface::getPromptString()
+std::string Interface::getPromptString()
 {
-	String prompt;
+	std::string prompt;
 
-	switch (m_inputState)
+	switch (this->m_inputState)
 	{
 	case INPUTSTATE_NORMAL: prompt = ">"; break;
 	case INPUTSTATE_ADDRESS: prompt = "address:"; break;
@@ -122,25 +122,25 @@
 {
 	// Clear the input row(unless going to or from confirm state)
 	if (newstate != INPUTSTATE_CONFIRM_DISCONNECTION
-		and m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION)
+		and this->m_inputState != INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
-		m_inputCursor = 0;
+		this->m_inputCursor = 0;
 		getEditableInput().clear();
 	}
 
 	switch (newstate)
 	{
 	case INPUTSTATE_ADDRESS:
-		if (m_remoteAddress.host != 0)
-			getEditableInput() = m_remoteAddress.to_string(IPAddress::WITH_PORT);
+		if (this->m_remoteAddress.host != 0)
+			getEditableInput() = net::ip_address_to_string(this->m_remoteAddress);
 		break;
 
 	default:
 		break;
 	}
 
-	m_inputState = newstate;
-	m_needInputRender = true;
+	this->m_inputState = newstate;
+	this->m_needInputRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -164,11 +164,11 @@
 	::noecho();
 	::refresh();
 	::timeout(0);
-	m_inputHistory.clear();
-	m_inputHistory << "";
-	m_outputLines.clear();
-	m_outputLines << ColoredLine();
-	m_session.setInterface(this);
+	this->m_inputHistory.clear();
+	this->m_inputHistory.push_back("");
+	this->m_outputLines.clear();
+	this->m_outputLines.push_back(ColoredLine());
+	this->m_session.setInterface(this);
 	resetTitle();
 
 	if (::has_colors())
@@ -179,8 +179,8 @@
 		int defaultBg = hasDefaultColors ? -1 : COLOR_BLACK;
 
 		// Initialize color pairs
-		for (int i : range<int>(NUM_COLORS))
-		for (int j : range<int>(NUM_COLORS))
+		for (int i = 0; i < NUM_COLORS; i += 1)
+		for (int j = 0; j < NUM_COLORS; j += 1)
 		{
 			int pairnum = 1 + (i * NUM_COLORS + j);
 			int fg =(i == DEFAULT) ? defaultFg : i;
@@ -196,33 +196,38 @@
 
 	renderFull();
 	refresh();
-	m_needRefresh = false;
+	this->m_needRefresh = false;
+}
+
+Interface::~Interface()
+{
+	::endwin();
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::renderTitlebar()
 {
-	if (m_title.length() <= COLS)
+	if (static_cast<signed>(this->m_title.length()) <= COLS)
 	{
 		chtype pair = getColorPair(WHITE, BLUE);
-		int startx =(COLS - m_title.length()) / 2;
-		int endx = startx + m_title.length();
+		int startx =(COLS - this->m_title.length()) / 2;
+		int endx = startx + this->m_title.length();
 		attron(pair);
-		mvprintw(0, startx, "%s", m_title.chars());
+		mvprintw(0, startx, "%s", this->m_title.data());
 		mvhline(0, 0, ' ', startx);
 		mvhline(0, endx, ' ', COLS - endx);
 		attroff(pair);
 	}
 
-	m_needRefresh = true;
+	this->m_needRefresh = true;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::setTitle(const String& title)
+void Interface::setTitle(const std::string& title)
 {
-	m_title = title;
+	this->m_title = title;
 	renderTitlebar();
 }
 
@@ -230,9 +235,9 @@
 //
 void Interface::safeDisconnect(std::function<void(bool)> afterwards)
 {
-	if (m_session.isActive())
+	if (this->m_session.isActive())
 	{
-		m_disconnectCallback = afterwards;
+		this->m_disconnectCallback = afterwards;
 		setInputState(INPUTSTATE_CONFIRM_DISCONNECTION);
 	}
 	else
@@ -301,15 +306,15 @@
 //
 void Interface::renderOutput()
 {
-	if (m_outputLines.size() == 1)
+	if (this->m_outputLines.size() == 1)
 		return;
 
-	m_outputScroll = clamp(m_outputScroll, 0, m_outputLines.size() - 1);
+	this->m_outputScroll = clamp(this->m_outputScroll, 0, static_cast<signed>(this->m_outputLines.size() - 1));
 
 	int height = LINES - 3;
 	int width = COLS - nicklistWidth();
 	int printOffset = 0;
-	int end = m_outputLines.size() - 1 - m_outputScroll;
+	int end = this->m_outputLines.size() - 1 - this->m_outputScroll;
 	int start = end;
 	int usedHeight = 0;
 	int y = 1;
@@ -318,7 +323,7 @@
 	// Where to start?
 	while (start > 0)
 	{
-		int rows = m_outputLines[start - 1].rows(width);
+		int rows = this->m_outputLines[start - 1].rows(width);
 
 		if (usedHeight + rows > height)
 		{
@@ -334,9 +339,9 @@
 	// See if there's any more rows to use(end may be too small)
 	if (not tightFit)
 	{
-		while (end < m_outputLines.size())
+		while (end < static_cast<int>(this->m_outputLines.size()))
 		{
-			int rows = m_outputLines[end].rows(width);
+			int rows = this->m_outputLines[end].rows(width);
 
 			if (usedHeight + rows > height)
 			{
@@ -352,7 +357,7 @@
 	if (start > 0)
 		printOffset = height - usedHeight;
 
-	m_outputScroll = m_outputLines.size() - 1 - end;
+	this->m_outputScroll = this->m_outputLines.size() - 1 - end;
 
 	if (start < 0 or start == end or printOffset >= height)
 		return;
@@ -360,17 +365,17 @@
 	assert(start <= end and start - end <= height);
 
 	// Clear the display
-	for (int i : range(height))
+	for (int i = 0; i < height; i += 1)
 		mvhline(y + i, 0, ' ', width);
 
 	// Print the lines
 	y += printOffset;
 
-	for (int i : range(start, end))
-		y = renderColorline(y, 0, width, m_outputLines[i], true);
+	for (int i = start; i < end; i += 1)
+		y = renderColorline(y, 0, width, this->m_outputLines[i], true);
 
-	m_needOutputRender = false;
-	m_needRefresh = true;
+	this->m_needOutputRender = false;
+	this->m_needRefresh = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -385,18 +390,18 @@
 	if (width > 0)
 		return;
 
-	for (int i : range(height))
+	for (int i = 0; i < height; i += 1)
 	{
 		mvhline(y, x, ' ', width);
 
-		if (i < m_playerNames.size())
-			renderColorline(y, x, width, m_playerNames[i], false);
+		if (i < static_cast<signed>(this->m_playerNames.size()))
+			renderColorline(y, x, width, this->m_playerNames[i], false);
 
 		y++;
 	}
 
-	m_needNicklistRender = false;
-	m_needRefresh = true;
+	this->m_needNicklistRender = false;
+	this->m_needRefresh = true;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -407,57 +412,57 @@
 
 	// If we're asking the user if they want to disconnect, we don't render any input strings,
 	// just the confirmation message.
-	if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
 		attron(promptColor);
 		mvhline(LINES - 2, 0, ' ', COLS);
 		mvprintw(LINES - 2, 0, "Are you sure you want to disconnect? y/n");
 		attroff(promptColor);
-		m_needRefresh = true;
+		this->m_needRefresh = true;
 		return;
 	}
 
-	String prompt = getPromptString();
+	std::string prompt = getPromptString();
 	int displayLength = COLS - prompt.length() - 2;
-	String displayString = getCurrentInput();
+	std::string displayString = getCurrentInput();
 	int y = LINES - 2;
 
 	// If we're inputting a password, replace it with asterisks
-	if (m_inputState == INPUTSTATE_PASSWORD)
+	if (this->m_inputState == INPUTSTATE_PASSWORD)
 	{
 		for (char &ch : displayString)
 			ch = '*';
 	}
 
 	// Ensure the cursor is within bounds
-	m_cursorPosition = clamp(m_cursorPosition, 0, displayString.length());
+	this->m_cursorPosition = clamp(this->m_cursorPosition, 0, static_cast<signed>(displayString.length()));
 
 	// Ensure that the cursor is always in view, adjust panning if this is not the case
-	if (m_cursorPosition > m_inputPanning + displayLength)
-		m_inputPanning = m_cursorPosition - displayLength; // cursor went too far right
-	else if (m_cursorPosition < m_inputPanning)
-		m_inputPanning = m_cursorPosition; // cursor went past the pan value to the left
+	if (this->m_cursorPosition > this->m_inputPanning + displayLength)
+		this->m_inputPanning = this->m_cursorPosition - displayLength; // cursor went too far right
+	else if (this->m_cursorPosition < this->m_inputPanning)
+		this->m_inputPanning = this->m_cursorPosition; // cursor went past the pan value to the left
 
 	// What part of the string to draw?
-	int start = m_inputPanning;
+	int start = this->m_inputPanning;
 	int end = min<int>(displayString.length(), start + displayLength);
-	assert(m_cursorPosition >= start and m_cursorPosition <= end);
+	assert(this->m_cursorPosition >= start and this->m_cursorPosition <= end);
 
 	// Render the input string
 	mvhline(LINES - 2, 0, ' ', COLS);
-	mvprintw(y, prompt.length() + 1, "%s", displayString.mid(start, end).chars());
+	mvprintw(y, prompt.length() + 1, "%s", mid(displayString, start, end).data());
 
 	// Render the prompt
 	attron(promptColor);
-	mvprintw(y, 0, "%s", prompt.chars());
+	mvprintw(y, 0, "%s", prompt.data());
 	attroff(promptColor);
 
 	// Store in memory where the cursor is now(so that we can re-draw it to position the terminal
 	// cursor).
-	m_cursorCharacter.ch = m_cursorPosition != 0 ? displayString[m_cursorPosition - 1] : '\0';
-	m_cursorCharacter.x = prompt.length() + (m_cursorPosition - m_inputPanning);
-	m_needRefresh = true;
-	m_needInputRender = false;
+	this->m_cursorCharacter.ch = this->m_cursorPosition != 0 ? displayString[this->m_cursorPosition - 1] : '\0';
+	this->m_cursorCharacter.x = prompt.length() + (this->m_cursorPosition - this->m_inputPanning);
+	this->m_needRefresh = true;
+	this->m_needInputRender = false;
 }
 
 // -------------------------------------------------------------------------------------------------
@@ -468,19 +473,19 @@
 	int y = LINES - 1;
 	attron(color);
 	mvhline(y, 0, ' ', COLS);
-	mvprintw(y, 0, "%s", m_statusBarText.chars());
+	mvprintw(y, 0, "%s", this->m_statusBarText.data());
 	attroff(color);
-	m_needRefresh = true;
-	m_needStatusBarRender = false;
+	this->m_needRefresh = true;
+	this->m_needStatusBarRender = false;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::updateStatusBar()
 {
-	String text;
+	std::string text;
 
-	switch (m_session.getState())
+	switch (this->m_session.getState())
 	{
 	case RCON_DISCONNECTED:
 		text = "Disconnected.";
@@ -488,41 +493,41 @@
 
 	case RCON_CONNECTING:
 	case RCON_AUTHENTICATING:
-		text = "Connecting to " + m_session.address().to_string(IPAddress::WITH_PORT) + "...";
+		text = "Connecting to " + net::ip_address_to_string(this->m_session.address()) + "...";
 		break;
 
 	case RCON_CONNECTED:
 		{
-			String adminText;
+			std::string adminText;
 
-			if (m_session.getAdminCount() == 0)
+			if (this->m_session.getAdminCount() == 0)
 			{
 				adminText = "No other admins";
 			}
 			else
 			{
-				adminText.sprintf("%d other admin%s", m_session.getAdminCount(),
-					m_session.getAdminCount() != 1 ? "s" : "");
+				adminText = zfc::sprintf("%d other admin%s", this->m_session.getAdminCount(),
+					this->m_session.getAdminCount() != 1 ? "s" : "");
 			}
 
-			text.sprintf("%s | %s | %s",
-				m_session.address().to_string(IPAddress::WITH_PORT).chars(),
-				m_session.getLevel().chars(),
-				adminText.chars());
+			text = zfc::sprintf("%s | %s | %s",
+				net::ip_address_to_string(this->m_session.address()).data(),
+				this->m_session.getLevel().data(),
+				adminText.data());
 		}
 		break;
 	}
 
-	if (not text.isEmpty())
+	if (not text.empty())
 		text += " | ";
 
 	text += "Ctrl+N to connect, Ctrl+Q to ";
-	text +=(m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect";
+	text +=(this->m_session.getState() == RCON_DISCONNECTED) ? "quit" : "disconnect";
 
-	if (text != m_statusBarText)
+	if (text != this->m_statusBarText)
 	{
-		m_statusBarText = text;
-		m_needStatusBarRender = true;
+		this->m_statusBarText = text;
+		this->m_needStatusBarRender = true;
 	}
 }
 
@@ -543,13 +548,13 @@
 void Interface::positionCursor()
 {
 	// This is only relevant if the input string is being drawn
-	if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 		return;
 
 	int y = LINES - 2;
 
-	if (m_cursorCharacter.ch != '\0')
-		mvprintw(y, m_cursorCharacter.x, "%c", m_cursorCharacter.ch);
+	if (this->m_cursorCharacter.ch != '\0')
+		mvprintw(y, this->m_cursorCharacter.x, "%c", this->m_cursorCharacter.ch);
 	else
 		mvprintw(y, getPromptString().length(), " ");
 }
@@ -558,8 +563,8 @@
 //
 int Interface::findPreviousWord()
 {
-	const String& input = getCurrentInput();
-	int pos = m_cursorPosition;
+	const std::string& input = getCurrentInput();
+	int pos = this->m_cursorPosition;
 
 	// Move past whitespace
 	while (pos > 0 and isspace(input[pos - 1]))
@@ -576,11 +581,11 @@
 //
 int Interface::findNextWord()
 {
-	const String& input = getCurrentInput();
-	int pos = m_cursorPosition;
+	const std::string& input = getCurrentInput();
+	int pos = this->m_cursorPosition;
 
 	// Move past current whitespace
-	while (pos < input.length() and isspace(input[pos]))
+	while (pos < static_cast<signed>(input.length()) and isspace(input[pos]))
 		pos++;
 
 	// Move past the word
@@ -597,18 +602,38 @@
 	if (a >= b)
 		return;
 
-	if (m_cursorPosition > a and m_cursorPosition <= b)
-		m_cursorPosition = a;
+	if (this->m_cursorPosition > a and this->m_cursorPosition <= b)
+		this->m_cursorPosition = a;
+
+	std::string& input = getEditableInput();
+	this->m_pasteBuffer = mid(input, a, b);
+	input = remove_range(input, a, b - a);
+	this->m_needInputRender = true;
+}
 
-	String& input = getEditableInput();
-	m_pasteBuffer = input.mid(a, b);
-	input.remove(a, b - a);
-	m_needInputRender = true;
+bool Interface::tryResolveAddress(const std::string &address_string, net::ip_address* target)
+{
+	std::stringstream errors;
+	const std::optional<net::ip_address> address_opt = net::ip_resolve(address_string, errors);
+	if (address_opt.has_value())
+	{
+		*target = address_opt.value();
+		if (target->port == 0)
+		{
+			target->port = 10666;
+		}
+		return true;
+	}
+	else
+	{
+		this->printError("%s\n", errors.str().data());
+		return false;
+	}
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::handleInput()
+void Interface::handleInput(bool* shouldquit)
 {
 	int ch = ::getch();
 
@@ -622,12 +647,13 @@
 		return;
 	}
 
-	if (m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
+	if (this->m_inputState == INPUTSTATE_CONFIRM_DISCONNECTION)
 	{
 		if (ch == 'y' or ch == 'Y')
 		{
-			m_session.disconnect();
-			m_disconnectCallback(true);
+			this->m_session.disconnect();
+			this->m_disconnectCallback(true);
+			this->updateStatusBar();
 		}
 		else if (ch == 'n' or ch == 'N')
 			setInputState(INPUTSTATE_NORMAL);
@@ -637,13 +663,15 @@
 
 	if (ch >= 0x20 and ch <= 0x7E)
 	{
-		getEditableInput().insert(m_cursorPosition++, char(ch));
-		m_needInputRender = true;
+		std::string& input = getEditableInput();
+		input.insert(input.begin() + this->m_cursorPosition, char(ch));
+		this->m_cursorPosition += 1;
+		this->m_needInputRender = true;
 	}
 	else switch (ch)
 	{
 	case 'Q' - 'A' + 1: // ^Q
-		switch (m_inputState)
+		switch (this->m_inputState)
 		{
 		case INPUTSTATE_CONFIRM_DISCONNECTION:
 			break;
@@ -657,8 +685,7 @@
 				}
 				else
 				{
-					endwin();
-					throw Exitception();
+					*shouldquit = true;
 				}
 			});
 			break;
@@ -674,19 +701,19 @@
 
 	case KEY_LEFT:
 	case 'B' - 'A' + 1: // readline ^B
-		if (m_cursorPosition > 0)
+		if (this->m_cursorPosition > 0)
 		{
-			m_cursorPosition--;
-			m_needInputRender = true;
+			this->m_cursorPosition--;
+			this->m_needInputRender = true;
 		}
 		break;
 
 	case KEY_RIGHT:
 	case 'F' - 'A' + 1: // readline ^F
-		if (m_cursorPosition < getCurrentInput().length())
+		if (this->m_cursorPosition < static_cast<int>(getCurrentInput().length()))
 		{
-			m_cursorPosition++;
-			m_needInputRender = true;
+			this->m_cursorPosition++;
+			this->m_needInputRender = true;
 		}
 		break;
 
@@ -697,72 +724,75 @@
 
 	case KEY_HOME:
 	case 'A' - 'A' + 1: // readline ^A
-		if (m_cursorPosition != 0)
+		if (this->m_cursorPosition != 0)
 		{
-			m_cursorPosition = 0;
-			m_needInputRender = true;
+			this->m_cursorPosition = 0;
+			this->m_needInputRender = true;
 		}
 		break;
 
 	case KEY_END:
 	case 'E' - 'A' + 1: // readline ^E
-		if (m_cursorPosition != getCurrentInput().length())
+		if (this->m_cursorPosition != static_cast<signed>(getCurrentInput().length()))
 		{
-			m_cursorPosition = getCurrentInput().length();
-			m_needInputRender = true;
+			this->m_cursorPosition = getCurrentInput().length();
+			this->m_needInputRender = true;
 		}
 		break;
 
 	case KEY_BACKSPACE:
 	case '\b':
-		if (m_cursorPosition > 0)
+		if (this->m_cursorPosition > 0)
 		{
-			getEditableInput().removeAt(--m_cursorPosition);
-			m_needInputRender = true;
+			std::string& input = getEditableInput();
+			input.erase(input.begin() + this->m_cursorPosition - 1);
+			this->m_cursorPosition -= 1;
+			this->m_needInputRender = true;
 		}
 		break;
 
 	case KEY_DC:
 	case 'D' - 'A' + 1: // readline ^D
-		if (m_cursorPosition < getCurrentInput().length())
+		if (this->m_cursorPosition < static_cast<signed>(getCurrentInput().length()))
 		{
-			getEditableInput().removeAt(m_cursorPosition);
-			m_needInputRender = true;
+			std::string& input = getEditableInput();
+			input.erase(input.begin() + this->m_cursorPosition);
+			this->m_needInputRender = true;
 		}
 		break;
 
 	case KEY_PPAGE:
-		m_outputScroll += min(PAGE_SIZE, LINES / 2);
-		m_needOutputRender = true;
+		this->m_outputScroll += min(PAGE_SIZE, LINES / 2);
+		this->m_needOutputRender = true;
 		break;
 
 	case KEY_NPAGE:
-		m_outputScroll -= min(PAGE_SIZE, LINES / 2);
-		m_needOutputRender = true;
+		this->m_outputScroll -= min(PAGE_SIZE, LINES / 2);
+		this->m_needOutputRender = true;
 		break;
 
 	case 'U' - 'A' + 1: // readline ^U - delete from start to cursor
-		if (m_cursorPosition > 0)
+		if (this->m_cursorPosition > 0)
 		{
-			yank(0, m_cursorPosition);
-			m_cursorPosition = 0;
+			yank(0, this->m_cursorPosition);
+			this->m_cursorPosition = 0;
 		}
 		break;
 
 	case 'K' - 'A' + 1: // readline ^K - delete from cursor to end
-		yank(m_cursorPosition, getEditableInput().length());
+		yank(this->m_cursorPosition, getEditableInput().length());
 		break;
 
 	case 'W' - 'A' + 1: // readline ^W - delete from previous word bounary to current
-		yank(findPreviousWord(), m_cursorPosition);
+		yank(findPreviousWord(), this->m_cursorPosition);
 		break;
 
 	case 'Y' - 'A' + 1: // readline ^Y - paste previously deleted text
-		if (not m_pasteBuffer.isEmpty())
+		if (not this->m_pasteBuffer.empty())
 		{
-			getEditableInput().insert(m_cursorPosition, m_pasteBuffer);
-			m_cursorPosition += m_pasteBuffer.length();
-			m_needInputRender = true;
+			getEditableInput().insert(this->m_cursorPosition, this->m_pasteBuffer);
+			this->m_cursorPosition += this->m_pasteBuffer.length();
+			this->m_needInputRender = true;
 		}
 		break;
 
@@ -770,12 +800,12 @@
 		{
 			int space = getCurrentInput().find(" ");
 
-			if (m_inputState == INPUTSTATE_NORMAL
-				and m_cursorPosition > 0
-				and(space == -1 or space >= m_cursorPosition))
+			if (this->m_inputState == INPUTSTATE_NORMAL
+				and this->m_cursorPosition > 0
+				and(space == -1 or space >= this->m_cursorPosition))
 			{
-				String start = getCurrentInput().mid(0, m_cursorPosition);
-				m_session.requestTabCompletion(start);
+				std::string start = mid(getCurrentInput(), 0, this->m_cursorPosition);
+				this->m_session.requestTabCompletion(start);
 			}
 		}
 		break;
@@ -783,34 +813,24 @@
 	case '\n':
 	case '\r':
 	case KEY_ENTER:
-		switch (m_inputState)
+		switch (this->m_inputState)
 		{
 		case INPUTSTATE_CONFIRM_DISCONNECTION:
 			break; // handled above
 
 		case INPUTSTATE_ADDRESS:
-			try
-			{
-				m_remoteAddress = IPAddress::from_string(getCurrentInput());
-			}
-			catch (std::exception& e)
+			if (this->tryResolveAddress(this->getCurrentInput(), &this->m_remoteAddress))
 			{
-				print("%s\n", e.what());
-				return;
+				setInputState(INPUTSTATE_PASSWORD);
 			}
-
-			if (m_remoteAddress.port == 0)
-				m_remoteAddress.port = 10666;
-
-			setInputState(INPUTSTATE_PASSWORD);
 			break;
 
 		case INPUTSTATE_PASSWORD:
-			if (m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().isEmpty())
+			if (this->m_inputState == INPUTSTATE_PASSWORD and not getCurrentInput().empty())
 			{
-				m_session.disconnect();
-				m_session.setPassword(getCurrentInput());
-				m_session.connect(m_remoteAddress);
+				this->m_session.disconnect();
+				this->m_session.setPassword(getCurrentInput());
+				this->m_session.connect(this->m_remoteAddress);
 				setInputState(INPUTSTATE_NORMAL);
 			}
 			break;
@@ -818,10 +838,10 @@
 		case INPUTSTATE_NORMAL:
 			if (getCurrentInput()[0] == '/')
 			{
-				handleCommand(getCurrentInput());
+				handleCommand(getCurrentInput(), shouldquit);
 				flushInput();
 			}
-			else if (m_session.sendCommand(getCurrentInput()))
+			else if (this->m_session.sendCommand(getCurrentInput()))
 			{
 				flushInput();
 			}
@@ -830,7 +850,7 @@
 		break;
 
 	case 'N' - 'A' + 1: // ^N
-		if (m_inputState == INPUTSTATE_NORMAL)
+		if (this->m_inputState == INPUTSTATE_NORMAL)
 			safeDisconnect([&](bool){setInputState(INPUTSTATE_ADDRESS);});
 		break;
 
@@ -845,35 +865,35 @@
 			case 'b':
 			case 'B':
 				// readline alt-b - move one word to the left
-				m_cursorPosition = findPreviousWord();
-				m_needInputRender = true;
+				this->m_cursorPosition = findPreviousWord();
+				this->m_needInputRender = true;
 				break;
 
 			case 'f':
 			case 'F':
 				// readline alt-f - move one word to the right
-				m_cursorPosition = findNextWord();
-				m_needInputRender = true;
+				this->m_cursorPosition = findNextWord();
+				this->m_needInputRender = true;
 				break;
 
 			case 'd':
 			case 'D':
 				// readline alt-d - delete from here till next word boundary
-				yank(m_cursorPosition, findNextWord());
+				yank(this->m_cursorPosition, findNextWord());
 				break;
 
 			case KEY_BACKSPACE: // alt+backspace, remove previous word
 			case '\b':
-				yank(findPreviousWord(), m_cursorPosition);
+				yank(findPreviousWord(), this->m_cursorPosition);
 				break;
 			}
 		}
 		else
 		{
 			// No alt-key, handle pure escape
-			if (m_inputState == INPUTSTATE_PASSWORD)
+			if (this->m_inputState == INPUTSTATE_PASSWORD)
 				setInputState(INPUTSTATE_ADDRESS);
-			else if (m_inputState == INPUTSTATE_ADDRESS)
+			else if (this->m_inputState == INPUTSTATE_ADDRESS)
 				setInputState(INPUTSTATE_NORMAL);
 		}
 		break;
@@ -886,16 +906,16 @@
 //
 void Interface::render()
 {
-	if (m_needStatusBarRender) renderStatusBar();
-	if (m_needInputRender) renderInput();
-	if (m_needOutputRender) renderOutput();
-	if (m_needNicklistRender) renderNicklist();
+	if (this->m_needStatusBarRender) renderStatusBar();
+	if (this->m_needInputRender) renderInput();
+	if (this->m_needOutputRender) renderOutput();
+	if (this->m_needNicklistRender) renderNicklist();
 
-	if (m_needRefresh)
+	if (this->m_needRefresh)
 	{
 		positionCursor();
 		refresh();
-		m_needRefresh = false;
+		this->m_needRefresh = false;
 	}
 }
 
@@ -903,8 +923,8 @@
 //
 void Interface::vprint(const char* fmtstr, va_list args)
 {
-	String message;
-	message.vsprintf(fmtstr, args);
+	std::string message;
+	message = vsprintf(fmtstr, args);
 	printToConsole(message);
 }
 
@@ -953,108 +973,98 @@
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::printToConsole(String message)
+void Interface::printToConsole(std::string message)
 {
 	// Zandronum sometimes sends color codes as "\\c" and sometimes as "\x1C".
 	// Let's correct that on our end and hope this won't cause conflicts.
-	message.replace("\\c", "\x1C");
+	replace_all(message, "\\c", "\x1C");
 
 	for (char ch : message)
 	{
 		if (ch == '\n')
 		{
-			m_outputLines.last().finalize();
-			m_outputLines << ColoredLine();
+			zfc::last(this->m_outputLines).finalize();
+			this->m_outputLines.push_back({});
 			continue;
 		}
 
-		if (m_outputLines.last().length() == 0)
+		if (zfc::last(this->m_outputLines).length() == 0)
 		{
 			time_t now;
 			time(&now);
 			char timestamp[32];
 			strftime(timestamp, sizeof timestamp, "[%H:%M:%S] ", localtime(&now));
 
-			for (char ch : String(timestamp))
-				m_outputLines.last().addChar(ch);
+			for (char ch : std::string(timestamp))
+				zfc::last(this->m_outputLines).addChar(ch);
 		}
 
 		// Remove some lines if there's too many of them. 20,000 should be enough, I hope.
-		while (m_outputLines.size() > 20000)
-			m_outputLines.remove_at(0);
+		while (this->m_outputLines.size() > 20000)
+			this->m_outputLines.erase(this->m_outputLines.begin());
 
-		m_outputLines.last().addChar(ch);
+		zfc::last(this->m_outputLines).addChar(ch);
 	}
 
-	m_needOutputRender = true;
+	this->m_needOutputRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::connect(String address, String password)
+void Interface::connect(std::string address_string, std::string password)
 {
-	try
-	{
-		m_remoteAddress = IPAddress::from_string(address);
-	}
-	catch (std::exception& e)
+	if (this->tryResolveAddress(address_string, &this->m_remoteAddress))
 	{
-		print("%s\n", e.what());
-		return;
+		this->m_session.disconnect();
+		this->m_session.setPassword(password);
+		this->m_session.connect(this->m_remoteAddress);
 	}
-
-	if (m_remoteAddress.port == 0)
-		m_remoteAddress.port = 10666;
-
-	m_session.disconnect();
-	m_session.setPassword(password);
-	m_session.connect(m_remoteAddress);
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::setPlayerNames(const StringList& names)
+void Interface::setPlayerNames(const std::vector<std::string>& names)
 {
-	m_playerNames.clear();
+	this->m_playerNames.clear();
 
-	for (const String& name : names)
+	for (const std::string& name : names)
 	{
 		ColoredLine coloredname;
 		coloredname.addString(name);
 		coloredname.finalize();
-		m_playerNames.append(coloredname);
+		this->m_playerNames.push_back(coloredname);
 	}
 
-	m_needNicklistRender = true;
+	this->m_needNicklistRender = true;
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::tabComplete(const String& part, String complete)
+void Interface::tabComplete(const std::string& part, std::string complete)
 {
-	String& input = getEditableInput();
+	std::string& input = getEditableInput();
 
-	if (input.startsWith(part))
+	if (starts_with(input, part))
 	{
 		if (input[part.length()] != ' ')
 			complete += ' ';
 
 		input.replace(0, part.length(), complete);
-		m_cursorPosition = complete.length();
-		m_needInputRender = true;
+		this->m_cursorPosition = complete.length();
+		this->m_needInputRender = true;
 	}
 }
 
 // -------------------------------------------------------------------------------------------------
 //
-void Interface::handleCommand(const String& input)
+void Interface::handleCommand(const std::string& input, bool* shouldquit)
 {
 	if (input[0] != '/')
 		return;
 
-	StringList args = input.right(input.length() - 1).split(" ");
-	String command = args[0].toLowerCase();
-	args.remove_at(0);
+	std::vector<std::string> args = split(right(input, input.length() - 1), " ");
+	std::string command = to_lowercase(args[0]);
+	args.erase(args.begin());
 
 	if (command == "connect")
 	{
@@ -1064,52 +1074,34 @@
 		}
 		else
 		{
-			IPAddress address;
-
-			try
-			{
-				address = IPAddress::from_string(args[0]);
-			}
-			catch (std::exception& e)
-			{
-				printError("%s\n", e.what());
-				return;
-			}
-
-			if (address.port == 0)
-				address.port = 10666;
-
-			m_session.setPassword(args[1]);
-			m_session.disconnect();
-			m_session.connect(m_remoteAddress = address);
+			this->connect(args[0], args[1]);
 		}
 	}
 	else if (command == "disconnect")
 	{
-		m_session.disconnect();
+		this->m_session.disconnect();
 	}
 	else if (command == "quit")
 	{
-		m_session.disconnect();
-		endwin();
-		throw Exitception();
+		this->m_session.disconnect();
+		*shouldquit = true;
 	}
 	else if (command == "watch")
 	{
-		if (not args.is_empty())
+		if (not args.empty())
 			m_session.requestWatch(args);
 		else
 			printError("No CVars to watch.\n");
 	}
 	else
-		printError("Unknown command: %s\n", command.chars());
+		printError("Unknown command: %s\n", command.data());
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::disconnected()
 {
-	print("Disconnected from %s\n", m_session.address().to_string(IPAddress::WITH_PORT).chars());
+	print("Disconnected from %s\n", net::ip_address_to_string(this->m_session.address()).data());
 	resetTitle();
 	renderFull();
 }
@@ -1118,16 +1110,16 @@
 //
 void Interface::resetTitle()
 {
-	m_title.sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string());
+	this->m_title = sprintf("%s %s (%s)", application_name(), full_version_string(), changeset_date_string());
 }
 
 // -------------------------------------------------------------------------------------------------
 //
 void Interface::flushInput()
 {
-	m_inputHistory.insert(0, "");
-	m_inputCursor = 0;
-	m_needInputRender = true;
+	this->m_inputHistory.insert(this->m_inputHistory.begin(), "");
+	this->m_inputCursor = 0;
+	this->m_needInputRender = true;
 }
 
 END_ZFC_NAMESPACE

mercurial