From 572990dcd3f43386860b5b14015ad722fb96ea83 Mon Sep 17 00:00:00 2001 From: est31 Date: Wed, 10 Jun 2015 00:35:21 +0200 Subject: [PATCH] Add utf-8 conversion utilities and re-add intlGUIEditBox --- build/android/Makefile | 69 +- build/android/jni/Android.mk | 9 +- build/android/libiconv_android.patch | 39 + build/android/libiconv_android_mk.patch | 51 + build/android/libiconv_stdio.patch | 10 + src/CMakeLists.txt | 1 + src/intlGUIEditBox.cpp | 1509 +++++++++++++++++++++++ src/intlGUIEditBox.h | 178 +++ src/util/string.cpp | 94 ++ src/util/string.h | 10 +- 10 files changed, 1966 insertions(+), 4 deletions(-) create mode 100644 build/android/libiconv_android.patch create mode 100644 build/android/libiconv_android_mk.patch create mode 100644 build/android/libiconv_stdio.patch create mode 100644 src/intlGUIEditBox.cpp create mode 100644 src/intlGUIEditBox.h diff --git a/build/android/Makefile b/build/android/Makefile index 1b33a4599..798d56037 100644 --- a/build/android/Makefile +++ b/build/android/Makefile @@ -131,6 +131,13 @@ FREETYPE_TIMESTAMP = $(FREETYPE_DIR)timestamp FREETYPE_TIMESTAMP_INT = $(ROOT)/deps/freetype_timestamp FREETYPE_URL_GIT = https://github.com/cdave1/freetype2-android +ICONV_VERSION = 1.14 +ICONV_DIR = $(ROOT)/deps/libiconv/ +ICONV_LIB = $(ICONV_DIR)/iconv.so +ICONV_TIMESTAMP = $(ICONV_DIR)timestamp +ICONV_TIMESTAMP_INT = $(ROOT)/deps/iconv_timestamp +ICONV_URL_HTTP = http://ftp.gnu.org/pub/gnu/libiconv/libiconv-$(ICONV_VERSION).tar.gz + SQLITE3_FOLDER = sqlite-amalgamation-3080704 SQLITE3_URL = http://www.sqlite.org/2014/$(SQLITE3_FOLDER).zip @@ -431,6 +438,64 @@ $(FREETYPE_LIB) : $(FREETYPE_TIMESTAMP) clean_freetype : $(RM) -rf ${FREETYPE_DIR} +$(ICONV_TIMESTAMP) : iconv_download + @LAST_MODIF=$$(find ${ICONV_DIR} -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "); \ + if [ $$(basename $$LAST_MODIF) != "timestamp" ] ; then \ + touch ${ICONV_TIMESTAMP}; \ + fi + +iconv_download : + @if [ ! -d ${ICONV_DIR} ] ; then \ + echo "iconv sources missing, downloading..."; \ + mkdir -p ${ROOT}/deps; \ + cd ${ROOT}/deps; \ + wget ${ICONV_URL_HTTP} || exit 1; \ + tar -xzf libiconv-${ICONV_VERSION}.tar.gz || exit 1; \ + rm libiconv-${ICONV_VERSION}.tar.gz; \ + ln -s libiconv-${ICONV_VERSION} libiconv; \ + cd ${ICONV_DIR}; \ + patch -p1 < ${ROOT}/libiconv_android.patch; \ + patch -p1 < ${ROOT}/libiconv_android_mk.patch; \ + patch -p1 < ${ROOT}/libiconv_stdio.patch; \ + cd jni; \ + ln -s .. src; \ + fi + +iconv : $(ICONV_LIB) + +$(ICONV_LIB) : $(ICONV_TIMESTAMP) + @REFRESH=0; \ + if [ ! -e ${ICONV_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ ! -e ${ICONV_LIB} ] ; then \ + REFRESH=1; \ + fi; \ + if [ ${ICONV_TIMESTAMP} -nt ${ICONV_TIMESTAMP_INT} ] ; then \ + REFRESH=1; \ + fi; \ + if [ $$REFRESH -ne 0 ] ; then \ + mkdir -p ${ICONV_DIR}; \ + export PATH=$$PATH:${SDKFOLDER}/platform-tools:${ANDROID_NDK}; \ + echo "changed timestamp for iconv detected building..."; \ + cd ${ICONV_DIR}; \ + ./configure; \ + make; \ + export NDK_PROJECT_PATH=${ICONV_DIR}; \ + ndk-build NDEBUG=${NDEBUG} NDK_MODULE_PATH=${NDK_MODULE_PATH} \ + APP_PLATFORM=${APP_PLATFORM} APP_ABI=${TARGET_ABI} \ + TARGET_CFLAGS+="${TARGET_CFLAGS_ADDON}" \ + TARGET_LDFLAGS+="${TARGET_LDFLAGS_ADDON}" \ + TARGET_CXXFLAGS+="${TARGET_CXXFLAGS_ADDON}" || exit 1; \ + touch ${ICONV_TIMESTAMP}; \ + touch ${ICONV_TIMESTAMP_INT}; \ + else \ + echo "nothing to be done for iconv"; \ + fi + +clean_iconv : + $(RM) -rf ${ICONV_DIR} + #Note: Texturehack patch is required for gpu's not supporting color format # correctly. Known bad GPU: # -geforce on emulator @@ -736,7 +801,7 @@ assets : $(ASSETS_TIMESTAMP) clean_assets : @$(RM) -r assets -apk: $(PATHCFGFILE) assets $(IRRLICHT_LIB) $(CURL_LIB) $(GMP_LIB) $(LEVELDB_TARGET) \ +apk: $(PATHCFGFILE) assets $(ICONV_LIB) $(IRRLICHT_LIB) $(CURL_LIB) $(GMP_LIB) $(LEVELDB_TARGET) \ $(OPENAL_LIB) $(OGG_LIB) prep_srcdir $(ROOT)/jni/src/android_version.h \ sqlite3_download @export NDEBUG=$$NDEBUG; $(MAKE) manifest; \ @@ -778,7 +843,7 @@ envpaths : clean_all : @$(MAKE) clean_apk; \ - $(MAKE) clean_assets clean_irrlicht clean_leveldb clean_curl clean_openssl \ + $(MAKE) clean_assets clean_iconv clean_irrlicht clean_leveldb clean_curl clean_openssl \ clean_openal clean_ogg clean_gmp clean_manifest; \ sleep 1; \ $(RM) -r gen libs obj deps bin Debug and_env diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk index 2c574bc41..d62698ce4 100644 --- a/build/android/jni/Android.mk +++ b/build/android/jni/Android.mk @@ -24,6 +24,11 @@ LOCAL_MODULE := freetype LOCAL_SRC_FILES := deps/freetype2-android/Android/obj/local/$(TARGET_ARCH_ABI)/libfreetype2-static.a include $(PREBUILT_STATIC_LIBRARY) +include $(CLEAR_VARS) +LOCAL_MODULE := iconv +LOCAL_SRC_FILES := deps/libiconv/obj/local/$(TARGET_ARCH_ABI)/libiconv.a +include $(PREBUILT_STATIC_LIBRARY) + include $(CLEAR_VARS) LOCAL_MODULE := openal LOCAL_SRC_FILES := deps/openal-soft/libs/$(TARGET_LIBDIR)/libopenal.so @@ -97,6 +102,7 @@ LOCAL_C_INCLUDES := \ jni/src/json \ jni/src/cguittfont \ deps/irrlicht/include \ + deps/libiconv/include \ deps/freetype2-android/include \ deps/curl/include \ deps/openal-soft/jni/OpenAL/include \ @@ -153,6 +159,7 @@ LOCAL_SRC_FILES := \ jni/src/httpfetch.cpp \ jni/src/hud.cpp \ jni/src/imagefilters.cpp \ + jni/src/intlGUIEditBox.cpp \ jni/src/inventory.cpp \ jni/src/inventorymanager.cpp \ jni/src/itemdef.cpp \ @@ -346,7 +353,7 @@ LOCAL_SRC_FILES += \ LOCAL_SRC_FILES += jni/src/json/jsoncpp.cpp LOCAL_SHARED_LIBRARIES := openal ogg vorbis gmp -LOCAL_STATIC_LIBRARIES := Irrlicht freetype curl ssl crypto android_native_app_glue $(PROFILER_LIBS) +LOCAL_STATIC_LIBRARIES := Irrlicht iconv freetype curl ssl crypto android_native_app_glue $(PROFILER_LIBS) ifeq ($(HAVE_LEVELDB), 1) LOCAL_STATIC_LIBRARIES += LevelDB diff --git a/build/android/libiconv_android.patch b/build/android/libiconv_android.patch new file mode 100644 index 000000000..4eca0a4ef --- /dev/null +++ b/build/android/libiconv_android.patch @@ -0,0 +1,39 @@ +--- a/libcharset/lib/localcharset.c 2015-06-10 11:55:25.933870724 +0200 ++++ b/libcharset/lib/localcharset.c 2015-06-10 11:55:39.578063493 +0200 +@@ -47,7 +47,7 @@ + + #if !defined WIN32_NATIVE + # include +-# if HAVE_LANGINFO_CODESET ++# if HAVE_LANGINFO_CODESET && !(defined __ANDROID__) + # include + # else + # if 0 /* see comment below */ +@@ -124,7 +124,7 @@ get_charset_aliases (void) + cp = charset_aliases; + if (cp == NULL) + { +-#if !(defined DARWIN7 || defined VMS || defined WIN32_NATIVE || defined __CYGWIN__) ++#if !(defined DARWIN7 || defined VMS || defined WIN32_NATIVE || defined __CYGWIN__ || defined __ANDROID__) + const char *dir; + const char *base = "charset.alias"; + char *file_name; +@@ -338,6 +338,9 @@ get_charset_aliases (void) + "CP54936" "\0" "GB18030" "\0" + "CP65001" "\0" "UTF-8" "\0"; + # endif ++# if defined __ANDROID__ ++ cp = "*" "\0" "UTF-8" "\0"; ++# endif + #endif + + charset_aliases = cp; +@@ -361,7 +364,7 @@ locale_charset (void) + const char *codeset; + const char *aliases; + +-#if !(defined WIN32_NATIVE || defined OS2) ++#if !(defined WIN32_NATIVE || defined OS2 || defined __ANDROID__) + + # if HAVE_LANGINFO_CODESET + diff --git a/build/android/libiconv_android_mk.patch b/build/android/libiconv_android_mk.patch new file mode 100644 index 000000000..9eb7a1915 --- /dev/null +++ b/build/android/libiconv_android_mk.patch @@ -0,0 +1,51 @@ +From fe27aae178d65b06d5f4104158343b0d2d33e3f0 Mon Sep 17 00:00:00 2001 +From: Pierre Zurek +Date: Sat, 2 Apr 2011 23:11:57 +0200 +Subject: [PATCH] Added Android.mk. + +This makefile first executes the configure script, that will +generate the config.h files necessary to build iconv. +--- + Android.mk | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + create mode 100644 Android.mk + +diff --git a/jni/Android.mk b/jni/Android.mk +new file mode 100644 +index 0000000..799b22d +--- /dev/null ++++ b/jni/Android.mk +@@ -0,0 +1,32 @@ ++LOCAL_PATH := $(call my-dir) ++include $(CLEAR_VARS) ++ ++LOCAL_ARM_MODE := arm ++ ++LOCAL_SRC_FILES := src/lib/iconv.c \ ++ src/libcharset/lib/localcharset.c \ ++ src/lib/relocatable.c ++ ++LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/include \ ++ $(LOCAL_PATH)/src/libcharset \ ++ $(LOCAL_PATH)/src/libcharset/include ++ ++LOCAL_CFLAGS := \ ++ -DLIBDIR="\"c\"" \ ++ -D_ANDROID \ ++ -DBUILDING_LIBCHARSET \ ++ -DBUILDING_LIBICONV \ ++ -DBUILDING_LIBICONV \ ++ -DIN_LIBRARY ++ ++LOCAL_MODULE:= iconv ++ ++$(info Configuring iconv...) ++COMMAND := $(shell \ ++ export PATH=$(TOOLCHAIN_INSTALL_DIR)/bin:$$PATH; \ ++ cd $(LOCAL_PATH); \ ++ make distclean; \ ++ ./configure --host="arm-linux-androideabi") ++$(info iconv configured.) ++ ++include $(BUILD_STATIC_LIBRARY) ++ diff --git a/build/android/libiconv_stdio.patch b/build/android/libiconv_stdio.patch new file mode 100644 index 000000000..19cb6aa09 --- /dev/null +++ b/build/android/libiconv_stdio.patch @@ -0,0 +1,10 @@ +--- a/srclib/stdio.in.h 2011-08-07 15:42:06.000000000 +0200 ++++ b/srclib/stdio.in.h 2015-06-10 09:27:58.129056262 +0200 +@@ -695,7 +696,8 @@ _GL_CXXALIASWARN (gets); + /* It is very rare that the developer ever has full control of stdin, + so any use of gets warrants an unconditional warning. Assume it is + always declared, since it is required by C89. */ +-_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead"); ++/*_GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");*/ ++#define gets(a) fgets( a, sizeof(*(a)), stdin) + #endif \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 190f4e9bd..ef508d9b8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -417,6 +417,7 @@ set(client_SRCS guiVolumeChange.cpp hud.cpp imagefilters.cpp + intlGUIEditBox.cpp keycode.cpp localplayer.cpp main.cpp diff --git a/src/intlGUIEditBox.cpp b/src/intlGUIEditBox.cpp new file mode 100644 index 000000000..33bf8a13c --- /dev/null +++ b/src/intlGUIEditBox.cpp @@ -0,0 +1,1509 @@ +// 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 "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& rectangle) + : IGUIEditBox(environment, parent, id, rectangle), MouseMarking(false), + Border(border), OverrideColorEnabled(false), MarkBegin(0), MarkEnd(0), + OverrideColor(video::SColor(101,255,255,255)), OverrideFont(0), LastBreakFont(0), + Operator(0), BlinkStartTime(0), CursorPos(0), HScrollPos(0), VScrollPos(0), Max(0), + WordWrap(false), MultiLine(false), AutoScroll(true), PasswordBox(false), + PasswordChar(L'*'), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER), + CurrentTextRect(0,0,1,1), FrameRect(rectangle) +{ + #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; + } + + breakText(); + + calculateScrollPos(); +} + + +//! 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 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(__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.size()) + { + 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.size() != 0) + { + 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) + { + 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; + } + core::rect 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.size()) + { + 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 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.size()) + 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 (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 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(event.MouseInput.X, event.MouseInput.Y))) + { + return false; + } + else + { + // 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; + } + } + 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=0; + s32 startPos=0; + x+=3; + + for (u32 i=0; i < lineCount; ++i) + { + setTextRect(i); + if (i == 0 && y < CurrentTextRect.UpperLeftCorner.Y) + y = CurrentTextRect.UpperLeftCorner.Y; + if (i == 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[i] : &Text; + startPos = (WordWrap || MultiLine) ? BrokenTextPositions[i] : 0; + break; + } + } + + if (x < CurrentTextRect.UpperLeftCorner.X) + x = CurrentTextRect.UpperLeftCorner.X; + + s32 idx = font->getCharacterFromPos(Text.c_str(), x - CurrentTextRect.UpperLeftCorner.X); + + // click was on or left of the line + if (idx != -1) + return idx + startPos; + + // click was off the right edge of the line, go to end. + return txtLine->size() + startPos; +} + + +//! 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; igetDimension(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 +} + +//! 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); + } +} + +//! 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); + + 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.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")); +} + + +} // end namespace gui +} // end namespace irr + +#endif // _IRR_COMPILE_WITH_GUI_ diff --git a/src/intlGUIEditBox.h b/src/intlGUIEditBox.h new file mode 100644 index 000000000..e3ee15a30 --- /dev/null +++ b/src/intlGUIEditBox.h @@ -0,0 +1,178 @@ +// 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 + +#ifndef __C_INTL_GUI_EDIT_BOX_H_INCLUDED__ +#define __C_INTL_GUI_EDIT_BOX_H_INCLUDED__ + +#include "IrrCompileConfig.h" +//#ifdef _IRR_COMPILE_WITH_GUI_ + +#include "IGUIEditBox.h" +#include "irrArray.h" +#include "IOSOperator.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& rectangle); + + //! 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(); + + //! 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); + + bool MouseMarking; + bool Border; + bool OverrideColorEnabled; + s32 MarkBegin; + s32 MarkEnd; + + video::SColor OverrideColor; + gui::IGUIFont *OverrideFont, *LastBreakFont; + IOSOperator* Operator; + + u32 BlinkStartTime; + s32 CursorPos; + s32 HScrollPos, VScrollPos; // scroll position in characters + u32 Max; + + bool WordWrap, MultiLine, AutoScroll, PasswordBox; + wchar_t PasswordChar; + EGUI_ALIGNMENT HAlign, VAlign; + + core::array< core::stringw > BrokenText; + core::array< s32 > BrokenTextPositions; + + core::rect CurrentTextRect, FrameRect; // temporary values + }; + + +} // end namespace gui +} // end namespace irr + +//#endif // _IRR_COMPILE_WITH_GUI_ +#endif // __C_GUI_EDIT_BOX_H_INCLUDED__ diff --git a/src/util/string.cpp b/src/util/string.cpp index 231eaf6be..49aff4a1f 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -29,9 +29,103 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include +#ifndef _WIN32 +#include +#else +#define _WIN32_WINNT 0x0501 +#include +#endif + static bool parseHexColorString(const std::string &value, video::SColor &color); static bool parseNamedColorString(const std::string &value, video::SColor &color); +#ifndef _WIN32 +size_t convert(const char *to, const char *from, char *outbuf, + size_t outbuf_size, char *inbuf, size_t inbuf_size) +{ + iconv_t cd = iconv_open(to, from); + +#if defined(__FreeBSD__) || defined(__FreeBSD) + const char *inbuf_ptr = inbuf; +#else + char *inbuf_ptr = inbuf; +#endif + + char *outbuf_ptr = outbuf; + + size_t *inbuf_left_ptr = &inbuf_size; + size_t *outbuf_left_ptr = &outbuf_size; + + while (inbuf_size > 0) + iconv(cd, &inbuf_ptr, inbuf_left_ptr, &outbuf_ptr, outbuf_left_ptr); + + iconv_close(cd); + return 0; +} + +std::wstring utf8_to_wide(const std::string &input) +{ + size_t inbuf_size = input.length() + 1; + // maximum possible size, every character is sizeof(wchar_t) bytes + size_t outbuf_size = (input.length() + 1) * sizeof(wchar_t); + + char *inbuf = new char[inbuf_size]; + memcpy(inbuf, input.c_str(), inbuf_size); + char *outbuf = new char[outbuf_size]; + memset(outbuf, 0, outbuf_size); + + convert("WCHAR_T", "UTF-8", outbuf, outbuf_size, inbuf, inbuf_size); + std::wstring out((wchar_t*)outbuf); + + delete[] inbuf; + delete[] outbuf; + + return out; +} + +std::string wide_to_utf8(const std::wstring &input) +{ + size_t inbuf_size = (input.length() + 1) * sizeof(wchar_t); + // maximum possible size: utf-8 encodes codepoints using 1 up to 6 bytes + size_t outbuf_size = (input.length() + 1) * 6; + + char *inbuf = new char[inbuf_size]; + memcpy(inbuf, input.c_str(), inbuf_size); + char *outbuf = new char[outbuf_size]; + memset(outbuf, 0, outbuf_size); + + convert("UTF-8", "WCHAR_T", outbuf, outbuf_size, inbuf, inbuf_size); + std::string out(outbuf); + + delete[] inbuf; + delete[] outbuf; + + return out; +} +#else +std::wstring utf8_to_wide(const std::string &input) +{ + size_t outbuf_size = input.size() + 1; + wchar_t *outbuf = new wchar_t[outbuf_size]; + memset(outbuf, 0, outbuf_size * sizeof(wchar_t)); + MultiByteToWideChar(CP_UTF8, 0, input.c_str(), input.size(), outbuf, outbuf_size); + std::wstring out(outbuf); + delete[] outbuf; + return out; +} + +std::string wide_to_utf8(const std::wstring &input) +{ + size_t outbuf_size = (input.size() + 1) * 6; + char *outbuf = new char[outbuf_size]; + memset(outbuf, 0, outbuf_size); + WideCharToMultiByte(CP_UTF8, 0, input.c_str(), input.size(), outbuf, outbuf_size, NULL, NULL); + std::string out(outbuf); + delete[] outbuf; + return out; +} +#endif + // You must free the returned string! // The returned string is allocated using new diff --git a/src/util/string.h b/src/util/string.h index b80e3c9a8..5bf2b5b7c 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -39,10 +39,18 @@ struct FlagDesc { u32 flag; }; +// try not to convert between wide/utf8 encodings; this can result in data loss +// try to only convert between them when you need to input/output stuff via Irrlicht +std::wstring utf8_to_wide(const std::string &input); +std::string wide_to_utf8(const std::wstring &input); + +// NEVER use those two functions unless you have a VERY GOOD reason to +// they just convert between wide and multibyte encoding +// multibyte encoding depends on current locale, this is no good, especially on Windows + // You must free the returned string! // The returned string is allocated using new wchar_t *narrow_to_wide_c(const char *str); - std::wstring narrow_to_wide(const std::string &mbs); std::string wide_to_narrow(const std::wstring &wcs);