From 9b8fa99fe30728c1fcfa73cdf74211841bdae9fb Mon Sep 17 00:00:00 2001 From: adelcoding1 Date: Sat, 18 Feb 2017 11:40:37 -0800 Subject: FormSpec : Add an auto vertical scrollbar to the textarea --- src/guiEditBoxWithScrollbar.cpp | 1524 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1524 insertions(+) create mode 100644 src/guiEditBoxWithScrollbar.cpp (limited to 'src/guiEditBoxWithScrollbar.cpp') diff --git a/src/guiEditBoxWithScrollbar.cpp b/src/guiEditBoxWithScrollbar.cpp new file mode 100644 index 000000000..a27a772b9 --- /dev/null +++ b/src/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& 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) +{ + 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 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 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 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 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(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 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 > 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")); +} -- cgit v1.2.3