aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/chat.cpp64
-rw-r--r--src/chat.h14
-rw-r--r--src/guiChatConsole.cpp84
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 @@ public:
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 @@ public:
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 @@ public:
enum CursorOpScope {
CURSOROP_SCOPE_CHARACTER,
CURSOROP_SCOPE_WORD,
- CURSOROP_SCOPE_LINE
+ CURSOROP_SCOPE_LINE,
+ CURSOROP_SCOPE_SELECTION
};
// Cursor operation
@@ -227,6 +235,8 @@ private:
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
'>545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614
--Minetest
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

--------------------------------------------------------------------------------

--modstore implementation
modstore = {}

--------------------------------------------------------------------------------
-- @function [parent=#modstore] init
function modstore.init(size, unsortedmods, searchmods)

	modstore.mods_on_unsorted_page = unsortedmods
	modstore.mods_on_search_page = searchmods
	modstore.modsperpage = modstore.mods_on_unsorted_page

	modstore.basetexturedir = core.get_texturepath() .. DIR_DELIM .. "base" ..
						DIR_DELIM .. "pack" .. DIR_DELIM

	modstore.lastmodtitle = ""
	modstore.last_search = ""

	modstore.searchlist = filterlist.create(
		function()
			if modstore.modlist_unsorted ~= nil and
				modstore.modlist_unsorted.data ~= nil then
				return modstore.modlist_unsorted.data
			end
			return {}
		end,
		function(element,modid)
			if element.id == modid then
				return true
			end
			return false
		end, --compare fct
		nil, --uid match fct
		function(element,substring)
			if substring == nil or
				substring == "" then
				return false
			end
			substring = substring:upper()

			if element.title ~= nil and
				element.title:upper():find(substring) ~= nil then
				return true
			end

			if element.details ~= nil and
				element.details.author ~= nil and
				element.details.author:upper():find(substring) ~= nil then
				return true
			end

			if element.details ~= nil and
				element.details.description ~= nil and
				element.details.description:upper():find(substring) ~= nil then
				return true
			end
			return false
		end --filter fct
		)

	modstore.current_list = nil

	modstore.tv_store = tabview_create("modstore",size,{x=0,y=0})

	modstore.tv_store:set_global_event_handler(modstore.handle_events)

	modstore.tv_store:add(
		{
		name = "unsorted",
		caption = fgettext("Unsorted"),
		cbf_formspec       = modstore.unsorted_tab,
		cbf_button_handler = modstore.handle_buttons,
		on_change          =
			function() modstore.modsperpage = modstore.mods_on_unsorted_page end
		}
	)

	modstore.tv_store:add(
		{
		name = "search",
		caption            = fgettext("Search"),
		cbf_formspec       = modstore.getsearchpage,
		cbf_button_handler = modstore.handle_buttons,
		on_change          = modstore.activate_search_tab
		}
	)
end

--------------------------------------------------------------------------------
-- @function [parent=#modstore] nametoindex
function modstore.nametoindex(name)

	for i=1,#modstore.tabnames,1 do
		if modstore.tabnames[i] == name then
			return i
		end
	end

	return 1
end

--------------------------------------------------------------------------------
-- @function [parent=#modstore] showdownloading
function modstore.showdownloading(title)
	local new_dlg = dialog_create("store_downloading",
		function(data)
			return "size[6,2]label[0.25,0.75;" ..
				fgettext("Downloading $1, please wait...", data.title) .. "]"
		end,
		function(this,fields)
			if fields["btn_hidden_close_download"] ~= nil then
				if fields["btn_hidden_close_download"].successfull then
					modstore.lastmodentry = fields["btn_hidden_close_download"]
					modstore.successfulldialog(this)
				else
					this.parent:show()
					this:delete()
					modstore.lastmodtitle = ""
				end

				return true
			end

			return false
		end,
		nil)

	new_dlg:set_parent(modstore.tv_store)
	modstore.tv_store:hide()
	new_dlg.data.title = title
	new_dlg:show()
end

--------------------------------------------------------------------------------
-- @function [parent=#modstore] successfulldialog
function modstore.successfulldialog(downloading_dlg)
	local new_dlg = dialog_create("store_downloading",
		function(data)
			local retval = ""
			retval = retval .. "size[6,2,true]"
			if modstore.lastmodentry ~= nil then
				retval = retval .. "label[0,0.25;" .. fgettext("Successfully installed:") .. "]"
				retval = retval .. "label[3,0.25;" .. modstore.lastmodentry.moddetails.title .. "]"
				retval = retval .. "label[0,0.75;" .. fgettext("Shortname:") .. "]"
				retval = retval .. "label[3,0.75;" .. core.formspec_escape(modstore.lastmodentry.moddetails.basename) .. "]"
			end
			retval = retval .. "button[2.2,1.5;1.5,0.5;btn_confirm_mod_successfull;" .. fgettext("Ok") .. "]"
			return retval
		end,
		function(this,fields)
			if fields["btn_confirm_mod_successfull"] ~= nil then
				this.parent:show()
				downloading_dlg:delete()
				this:delete()

				return true
			end

			return false
		end,
		nil)

	new_dlg:set_parent(modstore.tv_store)
	modstore.tv_store:hide()
	new_dlg:show()
end

--------------------------------------------------------------------------------
-- @function [parent=#modstore] handle_buttons
function modstore.handle_buttons(parent, fields, name, data)

	if fields["btn_modstore_page_up"] then
		if modstore.current_list ~= nil and modstore.current_list.page > 0 then
			modstore.current_list.page = modstore.current_list.page - 1
		end
		return true
	end

	if fields["btn_modstore_page_down"] then
		if modstore.current_list ~= nil and
			modstore.current_list.page <modstore.current_list.pagecount-1 then
			modstore.current_list.page = modstore.current_list.page +1
		end
		return true
	end

	if fields["btn_modstore_search"] or
		(fields["key_enter"] and fields["te_modstore_search"] ~= nil) then
		modstore.last_search = fields["te_modstore_search"]
		filterlist.set_filtercriteria(modstore.searchlist,fields["te_modstore_search"])
		filterlist.refresh(modstore.searchlist)
		modstore.currentlist = {
			page = 0,
			pagecount =  math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage),
			data = filterlist.get_list(modstore.searchlist),
		}
		return true
	end

	if fields["btn_modstore_close"] then
		local maintab = ui.find_by_name("maintab")
		parent:hide()
		maintab:show()
		return true
	end

	for key,value in pairs(fields) do
		local foundat = key:find("btn_install_mod_")
		if ( foundat == 1) then
			local modid = tonumber(key:sub(17))
			for i=1,#modstore.modlist_unsorted.data,1 do
				if modstore.modlist_unsorted.data[i].id == modid then
					local moddetails = modstore.modlist_unsorted.data[i].details
					modstore.lastmodtitle = moddetails.title

					if not core.handle_async(
						function(param)
							local fullurl = core.setting_get("modstore_download_url") ..
											param.moddetails.download_url

							if param.version ~= nil then
								local found = false
								for i=1,#param.moddetails.versions, 1 do
									if param.moddetails.versions[i].date:sub(1,10) == param.version then
										fullurl = core.setting_get("modstore_download_url") ..
														param.moddetails.versions[i].download_url
										found = true
									end
								end

								if not found then
									core.log("error","no download url found for version " .. dump(param.version))
									return {
										moddetails = param.moddetails,
										successfull = false
									}
								end
							end

							if core.download_file(fullurl,param.filename) then
								return {
									texturename = param.texturename,
									moddetails = param.moddetails,
									filename = param.filename,
									successfull = true
								}
							else
								core.log("error","downloading " .. dump(fullurl) .. " failed")
								return {
									moddetails = param.moddetails,
									successfull = false
								}
							end
						end,
						{
							moddetails = moddetails,
							version = fields["dd_version" .. modid],
							filename = os.tempfolder() .. "_MODNAME_" .. moddetails.basename .. ".zip",
							texturename = modstore.modlist_unsorted.data[i].texturename
						},
						function(result)
							--print("Result from async: " .. dump(result.successfull))
							if result.successfull then
								modmgr.installmod(result.filename,result.moddetails.basename)
								os.remove(result.filename)
							else
								gamedata.errormessage = "Failed to download " .. result.moddetails.title
							end

							if gamedata.errormessage == nil then
								core.button_handler({btn_hidden_close_download=result})
							else
								core.button_handler({btn_hidden_close_download={successfull=false}})
							end
						end
					) then
						print("ERROR: async event failed")
						gamedata.errormessage = "Failed to download " .. modstore.lastmodtitle
					end

					modstore.showdownloading(modstore.lastmodtitle)
					return true
				end
			end
			return true
		end
	end

	return false
end

--------------------------------------------------------------------------------
-- @function [parent=#modstore] handle_events
function modstore.handle_events(this,event)
	if (event == "MenuQuit") then
		this:hide()
		return true
	end
end

--------------------------------------------------------------------------------
-- @function [parent=#modstore] update_modlist
function modstore.update_modlist()
	modstore.modlist_unsorted = {}
	modstore.modlist_unsorted.data = {}
	modstore.modlist_unsorted.pagecount = 1
	modstore.modlist_unsorted.page = 0

	core.handle_async(
		function(param)
			return core.get_modstore_list()
		end,
		nil,
		function(result)
			if result ~= nil then
				modstore.modlist_unsorted = {}
				modstore.modlist_unsorted.data = result

				if modstore.modlist_unsorted.data ~= nil then
					modstore.modlist_unsorted.pagecount =
						math.ceil((#modstore.modlist_unsorted.data / modstore.modsperpage))
				else
					modstore.modlist_unsorted.data = {}
					modstore.modlist_unsorted.pagecount = 1
				end
				modstore.modlist_unsorted.page = 0
				modstore.fetchdetails()
				core.event_handler("Refresh")
			end
		end
	)
end

--------------------------------------------------------------------------------