From 430929e75a4e6c81d7dd2746570f925bb45e674e Mon Sep 17 00:00:00 2001
From: ShadowNinja <shadowninja@minetest.net>
Date: Sat, 27 Feb 2016 16:04:44 -0500
Subject: [PATCH] Add text selection and copying to console

---
 src/chat.cpp           | 64 ++++++++++++++++----------------
 src/chat.h             | 14 ++++++-
 src/guiChatConsole.cpp | 84 +++++++++++++++++++++++++++++-------------
 3 files changed, 103 insertions(+), 59 deletions(-)

diff --git a/src/chat.cpp b/src/chat.cpp
index d8cf3efd9..809d4e422 100644
--- a/src/chat.cpp
+++ b/src/chat.cpp
@@ -390,6 +390,7 @@ ChatPrompt::ChatPrompt(std::wstring prompt, u32 history_limit):
 	m_cols(0),
 	m_view(0),
 	m_cursor(0),
+	m_cursor_len(0),
 	m_nick_completion_start(0),
 	m_nick_completion_end(0)
 {
@@ -426,11 +427,6 @@ void ChatPrompt::addToHistory(std::wstring line)
 	m_history_index = m_history.size();
 }
 
-std::wstring ChatPrompt::getLine()
-{
-	return m_line;
-}
-
 void ChatPrompt::clear()
 {
 	m_line.clear();
@@ -590,14 +586,12 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
 	s32 length = m_line.size();
 	s32 increment = (dir == CURSOROP_DIR_RIGHT) ? 1 : -1;
 
-	if (scope == CURSOROP_SCOPE_CHARACTER)
-	{
+	switch (scope) {
+	case CURSOROP_SCOPE_CHARACTER:
 		new_cursor += increment;
-	}
-	else if (scope == CURSOROP_SCOPE_WORD)
-	{
-		if (increment > 0)
-		{
+		break;
+	case CURSOROP_SCOPE_WORD:
+		if (dir == CURSOROP_DIR_RIGHT) {
 			// skip one word to the right
 			while (new_cursor < length && isspace(m_line[new_cursor]))
 				new_cursor++;
@@ -605,39 +599,47 @@ void ChatPrompt::cursorOperation(CursorOp op, CursorOpDir dir, CursorOpScope sco
 				new_cursor++;
 			while (new_cursor < length && isspace(m_line[new_cursor]))
 				new_cursor++;
-		}
-		else
-		{
+		} else {
 			// skip one word to the left
 			while (new_cursor >= 1 && isspace(m_line[new_cursor - 1]))
 				new_cursor--;
 			while (new_cursor >= 1 && !isspace(m_line[new_cursor - 1]))
 				new_cursor--;
 		}
-	}
-	else if (scope == CURSOROP_SCOPE_LINE)
-	{
+		break;
+	case CURSOROP_SCOPE_LINE:
 		new_cursor += increment * length;
+		break;
+	case CURSOROP_SCOPE_SELECTION:
+		break;
 	}
 
 	new_cursor = MYMAX(MYMIN(new_cursor, length), 0);
 
-	if (op == CURSOROP_MOVE)
-	{
+	switch (op) {
+	case CURSOROP_MOVE:
 		m_cursor = new_cursor;
-	}
-	else if (op == CURSOROP_DELETE)
-	{
-		if (new_cursor < old_cursor)
-		{
-			m_line.erase(new_cursor, old_cursor - new_cursor);
-			m_cursor = new_cursor;
+		m_cursor_len = 0;
+		break;
+	case CURSOROP_DELETE:
+		if (m_cursor_len > 0) { // Delete selected text first
+			m_line.erase(m_cursor, m_cursor_len);
+		} else {
+			m_cursor = MYMIN(new_cursor, old_cursor);
+			m_line.erase(m_cursor, abs(new_cursor - old_cursor));
 		}
-		else if (new_cursor > old_cursor)
-		{
-			m_line.erase(old_cursor, new_cursor - old_cursor);
-			m_cursor = old_cursor;
+		m_cursor_len = 0;
+		break;
+	case CURSOROP_SELECT:
+		if (scope == CURSOROP_SCOPE_LINE) {
+			m_cursor = 0;
+			m_cursor_len = length;
+		} else {
+			m_cursor = MYMIN(new_cursor, old_cursor);
+			m_cursor_len += abs(new_cursor - old_cursor);
+			m_cursor_len = MYMIN(m_cursor_len, length - m_cursor);
 		}
+		break;
 	}
 
 	clampView();
diff --git a/src/chat.h b/src/chat.h
index 367baaaf2..db4146d35 100644
--- a/src/chat.h
+++ b/src/chat.h
@@ -150,7 +150,11 @@ class ChatPrompt
 	void addToHistory(std::wstring line);
 
 	// Get current line
-	std::wstring getLine();
+	std::wstring getLine() const { return m_line; }
+
+	// Get section of line that is currently selected
+	std::wstring getSelection() const
+		{ return m_line.substr(m_cursor, m_cursor_len); }
 
 	// Clear the current line
 	void clear();
@@ -172,10 +176,13 @@ class ChatPrompt
 	std::wstring getVisiblePortion() const;
 	// Get cursor position (relative to visible portion). -1 if invalid
 	s32 getVisibleCursorPosition() const;
+	// Get length of cursor selection
+	s32 getCursorLength() const { return m_cursor_len; }
 
 	// Cursor operations
 	enum CursorOp {
 		CURSOROP_MOVE,
+		CURSOROP_SELECT,
 		CURSOROP_DELETE
 	};
 
@@ -189,7 +196,8 @@ class ChatPrompt
 	enum CursorOpScope {
 		CURSOROP_SCOPE_CHARACTER,
 		CURSOROP_SCOPE_WORD,
-		CURSOROP_SCOPE_LINE
+		CURSOROP_SCOPE_LINE,
+		CURSOROP_SCOPE_SELECTION
 	};
 
 	// Cursor operation
@@ -227,6 +235,8 @@ class ChatPrompt
 	s32 m_view;
 	// Cursor (index into m_line)
 	s32 m_cursor;
+	// Cursor length (length of selected portion of line)
+	s32 m_cursor_len;
 
 	// Last nick completion start (index into m_line)
 	s32 m_nick_completion_start;
diff --git a/src/guiChatConsole.cpp b/src/guiChatConsole.cpp
index 4a084a8e5..d1351a0f7 100644
--- a/src/guiChatConsole.cpp
+++ b/src/guiChatConsole.cpp
@@ -377,13 +377,15 @@ void GUIChatConsole::drawPrompt()
 		s32 cursor_pos = prompt.getVisibleCursorPosition();
 		if (cursor_pos >= 0)
 		{
+			s32 cursor_len = prompt.getCursorLength();
 			video::IVideoDriver* driver = Environment->getVideoDriver();
 			s32 x = (1 + cursor_pos) * m_fontsize.X;
 			core::rect<s32> destrect(
 				x,
-				y + (1.0-m_cursor_height) * m_fontsize.Y,
-				x + m_fontsize.X,
-				y + m_fontsize.Y);
+				y + m_fontsize.Y * (1.0 - m_cursor_height),
+				x + m_fontsize.X * MYMAX(cursor_len, 1),
+				y + m_fontsize.Y * (cursor_len ? m_cursor_height+1 : 1)
+			);
 			video::SColor cursor_color(255,255,255,255);
 			driver->draw2DRectangle(
 				cursor_color,
@@ -454,32 +456,20 @@ bool GUIChatConsole::OnEvent(const SEvent& event)
 			prompt.historyNext();
 			return true;
 		}
-		else if(event.KeyInput.Key == KEY_LEFT)
+		else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT)
 		{
-			// Left or Ctrl-Left pressed
-			// move character / word to the left
-			ChatPrompt::CursorOpScope scope =
-				event.KeyInput.Control ?
+			// Left/right pressed
+			// Move/select character/word to the left depending on control and shift keys
+			ChatPrompt::CursorOp op = event.KeyInput.Shift ?
+				ChatPrompt::CURSOROP_SELECT :
+				ChatPrompt::CURSOROP_MOVE;
+			ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ?
+				ChatPrompt::CURSOROP_DIR_LEFT :
+				ChatPrompt::CURSOROP_DIR_RIGHT;
+			ChatPrompt::CursorOpScope scope = event.KeyInput.Control ?
 				ChatPrompt::CURSOROP_SCOPE_WORD :
 				ChatPrompt::CURSOROP_SCOPE_CHARACTER;
-			prompt.cursorOperation(
-				ChatPrompt::CURSOROP_MOVE,
-				ChatPrompt::CURSOROP_DIR_LEFT,
-				scope);
-			return true;
-		}
-		else if(event.KeyInput.Key == KEY_RIGHT)
-		{
-			// Right or Ctrl-Right pressed
-			// move character / word to the right
-			ChatPrompt::CursorOpScope scope =
-				event.KeyInput.Control ?
-				ChatPrompt::CURSOROP_SCOPE_WORD :
-				ChatPrompt::CURSOROP_SCOPE_CHARACTER;
-			prompt.cursorOperation(
-				ChatPrompt::CURSOROP_MOVE,
-				ChatPrompt::CURSOROP_DIR_RIGHT,
-				scope);
+			prompt.cursorOperation(op, dir, scope);
 			return true;
 		}
 		else if(event.KeyInput.Key == KEY_HOME)
@@ -530,16 +520,58 @@ bool GUIChatConsole::OnEvent(const SEvent& event)
 				scope);
 			return true;
 		}
+		else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control)
+		{
+			// Ctrl-A pressed
+			// Select all text
+			prompt.cursorOperation(
+				ChatPrompt::CURSOROP_SELECT,
+				ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
+				ChatPrompt::CURSOROP_SCOPE_LINE);
+			return true;
+		}
+		else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control)
+		{
+			// Ctrl-C pressed
+			// Copy text to clipboard
+			if (prompt.getCursorLength() <= 0)
+				return true;
+			std::string selected = wide_to_narrow(prompt.getSelection());
+			Environment->getOSOperator()->copyToClipboard(selected.c_str());
+			return true;
+		}
 		else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control)
 		{
 			// Ctrl-V pressed
 			// paste text from clipboard
+			if (prompt.getCursorLength() > 0) {
+				// Delete selected section of text
+				prompt.cursorOperation(
+					ChatPrompt::CURSOROP_DELETE,
+					ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
+					ChatPrompt::CURSOROP_SCOPE_SELECTION);
+			}
 			IOSOperator *os_operator = Environment->getOSOperator();
 			const c8 *text = os_operator->getTextFromClipboard();
 			if (text)
 				prompt.input(narrow_to_wide(text));
 			return true;
 		}
+		else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control)
+		{
+			// Ctrl-X pressed
+			// Cut text to clipboard
+			if (prompt.getCursorLength() <= 0)
+				return true;
+			std::wstring wselected = prompt.getSelection();
+			std::string selected(wselected.begin(), wselected.end());
+			Environment->getOSOperator()->copyToClipboard(selected.c_str());
+			prompt.cursorOperation(
+				ChatPrompt::CURSOROP_DELETE,
+				ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
+				ChatPrompt::CURSOROP_SCOPE_SELECTION);
+			return true;
+		}
 		else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control)
 		{
 			// Ctrl-U pressed
-- 
GitLab