summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/CMakeLists.txt13
-rw-r--r--src/gui/guiChatConsole.cpp642
-rw-r--r--src/gui/guiChatConsole.h133
-rw-r--r--src/gui/guiEditBoxWithScrollbar.cpp1524
-rw-r--r--src/gui/guiEditBoxWithScrollbar.h192
-rw-r--r--src/gui/guiEngine.cpp587
-rw-r--r--src/gui/guiEngine.h304
-rw-r--r--src/gui/guiFormSpecMenu.cpp3864
-rw-r--r--src/gui/guiFormSpecMenu.h565
-rw-r--r--src/gui/guiKeyChangeMenu.cpp436
-rw-r--r--src/gui/guiKeyChangeMenu.h74
-rw-r--r--src/gui/guiMainMenu.h55
-rw-r--r--src/gui/guiPasswordChange.cpp261
-rw-r--r--src/gui/guiPasswordChange.h53
-rw-r--r--src/gui/guiPathSelectMenu.cpp113
-rw-r--r--src/gui/guiPathSelectMenu.h59
-rw-r--r--src/gui/guiTable.cpp1261
-rw-r--r--src/gui/guiTable.h256
-rw-r--r--src/gui/guiVolumeChange.cpp196
-rw-r--r--src/gui/guiVolumeChange.h45
-rw-r--r--src/gui/intlGUIEditBox.cpp1601
-rw-r--r--src/gui/intlGUIEditBox.h198
-rw-r--r--src/gui/mainmenumanager.h166
-rw-r--r--src/gui/modalMenu.h137
-rw-r--r--src/gui/touchscreengui.cpp1063
-rw-r--r--src/gui/touchscreengui.h267
26 files changed, 14065 insertions, 0 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
new file mode 100644
index 000000000..067ba09a8
--- /dev/null
+++ b/src/gui/CMakeLists.txt
@@ -0,0 +1,13 @@
+set(gui_SRCS
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiChatConsole.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiEditBoxWithScrollbar.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiEngine.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiFormSpecMenu.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiKeyChangeMenu.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiPasswordChange.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiPathSelectMenu.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiTable.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/guiVolumeChange.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/intlGUIEditBox.cpp
+ PARENT_SCOPE
+)
diff --git a/src/gui/guiChatConsole.cpp b/src/gui/guiChatConsole.cpp
new file mode 100644
index 000000000..b194834e2
--- /dev/null
+++ b/src/gui/guiChatConsole.cpp
@@ -0,0 +1,642 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#include "guiChatConsole.h"
+#include "chat.h"
+#include "client.h"
+#include "debug.h"
+#include "gettime.h"
+#include "keycode.h"
+#include "settings.h"
+#include "porting.h"
+#include "client/tile.h"
+#include "fontengine.h"
+#include "log.h"
+#include "gettext.h"
+#include <string>
+
+#if USE_FREETYPE
+ #include "irrlicht_changes/CGUITTFont.h"
+#endif
+
+inline u32 clamp_u8(s32 value)
+{
+ return (u32) MYMIN(MYMAX(value, 0), 255);
+}
+
+
+GUIChatConsole::GUIChatConsole(
+ gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent,
+ s32 id,
+ ChatBackend* backend,
+ Client* client,
+ IMenuManager* menumgr
+):
+ IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
+ core::rect<s32>(0,0,100,100)),
+ m_chat_backend(backend),
+ m_client(client),
+ m_menumgr(menumgr),
+ m_animate_time_old(porting::getTimeMs())
+{
+ // load background settings
+ s32 console_alpha = g_settings->getS32("console_alpha");
+ m_background_color.setAlpha(clamp_u8(console_alpha));
+
+ // load the background texture depending on settings
+ ITextureSource *tsrc = client->getTextureSource();
+ if (tsrc->isKnownSourceImage("background_chat.jpg")) {
+ m_background = tsrc->getTexture("background_chat.jpg");
+ m_background_color.setRed(255);
+ m_background_color.setGreen(255);
+ m_background_color.setBlue(255);
+ } else {
+ v3f console_color = g_settings->getV3F("console_color");
+ m_background_color.setRed(clamp_u8(myround(console_color.X)));
+ m_background_color.setGreen(clamp_u8(myround(console_color.Y)));
+ m_background_color.setBlue(clamp_u8(myround(console_color.Z)));
+ }
+
+ m_font = g_fontengine->getFont(FONT_SIZE_UNSPECIFIED, FM_Mono);
+
+ if (!m_font) {
+ errorstream << "GUIChatConsole: Unable to load mono font ";
+ } else {
+ core::dimension2d<u32> dim = m_font->getDimension(L"M");
+ m_fontsize = v2u32(dim.Width, dim.Height);
+ m_font->grab();
+ }
+ m_fontsize.X = MYMAX(m_fontsize.X, 1);
+ m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
+
+ // set default cursor options
+ setCursor(true, true, 2.0, 0.1);
+}
+
+GUIChatConsole::~GUIChatConsole()
+{
+ if (m_font)
+ m_font->drop();
+}
+
+void GUIChatConsole::openConsole(f32 scale)
+{
+ assert(scale > 0.0f && scale <= 1.0f);
+
+ m_open = true;
+ m_desired_height_fraction = scale;
+ m_desired_height = scale * m_screensize.Y;
+ reformatConsole();
+ m_animate_time_old = porting::getTimeMs();
+ IGUIElement::setVisible(true);
+ Environment->setFocus(this);
+ m_menumgr->createdMenu(this);
+}
+
+bool GUIChatConsole::isOpen() const
+{
+ return m_open;
+}
+
+bool GUIChatConsole::isOpenInhibited() const
+{
+ return m_open_inhibited > 0;
+}
+
+void GUIChatConsole::closeConsole()
+{
+ m_open = false;
+ Environment->removeFocus(this);
+ m_menumgr->deletingMenu(this);
+}
+
+void GUIChatConsole::closeConsoleAtOnce()
+{
+ closeConsole();
+ m_height = 0;
+ recalculateConsolePosition();
+}
+
+f32 GUIChatConsole::getDesiredHeight() const
+{
+ return m_desired_height_fraction;
+}
+
+void GUIChatConsole::replaceAndAddToHistory(std::wstring line)
+{
+ ChatPrompt& prompt = m_chat_backend->getPrompt();
+ prompt.addToHistory(prompt.getLine());
+ prompt.replace(line);
+}
+
+
+void GUIChatConsole::setCursor(
+ bool visible, bool blinking, f32 blink_speed, f32 relative_height)
+{
+ if (visible)
+ {
+ if (blinking)
+ {
+ // leave m_cursor_blink unchanged
+ m_cursor_blink_speed = blink_speed;
+ }
+ else
+ {
+ m_cursor_blink = 0x8000; // on
+ m_cursor_blink_speed = 0.0;
+ }
+ }
+ else
+ {
+ m_cursor_blink = 0; // off
+ m_cursor_blink_speed = 0.0;
+ }
+ m_cursor_height = relative_height;
+}
+
+void GUIChatConsole::draw()
+{
+ if(!IsVisible)
+ return;
+
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+
+ // Check screen size
+ v2u32 screensize = driver->getScreenSize();
+ if (screensize != m_screensize)
+ {
+ // screen size has changed
+ // scale current console height to new window size
+ if (m_screensize.Y != 0)
+ m_height = m_height * screensize.Y / m_screensize.Y;
+ m_screensize = screensize;
+ m_desired_height = m_desired_height_fraction * m_screensize.Y;
+ reformatConsole();
+ }
+
+ // Animation
+ u64 now = porting::getTimeMs();
+ animate(now - m_animate_time_old);
+ m_animate_time_old = now;
+
+ // Draw console elements if visible
+ if (m_height > 0)
+ {
+ drawBackground();
+ drawText();
+ drawPrompt();
+ }
+
+ gui::IGUIElement::draw();
+}
+
+void GUIChatConsole::reformatConsole()
+{
+ s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better)
+ s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt
+ if (cols <= 0 || rows <= 0)
+ cols = rows = 0;
+ recalculateConsolePosition();
+ m_chat_backend->reformat(cols, rows);
+}
+
+void GUIChatConsole::recalculateConsolePosition()
+{
+ core::rect<s32> rect(0, 0, m_screensize.X, m_height);
+ DesiredRect = rect;
+ recalculateAbsolutePosition(false);
+}
+
+void GUIChatConsole::animate(u32 msec)
+{
+ // animate the console height
+ s32 goal = m_open ? m_desired_height : 0;
+
+ // Set invisible if close animation finished (reset by openConsole)
+ // This function (animate()) is never called once its visibility becomes false so do not
+ // actually set visible to false before the inhibited period is over
+ if (!m_open && m_height == 0 && m_open_inhibited == 0)
+ IGUIElement::setVisible(false);
+
+ if (m_height != goal)
+ {
+ s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0);
+ if (max_change == 0)
+ max_change = 1;
+
+ if (m_height < goal)
+ {
+ // increase height
+ if (m_height + max_change < goal)
+ m_height += max_change;
+ else
+ m_height = goal;
+ }
+ else
+ {
+ // decrease height
+ if (m_height > goal + max_change)
+ m_height -= max_change;
+ else
+ m_height = goal;
+ }
+
+ recalculateConsolePosition();
+ }
+
+ // blink the cursor
+ if (m_cursor_blink_speed != 0.0)
+ {
+ u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0);
+ if (blink_increase == 0)
+ blink_increase = 1;
+ m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff);
+ }
+
+ // decrease open inhibit counter
+ if (m_open_inhibited > msec)
+ m_open_inhibited -= msec;
+ else
+ m_open_inhibited = 0;
+}
+
+void GUIChatConsole::drawBackground()
+{
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+ if (m_background != NULL)
+ {
+ core::rect<s32> sourcerect(0, -m_height, m_screensize.X, 0);
+ driver->draw2DImage(
+ m_background,
+ v2s32(0, 0),
+ sourcerect,
+ &AbsoluteClippingRect,
+ m_background_color,
+ false);
+ }
+ else
+ {
+ driver->draw2DRectangle(
+ m_background_color,
+ core::rect<s32>(0, 0, m_screensize.X, m_height),
+ &AbsoluteClippingRect);
+ }
+}
+
+void GUIChatConsole::drawText()
+{
+ if (m_font == NULL)
+ return;
+
+ ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
+ for (u32 row = 0; row < buf.getRows(); ++row)
+ {
+ const ChatFormattedLine& line = buf.getFormattedLine(row);
+ if (line.fragments.empty())
+ continue;
+
+ s32 line_height = m_fontsize.Y;
+ s32 y = row * line_height + m_height - m_desired_height;
+ if (y + line_height < 0)
+ continue;
+
+ for (const ChatFormattedFragment &fragment : line.fragments) {
+ s32 x = (fragment.column + 1) * m_fontsize.X;
+ core::rect<s32> destrect(
+ x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
+
+
+ #if USE_FREETYPE
+ // Draw colored text if FreeType is enabled
+ irr::gui::CGUITTFont *tmp = dynamic_cast<irr::gui::CGUITTFont *>(m_font);
+ tmp->draw(
+ fragment.text,
+ destrect,
+ video::SColor(255, 255, 255, 255),
+ false,
+ false,
+ &AbsoluteClippingRect);
+ #else
+ // Otherwise use standard text
+ m_font->draw(
+ fragment.text.c_str(),
+ destrect,
+ video::SColor(255, 255, 255, 255),
+ false,
+ false,
+ &AbsoluteClippingRect);
+ #endif
+ }
+ }
+}
+
+void GUIChatConsole::drawPrompt()
+{
+ if (!m_font)
+ return;
+
+ u32 row = m_chat_backend->getConsoleBuffer().getRows();
+ s32 line_height = m_fontsize.Y;
+ s32 y = row * line_height + m_height - m_desired_height;
+
+ ChatPrompt& prompt = m_chat_backend->getPrompt();
+ std::wstring prompt_text = prompt.getVisiblePortion();
+
+ // FIXME Draw string at once, not character by character
+ // That will only work with the cursor once we have a monospace font
+ for (u32 i = 0; i < prompt_text.size(); ++i)
+ {
+ wchar_t ws[2] = {prompt_text[i], 0};
+ s32 x = (1 + i) * m_fontsize.X;
+ core::rect<s32> destrect(
+ x, y, x + m_fontsize.X, y + m_fontsize.Y);
+ m_font->draw(
+ ws,
+ destrect,
+ video::SColor(255, 255, 255, 255),
+ false,
+ false,
+ &AbsoluteClippingRect);
+ }
+
+ // Draw the cursor during on periods
+ if ((m_cursor_blink & 0x8000) != 0)
+ {
+ 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 + 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,
+ destrect,
+ &AbsoluteClippingRect);
+ }
+ }
+
+}
+
+bool GUIChatConsole::OnEvent(const SEvent& event)
+{
+
+ ChatPrompt &prompt = m_chat_backend->getPrompt();
+
+ if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
+ {
+ // Key input
+ if (KeyPress(event.KeyInput) == getKeySetting("keymap_console")) {
+ closeConsole();
+
+ // inhibit open so the_game doesn't reopen immediately
+ m_open_inhibited = 50;
+ m_close_on_enter = false;
+ return true;
+ }
+
+ if (event.KeyInput.Key == KEY_ESCAPE) {
+ closeConsoleAtOnce();
+ m_close_on_enter = false;
+ // inhibit open so the_game doesn't reopen immediately
+ m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu"
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_PRIOR)
+ {
+ m_chat_backend->scrollPageUp();
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_NEXT)
+ {
+ m_chat_backend->scrollPageDown();
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_RETURN)
+ {
+ prompt.addToHistory(prompt.getLine());
+ std::wstring text = prompt.replace(L"");
+ m_client->typeChatMessage(text);
+ if (m_close_on_enter) {
+ closeConsoleAtOnce();
+ m_close_on_enter = false;
+ }
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_UP)
+ {
+ // Up pressed
+ // Move back in history
+ prompt.historyPrev();
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_DOWN)
+ {
+ // Down pressed
+ // Move forward in history
+ prompt.historyNext();
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT)
+ {
+ // 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(op, dir, scope);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_HOME)
+ {
+ // Home pressed
+ // move to beginning of line
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_MOVE,
+ ChatPrompt::CURSOROP_DIR_LEFT,
+ ChatPrompt::CURSOROP_SCOPE_LINE);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_END)
+ {
+ // End pressed
+ // move to end of line
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_MOVE,
+ ChatPrompt::CURSOROP_DIR_RIGHT,
+ ChatPrompt::CURSOROP_SCOPE_LINE);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_BACK)
+ {
+ // Backspace or Ctrl-Backspace pressed
+ // delete character / word to the left
+ ChatPrompt::CursorOpScope scope =
+ event.KeyInput.Control ?
+ ChatPrompt::CURSOROP_SCOPE_WORD :
+ ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_DELETE,
+ ChatPrompt::CURSOROP_DIR_LEFT,
+ scope);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_DELETE)
+ {
+ // Delete or Ctrl-Delete pressed
+ // delete character / word to the right
+ ChatPrompt::CursorOpScope scope =
+ event.KeyInput.Control ?
+ ChatPrompt::CURSOROP_SCOPE_WORD :
+ ChatPrompt::CURSOROP_SCOPE_CHARACTER;
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_DELETE,
+ ChatPrompt::CURSOROP_DIR_RIGHT,
+ 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::wstring wselected = prompt.getSelection();
+ std::string selected(wselected.begin(), wselected.end());
+ 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)
+ return true;
+ std::basic_string<unsigned char> str((const unsigned char*)text);
+ prompt.input(std::wstring(str.begin(), str.end()));
+ 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
+ // kill line to left end
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_DELETE,
+ ChatPrompt::CURSOROP_DIR_LEFT,
+ ChatPrompt::CURSOROP_SCOPE_LINE);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control)
+ {
+ // Ctrl-K pressed
+ // kill line to right end
+ prompt.cursorOperation(
+ ChatPrompt::CURSOROP_DELETE,
+ ChatPrompt::CURSOROP_DIR_RIGHT,
+ ChatPrompt::CURSOROP_SCOPE_LINE);
+ return true;
+ }
+ else if(event.KeyInput.Key == KEY_TAB)
+ {
+ // Tab or Shift-Tab pressed
+ // Nick completion
+ std::list<std::string> names = m_client->getConnectedPlayerNames();
+ bool backwards = event.KeyInput.Shift;
+ prompt.nickCompletion(names, backwards);
+ return true;
+ } else if (!iswcntrl(event.KeyInput.Char) && !event.KeyInput.Control) {
+ #if defined(__linux__) && (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9)
+ wchar_t wc = L'_';
+ mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );
+ prompt.input(wc);
+ #else
+ prompt.input(event.KeyInput.Char);
+ #endif
+ return true;
+ }
+ }
+ else if(event.EventType == EET_MOUSE_INPUT_EVENT)
+ {
+ if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
+ {
+ s32 rows = myround(-3.0 * event.MouseInput.Wheel);
+ m_chat_backend->scroll(rows);
+ }
+ }
+
+ return Parent ? Parent->OnEvent(event) : false;
+}
+
+void GUIChatConsole::setVisible(bool visible)
+{
+ m_open = visible;
+ IGUIElement::setVisible(visible);
+ if (!visible) {
+ m_height = 0;
+ recalculateConsolePosition();
+ }
+}
+
diff --git a/src/gui/guiChatConsole.h b/src/gui/guiChatConsole.h
new file mode 100644
index 000000000..ef8a87673
--- /dev/null
+++ b/src/gui/guiChatConsole.h
@@ -0,0 +1,133 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "modalMenu.h"
+#include "chat.h"
+#include "config.h"
+
+class Client;
+
+class GUIChatConsole : public gui::IGUIElement
+{
+public:
+ GUIChatConsole(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent,
+ s32 id,
+ ChatBackend* backend,
+ Client* client,
+ IMenuManager* menumgr);
+ virtual ~GUIChatConsole();
+
+ // Open the console (height = desired fraction of screen size)
+ // This doesn't open immediately but initiates an animation.
+ // You should call isOpenInhibited() before this.
+ void openConsole(f32 scale);
+
+ bool isOpen() const;
+
+ // Check if the console should not be opened at the moment
+ // This is to avoid reopening the console immediately after closing
+ bool isOpenInhibited() const;
+ // Close the console, equivalent to openConsole(0).
+ // This doesn't close immediately but initiates an animation.
+ void closeConsole();
+ // Close the console immediately, without animation.
+ void closeConsoleAtOnce();
+ // Set whether to close the console after the user presses enter.
+ void setCloseOnEnter(bool close) { m_close_on_enter = close; }
+
+ // Return the desired height (fraction of screen size)
+ // Zero if the console is closed or getting closed
+ f32 getDesiredHeight() const;
+
+ // Replace actual line when adding the actual to the history (if there is any)
+ void replaceAndAddToHistory(std::wstring line);
+
+ // Change how the cursor looks
+ void setCursor(
+ bool visible,
+ bool blinking = false,
+ f32 blink_speed = 1.0,
+ f32 relative_height = 1.0);
+
+ // Irrlicht draw method
+ virtual void draw();
+
+ bool canTakeFocus(gui::IGUIElement* element) { return false; }
+
+ virtual bool OnEvent(const SEvent& event);
+
+ virtual void setVisible(bool visible);
+
+private:
+ void reformatConsole();
+ void recalculateConsolePosition();
+
+ // These methods are called by draw
+ void animate(u32 msec);
+ void drawBackground();
+ void drawText();
+ void drawPrompt();
+
+private:
+ ChatBackend* m_chat_backend;
+ Client* m_client;
+ IMenuManager* m_menumgr;
+
+ // current screen size
+ v2u32 m_screensize;
+
+ // used to compute how much time passed since last animate()
+ u64 m_animate_time_old;
+
+ // should the console be opened or closed?
+ bool m_open = false;
+ // should it close after you press enter?
+ bool m_close_on_enter = false;
+ // current console height [pixels]
+ s32 m_height = 0;
+ // desired height [pixels]
+ f32 m_desired_height = 0.0f;
+ // desired height [screen height fraction]
+ f32 m_desired_height_fraction = 0.0f;
+ // console open/close animation speed [screen height fraction / second]
+ f32 m_height_speed = 5.0f;
+ // if nonzero, opening the console is inhibited [milliseconds]
+ u32 m_open_inhibited = 0;
+
+ // cursor blink frame (16-bit value)
+ // cursor is off during [0,32767] and on during [32768,65535]
+ u32 m_cursor_blink = 0;
+ // cursor blink speed [on/off toggles / second]
+ f32 m_cursor_blink_speed = 0.0f;
+ // cursor height [line height]
+ f32 m_cursor_height = 0.0f;
+
+ // background texture
+ video::ITexture *m_background = nullptr;
+ // background color (including alpha)
+ video::SColor m_background_color = video::SColor(255, 0, 0, 0);
+
+ // font
+ gui::IGUIFont *m_font = nullptr;
+ v2u32 m_fontsize;
+};
diff --git a/src/gui/guiEditBoxWithScrollbar.cpp b/src/gui/guiEditBoxWithScrollbar.cpp
new file mode 100644
index 000000000..d4d2a0c1c
--- /dev/null
+++ b/src/gui/guiEditBoxWithScrollbar.cpp
@@ -0,0 +1,1524 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt
+// Modified by Mustapha T.
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#include "guiEditBoxWithScrollbar.h"
+
+#include "IGUISkin.h"
+#include "IGUIEnvironment.h"
+#include "IGUIFont.h"
+#include "IVideoDriver.h"
+#include "rect.h"
+#include "porting.h"
+#include "Keycodes.h"
+
+
+/*
+todo:
+optional scrollbars [done]
+ctrl+left/right to select word
+double click/ctrl click: word select + drag to select whole words, triple click to select line
+optional? dragging selected text
+numerical
+*/
+
+
+//! constructor
+GUIEditBoxWithScrollBar::GUIEditBoxWithScrollBar(const wchar_t* text, bool border,
+ IGUIEnvironment* environment, IGUIElement* parent, s32 id,
+ const core::rect<s32>& rectangle, bool writable, bool has_vscrollbar)
+ : IGUIEditBox(environment, parent, id, rectangle), m_mouse_marking(false),
+ m_border(border), m_background(true), m_override_color_enabled(false), m_mark_begin(0), m_mark_end(0),
+ m_override_color(video::SColor(101, 255, 255, 255)), m_override_font(0), m_last_break_font(0),
+ m_operator(0), m_blink_start_time(0), m_cursor_pos(0), m_hscroll_pos(0), m_vscroll_pos(0), m_max(0),
+ m_word_wrap(false), m_multiline(false), m_autoscroll(true), m_passwordbox(false),
+ m_passwordchar(L'*'), m_halign(EGUIA_UPPERLEFT), m_valign(EGUIA_CENTER),
+ m_current_text_rect(0, 0, 1, 1), m_frame_rect(rectangle),
+ m_scrollbar_width(0), m_vscrollbar(NULL), m_writable(writable),
+ m_bg_color_used(false)
+{
+#ifdef _DEBUG
+ setDebugName("GUIEditBoxWithScrollBar");
+#endif
+
+
+ Text = text;
+
+ if (Environment)
+ m_operator = Environment->getOSOperator();
+
+ if (m_operator)
+ m_operator->grab();
+
+ // this element can be tabbed to
+ setTabStop(true);
+ setTabOrder(-1);
+
+ if (has_vscrollbar) {
+ createVScrollBar();
+ }
+
+ calculateFrameRect();
+ breakText();
+
+ calculateScrollPos();
+ setWritable(writable);
+}
+
+
+//! destructor
+GUIEditBoxWithScrollBar::~GUIEditBoxWithScrollBar()
+{
+ if (m_override_font)
+ m_override_font->drop();
+
+ if (m_operator)
+ m_operator->drop();
+
+ m_vscrollbar->remove();
+}
+
+
+//! Sets another skin independent font.
+void GUIEditBoxWithScrollBar::setOverrideFont(IGUIFont* font)
+{
+ if (m_override_font == font)
+ return;
+
+ if (m_override_font)
+ m_override_font->drop();
+
+ m_override_font = font;
+
+ if (m_override_font)
+ m_override_font->grab();
+
+ breakText();
+}
+
+//! Gets the override font (if any)
+IGUIFont * GUIEditBoxWithScrollBar::getOverrideFont() const
+{
+ return m_override_font;
+}
+
+//! Get the font which is used right now for drawing
+IGUIFont* GUIEditBoxWithScrollBar::getActiveFont() const
+{
+ if (m_override_font)
+ return m_override_font;
+ IGUISkin* skin = Environment->getSkin();
+ if (skin)
+ return skin->getFont();
+ return 0;
+}
+
+//! Sets another color for the text.
+void GUIEditBoxWithScrollBar::setOverrideColor(video::SColor color)
+{
+ m_override_color = color;
+ m_override_color_enabled = true;
+}
+
+
+video::SColor GUIEditBoxWithScrollBar::getOverrideColor() const
+{
+ return m_override_color;
+}
+
+
+//! Turns the border on or off
+void GUIEditBoxWithScrollBar::setDrawBorder(bool border)
+{
+ m_border = border;
+}
+
+//! Sets whether to draw the background
+void GUIEditBoxWithScrollBar::setDrawBackground(bool draw)
+{
+ m_background = draw;
+}
+
+//! Sets if the text should use the overide color or the color in the gui skin.
+void GUIEditBoxWithScrollBar::enableOverrideColor(bool enable)
+{
+ m_override_color_enabled = enable;
+}
+
+bool GUIEditBoxWithScrollBar::isOverrideColorEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return m_override_color_enabled;
+}
+
+//! Enables or disables word wrap
+void GUIEditBoxWithScrollBar::setWordWrap(bool enable)
+{
+ m_word_wrap = enable;
+ breakText();
+}
+
+
+void GUIEditBoxWithScrollBar::updateAbsolutePosition()
+{
+ core::rect<s32> old_absolute_rect(AbsoluteRect);
+ IGUIElement::updateAbsolutePosition();
+ if (old_absolute_rect != AbsoluteRect) {
+ calculateFrameRect();
+ breakText();
+ calculateScrollPos();
+ }
+}
+
+//! Checks if word wrap is enabled
+bool GUIEditBoxWithScrollBar::isWordWrapEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return m_word_wrap;
+}
+
+
+//! Enables or disables newlines.
+void GUIEditBoxWithScrollBar::setMultiLine(bool enable)
+{
+ m_multiline = enable;
+}
+
+
+//! Checks if multi line editing is enabled
+bool GUIEditBoxWithScrollBar::isMultiLineEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return m_multiline;
+}
+
+
+void GUIEditBoxWithScrollBar::setPasswordBox(bool password_box, wchar_t password_char)
+{
+ m_passwordbox = password_box;
+ if (m_passwordbox) {
+ m_passwordchar = password_char;
+ setMultiLine(false);
+ setWordWrap(false);
+ m_broken_text.clear();
+ }
+}
+
+
+bool GUIEditBoxWithScrollBar::isPasswordBox() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return m_passwordbox;
+}
+
+
+//! Sets text justification
+void GUIEditBoxWithScrollBar::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
+{
+ m_halign = horizontal;
+ m_valign = vertical;
+}
+
+
+//! called if an event happened.
+bool GUIEditBoxWithScrollBar::OnEvent(const SEvent& event)
+{
+ if (isEnabled()) {
+ switch (event.EventType)
+ {
+ case EET_GUI_EVENT:
+ if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST) {
+ if (event.GUIEvent.Caller == this) {
+ m_mouse_marking = false;
+ setTextMarkers(0, 0);
+ }
+ }
+ break;
+ case EET_KEY_INPUT_EVENT:
+ if (processKey(event))
+ return true;
+ break;
+ case EET_MOUSE_INPUT_EVENT:
+ if (processMouse(event))
+ return true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return IGUIElement::OnEvent(event);
+}
+
+
+bool GUIEditBoxWithScrollBar::processKey(const SEvent& event)
+{
+ if (!m_writable) {
+ return false;
+ }
+
+ if (!event.KeyInput.PressedDown)
+ return false;
+
+ bool text_changed = false;
+ s32 new_mark_begin = m_mark_begin;
+ s32 new_mark_end = m_mark_end;
+
+ // control shortcut handling
+
+ if (event.KeyInput.Control) {
+
+ // german backlash '\' entered with control + '?'
+ if (event.KeyInput.Char == '\\') {
+ inputChar(event.KeyInput.Char);
+ return true;
+ }
+
+ switch (event.KeyInput.Key) {
+ case KEY_KEY_A:
+ // select all
+ new_mark_begin = 0;
+ new_mark_end = Text.size();
+ break;
+ case KEY_KEY_C:
+ // copy to clipboard
+ if (!m_passwordbox && m_operator && m_mark_begin != m_mark_end)
+ {
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ core::stringc s;
+ s = Text.subString(realmbgn, realmend - realmbgn).c_str();
+ m_operator->copyToClipboard(s.c_str());
+ }
+ break;
+ case KEY_KEY_X:
+ // cut to the clipboard
+ if (!m_passwordbox && m_operator && m_mark_begin != m_mark_end) {
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ // copy
+ core::stringc sc;
+ sc = Text.subString(realmbgn, realmend - realmbgn).c_str();
+ m_operator->copyToClipboard(sc.c_str());
+
+ if (isEnabled())
+ {
+ // delete
+ core::stringw s;
+ s = Text.subString(0, realmbgn);
+ s.append(Text.subString(realmend, Text.size() - realmend));
+ Text = s;
+
+ m_cursor_pos = realmbgn;
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ text_changed = true;
+ }
+ }
+ break;
+ case KEY_KEY_V:
+ if (!isEnabled())
+ break;
+
+ // paste from the clipboard
+ if (m_operator) {
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ // add new character
+ const c8* p = m_operator->getTextFromClipboard();
+ if (p) {
+ if (m_mark_begin == m_mark_end) {
+ // insert text
+ core::stringw s = Text.subString(0, m_cursor_pos);
+ s.append(p);
+ s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos));
+
+ if (!m_max || s.size() <= m_max) // thx to Fish FH for fix
+ {
+ Text = s;
+ s = p;
+ m_cursor_pos += s.size();
+ }
+ } else {
+ // replace text
+
+ core::stringw s = Text.subString(0, realmbgn);
+ s.append(p);
+ s.append(Text.subString(realmend, Text.size() - realmend));
+
+ if (!m_max || s.size() <= m_max) // thx to Fish FH for fix
+ {
+ Text = s;
+ s = p;
+ m_cursor_pos = realmbgn + s.size();
+ }
+ }
+ }
+
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ text_changed = true;
+ }
+ break;
+ case KEY_HOME:
+ // move/highlight to start of text
+ if (event.KeyInput.Shift) {
+ new_mark_end = m_cursor_pos;
+ new_mark_begin = 0;
+ m_cursor_pos = 0;
+ } else {
+ m_cursor_pos = 0;
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+ break;
+ case KEY_END:
+ // move/highlight to end of text
+ if (event.KeyInput.Shift) {
+ new_mark_begin = m_cursor_pos;
+ new_mark_end = Text.size();
+ m_cursor_pos = 0;
+ } else {
+ m_cursor_pos = Text.size();
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+ // default keyboard handling
+ else
+ switch (event.KeyInput.Key) {
+ case KEY_END:
+ {
+ s32 p = Text.size();
+ if (m_word_wrap || m_multiline) {
+ p = getLineFromPos(m_cursor_pos);
+ p = m_broken_text_positions[p] + (s32)m_broken_text[p].size();
+ if (p > 0 && (Text[p - 1] == L'\r' || Text[p - 1] == L'\n'))
+ p -= 1;
+ }
+
+ if (event.KeyInput.Shift) {
+ if (m_mark_begin == m_mark_end)
+ new_mark_begin = m_cursor_pos;
+
+ new_mark_end = p;
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+ m_cursor_pos = p;
+ m_blink_start_time = porting::getTimeMs();
+ }
+ break;
+ case KEY_HOME:
+ {
+
+ s32 p = 0;
+ if (m_word_wrap || m_multiline) {
+ p = getLineFromPos(m_cursor_pos);
+ p = m_broken_text_positions[p];
+ }
+
+ if (event.KeyInput.Shift) {
+ if (m_mark_begin == m_mark_end)
+ new_mark_begin = m_cursor_pos;
+ new_mark_end = p;
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+ m_cursor_pos = p;
+ m_blink_start_time = porting::getTimeMs();
+ }
+ break;
+ case KEY_RETURN:
+ if (m_multiline) {
+ inputChar(L'\n');
+ } else {
+ calculateScrollPos();
+ sendGuiEvent(EGET_EDITBOX_ENTER);
+ }
+ return true;
+ case KEY_LEFT:
+
+ if (event.KeyInput.Shift) {
+ if (m_cursor_pos > 0) {
+ if (m_mark_begin == m_mark_end)
+ new_mark_begin = m_cursor_pos;
+
+ new_mark_end = m_cursor_pos - 1;
+ }
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+
+ if (m_cursor_pos > 0)
+ m_cursor_pos--;
+ m_blink_start_time = porting::getTimeMs();
+ break;
+
+ case KEY_RIGHT:
+ if (event.KeyInput.Shift) {
+ if (Text.size() > (u32)m_cursor_pos) {
+ if (m_mark_begin == m_mark_end)
+ new_mark_begin = m_cursor_pos;
+
+ new_mark_end = m_cursor_pos + 1;
+ }
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+
+ if (Text.size() > (u32)m_cursor_pos)
+ m_cursor_pos++;
+ m_blink_start_time = porting::getTimeMs();
+ break;
+ case KEY_UP:
+ if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) {
+ s32 lineNo = getLineFromPos(m_cursor_pos);
+ s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : (m_mark_begin > m_mark_end ? m_mark_begin : m_mark_end);
+ if (lineNo > 0) {
+ s32 cp = m_cursor_pos - m_broken_text_positions[lineNo];
+ if ((s32)m_broken_text[lineNo - 1].size() < cp)
+ m_cursor_pos = m_broken_text_positions[lineNo - 1] + core::max_((u32)1, m_broken_text[lineNo - 1].size()) - 1;
+ else
+ m_cursor_pos = m_broken_text_positions[lineNo - 1] + cp;
+ }
+
+ if (event.KeyInput.Shift) {
+ new_mark_begin = mb;
+ new_mark_end = m_cursor_pos;
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+ } else {
+ return false;
+ }
+ break;
+ case KEY_DOWN:
+ if (m_multiline || (m_word_wrap && m_broken_text.size() > 1)) {
+ s32 lineNo = getLineFromPos(m_cursor_pos);
+ s32 mb = (m_mark_begin == m_mark_end) ? m_cursor_pos : (m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end);
+ if (lineNo < (s32)m_broken_text.size() - 1)
+ {
+ s32 cp = m_cursor_pos - m_broken_text_positions[lineNo];
+ if ((s32)m_broken_text[lineNo + 1].size() < cp)
+ m_cursor_pos = m_broken_text_positions[lineNo + 1] + core::max_((u32)1, m_broken_text[lineNo + 1].size()) - 1;
+ else
+ m_cursor_pos = m_broken_text_positions[lineNo + 1] + cp;
+ }
+
+ if (event.KeyInput.Shift) {
+ new_mark_begin = mb;
+ new_mark_end = m_cursor_pos;
+ } else {
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ }
+
+ } else {
+ return false;
+ }
+ break;
+
+ case KEY_BACK:
+ if (!isEnabled())
+ break;
+
+ if (Text.size()) {
+ core::stringw s;
+
+ if (m_mark_begin != m_mark_end) {
+ // delete marked text
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ s = Text.subString(0, realmbgn);
+ s.append(Text.subString(realmend, Text.size() - realmend));
+ Text = s;
+
+ m_cursor_pos = realmbgn;
+ } else {
+ // delete text behind cursor
+ if (m_cursor_pos > 0)
+ s = Text.subString(0, m_cursor_pos - 1);
+ else
+ s = L"";
+ s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos));
+ Text = s;
+ --m_cursor_pos;
+ }
+
+ if (m_cursor_pos < 0)
+ m_cursor_pos = 0;
+ m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime();
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ text_changed = true;
+ }
+ break;
+ case KEY_DELETE:
+ if (!isEnabled())
+ break;
+
+ if (Text.size() != 0) {
+ core::stringw s;
+
+ if (m_mark_begin != m_mark_end) {
+ // delete marked text
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ s = Text.subString(0, realmbgn);
+ s.append(Text.subString(realmend, Text.size() - realmend));
+ Text = s;
+
+ m_cursor_pos = realmbgn;
+ } else {
+ // delete text before cursor
+ s = Text.subString(0, m_cursor_pos);
+ s.append(Text.subString(m_cursor_pos + 1, Text.size() - m_cursor_pos - 1));
+ Text = s;
+ }
+
+ if (m_cursor_pos > (s32)Text.size())
+ m_cursor_pos = (s32)Text.size();
+
+ m_blink_start_time = porting::getTimeMs(); // os::Timer::getTime();
+ new_mark_begin = 0;
+ new_mark_end = 0;
+ text_changed = true;
+ }
+ break;
+
+ case KEY_ESCAPE:
+ case KEY_TAB:
+ case KEY_SHIFT:
+ case KEY_F1:
+ case KEY_F2:
+ case KEY_F3:
+ case KEY_F4:
+ case KEY_F5:
+ case KEY_F6:
+ case KEY_F7:
+ case KEY_F8:
+ case KEY_F9:
+ case KEY_F10:
+ case KEY_F11:
+ case KEY_F12:
+ case KEY_F13:
+ case KEY_F14:
+ case KEY_F15:
+ case KEY_F16:
+ case KEY_F17:
+ case KEY_F18:
+ case KEY_F19:
+ case KEY_F20:
+ case KEY_F21:
+ case KEY_F22:
+ case KEY_F23:
+ case KEY_F24:
+ // ignore these keys
+ return false;
+
+ default:
+ inputChar(event.KeyInput.Char);
+ return true;
+ }
+
+ // Set new text markers
+ setTextMarkers(new_mark_begin, new_mark_end);
+
+ // break the text if it has changed
+ if (text_changed) {
+ breakText();
+ calculateScrollPos();
+ sendGuiEvent(EGET_EDITBOX_CHANGED);
+ }
+ else
+ {
+ calculateScrollPos();
+ }
+
+ return true;
+}
+
+
+//! draws the element and its children
+void GUIEditBoxWithScrollBar::draw()
+{
+ if (!IsVisible)
+ return;
+
+ const bool focus = Environment->hasFocus(this);
+
+ IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+
+ video::SColor default_bg_color;
+ video::SColor bg_color;
+
+ default_bg_color = m_writable ? skin->getColor(EGDC_WINDOW) : video::SColor(0);
+ bg_color = m_bg_color_used ? m_bg_color : default_bg_color;
+
+ if (!m_border && m_background) {
+ skin->draw2DRectangle(this, bg_color, AbsoluteRect, &AbsoluteClippingRect);
+ }
+
+ // draw the border
+
+ if (m_border) {
+
+ if (m_writable) {
+ skin->draw3DSunkenPane(this, bg_color, false, m_background,
+ AbsoluteRect, &AbsoluteClippingRect);
+ }
+
+ calculateFrameRect();
+ }
+
+ core::rect<s32> local_clip_rect = m_frame_rect;
+ local_clip_rect.clipAgainst(AbsoluteClippingRect);
+
+ // draw the text
+
+ IGUIFont* font = getActiveFont();
+
+ s32 cursor_line = 0;
+ s32 charcursorpos = 0;
+
+ if (font) {
+ if (m_last_break_font != font) {
+ breakText();
+ }
+
+ // calculate cursor pos
+
+ core::stringw *txt_line = &Text;
+ s32 start_pos = 0;
+
+ core::stringw s, s2;
+
+ // get mark position
+ const bool ml = (!m_passwordbox && (m_word_wrap || m_multiline));
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+ const s32 hline_start = ml ? getLineFromPos(realmbgn) : 0;
+ const s32 hline_count = ml ? getLineFromPos(realmend) - hline_start + 1 : 1;
+ const s32 line_count = ml ? m_broken_text.size() : 1;
+
+ // Save the override color information.
+ // Then, alter it if the edit box is disabled.
+ const bool prevOver = m_override_color_enabled;
+ const video::SColor prevColor = m_override_color;
+
+ if (Text.size()) {
+ if (!isEnabled() && !m_override_color_enabled) {
+ m_override_color_enabled = true;
+ m_override_color = skin->getColor(EGDC_GRAY_TEXT);
+ }
+
+ for (s32 i = 0; i < line_count; ++i) {
+ setTextRect(i);
+
+ // clipping test - don't draw anything outside the visible area
+ core::rect<s32> c = local_clip_rect;
+ c.clipAgainst(m_current_text_rect);
+ if (!c.isValid())
+ continue;
+
+ // get current line
+ if (m_passwordbox) {
+ if (m_broken_text.size() != 1) {
+ m_broken_text.clear();
+ m_broken_text.push_back(core::stringw());
+ }
+ if (m_broken_text[0].size() != Text.size()){
+ m_broken_text[0] = Text;
+ for (u32 q = 0; q < Text.size(); ++q)
+ {
+ m_broken_text[0][q] = m_passwordchar;
+ }
+ }
+ txt_line = &m_broken_text[0];
+ start_pos = 0;
+ } else {
+ txt_line = ml ? &m_broken_text[i] : &Text;
+ start_pos = ml ? m_broken_text_positions[i] : 0;
+ }
+
+
+ // draw normal text
+ font->draw(txt_line->c_str(), m_current_text_rect,
+ m_override_color_enabled ? m_override_color : skin->getColor(EGDC_BUTTON_TEXT),
+ false, true, &local_clip_rect);
+
+ // draw mark and marked text
+ if (focus && m_mark_begin != m_mark_end && i >= hline_start && i < hline_start + hline_count) {
+
+ s32 mbegin = 0, mend = 0;
+ s32 lineStartPos = 0, lineEndPos = txt_line->size();
+
+ if (i == hline_start) {
+ // highlight start is on this line
+ s = txt_line->subString(0, realmbgn - start_pos);
+ mbegin = font->getDimension(s.c_str()).Width;
+
+ // deal with kerning
+ mbegin += font->getKerningWidth(
+ &((*txt_line)[realmbgn - start_pos]),
+ realmbgn - start_pos > 0 ? &((*txt_line)[realmbgn - start_pos - 1]) : 0);
+
+ lineStartPos = realmbgn - start_pos;
+ }
+ if (i == hline_start + hline_count - 1) {
+ // highlight end is on this line
+ s2 = txt_line->subString(0, realmend - start_pos);
+ mend = font->getDimension(s2.c_str()).Width;
+ lineEndPos = (s32)s2.size();
+ } else {
+ mend = font->getDimension(txt_line->c_str()).Width;
+ }
+
+
+ m_current_text_rect.UpperLeftCorner.X += mbegin;
+ m_current_text_rect.LowerRightCorner.X = m_current_text_rect.UpperLeftCorner.X + mend - mbegin;
+
+
+ // draw mark
+ skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), m_current_text_rect, &local_clip_rect);
+
+ // draw marked text
+ s = txt_line->subString(lineStartPos, lineEndPos - lineStartPos);
+
+ if (s.size())
+ font->draw(s.c_str(), m_current_text_rect,
+ m_override_color_enabled ? m_override_color : skin->getColor(EGDC_HIGH_LIGHT_TEXT),
+ false, true, &local_clip_rect);
+
+ }
+ }
+
+ // Return the override color information to its previous settings.
+ m_override_color_enabled = prevOver;
+ m_override_color = prevColor;
+ }
+
+ // draw cursor
+ if (IsEnabled && m_writable) {
+ if (m_word_wrap || m_multiline) {
+ cursor_line = getLineFromPos(m_cursor_pos);
+ txt_line = &m_broken_text[cursor_line];
+ start_pos = m_broken_text_positions[cursor_line];
+ }
+ s = txt_line->subString(0, m_cursor_pos - start_pos);
+ charcursorpos = font->getDimension(s.c_str()).Width +
+ font->getKerningWidth(L"_", m_cursor_pos - start_pos > 0 ? &((*txt_line)[m_cursor_pos - start_pos - 1]) : 0);
+
+ if (focus && (porting::getTimeMs() - m_blink_start_time) % 700 < 350) {
+ setTextRect(cursor_line);
+ m_current_text_rect.UpperLeftCorner.X += charcursorpos;
+
+ font->draw(L"_", m_current_text_rect,
+ m_override_color_enabled ? m_override_color : skin->getColor(EGDC_BUTTON_TEXT),
+ false, true, &local_clip_rect);
+ }
+ }
+ }
+
+ // draw children
+ IGUIElement::draw();
+}
+
+
+//! Sets the new caption of this element.
+void GUIEditBoxWithScrollBar::setText(const wchar_t* text)
+{
+ Text = text;
+ if (u32(m_cursor_pos) > Text.size())
+ m_cursor_pos = Text.size();
+ m_hscroll_pos = 0;
+ breakText();
+}
+
+
+//! Enables or disables automatic scrolling with cursor position
+//! \param enable: If set to true, the text will move around with the cursor position
+void GUIEditBoxWithScrollBar::setAutoScroll(bool enable)
+{
+ m_autoscroll = enable;
+}
+
+
+//! Checks to see if automatic scrolling is enabled
+//! \return true if automatic scrolling is enabled, false if not
+bool GUIEditBoxWithScrollBar::isAutoScrollEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return m_autoscroll;
+}
+
+
+//! Gets the area of the text in the edit box
+//! \return Returns the size in pixels of the text
+core::dimension2du GUIEditBoxWithScrollBar::getTextDimension()
+{
+ core::rect<s32> ret;
+
+ setTextRect(0);
+ ret = m_current_text_rect;
+
+ for (u32 i = 1; i < m_broken_text.size(); ++i) {
+ setTextRect(i);
+ ret.addInternalPoint(m_current_text_rect.UpperLeftCorner);
+ ret.addInternalPoint(m_current_text_rect.LowerRightCorner);
+ }
+
+ return core::dimension2du(ret.getSize());
+}
+
+
+//! Sets the maximum amount of characters which may be entered in the box.
+//! \param max: Maximum amount of characters. If 0, the character amount is
+//! infinity.
+void GUIEditBoxWithScrollBar::setMax(u32 max)
+{
+ m_max = max;
+
+ if (Text.size() > m_max && m_max != 0)
+ Text = Text.subString(0, m_max);
+}
+
+
+//! Returns maximum amount of characters, previously set by setMax();
+u32 GUIEditBoxWithScrollBar::getMax() const
+{
+ return m_max;
+}
+
+
+bool GUIEditBoxWithScrollBar::processMouse(const SEvent& event)
+{
+ switch (event.MouseInput.Event)
+ {
+ case irr::EMIE_LMOUSE_LEFT_UP:
+ if (Environment->hasFocus(this)) {
+ m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ if (m_mouse_marking) {
+ setTextMarkers(m_mark_begin, m_cursor_pos);
+ }
+ m_mouse_marking = false;
+ calculateScrollPos();
+ return true;
+ }
+ break;
+ case irr::EMIE_MOUSE_MOVED:
+ {
+ if (m_mouse_marking) {
+ m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ setTextMarkers(m_mark_begin, m_cursor_pos);
+ calculateScrollPos();
+ return true;
+ }
+ }
+ break;
+ case EMIE_LMOUSE_PRESSED_DOWN:
+
+ if (!Environment->hasFocus(this)) {
+ m_blink_start_time = porting::getTimeMs();
+ m_mouse_marking = true;
+ m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ setTextMarkers(m_cursor_pos, m_cursor_pos);
+ calculateScrollPos();
+ return true;
+ } else {
+ if (!AbsoluteClippingRect.isPointInside(
+ core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y))) {
+ return false;
+ } else {
+ // move cursor
+ m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+
+ s32 newMarkBegin = m_mark_begin;
+ if (!m_mouse_marking)
+ newMarkBegin = m_cursor_pos;
+
+ m_mouse_marking = true;
+ setTextMarkers(newMarkBegin, m_cursor_pos);
+ calculateScrollPos();
+ return true;
+ }
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+s32 GUIEditBoxWithScrollBar::getCursorPos(s32 x, s32 y)
+{
+ IGUIFont* font = getActiveFont();
+
+ const u32 line_count = (m_word_wrap || m_multiline) ? m_broken_text.size() : 1;
+
+ core::stringw *txt_line = 0;
+ s32 start_pos = 0;
+ x += 3;
+
+ for (u32 i = 0; i < line_count; ++i) {
+ setTextRect(i);
+ if (i == 0 && y < m_current_text_rect.UpperLeftCorner.Y)
+ y = m_current_text_rect.UpperLeftCorner.Y;
+ if (i == line_count - 1 && y > m_current_text_rect.LowerRightCorner.Y)
+ y = m_current_text_rect.LowerRightCorner.Y;
+
+ // is it inside this region?
+ if (y >= m_current_text_rect.UpperLeftCorner.Y && y <= m_current_text_rect.LowerRightCorner.Y) {
+ // we've found the clicked line
+ txt_line = (m_word_wrap || m_multiline) ? &m_broken_text[i] : &Text;
+ start_pos = (m_word_wrap || m_multiline) ? m_broken_text_positions[i] : 0;
+ break;
+ }
+ }
+
+ if (x < m_current_text_rect.UpperLeftCorner.X)
+ x = m_current_text_rect.UpperLeftCorner.X;
+
+ if (!txt_line)
+ return 0;
+
+ s32 idx = font->getCharacterFromPos(txt_line->c_str(), x - m_current_text_rect.UpperLeftCorner.X);
+
+ // click was on or left of the line
+ if (idx != -1)
+ return idx + start_pos;
+
+ // click was off the right edge of the line, go to end.
+ return txt_line->size() + start_pos;
+}
+
+
+//! Breaks the single text line.
+void GUIEditBoxWithScrollBar::breakText()
+{
+ if ((!m_word_wrap && !m_multiline))
+ return;
+
+ m_broken_text.clear(); // need to reallocate :/
+ m_broken_text_positions.clear();
+
+ IGUIFont* font = getActiveFont();
+ if (!font)
+ return;
+
+ m_last_break_font = font;
+
+ core::stringw line;
+ core::stringw word;
+ core::stringw whitespace;
+ s32 last_line_start = 0;
+ s32 size = Text.size();
+ s32 length = 0;
+ s32 el_width = RelativeRect.getWidth() - 6;
+ wchar_t c;
+
+ for (s32 i = 0; i < size; ++i) {
+ c = Text[i];
+ bool line_break = false;
+
+ if (c == L'\r') { // Mac or Windows breaks
+
+ line_break = true;
+ c = 0;
+ if (Text[i + 1] == L'\n') { // Windows breaks
+ // TODO: I (Michael) think that we shouldn't change the text given by the user for whatever reason.
+ // Instead rework the cursor positioning to be able to handle this (but not in stable release
+ // branch as users might already expect this behavior).
+ Text.erase(i + 1);
+ --size;
+ if (m_cursor_pos > i)
+ --m_cursor_pos;
+ }
+ } else if (c == L'\n') { // Unix breaks
+ line_break = true;
+ c = 0;
+ }
+
+ // don't break if we're not a multi-line edit box
+ if (!m_multiline)
+ line_break = false;
+
+ if (c == L' ' || c == 0 || i == (size - 1)) {
+ // here comes the next whitespace, look if
+ // we can break the last word to the next line
+ // We also break whitespace, otherwise cursor would vanish beside the right border.
+ s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
+ s32 worldlgth = font->getDimension(word.c_str()).Width;
+
+ if (m_word_wrap && length + worldlgth + whitelgth > el_width && line.size() > 0) {
+ // break to next line
+ length = worldlgth;
+ m_broken_text.push_back(line);
+ m_broken_text_positions.push_back(last_line_start);
+ last_line_start = i - (s32)word.size();
+ line = word;
+ } else {
+ // add word to line
+ line += whitespace;
+ line += word;
+ length += whitelgth + worldlgth;
+ }
+
+ word = L"";
+ whitespace = L"";
+
+
+ if (c)
+ whitespace += c;
+
+ // compute line break
+ if (line_break) {
+ line += whitespace;
+ line += word;
+ m_broken_text.push_back(line);
+ m_broken_text_positions.push_back(last_line_start);
+ last_line_start = i + 1;
+ line = L"";
+ word = L"";
+ whitespace = L"";
+ length = 0;
+ }
+ } else {
+ // yippee this is a word..
+ word += c;
+ }
+ }
+
+ line += whitespace;
+ line += word;
+ m_broken_text.push_back(line);
+ m_broken_text_positions.push_back(last_line_start);
+}
+
+// TODO: that function does interpret VAlign according to line-index (indexed line is placed on top-center-bottom)
+// but HAlign according to line-width (pixels) and not by row.
+// Intuitively I suppose HAlign handling is better as VScrollPos should handle the line-scrolling.
+// But please no one change this without also rewriting (and this time fucking testing!!!) autoscrolling (I noticed this when fixing the old autoscrolling).
+void GUIEditBoxWithScrollBar::setTextRect(s32 line)
+{
+ if (line < 0)
+ return;
+
+ IGUIFont* font = getActiveFont();
+ if (!font)
+ return;
+
+ core::dimension2du d;
+
+ // get text dimension
+ const u32 line_count = (m_word_wrap || m_multiline) ? m_broken_text.size() : 1;
+ if (m_word_wrap || m_multiline) {
+ d = font->getDimension(m_broken_text[line].c_str());
+ } else {
+ d = font->getDimension(Text.c_str());
+ d.Height = AbsoluteRect.getHeight();
+ }
+ d.Height += font->getKerningHeight();
+
+ // justification
+ switch (m_halign) {
+ case EGUIA_CENTER:
+ // align to h centre
+ m_current_text_rect.UpperLeftCorner.X = (m_frame_rect.getWidth() / 2) - (d.Width / 2);
+ m_current_text_rect.LowerRightCorner.X = (m_frame_rect.getWidth() / 2) + (d.Width / 2);
+ break;
+ case EGUIA_LOWERRIGHT:
+ // align to right edge
+ m_current_text_rect.UpperLeftCorner.X = m_frame_rect.getWidth() - d.Width;
+ m_current_text_rect.LowerRightCorner.X = m_frame_rect.getWidth();
+ break;
+ default:
+ // align to left edge
+ m_current_text_rect.UpperLeftCorner.X = 0;
+ m_current_text_rect.LowerRightCorner.X = d.Width;
+
+ }
+
+ switch (m_valign) {
+ case EGUIA_CENTER:
+ // align to v centre
+ m_current_text_rect.UpperLeftCorner.Y =
+ (m_frame_rect.getHeight() / 2) - (line_count*d.Height) / 2 + d.Height*line;
+ break;
+ case EGUIA_LOWERRIGHT:
+ // align to bottom edge
+ m_current_text_rect.UpperLeftCorner.Y =
+ m_frame_rect.getHeight() - line_count*d.Height + d.Height*line;
+ break;
+ default:
+ // align to top edge
+ m_current_text_rect.UpperLeftCorner.Y = d.Height*line;
+ break;
+ }
+
+ m_current_text_rect.UpperLeftCorner.X -= m_hscroll_pos;
+ m_current_text_rect.LowerRightCorner.X -= m_hscroll_pos;
+ m_current_text_rect.UpperLeftCorner.Y -= m_vscroll_pos;
+ m_current_text_rect.LowerRightCorner.Y = m_current_text_rect.UpperLeftCorner.Y + d.Height;
+
+ m_current_text_rect += m_frame_rect.UpperLeftCorner;
+}
+
+
+s32 GUIEditBoxWithScrollBar::getLineFromPos(s32 pos)
+{
+ if (!m_word_wrap && !m_multiline)
+ return 0;
+
+ s32 i = 0;
+ while (i < (s32)m_broken_text_positions.size()) {
+ if (m_broken_text_positions[i] > pos)
+ return i - 1;
+ ++i;
+ }
+ return (s32)m_broken_text_positions.size() - 1;
+}
+
+
+void GUIEditBoxWithScrollBar::inputChar(wchar_t c)
+{
+ if (!isEnabled())
+ return;
+
+ if (c != 0) {
+ if (Text.size() < m_max || m_max == 0) {
+ core::stringw s;
+
+ if (m_mark_begin != m_mark_end) {
+ // replace marked text
+ const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end;
+ const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin;
+
+ s = Text.subString(0, realmbgn);
+ s.append(c);
+ s.append(Text.subString(realmend, Text.size() - realmend));
+ Text = s;
+ m_cursor_pos = realmbgn + 1;
+ } else {
+ // add new character
+ s = Text.subString(0, m_cursor_pos);
+ s.append(c);
+ s.append(Text.subString(m_cursor_pos, Text.size() - m_cursor_pos));
+ Text = s;
+ ++m_cursor_pos;
+ }
+
+ m_blink_start_time = porting::getTimeMs();
+ setTextMarkers(0, 0);
+ }
+ }
+ breakText();
+ calculateScrollPos();
+ sendGuiEvent(EGET_EDITBOX_CHANGED);
+}
+
+// calculate autoscroll
+void GUIEditBoxWithScrollBar::calculateScrollPos()
+{
+ if (!m_autoscroll)
+ return;
+
+ IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+ IGUIFont* font = m_override_font ? m_override_font : skin->getFont();
+ if (!font)
+ return;
+
+ s32 curs_line = getLineFromPos(m_cursor_pos);
+ if (curs_line < 0)
+ return;
+ setTextRect(curs_line);
+ const bool has_broken_text = m_multiline || m_word_wrap;
+
+ // Check horizonal scrolling
+ // NOTE: Calculations different to vertical scrolling because setTextRect interprets VAlign relative to line but HAlign not relative to row
+ {
+ // get cursor position
+ IGUIFont* font = getActiveFont();
+ if (!font)
+ return;
+
+ // get cursor area
+ irr::u32 cursor_width = font->getDimension(L"_").Width;
+ core::stringw *txt_line = has_broken_text ? &m_broken_text[curs_line] : &Text;
+ s32 cpos = has_broken_text ? m_cursor_pos - m_broken_text_positions[curs_line] : m_cursor_pos; // column
+ s32 cstart = font->getDimension(txt_line->subString(0, cpos).c_str()).Width; // pixels from text-start
+ s32 cend = cstart + cursor_width;
+ s32 txt_width = font->getDimension(txt_line->c_str()).Width;
+
+ if (txt_width < m_frame_rect.getWidth()) {
+ // TODO: Needs a clean left and right gap removal depending on HAlign, similar to vertical scrolling tests for top/bottom.
+ // This check just fixes the case where it was most noticable (text smaller than clipping area).
+
+ m_hscroll_pos = 0;
+ setTextRect(curs_line);
+ }
+
+ if (m_current_text_rect.UpperLeftCorner.X + cstart < m_frame_rect.UpperLeftCorner.X) {
+ // cursor to the left of the clipping area
+ m_hscroll_pos -= m_frame_rect.UpperLeftCorner.X - (m_current_text_rect.UpperLeftCorner.X + cstart);
+ setTextRect(curs_line);
+
+ // TODO: should show more characters to the left when we're scrolling left
+ // and the cursor reaches the border.
+ } else if (m_current_text_rect.UpperLeftCorner.X + cend > m_frame_rect.LowerRightCorner.X) {
+ // cursor to the right of the clipping area
+ m_hscroll_pos += (m_current_text_rect.UpperLeftCorner.X + cend) - m_frame_rect.LowerRightCorner.X;
+ setTextRect(curs_line);
+ }
+ }
+
+ // calculate vertical scrolling
+ if (has_broken_text) {
+ irr::u32 line_height = font->getDimension(L"A").Height + font->getKerningHeight();
+ // only up to 1 line fits?
+ if (line_height >= (irr::u32)m_frame_rect.getHeight()) {
+ m_vscroll_pos = 0;
+ setTextRect(curs_line);
+ s32 unscrolledPos = m_current_text_rect.UpperLeftCorner.Y;
+ s32 pivot = m_frame_rect.UpperLeftCorner.Y;
+ switch (m_valign) {
+ case EGUIA_CENTER:
+ pivot += m_frame_rect.getHeight() / 2;
+ unscrolledPos += line_height / 2;
+ break;
+ case EGUIA_LOWERRIGHT:
+ pivot += m_frame_rect.getHeight();
+ unscrolledPos += line_height;
+ break;
+ default:
+ break;
+ }
+ m_vscroll_pos = unscrolledPos - pivot;
+ setTextRect(curs_line);
+ } else {
+ // First 2 checks are necessary when people delete lines
+ setTextRect(0);
+ if (m_current_text_rect.UpperLeftCorner.Y > m_frame_rect.UpperLeftCorner.Y && m_valign != EGUIA_LOWERRIGHT) {
+ // first line is leaving a gap on top
+ m_vscroll_pos = 0;
+ } else if (m_valign != EGUIA_UPPERLEFT) {
+ u32 lastLine = m_broken_text_positions.empty() ? 0 : m_broken_text_positions.size() - 1;
+ setTextRect(lastLine);
+ if (m_current_text_rect.LowerRightCorner.Y < m_frame_rect.LowerRightCorner.Y)
+ {
+ // last line is leaving a gap on bottom
+ m_vscroll_pos -= m_frame_rect.LowerRightCorner.Y - m_current_text_rect.LowerRightCorner.Y;
+ }
+ }
+
+ setTextRect(curs_line);
+ if (m_current_text_rect.UpperLeftCorner.Y < m_frame_rect.UpperLeftCorner.Y) {
+ // text above valid area
+ m_vscroll_pos -= m_frame_rect.UpperLeftCorner.Y - m_current_text_rect.UpperLeftCorner.Y;
+ setTextRect(curs_line);
+ } else if (m_current_text_rect.LowerRightCorner.Y > m_frame_rect.LowerRightCorner.Y){
+ // text below valid area
+ m_vscroll_pos += m_current_text_rect.LowerRightCorner.Y - m_frame_rect.LowerRightCorner.Y;
+ setTextRect(curs_line);
+ }
+ }
+ }
+
+ if (m_vscrollbar) {
+ m_vscrollbar->setPos(m_vscroll_pos);
+ }
+}
+
+void GUIEditBoxWithScrollBar::calculateFrameRect()
+{
+ m_frame_rect = AbsoluteRect;
+
+
+ IGUISkin *skin = 0;
+ if (Environment)
+ skin = Environment->getSkin();
+ if (m_border && skin) {
+ m_frame_rect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X) + 1;
+ m_frame_rect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1;
+ m_frame_rect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X) + 1;
+ m_frame_rect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y) + 1;
+ }
+
+ updateVScrollBar();
+}
+
+//! set text markers
+void GUIEditBoxWithScrollBar::setTextMarkers(s32 begin, s32 end)
+{
+ if (begin != m_mark_begin || end != m_mark_end) {
+ m_mark_begin = begin;
+ m_mark_end = end;
+ sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED);
+ }
+}
+
+//! send some gui event to parent
+void GUIEditBoxWithScrollBar::sendGuiEvent(EGUI_EVENT_TYPE type)
+{
+ if (Parent) {
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = 0;
+ e.GUIEvent.EventType = type;
+
+ Parent->OnEvent(e);
+ }
+}
+
+//! create a vertical scroll bar
+void GUIEditBoxWithScrollBar::createVScrollBar()
+{
+ IGUISkin *skin = 0;
+ if (Environment)
+ skin = Environment->getSkin();
+
+ m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16;
+
+ irr::core::rect<s32> scrollbarrect = m_frame_rect;
+ scrollbarrect.UpperLeftCorner.X += m_frame_rect.getWidth() - m_scrollbar_width;
+ m_vscrollbar = Environment->addScrollBar(false, scrollbarrect, getParent(), getID());
+ m_vscrollbar->setVisible(false);
+ m_vscrollbar->setSmallStep(1);
+ m_vscrollbar->setLargeStep(1);
+}
+
+void GUIEditBoxWithScrollBar::updateVScrollBar()
+{
+ if (!m_vscrollbar) {
+ return;
+ }
+
+ // OnScrollBarChanged(...)
+ if (m_vscrollbar->getPos() != m_vscroll_pos) {
+ s32 deltaScrollY = m_vscrollbar->getPos() - m_vscroll_pos;
+ m_current_text_rect.UpperLeftCorner.Y -= deltaScrollY;
+ m_current_text_rect.LowerRightCorner.Y -= deltaScrollY;
+
+ s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight();
+ if (scrollymax != m_vscrollbar->getMax()) {
+ // manage a newline or a deleted line
+ m_vscrollbar->setMax(scrollymax);
+ calculateScrollPos();
+ } else {
+ // manage a newline or a deleted line
+ m_vscroll_pos = m_vscrollbar->getPos();
+ }
+ }
+
+ // check if a vertical scrollbar is needed ?
+ if (getTextDimension().Height > (u32) m_frame_rect.getHeight()) {
+ m_frame_rect.LowerRightCorner.X -= m_scrollbar_width;
+
+ s32 scrollymax = getTextDimension().Height - m_frame_rect.getHeight();
+ if (scrollymax != m_vscrollbar->getMax()) {
+ m_vscrollbar->setMax(scrollymax);
+ }
+
+ if (!m_vscrollbar->isVisible()) {
+ m_vscrollbar->setVisible(true);
+ }
+ } else {
+ if (m_vscrollbar->isVisible())
+ {
+ m_vscrollbar->setVisible(false);
+ m_vscroll_pos = 0;
+ m_vscrollbar->setPos(0);
+ m_vscrollbar->setMax(1);
+ }
+ }
+
+
+}
+
+//! set true if this editbox is writable
+void GUIEditBoxWithScrollBar::setWritable(bool writable)
+{
+ m_writable = writable;
+}
+
+//! Change the background color
+void GUIEditBoxWithScrollBar::setBackgroundColor(const video::SColor &bg_color)
+{
+ m_bg_color = bg_color;
+ m_bg_color_used = true;
+}
+
+//! Writes attributes of the element.
+void GUIEditBoxWithScrollBar::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options = 0) const
+{
+ // IGUIEditBox::serializeAttributes(out,options);
+
+ out->addBool("Border", m_border);
+ out->addBool("Background", m_background);
+ out->addBool("OverrideColorEnabled", m_override_color_enabled);
+ out->addColor("OverrideColor", m_override_color);
+ // out->addFont("OverrideFont", OverrideFont);
+ out->addInt("MaxChars", m_max);
+ out->addBool("WordWrap", m_word_wrap);
+ out->addBool("MultiLine", m_multiline);
+ out->addBool("AutoScroll", m_autoscroll);
+ out->addBool("PasswordBox", m_passwordbox);
+ core::stringw ch = L" ";
+ ch[0] = m_passwordchar;
+ out->addString("PasswordChar", ch.c_str());
+ out->addEnum("HTextAlign", m_halign, GUIAlignmentNames);
+ out->addEnum("VTextAlign", m_valign, GUIAlignmentNames);
+ out->addBool("Writable", m_writable);
+
+ IGUIEditBox::serializeAttributes(out, options);
+}
+
+
+//! Reads attributes of the element
+void GUIEditBoxWithScrollBar::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options = 0)
+{
+ IGUIEditBox::deserializeAttributes(in, options);
+
+ setDrawBorder(in->getAttributeAsBool("Border"));
+ setDrawBackground(in->getAttributeAsBool("Background"));
+ setOverrideColor(in->getAttributeAsColor("OverrideColor"));
+ enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
+ setMax(in->getAttributeAsInt("MaxChars"));
+ setWordWrap(in->getAttributeAsBool("WordWrap"));
+ setMultiLine(in->getAttributeAsBool("MultiLine"));
+ setAutoScroll(in->getAttributeAsBool("AutoScroll"));
+ core::stringw ch = in->getAttributeAsStringW("PasswordChar");
+
+ if (!ch.size())
+ setPasswordBox(in->getAttributeAsBool("PasswordBox"));
+ else
+ setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]);
+
+ setTextAlignment((EGUI_ALIGNMENT)in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
+ (EGUI_ALIGNMENT)in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));
+
+ // setOverrideFont(in->getAttributeAsFont("OverrideFont"));
+ setWritable(in->getAttributeAsBool("Writable"));
+}
diff --git a/src/gui/guiEditBoxWithScrollbar.h b/src/gui/guiEditBoxWithScrollbar.h
new file mode 100644
index 000000000..cca2f6536
--- /dev/null
+++ b/src/gui/guiEditBoxWithScrollbar.h
@@ -0,0 +1,192 @@
+// Copyright (C) 2002-2012 Nikolaus Gebhardt, Modified by Mustapha Tachouct
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#ifndef GUIEDITBOXWITHSCROLLBAR_HEADER
+#define GUIEDITBOXWITHSCROLLBAR_HEADER
+
+#include "IGUIEditBox.h"
+#include "IOSOperator.h"
+#include "IGUIScrollBar.h"
+#include <vector>
+
+using namespace irr;
+using namespace irr::gui;
+
+class GUIEditBoxWithScrollBar : public IGUIEditBox
+{
+public:
+
+ //! constructor
+ GUIEditBoxWithScrollBar(const wchar_t* text, bool border, IGUIEnvironment* environment,
+ IGUIElement* parent, s32 id, const core::rect<s32>& rectangle,
+ bool writable = true, bool has_vscrollbar = true);
+
+ //! destructor
+ virtual ~GUIEditBoxWithScrollBar();
+
+ //! Sets another skin independent font.
+ virtual void setOverrideFont(IGUIFont* font = 0);
+
+ //! Gets the override font (if any)
+ /** \return The override font (may be 0) */
+ virtual IGUIFont* getOverrideFont() const;
+
+ //! Get the font which is used right now for drawing
+ /** Currently this is the override font when one is set and the
+ font of the active skin otherwise */
+ virtual IGUIFont* getActiveFont() const;
+
+ //! Sets another color for the text.
+ virtual void setOverrideColor(video::SColor color);
+
+ //! Gets the override color
+ virtual video::SColor getOverrideColor() const;
+
+ //! Sets if the text should use the overide color or the
+ //! color in the gui skin.
+ virtual void enableOverrideColor(bool enable);
+
+ //! Checks if an override color is enabled
+ /** \return true if the override color is enabled, false otherwise */
+ virtual bool isOverrideColorEnabled(void) const;
+
+ //! Sets whether to draw the background
+ virtual void setDrawBackground(bool draw);
+
+ //! Turns the border on or off
+ virtual void setDrawBorder(bool border);
+
+ //! Enables or disables word wrap for using the edit box as multiline text editor.
+ virtual void setWordWrap(bool enable);
+
+ //! Checks if word wrap is enabled
+ //! \return true if word wrap is enabled, false otherwise
+ virtual bool isWordWrapEnabled() const;
+
+ //! Enables or disables newlines.
+ /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired,
+ instead a newline character will be inserted. */
+ virtual void setMultiLine(bool enable);
+
+ //! Checks if multi line editing is enabled
+ //! \return true if mult-line is enabled, false otherwise
+ virtual bool isMultiLineEnabled() const;
+
+ //! Enables or disables automatic scrolling with cursor position
+ //! \param enable: If set to true, the text will move around with the cursor position
+ virtual void setAutoScroll(bool enable);
+
+ //! Checks to see if automatic scrolling is enabled
+ //! \return true if automatic scrolling is enabled, false if not
+ virtual bool isAutoScrollEnabled() const;
+
+ //! Gets the size area of the text in the edit box
+ //! \return Returns the size in pixels of the text
+ virtual core::dimension2du getTextDimension();
+
+ //! Sets text justification
+ virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical);
+
+ //! called if an event happened.
+ virtual bool OnEvent(const SEvent& event);
+
+ //! draws the element and its children
+ virtual void draw();
+
+ //! Sets the new caption of this element.
+ virtual void setText(const wchar_t* text);
+
+ //! Sets the maximum amount of characters which may be entered in the box.
+ //! \param max: Maximum amount of characters. If 0, the character amount is
+ //! infinity.
+ virtual void setMax(u32 max);
+
+ //! Returns maximum amount of characters, previously set by setMax();
+ virtual u32 getMax() const;
+
+ //! Sets whether the edit box is a password box. Setting this to true will
+ /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x
+ \param passwordBox: true to enable password, false to disable
+ \param passwordChar: the character that is displayed instead of letters */
+ virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*');
+
+ //! Returns true if the edit box is currently a password box.
+ virtual bool isPasswordBox() const;
+
+ //! Updates the absolute position, splits text if required
+ virtual void updateAbsolutePosition();
+
+ virtual void setWritable(bool writable);
+
+ //! Change the background color
+ virtual void setBackgroundColor(const video::SColor &bg_color);
+
+ //! Writes attributes of the element.
+ virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const;
+
+ //! Reads attributes of the element
+ virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options);
+
+protected:
+ //! Breaks the single text line.
+ void breakText();
+ //! sets the area of the given line
+ void setTextRect(s32 line);
+ //! returns the line number that the cursor is on
+ s32 getLineFromPos(s32 pos);
+ //! adds a letter to the edit box
+ void inputChar(wchar_t c);
+ //! calculates the current scroll position
+ void calculateScrollPos();
+ //! calculated the FrameRect
+ void calculateFrameRect();
+ //! send some gui event to parent
+ void sendGuiEvent(EGUI_EVENT_TYPE type);
+ //! set text markers
+ void setTextMarkers(s32 begin, s32 end);
+ //! create a Vertical ScrollBar
+ void createVScrollBar();
+ //! update the vertical scrollBar (visibilty & position)
+ void updateVScrollBar();
+
+ bool processKey(const SEvent& event);
+ bool processMouse(const SEvent& event);
+ s32 getCursorPos(s32 x, s32 y);
+
+ bool m_mouse_marking;
+ bool m_border;
+ bool m_background;
+ bool m_override_color_enabled;
+ s32 m_mark_begin;
+ s32 m_mark_end;
+
+ video::SColor m_override_color;
+ gui::IGUIFont *m_override_font, *m_last_break_font;
+ IOSOperator* m_operator;
+
+ u32 m_blink_start_time;
+ s32 m_cursor_pos;
+ s32 m_hscroll_pos, m_vscroll_pos; // scroll position in characters
+ u32 m_max;
+
+ bool m_word_wrap, m_multiline, m_autoscroll, m_passwordbox;
+ wchar_t m_passwordchar;
+ EGUI_ALIGNMENT m_halign, m_valign;
+
+ std::vector<core::stringw> m_broken_text;
+ std::vector<s32> m_broken_text_positions;
+
+ core::rect<s32> m_current_text_rect, m_frame_rect; // temporary values
+
+ u32 m_scrollbar_width;
+ IGUIScrollBar *m_vscrollbar;
+ bool m_writable;
+
+ bool m_bg_color_used;
+ video::SColor m_bg_color;
+};
+
+
+#endif // GUIEDITBOXWITHSCROLLBAR_HEADER
+
diff --git a/src/gui/guiEngine.cpp b/src/gui/guiEngine.cpp
new file mode 100644
index 000000000..e9b4e54c1
--- /dev/null
+++ b/src/gui/guiEngine.cpp
@@ -0,0 +1,587 @@
+/*
+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.
+*/
+
+#include "guiEngine.h"
+
+#include <IGUIStaticText.h>
+#include <ICameraSceneNode.h>
+#include "client/renderingengine.h"
+#include "scripting_mainmenu.h"
+#include "util/numeric.h"
+#include "config.h"
+#include "version.h"
+#include "porting.h"
+#include "filesys.h"
+#include "settings.h"
+#include "guiMainMenu.h"
+#include "sound.h"
+#include "sound_openal.h"
+#include "clouds.h"
+#include "httpfetch.h"
+#include "log.h"
+#include "fontengine.h"
+#include "guiscalingfilter.h"
+#include "irrlicht_changes/static_text.h"
+
+#ifdef __ANDROID__
+#include "client/tile.h"
+#include <GLES/gl.h>
+#endif
+
+
+/******************************************************************************/
+void TextDestGuiEngine::gotText(const StringMap &fields)
+{
+ m_engine->getScriptIface()->handleMainMenuButtons(fields);
+}
+
+/******************************************************************************/
+void TextDestGuiEngine::gotText(const std::wstring &text)
+{
+ m_engine->getScriptIface()->handleMainMenuEvent(wide_to_utf8(text));
+}
+
+/******************************************************************************/
+MenuTextureSource::~MenuTextureSource()
+{
+ for (const std::string &texture_to_delete : m_to_delete) {
+ const char *tname = texture_to_delete.c_str();
+ video::ITexture *texture = m_driver->getTexture(tname);
+ m_driver->removeTexture(texture);
+ }
+}
+
+/******************************************************************************/
+video::ITexture *MenuTextureSource::getTexture(const std::string &name, u32 *id)
+{
+ if(id)
+ *id = 0;
+ if(name.empty())
+ return NULL;
+ m_to_delete.insert(name);
+
+#ifdef __ANDROID__
+ video::IImage *image = m_driver->createImageFromFile(name.c_str());
+ if (image) {
+ image = Align2Npot2(image, m_driver);
+ video::ITexture* retval = m_driver->addTexture(name.c_str(), image);
+ image->drop();
+ return retval;
+ }
+#endif
+ return m_driver->getTexture(name.c_str());
+}
+
+/******************************************************************************/
+/** MenuMusicFetcher */
+/******************************************************************************/
+void MenuMusicFetcher::fetchSounds(const std::string &name,
+ std::set<std::string> &dst_paths,
+ std::set<std::string> &dst_datas)
+{
+ if(m_fetched.count(name))
+ return;
+ m_fetched.insert(name);
+ std::string base;
+ base = porting::path_share + DIR_DELIM + "sounds";
+ dst_paths.insert(base + DIR_DELIM + name + ".ogg");
+ int i;
+ for(i=0; i<10; i++)
+ dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg");
+ base = porting::path_user + DIR_DELIM + "sounds";
+ dst_paths.insert(base + DIR_DELIM + name + ".ogg");
+ for(i=0; i<10; i++)
+ dst_paths.insert(base + DIR_DELIM + name + "."+itos(i)+".ogg");
+}
+
+/******************************************************************************/
+/** GUIEngine */
+/******************************************************************************/
+GUIEngine::GUIEngine(JoystickController *joystick,
+ gui::IGUIElement *parent,
+ IMenuManager *menumgr,
+ MainMenuData *data,
+ bool &kill) :
+ m_parent(parent),
+ m_menumanager(menumgr),
+ m_smgr(RenderingEngine::get_scene_manager()),
+ m_data(data),
+ m_kill(kill)
+{
+ //initialize texture pointers
+ for (image_definition &texture : m_textures) {
+ texture.texture = NULL;
+ }
+ // is deleted by guiformspec!
+ m_buttonhandler = new TextDestGuiEngine(this);
+
+ //create texture source
+ m_texture_source = new MenuTextureSource(RenderingEngine::get_video_driver());
+
+ //create soundmanager
+ MenuMusicFetcher soundfetcher;
+#if USE_SOUND
+ m_sound_manager = createOpenALSoundManager(&soundfetcher);
+#endif
+ if(!m_sound_manager)
+ m_sound_manager = &dummySoundManager;
+
+ //create topleft header
+ m_toplefttext = L"";
+
+ core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()),
+ g_fontengine->getTextHeight());
+ rect += v2s32(4, 0);
+
+ m_irr_toplefttext =
+ addStaticText(RenderingEngine::get_gui_env(), m_toplefttext,
+ rect, false, true, 0, -1);
+
+ //create formspecsource
+ m_formspecgui = new FormspecFormSource("");
+
+ /* Create menu */
+ m_menu = new GUIFormSpecMenu(joystick,
+ m_parent,
+ -1,
+ m_menumanager,
+ NULL /* &client */,
+ m_texture_source,
+ m_formspecgui,
+ m_buttonhandler,
+ false);
+
+ m_menu->allowClose(false);
+ m_menu->lockSize(true,v2u32(800,600));
+
+ // Initialize scripting
+
+ infostream << "GUIEngine: Initializing Lua" << std::endl;
+
+ m_script = new MainMenuScripting(this);
+
+ try {
+ m_script->setMainMenuData(&m_data->script_data);
+ m_data->script_data.errormessage = "";
+
+ if (!loadMainMenuScript()) {
+ errorstream << "No future without main menu!" << std::endl;
+ abort();
+ }
+
+ run();
+ } catch (LuaError &e) {
+ errorstream << "Main menu error: " << e.what() << std::endl;
+ m_data->script_data.errormessage = e.what();
+ }
+
+ m_menu->quitMenu();
+ m_menu->drop();
+ m_menu = NULL;
+}
+
+/******************************************************************************/
+bool GUIEngine::loadMainMenuScript()
+{
+ // Set main menu path (for core.get_mainmenu_path())
+ m_scriptdir = g_settings->get("main_menu_path");
+ if (m_scriptdir.empty()) {
+ m_scriptdir = porting::path_share + DIR_DELIM + "builtin" + DIR_DELIM + "mainmenu";
+ }
+
+ // Load builtin (which will load the main menu script)
+ std::string script = porting::path_share + DIR_DELIM "builtin" + DIR_DELIM "init.lua";
+ try {
+ m_script->loadScript(script);
+ // Menu script loaded
+ return true;
+ } catch (const ModError &e) {
+ errorstream << "GUIEngine: execution of menu script failed: "
+ << e.what() << std::endl;
+ }
+
+ return false;
+}
+
+/******************************************************************************/
+void GUIEngine::run()
+{
+ // Always create clouds because they may or may not be
+ // needed based on the game selected
+ video::IVideoDriver *driver = RenderingEngine::get_video_driver();
+
+ cloudInit();
+
+ unsigned int text_height = g_fontengine->getTextHeight();
+
+ irr::core::dimension2d<u32> previous_screen_size(g_settings->getU16("screen_w"),
+ g_settings->getU16("screen_h"));
+
+ while (RenderingEngine::run() && (!m_startgame) && (!m_kill)) {
+
+ const irr::core::dimension2d<u32> &current_screen_size =
+ RenderingEngine::get_video_driver()->getScreenSize();
+ // Verify if window size has changed and save it if it's the case
+ // Ensure evaluating settings->getBool after verifying screensize
+ // First condition is cheaper
+ if (previous_screen_size != current_screen_size &&
+ current_screen_size != irr::core::dimension2d<u32>(0,0) &&
+ g_settings->getBool("autosave_screensize")) {
+ g_settings->setU16("screen_w", current_screen_size.Width);
+ g_settings->setU16("screen_h", current_screen_size.Height);
+ previous_screen_size = current_screen_size;
+ }
+
+ //check if we need to update the "upper left corner"-text
+ if (text_height != g_fontengine->getTextHeight()) {
+ updateTopLeftTextSize();
+ text_height = g_fontengine->getTextHeight();
+ }
+
+ driver->beginScene(true, true, video::SColor(255,140,186,250));
+
+ if (m_clouds_enabled)
+ {
+ cloudPreProcess();
+ drawOverlay(driver);
+ }
+ else
+ drawBackground(driver);
+
+ drawHeader(driver);
+ drawFooter(driver);
+
+ RenderingEngine::get_gui_env()->drawAll();
+
+ driver->endScene();
+
+ if (m_clouds_enabled)
+ cloudPostProcess();
+ else
+ sleep_ms(25);
+
+ m_script->step();
+
+#ifdef __ANDROID__
+ m_menu->getAndroidUIInput();
+#endif
+ }
+}
+
+/******************************************************************************/
+GUIEngine::~GUIEngine()
+{
+ if (m_sound_manager != &dummySoundManager){
+ delete m_sound_manager;
+ m_sound_manager = NULL;
+ }
+
+ infostream<<"GUIEngine: Deinitializing scripting"<<std::endl;
+ delete m_script;
+
+ m_irr_toplefttext->setText(L"");
+
+ //clean up texture pointers
+ for (image_definition &texture : m_textures) {
+ if (texture.texture)
+ RenderingEngine::get_video_driver()->removeTexture(texture.texture);
+ }
+
+ delete m_texture_source;
+
+ if (m_cloud.clouds)
+ m_cloud.clouds->drop();
+}
+
+/******************************************************************************/
+void GUIEngine::cloudInit()
+{
+ m_cloud.clouds = new Clouds(m_smgr, -1, rand());
+ m_cloud.clouds->setHeight(100.0f);
+ m_cloud.clouds->update(v3f(0, 0, 0), video::SColor(255,200,200,255));
+
+ m_cloud.camera = m_smgr->addCameraSceneNode(0,
+ v3f(0,0,0), v3f(0, 60, 100));
+ m_cloud.camera->setFarValue(10000);
+
+ m_cloud.lasttime = RenderingEngine::get_timer_time();
+}
+
+/******************************************************************************/
+void GUIEngine::cloudPreProcess()
+{
+ u32 time = RenderingEngine::get_timer_time();
+
+ if(time > m_cloud.lasttime)
+ m_cloud.dtime = (time - m_cloud.lasttime) / 1000.0;
+ else
+ m_cloud.dtime = 0;
+
+ m_cloud.lasttime = time;
+
+ m_cloud.clouds->step(m_cloud.dtime*3);
+ m_cloud.clouds->render();
+ m_smgr->drawAll();
+}
+
+/******************************************************************************/
+void GUIEngine::cloudPostProcess()
+{
+ float fps_max = g_settings->getFloat("pause_fps_max");
+ // Time of frame without fps limit
+ u32 busytime_u32;
+
+ // not using getRealTime is necessary for wine
+ u32 time = RenderingEngine::get_timer_time();
+ if(time > m_cloud.lasttime)
+ busytime_u32 = time - m_cloud.lasttime;
+ else
+ busytime_u32 = 0;
+
+ // FPS limiter
+ u32 frametime_min = 1000./fps_max;
+
+ if (busytime_u32 < frametime_min) {
+ u32 sleeptime = frametime_min - busytime_u32;
+ RenderingEngine::get_raw_device()->sleep(sleeptime);
+ }
+}
+
+/******************************************************************************/
+void GUIEngine::drawBackground(video::IVideoDriver *driver)
+{
+ v2u32 screensize = driver->getScreenSize();
+
+ video::ITexture* texture = m_textures[TEX_LAYER_BACKGROUND].texture;
+
+ /* If no texture, draw background of solid color */
+ if(!texture){
+ video::SColor color(255,80,58,37);
+ core::rect<s32> rect(0, 0, screensize.X, screensize.Y);
+ driver->draw2DRectangle(color, rect, NULL);
+ return;
+ }
+
+ v2u32 sourcesize = texture->getOriginalSize();
+
+ if (m_textures[TEX_LAYER_BACKGROUND].tile)
+ {
+ v2u32 tilesize(
+ MYMAX(sourcesize.X,m_textures[TEX_LAYER_BACKGROUND].minsize),
+ MYMAX(sourcesize.Y,m_textures[TEX_LAYER_BACKGROUND].minsize));
+ for (unsigned int x = 0; x < screensize.X; x += tilesize.X )
+ {
+ for (unsigned int y = 0; y < screensize.Y; y += tilesize.Y )
+ {
+ draw2DImageFilterScaled(driver, texture,
+ core::rect<s32>(x, y, x+tilesize.X, y+tilesize.Y),
+ core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
+ NULL, NULL, true);
+ }
+ }
+ return;
+ }
+
+ /* Draw background texture */
+ draw2DImageFilterScaled(driver, texture,
+ core::rect<s32>(0, 0, screensize.X, screensize.Y),
+ core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
+ NULL, NULL, true);
+}
+
+/******************************************************************************/
+void GUIEngine::drawOverlay(video::IVideoDriver *driver)
+{
+ v2u32 screensize = driver->getScreenSize();
+
+ video::ITexture* texture = m_textures[TEX_LAYER_OVERLAY].texture;
+
+ /* If no texture, draw nothing */
+ if(!texture)
+ return;
+
+ /* Draw background texture */
+ v2u32 sourcesize = texture->getOriginalSize();
+ draw2DImageFilterScaled(driver, texture,
+ core::rect<s32>(0, 0, screensize.X, screensize.Y),
+ core::rect<s32>(0, 0, sourcesize.X, sourcesize.Y),
+ NULL, NULL, true);
+}
+
+/******************************************************************************/
+void GUIEngine::drawHeader(video::IVideoDriver *driver)
+{
+ core::dimension2d<u32> screensize = driver->getScreenSize();
+
+ video::ITexture* texture = m_textures[TEX_LAYER_HEADER].texture;
+
+ /* If no texture, draw nothing */
+ if(!texture)
+ return;
+
+ f32 mult = (((f32)screensize.Width / 2.0)) /
+ ((f32)texture->getOriginalSize().Width);
+
+ v2s32 splashsize(((f32)texture->getOriginalSize().Width) * mult,
+ ((f32)texture->getOriginalSize().Height) * mult);
+
+ // Don't draw the header if there isn't enough room
+ s32 free_space = (((s32)screensize.Height)-320)/2;
+
+ if (free_space > splashsize.Y) {
+ core::rect<s32> splashrect(0, 0, splashsize.X, splashsize.Y);
+ splashrect += v2s32((screensize.Width/2)-(splashsize.X/2),
+ ((free_space/2)-splashsize.Y/2)+10);
+
+ video::SColor bgcolor(255,50,50,50);
+
+ draw2DImageFilterScaled(driver, texture, splashrect,
+ core::rect<s32>(core::position2d<s32>(0,0),
+ core::dimension2di(texture->getOriginalSize())),
+ NULL, NULL, true);
+ }
+}
+
+/******************************************************************************/
+void GUIEngine::drawFooter(video::IVideoDriver *driver)
+{
+ core::dimension2d<u32> screensize = driver->getScreenSize();
+
+ video::ITexture* texture = m_textures[TEX_LAYER_FOOTER].texture;
+
+ /* If no texture, draw nothing */
+ if(!texture)
+ return;
+
+ f32 mult = (((f32)screensize.Width)) /
+ ((f32)texture->getOriginalSize().Width);
+
+ v2s32 footersize(((f32)texture->getOriginalSize().Width) * mult,
+ ((f32)texture->getOriginalSize().Height) * mult);
+
+ // Don't draw the footer if there isn't enough room
+ s32 free_space = (((s32)screensize.Height)-320)/2;
+
+ if (free_space > footersize.Y) {
+ core::rect<s32> rect(0,0,footersize.X,footersize.Y);
+ rect += v2s32(screensize.Width/2,screensize.Height-footersize.Y);
+ rect -= v2s32(footersize.X/2, 0);
+
+ draw2DImageFilterScaled(driver, texture, rect,
+ core::rect<s32>(core::position2d<s32>(0,0),
+ core::dimension2di(texture->getOriginalSize())),
+ NULL, NULL, true);
+ }
+}
+
+/******************************************************************************/
+bool GUIEngine::setTexture(texture_layer layer, std::string texturepath,
+ bool tile_image, unsigned int minsize)
+{
+ video::IVideoDriver *driver = RenderingEngine::get_video_driver();
+
+ if (m_textures[layer].texture) {
+ driver->removeTexture(m_textures[layer].texture);
+ m_textures[layer].texture = NULL;
+ }
+
+ if (texturepath.empty() || !fs::PathExists(texturepath)) {
+ return false;
+ }
+
+ m_textures[layer].texture = driver->getTexture(texturepath.c_str());
+ m_textures[layer].tile = tile_image;
+ m_textures[layer].minsize = minsize;
+
+ if (!m_textures[layer].texture) {
+ return false;
+ }
+
+ return true;
+}
+
+/******************************************************************************/
+bool GUIEngine::downloadFile(const std::string &url, const std::string &target)
+{
+#if USE_CURL
+ std::ofstream target_file(target.c_str(), std::ios::out | std::ios::binary);
+
+ if (!target_file.good()) {
+ return false;
+ }
+
+ HTTPFetchRequest fetch_request;
+ HTTPFetchResult fetch_result;
+ fetch_request.url = url;
+ fetch_request.caller = HTTPFETCH_SYNC;
+ fetch_request.timeout = g_settings->getS32("curl_file_download_timeout");
+ httpfetch_sync(fetch_request, fetch_result);
+
+ if (!fetch_result.succeeded) {
+ return false;
+ }
+ target_file << fetch_result.data;
+
+ return true;
+#else
+ return false;
+#endif
+}
+
+/******************************************************************************/
+void GUIEngine::setTopleftText(const std::string &text)
+{
+ m_toplefttext = translate_string(utf8_to_wide(text));
+
+ updateTopLeftTextSize();
+}
+
+/******************************************************************************/
+void GUIEngine::updateTopLeftTextSize()
+{
+ core::rect<s32> rect(0, 0, g_fontengine->getTextWidth(m_toplefttext.c_str()),
+ g_fontengine->getTextHeight());
+ rect += v2s32(4, 0);
+
+ m_irr_toplefttext->remove();
+ m_irr_toplefttext =
+ addStaticText(RenderingEngine::get_gui_env(), m_toplefttext,
+ rect, false, true, 0, -1);
+}
+
+/******************************************************************************/
+s32 GUIEngine::playSound(SimpleSoundSpec spec, bool looped)
+{
+ s32 handle = m_sound_manager->playSound(spec, looped);
+ return handle;
+}
+
+/******************************************************************************/
+void GUIEngine::stopSound(s32 handle)
+{
+ m_sound_manager->stopSound(handle);
+}
+
+/******************************************************************************/
+unsigned int GUIEngine::queueAsync(const std::string &serialized_func,
+ const std::string &serialized_params)
+{
+ return m_script->queueAsync(serialized_func, serialized_params);
+}
+
diff --git a/src/gui/guiEngine.h b/src/gui/guiEngine.h
new file mode 100644
index 000000000..817d76014
--- /dev/null
+++ b/src/gui/guiEngine.h
@@ -0,0 +1,304 @@
+/*
+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.
+*/
+
+#pragma once
+
+/******************************************************************************/
+/* Includes */
+/******************************************************************************/
+#include "irrlichttypes.h"
+#include "modalMenu.h"
+#include "guiFormSpecMenu.h"
+#include "sound.h"
+#include "client/tile.h"
+#include "util/enriched_string.h"
+
+/******************************************************************************/
+/* Typedefs and macros */
+/******************************************************************************/
+/** texture layer ids */
+typedef enum {
+ TEX_LAYER_BACKGROUND = 0,
+ TEX_LAYER_OVERLAY,
+ TEX_LAYER_HEADER,
+ TEX_LAYER_FOOTER,
+ TEX_LAYER_MAX
+} texture_layer;
+
+typedef struct {
+ video::ITexture *texture = nullptr;
+ bool tile;
+ unsigned int minsize;
+} image_definition;
+
+/******************************************************************************/
+/* forward declarations */
+/******************************************************************************/
+class GUIEngine;
+class MainMenuScripting;
+class Clouds;
+struct MainMenuData;
+
+/******************************************************************************/
+/* declarations */
+/******************************************************************************/
+
+/** GUIEngine specific implementation of TextDest used within guiFormSpecMenu */
+class TextDestGuiEngine : public TextDest
+{
+public:
+ /**
+ * default constructor
+ * @param engine the engine data is transmitted for further processing
+ */
+ TextDestGuiEngine(GUIEngine* engine) : m_engine(engine) {};
+
+ /**
+ * receive fields transmitted by guiFormSpecMenu
+ * @param fields map containing formspec field elements currently active
+ */
+ void gotText(const StringMap &fields);
+
+ /**
+ * receive text/events transmitted by guiFormSpecMenu
+ * @param text textual representation of event
+ */
+ void gotText(const std::wstring &text);
+
+private:
+ /** target to transmit data to */
+ GUIEngine *m_engine = nullptr;
+};
+
+/** GUIEngine specific implementation of ISimpleTextureSource */
+class MenuTextureSource : public ISimpleTextureSource
+{
+public:
+ /**
+ * default constructor
+ * @param driver the video driver to load textures from
+ */
+ MenuTextureSource(video::IVideoDriver *driver) : m_driver(driver) {};
+
+ /**
+ * destructor, removes all loaded textures
+ */
+ virtual ~MenuTextureSource();
+
+ /**
+ * get a texture, loading it if required
+ * @param name path to the texture
+ * @param id receives the texture ID, always 0 in this implementation
+ */
+ video::ITexture *getTexture(const std::string &name, u32 *id = NULL);
+
+private:
+ /** driver to get textures from */
+ video::IVideoDriver *m_driver = nullptr;
+ /** set of texture names to delete */
+ std::set<std::string> m_to_delete;
+};
+
+/** GUIEngine specific implementation of OnDemandSoundFetcher */
+class MenuMusicFetcher: public OnDemandSoundFetcher
+{
+public:
+ /**
+ * get sound file paths according to sound name
+ * @param name sound name
+ * @param dst_paths receives possible paths to sound files
+ * @param dst_datas receives binary sound data (not used here)
+ */
+ void fetchSounds(const std::string &name,
+ std::set<std::string> &dst_paths,
+ std::set<std::string> &dst_datas);
+
+private:
+ /** set of fetched sound names */
+ std::set<std::string> m_fetched;
+};
+
+/** implementation of main menu based uppon formspecs */
+class GUIEngine {
+ /** grant ModApiMainMenu access to private members */
+ friend class ModApiMainMenu;
+ friend class ModApiSound;
+
+public:
+ /**
+ * default constructor
+ * @param dev device to draw at
+ * @param parent parent gui element
+ * @param menumgr manager to add menus to
+ * @param smgr scene manager to add scene elements to
+ * @param data struct to transfer data to main game handling
+ */
+ GUIEngine(JoystickController *joystick,
+ gui::IGUIElement *parent,
+ IMenuManager *menumgr,
+ MainMenuData *data,
+ bool &kill);
+
+ /** default destructor */
+ virtual ~GUIEngine();
+
+ /**
+ * return MainMenuScripting interface
+ */
+ MainMenuScripting *getScriptIface()
+ {
+ return m_script;
+ }
+
+ /**
+ * return dir of current menuscript
+ */
+ std::string getScriptDir()
+ {
+ return m_scriptdir;
+ }
+
+ /** pass async callback to scriptengine **/
+ unsigned int queueAsync(const std::string &serialized_fct,
+ const std::string &serialized_params);
+
+private:
+
+ /** find and run the main menu script */
+ bool loadMainMenuScript();
+
+ /** run main menu loop */
+ void run();
+
+ /** update size of topleftext element */
+ void updateTopLeftTextSize();
+
+ /** parent gui element */
+ gui::IGUIElement *m_parent = nullptr;
+ /** manager to add menus to */
+ IMenuManager *m_menumanager = nullptr;
+ /** scene manager to add scene elements to */
+ scene::ISceneManager *m_smgr = nullptr;
+ /** pointer to data beeing transfered back to main game handling */
+ MainMenuData *m_data = nullptr;
+ /** pointer to texture source */
+ ISimpleTextureSource *m_texture_source = nullptr;
+ /** pointer to soundmanager*/
+ ISoundManager *m_sound_manager = nullptr;
+
+ /** representation of form source to be used in mainmenu formspec */
+ FormspecFormSource *m_formspecgui = nullptr;
+ /** formspec input receiver */
+ TextDestGuiEngine *m_buttonhandler = nullptr;
+ /** the formspec menu */
+ GUIFormSpecMenu *m_menu = nullptr;
+
+ /** reference to kill variable managed by SIGINT handler */
+ bool &m_kill;
+
+ /** variable used to abort menu and return back to main game handling */
+ bool m_startgame = false;
+
+ /** scripting interface */
+ MainMenuScripting *m_script = nullptr;
+
+ /** script basefolder */
+ std::string m_scriptdir = "";
+
+ /**
+ * draw background layer
+ * @param driver to use for drawing
+ */
+ void drawBackground(video::IVideoDriver *driver);
+ /**
+ * draw overlay layer
+ * @param driver to use for drawing
+ */
+ void drawOverlay(video::IVideoDriver *driver);
+ /**
+ * draw header layer
+ * @param driver to use for drawing
+ */
+ void drawHeader(video::IVideoDriver *driver);
+ /**
+ * draw footer layer
+ * @param driver to use for drawing
+ */
+ void drawFooter(video::IVideoDriver *driver);
+
+ /**
+ * load a texture for a specified layer
+ * @param layer draw layer to specify texture
+ * @param texturepath full path of texture to load
+ */
+ bool setTexture(texture_layer layer, std::string texturepath,
+ bool tile_image, unsigned int minsize);
+
+ /**
+ * download a file using curl
+ * @param url url to download
+ * @param target file to store to
+ */
+ static bool downloadFile(const std::string &url, const std::string &target);
+
+ /** array containing pointers to current specified texture layers */
+ image_definition m_textures[TEX_LAYER_MAX];
+
+ /**
+ * specify text to appear as top left string
+ * @param text to set
+ */
+ void setTopleftText(const std::string &text);
+
+ /** pointer to gui element shown at topleft corner */
+ irr::gui::IGUIStaticText *m_irr_toplefttext = nullptr;
+ /** and text that is in it */
+ EnrichedString m_toplefttext;
+
+ /** initialize cloud subsystem */
+ void cloudInit();
+ /** do preprocessing for cloud subsystem */
+ void cloudPreProcess();
+ /** do postprocessing for cloud subsystem */
+ void cloudPostProcess();
+
+ /** internam data required for drawing clouds */
+ struct clouddata {
+ /** delta time since last cloud processing */
+ f32 dtime;
+ /** absolute time of last cloud processing */
+ u32 lasttime;
+ /** pointer to cloud class */
+ Clouds *clouds = nullptr;
+ /** camera required for drawing clouds */
+ scene::ICameraSceneNode *camera = nullptr;
+ };
+
+ /** is drawing of clouds enabled atm */
+ bool m_clouds_enabled = true;
+ /** data used to draw clouds */
+ clouddata m_cloud;
+
+ /** start playing a sound and return handle */
+ s32 playSound(SimpleSoundSpec spec, bool looped);
+ /** stop playing a sound started with playSound() */
+ void stopSound(s32 handle);
+
+
+};
diff --git a/src/gui/guiFormSpecMenu.cpp b/src/gui/guiFormSpecMenu.cpp
new file mode 100644
index 000000000..0691bc598
--- /dev/null
+++ b/src/gui/guiFormSpecMenu.cpp
@@ -0,0 +1,3864 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+
+#include <cstdlib>
+#include <algorithm>
+#include <iterator>
+#include <sstream>
+#include <limits>
+#include "guiFormSpecMenu.h"
+#include "guiTable.h"
+#include "constants.h"
+#include "gamedef.h"
+#include "keycode.h"
+#include "util/strfnd.h"
+#include <IGUICheckBox.h>
+#include <IGUIEditBox.h>
+#include <IGUIButton.h>
+#include <IGUIStaticText.h>
+#include <IGUIFont.h>
+#include <IGUITabControl.h>
+#include <IGUIComboBox.h>
+#include "client/renderingengine.h"
+#include "log.h"
+#include "client/tile.h" // ITextureSource
+#include "hud.h" // drawItemStack
+#include "filesys.h"
+#include "gettime.h"
+#include "gettext.h"
+#include "scripting_server.h"
+#include "porting.h"
+#include "settings.h"
+#include "client.h"
+#include "fontengine.h"
+#include "util/hex.h"
+#include "util/numeric.h"
+#include "util/string.h" // for parseColorString()
+#include "irrlicht_changes/static_text.h"
+#include "guiscalingfilter.h"
+#include "guiEditBoxWithScrollbar.h"
+
+#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
+#include "intlGUIEditBox.h"
+#endif
+
+#define MY_CHECKPOS(a,b) \
+ if (v_pos.size() != 2) { \
+ errorstream<< "Invalid pos for element " << a << "specified: \"" \
+ << parts[b] << "\"" << std::endl; \
+ return; \
+ }
+
+#define MY_CHECKGEOM(a,b) \
+ if (v_geom.size() != 2) { \
+ errorstream<< "Invalid pos for element " << a << "specified: \"" \
+ << parts[b] << "\"" << std::endl; \
+ return; \
+ }
+/*
+ GUIFormSpecMenu
+*/
+static unsigned int font_line_height(gui::IGUIFont *font)
+{
+ return font->getDimension(L"Ay").Height + font->getKerningHeight();
+}
+
+inline u32 clamp_u8(s32 value)
+{
+ return (u32) MYMIN(MYMAX(value, 0), 255);
+}
+
+GUIFormSpecMenu::GUIFormSpecMenu(JoystickController *joystick,
+ gui::IGUIElement *parent, s32 id, IMenuManager *menumgr,
+ Client *client, ISimpleTextureSource *tsrc, IFormSource *fsrc, TextDest *tdst,
+ bool remap_dbl_click) :
+ GUIModalMenu(RenderingEngine::get_gui_env(), parent, id, menumgr),
+ m_invmgr(client),
+ m_tsrc(tsrc),
+ m_client(client),
+ m_form_src(fsrc),
+ m_text_dst(tdst),
+ m_joystick(joystick),
+ m_remap_dbl_click(remap_dbl_click)
+#ifdef __ANDROID__
+ , m_JavaDialogFieldName("")
+#endif
+{
+ current_keys_pending.key_down = false;
+ current_keys_pending.key_up = false;
+ current_keys_pending.key_enter = false;
+ current_keys_pending.key_escape = false;
+
+ m_doubleclickdetect[0].time = 0;
+ m_doubleclickdetect[1].time = 0;
+
+ m_doubleclickdetect[0].pos = v2s32(0, 0);
+ m_doubleclickdetect[1].pos = v2s32(0, 0);
+
+ m_tooltip_show_delay = (u32)g_settings->getS32("tooltip_show_delay");
+ m_tooltip_append_itemname = g_settings->getBool("tooltip_append_itemname");
+}
+
+GUIFormSpecMenu::~GUIFormSpecMenu()
+{
+ removeChildren();
+
+ for (auto &table_it : m_tables) {
+ table_it.second->drop();
+ }
+
+ delete m_selected_item;
+ delete m_form_src;
+ delete m_text_dst;
+}
+
+void GUIFormSpecMenu::removeChildren()
+{
+ const core::list<gui::IGUIElement*> &children = getChildren();
+
+ while(!children.empty()) {
+ (*children.getLast())->remove();
+ }
+
+ if(m_tooltip_element) {
+ m_tooltip_element->remove();
+ m_tooltip_element->drop();
+ m_tooltip_element = NULL;
+ }
+
+}
+
+void GUIFormSpecMenu::setInitialFocus()
+{
+ // Set initial focus according to following order of precedence:
+ // 1. first empty editbox
+ // 2. first editbox
+ // 3. first table
+ // 4. last button
+ // 5. first focusable (not statictext, not tabheader)
+ // 6. first child element
+
+ core::list<gui::IGUIElement*> children = getChildren();
+
+ // in case "children" contains any NULL elements, remove them
+ for (core::list<gui::IGUIElement*>::Iterator it = children.begin();
+ it != children.end();) {
+ if (*it)
+ ++it;
+ else
+ it = children.erase(it);
+ }
+
+ // 1. first empty editbox
+ for (gui::IGUIElement *it : children) {
+ if (it->getType() == gui::EGUIET_EDIT_BOX
+ && it->getText()[0] == 0) {
+ Environment->setFocus(it);
+ return;
+ }
+ }
+
+ // 2. first editbox
+ for (gui::IGUIElement *it : children) {
+ if (it->getType() == gui::EGUIET_EDIT_BOX) {
+ Environment->setFocus(it);
+ return;
+ }
+ }
+
+ // 3. first table
+ for (gui::IGUIElement *it : children) {
+ if (it->getTypeName() == std::string("GUITable")) {
+ Environment->setFocus(it);
+ return;
+ }
+ }
+
+ // 4. last button
+ for (core::list<gui::IGUIElement*>::Iterator it = children.getLast();
+ it != children.end(); --it) {
+ if ((*it)->getType() == gui::EGUIET_BUTTON) {
+ Environment->setFocus(*it);
+ return;
+ }
+ }
+
+ // 5. first focusable (not statictext, not tabheader)
+ for (gui::IGUIElement *it : children) {
+ if (it->getType() != gui::EGUIET_STATIC_TEXT &&
+ it->getType() != gui::EGUIET_TAB_CONTROL) {
+ Environment->setFocus(it);
+ return;
+ }
+ }
+
+ // 6. first child element
+ if (children.empty())
+ Environment->setFocus(this);
+ else
+ Environment->setFocus(*(children.begin()));
+}
+
+GUITable* GUIFormSpecMenu::getTable(const std::string &tablename)
+{
+ for (auto &table : m_tables) {
+ if (tablename == table.first.fname)
+ return table.second;
+ }
+ return 0;
+}
+
+std::vector<std::string>* GUIFormSpecMenu::getDropDownValues(const std::string &name)
+{
+ for (auto &dropdown : m_dropdowns) {
+ if (name == dropdown.first.fname)
+ return &dropdown.second;
+ }
+ return NULL;
+}
+
+void GUIFormSpecMenu::parseSize(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,',');
+
+ if (((parts.size() == 2) || parts.size() == 3) ||
+ ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ if (parts[1].find(';') != std::string::npos)
+ parts[1] = parts[1].substr(0,parts[1].find(';'));
+
+ data->invsize.X = MYMAX(0, stof(parts[0]));
+ data->invsize.Y = MYMAX(0, stof(parts[1]));
+
+ lockSize(false);
+ if (parts.size() == 3) {
+ if (parts[2] == "true") {
+ lockSize(true,v2u32(800,600));
+ }
+ }
+
+ data->explicit_size = true;
+ return;
+ }
+ errorstream<< "Invalid size element (" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseContainer(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ',');
+
+ if (parts.size() >= 2) {
+ if (parts[1].find(';') != std::string::npos)
+ parts[1] = parts[1].substr(0, parts[1].find(';'));
+
+ container_stack.push(pos_offset);
+ pos_offset.X += MYMAX(0, stof(parts[0]));
+ pos_offset.Y += MYMAX(0, stof(parts[1]));
+ return;
+ }
+ errorstream<< "Invalid container start element (" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseContainerEnd(parserData* data)
+{
+ if (container_stack.empty()) {
+ errorstream<< "Invalid container end element, no matching container start element" << std::endl;
+ } else {
+ pos_offset = container_stack.top();
+ container_stack.pop();
+ }
+}
+
+void GUIFormSpecMenu::parseList(parserData* data, const std::string &element)
+{
+ if (m_client == 0) {
+ warningstream<<"invalid use of 'list' with m_client==0"<<std::endl;
+ return;
+ }
+
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 4) || (parts.size() == 5)) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::string location = parts[0];
+ std::string listname = parts[1];
+ std::vector<std::string> v_pos = split(parts[2],',');
+ std::vector<std::string> v_geom = split(parts[3],',');
+ std::string startindex;
+ if (parts.size() == 5)
+ startindex = parts[4];
+
+ MY_CHECKPOS("list",2);
+ MY_CHECKGEOM("list",3);
+
+ InventoryLocation loc;
+
+ if(location == "context" || location == "current_name")
+ loc = m_current_inventory_location;
+ else
+ loc.deSerialize(location);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ v2s32 geom;
+ geom.X = stoi(v_geom[0]);
+ geom.Y = stoi(v_geom[1]);
+
+ s32 start_i = 0;
+ if (!startindex.empty())
+ start_i = stoi(startindex);
+
+ if (geom.X < 0 || geom.Y < 0 || start_i < 0) {
+ errorstream<< "Invalid list element: '" << element << "'" << std::endl;
+ return;
+ }
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of list without a size[] element"<<std::endl;
+ m_inventorylists.emplace_back(loc, listname, pos, geom, start_i);
+ return;
+ }
+ errorstream<< "Invalid list element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseListRing(parserData* data, const std::string &element)
+{
+ if (m_client == 0) {
+ errorstream << "WARNING: invalid use of 'listring' with m_client==0" << std::endl;
+ return;
+ }
+
+ std::vector<std::string> parts = split(element, ';');
+
+ if (parts.size() == 2) {
+ std::string location = parts[0];
+ std::string listname = parts[1];
+
+ InventoryLocation loc;
+
+ if (location == "context" || location == "current_name")
+ loc = m_current_inventory_location;
+ else
+ loc.deSerialize(location);
+
+ m_inventory_rings.emplace_back(loc, listname);
+ return;
+ }
+
+ if (element.empty() && m_inventorylists.size() > 1) {
+ size_t siz = m_inventorylists.size();
+ // insert the last two inv list elements into the list ring
+ const ListDrawSpec &spa = m_inventorylists[siz - 2];
+ const ListDrawSpec &spb = m_inventorylists[siz - 1];
+ m_inventory_rings.emplace_back(spa.inventoryloc, spa.listname);
+ m_inventory_rings.emplace_back(spb.inventoryloc, spb.listname);
+ return;
+ }
+
+ errorstream<< "Invalid list ring element(" << parts.size() << ", "
+ << m_inventorylists.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseCheckbox(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() >= 3) && (parts.size() <= 4)) ||
+ ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::string name = parts[1];
+ std::string label = parts[2];
+ std::string selected;
+
+ if (parts.size() >= 4)
+ selected = parts[3];
+
+ MY_CHECKPOS("checkbox",0);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ bool fselected = false;
+
+ if (selected == "true")
+ fselected = true;
+
+ std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
+
+ core::rect<s32> rect = core::rect<s32>(
+ pos.X, pos.Y + ((imgsize.Y/2) - m_btn_height),
+ pos.X + m_font->getDimension(wlabel.c_str()).Width + 25, // text size + size of checkbox
+ pos.Y + ((imgsize.Y/2) + m_btn_height));
+
+ FieldSpec spec(
+ name,
+ wlabel, //Needed for displaying text on MSVC
+ wlabel,
+ 258+m_fields.size()
+ );
+
+ spec.ftype = f_CheckBox;
+
+ gui::IGUICheckBox* e = Environment->addCheckBox(fselected, rect, this,
+ spec.fid, spec.flabel.c_str());
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ m_checkboxes.emplace_back(spec,e);
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid checkbox element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseScrollBar(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (parts.size() >= 5) {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_dim = split(parts[1],',');
+ std::string name = parts[3];
+ std::string value = parts[4];
+
+ MY_CHECKPOS("scrollbar",0);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ if (v_dim.size() != 2) {
+ errorstream<< "Invalid size for element " << "scrollbar"
+ << "specified: \"" << parts[1] << "\"" << std::endl;
+ return;
+ }
+
+ v2s32 dim;
+ dim.X = stof(v_dim[0]) * (float) spacing.X;
+ dim.Y = stof(v_dim[1]) * (float) spacing.Y;
+
+ core::rect<s32> rect =
+ core::rect<s32>(pos.X, pos.Y, pos.X + dim.X, pos.Y + dim.Y);
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258+m_fields.size()
+ );
+
+ bool is_horizontal = true;
+
+ if (parts[2] == "vertical")
+ is_horizontal = false;
+
+ spec.ftype = f_ScrollBar;
+ spec.send = true;
+ gui::IGUIScrollBar* e =
+ Environment->addScrollBar(is_horizontal,rect,this,spec.fid);
+
+ e->setMax(1000);
+ e->setMin(0);
+ e->setPos(stoi(parts[4]));
+ e->setSmallStep(10);
+ e->setLargeStep(100);
+
+ m_scrollbars.emplace_back(spec,e);
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid scrollbar element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseImage(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 3) ||
+ ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = unescape_string(parts[2]);
+
+ MY_CHECKPOS("image", 0);
+ MY_CHECKGEOM("image", 1);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)imgsize.X;
+ geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
+
+ if (!data->explicit_size)
+ warningstream<<"invalid use of image without a size[] element"<<std::endl;
+ m_images.emplace_back(name, pos, geom);
+ return;
+ }
+
+ if (parts.size() == 2) {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::string name = unescape_string(parts[1]);
+
+ MY_CHECKPOS("image", 0);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ if (!data->explicit_size)
+ warningstream<<"invalid use of image without a size[] element"<<std::endl;
+ m_images.emplace_back(name, pos);
+ return;
+ }
+ errorstream<< "Invalid image element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseItemImage(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 3) ||
+ ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+
+ MY_CHECKPOS("itemimage",0);
+ MY_CHECKGEOM("itemimage",1);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)imgsize.X;
+ geom.Y = stof(v_geom[1]) * (float)imgsize.Y;
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of item_image without a size[] element"<<std::endl;
+ m_itemimages.emplace_back("", name, pos, geom);
+ return;
+ }
+ errorstream<< "Invalid ItemImage element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseButton(parserData* data, const std::string &element,
+ const std::string &type)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 4) ||
+ ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+ std::string label = parts[3];
+
+ MY_CHECKPOS("button",0);
+ MY_CHECKGEOM("button",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ v2s32 geom;
+ geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
+ pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
+
+ core::rect<s32> rect =
+ core::rect<s32>(pos.X, pos.Y - m_btn_height,
+ pos.X + geom.X, pos.Y + m_btn_height);
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of button without a size[] element"<<std::endl;
+
+ std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
+
+ FieldSpec spec(
+ name,
+ wlabel,
+ L"",
+ 258+m_fields.size()
+ );
+ spec.ftype = f_Button;
+ if(type == "button_exit")
+ spec.is_exit = true;
+ gui::IGUIButton* e = Environment->addButton(rect, this, spec.fid,
+ spec.flabel.c_str());
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid button element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseBackground(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 3) || (parts.size() == 4)) ||
+ ((parts.size() > 4) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = unescape_string(parts[2]);
+
+ MY_CHECKPOS("background",0);
+ MY_CHECKGEOM("background",1);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X - ((float)spacing.X - (float)imgsize.X)/2;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y - ((float)spacing.Y - (float)imgsize.Y)/2;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)spacing.X;
+ geom.Y = stof(v_geom[1]) * (float)spacing.Y;
+
+ if (!data->explicit_size)
+ warningstream<<"invalid use of background without a size[] element"<<std::endl;
+
+ bool clip = false;
+ if (parts.size() == 4 && is_yes(parts[3])) {
+ pos.X = stoi(v_pos[0]); //acts as offset
+ pos.Y = stoi(v_pos[1]); //acts as offset
+ clip = true;
+ }
+ m_backgrounds.emplace_back(name, pos, geom, clip);
+
+ return;
+ }
+ errorstream<< "Invalid background element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseTableOptions(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ data->table_options.clear();
+ for (const std::string &part : parts) {
+ // Parse table option
+ std::string opt = unescape_string(part);
+ data->table_options.push_back(GUITable::splitOption(opt));
+ }
+}
+
+void GUIFormSpecMenu::parseTableColumns(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ data->table_columns.clear();
+ for (const std::string &part : parts) {
+ std::vector<std::string> col_parts = split(part,',');
+ GUITable::TableColumn column;
+ // Parse column type
+ if (!col_parts.empty())
+ column.type = col_parts[0];
+ // Parse column options
+ for (size_t j = 1; j < col_parts.size(); ++j) {
+ std::string opt = unescape_string(col_parts[j]);
+ column.options.push_back(GUITable::splitOption(opt));
+ }
+ data->table_columns.push_back(column);
+ }
+}
+
+void GUIFormSpecMenu::parseTable(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 4) || (parts.size() == 5)) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+ std::vector<std::string> items = split(parts[3],',');
+ std::string str_initial_selection;
+ std::string str_transparent = "false";
+
+ if (parts.size() >= 5)
+ str_initial_selection = parts[4];
+
+ MY_CHECKPOS("table",0);
+ MY_CHECKGEOM("table",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)spacing.X;
+ geom.Y = stof(v_geom[1]) * (float)spacing.Y;
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258+m_fields.size()
+ );
+
+ spec.ftype = f_Table;
+
+ for (std::string &item : items) {
+ item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
+ }
+
+ //now really show table
+ GUITable *e = new GUITable(Environment, this, spec.fid, rect,
+ m_tsrc);
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ e->setTable(data->table_options, data->table_columns, items);
+
+ if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
+ e->setDynamicData(data->table_dyndata[name]);
+ }
+
+ if (!str_initial_selection.empty() && str_initial_selection != "0")
+ e->setSelected(stoi(str_initial_selection));
+
+ m_tables.emplace_back(spec, e);
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid table element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseTextList(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 4) || (parts.size() == 5) || (parts.size() == 6)) ||
+ ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+ std::vector<std::string> items = split(parts[3],',');
+ std::string str_initial_selection;
+ std::string str_transparent = "false";
+
+ if (parts.size() >= 5)
+ str_initial_selection = parts[4];
+
+ if (parts.size() >= 6)
+ str_transparent = parts[5];
+
+ MY_CHECKPOS("textlist",0);
+ MY_CHECKGEOM("textlist",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)spacing.X;
+ geom.Y = stof(v_geom[1]) * (float)spacing.Y;
+
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258+m_fields.size()
+ );
+
+ spec.ftype = f_Table;
+
+ for (std::string &item : items) {
+ item = wide_to_utf8(unescape_translate(utf8_to_wide(unescape_string(item))));
+ }
+
+ //now really show list
+ GUITable *e = new GUITable(Environment, this, spec.fid, rect,
+ m_tsrc);
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ e->setTextList(items, is_yes(str_transparent));
+
+ if (data->table_dyndata.find(name) != data->table_dyndata.end()) {
+ e->setDynamicData(data->table_dyndata[name]);
+ }
+
+ if (!str_initial_selection.empty() && str_initial_selection != "0")
+ e->setSelected(stoi(str_initial_selection));
+
+ m_tables.emplace_back(spec, e);
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid textlist element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+
+void GUIFormSpecMenu::parseDropDown(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 5) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::string name = parts[2];
+ std::vector<std::string> items = split(parts[3],',');
+ std::string str_initial_selection;
+ str_initial_selection = parts[4];
+
+ MY_CHECKPOS("dropdown",0);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ s32 width = stof(parts[1]) * (float)spacing.Y;
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y,
+ pos.X + width, pos.Y + (m_btn_height * 2));
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258+m_fields.size()
+ );
+
+ spec.ftype = f_DropDown;
+ spec.send = true;
+
+ //now really show list
+ gui::IGUIComboBox *e = Environment->addComboBox(rect, this,spec.fid);
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ for (const std::string &item : items) {
+ e->addItem(unescape_translate(unescape_string(
+ utf8_to_wide(item))).c_str());
+ }
+
+ if (!str_initial_selection.empty())
+ e->setSelected(stoi(str_initial_selection)-1);
+
+ m_fields.push_back(spec);
+
+ m_dropdowns.emplace_back(spec, std::vector<std::string>());
+ std::vector<std::string> &values = m_dropdowns.back().second;
+ for (const std::string &item : items) {
+ values.push_back(unescape_string(item));
+ }
+
+ return;
+ }
+ errorstream << "Invalid dropdown element(" << parts.size() << "): '"
+ << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseFieldCloseOnEnter(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+ if (parts.size() == 2 ||
+ (parts.size() > 2 && m_formspec_version > FORMSPEC_API_VERSION)) {
+ field_close_on_enter[parts[0]] = is_yes(parts[1]);
+ }
+}
+
+void GUIFormSpecMenu::parsePwdField(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 4) || (parts.size() == 5) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+ std::string label = parts[3];
+
+ MY_CHECKPOS("pwdfield",0);
+ MY_CHECKGEOM("pwdfield",1);
+
+ v2s32 pos = pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ v2s32 geom;
+ geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
+
+ pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
+ pos.Y -= m_btn_height;
+ geom.Y = m_btn_height*2;
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
+
+ FieldSpec spec(
+ name,
+ wlabel,
+ L"",
+ 258+m_fields.size()
+ );
+
+ spec.send = true;
+ gui::IGUIEditBox * e = Environment->addEditBox(0, rect, true, this, spec.fid);
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ if (label.length() >= 1)
+ {
+ int font_height = g_fontengine->getTextHeight();
+ rect.UpperLeftCorner.Y -= font_height;
+ rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
+ addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
+ }
+
+ e->setPasswordBox(true,L'*');
+
+ irr::SEvent evt;
+ evt.EventType = EET_KEY_INPUT_EVENT;
+ evt.KeyInput.Key = KEY_END;
+ evt.KeyInput.Char = 0;
+ evt.KeyInput.Control = false;
+ evt.KeyInput.Shift = false;
+ evt.KeyInput.PressedDown = true;
+ e->OnEvent(evt);
+
+ if (parts.size() >= 5) {
+ // TODO: remove after 2016-11-03
+ warningstream << "pwdfield: use field_close_on_enter[name, enabled]" <<
+ " instead of the 5th param" << std::endl;
+ field_close_on_enter[name] = is_yes(parts[4]);
+ }
+
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid pwdfield element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseSimpleField(parserData* data,
+ std::vector<std::string> &parts)
+{
+ std::string name = parts[0];
+ std::string label = parts[1];
+ std::string default_val = parts[2];
+
+ core::rect<s32> rect;
+
+ if(data->explicit_size)
+ warningstream<<"invalid use of unpositioned \"field\" in inventory"<<std::endl;
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.Y = ((m_fields.size()+2)*60);
+ v2s32 size = DesiredRect.getSize();
+
+ rect = core::rect<s32>(size.X / 2 - 150, pos.Y,
+ (size.X / 2 - 150) + 300, pos.Y + (m_btn_height*2));
+
+
+ if(m_form_src)
+ default_val = m_form_src->resolveText(default_val);
+
+
+ std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
+
+ FieldSpec spec(
+ name,
+ wlabel,
+ utf8_to_wide(unescape_string(default_val)),
+ 258+m_fields.size()
+ );
+
+ if (name.empty()) {
+ // spec field id to 0, this stops submit searching for a value that isn't there
+ addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, spec.fid);
+ } else {
+ spec.send = true;
+ gui::IGUIElement *e;
+#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
+ if (g_settings->getBool("freetype")) {
+ e = (gui::IGUIElement *) new gui::intlGUIEditBox(spec.fdefault.c_str(),
+ true, Environment, this, spec.fid, rect);
+ e->drop();
+ } else {
+#else
+ {
+#endif
+ e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid);
+ }
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ irr::SEvent evt;
+ evt.EventType = EET_KEY_INPUT_EVENT;
+ evt.KeyInput.Key = KEY_END;
+ evt.KeyInput.Char = 0;
+ evt.KeyInput.Control = 0;
+ evt.KeyInput.Shift = 0;
+ evt.KeyInput.PressedDown = true;
+ e->OnEvent(evt);
+
+ if (label.length() >= 1)
+ {
+ int font_height = g_fontengine->getTextHeight();
+ rect.UpperLeftCorner.Y -= font_height;
+ rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
+ addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
+ }
+ }
+
+ if (parts.size() >= 4) {
+ // TODO: remove after 2016-11-03
+ warningstream << "field/simple: use field_close_on_enter[name, enabled]" <<
+ " instead of the 4th param" << std::endl;
+ field_close_on_enter[name] = is_yes(parts[3]);
+ }
+
+ m_fields.push_back(spec);
+}
+
+void GUIFormSpecMenu::parseTextArea(parserData* data, std::vector<std::string>& parts,
+ const std::string &type)
+{
+
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string name = parts[2];
+ std::string label = parts[3];
+ std::string default_val = parts[4];
+
+ MY_CHECKPOS(type,0);
+ MY_CHECKGEOM(type,1);
+
+ v2s32 pos = pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ v2s32 geom;
+
+ geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
+
+ if (type == "textarea")
+ {
+ geom.Y = (stof(v_geom[1]) * (float)imgsize.Y) - (spacing.Y-imgsize.Y);
+ pos.Y += m_btn_height;
+ }
+ else
+ {
+ pos.Y += (stof(v_geom[1]) * (float)imgsize.Y)/2;
+ pos.Y -= m_btn_height;
+ geom.Y = m_btn_height*2;
+ }
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of positioned "<<type<<" without a size[] element"<<std::endl;
+
+ if(m_form_src)
+ default_val = m_form_src->resolveText(default_val);
+
+
+ std::wstring wlabel = translate_string(utf8_to_wide(unescape_string(label)));
+
+ FieldSpec spec(
+ name,
+ wlabel,
+ utf8_to_wide(unescape_string(default_val)),
+ 258+m_fields.size()
+ );
+
+ bool is_editable = !name.empty();
+
+ if (is_editable)
+ spec.send = true;
+
+ gui::IGUIEditBox *e = nullptr;
+ const wchar_t *text = spec.fdefault.empty() ?
+ wlabel.c_str() : spec.fdefault.c_str();
+
+#if USE_FREETYPE && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
+ if (g_settings->getBool("freetype")) {
+ e = (gui::IGUIEditBox *) new gui::intlGUIEditBox(text,
+ true, Environment, this, spec.fid, rect, is_editable, true);
+ e->drop();
+ } else {
+#else
+ {
+#endif
+ e = new GUIEditBoxWithScrollBar(text, true,
+ Environment, this, spec.fid, rect, is_editable, true);
+ }
+
+ if (is_editable && spec.fname == data->focused_fieldname)
+ Environment->setFocus(e);
+
+ if (e) {
+ if (type == "textarea")
+ {
+ e->setMultiLine(true);
+ e->setWordWrap(true);
+ e->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
+ } else {
+ irr::SEvent evt;
+ evt.EventType = EET_KEY_INPUT_EVENT;
+ evt.KeyInput.Key = KEY_END;
+ evt.KeyInput.Char = 0;
+ evt.KeyInput.Control = 0;
+ evt.KeyInput.Shift = 0;
+ evt.KeyInput.PressedDown = true;
+ e->OnEvent(evt);
+ }
+ }
+
+ if (is_editable && !label.empty()) {
+ int font_height = g_fontengine->getTextHeight();
+ rect.UpperLeftCorner.Y -= font_height;
+ rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + font_height;
+ addStaticText(Environment, spec.flabel.c_str(), rect, false, true, this, 0);
+ }
+
+ if (parts.size() >= 6) {
+ // TODO: remove after 2016-11-03
+ warningstream << "field/textarea: use field_close_on_enter[name, enabled]" <<
+ " instead of the 6th param" << std::endl;
+ field_close_on_enter[name] = is_yes(parts[5]);
+ }
+
+ m_fields.push_back(spec);
+}
+
+void GUIFormSpecMenu::parseField(parserData* data, const std::string &element,
+ const std::string &type)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (parts.size() == 3 || parts.size() == 4) {
+ parseSimpleField(data,parts);
+ return;
+ }
+
+ if ((parts.size() == 5) || (parts.size() == 6) ||
+ ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ parseTextArea(data,parts,type);
+ return;
+ }
+ errorstream<< "Invalid field element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseLabel(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 2) ||
+ ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::string text = parts[1];
+
+ MY_CHECKPOS("label",0);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += (stof(v_pos[1]) + 7.0/30.0) * (float)spacing.Y;
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of label without a size[] element"<<std::endl;
+
+ std::vector<std::string> lines = split(text, '\n');
+
+ for (unsigned int i = 0; i != lines.size(); i++) {
+ // Lines are spaced at the nominal distance of
+ // 2/5 inventory slot, even if the font doesn't
+ // quite match that. This provides consistent
+ // form layout, at the expense of sometimes
+ // having sub-optimal spacing for the font.
+ // We multiply by 2 and then divide by 5, rather
+ // than multiply by 0.4, to get exact results
+ // in the integer cases: 0.4 is not exactly
+ // representable in binary floating point.
+ s32 posy = pos.Y + ((float)i) * spacing.Y * 2.0 / 5.0;
+ std::wstring wlabel = utf8_to_wide(unescape_string(lines[i]));
+ core::rect<s32> rect = core::rect<s32>(
+ pos.X, posy - m_btn_height,
+ pos.X + m_font->getDimension(wlabel.c_str()).Width,
+ posy + m_btn_height);
+ FieldSpec spec(
+ "",
+ wlabel,
+ L"",
+ 258+m_fields.size()
+ );
+ gui::IGUIStaticText *e =
+ addStaticText(Environment, spec.flabel.c_str(),
+ rect, false, false, this, spec.fid);
+ e->setTextAlignment(gui::EGUIA_UPPERLEFT,
+ gui::EGUIA_CENTER);
+ m_fields.push_back(spec);
+ }
+
+ return;
+ }
+ errorstream<< "Invalid label element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseVertLabel(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 2) ||
+ ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::wstring text = unescape_translate(
+ unescape_string(utf8_to_wide(parts[1])));
+
+ MY_CHECKPOS("vertlabel",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+
+ core::rect<s32> rect = core::rect<s32>(
+ pos.X, pos.Y+((imgsize.Y/2)- m_btn_height),
+ pos.X+15, pos.Y +
+ font_line_height(m_font)
+ * (text.length()+1)
+ +((imgsize.Y/2)- m_btn_height));
+ //actually text.length() would be correct but adding +1 avoids to break all mods
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of label without a size[] element"<<std::endl;
+
+ std::wstring label;
+
+ for (wchar_t i : text) {
+ label += i;
+ label += L"\n";
+ }
+
+ FieldSpec spec(
+ "",
+ label,
+ L"",
+ 258+m_fields.size()
+ );
+ gui::IGUIStaticText *t =
+ addStaticText(Environment, spec.flabel.c_str(), rect, false, false, this, spec.fid);
+ t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream<< "Invalid vertlabel element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseImageButton(parserData* data, const std::string &element,
+ const std::string &type)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((((parts.size() >= 5) && (parts.size() <= 8)) && (parts.size() != 6)) ||
+ ((parts.size() > 8) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string image_name = parts[2];
+ std::string name = parts[3];
+ std::string label = parts[4];
+
+ MY_CHECKPOS("imagebutton",0);
+ MY_CHECKGEOM("imagebutton",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+ v2s32 geom;
+ geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
+ geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
+
+ bool noclip = false;
+ bool drawborder = true;
+ std::string pressed_image_name;
+
+ if (parts.size() >= 7) {
+ if (parts[5] == "true")
+ noclip = true;
+ if (parts[6] == "false")
+ drawborder = false;
+ }
+
+ if (parts.size() >= 8) {
+ pressed_image_name = parts[7];
+ }
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of image_button without a size[] element"<<std::endl;
+
+ image_name = unescape_string(image_name);
+ pressed_image_name = unescape_string(pressed_image_name);
+
+ std::wstring wlabel = utf8_to_wide(unescape_string(label));
+
+ FieldSpec spec(
+ name,
+ wlabel,
+ utf8_to_wide(image_name),
+ 258+m_fields.size()
+ );
+ spec.ftype = f_Button;
+ if(type == "image_button_exit")
+ spec.is_exit = true;
+
+ video::ITexture *texture = 0;
+ video::ITexture *pressed_texture = 0;
+ texture = m_tsrc->getTexture(image_name);
+ if (!pressed_image_name.empty())
+ pressed_texture = m_tsrc->getTexture(pressed_image_name);
+ else
+ pressed_texture = texture;
+
+ gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ e->setUseAlphaChannel(true);
+ e->setImage(guiScalingImageButton(
+ Environment->getVideoDriver(), texture, geom.X, geom.Y));
+ e->setPressedImage(guiScalingImageButton(
+ Environment->getVideoDriver(), pressed_texture, geom.X, geom.Y));
+ e->setScaleImage(true);
+ e->setNotClipped(noclip);
+ e->setDrawBorder(drawborder);
+
+ m_fields.push_back(spec);
+ return;
+ }
+
+ errorstream<< "Invalid imagebutton element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseTabHeader(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 4) || (parts.size() == 6)) ||
+ ((parts.size() > 6) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::string name = parts[1];
+ std::vector<std::string> buttons = split(parts[2],',');
+ std::string str_index = parts[3];
+ bool show_background = true;
+ bool show_border = true;
+ int tab_index = stoi(str_index) -1;
+
+ MY_CHECKPOS("tabheader",0);
+
+ if (parts.size() == 6) {
+ if (parts[4] == "true")
+ show_background = false;
+ if (parts[5] == "false")
+ show_border = false;
+ }
+
+ FieldSpec spec(
+ name,
+ L"",
+ L"",
+ 258+m_fields.size()
+ );
+
+ spec.ftype = f_TabHeader;
+
+ v2s32 pos = pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y - m_btn_height * 2;
+ v2s32 geom;
+ geom.X = DesiredRect.getWidth();
+ geom.Y = m_btn_height*2;
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X,
+ pos.Y+geom.Y);
+
+ gui::IGUITabControl *e = Environment->addTabControl(rect, this,
+ show_background, show_border, spec.fid);
+ e->setAlignment(irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_UPPERLEFT,
+ irr::gui::EGUIA_UPPERLEFT, irr::gui::EGUIA_LOWERRIGHT);
+ e->setTabHeight(m_btn_height*2);
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ e->setNotClipped(true);
+
+ for (const std::string &button : buttons) {
+ e->addTab(unescape_translate(unescape_string(
+ utf8_to_wide(button))).c_str(), -1);
+ }
+
+ if ((tab_index >= 0) &&
+ (buttons.size() < INT_MAX) &&
+ (tab_index < (int) buttons.size()))
+ e->setActiveTab(tab_index);
+
+ m_fields.push_back(spec);
+ return;
+ }
+ errorstream << "Invalid TabHeader element(" << parts.size() << "): '"
+ << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseItemImageButton(parserData* data, const std::string &element)
+{
+
+ if (m_client == 0) {
+ warningstream << "invalid use of item_image_button with m_client==0"
+ << std::endl;
+ return;
+ }
+
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 5) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+ std::string item_name = parts[2];
+ std::string name = parts[3];
+ std::string label = parts[4];
+
+ label = unescape_string(label);
+ item_name = unescape_string(item_name);
+
+ MY_CHECKPOS("itemimagebutton",0);
+ MY_CHECKGEOM("itemimagebutton",1);
+
+ v2s32 pos = padding + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float)spacing.X;
+ pos.Y += stof(v_pos[1]) * (float)spacing.Y;
+ v2s32 geom;
+ geom.X = (stof(v_geom[0]) * (float)spacing.X)-(spacing.X-imgsize.X);
+ geom.Y = (stof(v_geom[1]) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
+
+ core::rect<s32> rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
+
+ if(!data->explicit_size)
+ warningstream<<"invalid use of item_image_button without a size[] element"<<std::endl;
+
+ IItemDefManager *idef = m_client->idef();
+ ItemStack item;
+ item.deSerialize(item_name, idef);
+
+ m_tooltips[name] =
+ TooltipSpec(utf8_to_wide(item.getDefinition(idef).description),
+ m_default_tooltip_bgcolor,
+ m_default_tooltip_color);
+
+ FieldSpec spec(
+ name,
+ utf8_to_wide(label),
+ utf8_to_wide(item_name),
+ 258 + m_fields.size()
+ );
+
+ gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, L"");
+
+ if (spec.fname == data->focused_fieldname) {
+ Environment->setFocus(e);
+ }
+
+ spec.ftype = f_Button;
+ rect+=data->basepos-padding;
+ spec.rect=rect;
+ m_fields.push_back(spec);
+ pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+ m_itemimages.emplace_back("", item_name, e, pos, geom);
+ m_static_texts.emplace_back(utf8_to_wide(label), rect, e);
+ return;
+ }
+ errorstream<< "Invalid ItemImagebutton element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseBox(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if ((parts.size() == 3) ||
+ ((parts.size() > 3) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ std::vector<std::string> v_pos = split(parts[0],',');
+ std::vector<std::string> v_geom = split(parts[1],',');
+
+ MY_CHECKPOS("box",0);
+ MY_CHECKGEOM("box",1);
+
+ v2s32 pos = padding + AbsoluteRect.UpperLeftCorner + pos_offset * spacing;
+ pos.X += stof(v_pos[0]) * (float) spacing.X;
+ pos.Y += stof(v_pos[1]) * (float) spacing.Y;
+
+ v2s32 geom;
+ geom.X = stof(v_geom[0]) * (float)spacing.X;
+ geom.Y = stof(v_geom[1]) * (float)spacing.Y;
+
+ video::SColor tmp_color;
+
+ if (parseColorString(parts[2], tmp_color, false)) {
+ BoxDrawSpec spec(pos, geom, tmp_color);
+
+ m_boxes.push_back(spec);
+ }
+ else {
+ errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "' INVALID COLOR" << std::endl;
+ }
+ return;
+ }
+ errorstream<< "Invalid Box element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseBackgroundColor(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 1) || (parts.size() == 2)) ||
+ ((parts.size() > 2) && (m_formspec_version > FORMSPEC_API_VERSION))) {
+ parseColorString(parts[0], m_bgcolor, false);
+
+ if (parts.size() == 2) {
+ std::string fullscreen = parts[1];
+ m_bgfullscreen = is_yes(fullscreen);
+ }
+
+ return;
+ }
+
+ errorstream << "Invalid bgcolor element(" << parts.size() << "): '" << element << "'"
+ << std::endl;
+}
+
+void GUIFormSpecMenu::parseListColors(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+
+ if (((parts.size() == 2) || (parts.size() == 3) || (parts.size() == 5)) ||
+ ((parts.size() > 5) && (m_formspec_version > FORMSPEC_API_VERSION)))
+ {
+ parseColorString(parts[0], m_slotbg_n, false);
+ parseColorString(parts[1], m_slotbg_h, false);
+
+ if (parts.size() >= 3) {
+ if (parseColorString(parts[2], m_slotbordercolor, false)) {
+ m_slotborder = true;
+ }
+ }
+ if (parts.size() == 5) {
+ video::SColor tmp_color;
+
+ if (parseColorString(parts[3], tmp_color, false))
+ m_default_tooltip_bgcolor = tmp_color;
+ if (parseColorString(parts[4], tmp_color, false))
+ m_default_tooltip_color = tmp_color;
+ }
+ return;
+ }
+ errorstream<< "Invalid listcolors element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseTooltip(parserData* data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element,';');
+ if (parts.size() == 2) {
+ std::string name = parts[0];
+ m_tooltips[name] = TooltipSpec(utf8_to_wide(unescape_string(parts[1])),
+ m_default_tooltip_bgcolor, m_default_tooltip_color);
+ return;
+ }
+
+ if (parts.size() == 4) {
+ std::string name = parts[0];
+ video::SColor tmp_color1, tmp_color2;
+ if ( parseColorString(parts[2], tmp_color1, false) && parseColorString(parts[3], tmp_color2, false) ) {
+ m_tooltips[name] = TooltipSpec(utf8_to_wide(unescape_string(parts[1])),
+ tmp_color1, tmp_color2);
+ return;
+ }
+ }
+ errorstream<< "Invalid tooltip element(" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+bool GUIFormSpecMenu::parseVersionDirect(const std::string &data)
+{
+ //some prechecks
+ if (data.empty())
+ return false;
+
+ std::vector<std::string> parts = split(data,'[');
+
+ if (parts.size() < 2) {
+ return false;
+ }
+
+ if (parts[0] != "formspec_version") {
+ return false;
+ }
+
+ if (is_number(parts[1])) {
+ m_formspec_version = mystoi(parts[1]);
+ return true;
+ }
+
+ return false;
+}
+
+bool GUIFormSpecMenu::parseSizeDirect(parserData* data, const std::string &element)
+{
+ if (element.empty())
+ return false;
+
+ std::vector<std::string> parts = split(element,'[');
+
+ if (parts.size() < 2)
+ return false;
+
+ std::string type = trim(parts[0]);
+ std::string description = trim(parts[1]);
+
+ if (type != "size" && type != "invsize")
+ return false;
+
+ if (type == "invsize")
+ log_deprecated("Deprecated formspec element \"invsize\" is used");
+
+ parseSize(data, description);
+
+ return true;
+}
+
+bool GUIFormSpecMenu::parsePositionDirect(parserData *data, const std::string &element)
+{
+ if (element.empty())
+ return false;
+
+ std::vector<std::string> parts = split(element, '[');
+
+ if (parts.size() != 2)
+ return false;
+
+ std::string type = trim(parts[0]);
+ std::string description = trim(parts[1]);
+
+ if (type != "position")
+ return false;
+
+ parsePosition(data, description);
+
+ return true;
+}
+
+void GUIFormSpecMenu::parsePosition(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ',');
+
+ if (parts.size() == 2) {
+ data->offset.X = stof(parts[0]);
+ data->offset.Y = stof(parts[1]);
+ return;
+ }
+
+ errorstream << "Invalid position element (" << parts.size() << "): '" << element << "'" << std::endl;
+}
+
+bool GUIFormSpecMenu::parseAnchorDirect(parserData *data, const std::string &element)
+{
+ if (element.empty())
+ return false;
+
+ std::vector<std::string> parts = split(element, '[');
+
+ if (parts.size() != 2)
+ return false;
+
+ std::string type = trim(parts[0]);
+ std::string description = trim(parts[1]);
+
+ if (type != "anchor")
+ return false;
+
+ parseAnchor(data, description);
+
+ return true;
+}
+
+void GUIFormSpecMenu::parseAnchor(parserData *data, const std::string &element)
+{
+ std::vector<std::string> parts = split(element, ',');
+
+ if (parts.size() == 2) {
+ data->anchor.X = stof(parts[0]);
+ data->anchor.Y = stof(parts[1]);
+ return;
+ }
+
+ errorstream << "Invalid anchor element (" << parts.size() << "): '" << element
+ << "'" << std::endl;
+}
+
+void GUIFormSpecMenu::parseElement(parserData* data, const std::string &element)
+{
+ //some prechecks
+ if (element.empty())
+ return;
+
+ std::vector<std::string> parts = split(element,'[');
+
+ // ugly workaround to keep compatibility
+ if (parts.size() > 2) {
+ if (trim(parts[0]) == "image") {
+ for (unsigned int i=2;i< parts.size(); i++) {
+ parts[1] += "[" + parts[i];
+ }
+ }
+ else { return; }
+ }
+
+ if (parts.size() < 2) {
+ return;
+ }
+
+ std::string type = trim(parts[0]);
+ std::string description = trim(parts[1]);
+
+ if (type == "container") {
+ parseContainer(data, description);
+ return;
+ }
+
+ if (type == "container_end") {
+ parseContainerEnd(data);
+ return;
+ }
+
+ if (type == "list") {
+ parseList(data, description);
+ return;
+ }
+
+ if (type == "listring") {
+ parseListRing(data, description);
+ return;
+ }
+
+ if (type == "checkbox") {
+ parseCheckbox(data, description);
+ return;
+ }
+
+ if (type == "image") {
+ parseImage(data, description);
+ return;
+ }
+
+ if (type == "item_image") {
+ parseItemImage(data, description);
+ return;
+ }
+
+ if (type == "button" || type == "button_exit") {
+ parseButton(data, description, type);
+ return;
+ }
+
+ if (type == "background") {
+ parseBackground(data,description);
+ return;
+ }
+
+ if (type == "tableoptions"){
+ parseTableOptions(data,description);
+ return;
+ }
+
+ if (type == "tablecolumns"){
+ parseTableColumns(data,description);
+ return;
+ }
+
+ if (type == "table"){
+ parseTable(data,description);
+ return;
+ }
+
+ if (type == "textlist"){
+ parseTextList(data,description);
+ return;
+ }
+
+ if (type == "dropdown"){
+ parseDropDown(data,description);
+ return;
+ }
+
+ if (type == "field_close_on_enter") {
+ parseFieldCloseOnEnter(data, description);
+ return;
+ }
+
+ if (type == "pwdfield") {
+ parsePwdField(data,description);
+ return;
+ }
+
+ if ((type == "field") || (type == "textarea")){
+ parseField(data,description,type);
+ return;
+ }
+
+ if (type == "label") {
+ parseLabel(data,description);
+ return;
+ }
+
+ if (type == "vertlabel") {
+ parseVertLabel(data,description);
+ return;
+ }
+
+ if (type == "item_image_button") {
+ parseItemImageButton(data,description);
+ return;
+ }
+
+ if ((type == "image_button") || (type == "image_button_exit")) {
+ parseImageButton(data,description,type);
+ return;
+ }
+
+ if (type == "tabheader") {
+ parseTabHeader(data,description);
+ return;
+ }
+
+ if (type == "box") {
+ parseBox(data,description);
+ return;
+ }
+
+ if (type == "bgcolor") {
+ parseBackgroundColor(data,description);
+ return;
+ }
+
+ if (type == "listcolors") {
+ parseListColors(data,description);
+ return;
+ }
+
+ if (type == "tooltip") {
+ parseTooltip(data,description);
+ return;
+ }
+
+ if (type == "scrollbar") {
+ parseScrollBar(data, description);
+ return;
+ }
+
+ // Ignore others
+ infostream << "Unknown DrawSpec: type=" << type << ", data=\"" << description << "\""
+ << std::endl;
+}
+
+void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
+{
+ /* useless to regenerate without a screensize */
+ if ((screensize.X <= 0) || (screensize.Y <= 0)) {
+ return;
+ }
+
+ parserData mydata;
+
+ //preserve tables
+ for (auto &m_table : m_tables) {
+ std::string tablename = m_table.first.fname;
+ GUITable *table = m_table.second;
+ mydata.table_dyndata[tablename] = table->getDynamicData();
+ }
+
+ //set focus
+ if (!m_focused_element.empty())
+ mydata.focused_fieldname = m_focused_element;
+
+ //preserve focus
+ gui::IGUIElement *focused_element = Environment->getFocus();
+ if (focused_element && focused_element->getParent() == this) {
+ s32 focused_id = focused_element->getID();
+ if (focused_id > 257) {
+ for (const GUIFormSpecMenu::FieldSpec &field : m_fields) {
+ if (field.fid == focused_id) {
+ mydata.focused_fieldname = field.fname;
+ break;
+ }
+ }
+ }
+ }
+
+ // Remove children
+ removeChildren();
+
+ for (auto &table_it : m_tables) {
+ table_it.second->drop();
+ }
+
+ mydata.size= v2s32(100,100);
+ mydata.screensize = screensize;
+ mydata.offset = v2f32(0.5f, 0.5f);
+ mydata.anchor = v2f32(0.5f, 0.5f);
+
+ // Base position of contents of form
+ mydata.basepos = getBasePos();
+
+ /* Convert m_init_draw_spec to m_inventorylists */
+
+ m_inventorylists.clear();
+ m_images.clear();
+ m_backgrounds.clear();
+ m_itemimages.clear();
+ m_tables.clear();
+ m_checkboxes.clear();
+ m_scrollbars.clear();
+ m_fields.clear();
+ m_boxes.clear();
+ m_tooltips.clear();
+ m_inventory_rings.clear();
+ m_static_texts.clear();
+ m_dropdowns.clear();
+
+ m_bgfullscreen = false;
+
+ {
+ v3f formspec_bgcolor = g_settings->getV3F("formspec_default_bg_color");
+ m_bgcolor = video::SColor(
+ (u8) clamp_u8(g_settings->getS32("formspec_default_bg_opacity")),
+ clamp_u8(myround(formspec_bgcolor.X)),
+ clamp_u8(myround(formspec_bgcolor.Y)),
+ clamp_u8(myround(formspec_bgcolor.Z))
+ );
+ }
+
+ {
+ v3f formspec_bgcolor = g_settings->getV3F("formspec_fullscreen_bg_color");
+ m_fullscreen_bgcolor = video::SColor(
+ (u8) clamp_u8(g_settings->getS32("formspec_fullscreen_bg_opacity")),
+ clamp_u8(myround(formspec_bgcolor.X)),
+ clamp_u8(myround(formspec_bgcolor.Y)),
+ clamp_u8(myround(formspec_bgcolor.Z))
+ );
+ }
+
+
+ m_slotbg_n = video::SColor(255,128,128,128);
+ m_slotbg_h = video::SColor(255,192,192,192);
+
+ m_default_tooltip_bgcolor = video::SColor(255,110,130,60);
+ m_default_tooltip_color = video::SColor(255,255,255,255);
+
+ m_slotbordercolor = video::SColor(200,0,0,0);
+ m_slotborder = false;
+
+ // Add tooltip
+ {
+ assert(!m_tooltip_element);
+ // Note: parent != this so that the tooltip isn't clipped by the menu rectangle
+ m_tooltip_element = addStaticText(Environment, L"",core::rect<s32>(0,0,110,18));
+ m_tooltip_element->enableOverrideColor(true);
+ m_tooltip_element->setBackgroundColor(m_default_tooltip_bgcolor);
+ m_tooltip_element->setDrawBackground(true);
+ m_tooltip_element->setDrawBorder(true);
+ m_tooltip_element->setOverrideColor(m_default_tooltip_color);
+ m_tooltip_element->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_CENTER);
+ m_tooltip_element->setWordWrap(false);
+ //we're not parent so no autograb for this one!
+ m_tooltip_element->grab();
+ }
+
+ std::vector<std::string> elements = split(m_formspec_string,']');
+ unsigned int i = 0;
+
+ /* try to read version from first element only */
+ if (!elements.empty()) {
+ if ( parseVersionDirect(elements[0]) ) {
+ i++;
+ }
+ }
+
+ /* we need size first in order to calculate image scale */
+ mydata.explicit_size = false;
+ for (; i< elements.size(); i++) {
+ if (!parseSizeDirect(&mydata, elements[i])) {
+ break;
+ }
+ }
+
+ /* "position" element is always after "size" element if it used */
+ for (; i< elements.size(); i++) {
+ if (!parsePositionDirect(&mydata, elements[i])) {
+ break;
+ }
+ }
+
+ /* "anchor" element is always after "position" (or "size" element) if it used */
+ for (; i< elements.size(); i++) {
+ if (!parseAnchorDirect(&mydata, elements[i])) {
+ break;
+ }
+ }
+
+
+ if (mydata.explicit_size) {
+ // compute scaling for specified form size
+ if (m_lock) {
+ v2u32 current_screensize = RenderingEngine::get_video_driver()->getScreenSize();
+ v2u32 delta = current_screensize - m_lockscreensize;
+
+ if (current_screensize.Y > m_lockscreensize.Y)
+ delta.Y /= 2;
+ else
+ delta.Y = 0;
+
+ if (current_screensize.X > m_lockscreensize.X)
+ delta.X /= 2;
+ else
+ delta.X = 0;
+
+ offset = v2s32(delta.X,delta.Y);
+
+ mydata.screensize = m_lockscreensize;
+ } else {
+ offset = v2s32(0,0);
+ }
+
+ double gui_scaling = g_settings->getFloat("gui_scaling");
+ double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
+
+ double use_imgsize;
+ if (m_lock) {
+ // In fixed-size mode, inventory image size
+ // is 0.53 inch multiplied by the gui_scaling
+ // config parameter. This magic size is chosen
+ // to make the main menu (15.5 inventory images
+ // wide, including border) just fit into the
+ // default window (800 pixels wide) at 96 DPI
+ // and default scaling (1.00).
+ use_imgsize = 0.5555 * screen_dpi * gui_scaling;
+ } else {
+ // In variable-size mode, we prefer to make the
+ // inventory image size 1/15 of screen height,
+ // multiplied by the gui_scaling config parameter.
+ // If the preferred size won't fit the whole
+ // form on the screen, either horizontally or
+ // vertically, then we scale it down to fit.
+ // (The magic numbers in the computation of what
+ // fits arise from the scaling factors in the
+ // following stanza, including the form border,
+ // help text space, and 0.1 inventory slot spare.)
+ // However, a minimum size is also set, that
+ // the image size can't be less than 0.3 inch
+ // multiplied by gui_scaling, even if this means
+ // the form doesn't fit the screen.
+ double prefer_imgsize = mydata.screensize.Y / 15 *
+ gui_scaling;
+ double fitx_imgsize = mydata.screensize.X /
+ ((5.0/4.0) * (0.5 + mydata.invsize.X));
+ double fity_imgsize = mydata.screensize.Y /
+ ((15.0/13.0) * (0.85 * mydata.invsize.Y));
+ double screen_dpi = RenderingEngine::getDisplayDensity() * 96;
+ double min_imgsize = 0.3 * screen_dpi * gui_scaling;
+ use_imgsize = MYMAX(min_imgsize, MYMIN(prefer_imgsize,
+ MYMIN(fitx_imgsize, fity_imgsize)));
+ }
+
+ // Everything else is scaled in proportion to the
+ // inventory image size. The inventory slot spacing
+ // is 5/4 image size horizontally and 15/13 image size
+ // vertically. The padding around the form (incorporating
+ // the border of the outer inventory slots) is 3/8
+ // image size. Font height (baseline to baseline)
+ // is 2/5 vertical inventory slot spacing, and button
+ // half-height is 7/8 of font height.
+ imgsize = v2s32(use_imgsize, use_imgsize);
+ spacing = v2s32(use_imgsize*5.0/4, use_imgsize*15.0/13);
+ padding = v2s32(use_imgsize*3.0/8, use_imgsize*3.0/8);
+ m_btn_height = use_imgsize*15.0/13 * 0.35;
+
+ m_font = g_fontengine->getFont();
+
+ mydata.size = v2s32(
+ padding.X*2+spacing.X*(mydata.invsize.X-1.0)+imgsize.X,
+ padding.Y*2+spacing.Y*(mydata.invsize.Y-1.0)+imgsize.Y + m_btn_height*2.0/3.0
+ );
+ DesiredRect = mydata.rect = core::rect<s32>(
+ (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * (f32)mydata.size.X) + offset.X,
+ (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * (f32)mydata.size.Y) + offset.Y,
+ (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * (f32)mydata.size.X) + offset.X,
+ (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * (f32)mydata.size.Y) + offset.Y
+ );
+ } else {
+ // Non-size[] form must consist only of text fields and
+ // implicit "Proceed" button. Use default font, and
+ // temporary form size which will be recalculated below.
+ m_font = g_fontengine->getFont();
+ m_btn_height = font_line_height(m_font) * 0.875;
+ DesiredRect = core::rect<s32>(
+ (s32)((f32)mydata.screensize.X * mydata.offset.X) - (s32)(mydata.anchor.X * 580.0),
+ (s32)((f32)mydata.screensize.Y * mydata.offset.Y) - (s32)(mydata.anchor.Y * 300.0),
+ (s32)((f32)mydata.screensize.X * mydata.offset.X) + (s32)((1.0 - mydata.anchor.X) * 580.0),
+ (s32)((f32)mydata.screensize.Y * mydata.offset.Y) + (s32)((1.0 - mydata.anchor.Y) * 300.0)
+ );
+ }
+ recalculateAbsolutePosition(false);
+ mydata.basepos = getBasePos();
+ m_tooltip_element->setOverrideFont(m_font);
+
+ gui::IGUISkin* skin = Environment->getSkin();
+ sanity_check(skin);
+ gui::IGUIFont *old_font = skin->getFont();
+ skin->setFont(m_font);
+
+ pos_offset = v2s32();
+ for (; i< elements.size(); i++) {
+ parseElement(&mydata, elements[i]);
+ }
+
+ if (!container_stack.empty()) {
+ errorstream << "Invalid formspec string: container was never closed!"
+ << std::endl;
+ }
+
+ // If there are fields without explicit size[], add a "Proceed"
+ // button and adjust size to fit all the fields.
+ if (!m_fields.empty() && !mydata.explicit_size) {
+ mydata.rect = core::rect<s32>(
+ mydata.screensize.X/2 - 580/2,
+ mydata.screensize.Y/2 - 300/2,
+ mydata.screensize.X/2 + 580/2,
+ mydata.screensize.Y/2 + 240/2+(m_fields.size()*60)
+ );
+ DesiredRect = mydata.rect;
+ recalculateAbsolutePosition(false);
+ mydata.basepos = getBasePos();
+
+ {
+ v2s32 pos = mydata.basepos;
+ pos.Y = ((m_fields.size()+2)*60);
+
+ v2s32 size = DesiredRect.getSize();
+ mydata.rect =
+ core::rect<s32>(size.X/2-70, pos.Y,
+ (size.X/2-70)+140, pos.Y + (m_btn_height*2));
+ const wchar_t *text = wgettext("Proceed");
+ Environment->addButton(mydata.rect, this, 257, text);
+ delete[] text;
+ }
+
+ }
+
+ //set initial focus if parser didn't set it
+ focused_element = Environment->getFocus();
+ if (!focused_element
+ || !isMyChild(focused_element)
+ || focused_element->getType() == gui::EGUIET_TAB_CONTROL)
+ setInitialFocus();
+
+ skin->setFont(old_font);
+}
+
+#ifdef __ANDROID__
+bool GUIFormSpecMenu::getAndroidUIInput()
+{
+ /* no dialog shown */
+ if (m_JavaDialogFieldName == "") {
+ return false;
+ }
+
+ /* still waiting */
+ if (porting::getInputDialogState() == -1) {
+ return true;
+ }
+
+ std::string fieldname = m_JavaDialogFieldName;
+ m_JavaDialogFieldName = "";
+
+ /* no value abort dialog processing */
+ if (porting::getInputDialogState() != 0) {
+ return false;
+ }
+
+ for(std::vector<FieldSpec>::iterator iter = m_fields.begin();
+ iter != m_fields.end(); ++iter) {
+
+ if (iter->fname != fieldname) {
+ continue;
+ }
+ IGUIElement* tochange = getElementFromId(iter->fid);
+
+ if (tochange == 0) {
+ return false;
+ }
+
+ if (tochange->getType() != irr::gui::EGUIET_EDIT_BOX) {
+ return false;
+ }
+
+ std::string text = porting::getInputDialogValue();
+
+ ((gui::IGUIEditBox*) tochange)->
+ setText(utf8_to_wide(text).c_str());
+ }
+ return false;
+}
+#endif
+
+GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
+{
+ core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
+
+ for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
+ for(s32 i=0; i<s.geom.X*s.geom.Y; i++) {
+ s32 item_i = i + s.start_item_i;
+ s32 x = (i%s.geom.X) * spacing.X;
+ s32 y = (i/s.geom.X) * spacing.Y;
+ v2s32 p0(x,y);
+ core::rect<s32> rect = imgrect + s.pos + p0;
+ if(rect.isPointInside(p))
+ {
+ return ItemSpec(s.inventoryloc, s.listname, item_i);
+ }
+ }
+ }
+
+ return ItemSpec(InventoryLocation(), "", -1);
+}
+
+void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase,
+ bool &item_hovered)
+{
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+
+ Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
+ if(!inv){
+ warningstream<<"GUIFormSpecMenu::drawList(): "
+ <<"The inventory location "
+ <<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
+ <<std::endl;
+ return;
+ }
+ InventoryList *ilist = inv->getList(s.listname);
+ if(!ilist){
+ warningstream<<"GUIFormSpecMenu::drawList(): "
+ <<"The inventory list \""<<s.listname<<"\" @ \""
+ <<s.inventoryloc.dump()<<"\" doesn't exist"
+ <<std::endl;
+ return;
+ }
+
+ core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
+
+ for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
+ {
+ s32 item_i = i + s.start_item_i;
+ if(item_i >= (s32) ilist->getSize())
+ break;
+ s32 x = (i%s.geom.X) * spacing.X;
+ s32 y = (i/s.geom.X) * spacing.Y;
+ v2s32 p(x,y);
+ core::rect<s32> rect = imgrect + s.pos + p;
+ ItemStack item;
+ if(ilist)
+ item = ilist->getItem(item_i);
+
+ bool selected = m_selected_item
+ && m_invmgr->getInventory(m_selected_item->inventoryloc) == inv
+ && m_selected_item->listname == s.listname
+ && m_selected_item->i == item_i;
+ bool hovering = rect.isPointInside(m_pointer);
+ ItemRotationKind rotation_kind = selected ? IT_ROT_SELECTED :
+ (hovering ? IT_ROT_HOVERED : IT_ROT_NONE);
+
+ if (phase == 0) {
+ if (hovering) {
+ item_hovered = true;
+ driver->draw2DRectangle(m_slotbg_h, rect, &AbsoluteClippingRect);
+ } else {
+ driver->draw2DRectangle(m_slotbg_n, rect, &AbsoluteClippingRect);
+ }
+ }
+
+ //Draw inv slot borders
+ if (m_slotborder) {
+ s32 x1 = rect.UpperLeftCorner.X;
+ s32 y1 = rect.UpperLeftCorner.Y;
+ s32 x2 = rect.LowerRightCorner.X;
+ s32 y2 = rect.LowerRightCorner.Y;
+ s32 border = 1;
+ driver->draw2DRectangle(m_slotbordercolor,
+ core::rect<s32>(v2s32(x1 - border, y1 - border),
+ v2s32(x2 + border, y1)), NULL);
+ driver->draw2DRectangle(m_slotbordercolor,
+ core::rect<s32>(v2s32(x1 - border, y2),
+ v2s32(x2 + border, y2 + border)), NULL);
+ driver->draw2DRectangle(m_slotbordercolor,
+ core::rect<s32>(v2s32(x1 - border, y1),
+ v2s32(x1, y2)), NULL);
+ driver->draw2DRectangle(m_slotbordercolor,
+ core::rect<s32>(v2s32(x2, y1),
+ v2s32(x2 + border, y2)), NULL);
+ }
+
+ if(phase == 1)
+ {
+ // Draw item stack
+ if(selected)
+ {
+ item.takeItem(m_selected_amount);
+ }
+ if(!item.empty())
+ {
+ drawItemStack(driver, m_font, item,
+ rect, &AbsoluteClippingRect, m_client,
+ rotation_kind);
+ }
+
+ // Draw tooltip
+ std::wstring tooltip_text;
+ if (hovering && !m_selected_item) {
+ const std::string &desc = item.metadata.getString("description");
+ if (desc.empty())
+ tooltip_text =
+ utf8_to_wide(item.getDefinition(m_client->idef()).description);
+ else
+ tooltip_text = utf8_to_wide(desc);
+
+ if (!item.name.empty()) {
+ if (tooltip_text.empty())
+ tooltip_text = utf8_to_wide(item.name);
+ if (m_tooltip_append_itemname)
+ tooltip_text += utf8_to_wide(" [" + item.name + "]");
+ }
+ }
+ if (!tooltip_text.empty()) {
+ showTooltip(tooltip_text, m_default_tooltip_color,
+ m_default_tooltip_bgcolor);
+ }
+ }
+ }
+}
+
+void GUIFormSpecMenu::drawSelectedItem()
+{
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+
+ if (!m_selected_item) {
+ drawItemStack(driver, m_font, ItemStack(),
+ core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
+ NULL, m_client, IT_ROT_DRAGGED);
+ return;
+ }
+
+ Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
+ sanity_check(inv);
+ InventoryList *list = inv->getList(m_selected_item->listname);
+ sanity_check(list);
+ ItemStack stack = list->getItem(m_selected_item->i);
+ stack.count = m_selected_amount;
+
+ core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
+ core::rect<s32> rect = imgrect + (m_pointer - imgrect.getCenter());
+ rect.constrainTo(driver->getViewPort());
+ drawItemStack(driver, m_font, stack, rect, NULL, m_client, IT_ROT_DRAGGED);
+}
+
+void GUIFormSpecMenu::drawMenu()
+{
+ if (m_form_src) {
+ const std::string &newform = m_form_src->getForm();
+ if (newform != m_formspec_string) {
+ m_formspec_string = newform;
+ regenerateGui(m_screensize_old);
+ }
+ }
+
+ gui::IGUISkin* skin = Environment->getSkin();
+ sanity_check(skin != NULL);
+ gui::IGUIFont *old_font = skin->getFont();
+ skin->setFont(m_font);
+
+ updateSelectedItem();
+
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+
+ v2u32 screenSize = driver->getScreenSize();
+ core::rect<s32> allbg(0, 0, screenSize.X, screenSize.Y);
+
+ if (m_bgfullscreen)
+ driver->draw2DRectangle(m_fullscreen_bgcolor, allbg, &allbg);
+ else
+ driver->draw2DRectangle(m_bgcolor, AbsoluteRect, &AbsoluteClippingRect);
+
+ m_tooltip_element->setVisible(false);
+
+ /*
+ Draw backgrounds
+ */
+ for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_backgrounds) {
+ video::ITexture *texture = m_tsrc->getTexture(spec.name);
+
+ if (texture != 0) {
+ // Image size on screen
+ core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
+ // Image rectangle on screen
+ core::rect<s32> rect = imgrect + spec.pos;
+
+ if (spec.clip) {
+ core::dimension2d<s32> absrec_size = AbsoluteRect.getSize();
+ rect = core::rect<s32>(AbsoluteRect.UpperLeftCorner.X - spec.pos.X,
+ AbsoluteRect.UpperLeftCorner.Y - spec.pos.Y,
+ AbsoluteRect.UpperLeftCorner.X + absrec_size.Width + spec.pos.X,
+ AbsoluteRect.UpperLeftCorner.Y + absrec_size.Height + spec.pos.Y);
+ }
+
+ const video::SColor color(255,255,255,255);
+ const video::SColor colors[] = {color,color,color,color};
+ draw2DImageFilterScaled(driver, texture, rect,
+ core::rect<s32>(core::position2d<s32>(0,0),
+ core::dimension2di(texture->getOriginalSize())),
+ NULL/*&AbsoluteClippingRect*/, colors, true);
+ } else {
+ errorstream << "GUIFormSpecMenu::drawMenu() Draw backgrounds unable to load texture:" << std::endl;
+ errorstream << "\t" << spec.name << std::endl;
+ }
+ }
+
+ /*
+ Draw Boxes
+ */
+ for (const GUIFormSpecMenu::BoxDrawSpec &spec : m_boxes) {
+ irr::video::SColor todraw = spec.color;
+
+ todraw.setAlpha(140);
+
+ core::rect<s32> rect(spec.pos.X,spec.pos.Y,
+ spec.pos.X + spec.geom.X,spec.pos.Y + spec.geom.Y);
+
+ driver->draw2DRectangle(todraw, rect, 0);
+ }
+
+ /*
+ Call base class
+ */
+ gui::IGUIElement::draw();
+
+ /*
+ Draw images
+ */
+ for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_images) {
+ video::ITexture *texture = m_tsrc->getTexture(spec.name);
+
+ if (texture != 0) {
+ const core::dimension2d<u32>& img_origsize = texture->getOriginalSize();
+ // Image size on screen
+ core::rect<s32> imgrect;
+
+ if (spec.scale)
+ imgrect = core::rect<s32>(0,0,spec.geom.X, spec.geom.Y);
+ else {
+
+ imgrect = core::rect<s32>(0,0,img_origsize.Width,img_origsize.Height);
+ }
+ // Image rectangle on screen
+ core::rect<s32> rect = imgrect + spec.pos;
+ const video::SColor color(255,255,255,255);
+ const video::SColor colors[] = {color,color,color,color};
+ draw2DImageFilterScaled(driver, texture, rect,
+ core::rect<s32>(core::position2d<s32>(0,0),img_origsize),
+ NULL/*&AbsoluteClippingRect*/, colors, true);
+ }
+ else {
+ errorstream << "GUIFormSpecMenu::drawMenu() Draw images unable to load texture:" << std::endl;
+ errorstream << "\t" << spec.name << std::endl;
+ }
+ }
+
+ /*
+ Draw item images
+ */
+ for (const GUIFormSpecMenu::ImageDrawSpec &spec : m_itemimages) {
+ if (m_client == 0)
+ break;
+
+ IItemDefManager *idef = m_client->idef();
+ ItemStack item;
+ item.deSerialize(spec.item_name, idef);
+ core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
+ // Viewport rectangle on screen
+ core::rect<s32> rect = imgrect + spec.pos;
+ if (spec.parent_button && spec.parent_button->isPressed()) {
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
+ rect += core::dimension2d<s32>(
+ 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight());
+#else
+ rect += core::dimension2d<s32>(
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y));
+#endif
+ }
+ drawItemStack(driver, m_font, item, rect, &AbsoluteClippingRect,
+ m_client, IT_ROT_NONE);
+ }
+
+ /*
+ Draw items
+ Phase 0: Item slot rectangles
+ Phase 1: Item images; prepare tooltip
+ */
+ bool item_hovered = false;
+ int start_phase = 0;
+ for (int phase = start_phase; phase <= 1; phase++) {
+ for (const GUIFormSpecMenu::ListDrawSpec &spec : m_inventorylists) {
+ drawList(spec, phase, item_hovered);
+ }
+ }
+ if (!item_hovered) {
+ drawItemStack(driver, m_font, ItemStack(),
+ core::rect<s32>(v2s32(0, 0), v2s32(0, 0)),
+ NULL, m_client, IT_ROT_HOVERED);
+ }
+
+/* TODO find way to show tooltips on touchscreen */
+#ifndef HAVE_TOUCHSCREENGUI
+ m_pointer = RenderingEngine::get_raw_device()->getCursorControl()->getPosition();
+#endif
+
+ /*
+ Draw static text elements
+ */
+ for (const GUIFormSpecMenu::StaticTextSpec &spec : m_static_texts) {
+ core::rect<s32> rect = spec.rect;
+ if (spec.parent_button && spec.parent_button->isPressed()) {
+#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
+ rect += core::dimension2d<s32>(
+ 0.05 * (float)rect.getWidth(), 0.05 * (float)rect.getHeight());
+#else
+ // Use image offset instead of text's because its a bit smaller
+ // and fits better, also TEXT_OFFSET_X is always 0
+ rect += core::dimension2d<s32>(
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_X),
+ skin->getSize(irr::gui::EGDS_BUTTON_PRESSED_IMAGE_OFFSET_Y));
+#endif
+ }
+ video::SColor color(255, 255, 255, 255);
+ m_font->draw(spec.text.c_str(), rect, color, true, true, &rect);
+ }
+
+ /*
+ Draw fields/buttons tooltips
+ */
+ gui::IGUIElement *hovered =
+ Environment->getRootGUIElement()->getElementFromPoint(m_pointer);
+
+ if (hovered != NULL) {
+ s32 id = hovered->getID();
+
+ u64 delta = 0;
+ if (id == -1) {
+ m_old_tooltip_id = id;
+ } else {
+ if (id == m_old_tooltip_id) {
+ delta = porting::getDeltaMs(m_hovered_time, porting::getTimeMs());
+ } else {
+ m_hovered_time = porting::getTimeMs();
+ m_old_tooltip_id = id;
+ }
+ }
+
+ // Find and update the current tooltip
+ if (id != -1 && delta >= m_tooltip_show_delay) {
+ for (const FieldSpec &field : m_fields) {
+
+ if (field.fid != id)
+ continue;
+
+ const std::wstring &text = m_tooltips[field.fname].tooltip;
+ if (!text.empty())
+ showTooltip(text, m_tooltips[field.fname].color,
+ m_tooltips[field.fname].bgcolor);
+
+ break;
+ }
+ }
+ }
+
+ m_tooltip_element->draw();
+
+ /*
+ Draw dragged item stack
+ */
+ drawSelectedItem();
+
+ skin->setFont(old_font);
+}
+
+
+void GUIFormSpecMenu::showTooltip(const std::wstring &text,
+ const irr::video::SColor &color, const irr::video::SColor &bgcolor)
+{
+ const std::wstring ntext = translate_string(text);
+ m_tooltip_element->setOverrideColor(color);
+ m_tooltip_element->setBackgroundColor(bgcolor);
+ setStaticText(m_tooltip_element, ntext.c_str());
+
+ // Tooltip size and offset
+ s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
+#if (IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 8 && IRRLICHT_VERSION_REVISION < 2) || USE_FREETYPE == 1
+ std::vector<std::wstring> text_rows = str_split(ntext, L'\n');
+ s32 tooltip_height = m_tooltip_element->getTextHeight() * text_rows.size() + 5;
+#else
+ s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
+#endif
+ v2u32 screenSize = Environment->getVideoDriver()->getScreenSize();
+ int tooltip_offset_x = m_btn_height;
+ int tooltip_offset_y = m_btn_height;
+#ifdef __ANDROID__
+ tooltip_offset_x *= 3;
+ tooltip_offset_y = 0;
+ if (m_pointer.X > (s32)screenSize.X / 2)
+ tooltip_offset_x = -(tooltip_offset_x + tooltip_width);
+#endif
+
+ // Calculate and set the tooltip position
+ s32 tooltip_x = m_pointer.X + tooltip_offset_x;
+ s32 tooltip_y = m_pointer.Y + tooltip_offset_y;
+ if (tooltip_x + tooltip_width > (s32)screenSize.X)
+ tooltip_x = (s32)screenSize.X - tooltip_width - m_btn_height;
+ if (tooltip_y + tooltip_height > (s32)screenSize.Y)
+ tooltip_y = (s32)screenSize.Y - tooltip_height - m_btn_height;
+
+ m_tooltip_element->setRelativePosition(
+ core::rect<s32>(
+ core::position2d<s32>(tooltip_x, tooltip_y),
+ core::dimension2d<s32>(tooltip_width, tooltip_height)
+ )
+ );
+
+ // Display the tooltip
+ m_tooltip_element->setVisible(true);
+ bringToFront(m_tooltip_element);
+}
+
+void GUIFormSpecMenu::updateSelectedItem()
+{
+ // If the selected stack has become empty for some reason, deselect it.
+ // If the selected stack has become inaccessible, deselect it.
+ // If the selected stack has become smaller, adjust m_selected_amount.
+ ItemStack selected = verifySelectedItem();
+
+ // WARNING: BLACK MAGIC
+ // See if there is a stack suited for our current guess.
+ // If such stack does not exist, clear the guess.
+ if (!m_selected_content_guess.name.empty() &&
+ selected.name == m_selected_content_guess.name &&
+ selected.count == m_selected_content_guess.count){
+ // Selected item fits the guess. Skip the black magic.
+ } else if (!m_selected_content_guess.name.empty()) {
+ bool found = false;
+ for(u32 i=0; i<m_inventorylists.size() && !found; i++){
+ const ListDrawSpec &s = m_inventorylists[i];
+ Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
+ if(!inv)
+ continue;
+ InventoryList *list = inv->getList(s.listname);
+ if(!list)
+ continue;
+ for(s32 i=0; i<s.geom.X*s.geom.Y && !found; i++){
+ u32 item_i = i + s.start_item_i;
+ if(item_i >= list->getSize())
+ continue;
+ ItemStack stack = list->getItem(item_i);
+ if(stack.name == m_selected_content_guess.name &&
+ stack.count == m_selected_content_guess.count){
+ found = true;
+ infostream<<"Client: Changing selected content guess to "
+ <<s.inventoryloc.dump()<<" "<<s.listname
+ <<" "<<item_i<<std::endl;
+ delete m_selected_item;
+ m_selected_item = new ItemSpec(s.inventoryloc, s.listname, item_i);
+ m_selected_amount = stack.count;
+ }
+ }
+ }
+ if(!found){
+ infostream<<"Client: Discarding selected content guess: "
+ <<m_selected_content_guess.getItemString()<<std::endl;
+ m_selected_content_guess.name = "";
+ }
+ }
+
+ // If craftresult is nonempty and nothing else is selected, select it now.
+ if(!m_selected_item)
+ {
+ for (const GUIFormSpecMenu::ListDrawSpec &s : m_inventorylists) {
+ if(s.listname == "craftpreview")
+ {
+ Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
+ InventoryList *list = inv->getList("craftresult");
+ if(list && list->getSize() >= 1 && !list->getItem(0).empty())
+ {
+ m_selected_item = new ItemSpec;
+ m_selected_item->inventoryloc = s.inventoryloc;
+ m_selected_item->listname = "craftresult";
+ m_selected_item->i = 0;
+ m_selected_amount = 0;
+ m_selected_dragging = false;
+ break;
+ }
+ }
+ }
+ }
+
+ // If craftresult is selected, keep the whole stack selected
+ if(m_selected_item && m_selected_item->listname == "craftresult")
+ {
+ m_selected_amount = verifySelectedItem().count;
+ }
+}
+
+ItemStack GUIFormSpecMenu::verifySelectedItem()
+{
+ // If the selected stack has become empty for some reason, deselect it.
+ // If the selected stack has become inaccessible, deselect it.
+ // If the selected stack has become smaller, adjust m_selected_amount.
+ // Return the selected stack.
+
+ if(m_selected_item)
+ {
+ if(m_selected_item->isValid())
+ {
+ Inventory *inv = m_invmgr->getInventory(m_selected_item->inventoryloc);
+ if(inv)
+ {
+ InventoryList *list = inv->getList(m_selected_item->listname);
+ if(list && (u32) m_selected_item->i < list->getSize())
+ {
+ ItemStack stack = list->getItem(m_selected_item->i);
+ if(m_selected_amount > stack.count)
+ m_selected_amount = stack.count;
+ if(!stack.empty())
+ return stack;
+ }
+ }
+ }
+
+ // selection was not valid
+ delete m_selected_item;
+ m_selected_item = NULL;
+ m_selected_amount = 0;
+ m_selected_dragging = false;
+ }
+ return ItemStack();
+}
+
+void GUIFormSpecMenu::acceptInput(FormspecQuitMode quitmode=quit_mode_no)
+{
+ if(m_text_dst)
+ {
+ StringMap fields;
+
+ if (quitmode == quit_mode_accept) {
+ fields["quit"] = "true";
+ }
+
+ if (quitmode == quit_mode_cancel) {
+ fields["quit"] = "true";
+ m_text_dst->gotText(fields);
+ return;
+ }
+
+ if (current_keys_pending.key_down) {
+ fields["key_down"] = "true";
+ current_keys_pending.key_down = false;
+ }
+
+ if (current_keys_pending.key_up) {
+ fields["key_up"] = "true";
+ current_keys_pending.key_up = false;
+ }
+
+ if (current_keys_pending.key_enter) {
+ fields["key_enter"] = "true";
+ current_keys_pending.key_enter = false;
+ }
+
+ if (!current_field_enter_pending.empty()) {
+ fields["key_enter_field"] = current_field_enter_pending;
+ current_field_enter_pending = "";
+ }
+
+ if (current_keys_pending.key_escape) {
+ fields["key_escape"] = "true";
+ current_keys_pending.key_escape = false;
+ }
+
+ for (const GUIFormSpecMenu::FieldSpec &s : m_fields) {
+ if(s.send) {
+ std::string name = s.fname;
+ if (s.ftype == f_Button) {
+ fields[name] = wide_to_utf8(s.flabel);
+ } else if (s.ftype == f_Table) {
+ GUITable *table = getTable(s.fname);
+ if (table) {
+ fields[name] = table->checkEvent();
+ }
+ }
+ else if(s.ftype == f_DropDown) {
+ // no dynamic cast possible due to some distributions shipped
+ // without rtti support in irrlicht
+ IGUIElement * element = getElementFromId(s.fid);
+ gui::IGUIComboBox *e = NULL;
+ if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) {
+ e = static_cast<gui::IGUIComboBox*>(element);
+ }
+ s32 selected = e->getSelected();
+ if (selected >= 0) {
+ std::vector<std::string> *dropdown_values =
+ getDropDownValues(s.fname);
+ if (dropdown_values && selected < (s32)dropdown_values->size()) {
+ fields[name] = (*dropdown_values)[selected];
+ }
+ }
+ }
+ else if (s.ftype == f_TabHeader) {
+ // no dynamic cast possible due to some distributions shipped
+ // without rttzi support in irrlicht
+ IGUIElement * element = getElementFromId(s.fid);
+ gui::IGUITabControl *e = NULL;
+ if ((element) && (element->getType() == gui::EGUIET_TAB_CONTROL)) {
+ e = static_cast<gui::IGUITabControl *>(element);
+ }
+
+ if (e != 0) {
+ std::stringstream ss;
+ ss << (e->getActiveTab() +1);
+ fields[name] = ss.str();
+ }
+ }
+ else if (s.ftype == f_CheckBox) {
+ // no dynamic cast possible due to some distributions shipped
+ // without rtti support in irrlicht
+ IGUIElement * element = getElementFromId(s.fid);
+ gui::IGUICheckBox *e = NULL;
+ if ((element) && (element->getType() == gui::EGUIET_CHECK_BOX)) {
+ e = static_cast<gui::IGUICheckBox*>(element);
+ }
+
+ if (e != 0) {
+ if (e->isChecked())
+ fields[name] = "true";
+ else
+ fields[name] = "false";
+ }
+ }
+ else if (s.ftype == f_ScrollBar) {
+ // no dynamic cast possible due to some distributions shipped
+ // without rtti support in irrlicht
+ IGUIElement * element = getElementFromId(s.fid);
+ gui::IGUIScrollBar *e = NULL;
+ if ((element) && (element->getType() == gui::EGUIET_SCROLL_BAR)) {
+ e = static_cast<gui::IGUIScrollBar*>(element);
+ }
+
+ if (e != 0) {
+ std::stringstream os;
+ os << e->getPos();
+ if (s.fdefault == L"Changed")
+ fields[name] = "CHG:" + os.str();
+ else
+ fields[name] = "VAL:" + os.str();
+ }
+ }
+ else
+ {
+ IGUIElement* e = getElementFromId(s.fid);
+ if(e != NULL) {
+ fields[name] = wide_to_utf8(e->getText());
+ }
+ }
+ }
+ }
+
+ m_text_dst->gotText(fields);
+ }
+}
+
+static bool isChild(gui::IGUIElement * tocheck, gui::IGUIElement * parent)
+{
+ while(tocheck != NULL) {
+ if (tocheck == parent) {
+ return true;
+ }
+ tocheck = tocheck->getParent();
+ }
+ return false;
+}
+
+bool GUIFormSpecMenu::preprocessEvent(const SEvent& event)
+{
+ // The IGUITabControl renders visually using the skin's selected
+ // font, which we override for the duration of form drawing,
+ // but computes tab hotspots based on how it would have rendered
+ // using the font that is selected at the time of button release.
+ // To make these two consistent, temporarily override the skin's
+ // font while the IGUITabControl is processing the event.
+ if (event.EventType == EET_MOUSE_INPUT_EVENT &&
+ event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
+ s32 x = event.MouseInput.X;
+ s32 y = event.MouseInput.Y;
+ gui::IGUIElement *hovered =
+ Environment->getRootGUIElement()->getElementFromPoint(
+ core::position2d<s32>(x, y));
+ if (hovered && isMyChild(hovered) &&
+ hovered->getType() == gui::EGUIET_TAB_CONTROL) {
+ gui::IGUISkin* skin = Environment->getSkin();
+ sanity_check(skin != NULL);
+ gui::IGUIFont *old_font = skin->getFont();
+ skin->setFont(m_font);
+ bool retval = hovered->OnEvent(event);
+ skin->setFont(old_font);
+ return retval;
+ }
+ }
+
+ // Fix Esc/Return key being eaten by checkboxen and tables
+ if(event.EventType==EET_KEY_INPUT_EVENT) {
+ KeyPress kp(event.KeyInput);
+ if (kp == EscapeKey || kp == CancelKey
+ || kp == getKeySetting("keymap_inventory")
+ || event.KeyInput.Key==KEY_RETURN) {
+ gui::IGUIElement *focused = Environment->getFocus();
+ if (focused && isMyChild(focused) &&
+ (focused->getType() == gui::EGUIET_LIST_BOX ||
+ focused->getType() == gui::EGUIET_CHECK_BOX)) {
+ OnEvent(event);
+ return true;
+ }
+ }
+ }
+ // Mouse wheel events: send to hovered element instead of focused
+ if(event.EventType==EET_MOUSE_INPUT_EVENT
+ && event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
+ s32 x = event.MouseInput.X;
+ s32 y = event.MouseInput.Y;
+ gui::IGUIElement *hovered =
+ Environment->getRootGUIElement()->getElementFromPoint(
+ core::position2d<s32>(x, y));
+ if (hovered && isMyChild(hovered)) {
+ hovered->OnEvent(event);
+ return true;
+ }
+ }
+
+ if (event.EventType == EET_MOUSE_INPUT_EVENT) {
+ s32 x = event.MouseInput.X;
+ s32 y = event.MouseInput.Y;
+ gui::IGUIElement *hovered =
+ Environment->getRootGUIElement()->getElementFromPoint(
+ core::position2d<s32>(x, y));
+ if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
+ m_old_tooltip_id = -1;
+ }
+ if (!isChild(hovered,this)) {
+ if (DoubleClickDetection(event)) {
+ return true;
+ }
+ }
+ }
+
+ #ifdef __ANDROID__
+ // display software keyboard when clicking edit boxes
+ if (event.EventType == EET_MOUSE_INPUT_EVENT
+ && event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
+ gui::IGUIElement *hovered =
+ Environment->getRootGUIElement()->getElementFromPoint(
+ core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y));
+ if ((hovered) && (hovered->getType() == irr::gui::EGUIET_EDIT_BOX)) {
+ bool retval = hovered->OnEvent(event);
+ if (retval) {
+ Environment->setFocus(hovered);
+ }
+ m_JavaDialogFieldName = getNameByID(hovered->getID());
+ std::string message = gettext("Enter ");
+ std::string label = wide_to_utf8(getLabelByID(hovered->getID()));
+ if (label == "") {
+ label = "text";
+ }
+ message += gettext(label) + ":";
+
+ /* single line text input */
+ int type = 2;
+
+ /* multi line text input */
+ if (((gui::IGUIEditBox*) hovered)->isMultiLineEnabled()) {
+ type = 1;
+ }
+
+ /* passwords are always single line */
+ if (((gui::IGUIEditBox*) hovered)->isPasswordBox()) {
+ type = 3;
+ }
+
+ porting::showInputDialog(gettext("ok"), "",
+ wide_to_utf8(((gui::IGUIEditBox*) hovered)->getText()),
+ type);
+ return retval;
+ }
+ }
+
+ if (event.EventType == EET_TOUCH_INPUT_EVENT)
+ {
+ SEvent translated;
+ memset(&translated, 0, sizeof(SEvent));
+ translated.EventType = EET_MOUSE_INPUT_EVENT;
+ gui::IGUIElement* root = Environment->getRootGUIElement();
+
+ if (!root) {
+ errorstream
+ << "GUIFormSpecMenu::preprocessEvent unable to get root element"
+ << std::endl;
+ return false;
+ }
+ gui::IGUIElement* hovered = root->getElementFromPoint(
+ core::position2d<s32>(
+ event.TouchInput.X,
+ event.TouchInput.Y));
+
+ translated.MouseInput.X = event.TouchInput.X;
+ translated.MouseInput.Y = event.TouchInput.Y;
+ translated.MouseInput.Control = false;
+
+ bool dont_send_event = false;
+
+ if (event.TouchInput.touchedCount == 1) {
+ switch (event.TouchInput.Event) {
+ case ETIE_PRESSED_DOWN:
+ m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y);
+ translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
+ translated.MouseInput.ButtonStates = EMBSM_LEFT;
+ m_down_pos = m_pointer;
+ break;
+ case ETIE_MOVED:
+ m_pointer = v2s32(event.TouchInput.X,event.TouchInput.Y);
+ translated.MouseInput.Event = EMIE_MOUSE_MOVED;
+ translated.MouseInput.ButtonStates = EMBSM_LEFT;
+ break;
+ case ETIE_LEFT_UP:
+ translated.MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
+ translated.MouseInput.ButtonStates = 0;
+ hovered = root->getElementFromPoint(m_down_pos);
+ /* we don't have a valid pointer element use last
+ * known pointer pos */
+ translated.MouseInput.X = m_pointer.X;
+ translated.MouseInput.Y = m_pointer.Y;
+
+ /* reset down pos */
+ m_down_pos = v2s32(0,0);
+ break;
+ default:
+ dont_send_event = true;
+ //this is not supposed to happen
+ errorstream
+ << "GUIFormSpecMenu::preprocessEvent unexpected usecase Event="
+ << event.TouchInput.Event << std::endl;
+ }
+ } else if ( (event.TouchInput.touchedCount == 2) &&
+ (event.TouchInput.Event == ETIE_PRESSED_DOWN) ) {
+ hovered = root->getElementFromPoint(m_down_pos);
+
+ translated.MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
+ translated.MouseInput.ButtonStates = EMBSM_LEFT | EMBSM_RIGHT;
+ translated.MouseInput.X = m_pointer.X;
+ translated.MouseInput.Y = m_pointer.Y;
+
+ if (hovered) {
+ hovered->OnEvent(translated);
+ }
+
+ translated.MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
+ translated.MouseInput.ButtonStates = EMBSM_LEFT;
+
+
+ if (hovered) {
+ hovered->OnEvent(translated);
+ }
+ dont_send_event = true;
+ }
+ /* ignore unhandled 2 touch events ... accidental moving for example */
+ else if (event.TouchInput.touchedCount == 2) {
+ dont_send_event = true;
+ }
+ else if (event.TouchInput.touchedCount > 2) {
+ errorstream
+ << "GUIFormSpecMenu::preprocessEvent to many multitouch events "
+ << event.TouchInput.touchedCount << " ignoring them" << std::endl;
+ }
+
+ if (dont_send_event) {
+ return true;
+ }
+
+ /* check if translated event needs to be preprocessed again */
+ if (preprocessEvent(translated)) {
+ return true;
+ }
+ if (hovered) {
+ grab();
+ bool retval = hovered->OnEvent(translated);
+
+ if (event.TouchInput.Event == ETIE_LEFT_UP) {
+ /* reset pointer */
+ m_pointer = v2s32(0,0);
+ }
+ drop();
+ return retval;
+ }
+ }
+ #endif
+
+ if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT) {
+ /* TODO add a check like:
+ if (event.JoystickEvent != joystick_we_listen_for)
+ return false;
+ */
+ bool handled = m_joystick->handleEvent(event.JoystickEvent);
+ if (handled) {
+ if (m_joystick->wasKeyDown(KeyType::ESC)) {
+ tryClose();
+ } else if (m_joystick->wasKeyDown(KeyType::JUMP)) {
+ if (m_allowclose) {
+ acceptInput(quit_mode_accept);
+ quitMenu();
+ }
+ }
+ }
+ return handled;
+ }
+
+ return false;
+}
+
+/******************************************************************************/
+bool GUIFormSpecMenu::DoubleClickDetection(const SEvent event)
+{
+ /* The following code is for capturing double-clicks of the mouse button
+ * and translating the double-click into an EET_KEY_INPUT_EVENT event
+ * -- which closes the form -- under some circumstances.
+ *
+ * There have been many github issues reporting this as a bug even though it
+ * was an intended feature. For this reason, remapping the double-click as
+ * an ESC must be explicitly set when creating this class via the
+ * /p remap_dbl_click parameter of the constructor.
+ */
+
+ if (!m_remap_dbl_click)
+ return false;
+
+ if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
+ m_doubleclickdetect[0].pos = m_doubleclickdetect[1].pos;
+ m_doubleclickdetect[0].time = m_doubleclickdetect[1].time;
+
+ m_doubleclickdetect[1].pos = m_pointer;
+ m_doubleclickdetect[1].time = porting::getTimeMs();
+ }
+ else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
+ u64 delta = porting::getDeltaMs(m_doubleclickdetect[0].time, porting::getTimeMs());
+ if (delta > 400) {
+ return false;
+ }
+
+ double squaredistance =
+ m_doubleclickdetect[0].pos
+ .getDistanceFromSQ(m_doubleclickdetect[1].pos);
+
+ if (squaredistance > (30*30)) {
+ return false;
+ }
+
+ SEvent* translated = new SEvent();
+ assert(translated != 0);
+ //translate doubleclick to escape
+ memset(translated, 0, sizeof(SEvent));
+ translated->EventType = irr::EET_KEY_INPUT_EVENT;
+ translated->KeyInput.Key = KEY_ESCAPE;
+ translated->KeyInput.Control = false;
+ translated->KeyInput.Shift = false;
+ translated->KeyInput.PressedDown = true;
+ translated->KeyInput.Char = 0;
+ OnEvent(*translated);
+
+ // no need to send the key up event as we're already deleted
+ // and no one else did notice this event
+ delete translated;
+ return true;
+ }
+
+ return false;
+}
+
+void GUIFormSpecMenu::tryClose()
+{
+ if (m_allowclose) {
+ doPause = false;
+ acceptInput(quit_mode_cancel);
+ quitMenu();
+ } else {
+ m_text_dst->gotText(L"MenuQuit");
+ }
+}
+
+bool GUIFormSpecMenu::OnEvent(const SEvent& event)
+{
+ if (event.EventType==EET_KEY_INPUT_EVENT) {
+ KeyPress kp(event.KeyInput);
+ if (event.KeyInput.PressedDown && (
+ (kp == EscapeKey) || (kp == CancelKey) ||
+ ((m_client != NULL) && (kp == getKeySetting("keymap_inventory"))))) {
+ tryClose();
+ return true;
+ }
+
+ if (m_client != NULL && event.KeyInput.PressedDown &&
+ (kp == getKeySetting("keymap_screenshot"))) {
+ m_client->makeScreenshot();
+ }
+ if (event.KeyInput.PressedDown &&
+ (event.KeyInput.Key==KEY_RETURN ||
+ event.KeyInput.Key==KEY_UP ||
+ event.KeyInput.Key==KEY_DOWN)
+ ) {
+ switch (event.KeyInput.Key) {
+ case KEY_RETURN:
+ current_keys_pending.key_enter = true;
+ break;
+ case KEY_UP:
+ current_keys_pending.key_up = true;
+ break;
+ case KEY_DOWN:
+ current_keys_pending.key_down = true;
+ break;
+ break;
+ default:
+ //can't happen at all!
+ FATAL_ERROR("Reached a source line that can't ever been reached");
+ break;
+ }
+ if (current_keys_pending.key_enter && m_allowclose) {
+ acceptInput(quit_mode_accept);
+ quitMenu();
+ } else {
+ acceptInput();
+ }
+ return true;
+ }
+
+ }
+
+ /* Mouse event other than movement, or crossing the border of inventory
+ field while holding right mouse button
+ */
+ if (event.EventType == EET_MOUSE_INPUT_EVENT &&
+ (event.MouseInput.Event != EMIE_MOUSE_MOVED ||
+ (event.MouseInput.Event == EMIE_MOUSE_MOVED &&
+ event.MouseInput.isRightPressed() &&
+ getItemAtPos(m_pointer).i != getItemAtPos(m_old_pointer).i))) {
+
+ // Get selected item and hovered/clicked item (s)
+
+ m_old_tooltip_id = -1;
+ updateSelectedItem();
+ ItemSpec s = getItemAtPos(m_pointer);
+
+ Inventory *inv_selected = NULL;
+ Inventory *inv_s = NULL;
+ InventoryList *list_s = NULL;
+
+ if (m_selected_item) {
+ inv_selected = m_invmgr->getInventory(m_selected_item->inventoryloc);
+ sanity_check(inv_selected);
+ sanity_check(inv_selected->getList(m_selected_item->listname) != NULL);
+ }
+
+ u32 s_count = 0;
+
+ if (s.isValid())
+ do { // breakable
+ inv_s = m_invmgr->getInventory(s.inventoryloc);
+
+ if (!inv_s) {
+ errorstream << "InventoryMenu: The selected inventory location "
+ << "\"" << s.inventoryloc.dump() << "\" doesn't exist"
+ << std::endl;
+ s.i = -1; // make it invalid again
+ break;
+ }
+
+ list_s = inv_s->getList(s.listname);
+ if (list_s == NULL) {
+ verbosestream << "InventoryMenu: The selected inventory list \""
+ << s.listname << "\" does not exist" << std::endl;
+ s.i = -1; // make it invalid again
+ break;
+ }
+
+ if ((u32)s.i >= list_s->getSize()) {
+ infostream << "InventoryMenu: The selected inventory list \""
+ << s.listname << "\" is too small (i=" << s.i << ", size="
+ << list_s->getSize() << ")" << std::endl;
+ s.i = -1; // make it invalid again
+ break;
+ }
+
+ s_count = list_s->getItem(s.i).count;
+ } while(0);
+
+ bool identical = (m_selected_item != NULL) && s.isValid() &&
+ (inv_selected == inv_s) &&
+ (m_selected_item->listname == s.listname) &&
+ (m_selected_item->i == s.i);
+
+ // buttons: 0 = left, 1 = right, 2 = middle
+ // up/down: 0 = down (press), 1 = up (release), 2 = unknown event, -1 movement
+ int button = 0;
+ int updown = 2;
+ if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN)
+ { button = 0; updown = 0; }
+ else if (event.MouseInput.Event == EMIE_RMOUSE_PRESSED_DOWN)
+ { button = 1; updown = 0; }
+ else if (event.MouseInput.Event == EMIE_MMOUSE_PRESSED_DOWN)
+ { button = 2; updown = 0; }
+ else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP)
+ { button = 0; updown = 1; }
+ else if (event.MouseInput.Event == EMIE_RMOUSE_LEFT_UP)
+ { button = 1; updown = 1; }
+ else if (event.MouseInput.Event == EMIE_MMOUSE_LEFT_UP)
+ { button = 2; updown = 1; }
+ else if (event.MouseInput.Event == EMIE_MOUSE_MOVED)
+ { updown = -1;}
+
+ // Set this number to a positive value to generate a move action
+ // from m_selected_item to s.
+ u32 move_amount = 0;
+
+ // Set this number to a positive value to generate a move action
+ // from s to the next inventory ring.
+ u32 shift_move_amount = 0;
+
+ // Set this number to a positive value to generate a drop action
+ // from m_selected_item.
+ u32 drop_amount = 0;
+
+ // Set this number to a positive value to generate a craft action at s.
+ u32 craft_amount = 0;
+
+ if (updown == 0) {
+ // Some mouse button has been pressed
+
+ //infostream<<"Mouse button "<<button<<" pressed at p=("
+ // <<p.X<<","<<p.Y<<")"<<std::endl;
+
+ m_selected_dragging = false;
+
+ if (s.isValid() && s.listname == "craftpreview") {
+ // Craft preview has been clicked: craft
+ craft_amount = (button == 2 ? 10 : 1);
+ } else if (m_selected_item == NULL) {
+ if (s_count != 0) {
+ // Non-empty stack has been clicked: select or shift-move it
+ m_selected_item = new ItemSpec(s);
+
+ u32 count;
+ if (button == 1) // right
+ count = (s_count + 1) / 2;
+ else if (button == 2) // middle
+ count = MYMIN(s_count, 10);
+ else // left
+ count = s_count;
+
+ if (!event.MouseInput.Shift) {
+ // no shift: select item
+ m_selected_amount = count;
+ m_selected_dragging = true;
+ m_auto_place = false;
+ } else {
+ // shift pressed: move item
+ if (button != 1)
+ shift_move_amount = count;
+ else // count of 1 at left click like after drag & drop
+ shift_move_amount = 1;
+ }
+ }
+ } else { // m_selected_item != NULL
+ assert(m_selected_amount >= 1);
+
+ if (s.isValid()) {
+ // Clicked a slot: move
+ if (button == 1) // right
+ move_amount = 1;
+ else if (button == 2) // middle
+ move_amount = MYMIN(m_selected_amount, 10);
+ else // left
+ move_amount = m_selected_amount;
+
+ if (identical) {
+ if (move_amount >= m_selected_amount)
+ m_selected_amount = 0;
+ else
+ m_selected_amount -= move_amount;
+ move_amount = 0;
+ }
+ }
+ else if (!getAbsoluteClippingRect().isPointInside(m_pointer)) {
+ // Clicked outside of the window: drop
+ if (button == 1) // right
+ drop_amount = 1;
+ else if (button == 2) // middle
+ drop_amount = MYMIN(m_selected_amount, 10);
+ else // left
+ drop_amount = m_selected_amount;
+ }
+ }
+ }
+ else if (updown == 1) {
+ // Some mouse button has been released
+
+ //infostream<<"Mouse button "<<button<<" released at p=("
+ // <<p.X<<","<<p.Y<<")"<<std::endl;
+
+ if (m_selected_item != NULL && m_selected_dragging && s.isValid()) {
+ if (!identical) {
+ // Dragged to different slot: move all selected
+ move_amount = m_selected_amount;
+ }
+ } else if (m_selected_item != NULL && m_selected_dragging &&
+ !(getAbsoluteClippingRect().isPointInside(m_pointer))) {
+ // Dragged outside of window: drop all selected
+ drop_amount = m_selected_amount;
+ }
+
+ m_selected_dragging = false;
+ // Keep track of whether the mouse button be released
+ // One click is drag without dropping. Click + release
+ // + click changes to drop item when moved mode
+ if (m_selected_item)
+ m_auto_place = true;
+ } else if (updown == -1) {
+ // Mouse has been moved and rmb is down and mouse pointer just
+ // entered a new inventory field (checked in the entry-if, this
+ // is the only action here that is generated by mouse movement)
+ if (m_selected_item != NULL && s.isValid()) {
+ // Move 1 item
+ // TODO: middle mouse to move 10 items might be handy
+ if (m_auto_place) {
+ // Only move an item if the destination slot is empty
+ // or contains the same item type as what is going to be
+ // moved
+ InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
+ InventoryList *list_to = list_s;
+ assert(list_from && list_to);
+ ItemStack stack_from = list_from->getItem(m_selected_item->i);
+ ItemStack stack_to = list_to->getItem(s.i);
+ if (stack_to.empty() || stack_to.name == stack_from.name)
+ move_amount = 1;
+ }
+ }
+ }
+
+ // Possibly send inventory action to server
+ if (move_amount > 0) {
+ // Send IAction::Move
+
+ assert(m_selected_item && m_selected_item->isValid());
+ assert(s.isValid());
+
+ assert(inv_selected && inv_s);
+ InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
+ InventoryList *list_to = list_s;
+ assert(list_from && list_to);
+ ItemStack stack_from = list_from->getItem(m_selected_item->i);
+ ItemStack stack_to = list_to->getItem(s.i);
+
+ // Check how many items can be moved
+ move_amount = stack_from.count = MYMIN(move_amount, stack_from.count);
+ ItemStack leftover = stack_to.addItem(stack_from, m_client->idef());
+ // If source stack cannot be added to destination stack at all,
+ // they are swapped
+ if ((leftover.count == stack_from.count) &&
+ (leftover.name == stack_from.name)) {
+ m_selected_amount = stack_to.count;
+ // In case the server doesn't directly swap them but instead
+ // moves stack_to somewhere else, set this
+ m_selected_content_guess = stack_to;
+ m_selected_content_guess_inventory = s.inventoryloc;
+ }
+ // Source stack goes fully into destination stack
+ else if (leftover.empty()) {
+ m_selected_amount -= move_amount;
+ m_selected_content_guess = ItemStack(); // Clear
+ }
+ // Source stack goes partly into destination stack
+ else {
+ move_amount -= leftover.count;
+ m_selected_amount -= move_amount;
+ m_selected_content_guess = ItemStack(); // Clear
+ }
+
+ infostream << "Handing IAction::Move to manager" << std::endl;
+ IMoveAction *a = new IMoveAction();
+ a->count = move_amount;
+ a->from_inv = m_selected_item->inventoryloc;
+ a->from_list = m_selected_item->listname;
+ a->from_i = m_selected_item->i;
+ a->to_inv = s.inventoryloc;
+ a->to_list = s.listname;
+ a->to_i = s.i;
+ m_invmgr->inventoryAction(a);
+ } else if (shift_move_amount > 0) {
+ u32 mis = m_inventory_rings.size();
+ u32 i = 0;
+ for (; i < mis; i++) {
+ const ListRingSpec &sp = m_inventory_rings[i];
+ if (sp.inventoryloc == s.inventoryloc
+ && sp.listname == s.listname)
+ break;
+ }
+ do {
+ if (i >= mis) // if not found
+ break;
+ u32 to_inv_ind = (i + 1) % mis;
+ const ListRingSpec &to_inv_sp = m_inventory_rings[to_inv_ind];
+ InventoryList *list_from = list_s;
+ if (!s.isValid())
+ break;
+ Inventory *inv_to = m_invmgr->getInventory(to_inv_sp.inventoryloc);
+ if (!inv_to)
+ break;
+ InventoryList *list_to = inv_to->getList(to_inv_sp.listname);
+ if (!list_to)
+ break;
+ ItemStack stack_from = list_from->getItem(s.i);
+ assert(shift_move_amount <= stack_from.count);
+ if (m_client->getProtoVersion() >= 25) {
+ infostream << "Handing IAction::Move to manager" << std::endl;
+ IMoveAction *a = new IMoveAction();
+ a->count = shift_move_amount;
+ a->from_inv = s.inventoryloc;
+ a->from_list = s.listname;
+ a->from_i = s.i;
+ a->to_inv = to_inv_sp.inventoryloc;
+ a->to_list = to_inv_sp.listname;
+ a->move_somewhere = true;
+ m_invmgr->inventoryAction(a);
+ } else {
+ // find a place (or more than one) to add the new item
+ u32 ilt_size = list_to->getSize();
+ ItemStack leftover;
+ for (u32 slot_to = 0; slot_to < ilt_size
+ && shift_move_amount > 0; slot_to++) {
+ list_to->itemFits(slot_to, stack_from, &leftover);
+ if (leftover.count < stack_from.count) {
+ infostream << "Handing IAction::Move to manager" << std::endl;
+ IMoveAction *a = new IMoveAction();
+ a->count = MYMIN(shift_move_amount,
+ (u32) (stack_from.count - leftover.count));
+ shift_move_amount -= a->count;
+ a->from_inv = s.inventoryloc;
+ a->from_list = s.listname;
+ a->from_i = s.i;
+ a->to_inv = to_inv_sp.inventoryloc;
+ a->to_list = to_inv_sp.listname;
+ a->to_i = slot_to;
+ m_invmgr->inventoryAction(a);
+ stack_from = leftover;
+ }
+ }
+ }
+ } while (0);
+ } else if (drop_amount > 0) {
+ m_selected_content_guess = ItemStack(); // Clear
+
+ // Send IAction::Drop
+
+ assert(m_selected_item && m_selected_item->isValid());
+ assert(inv_selected);
+ InventoryList *list_from = inv_selected->getList(m_selected_item->listname);
+ assert(list_from);
+ ItemStack stack_from = list_from->getItem(m_selected_item->i);
+
+ // Check how many items can be dropped
+ drop_amount = stack_from.count = MYMIN(drop_amount, stack_from.count);
+ assert(drop_amount > 0 && drop_amount <= m_selected_amount);
+ m_selected_amount -= drop_amount;
+
+ infostream << "Handing IAction::Drop to manager" << std::endl;
+ IDropAction *a = new IDropAction();
+ a->count = drop_amount;
+ a->from_inv = m_selected_item->inventoryloc;
+ a->from_list = m_selected_item->listname;
+ a->from_i = m_selected_item->i;
+ m_invmgr->inventoryAction(a);
+ } else if (craft_amount > 0) {
+ m_selected_content_guess = ItemStack(); // Clear
+
+ // Send IAction::Craft
+
+ assert(s.isValid());
+ assert(inv_s);
+
+ infostream << "Handing IAction::Craft to manager" << std::endl;
+ ICraftAction *a = new ICraftAction();
+ a->count = craft_amount;
+ a->craft_inv = s.inventoryloc;
+ m_invmgr->inventoryAction(a);
+ }
+
+ // If m_selected_amount has been decreased to zero, deselect
+ if (m_selected_amount == 0) {
+ delete m_selected_item;
+ m_selected_item = NULL;
+ m_selected_amount = 0;
+ m_selected_dragging = false;
+ m_selected_content_guess = ItemStack();
+ }
+ m_old_pointer = m_pointer;
+ }
+ if (event.EventType == EET_GUI_EVENT) {
+
+ if (event.GUIEvent.EventType == gui::EGET_TAB_CHANGED
+ && isVisible()) {
+ // find the element that was clicked
+ for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
+ if ((s.ftype == f_TabHeader) &&
+ (s.fid == event.GUIEvent.Caller->getID())) {
+ s.send = true;
+ acceptInput();
+ s.send = false;
+ return true;
+ }
+ }
+ }
+ if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
+ && isVisible()) {
+ if (!canTakeFocus(event.GUIEvent.Element)) {
+ infostream<<"GUIFormSpecMenu: Not allowing focus change."
+ <<std::endl;
+ // Returning true disables focus change
+ return true;
+ }
+ }
+ if ((event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) ||
+ (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) ||
+ (event.GUIEvent.EventType == gui::EGET_COMBO_BOX_CHANGED) ||
+ (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED)) {
+ unsigned int btn_id = event.GUIEvent.Caller->getID();
+
+ if (btn_id == 257) {
+ if (m_allowclose) {
+ acceptInput(quit_mode_accept);
+ quitMenu();
+ } else {
+ acceptInput();
+ m_text_dst->gotText(L"ExitButton");
+ }
+ // quitMenu deallocates menu
+ return true;
+ }
+
+ // find the element that was clicked
+ for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
+ // if its a button, set the send field so
+ // lua knows which button was pressed
+ if ((s.ftype == f_Button || s.ftype == f_CheckBox) &&
+ s.fid == event.GUIEvent.Caller->getID()) {
+ s.send = true;
+ if (s.is_exit) {
+ if (m_allowclose) {
+ acceptInput(quit_mode_accept);
+ quitMenu();
+ } else {
+ m_text_dst->gotText(L"ExitButton");
+ }
+ return true;
+ }
+
+ acceptInput(quit_mode_no);
+ s.send = false;
+ return true;
+
+ } else if ((s.ftype == f_DropDown) &&
+ (s.fid == event.GUIEvent.Caller->getID())) {
+ // only send the changed dropdown
+ for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
+ if (s2.ftype == f_DropDown) {
+ s2.send = false;
+ }
+ }
+ s.send = true;
+ acceptInput(quit_mode_no);
+
+ // revert configuration to make sure dropdowns are sent on
+ // regular button click
+ for (GUIFormSpecMenu::FieldSpec &s2 : m_fields) {
+ if (s2.ftype == f_DropDown) {
+ s2.send = true;
+ }
+ }
+ return true;
+ } else if ((s.ftype == f_ScrollBar) &&
+ (s.fid == event.GUIEvent.Caller->getID())) {
+ s.fdefault = L"Changed";
+ acceptInput(quit_mode_no);
+ s.fdefault = L"";
+ }
+ }
+ }
+
+ if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
+ if (event.GUIEvent.Caller->getID() > 257) {
+ bool close_on_enter = true;
+ for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
+ if (s.ftype == f_Unknown &&
+ s.fid == event.GUIEvent.Caller->getID()) {
+ current_field_enter_pending = s.fname;
+ std::unordered_map<std::string, bool>::const_iterator it =
+ field_close_on_enter.find(s.fname);
+ if (it != field_close_on_enter.end())
+ close_on_enter = (*it).second;
+
+ break;
+ }
+ }
+
+ if (m_allowclose && close_on_enter) {
+ current_keys_pending.key_enter = true;
+ acceptInput(quit_mode_accept);
+ quitMenu();
+ } else {
+ current_keys_pending.key_enter = true;
+ acceptInput();
+ }
+ // quitMenu deallocates menu
+ return true;
+ }
+ }
+
+ if (event.GUIEvent.EventType == gui::EGET_TABLE_CHANGED) {
+ int current_id = event.GUIEvent.Caller->getID();
+ if (current_id > 257) {
+ // find the element that was clicked
+ for (GUIFormSpecMenu::FieldSpec &s : m_fields) {
+ // if it's a table, set the send field
+ // so lua knows which table was changed
+ if ((s.ftype == f_Table) && (s.fid == current_id)) {
+ s.send = true;
+ acceptInput();
+ s.send=false;
+ }
+ }
+ return true;
+ }
+ }
+ }
+
+ return Parent ? Parent->OnEvent(event) : false;
+}
+
+/**
+ * get name of element by element id
+ * @param id of element
+ * @return name string or empty string
+ */
+std::string GUIFormSpecMenu::getNameByID(s32 id)
+{
+ for (FieldSpec &spec : m_fields) {
+ if (spec.fid == id) {
+ return spec.fname;
+ }
+ }
+ return "";
+}
+
+/**
+ * get label of element by id
+ * @param id of element
+ * @return label string or empty string
+ */
+std::wstring GUIFormSpecMenu::getLabelByID(s32 id)
+{
+ for (FieldSpec &spec : m_fields) {
+ if (spec.fid == id) {
+ return spec.flabel;
+ }
+ }
+ return L"";
+}
diff --git a/src/gui/guiFormSpecMenu.h b/src/gui/guiFormSpecMenu.h
new file mode 100644
index 000000000..071efb37f
--- /dev/null
+++ b/src/gui/guiFormSpecMenu.h
@@ -0,0 +1,565 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include <utility>
+#include <stack>
+
+#include "irrlichttypes_extrabloated.h"
+#include "inventorymanager.h"
+#include "modalMenu.h"
+#include "guiTable.h"
+#include "network/networkprotocol.h"
+#include "client/joystick_controller.h"
+#include "util/string.h"
+#include "util/enriched_string.h"
+
+class InventoryManager;
+class ISimpleTextureSource;
+class Client;
+
+typedef enum {
+ f_Button,
+ f_Table,
+ f_TabHeader,
+ f_CheckBox,
+ f_DropDown,
+ f_ScrollBar,
+ f_Unknown
+} FormspecFieldType;
+
+typedef enum {
+ quit_mode_no,
+ quit_mode_accept,
+ quit_mode_cancel
+} FormspecQuitMode;
+
+struct TextDest
+{
+ virtual ~TextDest() = default;
+
+ // This is deprecated I guess? -celeron55
+ virtual void gotText(const std::wstring &text) {}
+ virtual void gotText(const StringMap &fields) = 0;
+
+ std::string m_formname;
+};
+
+class IFormSource
+{
+public:
+ virtual ~IFormSource() = default;
+ virtual const std::string &getForm() const = 0;
+ // Fill in variables in field text
+ virtual std::string resolveText(const std::string &str) { return str; }
+};
+
+class GUIFormSpecMenu : public GUIModalMenu
+{
+ struct ItemSpec
+ {
+ ItemSpec() = default;
+
+ ItemSpec(const InventoryLocation &a_inventoryloc,
+ const std::string &a_listname,
+ s32 a_i) :
+ inventoryloc(a_inventoryloc),
+ listname(a_listname),
+ i(a_i)
+ {
+ }
+
+ bool isValid() const { return i != -1; }
+
+ InventoryLocation inventoryloc;
+ std::string listname;
+ s32 i = -1;
+ };
+
+ struct ListDrawSpec
+ {
+ ListDrawSpec() = default;
+
+ ListDrawSpec(const InventoryLocation &a_inventoryloc,
+ const std::string &a_listname,
+ v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i):
+ inventoryloc(a_inventoryloc),
+ listname(a_listname),
+ pos(a_pos),
+ geom(a_geom),
+ start_item_i(a_start_item_i)
+ {
+ }
+
+ InventoryLocation inventoryloc;
+ std::string listname;
+ v2s32 pos;
+ v2s32 geom;
+ s32 start_item_i;
+ };
+
+ struct ListRingSpec
+ {
+ ListRingSpec() = default;
+
+ ListRingSpec(const InventoryLocation &a_inventoryloc,
+ const std::string &a_listname):
+ inventoryloc(a_inventoryloc),
+ listname(a_listname)
+ {
+ }
+
+ InventoryLocation inventoryloc;
+ std::string listname;
+ };
+
+ struct ImageDrawSpec
+ {
+ ImageDrawSpec():
+ parent_button(NULL),
+ clip(false)
+ {
+ }
+
+ ImageDrawSpec(const std::string &a_name,
+ const std::string &a_item_name,
+ gui::IGUIButton *a_parent_button,
+ const v2s32 &a_pos, const v2s32 &a_geom):
+ name(a_name),
+ item_name(a_item_name),
+ parent_button(a_parent_button),
+ pos(a_pos),
+ geom(a_geom),
+ scale(true),
+ clip(false)
+ {
+ }
+
+ ImageDrawSpec(const std::string &a_name,
+ const std::string &a_item_name,
+ const v2s32 &a_pos, const v2s32 &a_geom):
+ name(a_name),
+ item_name(a_item_name),
+ parent_button(NULL),
+ pos(a_pos),
+ geom(a_geom),
+ scale(true),
+ clip(false)
+ {
+ }
+
+ ImageDrawSpec(const std::string &a_name,
+ const v2s32 &a_pos, const v2s32 &a_geom, bool clip=false):
+ name(a_name),
+ parent_button(NULL),
+ pos(a_pos),
+ geom(a_geom),
+ scale(true),
+ clip(clip)
+ {
+ }
+
+ ImageDrawSpec(const std::string &a_name,
+ const v2s32 &a_pos):
+ name(a_name),
+ parent_button(NULL),
+ pos(a_pos),
+ scale(false),
+ clip(false)
+ {
+ }
+
+ std::string name;
+ std::string item_name;
+ gui::IGUIButton *parent_button;
+ v2s32 pos;
+ v2s32 geom;
+ bool scale;
+ bool clip;
+ };
+
+ struct FieldSpec
+ {
+ FieldSpec() = default;
+
+ FieldSpec(const std::string &name, const std::wstring &label,
+ const std::wstring &default_text, int id) :
+ fname(name),
+ flabel(label),
+ fdefault(unescape_enriched(translate_string(default_text))),
+ fid(id),
+ send(false),
+ ftype(f_Unknown),
+ is_exit(false)
+ {
+ }
+
+ std::string fname;
+ std::wstring flabel;
+ std::wstring fdefault;
+ int fid;
+ bool send;
+ FormspecFieldType ftype;
+ bool is_exit;
+ core::rect<s32> rect;
+ };
+
+ struct BoxDrawSpec
+ {
+ BoxDrawSpec(v2s32 a_pos, v2s32 a_geom,irr::video::SColor a_color):
+ pos(a_pos),
+ geom(a_geom),
+ color(a_color)
+ {
+ }
+ v2s32 pos;
+ v2s32 geom;
+ irr::video::SColor color;
+ };
+
+ struct TooltipSpec
+ {
+ TooltipSpec() = default;
+ TooltipSpec(const std::wstring &a_tooltip, irr::video::SColor a_bgcolor,
+ irr::video::SColor a_color):
+ tooltip(translate_string(a_tooltip)),
+ bgcolor(a_bgcolor),
+ color(a_color)
+ {
+ }
+
+ std::wstring tooltip;
+ irr::video::SColor bgcolor;
+ irr::video::SColor color;
+ };
+
+ struct StaticTextSpec
+ {
+ StaticTextSpec():
+ parent_button(NULL)
+ {
+ }
+
+ StaticTextSpec(const std::wstring &a_text,
+ const core::rect<s32> &a_rect):
+ text(a_text),
+ rect(a_rect),
+ parent_button(NULL)
+ {
+ }
+
+ StaticTextSpec(const std::wstring &a_text,
+ const core::rect<s32> &a_rect,
+ gui::IGUIButton *a_parent_button):
+ text(a_text),
+ rect(a_rect),
+ parent_button(a_parent_button)
+ {
+ }
+
+ std::wstring text;
+ core::rect<s32> rect;
+ gui::IGUIButton *parent_button;
+ };
+
+public:
+ GUIFormSpecMenu(JoystickController *joystick,
+ gui::IGUIElement* parent, s32 id,
+ IMenuManager *menumgr,
+ Client *client,
+ ISimpleTextureSource *tsrc,
+ IFormSource* fs_src,
+ TextDest* txt_dst,
+ bool remap_dbl_click = true);
+
+ ~GUIFormSpecMenu();
+
+ void setFormSpec(const std::string &formspec_string,
+ const InventoryLocation &current_inventory_location)
+ {
+ m_formspec_string = formspec_string;
+ m_current_inventory_location = current_inventory_location;
+ regenerateGui(m_screensize_old);
+ }
+
+ // form_src is deleted by this GUIFormSpecMenu
+ void setFormSource(IFormSource *form_src)
+ {
+ delete m_form_src;
+ m_form_src = form_src;
+ }
+
+ // text_dst is deleted by this GUIFormSpecMenu
+ void setTextDest(TextDest *text_dst)
+ {
+ delete m_text_dst;
+ m_text_dst = text_dst;
+ }
+
+ void allowClose(bool value)
+ {
+ m_allowclose = value;
+ }
+
+ void lockSize(bool lock,v2u32 basescreensize=v2u32(0,0))
+ {
+ m_lock = lock;
+ m_lockscreensize = basescreensize;
+ }
+
+ void removeChildren();
+ void setInitialFocus();
+
+ void setFocus(const std::string &elementname)
+ {
+ m_focused_element = elementname;
+ }
+
+ /*
+ Remove and re-add (or reposition) stuff
+ */
+ void regenerateGui(v2u32 screensize);
+
+ ItemSpec getItemAtPos(v2s32 p) const;
+ void drawList(const ListDrawSpec &s, int phase, bool &item_hovered);
+ void drawSelectedItem();
+ void drawMenu();
+ void updateSelectedItem();
+ ItemStack verifySelectedItem();
+
+ void acceptInput(FormspecQuitMode quitmode);
+ bool preprocessEvent(const SEvent& event);
+ bool OnEvent(const SEvent& event);
+ bool doPause;
+ bool pausesGame() { return doPause; }
+
+ GUITable* getTable(const std::string &tablename);
+ std::vector<std::string>* getDropDownValues(const std::string &name);
+
+#ifdef __ANDROID__
+ bool getAndroidUIInput();
+#endif
+
+protected:
+ v2s32 getBasePos() const
+ {
+ return padding + offset + AbsoluteRect.UpperLeftCorner;
+ }
+
+ v2s32 padding;
+ v2s32 spacing;
+ v2s32 imgsize;
+ v2s32 offset;
+ v2s32 pos_offset;
+ std::stack<v2s32> container_stack;
+
+ InventoryManager *m_invmgr;
+ ISimpleTextureSource *m_tsrc;
+ Client *m_client;
+
+ std::string m_formspec_string;
+ InventoryLocation m_current_inventory_location;
+
+ std::vector<ListDrawSpec> m_inventorylists;
+ std::vector<ListRingSpec> m_inventory_rings;
+ std::vector<ImageDrawSpec> m_backgrounds;
+ std::vector<ImageDrawSpec> m_images;
+ std::vector<ImageDrawSpec> m_itemimages;
+ std::vector<BoxDrawSpec> m_boxes;
+ std::unordered_map<std::string, bool> field_close_on_enter;
+ std::vector<FieldSpec> m_fields;
+ std::vector<StaticTextSpec> m_static_texts;
+ std::vector<std::pair<FieldSpec,GUITable*> > m_tables;
+ std::vector<std::pair<FieldSpec,gui::IGUICheckBox*> > m_checkboxes;
+ std::map<std::string, TooltipSpec> m_tooltips;
+ std::vector<std::pair<FieldSpec,gui::IGUIScrollBar*> > m_scrollbars;
+ std::vector<std::pair<FieldSpec, std::vector<std::string> > > m_dropdowns;
+
+ ItemSpec *m_selected_item = nullptr;
+ u32 m_selected_amount = 0;
+ bool m_selected_dragging = false;
+
+ // WARNING: BLACK MAGIC
+ // Used to guess and keep up with some special things the server can do.
+ // If name is "", no guess exists.
+ ItemStack m_selected_content_guess;
+ InventoryLocation m_selected_content_guess_inventory;
+
+ v2s32 m_pointer;
+ v2s32 m_old_pointer; // Mouse position after previous mouse event
+ gui::IGUIStaticText *m_tooltip_element = nullptr;
+
+ u64 m_tooltip_show_delay;
+ bool m_tooltip_append_itemname;
+ u64 m_hovered_time = 0;
+ s32 m_old_tooltip_id = -1;
+
+ bool m_auto_place = false;
+
+ bool m_allowclose = true;
+ bool m_lock = false;
+ v2u32 m_lockscreensize;
+
+ bool m_bgfullscreen;
+ bool m_slotborder;
+ video::SColor m_bgcolor;
+ video::SColor m_fullscreen_bgcolor;
+ video::SColor m_slotbg_n;
+ video::SColor m_slotbg_h;
+ video::SColor m_slotbordercolor;
+ video::SColor m_default_tooltip_bgcolor;
+ video::SColor m_default_tooltip_color;
+
+private:
+ IFormSource *m_form_src;
+ TextDest *m_text_dst;
+ u32 m_formspec_version = 0;
+ std::string m_focused_element = "";
+ JoystickController *m_joystick;
+
+ typedef struct {
+ bool explicit_size;
+ v2f invsize;
+ v2s32 size;
+ v2f32 offset;
+ v2f32 anchor;
+ core::rect<s32> rect;
+ v2s32 basepos;
+ v2u32 screensize;
+ std::string focused_fieldname;
+ GUITable::TableOptions table_options;
+ GUITable::TableColumns table_columns;
+ // used to restore table selection/scroll/treeview state
+ std::unordered_map<std::string, GUITable::DynamicData> table_dyndata;
+ } parserData;
+
+ typedef struct {
+ bool key_up;
+ bool key_down;
+ bool key_enter;
+ bool key_escape;
+ } fs_key_pendig;
+
+ fs_key_pendig current_keys_pending;
+ std::string current_field_enter_pending = "";
+
+ void parseElement(parserData* data, const std::string &element);
+
+ void parseSize(parserData* data, const std::string &element);
+ void parseContainer(parserData* data, const std::string &element);
+ void parseContainerEnd(parserData* data);
+ void parseList(parserData* data, const std::string &element);
+ void parseListRing(parserData* data, const std::string &element);
+ void parseCheckbox(parserData* data, const std::string &element);
+ void parseImage(parserData* data, const std::string &element);
+ void parseItemImage(parserData* data, const std::string &element);
+ void parseButton(parserData* data, const std::string &element,
+ const std::string &typ);
+ void parseBackground(parserData* data, const std::string &element);
+ void parseTableOptions(parserData* data, const std::string &element);
+ void parseTableColumns(parserData* data, const std::string &element);
+ void parseTable(parserData* data, const std::string &element);
+ void parseTextList(parserData* data, const std::string &element);
+ void parseDropDown(parserData* data, const std::string &element);
+ void parseFieldCloseOnEnter(parserData *data, const std::string &element);
+ void parsePwdField(parserData* data, const std::string &element);
+ void parseField(parserData* data, const std::string &element, const std::string &type);
+ void parseSimpleField(parserData* data,std::vector<std::string> &parts);
+ void parseTextArea(parserData* data,std::vector<std::string>& parts,
+ const std::string &type);
+ void parseLabel(parserData* data, const std::string &element);
+ void parseVertLabel(parserData* data, const std::string &element);
+ void parseImageButton(parserData* data, const std::string &element,
+ const std::string &type);
+ void parseItemImageButton(parserData* data, const std::string &element);
+ void parseTabHeader(parserData* data, const std::string &element);
+ void parseBox(parserData* data, const std::string &element);
+ void parseBackgroundColor(parserData* data, const std::string &element);
+ void parseListColors(parserData* data, const std::string &element);
+ void parseTooltip(parserData* data, const std::string &element);
+ bool parseVersionDirect(const std::string &data);
+ bool parseSizeDirect(parserData* data, const std::string &element);
+ void parseScrollBar(parserData* data, const std::string &element);
+ bool parsePositionDirect(parserData *data, const std::string &element);
+ void parsePosition(parserData *data, const std::string &element);
+ bool parseAnchorDirect(parserData *data, const std::string &element);
+ void parseAnchor(parserData *data, const std::string &element);
+
+ void tryClose();
+
+ void showTooltip(const std::wstring &text, const irr::video::SColor &color,
+ const irr::video::SColor &bgcolor);
+
+ /**
+ * check if event is part of a double click
+ * @param event event to evaluate
+ * @return true/false if a doubleclick was detected
+ */
+ bool DoubleClickDetection(const SEvent event);
+
+ struct clickpos
+ {
+ v2s32 pos;
+ s64 time;
+ };
+ clickpos m_doubleclickdetect[2];
+
+ int m_btn_height;
+ gui::IGUIFont *m_font = nullptr;
+
+ std::wstring getLabelByID(s32 id);
+ std::string getNameByID(s32 id);
+#ifdef __ANDROID__
+ v2s32 m_down_pos;
+ std::string m_JavaDialogFieldName;
+#endif
+
+ /* If true, remap a double-click (or double-tap) action to ESC. This is so
+ * that, for example, Android users can double-tap to close a formspec.
+ *
+ * This value can (currently) only be set by the class constructor
+ * and the default value for the setting is true.
+ */
+ bool m_remap_dbl_click;
+
+};
+
+class FormspecFormSource: public IFormSource
+{
+public:
+ FormspecFormSource(const std::string &formspec):
+ m_formspec(formspec)
+ {
+ }
+
+ ~FormspecFormSource() = default;
+
+ void setForm(const std::string &formspec)
+ {
+ m_formspec = FORMSPEC_VERSION_STRING + formspec;
+ }
+
+ const std::string &getForm() const
+ {
+ return m_formspec;
+ }
+
+ std::string m_formspec;
+};
diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp
new file mode 100644
index 000000000..53677a57b
--- /dev/null
+++ b/src/gui/guiKeyChangeMenu.cpp
@@ -0,0 +1,436 @@
+/*
+ Minetest
+ Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+ Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+ Copyright (C) 2013 teddydestodes <derkomtur@schattengang.net>
+
+ 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.
+ */
+
+#include "guiKeyChangeMenu.h"
+#include "debug.h"
+#include "serialization.h"
+#include <string>
+#include <IGUICheckBox.h>
+#include <IGUIEditBox.h>
+#include <IGUIButton.h>
+#include <IGUIStaticText.h>
+#include <IGUIFont.h>
+#include "settings.h"
+#include <algorithm>
+
+#include "mainmenumanager.h" // for g_gamecallback
+
+#define KMaxButtonPerColumns 12
+
+extern MainGameCallback *g_gamecallback;
+
+enum
+{
+ GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR,
+ // buttons
+ GUI_ID_KEY_FORWARD_BUTTON,
+ GUI_ID_KEY_BACKWARD_BUTTON,
+ GUI_ID_KEY_LEFT_BUTTON,
+ GUI_ID_KEY_RIGHT_BUTTON,
+ GUI_ID_KEY_USE_BUTTON,
+ GUI_ID_KEY_FLY_BUTTON,
+ GUI_ID_KEY_FAST_BUTTON,
+ GUI_ID_KEY_JUMP_BUTTON,
+ GUI_ID_KEY_NOCLIP_BUTTON,
+ GUI_ID_KEY_CINEMATIC_BUTTON,
+ GUI_ID_KEY_CHAT_BUTTON,
+ GUI_ID_KEY_CMD_BUTTON,
+ GUI_ID_KEY_CMD_LOCAL_BUTTON,
+ GUI_ID_KEY_CONSOLE_BUTTON,
+ GUI_ID_KEY_SNEAK_BUTTON,
+ GUI_ID_KEY_DROP_BUTTON,
+ GUI_ID_KEY_INVENTORY_BUTTON,
+ GUI_ID_KEY_HOTBAR_PREV_BUTTON,
+ GUI_ID_KEY_HOTBAR_NEXT_BUTTON,
+ GUI_ID_KEY_MUTE_BUTTON,
+ GUI_ID_KEY_DEC_VOLUME_BUTTON,
+ GUI_ID_KEY_INC_VOLUME_BUTTON,
+ GUI_ID_KEY_RANGE_BUTTON,
+ GUI_ID_KEY_ZOOM_BUTTON,
+ GUI_ID_KEY_CAMERA_BUTTON,
+ GUI_ID_KEY_MINIMAP_BUTTON,
+ GUI_ID_KEY_SCREENSHOT_BUTTON,
+ GUI_ID_KEY_CHATLOG_BUTTON,
+ GUI_ID_KEY_HUD_BUTTON,
+ GUI_ID_KEY_FOG_BUTTON,
+ GUI_ID_KEY_DEC_RANGE_BUTTON,
+ GUI_ID_KEY_INC_RANGE_BUTTON,
+ GUI_ID_KEY_AUTOFWD_BUTTON,
+ // other
+ GUI_ID_CB_AUX1_DESCENDS,
+ GUI_ID_CB_DOUBLETAP_JUMP,
+};
+
+GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) :
+GUIModalMenu(env, parent, id, menumgr)
+{
+ init_keys();
+ for (key_setting *ks : key_settings)
+ key_used.push_back(ks->key);
+}
+
+GUIKeyChangeMenu::~GUIKeyChangeMenu()
+{
+ removeChildren();
+
+ for (key_setting *ks : key_settings) {
+ delete[] ks->button_name;
+ delete ks;
+ }
+ key_settings.clear();
+}
+
+void GUIKeyChangeMenu::removeChildren()
+{
+ const core::list<gui::IGUIElement*> &children = getChildren();
+ core::list<gui::IGUIElement*> children_copy;
+ for (gui::IGUIElement*i : children) {
+ children_copy.push_back(i);
+ }
+
+ for (gui::IGUIElement *i : children_copy) {
+ i->remove();
+ }
+}
+
+void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
+{
+ removeChildren();
+ v2s32 size(745, 430);
+
+ core::rect < s32 > rect(screensize.X / 2 - size.X / 2,
+ screensize.Y / 2 - size.Y / 2, screensize.X / 2 + size.X / 2,
+ screensize.Y / 2 + size.Y / 2);
+
+ DesiredRect = rect;
+ recalculateAbsolutePosition(false);
+
+ v2s32 topleft(0, 0);
+
+ {
+ core::rect < s32 > rect(0, 0, 600, 40);
+ rect += topleft + v2s32(25, 3);
+ //gui::IGUIStaticText *t =
+ const wchar_t *text = wgettext("Keybindings. (If this menu screws up, remove stuff from minetest.conf)");
+ Environment->addStaticText(text,
+ rect, false, true, this, -1);
+ delete[] text;
+ //t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
+ }
+
+ // Build buttons
+
+ v2s32 offset(25, 60);
+
+ for(size_t i = 0; i < key_settings.size(); i++)
+ {
+ key_setting *k = key_settings.at(i);
+ {
+ core::rect < s32 > rect(0, 0, 150, 20);
+ rect += topleft + v2s32(offset.X, offset.Y);
+ Environment->addStaticText(k->button_name, rect, false, true, this, -1);
+ }
+
+ {
+ core::rect < s32 > rect(0, 0, 100, 30);
+ rect += topleft + v2s32(offset.X + 120, offset.Y - 5);
+ const wchar_t *text = wgettext(k->key.name());
+ k->button = Environment->addButton(rect, this, k->id, text);
+ delete[] text;
+ }
+ if ((i + 1) % KMaxButtonPerColumns == 0) {
+ offset.X += 230;
+ offset.Y = 60;
+ } else {
+ offset += v2s32(0, 25);
+ }
+ }
+
+ {
+ s32 option_x = offset.X;
+ s32 option_y = offset.Y + 5;
+ u32 option_w = 180;
+ {
+ core::rect<s32> rect(0, 0, option_w, 30);
+ rect += topleft + v2s32(option_x, option_y);
+ const wchar_t *text = wgettext("\"Special\" = climb down");
+ Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this,
+ GUI_ID_CB_AUX1_DESCENDS, text);
+ delete[] text;
+ }
+ offset += v2s32(0, 25);
+ }
+
+ {
+ s32 option_x = offset.X;
+ s32 option_y = offset.Y + 5;
+ u32 option_w = 280;
+ {
+ core::rect<s32> rect(0, 0, option_w, 30);
+ rect += topleft + v2s32(option_x, option_y);
+ const wchar_t *text = wgettext("Double tap \"jump\" to toggle fly");
+ Environment->addCheckBox(g_settings->getBool("doubletap_jump"), rect, this,
+ GUI_ID_CB_DOUBLETAP_JUMP, text);
+ delete[] text;
+ }
+ offset += v2s32(0, 25);
+ }
+
+ {
+ core::rect < s32 > rect(0, 0, 100, 30);
+ rect += topleft + v2s32(size.X / 2 - 105, size.Y - 40);
+ const wchar_t *text = wgettext("Save");
+ Environment->addButton(rect, this, GUI_ID_BACK_BUTTON,
+ text);
+ delete[] text;
+ }
+ {
+ core::rect < s32 > rect(0, 0, 100, 30);
+ rect += topleft + v2s32(size.X / 2 + 5, size.Y - 40);
+ const wchar_t *text = wgettext("Cancel");
+ Environment->addButton(rect, this, GUI_ID_ABORT_BUTTON,
+ text);
+ delete[] text;
+ }
+}
+
+void GUIKeyChangeMenu::drawMenu()
+{
+ gui::IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+
+ video::SColor bgcolor(140, 0, 0, 0);
+
+ {
+ core::rect < s32 > rect(0, 0, 745, 620);
+ rect += AbsoluteRect.UpperLeftCorner;
+ driver->draw2DRectangle(bgcolor, rect, &AbsoluteClippingRect);
+ }
+
+ gui::IGUIElement::draw();
+}
+
+bool GUIKeyChangeMenu::acceptInput()
+{
+ for (key_setting *k : key_settings) {
+ g_settings->set(k->setting_name, k->key.sym());
+ }
+
+ {
+ gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS);
+ if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
+ g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked());
+ }
+ {
+ gui::IGUIElement *e = getElementFromId(GUI_ID_CB_DOUBLETAP_JUMP);
+ if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
+ g_settings->setBool("doubletap_jump", ((gui::IGUICheckBox*)e)->isChecked());
+ }
+
+ clearKeyCache();
+
+ g_gamecallback->signalKeyConfigChange();
+
+ return true;
+}
+
+bool GUIKeyChangeMenu::resetMenu()
+{
+ if (activeKey >= 0)
+ {
+ for (key_setting *k : key_settings) {
+ if (k->id == activeKey) {
+ const wchar_t *text = wgettext(k->key.name());
+ k->button->setText(text);
+ delete[] text;
+ break;
+ }
+ }
+ activeKey = -1;
+ return false;
+ }
+ return true;
+}
+bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
+{
+ if (event.EventType == EET_KEY_INPUT_EVENT && activeKey >= 0
+ && event.KeyInput.PressedDown) {
+
+ bool prefer_character = shift_down;
+ KeyPress kp(event.KeyInput, prefer_character);
+
+ bool shift_went_down = false;
+ if(!shift_down &&
+ (event.KeyInput.Key == irr::KEY_SHIFT ||
+ event.KeyInput.Key == irr::KEY_LSHIFT ||
+ event.KeyInput.Key == irr::KEY_RSHIFT))
+ shift_went_down = true;
+
+ // Remove Key already in use message
+ if(this->key_used_text)
+ {
+ this->key_used_text->remove();
+ this->key_used_text = NULL;
+ }
+ // Display Key already in use message
+ if (std::find(this->key_used.begin(), this->key_used.end(), kp) != this->key_used.end())
+ {
+ core::rect < s32 > rect(0, 0, 600, 40);
+ rect += v2s32(0, 0) + v2s32(25, 30);
+ const wchar_t *text = wgettext("Key already in use");
+ this->key_used_text = Environment->addStaticText(text,
+ rect, false, true, this, -1);
+ delete[] text;
+ //infostream << "Key already in use" << std::endl;
+ }
+
+ // But go on
+ {
+ key_setting *k = NULL;
+ for (key_setting *ks : key_settings) {
+ if (ks->id == activeKey) {
+ k = ks;
+ break;
+ }
+ }
+ FATAL_ERROR_IF(k == NULL, "Key setting not found");
+ k->key = kp;
+ const wchar_t *text = wgettext(k->key.name());
+ k->button->setText(text);
+ delete[] text;
+
+ this->key_used.push_back(kp);
+
+ // Allow characters made with shift
+ if(shift_went_down){
+ shift_down = true;
+ return false;
+ }
+
+ activeKey = -1;
+ return true;
+ }
+ } else if (event.EventType == EET_KEY_INPUT_EVENT && activeKey < 0
+ && event.KeyInput.PressedDown
+ && event.KeyInput.Key == irr::KEY_ESCAPE) {
+ quitMenu();
+ return true;
+ } else if (event.EventType == EET_GUI_EVENT) {
+ if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
+ && isVisible())
+ {
+ if (!canTakeFocus(event.GUIEvent.Element))
+ {
+ dstream << "GUIMainMenu: Not allowing focus change."
+ << std::endl;
+ // Returning true disables focus change
+ return true;
+ }
+ }
+ if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED)
+ {
+ switch (event.GUIEvent.Caller->getID())
+ {
+ case GUI_ID_BACK_BUTTON: //back
+ acceptInput();
+ quitMenu();
+ return true;
+ case GUI_ID_ABORT_BUTTON: //abort
+ quitMenu();
+ return true;
+ default:
+ key_setting *k = NULL;
+
+ for (key_setting *ks : key_settings) {
+ if (ks->id == event.GUIEvent.Caller->getID()) {
+ k = ks;
+ break;
+ }
+ }
+ FATAL_ERROR_IF(k == NULL, "Key setting not found");
+
+ resetMenu();
+ shift_down = false;
+ activeKey = event.GUIEvent.Caller->getID();
+ const wchar_t *text = wgettext("press key");
+ k->button->setText(text);
+ delete[] text;
+ this->key_used.erase(std::remove(this->key_used.begin(),
+ this->key_used.end(), k->key), this->key_used.end());
+ break;
+ }
+ Environment->setFocus(this);
+ }
+ }
+ return Parent ? Parent->OnEvent(event) : false;
+}
+
+void GUIKeyChangeMenu::add_key(int id, const wchar_t *button_name, const std::string &setting_name)
+{
+ key_setting *k = new key_setting;
+ k->id = id;
+
+ k->button_name = button_name;
+ k->setting_name = setting_name;
+ k->key = getKeySetting(k->setting_name.c_str());
+ key_settings.push_back(k);
+}
+
+void GUIKeyChangeMenu::init_keys()
+{
+ this->add_key(GUI_ID_KEY_FORWARD_BUTTON, wgettext("Forward"), "keymap_forward");
+ this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, wgettext("Backward"), "keymap_backward");
+ this->add_key(GUI_ID_KEY_LEFT_BUTTON, wgettext("Left"), "keymap_left");
+ this->add_key(GUI_ID_KEY_RIGHT_BUTTON, wgettext("Right"), "keymap_right");
+ this->add_key(GUI_ID_KEY_USE_BUTTON, wgettext("Special"), "keymap_special1");
+ this->add_key(GUI_ID_KEY_JUMP_BUTTON, wgettext("Jump"), "keymap_jump");
+ this->add_key(GUI_ID_KEY_SNEAK_BUTTON, wgettext("Sneak"), "keymap_sneak");
+ this->add_key(GUI_ID_KEY_DROP_BUTTON, wgettext("Drop"), "keymap_drop");
+ this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, wgettext("Inventory"), "keymap_inventory");
+ this->add_key(GUI_ID_KEY_HOTBAR_PREV_BUTTON,wgettext("Prev. item"), "keymap_hotbar_previous");
+ this->add_key(GUI_ID_KEY_HOTBAR_NEXT_BUTTON,wgettext("Next item"), "keymap_hotbar_next");
+ this->add_key(GUI_ID_KEY_ZOOM_BUTTON, wgettext("Zoom"), "keymap_zoom");
+ this->add_key(GUI_ID_KEY_CAMERA_BUTTON, wgettext("Change camera"), "keymap_camera_mode");
+ this->add_key(GUI_ID_KEY_CINEMATIC_BUTTON, wgettext("Toggle Cinematic"), "keymap_cinematic");
+ this->add_key(GUI_ID_KEY_MINIMAP_BUTTON, wgettext("Toggle minimap"), "keymap_minimap");
+ this->add_key(GUI_ID_KEY_FLY_BUTTON, wgettext("Toggle fly"), "keymap_freemove");
+ this->add_key(GUI_ID_KEY_FAST_BUTTON, wgettext("Toggle fast"), "keymap_fastmove");
+ this->add_key(GUI_ID_KEY_NOCLIP_BUTTON, wgettext("Toggle noclip"), "keymap_noclip");
+ this->add_key(GUI_ID_KEY_MUTE_BUTTON, wgettext("Mute"), "keymap_mute");
+ this->add_key(GUI_ID_KEY_DEC_VOLUME_BUTTON,wgettext("Dec. volume"), "keymap_decrease_volume");
+ this->add_key(GUI_ID_KEY_INC_VOLUME_BUTTON,wgettext("Inc. volume"), "keymap_increase_volume");
+ this->add_key(GUI_ID_KEY_AUTOFWD_BUTTON, wgettext("Autoforward"), "keymap_autoforward");
+ this->add_key(GUI_ID_KEY_CHAT_BUTTON, wgettext("Chat"), "keymap_chat");
+ this->add_key(GUI_ID_KEY_SCREENSHOT_BUTTON,wgettext("Screenshot"), "keymap_screenshot");
+ this->add_key(GUI_ID_KEY_RANGE_BUTTON, wgettext("Range select"), "keymap_rangeselect");
+ this->add_key(GUI_ID_KEY_DEC_RANGE_BUTTON, wgettext("Dec. range"), "keymap_decrease_viewing_range_min");
+ this->add_key(GUI_ID_KEY_INC_RANGE_BUTTON, wgettext("Inc. range"), "keymap_increase_viewing_range_min");
+ this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, wgettext("Console"), "keymap_console");
+ this->add_key(GUI_ID_KEY_CMD_BUTTON, wgettext("Command"), "keymap_cmd");
+ this->add_key(GUI_ID_KEY_CMD_LOCAL_BUTTON, wgettext("Local command"), "keymap_cmd_local");
+ this->add_key(GUI_ID_KEY_HUD_BUTTON, wgettext("Toggle HUD"), "keymap_toggle_hud");
+ this->add_key(GUI_ID_KEY_CHATLOG_BUTTON, wgettext("Toggle chat log"), "keymap_toggle_chat");
+ this->add_key(GUI_ID_KEY_FOG_BUTTON, wgettext("Toggle fog"), "keymap_toggle_force_fog_off");
+}
+
diff --git a/src/gui/guiKeyChangeMenu.h b/src/gui/guiKeyChangeMenu.h
new file mode 100644
index 000000000..7cf11d3f9
--- /dev/null
+++ b/src/gui/guiKeyChangeMenu.h
@@ -0,0 +1,74 @@
+/*
+ Minetest
+ Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+ Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+ Copyright (C) 2013 teddydestodes <derkomtur@schattengang.net>
+
+ 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.
+ */
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "modalMenu.h"
+#include "gettext.h"
+#include "keycode.h"
+#include <string>
+#include <vector>
+
+struct key_setting
+{
+ int id;
+ const wchar_t *button_name;
+ KeyPress key;
+ std::string setting_name;
+ gui::IGUIButton *button;
+};
+
+class GUIKeyChangeMenu : public GUIModalMenu
+{
+public:
+ GUIKeyChangeMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+ IMenuManager *menumgr);
+ ~GUIKeyChangeMenu();
+
+ void removeChildren();
+ /*
+ Remove and re-add (or reposition) stuff
+ */
+ void regenerateGui(v2u32 screensize);
+
+ void drawMenu();
+
+ bool acceptInput();
+
+ bool OnEvent(const SEvent &event);
+
+ bool pausesGame() { return true; }
+
+private:
+ void init_keys();
+
+ bool resetMenu();
+
+ void add_key(int id, const wchar_t *button_name, const std::string &setting_name);
+
+ bool shift_down = false;
+ s32 activeKey = -1;
+
+ std::vector<KeyPress> key_used;
+ gui::IGUIStaticText *key_used_text = nullptr;
+ std::vector<key_setting *> key_settings;
+};
diff --git a/src/gui/guiMainMenu.h b/src/gui/guiMainMenu.h
new file mode 100644
index 000000000..43a3b1a33
--- /dev/null
+++ b/src/gui/guiMainMenu.h
@@ -0,0 +1,55 @@
+/*
+Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "modalMenu.h"
+#include <string>
+#include <list>
+
+struct MainMenuDataForScript {
+
+ MainMenuDataForScript() = default;
+
+ // Whether the server has requested a reconnect
+ bool reconnect_requested = false;
+ std::string errormessage = "";
+};
+
+struct MainMenuData {
+ // Client options
+ std::string servername;
+ std::string serverdescription;
+ std::string address;
+ std::string port;
+ std::string name;
+ std::string password;
+ // Whether to reconnect
+ bool do_reconnect = false;
+
+ // Server options
+ int selected_world = 0;
+ bool simple_singleplayer_mode = false;
+
+ // Data to be passed to the script
+ MainMenuDataForScript script_data;
+
+ MainMenuData() = default;
+};
diff --git a/src/gui/guiPasswordChange.cpp b/src/gui/guiPasswordChange.cpp
new file mode 100644
index 000000000..46de2026c
--- /dev/null
+++ b/src/gui/guiPasswordChange.cpp
@@ -0,0 +1,261 @@
+/*
+Part of Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include "guiPasswordChange.h"
+#include "client.h"
+#include <IGUICheckBox.h>
+#include <IGUIEditBox.h>
+#include <IGUIButton.h>
+#include <IGUIStaticText.h>
+#include <IGUIFont.h>
+
+#include "gettext.h"
+
+const int ID_oldPassword = 256;
+const int ID_newPassword1 = 257;
+const int ID_newPassword2 = 258;
+const int ID_change = 259;
+const int ID_message = 260;
+const int ID_cancel = 261;
+
+GUIPasswordChange::GUIPasswordChange(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent, s32 id,
+ IMenuManager *menumgr,
+ Client* client
+):
+ GUIModalMenu(env, parent, id, menumgr),
+ m_client(client)
+{
+}
+
+GUIPasswordChange::~GUIPasswordChange()
+{
+ removeChildren();
+}
+
+void GUIPasswordChange::removeChildren()
+{
+ const core::list<gui::IGUIElement *> &children = getChildren();
+ core::list<gui::IGUIElement *> children_copy;
+ for (gui::IGUIElement *i : children) {
+ children_copy.push_back(i);
+ }
+
+ for (gui::IGUIElement *i : children_copy) {
+ i->remove();
+ }
+}
+void GUIPasswordChange::regenerateGui(v2u32 screensize)
+{
+ /*
+ save current input
+ */
+ acceptInput();
+
+ /*
+ Remove stuff
+ */
+ removeChildren();
+
+ /*
+ Calculate new sizes and positions
+ */
+ core::rect<s32> rect(
+ screensize.X/2 - 580/2,
+ screensize.Y/2 - 300/2,
+ screensize.X/2 + 580/2,
+ screensize.Y/2 + 300/2
+ );
+
+ DesiredRect = rect;
+ recalculateAbsolutePosition(false);
+
+ v2s32 size = rect.getSize();
+ v2s32 topleft_client(40, 0);
+
+ const wchar_t *text;
+
+ /*
+ Add stuff
+ */
+ s32 ypos = 50;
+ {
+ core::rect<s32> rect(0, 0, 150, 20);
+ rect += topleft_client + v2s32(25, ypos + 6);
+ text = wgettext("Old Password");
+ Environment->addStaticText(text, rect, false, true, this, -1);
+ delete[] text;
+ }
+ {
+ core::rect<s32> rect(0, 0, 230, 30);
+ rect += topleft_client + v2s32(160, ypos);
+ gui::IGUIEditBox *e = Environment->addEditBox(
+ m_oldpass.c_str(), rect, true, this, ID_oldPassword);
+ Environment->setFocus(e);
+ e->setPasswordBox(true);
+ }
+ ypos += 50;
+ {
+ core::rect<s32> rect(0, 0, 150, 20);
+ rect += topleft_client + v2s32(25, ypos + 6);
+ text = wgettext("New Password");
+ Environment->addStaticText(text, rect, false, true, this, -1);
+ delete[] text;
+ }
+ {
+ core::rect<s32> rect(0, 0, 230, 30);
+ rect += topleft_client + v2s32(160, ypos);
+ gui::IGUIEditBox *e = Environment->addEditBox(
+ m_newpass.c_str(), rect, true, this, ID_newPassword1);
+ e->setPasswordBox(true);
+ }
+ ypos += 50;
+ {
+ core::rect<s32> rect(0, 0, 150, 20);
+ rect += topleft_client + v2s32(25, ypos + 6);
+ text = wgettext("Confirm Password");
+ Environment->addStaticText(text, rect, false, true, this, -1);
+ delete[] text;
+ }
+ {
+ core::rect<s32> rect(0, 0, 230, 30);
+ rect += topleft_client + v2s32(160, ypos);
+ gui::IGUIEditBox *e = Environment->addEditBox(
+ m_newpass_confirm.c_str(), rect, true, this, ID_newPassword2);
+ e->setPasswordBox(true);
+ }
+
+ ypos += 50;
+ {
+ core::rect<s32> rect(0, 0, 100, 30);
+ rect = rect + v2s32(size.X / 4 + 56, ypos);
+ text = wgettext("Change");
+ Environment->addButton(rect, this, ID_change, text);
+ delete[] text;
+ }
+ {
+ core::rect<s32> rect(0, 0, 100, 30);
+ rect = rect + v2s32(size.X / 4 + 185, ypos);
+ text = wgettext("Cancel");
+ Environment->addButton(rect, this, ID_cancel, text);
+ delete[] text;
+ }
+
+ ypos += 50;
+ {
+ core::rect<s32> rect(0, 0, 300, 20);
+ rect += topleft_client + v2s32(35, ypos);
+ text = wgettext("Passwords do not match!");
+ IGUIElement *e =
+ Environment->addStaticText(
+ text, rect, false, true, this, ID_message);
+ e->setVisible(false);
+ delete[] text;
+ }
+}
+
+void GUIPasswordChange::drawMenu()
+{
+ gui::IGUISkin *skin = Environment->getSkin();
+ if (!skin)
+ return;
+ video::IVideoDriver *driver = Environment->getVideoDriver();
+
+ video::SColor bgcolor(140, 0, 0, 0);
+ driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
+
+ gui::IGUIElement::draw();
+}
+
+void GUIPasswordChange::acceptInput()
+{
+ gui::IGUIElement *e;
+ e = getElementFromId(ID_oldPassword);
+ if (e != NULL)
+ m_oldpass = e->getText();
+ e = getElementFromId(ID_newPassword1);
+ if (e != NULL)
+ m_newpass = e->getText();
+ e = getElementFromId(ID_newPassword2);
+ if (e != NULL)
+ m_newpass_confirm = e->getText();
+}
+
+bool GUIPasswordChange::processInput()
+{
+ if (m_newpass != m_newpass_confirm) {
+ gui::IGUIElement *e = getElementFromId(ID_message);
+ if (e != NULL)
+ e->setVisible(true);
+ return false;
+ }
+ m_client->sendChangePassword(wide_to_utf8(m_oldpass), wide_to_utf8(m_newpass));
+ return true;
+}
+
+bool GUIPasswordChange::OnEvent(const SEvent &event)
+{
+ if (event.EventType == EET_KEY_INPUT_EVENT) {
+ if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) {
+ quitMenu();
+ return true;
+ }
+ if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) {
+ acceptInput();
+ if (processInput())
+ quitMenu();
+ return true;
+ }
+ }
+ if (event.EventType == EET_GUI_EVENT) {
+ if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST &&
+ isVisible()) {
+ if (!canTakeFocus(event.GUIEvent.Element)) {
+ dstream << "GUIPasswordChange: Not allowing focus change."
+ << std::endl;
+ // Returning true disables focus change
+ return true;
+ }
+ }
+ if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) {
+ switch (event.GUIEvent.Caller->getID()) {
+ case ID_change:
+ acceptInput();
+ if (processInput())
+ quitMenu();
+ return true;
+ case ID_cancel:
+ quitMenu();
+ return true;
+ }
+ }
+ if (event.GUIEvent.EventType == gui::EGET_EDITBOX_ENTER) {
+ switch (event.GUIEvent.Caller->getID()) {
+ case ID_oldPassword:
+ case ID_newPassword1:
+ case ID_newPassword2:
+ acceptInput();
+ if (processInput())
+ quitMenu();
+ return true;
+ }
+ }
+ }
+
+ return Parent ? Parent->OnEvent(event) : false;
+}
diff --git a/src/gui/guiPasswordChange.h b/src/gui/guiPasswordChange.h
new file mode 100644
index 000000000..59f3513b2
--- /dev/null
+++ b/src/gui/guiPasswordChange.h
@@ -0,0 +1,53 @@
+/*
+Part of Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "modalMenu.h"
+#include <string>
+
+class Client;
+
+class GUIPasswordChange : public GUIModalMenu
+{
+public:
+ GUIPasswordChange(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+ IMenuManager *menumgr, Client *client);
+ ~GUIPasswordChange();
+
+ void removeChildren();
+ /*
+ Remove and re-add (or reposition) stuff
+ */
+ void regenerateGui(v2u32 screensize);
+
+ void drawMenu();
+
+ void acceptInput();
+
+ bool processInput();
+
+ bool OnEvent(const SEvent &event);
+
+private:
+ Client *m_client;
+ std::wstring m_oldpass = L"";
+ std::wstring m_newpass = L"";
+ std::wstring m_newpass_confirm = L"";
+};
diff --git a/src/gui/guiPathSelectMenu.cpp b/src/gui/guiPathSelectMenu.cpp
new file mode 100644
index 000000000..b999f0a68
--- /dev/null
+++ b/src/gui/guiPathSelectMenu.cpp
@@ -0,0 +1,113 @@
+/*
+ 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.
+ */
+
+#include "guiPathSelectMenu.h"
+
+GUIFileSelectMenu::GUIFileSelectMenu(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent, s32 id, IMenuManager *menumgr,
+ const std::string &title, const std::string &formname,
+ bool is_file_select) :
+ GUIModalMenu(env, parent, id, menumgr),
+ m_title(utf8_to_wide(title)),
+ m_formname(formname),
+ m_file_select_dialog(is_file_select)
+{
+}
+
+GUIFileSelectMenu::~GUIFileSelectMenu()
+{
+ removeChildren();
+ setlocale(LC_NUMERIC, "C");
+}
+
+void GUIFileSelectMenu::regenerateGui(v2u32 screensize)
+{
+ removeChildren();
+ m_fileOpenDialog = 0;
+
+ core::dimension2du size(600, 400);
+ core::rect<s32> rect(0, 0, screensize.X, screensize.Y);
+
+ DesiredRect = rect;
+ recalculateAbsolutePosition(false);
+
+ m_fileOpenDialog =
+ Environment->addFileOpenDialog(m_title.c_str(), false, this, -1);
+
+ core::position2di pos = core::position2di(screensize.X / 2 - size.Width / 2,
+ screensize.Y / 2 - size.Height / 2);
+ m_fileOpenDialog->setRelativePosition(pos);
+ m_fileOpenDialog->setMinSize(size);
+}
+
+void GUIFileSelectMenu::drawMenu()
+{
+ gui::IGUISkin *skin = Environment->getSkin();
+ if (!skin)
+ return;
+
+ gui::IGUIElement::draw();
+}
+
+void GUIFileSelectMenu::acceptInput()
+{
+ if (m_text_dst && !m_formname.empty()) {
+ StringMap fields;
+ if (m_accepted) {
+ std::string path;
+ if (!m_file_select_dialog) {
+ core::string<fschar_t> string =
+ m_fileOpenDialog->getDirectoryName();
+ path = std::string(string.c_str());
+ } else {
+ path = wide_to_utf8(m_fileOpenDialog->getFileName());
+ }
+ fields[m_formname + "_accepted"] = path;
+ } else {
+ fields[m_formname + "_canceled"] = m_formname;
+ }
+ m_text_dst->gotText(fields);
+ }
+ quitMenu();
+}
+
+bool GUIFileSelectMenu::OnEvent(const SEvent &event)
+{
+ if (event.EventType == irr::EET_GUI_EVENT) {
+ switch (event.GUIEvent.EventType) {
+ case gui::EGET_ELEMENT_CLOSED:
+ case gui::EGET_FILE_CHOOSE_DIALOG_CANCELLED:
+ m_accepted = false;
+ acceptInput();
+ return true;
+ case gui::EGET_DIRECTORY_SELECTED:
+ m_accepted = !m_file_select_dialog;
+ acceptInput();
+ return true;
+ case gui::EGET_FILE_SELECTED:
+ m_accepted = m_file_select_dialog;
+ acceptInput();
+ return true;
+ default:
+ // ignore this event
+ break;
+ }
+ }
+ return Parent ? Parent->OnEvent(event) : false;
+}
diff --git a/src/gui/guiPathSelectMenu.h b/src/gui/guiPathSelectMenu.h
new file mode 100644
index 000000000..f69d0acd7
--- /dev/null
+++ b/src/gui/guiPathSelectMenu.h
@@ -0,0 +1,59 @@
+/*
+ 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.
+ */
+
+#pragma once
+
+#include <string>
+
+#include "modalMenu.h"
+#include "IGUIFileOpenDialog.h"
+#include "guiFormSpecMenu.h" //required because of TextDest only !!!
+
+class GUIFileSelectMenu : public GUIModalMenu
+{
+public:
+ GUIFileSelectMenu(gui::IGUIEnvironment *env, gui::IGUIElement *parent, s32 id,
+ IMenuManager *menumgr, const std::string &title,
+ const std::string &formid, bool is_file_select);
+ ~GUIFileSelectMenu();
+
+ /*
+ Remove and re-add (or reposition) stuff
+ */
+ void regenerateGui(v2u32 screensize);
+
+ void drawMenu();
+
+ bool OnEvent(const SEvent &event);
+
+ void setTextDest(TextDest *dest) { m_text_dst = dest; }
+
+private:
+ void acceptInput();
+
+ std::wstring m_title;
+ bool m_accepted = false;
+
+ gui::IGUIFileOpenDialog *m_fileOpenDialog = nullptr;
+
+ TextDest *m_text_dst = nullptr;
+
+ std::string m_formname;
+ bool m_file_select_dialog;
+};
diff --git a/src/gui/guiTable.cpp b/src/gui/guiTable.cpp
new file mode 100644
index 000000000..a2738afa9
--- /dev/null
+++ b/src/gui/guiTable.cpp
@@ -0,0 +1,1261 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+
+#include "guiTable.h"
+#include <queue>
+#include <sstream>
+#include <utility>
+#include <cstring>
+#include <IGUISkin.h>
+#include <IGUIFont.h>
+#include <IGUIScrollBar.h>
+#include "client/renderingengine.h"
+#include "debug.h"
+#include "log.h"
+#include "client/tile.h"
+#include "gettime.h"
+#include "util/string.h"
+#include "util/numeric.h"
+#include "util/string.h" // for parseColorString()
+#include "settings.h" // for settings
+#include "porting.h" // for dpi
+#include "guiscalingfilter.h"
+
+/*
+ GUITable
+*/
+
+GUITable::GUITable(gui::IGUIEnvironment *env,
+ gui::IGUIElement* parent, s32 id,
+ core::rect<s32> rectangle,
+ ISimpleTextureSource *tsrc
+):
+ gui::IGUIElement(gui::EGUIET_ELEMENT, env, parent, id, rectangle),
+ m_tsrc(tsrc)
+{
+ assert(tsrc != NULL);
+
+ gui::IGUISkin* skin = Environment->getSkin();
+
+ m_font = skin->getFont();
+ if (m_font) {
+ m_font->grab();
+ m_rowheight = m_font->getDimension(L"A").Height + 4;
+ m_rowheight = MYMAX(m_rowheight, 1);
+ }
+
+ const s32 s = skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
+ m_scrollbar = Environment->addScrollBar(false,
+ core::rect<s32>(RelativeRect.getWidth() - s,
+ 0,
+ RelativeRect.getWidth(),
+ RelativeRect.getHeight()),
+ this, -1);
+ m_scrollbar->setSubElement(true);
+ m_scrollbar->setTabStop(false);
+ m_scrollbar->setAlignment(gui::EGUIA_LOWERRIGHT, gui::EGUIA_LOWERRIGHT,
+ gui::EGUIA_UPPERLEFT, gui::EGUIA_LOWERRIGHT);
+ m_scrollbar->setVisible(false);
+ m_scrollbar->setPos(0);
+
+ setTabStop(true);
+ setTabOrder(-1);
+ updateAbsolutePosition();
+
+ core::rect<s32> relative_rect = m_scrollbar->getRelativePosition();
+ s32 width = (relative_rect.getWidth()/(2.0/3.0)) *
+ RenderingEngine::getDisplayDensity() *
+ g_settings->getFloat("gui_scaling");
+ m_scrollbar->setRelativePosition(core::rect<s32>(
+ relative_rect.LowerRightCorner.X-width,relative_rect.UpperLeftCorner.Y,
+ relative_rect.LowerRightCorner.X,relative_rect.LowerRightCorner.Y
+ ));
+}
+
+GUITable::~GUITable()
+{
+ for (GUITable::Row &row : m_rows)
+ delete[] row.cells;
+
+ if (m_font)
+ m_font->drop();
+
+ m_scrollbar->remove();
+}
+
+GUITable::Option GUITable::splitOption(const std::string &str)
+{
+ size_t equal_pos = str.find('=');
+ if (equal_pos == std::string::npos)
+ return GUITable::Option(str, "");
+
+ return GUITable::Option(str.substr(0, equal_pos),
+ str.substr(equal_pos + 1));
+}
+
+void GUITable::setTextList(const std::vector<std::string> &content,
+ bool transparent)
+{
+ clear();
+
+ if (transparent) {
+ m_background.setAlpha(0);
+ m_border = false;
+ }
+
+ m_is_textlist = true;
+
+ s32 empty_string_index = allocString("");
+
+ m_rows.resize(content.size());
+ for (s32 i = 0; i < (s32) content.size(); ++i) {
+ Row *row = &m_rows[i];
+ row->cells = new Cell[1];
+ row->cellcount = 1;
+ row->indent = 0;
+ row->visible_index = i;
+ m_visible_rows.push_back(i);
+
+ Cell *cell = row->cells;
+ cell->xmin = 0;
+ cell->xmax = 0x7fff; // something large enough
+ cell->xpos = 6;
+ cell->content_type = COLUMN_TYPE_TEXT;
+ cell->content_index = empty_string_index;
+ cell->tooltip_index = empty_string_index;
+ cell->color.set(255, 255, 255, 255);
+ cell->color_defined = false;
+ cell->reported_column = 1;
+
+ // parse row content (color)
+ const std::string &s = content[i];
+ if (s[0] == '#' && s[1] == '#') {
+ // double # to escape
+ cell->content_index = allocString(s.substr(2));
+ }
+ else if (s[0] == '#' && s.size() >= 7 &&
+ parseColorString(
+ s.substr(0,7), cell->color, false)) {
+ // single # for color
+ cell->color_defined = true;
+ cell->content_index = allocString(s.substr(7));
+ }
+ else {
+ // no #, just text
+ cell->content_index = allocString(s);
+ }
+
+ }
+
+ allocationComplete();
+
+ // Clamp scroll bar position
+ updateScrollBar();
+}
+
+void GUITable::setTable(const TableOptions &options,
+ const TableColumns &columns,
+ std::vector<std::string> &content)
+{
+ clear();
+
+ // Naming conventions:
+ // i is always a row index, 0-based
+ // j is always a column index, 0-based
+ // k is another index, for example an option index
+
+ // Handle a stupid error case... (issue #1187)
+ if (columns.empty()) {
+ TableColumn text_column;
+ text_column.type = "text";
+ TableColumns new_columns;
+ new_columns.push_back(text_column);
+ setTable(options, new_columns, content);
+ return;
+ }
+
+ // Handle table options
+ video::SColor default_color(255, 255, 255, 255);
+ s32 opendepth = 0;
+ for (const Option &option : options) {
+ const std::string &name = option.name;
+ const std::string &value = option.value;
+ if (name == "color")
+ parseColorString(value, m_color, false);
+ else if (name == "background")
+ parseColorString(value, m_background, false);
+ else if (name == "border")
+ m_border = is_yes(value);
+ else if (name == "highlight")
+ parseColorString(value, m_highlight, false);
+ else if (name == "highlight_text")
+ parseColorString(value, m_highlight_text, false);
+ else if (name == "opendepth")
+ opendepth = stoi(value);
+ else
+ errorstream<<"Invalid table option: \""<<name<<"\""
+ <<" (value=\""<<value<<"\")"<<std::endl;
+ }
+
+ // Get number of columns and rows
+ // note: error case columns.size() == 0 was handled above
+ s32 colcount = columns.size();
+ assert(colcount >= 1);
+ // rowcount = ceil(cellcount / colcount) but use integer arithmetic
+ s32 rowcount = (content.size() + colcount - 1) / colcount;
+ assert(rowcount >= 0);
+ // Append empty strings to content if there is an incomplete row
+ s32 cellcount = rowcount * colcount;
+ while (content.size() < (u32) cellcount)
+ content.emplace_back("");
+
+ // Create temporary rows (for processing columns)
+ struct TempRow {
+ // Current horizontal position (may different between rows due
+ // to indent/tree columns, or text/image columns with width<0)
+ s32 x;
+ // Tree indentation level
+ s32 indent;
+ // Next cell: Index into m_strings or m_images
+ s32 content_index;
+ // Next cell: Width in pixels
+ s32 content_width;
+ // Vector of completed cells in this row
+ std::vector<Cell> cells;
+ // Stores colors and how long they last (maximum column index)
+ std::vector<std::pair<video::SColor, s32> > colors;
+
+ TempRow(): x(0), indent(0), content_index(0), content_width(0) {}
+ };
+ TempRow *rows = new TempRow[rowcount];
+
+ // Get em width. Pedantically speaking, the width of "M" is not
+ // necessarily the same as the em width, but whatever, close enough.
+ s32 em = 6;
+ if (m_font)
+ em = m_font->getDimension(L"M").Width;
+
+ s32 default_tooltip_index = allocString("");
+
+ std::map<s32, s32> active_image_indices;
+
+ // Process content in column-major order
+ for (s32 j = 0; j < colcount; ++j) {
+ // Check column type
+ ColumnType columntype = COLUMN_TYPE_TEXT;
+ if (columns[j].type == "text")
+ columntype = COLUMN_TYPE_TEXT;
+ else if (columns[j].type == "image")
+ columntype = COLUMN_TYPE_IMAGE;
+ else if (columns[j].type == "color")
+ columntype = COLUMN_TYPE_COLOR;
+ else if (columns[j].type == "indent")
+ columntype = COLUMN_TYPE_INDENT;
+ else if (columns[j].type == "tree")
+ columntype = COLUMN_TYPE_TREE;
+ else
+ errorstream<<"Invalid table column type: \""
+ <<columns[j].type<<"\""<<std::endl;
+
+ // Process column options
+ s32 padding = myround(0.5 * em);
+ s32 tooltip_index = default_tooltip_index;
+ s32 align = 0;
+ s32 width = 0;
+ s32 span = colcount;
+
+ if (columntype == COLUMN_TYPE_INDENT) {
+ padding = 0; // default indent padding
+ }
+ if (columntype == COLUMN_TYPE_INDENT ||
+ columntype == COLUMN_TYPE_TREE) {
+ width = myround(em * 1.5); // default indent width
+ }
+
+ for (const Option &option : columns[j].options) {
+ const std::string &name = option.name;
+ const std::string &value = option.value;
+ if (name == "padding")
+ padding = myround(stof(value) * em);
+ else if (name == "tooltip")
+ tooltip_index = allocString(value);
+ else if (name == "align" && value == "left")
+ align = 0;
+ else if (name == "align" && value == "center")
+ align = 1;
+ else if (name == "align" && value == "right")
+ align = 2;
+ else if (name == "align" && value == "inline")
+ align = 3;
+ else if (name == "width")
+ width = myround(stof(value) * em);
+ else if (name == "span" && columntype == COLUMN_TYPE_COLOR)
+ span = stoi(value);
+ else if (columntype == COLUMN_TYPE_IMAGE &&
+ !name.empty() &&
+ string_allowed(name, "0123456789")) {
+ s32 content_index = allocImage(value);
+ active_image_indices.insert(std::make_pair(
+ stoi(name),
+ content_index));
+ }
+ else {
+ errorstream<<"Invalid table column option: \""<<name<<"\""
+ <<" (value=\""<<value<<"\")"<<std::endl;
+ }
+ }
+
+ // If current column type can use information from "color" columns,
+ // find out which of those is currently active
+ if (columntype == COLUMN_TYPE_TEXT) {
+ for (s32 i = 0; i < rowcount; ++i) {
+ TempRow *row = &rows[i];
+ while (!row->colors.empty() && row->colors.back().second < j)
+ row->colors.pop_back();
+ }
+ }
+
+ // Make template for new cells
+ Cell newcell;
+ memset(&newcell, 0, sizeof newcell);
+ newcell.content_type = columntype;
+ newcell.tooltip_index = tooltip_index;
+ newcell.reported_column = j+1;
+
+ if (columntype == COLUMN_TYPE_TEXT) {
+ // Find right edge of column
+ s32 xmax = 0;
+ for (s32 i = 0; i < rowcount; ++i) {
+ TempRow *row = &rows[i];
+ row->content_index = allocString(content[i * colcount + j]);
+ const core::stringw &text = m_strings[row->content_index];
+ row->content_width = m_font ?
+ m_font->getDimension(text.c_str()).Width : 0;
+ row->content_width = MYMAX(row->content_width, width);
+ s32 row_xmax = row->x + padding + row->content_width;
+ xmax = MYMAX(xmax, row_xmax);
+ }
+ // Add a new cell (of text type) to each row
+ for (s32 i = 0; i < rowcount; ++i) {
+ newcell.xmin = rows[i].x + padding;
+ alignContent(&newcell, xmax, rows[i].content_width, align);
+ newcell.content_index = rows[i].content_index;
+ newcell.color_defined = !rows[i].colors.empty();
+ if (newcell.color_defined)
+ newcell.color = rows[i].colors.back().first;
+ rows[i].cells.push_back(newcell);
+ rows[i].x = newcell.xmax;
+ }
+ }
+ else if (columntype == COLUMN_TYPE_IMAGE) {
+ // Find right edge of column
+ s32 xmax = 0;
+ for (s32 i = 0; i < rowcount; ++i) {
+ TempRow *row = &rows[i];
+ row->content_index = -1;
+
+ // Find content_index. Image indices are defined in
+ // column options so check active_image_indices.
+ s32 image_index = stoi(content[i * colcount + j]);
+ std::map<s32, s32>::iterator image_iter =
+ active_image_indices.find(image_index);
+ if (image_iter != active_image_indices.end())
+ row->content_index = image_iter->second;
+
+ // Get texture object (might be NULL)
+ video::ITexture *image = NULL;
+ if (row->content_index >= 0)
+ image = m_images[row->content_index];
+
+ // Get content width and update xmax
+ row->content_width = image ? image->getOriginalSize().Width : 0;
+ row->content_width = MYMAX(row->content_width, width);
+ s32 row_xmax = row->x + padding + row->content_width;
+ xmax = MYMAX(xmax, row_xmax);
+ }
+ // Add a new cell (of image type) to each row
+ for (s32 i = 0; i < rowcount; ++i) {
+ newcell.xmin = rows[i].x + padding;
+ alignContent(&newcell, xmax, rows[i].content_width, align);
+ newcell.content_index = rows[i].content_index;
+ rows[i].cells.push_back(newcell);
+ rows[i].x = newcell.xmax;
+ }
+ active_image_indices.clear();
+ }
+ else if (columntype == COLUMN_TYPE_COLOR) {
+ for (s32 i = 0; i < rowcount; ++i) {
+ video::SColor cellcolor(255, 255, 255, 255);
+ if (parseColorString(content[i * colcount + j], cellcolor, true))
+ rows[i].colors.emplace_back(cellcolor, j+span);
+ }
+ }
+ else if (columntype == COLUMN_TYPE_INDENT ||
+ columntype == COLUMN_TYPE_TREE) {
+ // For column type "tree", reserve additional space for +/-
+ // Also enable special processing for treeview-type tables
+ s32 content_width = 0;
+ if (columntype == COLUMN_TYPE_TREE) {
+ content_width = m_font ? m_font->getDimension(L"+").Width : 0;
+ m_has_tree_column = true;
+ }
+ // Add a new cell (of indent or tree type) to each row
+ for (s32 i = 0; i < rowcount; ++i) {
+ TempRow *row = &rows[i];
+
+ s32 indentlevel = stoi(content[i * colcount + j]);
+ indentlevel = MYMAX(indentlevel, 0);
+ if (columntype == COLUMN_TYPE_TREE)
+ row->indent = indentlevel;
+
+ newcell.xmin = row->x + padding;
+ newcell.xpos = newcell.xmin + indentlevel * width;
+ newcell.xmax = newcell.xpos + content_width;
+ newcell.content_index = 0;
+ newcell.color_defined = !rows[i].colors.empty();
+ if (newcell.color_defined)
+ newcell.color = rows[i].colors.back().first;
+ row->cells.push_back(newcell);
+ row->x = newcell.xmax;
+ }
+ }
+ }
+
+ // Copy temporary rows to not so temporary rows
+ if (rowcount >= 1) {
+ m_rows.resize(rowcount);
+ for (s32 i = 0; i < rowcount; ++i) {
+ Row *row = &m_rows[i];
+ row->cellcount = rows[i].cells.size();
+ row->cells = new Cell[row->cellcount];
+ memcpy((void*) row->cells, (void*) &rows[i].cells[0],
+ row->cellcount * sizeof(Cell));
+ row->indent = rows[i].indent;
+ row->visible_index = i;
+ m_visible_rows.push_back(i);
+ }
+ }
+
+ if (m_has_tree_column) {
+ // Treeview: convert tree to indent cells on leaf rows
+ for (s32 i = 0; i < rowcount; ++i) {
+ if (i == rowcount-1 || m_rows[i].indent >= m_rows[i+1].indent)
+ for (s32 j = 0; j < m_rows[i].cellcount; ++j)
+ if (m_rows[i].cells[j].content_type == COLUMN_TYPE_TREE)
+ m_rows[i].cells[j].content_type = COLUMN_TYPE_INDENT;
+ }
+
+ // Treeview: close rows according to opendepth option
+ std::set<s32> opened_trees;
+ for (s32 i = 0; i < rowcount; ++i)
+ if (m_rows[i].indent < opendepth)
+ opened_trees.insert(i);
+ setOpenedTrees(opened_trees);
+ }
+
+ // Delete temporary information used only during setTable()
+ delete[] rows;
+ allocationComplete();
+
+ // Clamp scroll bar position
+ updateScrollBar();
+}
+
+void GUITable::clear()
+{
+ // Clean up cells and rows
+ for (GUITable::Row &row : m_rows)
+ delete[] row.cells;
+ m_rows.clear();
+ m_visible_rows.clear();
+
+ // Get colors from skin
+ gui::IGUISkin *skin = Environment->getSkin();
+ m_color = skin->getColor(gui::EGDC_BUTTON_TEXT);
+ m_background = skin->getColor(gui::EGDC_3D_HIGH_LIGHT);
+ m_highlight = skin->getColor(gui::EGDC_HIGH_LIGHT);
+ m_highlight_text = skin->getColor(gui::EGDC_HIGH_LIGHT_TEXT);
+
+ // Reset members
+ m_is_textlist = false;
+ m_has_tree_column = false;
+ m_selected = -1;
+ m_sel_column = 0;
+ m_sel_doubleclick = false;
+ m_keynav_time = 0;
+ m_keynav_buffer = L"";
+ m_border = true;
+ m_strings.clear();
+ m_images.clear();
+ m_alloc_strings.clear();
+ m_alloc_images.clear();
+}
+
+std::string GUITable::checkEvent()
+{
+ s32 sel = getSelected();
+ assert(sel >= 0);
+
+ if (sel == 0) {
+ return "INV";
+ }
+
+ std::ostringstream os(std::ios::binary);
+ if (m_sel_doubleclick) {
+ os<<"DCL:";
+ m_sel_doubleclick = false;
+ }
+ else {
+ os<<"CHG:";
+ }
+ os<<sel;
+ if (!m_is_textlist) {
+ os<<":"<<m_sel_column;
+ }
+ return os.str();
+}
+
+s32 GUITable::getSelected() const
+{
+ if (m_selected < 0)
+ return 0;
+
+ assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size());
+ return m_visible_rows[m_selected] + 1;
+}
+
+void GUITable::setSelected(s32 index)
+{
+ s32 old_selected = m_selected;
+
+ m_selected = -1;
+ m_sel_column = 0;
+ m_sel_doubleclick = false;
+
+ --index; // Switch from 1-based indexing to 0-based indexing
+
+ s32 rowcount = m_rows.size();
+ if (rowcount == 0 || index < 0) {
+ return;
+ }
+
+ if (index >= rowcount) {
+ index = rowcount - 1;
+ }
+
+ // If the selected row is not visible, open its ancestors to make it visible
+ bool selection_invisible = m_rows[index].visible_index < 0;
+ if (selection_invisible) {
+ std::set<s32> opened_trees;
+ getOpenedTrees(opened_trees);
+ s32 indent = m_rows[index].indent;
+ for (s32 j = index - 1; j >= 0; --j) {
+ if (m_rows[j].indent < indent) {
+ opened_trees.insert(j);
+ indent = m_rows[j].indent;
+ }
+ }
+ setOpenedTrees(opened_trees);
+ }
+
+ if (index >= 0) {
+ m_selected = m_rows[index].visible_index;
+ assert(m_selected >= 0 && m_selected < (s32) m_visible_rows.size());
+ }
+
+ if (m_selected != old_selected || selection_invisible) {
+ autoScroll();
+ }
+}
+
+GUITable::DynamicData GUITable::getDynamicData() const
+{
+ DynamicData dyndata;
+ dyndata.selected = getSelected();
+ dyndata.scrollpos = m_scrollbar->getPos();
+ dyndata.keynav_time = m_keynav_time;
+ dyndata.keynav_buffer = m_keynav_buffer;
+ if (m_has_tree_column)
+ getOpenedTrees(dyndata.opened_trees);
+ return dyndata;
+}
+
+void GUITable::setDynamicData(const DynamicData &dyndata)
+{
+ if (m_has_tree_column)
+ setOpenedTrees(dyndata.opened_trees);
+
+ m_keynav_time = dyndata.keynav_time;
+ m_keynav_buffer = dyndata.keynav_buffer;
+
+ setSelected(dyndata.selected);
+ m_sel_column = 0;
+ m_sel_doubleclick = false;
+
+ m_scrollbar->setPos(dyndata.scrollpos);
+}
+
+const c8* GUITable::getTypeName() const
+{
+ return "GUITable";
+}
+
+void GUITable::updateAbsolutePosition()
+{
+ IGUIElement::updateAbsolutePosition();
+ updateScrollBar();
+}
+
+void GUITable::draw()
+{
+ if (!IsVisible)
+ return;
+
+ gui::IGUISkin *skin = Environment->getSkin();
+
+ // draw background
+
+ bool draw_background = m_background.getAlpha() > 0;
+ if (m_border)
+ skin->draw3DSunkenPane(this, m_background,
+ true, draw_background,
+ AbsoluteRect, &AbsoluteClippingRect);
+ else if (draw_background)
+ skin->draw2DRectangle(this, m_background,
+ AbsoluteRect, &AbsoluteClippingRect);
+
+ // get clipping rect
+
+ core::rect<s32> client_clip(AbsoluteRect);
+ client_clip.UpperLeftCorner.Y += 1;
+ client_clip.UpperLeftCorner.X += 1;
+ client_clip.LowerRightCorner.Y -= 1;
+ client_clip.LowerRightCorner.X -= 1;
+ if (m_scrollbar->isVisible()) {
+ client_clip.LowerRightCorner.X =
+ m_scrollbar->getAbsolutePosition().UpperLeftCorner.X;
+ }
+ client_clip.clipAgainst(AbsoluteClippingRect);
+
+ // draw visible rows
+
+ s32 scrollpos = m_scrollbar->getPos();
+ s32 row_min = scrollpos / m_rowheight;
+ s32 row_max = (scrollpos + AbsoluteRect.getHeight() - 1)
+ / m_rowheight + 1;
+ row_max = MYMIN(row_max, (s32) m_visible_rows.size());
+
+ core::rect<s32> row_rect(AbsoluteRect);
+ if (m_scrollbar->isVisible())
+ row_rect.LowerRightCorner.X -=
+ skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
+ row_rect.UpperLeftCorner.Y += row_min * m_rowheight - scrollpos;
+ row_rect.LowerRightCorner.Y = row_rect.UpperLeftCorner.Y + m_rowheight;
+
+ for (s32 i = row_min; i < row_max; ++i) {
+ Row *row = &m_rows[m_visible_rows[i]];
+ bool is_sel = i == m_selected;
+ video::SColor color = m_color;
+
+ if (is_sel) {
+ skin->draw2DRectangle(this, m_highlight, row_rect, &client_clip);
+ color = m_highlight_text;
+ }
+
+ for (s32 j = 0; j < row->cellcount; ++j)
+ drawCell(&row->cells[j], color, row_rect, client_clip);
+
+ row_rect.UpperLeftCorner.Y += m_rowheight;
+ row_rect.LowerRightCorner.Y += m_rowheight;
+ }
+
+ // Draw children
+ IGUIElement::draw();
+}
+
+void GUITable::drawCell(const Cell *cell, video::SColor color,
+ const core::rect<s32> &row_rect,
+ const core::rect<s32> &client_clip)
+{
+ if ((cell->content_type == COLUMN_TYPE_TEXT)
+ || (cell->content_type == COLUMN_TYPE_TREE)) {
+
+ core::rect<s32> text_rect = row_rect;
+ text_rect.UpperLeftCorner.X = row_rect.UpperLeftCorner.X
+ + cell->xpos;
+ text_rect.LowerRightCorner.X = row_rect.UpperLeftCorner.X
+ + cell->xmax;
+
+ if (cell->color_defined)
+ color = cell->color;
+
+ if (m_font) {
+ if (cell->content_type == COLUMN_TYPE_TEXT)
+ m_font->draw(m_strings[cell->content_index],
+ text_rect, color,
+ false, true, &client_clip);
+ else // tree
+ m_font->draw(cell->content_index ? L"+" : L"-",
+ text_rect, color,
+ false, true, &client_clip);
+ }
+ }
+ else if (cell->content_type == COLUMN_TYPE_IMAGE) {
+
+ if (cell->content_index < 0)
+ return;
+
+ video::IVideoDriver *driver = Environment->getVideoDriver();
+ video::ITexture *image = m_images[cell->content_index];
+
+ if (image) {
+ core::position2d<s32> dest_pos =
+ row_rect.UpperLeftCorner;
+ dest_pos.X += cell->xpos;
+ core::rect<s32> source_rect(
+ core::position2d<s32>(0, 0),
+ image->getOriginalSize());
+ s32 imgh = source_rect.LowerRightCorner.Y;
+ s32 rowh = row_rect.getHeight();
+ if (imgh < rowh)
+ dest_pos.Y += (rowh - imgh) / 2;
+ else
+ source_rect.LowerRightCorner.Y = rowh;
+
+ video::SColor color(255, 255, 255, 255);
+
+ driver->draw2DImage(image, dest_pos, source_rect,
+ &client_clip, color, true);
+ }
+ }
+}
+
+bool GUITable::OnEvent(const SEvent &event)
+{
+ if (!isEnabled())
+ return IGUIElement::OnEvent(event);
+
+ if (event.EventType == EET_KEY_INPUT_EVENT) {
+ if (event.KeyInput.PressedDown && (
+ event.KeyInput.Key == KEY_DOWN ||
+ event.KeyInput.Key == KEY_UP ||
+ event.KeyInput.Key == KEY_HOME ||
+ event.KeyInput.Key == KEY_END ||
+ event.KeyInput.Key == KEY_NEXT ||
+ event.KeyInput.Key == KEY_PRIOR)) {
+ s32 offset = 0;
+ switch (event.KeyInput.Key) {
+ case KEY_DOWN:
+ offset = 1;
+ break;
+ case KEY_UP:
+ offset = -1;
+ break;
+ case KEY_HOME:
+ offset = - (s32) m_visible_rows.size();
+ break;
+ case KEY_END:
+ offset = m_visible_rows.size();
+ break;
+ case KEY_NEXT:
+ offset = AbsoluteRect.getHeight() / m_rowheight;
+ break;
+ case KEY_PRIOR:
+ offset = - (s32) (AbsoluteRect.getHeight() / m_rowheight);
+ break;
+ default:
+ break;
+ }
+ s32 old_selected = m_selected;
+ s32 rowcount = m_visible_rows.size();
+ if (rowcount != 0) {
+ m_selected = rangelim(m_selected + offset, 0, rowcount-1);
+ autoScroll();
+ }
+
+ if (m_selected != old_selected)
+ sendTableEvent(0, false);
+
+ return true;
+ }
+
+ if (event.KeyInput.PressedDown && (
+ event.KeyInput.Key == KEY_LEFT ||
+ event.KeyInput.Key == KEY_RIGHT)) {
+ // Open/close subtree via keyboard
+ if (m_selected >= 0) {
+ int dir = event.KeyInput.Key == KEY_LEFT ? -1 : 1;
+ toggleVisibleTree(m_selected, dir, true);
+ }
+ return true;
+ }
+ else if (!event.KeyInput.PressedDown && (
+ event.KeyInput.Key == KEY_RETURN ||
+ event.KeyInput.Key == KEY_SPACE)) {
+ sendTableEvent(0, true);
+ return true;
+ }
+ else if (event.KeyInput.Key == KEY_ESCAPE ||
+ event.KeyInput.Key == KEY_SPACE) {
+ // pass to parent
+ }
+ else if (event.KeyInput.PressedDown && event.KeyInput.Char) {
+ // change selection based on text as it is typed
+ u64 now = porting::getTimeMs();
+ if (now - m_keynav_time >= 500)
+ m_keynav_buffer = L"";
+ m_keynav_time = now;
+
+ // add to key buffer if not a key repeat
+ if (!(m_keynav_buffer.size() == 1 &&
+ m_keynav_buffer[0] == event.KeyInput.Char)) {
+ m_keynav_buffer.append(event.KeyInput.Char);
+ }
+
+ // find the selected item, starting at the current selection
+ // don't change selection if the key buffer matches the current item
+ s32 old_selected = m_selected;
+ s32 start = MYMAX(m_selected, 0);
+ s32 rowcount = m_visible_rows.size();
+ for (s32 k = 1; k < rowcount; ++k) {
+ s32 current = start + k;
+ if (current >= rowcount)
+ current -= rowcount;
+ if (doesRowStartWith(getRow(current), m_keynav_buffer)) {
+ m_selected = current;
+ break;
+ }
+ }
+ autoScroll();
+ if (m_selected != old_selected)
+ sendTableEvent(0, false);
+
+ return true;
+ }
+ }
+ if (event.EventType == EET_MOUSE_INPUT_EVENT) {
+ core::position2d<s32> p(event.MouseInput.X, event.MouseInput.Y);
+
+ if (event.MouseInput.Event == EMIE_MOUSE_WHEEL) {
+ m_scrollbar->setPos(m_scrollbar->getPos() +
+ (event.MouseInput.Wheel < 0 ? -3 : 3) *
+ - (s32) m_rowheight / 2);
+ return true;
+ }
+
+ // Find hovered row and cell
+ bool really_hovering = false;
+ s32 row_i = getRowAt(p.Y, really_hovering);
+ const Cell *cell = NULL;
+ if (really_hovering) {
+ s32 cell_j = getCellAt(p.X, row_i);
+ if (cell_j >= 0)
+ cell = &(getRow(row_i)->cells[cell_j]);
+ }
+
+ // Update tooltip
+ setToolTipText(cell ? m_strings[cell->tooltip_index].c_str() : L"");
+
+ // Fix for #1567/#1806:
+ // IGUIScrollBar passes double click events to its parent,
+ // which we don't want. Detect this case and discard the event
+ if (event.MouseInput.Event != EMIE_MOUSE_MOVED &&
+ m_scrollbar->isVisible() &&
+ m_scrollbar->isPointInside(p))
+ return true;
+
+ if (event.MouseInput.isLeftPressed() &&
+ (isPointInside(p) ||
+ event.MouseInput.Event == EMIE_MOUSE_MOVED)) {
+ s32 sel_column = 0;
+ bool sel_doubleclick = (event.MouseInput.Event
+ == EMIE_LMOUSE_DOUBLE_CLICK);
+ bool plusminus_clicked = false;
+
+ // For certain events (left click), report column
+ // Also open/close subtrees when the +/- is clicked
+ if (cell && (
+ event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN ||
+ event.MouseInput.Event == EMIE_LMOUSE_DOUBLE_CLICK ||
+ event.MouseInput.Event == EMIE_LMOUSE_TRIPLE_CLICK)) {
+ sel_column = cell->reported_column;
+ if (cell->content_type == COLUMN_TYPE_TREE)
+ plusminus_clicked = true;
+ }
+
+ if (plusminus_clicked) {
+ if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
+ toggleVisibleTree(row_i, 0, false);
+ }
+ }
+ else {
+ // Normal selection
+ s32 old_selected = m_selected;
+ m_selected = row_i;
+ autoScroll();
+
+ if (m_selected != old_selected ||
+ sel_column >= 1 ||
+ sel_doubleclick) {
+ sendTableEvent(sel_column, sel_doubleclick);
+ }
+
+ // Treeview: double click opens/closes trees
+ if (m_has_tree_column && sel_doubleclick) {
+ toggleVisibleTree(m_selected, 0, false);
+ }
+ }
+ }
+ return true;
+ }
+ if (event.EventType == EET_GUI_EVENT &&
+ event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED &&
+ event.GUIEvent.Caller == m_scrollbar) {
+ // Don't pass events from our scrollbar to the parent
+ return true;
+ }
+
+ return IGUIElement::OnEvent(event);
+}
+
+/******************************************************************************/
+/* GUITable helper functions */
+/******************************************************************************/
+
+s32 GUITable::allocString(const std::string &text)
+{
+ std::map<std::string, s32>::iterator it = m_alloc_strings.find(text);
+ if (it == m_alloc_strings.end()) {
+ s32 id = m_strings.size();
+ std::wstring wtext = utf8_to_wide(text);
+ m_strings.emplace_back(wtext.c_str());
+ m_alloc_strings.insert(std::make_pair(text, id));
+ return id;
+ }
+
+ return it->second;
+}
+
+s32 GUITable::allocImage(const std::string &imagename)
+{
+ std::map<std::string, s32>::iterator it = m_alloc_images.find(imagename);
+ if (it == m_alloc_images.end()) {
+ s32 id = m_images.size();
+ m_images.push_back(m_tsrc->getTexture(imagename));
+ m_alloc_images.insert(std::make_pair(imagename, id));
+ return id;
+ }
+
+ return it->second;
+}
+
+void GUITable::allocationComplete()
+{
+ // Called when done with creating rows and cells from table data,
+ // i.e. when allocString and allocImage won't be called anymore
+ m_alloc_strings.clear();
+ m_alloc_images.clear();
+}
+
+const GUITable::Row* GUITable::getRow(s32 i) const
+{
+ if (i >= 0 && i < (s32) m_visible_rows.size())
+ return &m_rows[m_visible_rows[i]];
+
+ return NULL;
+}
+
+bool GUITable::doesRowStartWith(const Row *row, const core::stringw &str) const
+{
+ if (row == NULL)
+ return false;
+
+ for (s32 j = 0; j < row->cellcount; ++j) {
+ Cell *cell = &row->cells[j];
+ if (cell->content_type == COLUMN_TYPE_TEXT) {
+ const core::stringw &cellstr = m_strings[cell->content_index];
+ if (cellstr.size() >= str.size() &&
+ str.equals_ignore_case(cellstr.subString(0, str.size())))
+ return true;
+ }
+ }
+ return false;
+}
+
+s32 GUITable::getRowAt(s32 y, bool &really_hovering) const
+{
+ really_hovering = false;
+
+ s32 rowcount = m_visible_rows.size();
+ if (rowcount == 0)
+ return -1;
+
+ // Use arithmetic to find row
+ s32 rel_y = y - AbsoluteRect.UpperLeftCorner.Y - 1;
+ s32 i = (rel_y + m_scrollbar->getPos()) / m_rowheight;
+
+ if (i >= 0 && i < rowcount) {
+ really_hovering = true;
+ return i;
+ }
+ if (i < 0)
+ return 0;
+
+ return rowcount - 1;
+}
+
+s32 GUITable::getCellAt(s32 x, s32 row_i) const
+{
+ const Row *row = getRow(row_i);
+ if (row == NULL)
+ return -1;
+
+ // Use binary search to find cell in row
+ s32 rel_x = x - AbsoluteRect.UpperLeftCorner.X - 1;
+ s32 jmin = 0;
+ s32 jmax = row->cellcount - 1;
+ while (jmin < jmax) {
+ s32 pivot = jmin + (jmax - jmin) / 2;
+ assert(pivot >= 0 && pivot < row->cellcount);
+ const Cell *cell = &row->cells[pivot];
+
+ if (rel_x >= cell->xmin && rel_x <= cell->xmax)
+ return pivot;
+
+ if (rel_x < cell->xmin)
+ jmax = pivot - 1;
+ else
+ jmin = pivot + 1;
+ }
+
+ if (jmin >= 0 && jmin < row->cellcount &&
+ rel_x >= row->cells[jmin].xmin &&
+ rel_x <= row->cells[jmin].xmax)
+ return jmin;
+
+ return -1;
+}
+
+void GUITable::autoScroll()
+{
+ if (m_selected >= 0) {
+ s32 pos = m_scrollbar->getPos();
+ s32 maxpos = m_selected * m_rowheight;
+ s32 minpos = maxpos - (AbsoluteRect.getHeight() - m_rowheight);
+ if (pos > maxpos)
+ m_scrollbar->setPos(maxpos);
+ else if (pos < minpos)
+ m_scrollbar->setPos(minpos);
+ }
+}
+
+void GUITable::updateScrollBar()
+{
+ s32 totalheight = m_rowheight * m_visible_rows.size();
+ s32 scrollmax = MYMAX(0, totalheight - AbsoluteRect.getHeight());
+ m_scrollbar->setVisible(scrollmax > 0);
+ m_scrollbar->setMax(scrollmax);
+ m_scrollbar->setSmallStep(m_rowheight);
+ m_scrollbar->setLargeStep(2 * m_rowheight);
+}
+
+void GUITable::sendTableEvent(s32 column, bool doubleclick)
+{
+ m_sel_column = column;
+ m_sel_doubleclick = doubleclick;
+ if (Parent) {
+ SEvent e;
+ memset(&e, 0, sizeof e);
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = 0;
+ e.GUIEvent.EventType = gui::EGET_TABLE_CHANGED;
+ Parent->OnEvent(e);
+ }
+}
+
+void GUITable::getOpenedTrees(std::set<s32> &opened_trees) const
+{
+ opened_trees.clear();
+ s32 rowcount = m_rows.size();
+ for (s32 i = 0; i < rowcount - 1; ++i) {
+ if (m_rows[i].indent < m_rows[i+1].indent &&
+ m_rows[i+1].visible_index != -2)
+ opened_trees.insert(i);
+ }
+}
+
+void GUITable::setOpenedTrees(const std::set<s32> &opened_trees)
+{
+ s32 old_selected = -1;
+ if (m_selected >= 0)
+ old_selected = m_visible_rows[m_selected];
+
+ std::vector<s32> parents;
+ std::vector<s32> closed_parents;
+
+ m_visible_rows.clear();
+
+ for (size_t i = 0; i < m_rows.size(); ++i) {
+ Row *row = &m_rows[i];
+
+ // Update list of ancestors
+ while (!parents.empty() && m_rows[parents.back()].indent >= row->indent)
+ parents.pop_back();
+ while (!closed_parents.empty() &&
+ m_rows[closed_parents.back()].indent >= row->indent)
+ closed_parents.pop_back();
+
+ assert(closed_parents.size() <= parents.size());
+
+ if (closed_parents.empty()) {
+ // Visible row
+ row->visible_index = m_visible_rows.size();
+ m_visible_rows.push_back(i);
+ }
+ else if (parents.back() == closed_parents.back()) {
+ // Invisible row, direct parent is closed
+ row->visible_index = -2;
+ }
+ else {
+ // Invisible row, direct parent is open, some ancestor is closed
+ row->visible_index = -1;
+ }
+
+ // If not a leaf, add to parents list
+ if (i < m_rows.size()-1 && row->indent < m_rows[i+1].indent) {
+ parents.push_back(i);
+
+ s32 content_index = 0; // "-", open
+ if (opened_trees.count(i) == 0) {
+ closed_parents.push_back(i);
+ content_index = 1; // "+", closed
+ }
+
+ // Update all cells of type "tree"
+ for (s32 j = 0; j < row->cellcount; ++j)
+ if (row->cells[j].content_type == COLUMN_TYPE_TREE)
+ row->cells[j].content_index = content_index;
+ }
+ }
+
+ updateScrollBar();
+
+ // m_selected must be updated since it is a visible row index
+ if (old_selected >= 0)
+ m_selected = m_rows[old_selected].visible_index;
+}
+
+void GUITable::openTree(s32 to_open)
+{
+ std::set<s32> opened_trees;
+ getOpenedTrees(opened_trees);
+ opened_trees.insert(to_open);
+ setOpenedTrees(opened_trees);
+}
+
+void GUITable::closeTree(s32 to_close)
+{
+ std::set<s32> opened_trees;
+ getOpenedTrees(opened_trees);
+ opened_trees.erase(to_close);
+ setOpenedTrees(opened_trees);
+}
+
+// The following function takes a visible row index (hidden rows skipped)
+// dir: -1 = left (close), 0 = auto (toggle), 1 = right (open)
+void GUITable::toggleVisibleTree(s32 row_i, int dir, bool move_selection)
+{
+ // Check if the chosen tree is currently open
+ const Row *row = getRow(row_i);
+ if (row == NULL)
+ return;
+
+ bool was_open = false;
+ for (s32 j = 0; j < row->cellcount; ++j) {
+ if (row->cells[j].content_type == COLUMN_TYPE_TREE) {
+ was_open = row->cells[j].content_index == 0;
+ break;
+ }
+ }
+
+ // Check if the chosen tree should be opened
+ bool do_open = !was_open;
+ if (dir < 0)
+ do_open = false;
+ else if (dir > 0)
+ do_open = true;
+
+ // Close or open the tree; the heavy lifting is done by setOpenedTrees
+ if (was_open && !do_open)
+ closeTree(m_visible_rows[row_i]);
+ else if (!was_open && do_open)
+ openTree(m_visible_rows[row_i]);
+
+ // Change selected row if requested by caller,
+ // this is useful for keyboard navigation
+ if (move_selection) {
+ s32 sel = row_i;
+ if (was_open && do_open) {
+ // Move selection to first child
+ const Row *maybe_child = getRow(sel + 1);
+ if (maybe_child && maybe_child->indent > row->indent)
+ sel++;
+ }
+ else if (!was_open && !do_open) {
+ // Move selection to parent
+ assert(getRow(sel) != NULL);
+ while (sel > 0 && getRow(sel - 1)->indent >= row->indent)
+ sel--;
+ sel--;
+ if (sel < 0) // was root already selected?
+ sel = row_i;
+ }
+ if (sel != m_selected) {
+ m_selected = sel;
+ autoScroll();
+ sendTableEvent(0, false);
+ }
+ }
+}
+
+void GUITable::alignContent(Cell *cell, s32 xmax, s32 content_width, s32 align)
+{
+ // requires that cell.xmin, cell.xmax are properly set
+ // align = 0: left aligned, 1: centered, 2: right aligned, 3: inline
+ if (align == 0) {
+ cell->xpos = cell->xmin;
+ cell->xmax = xmax;
+ }
+ else if (align == 1) {
+ cell->xpos = (cell->xmin + xmax - content_width) / 2;
+ cell->xmax = xmax;
+ }
+ else if (align == 2) {
+ cell->xpos = xmax - content_width;
+ cell->xmax = xmax;
+ }
+ else {
+ // inline alignment: the cells of the column don't have an aligned
+ // right border, the right border of each cell depends on the content
+ cell->xpos = cell->xmin;
+ cell->xmax = cell->xmin + content_width;
+ }
+}
diff --git a/src/gui/guiTable.h b/src/gui/guiTable.h
new file mode 100644
index 000000000..f9337ff6d
--- /dev/null
+++ b/src/gui/guiTable.h
@@ -0,0 +1,256 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+#include <iostream>
+
+#include "irrlichttypes_extrabloated.h"
+
+class ISimpleTextureSource;
+
+/*
+ A table GUI element for GUIFormSpecMenu.
+
+ Sends a EGET_TABLE_CHANGED event to the parent when
+ an item is selected or double-clicked.
+ Call checkEvent() to get info.
+
+ Credits: The interface and implementation of this class are (very)
+ loosely based on the Irrlicht classes CGUITable and CGUIListBox.
+ CGUITable and CGUIListBox are licensed under the Irrlicht license;
+ they are Copyright (C) 2002-2012 Nikolaus Gebhardt
+*/
+class GUITable : public gui::IGUIElement
+{
+public:
+ /*
+ Stores dynamic data that should be preserved
+ when updating a formspec
+ */
+ struct DynamicData
+ {
+ s32 selected = 0;
+ s32 scrollpos = 0;
+ s32 keynav_time = 0;
+ core::stringw keynav_buffer;
+ std::set<s32> opened_trees;
+ };
+
+ /*
+ An option of the form <name>=<value>
+ */
+ struct Option
+ {
+ std::string name;
+ std::string value;
+
+ Option(const std::string &name_, const std::string &value_) :
+ name(name_),
+ value(value_)
+ {}
+ };
+
+ /*
+ A list of options that concern the entire table
+ */
+ typedef std::vector<Option> TableOptions;
+
+ /*
+ A column with options
+ */
+ struct TableColumn
+ {
+ std::string type;
+ std::vector<Option> options;
+ };
+ typedef std::vector<TableColumn> TableColumns;
+
+
+ GUITable(gui::IGUIEnvironment *env,
+ gui::IGUIElement *parent, s32 id,
+ core::rect<s32> rectangle,
+ ISimpleTextureSource *tsrc);
+
+ virtual ~GUITable();
+
+ /* Split a string of the form "name=value" into name and value */
+ static Option splitOption(const std::string &str);
+
+ /* Set textlist-like options, columns and data */
+ void setTextList(const std::vector<std::string> &content,
+ bool transparent);
+
+ /* Set generic table options, columns and content */
+ // Adds empty strings to end of content if there is an incomplete row
+ void setTable(const TableOptions &options,
+ const TableColumns &columns,
+ std::vector<std::string> &content);
+
+ /* Clear the table */
+ void clear();
+
+ /* Get info about last event (string such as "CHG:1:2") */
+ // Call this after EGET_TABLE_CHANGED
+ std::string checkEvent();
+
+ /* Get index of currently selected row (first=1; 0 if none selected) */
+ s32 getSelected() const;
+
+ /* Set currently selected row (first=1; 0 if none selected) */
+ // If given index is not visible at the moment, select its parent
+ // Autoscroll to make the selected row fully visible
+ void setSelected(s32 index);
+
+ /* Get selection, scroll position and opened (sub)trees */
+ DynamicData getDynamicData() const;
+
+ /* Set selection, scroll position and opened (sub)trees */
+ void setDynamicData(const DynamicData &dyndata);
+
+ /* Returns "GUITable" */
+ virtual const c8* getTypeName() const;
+
+ /* Must be called when position or size changes */
+ virtual void updateAbsolutePosition();
+
+ /* Irrlicht draw method */
+ virtual void draw();
+
+ /* Irrlicht event handler */
+ virtual bool OnEvent(const SEvent &event);
+
+protected:
+ enum ColumnType {
+ COLUMN_TYPE_TEXT,
+ COLUMN_TYPE_IMAGE,
+ COLUMN_TYPE_COLOR,
+ COLUMN_TYPE_INDENT,
+ COLUMN_TYPE_TREE,
+ };
+
+ struct Cell {
+ s32 xmin;
+ s32 xmax;
+ s32 xpos;
+ ColumnType content_type;
+ s32 content_index;
+ s32 tooltip_index;
+ video::SColor color;
+ bool color_defined;
+ s32 reported_column;
+ };
+
+ struct Row {
+ Cell *cells;
+ s32 cellcount;
+ s32 indent;
+ // visible_index >= 0: is index of row in m_visible_rows
+ // visible_index == -1: parent open but other ancestor closed
+ // visible_index == -2: parent closed
+ s32 visible_index;
+ };
+
+ // Texture source
+ ISimpleTextureSource *m_tsrc;
+
+ // Table content (including hidden rows)
+ std::vector<Row> m_rows;
+ // Table content (only visible; indices into m_rows)
+ std::vector<s32> m_visible_rows;
+ bool m_is_textlist = false;
+ bool m_has_tree_column = false;
+
+ // Selection status
+ s32 m_selected = -1; // index of row (1...n), or 0 if none selected
+ s32 m_sel_column = 0;
+ bool m_sel_doubleclick = false;
+
+ // Keyboard navigation stuff
+ u64 m_keynav_time = 0;
+ core::stringw m_keynav_buffer = L"";
+
+ // Drawing and geometry information
+ bool m_border = true;
+ video::SColor m_color = video::SColor(255, 255, 255, 255);
+ video::SColor m_background = video::SColor(255, 0, 0, 0);
+ video::SColor m_highlight = video::SColor(255, 70, 100, 50);
+ video::SColor m_highlight_text = video::SColor(255, 255, 255, 255);
+ s32 m_rowheight = 1;
+ gui::IGUIFont *m_font = nullptr;
+ gui::IGUIScrollBar *m_scrollbar = nullptr;
+
+ // Allocated strings and images
+ std::vector<core::stringw> m_strings;
+ std::vector<video::ITexture*> m_images;
+ std::map<std::string, s32> m_alloc_strings;
+ std::map<std::string, s32> m_alloc_images;
+
+ s32 allocString(const std::string &text);
+ s32 allocImage(const std::string &imagename);
+ void allocationComplete();
+
+ // Helper for draw() that draws a single cell
+ void drawCell(const Cell *cell, video::SColor color,
+ const core::rect<s32> &rowrect,
+ const core::rect<s32> &client_clip);
+
+ // Returns the i-th visible row (NULL if i is invalid)
+ const Row *getRow(s32 i) const;
+
+ // Key navigation helper
+ bool doesRowStartWith(const Row *row, const core::stringw &str) const;
+
+ // Returns the row at a given screen Y coordinate
+ // Returns index i such that m_rows[i] is valid (or -1 on error)
+ s32 getRowAt(s32 y, bool &really_hovering) const;
+
+ // Returns the cell at a given screen X coordinate within m_rows[row_i]
+ // Returns index j such that m_rows[row_i].cells[j] is valid
+ // (or -1 on error)
+ s32 getCellAt(s32 x, s32 row_i) const;
+
+ // Make the selected row fully visible
+ void autoScroll();
+
+ // Should be called when m_rowcount or m_rowheight changes
+ void updateScrollBar();
+
+ // Sends EET_GUI_EVENT / EGET_TABLE_CHANGED to parent
+ void sendTableEvent(s32 column, bool doubleclick);
+
+ // Functions that help deal with hidden rows
+ // The following functions take raw row indices (hidden rows not skipped)
+ void getOpenedTrees(std::set<s32> &opened_trees) const;
+ void setOpenedTrees(const std::set<s32> &opened_trees);
+ void openTree(s32 to_open);
+ void closeTree(s32 to_close);
+ // The following function takes a visible row index (hidden rows skipped)
+ // dir: -1 = left (close), 0 = auto (toggle), 1 = right (open)
+ void toggleVisibleTree(s32 row_i, int dir, bool move_selection);
+
+ // Aligns cell content in column according to alignment specification
+ // align = 0: left aligned, 1: centered, 2: right aligned, 3: inline
+ static void alignContent(Cell *cell, s32 xmax, s32 content_width,
+ s32 align);
+};
diff --git a/src/gui/guiVolumeChange.cpp b/src/gui/guiVolumeChange.cpp
new file mode 100644
index 000000000..8c462312b
--- /dev/null
+++ b/src/gui/guiVolumeChange.cpp
@@ -0,0 +1,196 @@
+/*
+Part of Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include "guiVolumeChange.h"
+#include "debug.h"
+#include "serialization.h"
+#include <string>
+#include <IGUICheckBox.h>
+#include <IGUIButton.h>
+#include <IGUIScrollBar.h>
+#include <IGUIStaticText.h>
+#include <IGUIFont.h>
+#include "settings.h"
+
+#include "gettext.h"
+
+const int ID_soundText = 263;
+const int ID_soundExitButton = 264;
+const int ID_soundSlider = 265;
+const int ID_soundMuteButton = 266;
+
+GUIVolumeChange::GUIVolumeChange(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent, s32 id,
+ IMenuManager *menumgr
+):
+ GUIModalMenu(env, parent, id, menumgr)
+{
+}
+
+GUIVolumeChange::~GUIVolumeChange()
+{
+ removeChildren();
+}
+
+void GUIVolumeChange::removeChildren()
+{
+ if (gui::IGUIElement *e = getElementFromId(ID_soundText))
+ e->remove();
+
+ if (gui::IGUIElement *e = getElementFromId(ID_soundExitButton))
+ e->remove();
+
+ if (gui::IGUIElement *e = getElementFromId(ID_soundSlider))
+ e->remove();
+}
+
+void GUIVolumeChange::regenerateGui(v2u32 screensize)
+{
+ /*
+ Remove stuff
+ */
+ removeChildren();
+
+ /*
+ Calculate new sizes and positions
+ */
+ DesiredRect = core::rect<s32>(
+ screensize.X/2 - 380/2,
+ screensize.Y/2 - 200/2,
+ screensize.X/2 + 380/2,
+ screensize.Y/2 + 200/2
+ );
+ recalculateAbsolutePosition(false);
+
+ v2s32 size = DesiredRect.getSize();
+ int volume = (int)(g_settings->getFloat("sound_volume") * 100);
+
+ /*
+ Add stuff
+ */
+ {
+ core::rect<s32> rect(0, 0, 160, 20);
+ rect = rect + v2s32(size.X / 2 - 80, size.Y / 2 - 70);
+
+ const wchar_t *text = wgettext("Sound Volume: ");
+ core::stringw volume_text = text;
+ delete [] text;
+
+ volume_text += core::stringw(volume) + core::stringw("%");
+ Environment->addStaticText(volume_text.c_str(), rect, false,
+ true, this, ID_soundText);
+ }
+ {
+ core::rect<s32> rect(0, 0, 80, 30);
+ rect = rect + v2s32(size.X/2-80/2, size.Y/2+55);
+ const wchar_t *text = wgettext("Exit");
+ Environment->addButton(rect, this, ID_soundExitButton,
+ text);
+ delete[] text;
+ }
+ {
+ core::rect<s32> rect(0, 0, 300, 20);
+ rect = rect + v2s32(size.X / 2 - 150, size.Y / 2);
+ gui::IGUIScrollBar *e = Environment->addScrollBar(true,
+ rect, this, ID_soundSlider);
+ e->setMax(100);
+ e->setPos(volume);
+ }
+ {
+ core::rect<s32> rect(0, 0, 160, 20);
+ rect = rect + v2s32(size.X / 2 - 80, size.Y / 2 - 35);
+ const wchar_t *text = wgettext("Muted");
+ Environment->addCheckBox(g_settings->getBool("mute_sound"), rect, this,
+ ID_soundMuteButton, text);
+ delete[] text;
+ }
+}
+
+void GUIVolumeChange::drawMenu()
+{
+ gui::IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+ video::SColor bgcolor(140, 0, 0, 0);
+ driver->draw2DRectangle(bgcolor, AbsoluteRect, &AbsoluteClippingRect);
+ gui::IGUIElement::draw();
+}
+
+bool GUIVolumeChange::OnEvent(const SEvent& event)
+{
+ if (event.EventType == EET_KEY_INPUT_EVENT) {
+ if (event.KeyInput.Key == KEY_ESCAPE && event.KeyInput.PressedDown) {
+ quitMenu();
+ return true;
+ }
+
+ if (event.KeyInput.Key == KEY_RETURN && event.KeyInput.PressedDown) {
+ quitMenu();
+ return true;
+ }
+ } else if (event.EventType == EET_GUI_EVENT) {
+ if (event.GUIEvent.EventType == gui::EGET_CHECKBOX_CHANGED) {
+ gui::IGUIElement *e = getElementFromId(ID_soundMuteButton);
+ if (e != NULL && e->getType() == gui::EGUIET_CHECK_BOX) {
+ g_settings->setBool("mute_sound", ((gui::IGUICheckBox*)e)->isChecked());
+ }
+
+ Environment->setFocus(this);
+ return true;
+ }
+
+ if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED) {
+ if (event.GUIEvent.Caller->getID() == ID_soundExitButton) {
+ quitMenu();
+ return true;
+ }
+ Environment->setFocus(this);
+ }
+
+ if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
+ && isVisible()) {
+ if (!canTakeFocus(event.GUIEvent.Element)) {
+ dstream << "GUIMainMenu: Not allowing focus change."
+ << std::endl;
+ // Returning true disables focus change
+ return true;
+ }
+ }
+ if (event.GUIEvent.EventType == gui::EGET_SCROLL_BAR_CHANGED) {
+ if (event.GUIEvent.Caller->getID() == ID_soundSlider) {
+ s32 pos = ((gui::IGUIScrollBar*)event.GUIEvent.Caller)->getPos();
+ g_settings->setFloat("sound_volume", (float) pos / 100);
+
+ gui::IGUIElement *e = getElementFromId(ID_soundText);
+ const wchar_t *text = wgettext("Sound Volume: ");
+ core::stringw volume_text = text;
+ delete [] text;
+
+ volume_text += core::stringw(pos) + core::stringw("%");
+ e->setText(volume_text.c_str());
+ return true;
+ }
+ }
+
+ }
+
+ return Parent ? Parent->OnEvent(event) : false;
+}
+
diff --git a/src/gui/guiVolumeChange.h b/src/gui/guiVolumeChange.h
new file mode 100644
index 000000000..7c7e19a86
--- /dev/null
+++ b/src/gui/guiVolumeChange.h
@@ -0,0 +1,45 @@
+/*
+Part of Minetest
+Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+Copyright (C) 2013 Ciaran Gultnieks <ciaran@ciarang.com>
+Copyright (C) 2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl>
+
+Permission to use, copy, modify, and distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#include "modalMenu.h"
+#include <string>
+
+class GUIVolumeChange : public GUIModalMenu
+{
+public:
+ GUIVolumeChange(gui::IGUIEnvironment* env,
+ gui::IGUIElement* parent, s32 id,
+ IMenuManager *menumgr);
+ ~GUIVolumeChange();
+
+ void removeChildren();
+ /*
+ Remove and re-add (or reposition) stuff
+ */
+ void regenerateGui(v2u32 screensize);
+
+ void drawMenu();
+
+ bool OnEvent(const SEvent& event);
+
+ bool pausesGame() { return true; }
+};
diff --git a/src/gui/intlGUIEditBox.cpp b/src/gui/intlGUIEditBox.cpp
new file mode 100644
index 000000000..279e7a48a
--- /dev/null
+++ b/src/gui/intlGUIEditBox.cpp
@@ -0,0 +1,1601 @@
+// 11.11.2011 11:11 ValkaTR
+//
+// This is a copy of intlGUIEditBox from the irrlicht, but with a
+// fix in the OnEvent function, which doesn't allowed input of
+// other keyboard layouts than latin-1
+//
+// Characters like: ä ö ü õ ы й ю я ъ № € ° ...
+//
+// This fix is only needed for linux, because of a bug
+// in the CIrrDeviceLinux.cpp:1014-1015 of the irrlicht
+//
+// Also locale in the programm should not be changed to
+// a "C", "POSIX" or whatever, it should be set to "",
+// or XLookupString will return nothing for the international
+// characters.
+//
+// From the "man setlocale":
+//
+// On startup of the main program, the portable "C" locale
+// is selected as default. A program may be made
+// portable to all locales by calling:
+//
+// setlocale(LC_ALL, "");
+//
+// after program initialization....
+//
+
+// Copyright (C) 2002-2013 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#include <util/numeric.h>
+#include "intlGUIEditBox.h"
+
+#if defined(_IRR_COMPILE_WITH_GUI_) && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9
+
+#include "IGUISkin.h"
+#include "IGUIEnvironment.h"
+#include "IGUIFont.h"
+#include "IVideoDriver.h"
+//#include "rect.h"
+//#include "irrlicht/os.cpp"
+#include "porting.h"
+//#include "Keycodes.h"
+#include "log.h"
+
+/*
+ todo:
+ optional scrollbars
+ ctrl+left/right to select word
+ double click/ctrl click: word select + drag to select whole words, triple click to select line
+ optional? dragging selected text
+ numerical
+*/
+
+namespace irr
+{
+namespace gui
+{
+
+//! constructor
+intlGUIEditBox::intlGUIEditBox(const wchar_t* text, bool border,
+ IGUIEnvironment* environment, IGUIElement* parent, s32 id,
+ const core::rect<s32>& rectangle, bool writable, bool has_vscrollbar)
+ : IGUIEditBox(environment, parent, id, rectangle),
+ Border(border), FrameRect(rectangle),
+ m_scrollbar_width(0), m_vscrollbar(NULL), m_writable(writable)
+{
+ #ifdef _DEBUG
+ setDebugName("intlintlGUIEditBox");
+ #endif
+
+ Text = text;
+
+ if (Environment)
+ Operator = Environment->getOSOperator();
+
+ if (Operator)
+ Operator->grab();
+
+ // this element can be tabbed to
+ setTabStop(true);
+ setTabOrder(-1);
+
+ IGUISkin *skin = 0;
+ if (Environment)
+ skin = Environment->getSkin();
+ if (Border && skin)
+ {
+ FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
+ FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
+ FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
+ FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
+ }
+
+ if (skin && has_vscrollbar) {
+ m_scrollbar_width = skin->getSize(gui::EGDS_SCROLLBAR_SIZE);
+
+ if (m_scrollbar_width > 0) {
+ createVScrollBar();
+ }
+ }
+
+ breakText();
+
+ calculateScrollPos();
+ setWritable(writable);
+}
+
+
+//! destructor
+intlGUIEditBox::~intlGUIEditBox()
+{
+ if (OverrideFont)
+ OverrideFont->drop();
+
+ if (Operator)
+ Operator->drop();
+}
+
+
+//! Sets another skin independent font.
+void intlGUIEditBox::setOverrideFont(IGUIFont* font)
+{
+ if (OverrideFont == font)
+ return;
+
+ if (OverrideFont)
+ OverrideFont->drop();
+
+ OverrideFont = font;
+
+ if (OverrideFont)
+ OverrideFont->grab();
+
+ breakText();
+}
+
+IGUIFont * intlGUIEditBox::getOverrideFont() const
+{
+ return OverrideFont;
+}
+
+//! Get the font which is used right now for drawing
+IGUIFont* intlGUIEditBox::getActiveFont() const
+{
+ if ( OverrideFont )
+ return OverrideFont;
+ IGUISkin* skin = Environment->getSkin();
+ if (skin)
+ return skin->getFont();
+ return 0;
+}
+
+//! Sets another color for the text.
+void intlGUIEditBox::setOverrideColor(video::SColor color)
+{
+ OverrideColor = color;
+ OverrideColorEnabled = true;
+}
+
+video::SColor intlGUIEditBox::getOverrideColor() const
+{
+ return OverrideColor;
+}
+
+//! Turns the border on or off
+void intlGUIEditBox::setDrawBorder(bool border)
+{
+ Border = border;
+}
+
+//! Sets whether to draw the background
+void intlGUIEditBox::setDrawBackground(bool draw)
+{
+}
+
+//! Sets if the text should use the overide color or the color in the gui skin.
+void intlGUIEditBox::enableOverrideColor(bool enable)
+{
+ OverrideColorEnabled = enable;
+}
+
+bool intlGUIEditBox::isOverrideColorEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return OverrideColorEnabled;
+}
+
+//! Enables or disables word wrap
+void intlGUIEditBox::setWordWrap(bool enable)
+{
+ WordWrap = enable;
+ breakText();
+}
+
+
+void intlGUIEditBox::updateAbsolutePosition()
+{
+ core::rect<s32> oldAbsoluteRect(AbsoluteRect);
+ IGUIElement::updateAbsolutePosition();
+ if ( oldAbsoluteRect != AbsoluteRect )
+ {
+ breakText();
+ }
+}
+
+
+//! Checks if word wrap is enabled
+bool intlGUIEditBox::isWordWrapEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return WordWrap;
+}
+
+
+//! Enables or disables newlines.
+void intlGUIEditBox::setMultiLine(bool enable)
+{
+ MultiLine = enable;
+}
+
+
+//! Checks if multi line editing is enabled
+bool intlGUIEditBox::isMultiLineEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return MultiLine;
+}
+
+
+void intlGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar)
+{
+ PasswordBox = passwordBox;
+ if (PasswordBox)
+ {
+ PasswordChar = passwordChar;
+ setMultiLine(false);
+ setWordWrap(false);
+ BrokenText.clear();
+ }
+}
+
+
+bool intlGUIEditBox::isPasswordBox() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return PasswordBox;
+}
+
+
+//! Sets text justification
+void intlGUIEditBox::setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical)
+{
+ HAlign = horizontal;
+ VAlign = vertical;
+}
+
+
+//! called if an event happened.
+bool intlGUIEditBox::OnEvent(const SEvent& event)
+{
+ if (IsEnabled)
+ {
+
+ switch(event.EventType)
+ {
+ case EET_GUI_EVENT:
+ if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUS_LOST)
+ {
+ if (event.GUIEvent.Caller == this)
+ {
+ MouseMarking = false;
+ setTextMarkers(0,0);
+ }
+ }
+ break;
+ case EET_KEY_INPUT_EVENT:
+ {
+#if (defined(__linux__) || defined(__FreeBSD__))
+ // ################################################################
+ // ValkaTR:
+ // This part is the difference from the original intlGUIEditBox
+ // It converts UTF-8 character into a UCS-2 (wchar_t)
+ wchar_t wc = L'_';
+ mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );
+
+ //printf( "char: %lc (%u) \r\n", wc, wc );
+
+ SEvent irrevent(event);
+ irrevent.KeyInput.Char = wc;
+ // ################################################################
+
+ if (processKey(irrevent))
+ return true;
+#else
+ if (processKey(event))
+ return true;
+#endif // defined(linux)
+
+ break;
+ }
+ case EET_MOUSE_INPUT_EVENT:
+ if (processMouse(event))
+ return true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return IGUIElement::OnEvent(event);
+}
+
+
+bool intlGUIEditBox::processKey(const SEvent& event)
+{
+ if (!event.KeyInput.PressedDown)
+ return false;
+
+ bool textChanged = false;
+ s32 newMarkBegin = MarkBegin;
+ s32 newMarkEnd = MarkEnd;
+
+ // control shortcut handling
+
+ if (event.KeyInput.Control)
+ {
+ // german backlash '\' entered with control + '?'
+ if ( event.KeyInput.Char == '\\' )
+ {
+ inputChar(event.KeyInput.Char);
+ return true;
+ }
+
+ switch(event.KeyInput.Key)
+ {
+ case KEY_KEY_A:
+ // select all
+ newMarkBegin = 0;
+ newMarkEnd = Text.size();
+ break;
+ case KEY_KEY_C:
+ // copy to clipboard
+ if (!PasswordBox && Operator && MarkBegin != MarkEnd)
+ {
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ core::stringc s;
+ s = Text.subString(realmbgn, realmend - realmbgn).c_str();
+ Operator->copyToClipboard(s.c_str());
+ }
+ break;
+ case KEY_KEY_X:
+ // cut to the clipboard
+ if (!PasswordBox && Operator && MarkBegin != MarkEnd)
+ {
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ // copy
+ core::stringc sc;
+ sc = Text.subString(realmbgn, realmend - realmbgn).c_str();
+ Operator->copyToClipboard(sc.c_str());
+
+ if (IsEnabled)
+ {
+ // delete
+ core::stringw s;
+ s = Text.subString(0, realmbgn);
+ s.append( Text.subString(realmend, Text.size()-realmend) );
+ Text = s;
+
+ CursorPos = realmbgn;
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ textChanged = true;
+ }
+ }
+ break;
+ case KEY_KEY_V:
+ if ( !IsEnabled )
+ break;
+
+ // paste from the clipboard
+ if (Operator)
+ {
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ // add new character
+ const c8* p = Operator->getTextFromClipboard();
+ if (p)
+ {
+ if (MarkBegin == MarkEnd)
+ {
+ // insert text
+ core::stringw s = Text.subString(0, CursorPos);
+ s.append(p);
+ s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
+
+ if (!Max || s.size()<=Max) // thx to Fish FH for fix
+ {
+ Text = s;
+ s = p;
+ CursorPos += s.size();
+ }
+ }
+ else
+ {
+ // replace text
+
+ core::stringw s = Text.subString(0, realmbgn);
+ s.append(p);
+ s.append( Text.subString(realmend, Text.size()-realmend) );
+
+ if (!Max || s.size()<=Max) // thx to Fish FH for fix
+ {
+ Text = s;
+ s = p;
+ CursorPos = realmbgn + s.size();
+ }
+ }
+ }
+
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ textChanged = true;
+ }
+ break;
+ case KEY_HOME:
+ // move/highlight to start of text
+ if (event.KeyInput.Shift)
+ {
+ newMarkEnd = CursorPos;
+ newMarkBegin = 0;
+ CursorPos = 0;
+ }
+ else
+ {
+ CursorPos = 0;
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+ break;
+ case KEY_END:
+ // move/highlight to end of text
+ if (event.KeyInput.Shift)
+ {
+ newMarkBegin = CursorPos;
+ newMarkEnd = Text.size();
+ CursorPos = 0;
+ }
+ else
+ {
+ CursorPos = Text.size();
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+ break;
+ default:
+ return false;
+ }
+ }
+ // default keyboard handling
+ else
+ switch(event.KeyInput.Key)
+ {
+ case KEY_END:
+ {
+ s32 p = Text.size();
+ if (WordWrap || MultiLine)
+ {
+ p = getLineFromPos(CursorPos);
+ p = BrokenTextPositions[p] + (s32)BrokenText[p].size();
+ if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' ))
+ p-=1;
+ }
+
+ if (event.KeyInput.Shift)
+ {
+ if (MarkBegin == MarkEnd)
+ newMarkBegin = CursorPos;
+
+ newMarkEnd = p;
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+ CursorPos = p;
+ BlinkStartTime = porting::getTimeMs();
+ }
+ break;
+ case KEY_HOME:
+ {
+
+ s32 p = 0;
+ if (WordWrap || MultiLine)
+ {
+ p = getLineFromPos(CursorPos);
+ p = BrokenTextPositions[p];
+ }
+
+ if (event.KeyInput.Shift)
+ {
+ if (MarkBegin == MarkEnd)
+ newMarkBegin = CursorPos;
+ newMarkEnd = p;
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+ CursorPos = p;
+ BlinkStartTime = porting::getTimeMs();
+ }
+ break;
+ case KEY_RETURN:
+ if (MultiLine)
+ {
+ inputChar(L'\n');
+ return true;
+ }
+ else
+ {
+ sendGuiEvent( EGET_EDITBOX_ENTER );
+ }
+ break;
+ case KEY_LEFT:
+
+ if (event.KeyInput.Shift)
+ {
+ if (CursorPos > 0)
+ {
+ if (MarkBegin == MarkEnd)
+ newMarkBegin = CursorPos;
+
+ newMarkEnd = CursorPos-1;
+ }
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+
+ if (CursorPos > 0) CursorPos--;
+ BlinkStartTime = porting::getTimeMs();
+ break;
+
+ case KEY_RIGHT:
+ if (event.KeyInput.Shift)
+ {
+ if (Text.size() > (u32)CursorPos)
+ {
+ if (MarkBegin == MarkEnd)
+ newMarkBegin = CursorPos;
+
+ newMarkEnd = CursorPos+1;
+ }
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+
+ if (Text.size() > (u32)CursorPos) CursorPos++;
+ BlinkStartTime = porting::getTimeMs();
+ break;
+ case KEY_UP:
+ if (MultiLine || (WordWrap && BrokenText.size() > 1) )
+ {
+ s32 lineNo = getLineFromPos(CursorPos);
+ s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd);
+ if (lineNo > 0)
+ {
+ s32 cp = CursorPos - BrokenTextPositions[lineNo];
+ if ((s32)BrokenText[lineNo-1].size() < cp)
+ CursorPos = BrokenTextPositions[lineNo-1] + (s32)BrokenText[lineNo-1].size()-1;
+ else
+ CursorPos = BrokenTextPositions[lineNo-1] + cp;
+ }
+
+ if (event.KeyInput.Shift)
+ {
+ newMarkBegin = mb;
+ newMarkEnd = CursorPos;
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+
+ }
+ else
+ {
+ return false;
+ }
+ break;
+ case KEY_DOWN:
+ if (MultiLine || (WordWrap && BrokenText.size() > 1) )
+ {
+ s32 lineNo = getLineFromPos(CursorPos);
+ s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd);
+ if (lineNo < (s32)BrokenText.size()-1)
+ {
+ s32 cp = CursorPos - BrokenTextPositions[lineNo];
+ if ((s32)BrokenText[lineNo+1].size() < cp)
+ CursorPos = BrokenTextPositions[lineNo+1] + BrokenText[lineNo+1].size()-1;
+ else
+ CursorPos = BrokenTextPositions[lineNo+1] + cp;
+ }
+
+ if (event.KeyInput.Shift)
+ {
+ newMarkBegin = mb;
+ newMarkEnd = CursorPos;
+ }
+ else
+ {
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ }
+
+ }
+ else
+ {
+ return false;
+ }
+ break;
+
+ case KEY_BACK:
+ if ( !this->IsEnabled )
+ break;
+
+ if (!Text.empty()) {
+ core::stringw s;
+
+ if (MarkBegin != MarkEnd)
+ {
+ // delete marked text
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ s = Text.subString(0, realmbgn);
+ s.append( Text.subString(realmend, Text.size()-realmend) );
+ Text = s;
+
+ CursorPos = realmbgn;
+ }
+ else
+ {
+ // delete text behind cursor
+ if (CursorPos>0)
+ s = Text.subString(0, CursorPos-1);
+ else
+ s = L"";
+ s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
+ Text = s;
+ --CursorPos;
+ }
+
+ if (CursorPos < 0)
+ CursorPos = 0;
+ BlinkStartTime = porting::getTimeMs();
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ textChanged = true;
+ }
+ break;
+ case KEY_DELETE:
+ if ( !this->IsEnabled )
+ break;
+
+ if (!Text.empty()) {
+ core::stringw s;
+
+ if (MarkBegin != MarkEnd)
+ {
+ // delete marked text
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ s = Text.subString(0, realmbgn);
+ s.append( Text.subString(realmend, Text.size()-realmend) );
+ Text = s;
+
+ CursorPos = realmbgn;
+ }
+ else
+ {
+ // delete text before cursor
+ s = Text.subString(0, CursorPos);
+ s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) );
+ Text = s;
+ }
+
+ if (CursorPos > (s32)Text.size())
+ CursorPos = (s32)Text.size();
+
+ BlinkStartTime = porting::getTimeMs();
+ newMarkBegin = 0;
+ newMarkEnd = 0;
+ textChanged = true;
+ }
+ break;
+
+ case KEY_ESCAPE:
+ case KEY_TAB:
+ case KEY_SHIFT:
+ case KEY_F1:
+ case KEY_F2:
+ case KEY_F3:
+ case KEY_F4:
+ case KEY_F5:
+ case KEY_F6:
+ case KEY_F7:
+ case KEY_F8:
+ case KEY_F9:
+ case KEY_F10:
+ case KEY_F11:
+ case KEY_F12:
+ case KEY_F13:
+ case KEY_F14:
+ case KEY_F15:
+ case KEY_F16:
+ case KEY_F17:
+ case KEY_F18:
+ case KEY_F19:
+ case KEY_F20:
+ case KEY_F21:
+ case KEY_F22:
+ case KEY_F23:
+ case KEY_F24:
+ // ignore these keys
+ return false;
+
+ default:
+ inputChar(event.KeyInput.Char);
+ return true;
+ }
+
+ // Set new text markers
+ setTextMarkers( newMarkBegin, newMarkEnd );
+
+ // break the text if it has changed
+ if (textChanged)
+ {
+ breakText();
+ sendGuiEvent(EGET_EDITBOX_CHANGED);
+ }
+
+ calculateScrollPos();
+
+ return true;
+}
+
+
+//! draws the element and its children
+void intlGUIEditBox::draw()
+{
+ if (!IsVisible)
+ return;
+
+ const bool focus = Environment->hasFocus(this);
+
+ IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+
+ FrameRect = AbsoluteRect;
+
+ // draw the border
+
+ if (Border)
+ {
+ if (m_writable) {
+ skin->draw3DSunkenPane(this, skin->getColor(EGDC_WINDOW),
+ false, true, FrameRect, &AbsoluteClippingRect);
+ }
+
+ FrameRect.UpperLeftCorner.X += skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
+ FrameRect.UpperLeftCorner.Y += skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
+ FrameRect.LowerRightCorner.X -= skin->getSize(EGDS_TEXT_DISTANCE_X)+1;
+ FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1;
+ }
+
+ updateVScrollBar();
+ core::rect<s32> localClipRect = FrameRect;
+ localClipRect.clipAgainst(AbsoluteClippingRect);
+
+ // draw the text
+
+ IGUIFont* font = OverrideFont;
+ if (!OverrideFont)
+ font = skin->getFont();
+
+ s32 cursorLine = 0;
+ s32 charcursorpos = 0;
+
+ if (font)
+ {
+ if (LastBreakFont != font)
+ {
+ breakText();
+ }
+
+ // calculate cursor pos
+
+ core::stringw *txtLine = &Text;
+ s32 startPos = 0;
+
+ core::stringw s, s2;
+
+ // get mark position
+ const bool ml = (!PasswordBox && (WordWrap || MultiLine));
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+ const s32 hlineStart = ml ? getLineFromPos(realmbgn) : 0;
+ const s32 hlineCount = ml ? getLineFromPos(realmend) - hlineStart + 1 : 1;
+ const s32 lineCount = ml ? BrokenText.size() : 1;
+
+ // Save the override color information.
+ // Then, alter it if the edit box is disabled.
+ const bool prevOver = OverrideColorEnabled;
+ const video::SColor prevColor = OverrideColor;
+
+ if (!Text.empty()) {
+ if (!IsEnabled && !OverrideColorEnabled)
+ {
+ OverrideColorEnabled = true;
+ OverrideColor = skin->getColor(EGDC_GRAY_TEXT);
+ }
+
+ for (s32 i=0; i < lineCount; ++i)
+ {
+ setTextRect(i);
+
+ // clipping test - don't draw anything outside the visible area
+ core::rect<s32> c = localClipRect;
+ c.clipAgainst(CurrentTextRect);
+ if (!c.isValid())
+ continue;
+
+ // get current line
+ if (PasswordBox)
+ {
+ if (BrokenText.size() != 1)
+ {
+ BrokenText.clear();
+ BrokenText.push_back(core::stringw());
+ }
+ if (BrokenText[0].size() != Text.size())
+ {
+ BrokenText[0] = Text;
+ for (u32 q = 0; q < Text.size(); ++q)
+ {
+ BrokenText[0] [q] = PasswordChar;
+ }
+ }
+ txtLine = &BrokenText[0];
+ startPos = 0;
+ }
+ else
+ {
+ txtLine = ml ? &BrokenText[i] : &Text;
+ startPos = ml ? BrokenTextPositions[i] : 0;
+ }
+
+
+ // draw normal text
+ font->draw(txtLine->c_str(), CurrentTextRect,
+ OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
+ false, true, &localClipRect);
+
+ // draw mark and marked text
+ if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount)
+ {
+
+ s32 mbegin = 0, mend = 0;
+ s32 lineStartPos = 0, lineEndPos = txtLine->size();
+
+ if (i == hlineStart)
+ {
+ // highlight start is on this line
+ s = txtLine->subString(0, realmbgn - startPos);
+ mbegin = font->getDimension(s.c_str()).Width;
+
+ // deal with kerning
+ mbegin += font->getKerningWidth(
+ &((*txtLine)[realmbgn - startPos]),
+ realmbgn - startPos > 0 ? &((*txtLine)[realmbgn - startPos - 1]) : 0);
+
+ lineStartPos = realmbgn - startPos;
+ }
+ if (i == hlineStart + hlineCount - 1)
+ {
+ // highlight end is on this line
+ s2 = txtLine->subString(0, realmend - startPos);
+ mend = font->getDimension(s2.c_str()).Width;
+ lineEndPos = (s32)s2.size();
+ }
+ else
+ mend = font->getDimension(txtLine->c_str()).Width;
+
+ CurrentTextRect.UpperLeftCorner.X += mbegin;
+ CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin;
+
+ // draw mark
+ skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect);
+
+ // draw marked text
+ s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos);
+
+ if (!s.empty())
+ font->draw(s.c_str(), CurrentTextRect,
+ OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT),
+ false, true, &localClipRect);
+
+ }
+ }
+
+ // Return the override color information to its previous settings.
+ OverrideColorEnabled = prevOver;
+ OverrideColor = prevColor;
+ }
+
+ // draw cursor
+
+ if (WordWrap || MultiLine)
+ {
+ cursorLine = getLineFromPos(CursorPos);
+ txtLine = &BrokenText[cursorLine];
+ startPos = BrokenTextPositions[cursorLine];
+ }
+ s = txtLine->subString(0,CursorPos-startPos);
+ charcursorpos = font->getDimension(s.c_str()).Width +
+ font->getKerningWidth(L"_", CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0);
+
+ if (m_writable) {
+ if (focus && (porting::getTimeMs() - BlinkStartTime) % 700 < 350) {
+ setTextRect(cursorLine);
+ CurrentTextRect.UpperLeftCorner.X += charcursorpos;
+
+ font->draw(L"_", CurrentTextRect,
+ OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT),
+ false, true, &localClipRect);
+ }
+ }
+ }
+
+ // draw children
+ IGUIElement::draw();
+}
+
+
+//! Sets the new caption of this element.
+void intlGUIEditBox::setText(const wchar_t* text)
+{
+ Text = text;
+ if (u32(CursorPos) > Text.size())
+ CursorPos = Text.size();
+ HScrollPos = 0;
+ breakText();
+}
+
+
+//! Enables or disables automatic scrolling with cursor position
+//! \param enable: If set to true, the text will move around with the cursor position
+void intlGUIEditBox::setAutoScroll(bool enable)
+{
+ AutoScroll = enable;
+}
+
+
+//! Checks to see if automatic scrolling is enabled
+//! \return true if automatic scrolling is enabled, false if not
+bool intlGUIEditBox::isAutoScrollEnabled() const
+{
+ _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX;
+ return AutoScroll;
+}
+
+
+//! Gets the area of the text in the edit box
+//! \return Returns the size in pixels of the text
+core::dimension2du intlGUIEditBox::getTextDimension()
+{
+ core::rect<s32> ret;
+
+ setTextRect(0);
+ ret = CurrentTextRect;
+
+ for (u32 i=1; i < BrokenText.size(); ++i)
+ {
+ setTextRect(i);
+ ret.addInternalPoint(CurrentTextRect.UpperLeftCorner);
+ ret.addInternalPoint(CurrentTextRect.LowerRightCorner);
+ }
+
+ return core::dimension2du(ret.getSize());
+}
+
+
+//! Sets the maximum amount of characters which may be entered in the box.
+//! \param max: Maximum amount of characters. If 0, the character amount is
+//! infinity.
+void intlGUIEditBox::setMax(u32 max)
+{
+ Max = max;
+
+ if (Text.size() > Max && Max != 0)
+ Text = Text.subString(0, Max);
+}
+
+
+//! Returns maximum amount of characters, previously set by setMax();
+u32 intlGUIEditBox::getMax() const
+{
+ return Max;
+}
+
+
+bool intlGUIEditBox::processMouse(const SEvent& event)
+{
+ switch(event.MouseInput.Event)
+ {
+ case irr::EMIE_LMOUSE_LEFT_UP:
+ if (Environment->hasFocus(this))
+ {
+ CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ if (MouseMarking)
+ {
+ setTextMarkers( MarkBegin, CursorPos );
+ }
+ MouseMarking = false;
+ calculateScrollPos();
+ return true;
+ }
+ break;
+ case irr::EMIE_MOUSE_MOVED:
+ {
+ if (MouseMarking)
+ {
+ CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ setTextMarkers( MarkBegin, CursorPos );
+ calculateScrollPos();
+ return true;
+ }
+ }
+ break;
+ case EMIE_LMOUSE_PRESSED_DOWN:
+ if (!Environment->hasFocus(this))
+ {
+ BlinkStartTime = porting::getTimeMs();
+ MouseMarking = true;
+ CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+ setTextMarkers(CursorPos, CursorPos );
+ calculateScrollPos();
+ return true;
+ }
+ else
+ {
+ if (!AbsoluteClippingRect.isPointInside(
+ core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y))) {
+ return false;
+ }
+
+
+ // move cursor
+ CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
+
+ s32 newMarkBegin = MarkBegin;
+ if (!MouseMarking)
+ newMarkBegin = CursorPos;
+
+ MouseMarking = true;
+ setTextMarkers( newMarkBegin, CursorPos);
+ calculateScrollPos();
+ return true;
+ }
+ break;
+ case EMIE_MOUSE_WHEEL:
+ if (m_vscrollbar) {
+ s32 pos = m_vscrollbar->getPos();
+ s32 step = m_vscrollbar->getSmallStep();
+ m_vscrollbar->setPos(pos - event.MouseInput.Wheel * step);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+s32 intlGUIEditBox::getCursorPos(s32 x, s32 y)
+{
+ IGUIFont* font = OverrideFont;
+ IGUISkin* skin = Environment->getSkin();
+ if (!OverrideFont)
+ font = skin->getFont();
+
+ const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1;
+
+ core::stringw *txtLine = NULL;
+ s32 startPos = 0;
+ u32 curr_line_idx = 0;
+ x += 3;
+
+ for (; curr_line_idx < lineCount; ++curr_line_idx) {
+ setTextRect(curr_line_idx);
+ if (curr_line_idx == 0 && y < CurrentTextRect.UpperLeftCorner.Y)
+ y = CurrentTextRect.UpperLeftCorner.Y;
+ if (curr_line_idx == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y)
+ y = CurrentTextRect.LowerRightCorner.Y;
+
+ // is it inside this region?
+ if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y) {
+ // we've found the clicked line
+ txtLine = (WordWrap || MultiLine) ? &BrokenText[curr_line_idx] : &Text;
+ startPos = (WordWrap || MultiLine) ? BrokenTextPositions[curr_line_idx] : 0;
+ break;
+ }
+ }
+
+ if (x < CurrentTextRect.UpperLeftCorner.X)
+ x = CurrentTextRect.UpperLeftCorner.X;
+ else if (x > CurrentTextRect.LowerRightCorner.X)
+ x = CurrentTextRect.LowerRightCorner.X;
+
+ s32 idx = font->getCharacterFromPos(txtLine->c_str(), x - CurrentTextRect.UpperLeftCorner.X);
+ // Special handling for last line, if we are on limits, add 1 extra shift because idx
+ // will be the last char, not null char of the wstring
+ if (curr_line_idx == lineCount - 1 && x == CurrentTextRect.LowerRightCorner.X)
+ idx++;
+
+ return rangelim(idx + startPos, 0, S32_MAX);
+}
+
+
+//! Breaks the single text line.
+void intlGUIEditBox::breakText()
+{
+ IGUISkin* skin = Environment->getSkin();
+
+ if ((!WordWrap && !MultiLine) || !skin)
+ return;
+
+ BrokenText.clear(); // need to reallocate :/
+ BrokenTextPositions.set_used(0);
+
+ IGUIFont* font = OverrideFont;
+ if (!OverrideFont)
+ font = skin->getFont();
+
+ if (!font)
+ return;
+
+ LastBreakFont = font;
+
+ core::stringw line;
+ core::stringw word;
+ core::stringw whitespace;
+ s32 lastLineStart = 0;
+ s32 size = Text.size();
+ s32 length = 0;
+ s32 elWidth = RelativeRect.getWidth() - 6;
+ wchar_t c;
+
+ for (s32 i=0; i<size; ++i)
+ {
+ c = Text[i];
+ bool lineBreak = false;
+
+ if (c == L'\r') // Mac or Windows breaks
+ {
+ lineBreak = true;
+ c = ' ';
+ if (Text[i+1] == L'\n') // Windows breaks
+ {
+ Text.erase(i+1);
+ --size;
+ }
+ }
+ else if (c == L'\n') // Unix breaks
+ {
+ lineBreak = true;
+ c = ' ';
+ }
+
+ // don't break if we're not a multi-line edit box
+ if (!MultiLine)
+ lineBreak = false;
+
+ if (c == L' ' || c == 0 || i == (size-1))
+ {
+ if (!word.empty()) {
+ // here comes the next whitespace, look if
+ // we can break the last word to the next line.
+ s32 whitelgth = font->getDimension(whitespace.c_str()).Width;
+ s32 worldlgth = font->getDimension(word.c_str()).Width;
+
+ if (WordWrap && length + worldlgth + whitelgth > elWidth)
+ {
+ // break to next line
+ length = worldlgth;
+ BrokenText.push_back(line);
+ BrokenTextPositions.push_back(lastLineStart);
+ lastLineStart = i - (s32)word.size();
+ line = word;
+ }
+ else
+ {
+ // add word to line
+ line += whitespace;
+ line += word;
+ length += whitelgth + worldlgth;
+ }
+
+ word = L"";
+ whitespace = L"";
+ }
+
+ whitespace += c;
+
+ // compute line break
+ if (lineBreak)
+ {
+ line += whitespace;
+ line += word;
+ BrokenText.push_back(line);
+ BrokenTextPositions.push_back(lastLineStart);
+ lastLineStart = i+1;
+ line = L"";
+ word = L"";
+ whitespace = L"";
+ length = 0;
+ }
+ }
+ else
+ {
+ // yippee this is a word..
+ word += c;
+ }
+ }
+
+ line += whitespace;
+ line += word;
+ BrokenText.push_back(line);
+ BrokenTextPositions.push_back(lastLineStart);
+}
+
+
+void intlGUIEditBox::setTextRect(s32 line)
+{
+ core::dimension2du d;
+
+ IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+
+ IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont();
+
+ if (!font)
+ return;
+
+ // get text dimension
+ const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1;
+ if (WordWrap || MultiLine)
+ {
+ d = font->getDimension(BrokenText[line].c_str());
+ }
+ else
+ {
+ d = font->getDimension(Text.c_str());
+ d.Height = AbsoluteRect.getHeight();
+ }
+ d.Height += font->getKerningHeight();
+
+ // justification
+ switch (HAlign)
+ {
+ case EGUIA_CENTER:
+ // align to h centre
+ CurrentTextRect.UpperLeftCorner.X = (FrameRect.getWidth()/2) - (d.Width/2);
+ CurrentTextRect.LowerRightCorner.X = (FrameRect.getWidth()/2) + (d.Width/2);
+ break;
+ case EGUIA_LOWERRIGHT:
+ // align to right edge
+ CurrentTextRect.UpperLeftCorner.X = FrameRect.getWidth() - d.Width;
+ CurrentTextRect.LowerRightCorner.X = FrameRect.getWidth();
+ break;
+ default:
+ // align to left edge
+ CurrentTextRect.UpperLeftCorner.X = 0;
+ CurrentTextRect.LowerRightCorner.X = d.Width;
+
+ }
+
+ switch (VAlign)
+ {
+ case EGUIA_CENTER:
+ // align to v centre
+ CurrentTextRect.UpperLeftCorner.Y =
+ (FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line;
+ break;
+ case EGUIA_LOWERRIGHT:
+ // align to bottom edge
+ CurrentTextRect.UpperLeftCorner.Y =
+ FrameRect.getHeight() - lineCount*d.Height + d.Height*line;
+ break;
+ default:
+ // align to top edge
+ CurrentTextRect.UpperLeftCorner.Y = d.Height*line;
+ break;
+ }
+
+ CurrentTextRect.UpperLeftCorner.X -= HScrollPos;
+ CurrentTextRect.LowerRightCorner.X -= HScrollPos;
+ CurrentTextRect.UpperLeftCorner.Y -= VScrollPos;
+ CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height;
+
+ CurrentTextRect += FrameRect.UpperLeftCorner;
+
+}
+
+
+s32 intlGUIEditBox::getLineFromPos(s32 pos)
+{
+ if (!WordWrap && !MultiLine)
+ return 0;
+
+ s32 i=0;
+ while (i < (s32)BrokenTextPositions.size())
+ {
+ if (BrokenTextPositions[i] > pos)
+ return i-1;
+ ++i;
+ }
+ return (s32)BrokenTextPositions.size() - 1;
+}
+
+
+void intlGUIEditBox::inputChar(wchar_t c)
+{
+ if (!IsEnabled)
+ return;
+
+ if (c != 0)
+ {
+ if (Text.size() < Max || Max == 0)
+ {
+ core::stringw s;
+
+ if (MarkBegin != MarkEnd)
+ {
+ // replace marked text
+ const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd;
+ const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin;
+
+ s = Text.subString(0, realmbgn);
+ s.append(c);
+ s.append( Text.subString(realmend, Text.size()-realmend) );
+ Text = s;
+ CursorPos = realmbgn+1;
+ }
+ else
+ {
+ // add new character
+ s = Text.subString(0, CursorPos);
+ s.append(c);
+ s.append( Text.subString(CursorPos, Text.size()-CursorPos) );
+ Text = s;
+ ++CursorPos;
+ }
+
+ BlinkStartTime = porting::getTimeMs();
+ setTextMarkers(0, 0);
+ }
+ }
+ breakText();
+ sendGuiEvent(EGET_EDITBOX_CHANGED);
+ calculateScrollPos();
+}
+
+
+void intlGUIEditBox::calculateScrollPos()
+{
+ if (!AutoScroll)
+ return;
+
+ // calculate horizontal scroll position
+ s32 cursLine = getLineFromPos(CursorPos);
+ setTextRect(cursLine);
+
+ // don't do horizontal scrolling when wordwrap is enabled.
+ if (!WordWrap)
+ {
+ // get cursor position
+ IGUISkin* skin = Environment->getSkin();
+ if (!skin)
+ return;
+ IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont();
+ if (!font)
+ return;
+
+ core::stringw *txtLine = MultiLine ? &BrokenText[cursLine] : &Text;
+ s32 cPos = MultiLine ? CursorPos - BrokenTextPositions[cursLine] : CursorPos;
+
+ s32 cStart = CurrentTextRect.UpperLeftCorner.X + HScrollPos +
+ font->getDimension(txtLine->subString(0, cPos).c_str()).Width;
+
+ s32 cEnd = cStart + font->getDimension(L"_ ").Width;
+
+ if (FrameRect.LowerRightCorner.X < cEnd)
+ HScrollPos = cEnd - FrameRect.LowerRightCorner.X;
+ else if (FrameRect.UpperLeftCorner.X > cStart)
+ HScrollPos = cStart - FrameRect.UpperLeftCorner.X;
+ else
+ HScrollPos = 0;
+
+ // todo: adjust scrollbar
+ }
+
+ // vertical scroll position
+ if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y + VScrollPos)
+ VScrollPos = CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y + VScrollPos;
+
+ else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y + VScrollPos)
+ VScrollPos = CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y + VScrollPos;
+ else
+ VScrollPos = 0;
+
+ // todo: adjust scrollbar
+ if (m_vscrollbar)
+ m_vscrollbar->setPos(VScrollPos);
+}
+
+//! set text markers
+void intlGUIEditBox::setTextMarkers(s32 begin, s32 end)
+{
+ if ( begin != MarkBegin || end != MarkEnd )
+ {
+ MarkBegin = begin;
+ MarkEnd = end;
+ sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED);
+ }
+}
+
+//! send some gui event to parent
+void intlGUIEditBox::sendGuiEvent(EGUI_EVENT_TYPE type)
+{
+ if ( Parent )
+ {
+ SEvent e;
+ e.EventType = EET_GUI_EVENT;
+ e.GUIEvent.Caller = this;
+ e.GUIEvent.Element = 0;
+ e.GUIEvent.EventType = type;
+
+ Parent->OnEvent(e);
+ }
+}
+
+//! Create a vertical scrollbar
+void intlGUIEditBox::createVScrollBar()
+{
+ s32 fontHeight = 1;
+
+ if (OverrideFont) {
+ fontHeight = OverrideFont->getDimension(L"").Height;
+ } else {
+ if (IGUISkin* skin = Environment->getSkin()) {
+ if (IGUIFont* font = skin->getFont()) {
+ fontHeight = font->getDimension(L"").Height;
+ }
+ }
+ }
+
+ irr::core::rect<s32> scrollbarrect = FrameRect;
+ scrollbarrect.UpperLeftCorner.X += FrameRect.getWidth() - m_scrollbar_width;
+ m_vscrollbar = Environment->addScrollBar(false, scrollbarrect, getParent(), getID());
+ m_vscrollbar->setVisible(false);
+ m_vscrollbar->setSmallStep(3 * fontHeight);
+ m_vscrollbar->setLargeStep(10 * fontHeight);
+}
+
+//! Update the vertical scrollbar (visibilty & scroll position)
+void intlGUIEditBox::updateVScrollBar()
+{
+ if (!m_vscrollbar)
+ return;
+
+ // OnScrollBarChanged(...)
+ if (m_vscrollbar->getPos() != VScrollPos) {
+ s32 deltaScrollY = m_vscrollbar->getPos() - VScrollPos;
+ CurrentTextRect.UpperLeftCorner.Y -= deltaScrollY;
+ CurrentTextRect.LowerRightCorner.Y -= deltaScrollY;
+
+ s32 scrollymax = getTextDimension().Height - FrameRect.getHeight();
+ if (scrollymax != m_vscrollbar->getMax()) {
+ // manage a newline or a deleted line
+ m_vscrollbar->setMax(scrollymax);
+ calculateScrollPos();
+ } else {
+ // manage a newline or a deleted line
+ VScrollPos = m_vscrollbar->getPos();
+ }
+ }
+
+ // check if a vertical scrollbar is needed ?
+ if (getTextDimension().Height > (u32) FrameRect.getHeight()) {
+ s32 scrollymax = getTextDimension().Height - FrameRect.getHeight();
+ if (scrollymax != m_vscrollbar->getMax()) {
+ m_vscrollbar->setMax(scrollymax);
+ }
+
+ if (!m_vscrollbar->isVisible() && MultiLine) {
+ AbsoluteRect.LowerRightCorner.X -= m_scrollbar_width;
+
+ m_vscrollbar->setVisible(true);
+ }
+ } else {
+ if (m_vscrollbar->isVisible()) {
+ AbsoluteRect.LowerRightCorner.X += m_scrollbar_width;
+
+ VScrollPos = 0;
+ m_vscrollbar->setPos(0);
+ m_vscrollbar->setMax(1);
+ m_vscrollbar->setVisible(false);
+ }
+ }
+}
+
+void intlGUIEditBox::setWritable(bool can_write_text)
+{
+ m_writable = can_write_text;
+}
+
+//! Writes attributes of the element.
+void intlGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options=0) const
+{
+ // IGUIEditBox::serializeAttributes(out,options);
+
+ out->addBool ("OverrideColorEnabled",OverrideColorEnabled );
+ out->addColor ("OverrideColor", OverrideColor);
+ // out->addFont("OverrideFont",OverrideFont);
+ out->addInt ("MaxChars", Max);
+ out->addBool ("WordWrap", WordWrap);
+ out->addBool ("MultiLine", MultiLine);
+ out->addBool ("AutoScroll", AutoScroll);
+ out->addBool ("PasswordBox", PasswordBox);
+ core::stringw ch = L" ";
+ ch[0] = PasswordChar;
+ out->addString("PasswordChar", ch.c_str());
+ out->addEnum ("HTextAlign", HAlign, GUIAlignmentNames);
+ out->addEnum ("VTextAlign", VAlign, GUIAlignmentNames);
+ out->addBool ("Writable", m_writable);
+
+ IGUIEditBox::serializeAttributes(out,options);
+}
+
+
+//! Reads attributes of the element
+void intlGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options=0)
+{
+ IGUIEditBox::deserializeAttributes(in,options);
+
+ setOverrideColor(in->getAttributeAsColor("OverrideColor"));
+ enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
+ setMax(in->getAttributeAsInt("MaxChars"));
+ setWordWrap(in->getAttributeAsBool("WordWrap"));
+ setMultiLine(in->getAttributeAsBool("MultiLine"));
+ setAutoScroll(in->getAttributeAsBool("AutoScroll"));
+ core::stringw ch = in->getAttributeAsStringW("PasswordChar");
+
+ if (ch.empty())
+ setPasswordBox(in->getAttributeAsBool("PasswordBox"));
+ else
+ setPasswordBox(in->getAttributeAsBool("PasswordBox"), ch[0]);
+
+ setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
+ (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));
+
+ setWritable(in->getAttributeAsBool("Writable"));
+ // setOverrideFont(in->getAttributeAsFont("OverrideFont"));
+}
+
+
+} // end namespace gui
+} // end namespace irr
+
+#endif // _IRR_COMPILE_WITH_GUI_
diff --git a/src/gui/intlGUIEditBox.h b/src/gui/intlGUIEditBox.h
new file mode 100644
index 000000000..aa35e2e71
--- /dev/null
+++ b/src/gui/intlGUIEditBox.h
@@ -0,0 +1,198 @@
+// Copyright (C) 2002-2013 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#pragma once
+
+#include "IrrCompileConfig.h"
+//#ifdef _IRR_COMPILE_WITH_GUI_
+
+#include "IGUIEditBox.h"
+#include "irrArray.h"
+#include "IOSOperator.h"
+#include "IGUIScrollBar.h"
+
+namespace irr
+{
+namespace gui
+{
+ class intlGUIEditBox : public IGUIEditBox
+ {
+ public:
+
+ //! constructor
+ intlGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment,
+ IGUIElement* parent, s32 id, const core::rect<s32>& rectangle,
+ bool writable = true, bool has_vscrollbar = false);
+
+ //! destructor
+ virtual ~intlGUIEditBox();
+
+ //! Sets another skin independent font.
+ virtual void setOverrideFont(IGUIFont* font=0);
+
+ //! Gets the override font (if any)
+ /** \return The override font (may be 0) */
+ virtual IGUIFont* getOverrideFont() const;
+
+ //! Get the font which is used right now for drawing
+ /** Currently this is the override font when one is set and the
+ font of the active skin otherwise */
+ virtual IGUIFont* getActiveFont() const;
+
+ //! Sets another color for the text.
+ virtual void setOverrideColor(video::SColor color);
+
+ //! Gets the override color
+ virtual video::SColor getOverrideColor() const;
+
+ //! Sets if the text should use the overide color or the
+ //! color in the gui skin.
+ virtual void enableOverrideColor(bool enable);
+
+ //! Checks if an override color is enabled
+ /** \return true if the override color is enabled, false otherwise */
+ virtual bool isOverrideColorEnabled(void) const;
+
+ //! Sets whether to draw the background
+ virtual void setDrawBackground(bool draw);
+
+ //! Turns the border on or off
+ virtual void setDrawBorder(bool border);
+
+ //! Enables or disables word wrap for using the edit box as multiline text editor.
+ virtual void setWordWrap(bool enable);
+
+ //! Checks if word wrap is enabled
+ //! \return true if word wrap is enabled, false otherwise
+ virtual bool isWordWrapEnabled() const;
+
+ //! Enables or disables newlines.
+ /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired,
+ instead a newline character will be inserted. */
+ virtual void setMultiLine(bool enable);
+
+ //! Checks if multi line editing is enabled
+ //! \return true if mult-line is enabled, false otherwise
+ virtual bool isMultiLineEnabled() const;
+
+ //! Enables or disables automatic scrolling with cursor position
+ //! \param enable: If set to true, the text will move around with the cursor position
+ virtual void setAutoScroll(bool enable);
+
+ //! Checks to see if automatic scrolling is enabled
+ //! \return true if automatic scrolling is enabled, false if not
+ virtual bool isAutoScrollEnabled() const;
+
+ //! Gets the size area of the text in the edit box
+ //! \return Returns the size in pixels of the text
+ virtual core::dimension2du getTextDimension();
+
+ //! Sets text justification
+ virtual void setTextAlignment(EGUI_ALIGNMENT horizontal, EGUI_ALIGNMENT vertical);
+
+ //! called if an event happened.
+ virtual bool OnEvent(const SEvent& event);
+
+ //! draws the element and its children
+ virtual void draw();
+
+ //! Sets the new caption of this element.
+ virtual void setText(const wchar_t* text);
+
+ //! Sets the maximum amount of characters which may be entered in the box.
+ //! \param max: Maximum amount of characters. If 0, the character amount is
+ //! infinity.
+ virtual void setMax(u32 max);
+
+ //! Returns maximum amount of characters, previously set by setMax();
+ virtual u32 getMax() const;
+
+ //! Sets whether the edit box is a password box. Setting this to true will
+ /** disable MultiLine, WordWrap and the ability to copy with ctrl+c or ctrl+x
+ \param passwordBox: true to enable password, false to disable
+ \param passwordChar: the character that is displayed instead of letters */
+ virtual void setPasswordBox(bool passwordBox, wchar_t passwordChar = L'*');
+
+ //! Returns true if the edit box is currently a password box.
+ virtual bool isPasswordBox() const;
+
+ //! Updates the absolute position, splits text if required
+ virtual void updateAbsolutePosition();
+
+ //! set true if this EditBox is writable
+ virtual void setWritable(bool can_write_text);
+
+ //! Writes attributes of the element.
+ virtual void serializeAttributes(io::IAttributes* out, io::SAttributeReadWriteOptions* options) const;
+
+ //! Reads attributes of the element
+ virtual void deserializeAttributes(io::IAttributes* in, io::SAttributeReadWriteOptions* options);
+
+ protected:
+ //! Breaks the single text line.
+ void breakText();
+ //! sets the area of the given line
+ void setTextRect(s32 line);
+ //! returns the line number that the cursor is on
+ s32 getLineFromPos(s32 pos);
+ //! adds a letter to the edit box
+ void inputChar(wchar_t c);
+ //! calculates the current scroll position
+ void calculateScrollPos();
+ //! send some gui event to parent
+ void sendGuiEvent(EGUI_EVENT_TYPE type);
+ //! set text markers
+ void setTextMarkers(s32 begin, s32 end);
+
+ bool processKey(const SEvent& event);
+ bool processMouse(const SEvent& event);
+ s32 getCursorPos(s32 x, s32 y);
+
+ //! Create a vertical scrollbar
+ void createVScrollBar();
+
+ //! Update the vertical scrollbar (visibilty & scroll position)
+ void updateVScrollBar();
+
+ bool MouseMarking = false;
+ bool Border;
+ bool OverrideColorEnabled = false;
+ s32 MarkBegin = 0;
+ s32 MarkEnd = 0;
+
+ video::SColor OverrideColor = video::SColor(101,255,255,255);
+ gui::IGUIFont *OverrideFont = nullptr;
+ gui::IGUIFont *LastBreakFont = nullptr;
+ IOSOperator *Operator = nullptr;
+
+ u64 BlinkStartTime = 0;
+ s32 CursorPos = 0;
+ s32 HScrollPos = 0;
+ s32 VScrollPos = 0; // scroll position in characters
+ u32 Max = 0;
+
+ bool WordWrap = false;
+ bool MultiLine = false;
+ bool AutoScroll = true;
+ bool PasswordBox = false;
+ wchar_t PasswordChar = L'*';
+ EGUI_ALIGNMENT HAlign = EGUIA_UPPERLEFT;
+ EGUI_ALIGNMENT VAlign = EGUIA_CENTER;
+
+ core::array<core::stringw> BrokenText;
+ core::array<s32> BrokenTextPositions;
+
+ core::rect<s32> CurrentTextRect = core::rect<s32>(0,0,1,1);
+ core::rect<s32> FrameRect; // temporary values
+ u32 m_scrollbar_width;
+ IGUIScrollBar *m_vscrollbar;
+ bool m_writable;
+
+ };
+
+
+} // end namespace gui
+} // end namespace irr
+
+//#endif // _IRR_COMPILE_WITH_GUI_
diff --git a/src/gui/mainmenumanager.h b/src/gui/mainmenumanager.h
new file mode 100644
index 000000000..ea9327813
--- /dev/null
+++ b/src/gui/mainmenumanager.h
@@ -0,0 +1,166 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+/*
+ All kinds of stuff that needs to be exposed from main.cpp
+*/
+#include "modalMenu.h"
+#include <cassert>
+#include <list>
+
+class IGameCallback
+{
+public:
+ virtual void exitToOS() = 0;
+ virtual void keyConfig() = 0;
+ virtual void disconnect() = 0;
+ virtual void changePassword() = 0;
+ virtual void changeVolume() = 0;
+
+ virtual void signalKeyConfigChange() = 0;
+};
+
+extern gui::IGUIEnvironment *guienv;
+extern gui::IGUIStaticText *guiroot;
+
+// Handler for the modal menus
+
+class MainMenuManager : public IMenuManager
+{
+public:
+ virtual void createdMenu(gui::IGUIElement *menu)
+ {
+#ifndef NDEBUG
+ for (gui::IGUIElement *i : m_stack) {
+ assert(i != menu);
+ }
+#endif
+
+ if(!m_stack.empty())
+ m_stack.back()->setVisible(false);
+ m_stack.push_back(menu);
+ }
+
+ virtual void deletingMenu(gui::IGUIElement *menu)
+ {
+ // Remove all entries if there are duplicates
+ bool removed_entry;
+ do{
+ removed_entry = false;
+ for(std::list<gui::IGUIElement*>::iterator
+ i = m_stack.begin();
+ i != m_stack.end(); ++i)
+ {
+ if(*i == menu)
+ {
+ m_stack.erase(i);
+ removed_entry = true;
+ break;
+ }
+ }
+ }while(removed_entry);
+
+ /*core::list<GUIModalMenu*>::Iterator i = m_stack.getLast();
+ assert(*i == menu);
+ m_stack.erase(i);*/
+
+ if(!m_stack.empty())
+ m_stack.back()->setVisible(true);
+ }
+
+ // Returns true to prevent further processing
+ virtual bool preprocessEvent(const SEvent& event)
+ {
+ if (m_stack.empty())
+ return false;
+ GUIModalMenu *mm = dynamic_cast<GUIModalMenu*>(m_stack.back());
+ return mm && mm->preprocessEvent(event);
+ }
+
+ u32 menuCount()
+ {
+ return m_stack.size();
+ }
+
+ bool pausesGame()
+ {
+ for (gui::IGUIElement *i : m_stack) {
+ GUIModalMenu *mm = dynamic_cast<GUIModalMenu*>(i);
+ if (mm && mm->pausesGame())
+ return true;
+ }
+ return false;
+ }
+
+ std::list<gui::IGUIElement*> m_stack;
+};
+
+extern MainMenuManager g_menumgr;
+
+extern bool isMenuActive();
+
+class MainGameCallback : public IGameCallback
+{
+public:
+ MainGameCallback() = default;
+ virtual ~MainGameCallback() = default;
+
+ virtual void exitToOS()
+ {
+ shutdown_requested = true;
+ }
+
+ virtual void disconnect()
+ {
+ disconnect_requested = true;
+ }
+
+ virtual void changePassword()
+ {
+ changepassword_requested = true;
+ }
+
+ virtual void changeVolume()
+ {
+ changevolume_requested = true;
+ }
+
+ virtual void keyConfig()
+ {
+ keyconfig_requested = true;
+ }
+
+ virtual void signalKeyConfigChange()
+ {
+ keyconfig_changed = true;
+ }
+
+
+ bool disconnect_requested = false;
+ bool changepassword_requested = false;
+ bool changevolume_requested = false;
+ bool keyconfig_requested = false;
+ bool shutdown_requested = false;
+
+ bool keyconfig_changed = false;
+};
+
+extern MainGameCallback *g_gamecallback;
diff --git a/src/gui/modalMenu.h b/src/gui/modalMenu.h
new file mode 100644
index 000000000..f41591c3f
--- /dev/null
+++ b/src/gui/modalMenu.h
@@ -0,0 +1,137 @@
+/*
+Minetest
+Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
+
+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.
+*/
+
+#pragma once
+
+#include "irrlichttypes_extrabloated.h"
+#ifdef HAVE_TOUCHSCREENGUI
+#include "touchscreengui.h"
+#endif
+
+class GUIModalMenu;
+
+class IMenuManager
+{
+public:
+ // A GUIModalMenu calls these when this class is passed as a parameter
+ virtual void createdMenu(gui::IGUIElement *menu) = 0;
+ virtual void deletingMenu(gui::IGUIElement *menu) = 0;
+};
+
+/*
+ Remember to drop() the menu after creating, so that it can
+ remove itself when it wants to.
+*/
+
+class GUIModalMenu : public gui::IGUIElement
+{
+public:
+ GUIModalMenu(gui::IGUIEnvironment* env, gui::IGUIElement* parent, s32 id,
+ IMenuManager *menumgr):
+ IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
+ core::rect<s32>(0,0,100,100))
+ {
+ m_menumgr = menumgr;
+
+ setVisible(true);
+ Environment->setFocus(this);
+ m_menumgr->createdMenu(this);
+ }
+
+ virtual ~GUIModalMenu()
+ {
+ m_menumgr->deletingMenu(this);
+ }
+
+ void allowFocusRemoval(bool allow)
+ {
+ m_allow_focus_removal = allow;
+ }
+
+ bool canTakeFocus(gui::IGUIElement *e)
+ {
+ return (e && (e == this || isMyChild(e))) || m_allow_focus_removal;
+ }
+
+ void draw()
+ {
+ if(!IsVisible)
+ return;
+
+ video::IVideoDriver* driver = Environment->getVideoDriver();
+ v2u32 screensize = driver->getScreenSize();
+ if(screensize != m_screensize_old /*|| m_force_regenerate_gui*/)
+ {
+ m_screensize_old = screensize;
+ regenerateGui(screensize);
+ //m_force_regenerate_gui = false;
+ }
+
+ drawMenu();
+ }
+
+ /*
+ This should be called when the menu wants to quit.
+
+ WARNING: THIS DEALLOCATES THE MENU FROM MEMORY. Return
+ immediately if you call this from the menu itself.
+
+ (More precisely, this decrements the reference count.)
+ */
+ void quitMenu()
+ {
+ allowFocusRemoval(true);
+ // This removes Environment's grab on us
+ Environment->removeFocus(this);
+ m_menumgr->deletingMenu(this);
+ this->remove();
+#ifdef HAVE_TOUCHSCREENGUI
+ if (g_touchscreengui)
+ g_touchscreengui->show();
+#endif
+ }
+
+ void removeChildren()
+ {
+ const core::list<gui::IGUIElement*> &children = getChildren();
+ core::list<gui::IGUIElement*> children_copy;
+ for (gui::IGUIElement *i : children) {
+ children_copy.push_back(i);
+ }
+
+ for (gui::IGUIElement *i : children_copy) {
+ i->remove();
+ }
+ }
+
+ virtual void regenerateGui(v2u32 screensize) = 0;
+ virtual void drawMenu() = 0;
+ virtual bool preprocessEvent(const SEvent& event) { return false; };
+ virtual bool OnEvent(const SEvent& event) { return false; };
+ virtual bool pausesGame(){ return false; } // Used for pause menu
+
+protected:
+ //bool m_force_regenerate_gui;
+ v2u32 m_screensize_old;
+private:
+ IMenuManager *m_menumgr;
+ // This might be necessary to expose to the implementation if it
+ // wants to launch other menus
+ bool m_allow_focus_removal = false;
+};
diff --git a/src/gui/touchscreengui.cpp b/src/gui/touchscreengui.cpp
new file mode 100644
index 000000000..e849b4053
--- /dev/null
+++ b/src/gui/touchscreengui.cpp
@@ -0,0 +1,1063 @@
+/*
+Copyright (C) 2014 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.
+*/
+
+#include "touchscreengui.h"
+#include "irrlichttypes.h"
+#include "irr_v2d.h"
+#include "log.h"
+#include "keycode.h"
+#include "settings.h"
+#include "gettime.h"
+#include "util/numeric.h"
+#include "porting.h"
+#include "guiscalingfilter.h"
+
+#include <iostream>
+#include <algorithm>
+
+#include <ISceneCollisionManager.h>
+
+// Very slow button repeat frequency (in seconds)
+#define SLOW_BUTTON_REPEAT (1.0f)
+
+using namespace irr::core;
+
+const char** touchgui_button_imagenames = (const char*[]) {
+ "up_arrow.png",
+ "down_arrow.png",
+ "left_arrow.png",
+ "right_arrow.png",
+ "jump_btn.png",
+ "down.png"
+};
+
+static irr::EKEY_CODE id2keycode(touch_gui_button_id id)
+{
+ std::string key = "";
+ switch (id) {
+ case forward_id:
+ key = "forward";
+ break;
+ case left_id:
+ key = "left";
+ break;
+ case right_id:
+ key = "right";
+ break;
+ case backward_id:
+ key = "backward";
+ break;
+ case inventory_id:
+ key = "inventory";
+ break;
+ case drop_id:
+ key = "drop";
+ break;
+ case jump_id:
+ key = "jump";
+ break;
+ case crunch_id:
+ key = "sneak";
+ break;
+ case fly_id:
+ key = "freemove";
+ break;
+ case noclip_id:
+ key = "noclip";
+ break;
+ case fast_id:
+ key = "fastmove";
+ break;
+ case debug_id:
+ key = "toggle_debug";
+ break;
+ case chat_id:
+ key = "chat";
+ break;
+ case camera_id:
+ key = "camera_mode";
+ break;
+ case range_id:
+ key = "rangeselect";
+ break;
+ }
+ assert(key != "");
+ return keyname_to_keycode(g_settings->get("keymap_" + key).c_str());
+}
+
+TouchScreenGUI *g_touchscreengui;
+
+static void load_button_texture(button_info* btn, const char* path,
+ rect<s32> button_rect, ISimpleTextureSource* tsrc, video::IVideoDriver *driver)
+{
+ unsigned int tid;
+ video::ITexture *texture = guiScalingImageButton(driver,
+ tsrc->getTexture(path, &tid), button_rect.getWidth(),
+ button_rect.getHeight());
+ if (texture) {
+ btn->guibutton->setUseAlphaChannel(true);
+ if (g_settings->getBool("gui_scaling_filter")) {
+ rect<s32> txr_rect = rect<s32>(0, 0, button_rect.getWidth(), button_rect.getHeight());
+ btn->guibutton->setImage(texture, txr_rect);
+ btn->guibutton->setPressedImage(texture, txr_rect);
+ btn->guibutton->setScaleImage(false);
+ } else {
+ btn->guibutton->setImage(texture);
+ btn->guibutton->setPressedImage(texture);
+ btn->guibutton->setScaleImage(true);
+ }
+ btn->guibutton->setDrawBorder(false);
+ btn->guibutton->setText(L"");
+ }
+}
+
+AutoHideButtonBar::AutoHideButtonBar(IrrlichtDevice *device,
+ IEventReceiver* receiver) :
+ m_driver(device->getVideoDriver()),
+ m_guienv(device->getGUIEnvironment()),
+ m_receiver(receiver)
+{
+}
+
+void AutoHideButtonBar::init(ISimpleTextureSource* tsrc,
+ const char* starter_img, int button_id, v2s32 UpperLeft,
+ v2s32 LowerRight, autohide_button_bar_dir dir, float timeout)
+{
+ m_texturesource = tsrc;
+
+ m_upper_left = UpperLeft;
+ m_lower_right = LowerRight;
+
+ /* init settings bar */
+
+ irr::core::rect<int> current_button = rect<s32>(UpperLeft.X, UpperLeft.Y,
+ LowerRight.X, LowerRight.Y);
+
+ m_starter.guibutton = m_guienv->addButton(current_button, 0, button_id, L"", 0);
+ m_starter.guibutton->grab();
+ m_starter.repeatcounter = -1;
+ m_starter.keycode = KEY_OEM_8; // use invalid keycode as it's not relevant
+ m_starter.immediate_release = true;
+ m_starter.ids.clear();
+
+ load_button_texture(&m_starter, starter_img, current_button,
+ m_texturesource, m_driver);
+
+ m_dir = dir;
+ m_timeout_value = timeout;
+
+ m_initialized = true;
+}
+
+AutoHideButtonBar::~AutoHideButtonBar()
+{
+ if (m_starter.guibutton) {
+ m_starter.guibutton->setVisible(false);
+ m_starter.guibutton->drop();
+ }
+}
+
+void AutoHideButtonBar::addButton(touch_gui_button_id button_id,
+ const wchar_t* caption, const char* btn_image)
+{
+
+ if (!m_initialized) {
+ errorstream << "AutoHideButtonBar::addButton not yet initialized!"
+ << std::endl;
+ return;
+ }
+ int button_size = 0;
+
+ if ((m_dir == AHBB_Dir_Top_Bottom) || (m_dir == AHBB_Dir_Bottom_Top)) {
+ button_size = m_lower_right.X - m_upper_left.X;
+ } else {
+ button_size = m_lower_right.Y - m_upper_left.Y;
+ }
+
+ irr::core::rect<int> current_button;
+
+ if ((m_dir == AHBB_Dir_Right_Left) || (m_dir == AHBB_Dir_Left_Right)) {
+
+ int x_start = 0;
+ int x_end = 0;
+
+ if (m_dir == AHBB_Dir_Left_Right) {
+ x_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size())
+ + (button_size * 0.25);
+ x_end = x_start + button_size;
+ } else {
+ x_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size())
+ - (button_size * 0.25);
+ x_start = x_end - button_size;
+ }
+
+ current_button = rect<s32>(x_start, m_upper_left.Y, x_end,
+ m_lower_right.Y);
+ } else {
+ int y_start = 0;
+ int y_end = 0;
+
+ if (m_dir == AHBB_Dir_Top_Bottom) {
+ y_start = m_lower_right.X + (button_size * 1.25 * m_buttons.size())
+ + (button_size * 0.25);
+ y_end = y_start + button_size;
+ } else {
+ y_end = m_upper_left.X - (button_size * 1.25 * m_buttons.size())
+ - (button_size * 0.25);
+ y_start = y_end - button_size;
+ }
+
+ current_button = rect<s32>(m_upper_left.X, y_start, m_lower_right.Y,
+ y_end);
+ }
+
+ button_info* btn = new button_info();
+ btn->guibutton = m_guienv->addButton(current_button, 0, button_id, caption, 0);
+ btn->guibutton->grab();
+ btn->guibutton->setVisible(false);
+ btn->guibutton->setEnabled(false);
+ btn->repeatcounter = -1;
+ btn->keycode = id2keycode(button_id);
+ btn->immediate_release = true;
+ btn->ids.clear();
+
+ load_button_texture(btn, btn_image, current_button, m_texturesource,
+ m_driver);
+
+ m_buttons.push_back(btn);
+}
+
+bool AutoHideButtonBar::isButton(const SEvent &event)
+{
+ IGUIElement* rootguielement = m_guienv->getRootGUIElement();
+
+ if (rootguielement == NULL) {
+ return false;
+ }
+
+ gui::IGUIElement *element = rootguielement->getElementFromPoint(
+ core::position2d<s32>(event.TouchInput.X, event.TouchInput.Y));
+
+ if (element == NULL) {
+ return false;
+ }
+
+ if (m_active) {
+ /* check for all buttons in vector */
+
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ if ((*iter)->guibutton == element) {
+
+ SEvent* translated = new SEvent();
+ memset(translated, 0, sizeof(SEvent));
+ translated->EventType = irr::EET_KEY_INPUT_EVENT;
+ translated->KeyInput.Key = (*iter)->keycode;
+ translated->KeyInput.Control = false;
+ translated->KeyInput.Shift = false;
+ translated->KeyInput.Char = 0;
+
+ /* add this event */
+ translated->KeyInput.PressedDown = true;
+ m_receiver->OnEvent(*translated);
+
+ /* remove this event */
+ translated->KeyInput.PressedDown = false;
+ m_receiver->OnEvent(*translated);
+
+ delete translated;
+
+ (*iter)->ids.push_back(event.TouchInput.ID);
+
+ m_timeout = 0;
+
+ return true;
+ }
+ ++iter;
+ }
+ } else {
+ /* check for starter button only */
+ if (element == m_starter.guibutton) {
+ m_starter.ids.push_back(event.TouchInput.ID);
+ m_starter.guibutton->setVisible(false);
+ m_starter.guibutton->setEnabled(false);
+ m_active = true;
+ m_timeout = 0;
+
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ (*iter)->guibutton->setVisible(true);
+ (*iter)->guibutton->setEnabled(true);
+ ++iter;
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
+
+bool AutoHideButtonBar::isReleaseButton(int eventID)
+{
+ std::vector<int>::iterator id = std::find(m_starter.ids.begin(),
+ m_starter.ids.end(), eventID);
+
+ if (id != m_starter.ids.end()) {
+ m_starter.ids.erase(id);
+ return true;
+ }
+
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ std::vector<int>::iterator id = std::find((*iter)->ids.begin(),
+ (*iter)->ids.end(), eventID);
+
+ if (id != (*iter)->ids.end()) {
+ (*iter)->ids.erase(id);
+ // TODO handle settings button release
+ return true;
+ }
+ ++iter;
+ }
+
+ return false;
+}
+
+void AutoHideButtonBar::step(float dtime)
+{
+ if (m_active) {
+ m_timeout += dtime;
+
+ if (m_timeout > m_timeout_value) {
+ deactivate();
+ }
+ }
+}
+
+void AutoHideButtonBar::deactivate()
+{
+ if (m_visible) {
+ m_starter.guibutton->setVisible(true);
+ m_starter.guibutton->setEnabled(true);
+ }
+ m_active = false;
+
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ (*iter)->guibutton->setVisible(false);
+ (*iter)->guibutton->setEnabled(false);
+ ++iter;
+ }
+}
+
+void AutoHideButtonBar::hide()
+{
+ m_visible = false;
+ m_starter.guibutton->setVisible(false);
+ m_starter.guibutton->setEnabled(false);
+
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ (*iter)->guibutton->setVisible(false);
+ (*iter)->guibutton->setEnabled(false);
+ ++iter;
+ }
+}
+
+void AutoHideButtonBar::show()
+{
+ m_visible = true;
+
+ if (m_active) {
+ std::vector<button_info*>::iterator iter = m_buttons.begin();
+
+ while (iter != m_buttons.end()) {
+ (*iter)->guibutton->setVisible(true);
+ (*iter)->guibutton->setEnabled(true);
+ ++iter;
+ }
+ } else {
+ m_starter.guibutton->setVisible(true);
+ m_starter.guibutton->setEnabled(true);
+ }
+}
+
+TouchScreenGUI::TouchScreenGUI(IrrlichtDevice *device, IEventReceiver* receiver):
+ m_device(device),
+ m_guienv(device->getGUIEnvironment()),
+ m_receiver(receiver),
+ m_settingsbar(device, receiver),
+ m_rarecontrolsbar(device, receiver)
+{
+ for (unsigned int i=0; i < after_last_element_id; i++) {
+ m_buttons[i].guibutton = 0;
+ m_buttons[i].repeatcounter = -1;
+ m_buttons[i].repeatdelay = BUTTON_REPEAT_DELAY;
+ }
+
+ m_screensize = m_device->getVideoDriver()->getScreenSize();
+}
+
+void TouchScreenGUI::initButton(touch_gui_button_id id, rect<s32> button_rect,
+ std::wstring caption, bool immediate_release, float repeat_delay)
+{
+
+ button_info* btn = &m_buttons[id];
+ btn->guibutton = m_guienv->addButton(button_rect, 0, id, caption.c_str());
+ btn->guibutton->grab();
+ btn->repeatcounter = -1;
+ btn->repeatdelay = repeat_delay;
+ btn->keycode = id2keycode(id);
+ btn->immediate_release = immediate_release;
+ btn->ids.clear();
+
+ load_button_texture(btn,touchgui_button_imagenames[id],button_rect,
+ m_texturesource, m_device->getVideoDriver());
+}
+
+static int getMaxControlPadSize(float density) {
+ return 200 * density * g_settings->getFloat("hud_scaling");
+}
+
+int TouchScreenGUI::getGuiButtonSize()
+{
+ u32 control_pad_size = MYMIN((2 * m_screensize.Y) / 3,
+ getMaxControlPadSize(porting::getDisplayDensity()));
+
+ return control_pad_size / 3;
+}
+
+void TouchScreenGUI::init(ISimpleTextureSource* tsrc)
+{
+ assert(tsrc != 0);
+
+ u32 button_size = getGuiButtonSize();
+ m_visible = true;
+ m_texturesource = tsrc;
+ /*
+ draw control pad
+ 0 1 2
+ 3 4 5
+ for now only 0, 1, 2, and 4 are used
+ */
+ int number = 0;
+ for (int y = 0; y < 2; ++y)
+ for (int x = 0; x < 3; ++x, ++number) {
+ rect<s32> button_rect(
+ x * button_size, m_screensize.Y - button_size * (2 - y),
+ (x + 1) * button_size, m_screensize.Y - button_size * (1 - y)
+ );
+ touch_gui_button_id id = after_last_element_id;
+ std::wstring caption;
+ switch (number) {
+ case 0:
+ id = left_id;
+ caption = L"<";
+ break;
+ case 1:
+ id = forward_id;
+ caption = L"^";
+ break;
+ case 2:
+ id = right_id;
+ caption = L">";
+ break;
+ case 4:
+ id = backward_id;
+ caption = L"v";
+ break;
+ }
+ if (id != after_last_element_id) {
+ initButton(id, button_rect, caption, false);
+ }
+ }
+
+ /* init jump button */
+ initButton(jump_id,
+ rect<s32>(m_screensize.X-(1.75*button_size),
+ m_screensize.Y - (0.5*button_size),
+ m_screensize.X-(0.25*button_size),
+ m_screensize.Y),
+ L"x",false);
+
+ /* init crunch button */
+ initButton(crunch_id,
+ rect<s32>(m_screensize.X-(3.25*button_size),
+ m_screensize.Y - (0.5*button_size),
+ m_screensize.X-(1.75*button_size),
+ m_screensize.Y),
+ L"H",false);
+
+ m_settingsbar.init(m_texturesource, "gear_icon.png", settings_starter_id,
+ v2s32(m_screensize.X - (button_size / 2),
+ m_screensize.Y - ((SETTINGS_BAR_Y_OFFSET + 1) * button_size)
+ + (button_size * 0.5)),
+ v2s32(m_screensize.X,
+ m_screensize.Y - (SETTINGS_BAR_Y_OFFSET * button_size)
+ + (button_size * 0.5)), AHBB_Dir_Right_Left,
+ 3.0);
+
+ m_settingsbar.addButton(fly_id, L"fly", "fly_btn.png");
+ m_settingsbar.addButton(noclip_id, L"noclip", "noclip_btn.png");
+ m_settingsbar.addButton(fast_id, L"fast", "fast_btn.png");
+ m_settingsbar.addButton(debug_id, L"debug", "debug_btn.png");
+ m_settingsbar.addButton(camera_id, L"camera", "camera_btn.png");
+ m_settingsbar.addButton(range_id, L"rangeview", "rangeview_btn.png");
+
+ m_rarecontrolsbar.init(m_texturesource, "rare_controls.png",
+ rare_controls_starter_id,
+ v2s32(0,
+ m_screensize.Y
+ - ((RARE_CONTROLS_BAR_Y_OFFSET + 1) * button_size)
+ + (button_size * 0.5)),
+ v2s32(button_size / 2,
+ m_screensize.Y - (RARE_CONTROLS_BAR_Y_OFFSET * button_size)
+ + (button_size * 0.5)), AHBB_Dir_Left_Right,
+ 2);
+
+ m_rarecontrolsbar.addButton(chat_id, L"Chat", "chat_btn.png");
+ m_rarecontrolsbar.addButton(inventory_id, L"inv", "inventory_btn.png");
+ m_rarecontrolsbar.addButton(drop_id, L"drop", "drop_btn.png");
+
+}
+
+touch_gui_button_id TouchScreenGUI::getButtonID(s32 x, s32 y)
+{
+ IGUIElement* rootguielement = m_guienv->getRootGUIElement();
+
+ if (rootguielement != NULL) {
+ gui::IGUIElement *element =
+ rootguielement->getElementFromPoint(core::position2d<s32>(x,y));
+
+ if (element) {
+ for (unsigned int i=0; i < after_last_element_id; i++) {
+ if (element == m_buttons[i].guibutton) {
+ return (touch_gui_button_id) i;
+ }
+ }
+ }
+ }
+ return after_last_element_id;
+}
+
+touch_gui_button_id TouchScreenGUI::getButtonID(int eventID)
+{
+ for (unsigned int i=0; i < after_last_element_id; i++) {
+ button_info* btn = &m_buttons[i];
+
+ std::vector<int>::iterator id =
+ std::find(btn->ids.begin(),btn->ids.end(), eventID);
+
+ if (id != btn->ids.end())
+ return (touch_gui_button_id) i;
+ }
+
+ return after_last_element_id;
+}
+
+bool TouchScreenGUI::isHUDButton(const SEvent &event)
+{
+ // check if hud item is pressed
+ for (std::map<int,rect<s32> >::iterator iter = m_hud_rects.begin();
+ iter != m_hud_rects.end(); ++iter) {
+ if (iter->second.isPointInside(
+ v2s32(event.TouchInput.X,
+ event.TouchInput.Y)
+ )) {
+ if ( iter->first < 8) {
+ SEvent* translated = new SEvent();
+ memset(translated,0,sizeof(SEvent));
+ translated->EventType = irr::EET_KEY_INPUT_EVENT;
+ translated->KeyInput.Key = (irr::EKEY_CODE) (KEY_KEY_1 + iter->first);
+ translated->KeyInput.Control = false;
+ translated->KeyInput.Shift = false;
+ translated->KeyInput.PressedDown = true;
+ m_receiver->OnEvent(*translated);
+ m_hud_ids[event.TouchInput.ID] = translated->KeyInput.Key;
+ delete translated;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool TouchScreenGUI::isReleaseHUDButton(int eventID)
+{
+ std::map<int,irr::EKEY_CODE>::iterator iter = m_hud_ids.find(eventID);
+
+ if (iter != m_hud_ids.end()) {
+ SEvent* translated = new SEvent();
+ memset(translated,0,sizeof(SEvent));
+ translated->EventType = irr::EET_KEY_INPUT_EVENT;
+ translated->KeyInput.Key = iter->second;
+ translated->KeyInput.PressedDown = false;
+ translated->KeyInput.Control = false;
+ translated->KeyInput.Shift = false;
+ m_receiver->OnEvent(*translated);
+ m_hud_ids.erase(iter);
+ delete translated;
+ return true;
+ }
+ return false;
+}
+
+void TouchScreenGUI::handleButtonEvent(touch_gui_button_id button,
+ int eventID, bool action)
+{
+ button_info* btn = &m_buttons[button];
+ SEvent* translated = new SEvent();
+ memset(translated,0,sizeof(SEvent));
+ translated->EventType = irr::EET_KEY_INPUT_EVENT;
+ translated->KeyInput.Key = btn->keycode;
+ translated->KeyInput.Control = false;
+ translated->KeyInput.Shift = false;
+ translated->KeyInput.Char = 0;
+
+ /* add this event */
+ if (action) {
+ assert(std::find(btn->ids.begin(),btn->ids.end(), eventID) == btn->ids.end());
+
+ btn->ids.push_back(eventID);
+
+ if (btn->ids.size() > 1) return;
+
+ btn->repeatcounter = 0;
+ translated->KeyInput.PressedDown = true;
+ translated->KeyInput.Key = btn->keycode;
+ m_receiver->OnEvent(*translated);
+ }
+ /* remove event */
+ if ((!action) || (btn->immediate_release)) {
+
+ std::vector<int>::iterator pos =
+ std::find(btn->ids.begin(),btn->ids.end(), eventID);
+ /* has to be in touch list */
+ assert(pos != btn->ids.end());
+ btn->ids.erase(pos);
+
+ if (btn->ids.size() > 0) { return; }
+
+ translated->KeyInput.PressedDown = false;
+ btn->repeatcounter = -1;
+ m_receiver->OnEvent(*translated);
+ }
+ delete translated;
+}
+
+
+void TouchScreenGUI::handleReleaseEvent(int evt_id)
+{
+ touch_gui_button_id button = getButtonID(evt_id);
+
+ /* handle button events */
+ if (button != after_last_element_id) {
+ handleButtonEvent(button, evt_id, false);
+ }
+ /* handle hud button events */
+ else if (isReleaseHUDButton(evt_id)) {
+ /* nothing to do here */
+ } else if (m_settingsbar.isReleaseButton(evt_id)) {
+ /* nothing to do here */
+ } else if (m_rarecontrolsbar.isReleaseButton(evt_id)) {
+ /* nothing to do here */
+ }
+ /* handle the point used for moving view */
+ else if (evt_id == m_move_id) {
+ m_move_id = -1;
+
+ /* if this pointer issued a mouse event issue symmetric release here */
+ if (m_move_sent_as_mouse_event) {
+ SEvent* translated = new SEvent;
+ memset(translated,0,sizeof(SEvent));
+ translated->EventType = EET_MOUSE_INPUT_EVENT;
+ translated->MouseInput.X = m_move_downlocation.X;
+ translated->MouseInput.Y = m_move_downlocation.Y;
+ translated->MouseInput.Shift = false;
+ translated->MouseInput.Control = false;
+ translated->MouseInput.ButtonStates = 0;
+ translated->MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
+ m_receiver->OnEvent(*translated);
+ delete translated;
+ }
+ else {
+ /* do double tap detection */
+ doubleTapDetection();
+ }
+ }
+ else {
+ infostream
+ << "TouchScreenGUI::translateEvent released unknown button: "
+ << evt_id << std::endl;
+ }
+
+ for (std::vector<id_status>::iterator iter = m_known_ids.begin();
+ iter != m_known_ids.end(); ++iter) {
+ if (iter->id == evt_id) {
+ m_known_ids.erase(iter);
+ break;
+ }
+ }
+}
+
+void TouchScreenGUI::translateEvent(const SEvent &event)
+{
+ if (!m_visible) {
+ infostream << "TouchScreenGUI::translateEvent got event but not visible?!" << std::endl;
+ return;
+ }
+
+ if (event.EventType != EET_TOUCH_INPUT_EVENT) {
+ return;
+ }
+
+ if (event.TouchInput.Event == ETIE_PRESSED_DOWN) {
+
+ /* add to own copy of eventlist ...
+ * android would provide this information but irrlicht guys don't
+ * wanna design a efficient interface
+ */
+ id_status toadd;
+ toadd.id = event.TouchInput.ID;
+ toadd.X = event.TouchInput.X;
+ toadd.Y = event.TouchInput.Y;
+ m_known_ids.push_back(toadd);
+
+ int eventID = event.TouchInput.ID;
+
+ touch_gui_button_id button =
+ getButtonID(event.TouchInput.X, event.TouchInput.Y);
+
+ /* handle button events */
+ if (button != after_last_element_id) {
+ handleButtonEvent(button, eventID, true);
+ m_settingsbar.deactivate();
+ m_rarecontrolsbar.deactivate();
+ } else if (isHUDButton(event)) {
+ m_settingsbar.deactivate();
+ m_rarecontrolsbar.deactivate();
+ /* already handled in isHUDButton() */
+ } else if (m_settingsbar.isButton(event)) {
+ m_rarecontrolsbar.deactivate();
+ /* already handled in isSettingsBarButton() */
+ } else if (m_rarecontrolsbar.isButton(event)) {
+ m_settingsbar.deactivate();
+ /* already handled in isSettingsBarButton() */
+ }
+ /* handle non button events */
+ else {
+ m_settingsbar.deactivate();
+ m_rarecontrolsbar.deactivate();
+ /* if we don't already have a moving point make this the moving one */
+ if (m_move_id == -1) {
+ m_move_id = event.TouchInput.ID;
+ m_move_has_really_moved = false;
+ m_move_downtime = porting::getTimeMs();
+ m_move_downlocation = v2s32(event.TouchInput.X, event.TouchInput.Y);
+ m_move_sent_as_mouse_event = false;
+ }
+ }
+
+ m_pointerpos[event.TouchInput.ID] = v2s32(event.TouchInput.X, event.TouchInput.Y);
+ }
+ else if (event.TouchInput.Event == ETIE_LEFT_UP) {
+ verbosestream << "Up event for pointerid: " << event.TouchInput.ID << std::endl;
+ handleReleaseEvent(event.TouchInput.ID);
+ }
+ else {
+ assert(event.TouchInput.Event == ETIE_MOVED);
+ int move_idx = event.TouchInput.ID;
+
+ if (m_pointerpos[event.TouchInput.ID] ==
+ v2s32(event.TouchInput.X, event.TouchInput.Y)) {
+ return;
+ }
+
+ if (m_move_id != -1) {
+ if ((event.TouchInput.ID == m_move_id) &&
+ (!m_move_sent_as_mouse_event)) {
+
+ double distance = sqrt(
+ (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) *
+ (m_pointerpos[event.TouchInput.ID].X - event.TouchInput.X) +
+ (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y) *
+ (m_pointerpos[event.TouchInput.ID].Y - event.TouchInput.Y));
+
+ if ((distance > g_settings->getU16("touchscreen_threshold")) ||
+ (m_move_has_really_moved)) {
+ m_move_has_really_moved = true;
+ s32 X = event.TouchInput.X;
+ s32 Y = event.TouchInput.Y;
+
+ // update camera_yaw and camera_pitch
+ s32 dx = X - m_pointerpos[event.TouchInput.ID].X;
+ s32 dy = Y - m_pointerpos[event.TouchInput.ID].Y;
+
+ /* adapt to similar behaviour as pc screen */
+ double d = g_settings->getFloat("mouse_sensitivity") *4;
+ double old_yaw = m_camera_yaw_change;
+ double old_pitch = m_camera_pitch;
+
+ m_camera_yaw_change -= dx * d;
+ m_camera_pitch = MYMIN(MYMAX(m_camera_pitch + (dy * d), -180), 180);
+
+ // update shootline
+ m_shootline = m_device
+ ->getSceneManager()
+ ->getSceneCollisionManager()
+ ->getRayFromScreenCoordinates(v2s32(X, Y));
+ m_pointerpos[event.TouchInput.ID] = v2s32(X, Y);
+ }
+ }
+ else if ((event.TouchInput.ID == m_move_id) &&
+ (m_move_sent_as_mouse_event)) {
+ m_shootline = m_device
+ ->getSceneManager()
+ ->getSceneCollisionManager()
+ ->getRayFromScreenCoordinates(
+ v2s32(event.TouchInput.X,event.TouchInput.Y));
+ }
+ } else {
+ handleChangedButton(event);
+ }
+ }
+}
+
+void TouchScreenGUI::handleChangedButton(const SEvent &event)
+{
+ for (unsigned int i = 0; i < after_last_element_id; i++) {
+
+ if (m_buttons[i].ids.empty()) {
+ continue;
+ }
+ for (std::vector<int>::iterator iter = m_buttons[i].ids.begin();
+ iter != m_buttons[i].ids.end(); ++iter) {
+
+ if (event.TouchInput.ID == *iter) {
+
+ int current_button_id =
+ getButtonID(event.TouchInput.X, event.TouchInput.Y);
+
+ if (current_button_id == i) {
+ continue;
+ }
+
+ /* remove old button */
+ handleButtonEvent((touch_gui_button_id) i,*iter,false);
+
+ if (current_button_id == after_last_element_id) {
+ return;
+ }
+ handleButtonEvent((touch_gui_button_id) current_button_id,*iter,true);
+ return;
+
+ }
+ }
+ }
+
+ int current_button_id = getButtonID(event.TouchInput.X, event.TouchInput.Y);
+
+ if (current_button_id == after_last_element_id) {
+ return;
+ }
+
+ button_info* btn = &m_buttons[current_button_id];
+ if (std::find(btn->ids.begin(),btn->ids.end(), event.TouchInput.ID)
+ == btn->ids.end())
+ {
+ handleButtonEvent((touch_gui_button_id) current_button_id,
+ event.TouchInput.ID, true);
+ }
+
+}
+
+bool TouchScreenGUI::doubleTapDetection()
+{
+ m_key_events[0].down_time = m_key_events[1].down_time;
+ m_key_events[0].x = m_key_events[1].x;
+ m_key_events[0].y = m_key_events[1].y;
+ m_key_events[1].down_time = m_move_downtime;
+ m_key_events[1].x = m_move_downlocation.X;
+ m_key_events[1].y = m_move_downlocation.Y;
+
+ u64 delta = porting::getDeltaMs(m_key_events[0].down_time, porting::getTimeMs());
+ if (delta > 400)
+ return false;
+
+ double distance = sqrt(
+ (m_key_events[0].x - m_key_events[1].x) * (m_key_events[0].x - m_key_events[1].x) +
+ (m_key_events[0].y - m_key_events[1].y) * (m_key_events[0].y - m_key_events[1].y));
+
+
+ if (distance > (20 + g_settings->getU16("touchscreen_threshold")))
+ return false;
+
+ SEvent* translated = new SEvent();
+ memset(translated, 0, sizeof(SEvent));
+ translated->EventType = EET_MOUSE_INPUT_EVENT;
+ translated->MouseInput.X = m_key_events[0].x;
+ translated->MouseInput.Y = m_key_events[0].y;
+ translated->MouseInput.Shift = false;
+ translated->MouseInput.Control = false;
+ translated->MouseInput.ButtonStates = EMBSM_RIGHT;
+
+ // update shootline
+ m_shootline = m_device
+ ->getSceneManager()
+ ->getSceneCollisionManager()
+ ->getRayFromScreenCoordinates(v2s32(m_key_events[0].x, m_key_events[0].y));
+
+ translated->MouseInput.Event = EMIE_RMOUSE_PRESSED_DOWN;
+ verbosestream << "TouchScreenGUI::translateEvent right click press" << std::endl;
+ m_receiver->OnEvent(*translated);
+
+ translated->MouseInput.ButtonStates = 0;
+ translated->MouseInput.Event = EMIE_RMOUSE_LEFT_UP;
+ verbosestream << "TouchScreenGUI::translateEvent right click release" << std::endl;
+ m_receiver->OnEvent(*translated);
+ delete translated;
+ return true;
+
+}
+
+TouchScreenGUI::~TouchScreenGUI()
+{
+ for (unsigned int i = 0; i < after_last_element_id; i++) {
+ button_info* btn = &m_buttons[i];
+ if (btn->guibutton != 0) {
+ btn->guibutton->drop();
+ btn->guibutton = NULL;
+ }
+ }
+}
+
+void TouchScreenGUI::step(float dtime)
+{
+ /* simulate keyboard repeats */
+ for (unsigned int i = 0; i < after_last_element_id; i++) {
+ button_info* btn = &m_buttons[i];
+
+ if (btn->ids.size() > 0) {
+ btn->repeatcounter += dtime;
+
+ /* in case we're moving around digging does not happen */
+ if (m_move_id != -1)
+ m_move_has_really_moved = true;
+
+ if (btn->repeatcounter < btn->repeatdelay) continue;
+
+ btn->repeatcounter = 0;
+ SEvent translated;
+ memset(&translated, 0, sizeof(SEvent));
+ translated.EventType = irr::EET_KEY_INPUT_EVENT;
+ translated.KeyInput.Key = btn->keycode;
+ translated.KeyInput.PressedDown = false;
+ m_receiver->OnEvent(translated);
+
+ translated.KeyInput.PressedDown = true;
+ m_receiver->OnEvent(translated);
+ }
+ }
+
+ /* if a new placed pointer isn't moved for some time start digging */
+ if ((m_move_id != -1) &&
+ (!m_move_has_really_moved) &&
+ (!m_move_sent_as_mouse_event)) {
+
+ u64 delta = porting::getDeltaMs(m_move_downtime, porting::getTimeMs());
+
+ if (delta > MIN_DIG_TIME_MS) {
+ m_shootline = m_device
+ ->getSceneManager()
+ ->getSceneCollisionManager()
+ ->getRayFromScreenCoordinates(
+ v2s32(m_move_downlocation.X,m_move_downlocation.Y));
+
+ SEvent translated;
+ memset(&translated, 0, sizeof(SEvent));
+ translated.EventType = EET_MOUSE_INPUT_EVENT;
+ translated.MouseInput.X = m_move_downlocation.X;
+ translated.MouseInput.Y = m_move_downlocation.Y;
+ translated.MouseInput.Shift = false;
+ translated.MouseInput.Control = false;
+ translated.MouseInput.ButtonStates = EMBSM_LEFT;
+ translated.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
+ verbosestream << "TouchScreenGUI::step left click press" << std::endl;
+ m_receiver->OnEvent(translated);
+ m_move_sent_as_mouse_event = true;
+ }
+ }
+
+ m_settingsbar.step(dtime);
+ m_rarecontrolsbar.step(dtime);
+}
+
+void TouchScreenGUI::resetHud()
+{
+ m_hud_rects.clear();
+}
+
+void TouchScreenGUI::registerHudItem(int index, const rect<s32> &rect)
+{
+ m_hud_rects[index] = rect;
+}
+
+void TouchScreenGUI::Toggle(bool visible)
+{
+ m_visible = visible;
+ for (unsigned int i = 0; i < after_last_element_id; i++) {
+ button_info* btn = &m_buttons[i];
+ if (btn->guibutton != 0) {
+ btn->guibutton->setVisible(visible);
+ }
+ }
+
+ /* clear all active buttons */
+ if (!visible) {
+ while (m_known_ids.size() > 0) {
+ handleReleaseEvent(m_known_ids.begin()->id);
+ }
+
+ m_settingsbar.hide();
+ m_rarecontrolsbar.hide();
+ } else {
+ m_settingsbar.show();
+ m_rarecontrolsbar.show();
+ }
+}
+
+void TouchScreenGUI::hide()
+{
+ if (!m_visible)
+ return;
+
+ Toggle(false);
+}
+
+void TouchScreenGUI::show()
+{
+ if (m_visible)
+ return;
+
+ Toggle(true);
+}
diff --git a/src/gui/touchscreengui.h b/src/gui/touchscreengui.h
new file mode 100644
index 000000000..da97381cd
--- /dev/null
+++ b/src/gui/touchscreengui.h
@@ -0,0 +1,267 @@
+/*
+Copyright (C) 2014 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.
+*/
+
+#pragma once
+
+#include <IEventReceiver.h>
+#include <IGUIButton.h>
+#include <IGUIEnvironment.h>
+
+#include <map>
+#include <vector>
+
+#include "client/tile.h"
+#include "game.h"
+
+using namespace irr;
+using namespace irr::core;
+using namespace irr::gui;
+
+typedef enum {
+ forward_id = 0,
+ backward_id,
+ left_id,
+ right_id,
+ jump_id,
+ crunch_id,
+ after_last_element_id,
+ settings_starter_id,
+ rare_controls_starter_id,
+ fly_id,
+ noclip_id,
+ fast_id,
+ debug_id,
+ camera_id,
+ range_id,
+ chat_id,
+ inventory_id,
+ drop_id
+} touch_gui_button_id;
+
+typedef enum {
+ AHBB_Dir_Top_Bottom,
+ AHBB_Dir_Bottom_Top,
+ AHBB_Dir_Left_Right,
+ AHBB_Dir_Right_Left
+} autohide_button_bar_dir;
+
+#define MIN_DIG_TIME_MS 500
+#define MAX_TOUCH_COUNT 64
+#define BUTTON_REPEAT_DELAY 0.2f
+
+#define SETTINGS_BAR_Y_OFFSET 6.5
+#define RARE_CONTROLS_BAR_Y_OFFSET 4
+
+extern const char **touchgui_button_imagenames;
+
+struct button_info
+{
+ float repeatcounter;
+ float repeatdelay;
+ irr::EKEY_CODE keycode;
+ std::vector<int> ids;
+ IGUIButton *guibutton = nullptr;
+ bool immediate_release;
+};
+
+class AutoHideButtonBar
+{
+public:
+ AutoHideButtonBar(IrrlichtDevice *device, IEventReceiver *receiver);
+
+ void init(ISimpleTextureSource *tsrc, const char *starter_img, int button_id,
+ v2s32 UpperLeft, v2s32 LowerRight, autohide_button_bar_dir dir,
+ float timeout);
+
+ ~AutoHideButtonBar();
+
+ /* add button to be shown */
+ void addButton(touch_gui_button_id id, const wchar_t *caption,
+ const char *btn_image);
+
+ /* detect settings bar button events */
+ bool isButton(const SEvent &event);
+
+ /* handle released hud buttons */
+ bool isReleaseButton(int eventID);
+
+ /* step handler */
+ void step(float dtime);
+
+ /* deactivate button bar */
+ void deactivate();
+
+ /* hide the whole buttonbar */
+ void hide();
+
+ /* unhide the buttonbar */
+ void show();
+
+private:
+ ISimpleTextureSource *m_texturesource = nullptr;
+ irr::video::IVideoDriver *m_driver;
+ IGUIEnvironment *m_guienv;
+ IEventReceiver *m_receiver;
+ button_info m_starter;
+ std::vector<button_info *> m_buttons;
+
+ v2s32 m_upper_left;
+ v2s32 m_lower_right;
+
+ /* show settings bar */
+ bool m_active = false;
+
+ bool m_visible = true;
+
+ /* settings bar timeout */
+ float m_timeout = 0.0f;
+ float m_timeout_value = 3.0f;
+ bool m_initialized = false;
+ autohide_button_bar_dir m_dir = AHBB_Dir_Right_Left;
+};
+
+class TouchScreenGUI
+{
+public:
+ TouchScreenGUI(IrrlichtDevice *device, IEventReceiver *receiver);
+ ~TouchScreenGUI();
+
+ void translateEvent(const SEvent &event);
+
+ void init(ISimpleTextureSource *tsrc);
+
+ double getYawChange()
+ {
+ double res = m_camera_yaw_change;
+ m_camera_yaw_change = 0;
+ return res;
+ }
+
+ double getPitch() { return m_camera_pitch; }
+
+ /*!
+ * Returns a line which describes what the player is pointing at.
+ * The starting point and looking direction are significant,
+ * the line should be scaled to match its length to the actual distance
+ * the player can reach.
+ * The line starts at the camera and ends on the camera's far plane.
+ * The coordinates do not contain the camera offset.
+ */
+ line3d<f32> getShootline() { return m_shootline; }
+
+ void step(float dtime);
+ void resetHud();
+ void registerHudItem(int index, const rect<s32> &rect);
+ void Toggle(bool visible);
+
+ void hide();
+ void show();
+
+private:
+ IrrlichtDevice *m_device;
+ IGUIEnvironment *m_guienv;
+ IEventReceiver *m_receiver;
+ ISimpleTextureSource *m_texturesource;
+ v2u32 m_screensize;
+ std::map<int, rect<s32>> m_hud_rects;
+ std::map<int, irr::EKEY_CODE> m_hud_ids;
+ bool m_visible; // is the gui visible
+
+ /* value in degree */
+ double m_camera_yaw_change = 0.0;
+ double m_camera_pitch = 0.0;
+
+ /*!
+ * A line starting at the camera and pointing towards the
+ * selected object.
+ * The line ends on the camera's far plane.
+ * The coordinates do not contain the camera offset.
+ */
+ line3d<f32> m_shootline;
+
+ int m_move_id = -1;
+ bool m_move_has_really_moved = false;
+ s64 m_move_downtime = 0;
+ bool m_move_sent_as_mouse_event = false;
+ v2s32 m_move_downlocation = v2s32(-10000, -10000);
+
+ button_info m_buttons[after_last_element_id];
+
+ /* gui button detection */
+ touch_gui_button_id getButtonID(s32 x, s32 y);
+
+ /* gui button by eventID */
+ touch_gui_button_id getButtonID(int eventID);
+
+ /* check if a button has changed */
+ void handleChangedButton(const SEvent &event);
+
+ /* initialize a button */
+ void initButton(touch_gui_button_id id, rect<s32> button_rect,
+ std::wstring caption, bool immediate_release,
+ float repeat_delay = BUTTON_REPEAT_DELAY);
+
+ struct id_status
+ {
+ int id;
+ int X;
+ int Y;
+ };
+
+ /* vector to store known ids and their initial touch positions*/
+ std::vector<id_status> m_known_ids;
+
+ /* handle a button event */
+ void handleButtonEvent(touch_gui_button_id bID, int eventID, bool action);
+
+ /* handle pressed hud buttons */
+ bool isHUDButton(const SEvent &event);
+
+ /* handle released hud buttons */
+ bool isReleaseHUDButton(int eventID);
+
+ /* handle double taps */
+ bool doubleTapDetection();
+
+ /* handle release event */
+ void handleReleaseEvent(int evt_id);
+
+ /* get size of regular gui control button */
+ int getGuiButtonSize();
+
+ /* doubleclick detection variables */
+ struct key_event
+ {
+ unsigned int down_time;
+ s32 x;
+ s32 y;
+ };
+
+ /* array for saving last known position of a pointer */
+ v2s32 m_pointerpos[MAX_TOUCH_COUNT];
+
+ /* array for doubletap detection */
+ key_event m_key_events[2];
+
+ /* settings bar */
+ AutoHideButtonBar m_settingsbar;
+
+ /* rare controls bar */
+ AutoHideButtonBar m_rarecontrolsbar;
+};
+extern TouchScreenGUI *g_touchscreengui;