// 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 #include "intlGUIEditBox.h" #if defined(_IRR_COMPILE_WITH_GUI_) && IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9 || defined(__ANDROID__) #include "IGUISkin.h" #include "IGUIEnvironment.h" #include "IGUIFont.h" #include "IVideoDriver.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, 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 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 && m_writable) { // 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 || !m_writable) break; // paste from the clipboard if (Operato/* Minetest Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr> 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 "test.h" #include <algorithm> #include "server/mods.h" #include "test_config.h" class TestServerModManager : public TestBase { public: TestServerModManager() { TestManager::registerTestModule(this); } const char *getName() { return "TestServerModManager"; } void runTests(IGameDef *gamedef); void testCreation(); void testIsConsistent(); void testUnsatisfiedMods(); void testGetMods(); void testGetModsWrongDir(); void testGetModspec(); void testGetModNamesWrongDir(); void testGetModNames(); void testGetModMediaPathsWrongDir(); void testGetModMediaPaths(); }; static TestServerModManager g_test_instance; void TestServerModManager::runTests(IGameDef *gamedef) { const char *saved_env_mt_subgame_path = getenv("MINETEST_SUBGAME_PATH"); #ifdef WIN32 { std::string subgame_path("MINETEST_SUBGAME_PATH="); subgame_path.append(TEST_SUBGAME_PATH); _putenv(subgame_path.c_str()); } #else setenv("MINETEST_SUBGAME_PATH", TEST_SUBGAME_PATH, 1); #endif TEST(testCreation); TEST(testIsConsistent); TEST(testGetModsWrongDir); TEST(testUnsatisfiedMods); TEST(testGetMods); TEST(testGetModspec); TEST(testGetModNamesWrongDir); TEST(testGetModNames); TEST(testGetModMediaPathsWrongDir); TEST(testGetModMediaPaths); #ifdef WIN32 { std::string subgame_path("MINETEST_SUBGAME_PATH="); if (saved_env_mt_subgame_path) subgame_path.append(saved_env_mt_subgame_path); _putenv(subgame_path.c_str()); } #else if (saved_env_mt_subgame_path) setenv("MINETEST_SUBGAME_PATH", saved_env_mt_subgame_path, 1); else unsetenv("MINETEST_SUBGAME_PATH"); #endif } void TestServerModManager::testCreation() { ServerModManager sm(TEST_WORLDDIR); } void TestServerModManager::testGetModsWrongDir() { // Test in non worlddir to ensure no mods are found ServerModManager sm(std::string(TEST_WORLDDIR) + DIR_DELIM + ".."); UASSERTEQ(bool, sm.getMods().empty(), true); } void TestServerModManager::testUnsatisfiedMods() { ServerModManager sm(std::string(TEST_WORLDDIR)); UASSERTEQ(bool, sm.getUnsatisfiedMods().empty(), true); } void TestServerModManager::testIsConsistent() { ServerModManager sm(std::string(TEST_WORLDDIR)); UASSERTEQ(bool, sm.isConsistent(), true); } void TestServerModManager::testGetMods() { ServerModManager sm(std::string(TEST_WORLDDIR)); const auto &mods = sm.getMods(); UASSERTEQ(bool, mods.empty(), false); // Ensure we found default mod inside the test folder bool default_found = false; for (const auto &m : mods) { if (m.name == "default") default_found = true; // Verify if paths are not empty UASSERTEQ(bool, m.path.empty(), false); } UASSERTEQ(bool, default_found, true); } void TestServerModManager::testGetModspec() { ServerModManager sm(std::string(TEST_WORLDDIR)); UASSERTEQ(const ModSpec *, sm.getModSpec("wrongmod"), NULL); UASSERT(sm.getModSpec("default") != NULL); } void TestServerModManager::testGetModNamesWrongDir() { ServerModManager sm(std::string(TEST_WORLDDIR) + DIR_DELIM + ".."); std::vector<std::string> result; sm.getModNames(result); UASSERTEQ(bool, result.empty(), true); } void TestServerModManager::testGetModNames() { ServerModManager sm(std::string(TEST_WORLDDIR)); std::vector<std::string> result; sm.getModNames(result); UASSERTEQ(bool, result.empty(), false); UASSERT(std::find(result.begin(), result.end(), "default") != result.end()); } void TestServerModManager::testGetModMediaPathsWrongDir() { ServerModManager sm(std::string(TEST_WORLDDIR) + DIR_DELIM + ".."); std::vector<std::string> result; sm.getModsMediaPaths(result); UASSERTEQ(bool, result.empty(), true); } void TestServerModManager::testGetModMediaPaths() { ServerModManager sm(std::string(TEST_WORLDDIR)); std::vector<std::string> result; sm.getModsMediaPaths(result); UASSERTEQ(bool, result.empty(), false); // We should have 5 folders for each mod (textures, media, locale, model, sounds) UASSERTEQ(unsigned long, result.size() % 5, 0); } t.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 || !m_writable) 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 } if (!WordWrap && !MultiLine) return; // vertical scroll position if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y) VScrollPos += CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y; // scrolling downwards else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y) VScrollPos += CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y; // scrolling upwards // 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 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_