forked from 0ad/0ad
janwas
32cfc6d807
textureentry: add performance note list, text: fix mousewheel direction input: was going to add a deque to allow registering handlers in back and front. obviated by minimap fix, though. cleaned up a bit and renamed stuff (again, sigh). loaderthunks: fixed weird alignment/padding warning This was SVN commit r2979.
1655 lines
44 KiB
C++
Executable File
1655 lines
44 KiB
C++
Executable File
/*
|
|
CInput
|
|
by Gustav Larsson
|
|
gee@pyro.nu
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
#include "GUI.h"
|
|
#include "CInput.h"
|
|
|
|
#include "ps/Font.h"
|
|
#include "ogl.h"
|
|
|
|
// TODO Gee: new
|
|
#include "OverlayText.h"
|
|
#include "lib/res/graphics/unifont.h"
|
|
|
|
#include "ps/Hotkey.h"
|
|
#include "ps/CLogger.h"
|
|
#include "ps/Globals.h"
|
|
|
|
#define LOG_CATEGORY "gui"
|
|
|
|
using namespace std;
|
|
|
|
//-------------------------------------------------------------------
|
|
// Constructor / Destructor
|
|
//-------------------------------------------------------------------
|
|
CInput::CInput() : m_iBufferPos(-1), m_iBufferPos_Tail(-1), m_SelectingText(false), m_HorizontalScroll(0.f)
|
|
{
|
|
AddSetting(GUIST_float, "buffer_zone");
|
|
AddSetting(GUIST_CStrW, "caption");
|
|
AddSetting(GUIST_int, "cell_id");
|
|
AddSetting(GUIST_CStr, "font");
|
|
AddSetting(GUIST_int, "max_length");
|
|
AddSetting(GUIST_bool, "multiline");
|
|
AddSetting(GUIST_bool, "scrollbar");
|
|
AddSetting(GUIST_CStr, "scrollbar_style");
|
|
AddSetting(GUIST_CGUISpriteInstance, "sprite");
|
|
AddSetting(GUIST_CGUISpriteInstance, "sprite_selectarea");
|
|
AddSetting(GUIST_CColor, "textcolor");
|
|
AddSetting(GUIST_CColor, "textcolor_selected");
|
|
AddSetting(GUIST_CStr, "tooltip");
|
|
AddSetting(GUIST_CStr, "tooltip_style");
|
|
|
|
// Add scroll-bar
|
|
CGUIScrollBarVertical * bar = new CGUIScrollBarVertical();
|
|
bar->SetRightAligned(true);
|
|
bar->SetUseEdgeButtons(true);
|
|
AddScrollBar(bar);
|
|
}
|
|
|
|
CInput::~CInput()
|
|
{
|
|
}
|
|
|
|
InReaction CInput::ManuallyHandleEvent(const SDL_Event* ev)
|
|
{
|
|
debug_assert(m_iBufferPos != -1);
|
|
|
|
// Since the GUI framework doesn't handle to set settings
|
|
// in Unicode (CStrW), we'll simply retrieve the actual
|
|
// pointer and edit that.
|
|
CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting;
|
|
|
|
if (ev->type == SDL_HOTKEYDOWN)
|
|
{
|
|
if (ev->user.code == HOTKEY_CONSOLE_PASTE)
|
|
{
|
|
wchar_t* text = clipboard_get();
|
|
if (text)
|
|
{
|
|
if (m_iBufferPos == (int)pCaption->Length())
|
|
*pCaption += text;
|
|
else
|
|
*pCaption = pCaption->Left(m_iBufferPos) + text +
|
|
pCaption->Right((long) pCaption->Length()-m_iBufferPos);
|
|
|
|
UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
|
|
|
|
m_iBufferPos += (int)wcslen(text);
|
|
|
|
clipboard_free(text);
|
|
}
|
|
|
|
return IN_HANDLED;
|
|
}
|
|
}
|
|
else if (ev->type == SDL_KEYDOWN)
|
|
{
|
|
int szChar = ev->key.keysym.sym;
|
|
wchar_t cooked = (wchar_t)ev->key.keysym.unicode;
|
|
|
|
switch (szChar)
|
|
{
|
|
case '\t':
|
|
/* Auto Complete */
|
|
// TODO Gee: (2004-09-07) What to do with tab?
|
|
break;
|
|
|
|
case '\b':
|
|
m_WantedX=0.f;
|
|
|
|
if (SelectingText())
|
|
DeleteCurSelection();
|
|
else
|
|
{
|
|
m_iBufferPos_Tail = -1;
|
|
|
|
if (pCaption->Length() == 0 ||
|
|
m_iBufferPos == 0)
|
|
break;
|
|
|
|
if (m_iBufferPos == (int)pCaption->Length())
|
|
*pCaption = pCaption->Left( (long) pCaption->Length()-1);
|
|
else
|
|
*pCaption = pCaption->Left( m_iBufferPos-1 ) +
|
|
pCaption->Right( (long) pCaption->Length()-m_iBufferPos );
|
|
|
|
UpdateText(m_iBufferPos-1, m_iBufferPos, m_iBufferPos-1);
|
|
|
|
--m_iBufferPos;
|
|
}
|
|
|
|
UpdateAutoScroll();
|
|
break;
|
|
|
|
case SDLK_DELETE:
|
|
m_WantedX=0.f;
|
|
// If selection:
|
|
if (SelectingText())
|
|
{
|
|
DeleteCurSelection();
|
|
}
|
|
else
|
|
{
|
|
if (pCaption->Length() == 0 ||
|
|
m_iBufferPos == (int)pCaption->Length())
|
|
break;
|
|
|
|
*pCaption = pCaption->Left( m_iBufferPos ) +
|
|
pCaption->Right( (long) pCaption->Length()-(m_iBufferPos+1) );
|
|
|
|
UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos);
|
|
}
|
|
|
|
UpdateAutoScroll();
|
|
break;
|
|
|
|
case SDLK_HOME:
|
|
// If there's not a selection, we should create one now
|
|
if (!g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT])
|
|
{
|
|
// Make sure a selection isn't created.
|
|
m_iBufferPos_Tail = -1;
|
|
}
|
|
else if (!SelectingText())
|
|
{
|
|
// Place tail at the current point:
|
|
m_iBufferPos_Tail = m_iBufferPos;
|
|
}
|
|
|
|
m_iBufferPos = 0;
|
|
m_WantedX=0.f;
|
|
|
|
UpdateAutoScroll();
|
|
break;
|
|
|
|
case SDLK_END:
|
|
// If there's not a selection, we should create one now
|
|
if (!g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT])
|
|
{
|
|
// Make sure a selection isn't created.
|
|
m_iBufferPos_Tail = -1;
|
|
}
|
|
else if (!SelectingText())
|
|
{
|
|
// Place tail at the current point:
|
|
m_iBufferPos_Tail = m_iBufferPos;
|
|
}
|
|
|
|
m_iBufferPos = (long) pCaption->Length();
|
|
m_WantedX=0.f;
|
|
|
|
UpdateAutoScroll();
|
|
break;
|
|
|
|
/**
|
|
Conventions for Left/Right when text is selected:
|
|
|
|
References:
|
|
|
|
Visual Studio
|
|
Visual Studio has the 'newer' approach, used by newer versions of
|
|
things, and in newer applications. A left press will always place
|
|
the pointer on the left edge of the selection, and then of course
|
|
remove the selection. Right will do the exakt same thing.
|
|
If you have the pointer on the right edge and press right, it will
|
|
in other words just remove the selection.
|
|
|
|
Windows (eg. Notepad)
|
|
A left press always takes the pointer a step to the left and
|
|
removes the selection as if it were never there in the first place.
|
|
Right of course does the same thing but to the right.
|
|
|
|
I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN
|
|
Messenger.
|
|
|
|
**/
|
|
case SDLK_LEFT:
|
|
// reset m_WantedX, very important
|
|
m_WantedX=0.f;
|
|
|
|
if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT] ||
|
|
!SelectingText())
|
|
{
|
|
// If there's not a selection, we should create one now
|
|
if (!SelectingText() && !g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT])
|
|
{
|
|
// Make sure a selection isn't created.
|
|
m_iBufferPos_Tail = -1;
|
|
}
|
|
else if (!SelectingText())
|
|
{
|
|
// Place tail at the current point:
|
|
m_iBufferPos_Tail = m_iBufferPos;
|
|
}
|
|
|
|
if (m_iBufferPos)
|
|
--m_iBufferPos;
|
|
}
|
|
else
|
|
{
|
|
if (m_iBufferPos_Tail < m_iBufferPos)
|
|
m_iBufferPos = m_iBufferPos_Tail;
|
|
|
|
m_iBufferPos_Tail = -1;
|
|
}
|
|
|
|
UpdateAutoScroll();
|
|
break;
|
|
|
|
case SDLK_RIGHT:
|
|
m_WantedX=0.f;
|
|
|
|
if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT] ||
|
|
!SelectingText())
|
|
{
|
|
// If there's not a selection, we should create one now
|
|
if (!SelectingText() && !g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT])
|
|
{
|
|
// Make sure a selection isn't created.
|
|
m_iBufferPos_Tail = -1;
|
|
}
|
|
else if (!SelectingText())
|
|
{
|
|
// Place tail at the current point:
|
|
m_iBufferPos_Tail = m_iBufferPos;
|
|
}
|
|
|
|
|
|
if (m_iBufferPos != (int)pCaption->Length())
|
|
++m_iBufferPos;
|
|
}
|
|
else
|
|
{
|
|
if (m_iBufferPos_Tail > m_iBufferPos)
|
|
m_iBufferPos = m_iBufferPos_Tail;
|
|
|
|
m_iBufferPos_Tail = -1;
|
|
}
|
|
|
|
UpdateAutoScroll();
|
|
break;
|
|
|
|
/**
|
|
Conventions for Up/Down when text is selected:
|
|
|
|
References:
|
|
|
|
Visual Studio
|
|
Visual Studio has a very strange approach, down takes you below the
|
|
selection to the next row, and up to the one prior to the whole
|
|
selection. The weird part is that it is always aligned as the
|
|
'pointer'. I decided this is to much work for something that is
|
|
a bit arbitrary
|
|
|
|
Windows (eg. Notepad)
|
|
Just like with left/right, the selection is destroyed and it moves
|
|
just as if there never were a selection.
|
|
|
|
I chose the Notepad convention even though I use the VS convention with
|
|
left/right.
|
|
|
|
**/
|
|
case SDLK_UP:
|
|
{
|
|
// If there's not a selection, we should create one now
|
|
if (!g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT])
|
|
{
|
|
// Make sure a selection isn't created.
|
|
m_iBufferPos_Tail = -1;
|
|
}
|
|
else if (!SelectingText())
|
|
{
|
|
// Place tail at the current point:
|
|
m_iBufferPos_Tail = m_iBufferPos;
|
|
}
|
|
|
|
list<SRow>::iterator current = m_CharacterPositions.begin();
|
|
while (current != m_CharacterPositions.end())
|
|
{
|
|
if (m_iBufferPos >= current->m_ListStart &&
|
|
m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size())
|
|
break;
|
|
|
|
++current;
|
|
}
|
|
|
|
float pos_x;
|
|
|
|
if (m_iBufferPos-current->m_ListStart == 0)
|
|
pos_x = 0.f;
|
|
else
|
|
pos_x = current->m_ListOfX[m_iBufferPos-current->m_ListStart-1];
|
|
|
|
if (m_WantedX > pos_x)
|
|
pos_x = m_WantedX;
|
|
|
|
// Now change row:
|
|
if (current != m_CharacterPositions.begin())
|
|
{
|
|
--current;
|
|
|
|
// Find X-position:
|
|
m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX);
|
|
}
|
|
// else we can't move up
|
|
|
|
UpdateAutoScroll();
|
|
}
|
|
break;
|
|
|
|
case SDLK_DOWN:
|
|
{
|
|
// If there's not a selection, we should create one now
|
|
if (!g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT])
|
|
{
|
|
// Make sure a selection isn't created.
|
|
m_iBufferPos_Tail = -1;
|
|
}
|
|
else if (!SelectingText())
|
|
{
|
|
// Place tail at the current point:
|
|
m_iBufferPos_Tail = m_iBufferPos;
|
|
}
|
|
|
|
list<SRow>::iterator current = m_CharacterPositions.begin();
|
|
while (current != m_CharacterPositions.end())
|
|
{
|
|
if (m_iBufferPos >= current->m_ListStart &&
|
|
m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size())
|
|
break;
|
|
|
|
++current;
|
|
}
|
|
|
|
float pos_x;
|
|
|
|
if (m_iBufferPos-current->m_ListStart == 0)
|
|
pos_x = 0.f;
|
|
else
|
|
pos_x = current->m_ListOfX[m_iBufferPos-current->m_ListStart-1];
|
|
|
|
if (m_WantedX > pos_x)
|
|
pos_x = m_WantedX;
|
|
|
|
// Now change row:
|
|
// Add first, so we can check if it's .end()
|
|
++current;
|
|
if (current != m_CharacterPositions.end())
|
|
{
|
|
// Find X-position:
|
|
m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX);
|
|
}
|
|
// else we can't move up
|
|
|
|
UpdateAutoScroll();
|
|
}
|
|
break;
|
|
|
|
case SDLK_PAGEUP:
|
|
GetScrollBar(0).ScrollMinusPlenty();
|
|
break;
|
|
|
|
case SDLK_PAGEDOWN:
|
|
GetScrollBar(0).ScrollPlusPlenty();
|
|
break;
|
|
/* END: Message History Lookup */
|
|
|
|
case '\r':
|
|
// 'Return' should do nothing for singe liners
|
|
// otherwise a '\n' character will be added.
|
|
{
|
|
bool multiline;
|
|
GUI<bool>::GetSetting(this, "multiline", multiline);
|
|
if (!multiline)
|
|
break;
|
|
|
|
cooked = '\n'; // Change to '\n' and do default:
|
|
// NOTE: Fall-through
|
|
}
|
|
default: //Insert a character
|
|
{
|
|
// If there's a selection, delete if first.
|
|
if (cooked == 0)
|
|
return IN_PASS; // Important, because we didn't use any key
|
|
|
|
// check max length
|
|
int max_length;
|
|
GUI<int>::GetSetting(this, "max_length", max_length);
|
|
if (max_length != 0 && (int)pCaption->Length() >= max_length)
|
|
break;
|
|
|
|
m_WantedX=0.f;
|
|
|
|
if (SelectingText())
|
|
DeleteCurSelection();
|
|
m_iBufferPos_Tail = -1;
|
|
|
|
if (m_iBufferPos == (int)pCaption->Length())
|
|
*pCaption += cooked;
|
|
else
|
|
*pCaption = pCaption->Left(m_iBufferPos) + CStrW(cooked) +
|
|
pCaption->Right((long) pCaption->Length()-m_iBufferPos);
|
|
|
|
UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
|
|
|
|
++m_iBufferPos;
|
|
|
|
UpdateAutoScroll();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return IN_HANDLED;
|
|
}
|
|
|
|
void CInput::HandleMessage(const SGUIMessage &Message)
|
|
{
|
|
// TODO Gee:
|
|
IGUIScrollBarOwner::HandleMessage(Message);
|
|
|
|
switch (Message.type)
|
|
{
|
|
case GUIM_SETTINGS_UPDATED:
|
|
{
|
|
bool scrollbar;
|
|
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
|
|
|
|
// Update scroll-bar
|
|
// TODO Gee: (2004-09-01) Is this really updated each time it should?
|
|
if (scrollbar &&
|
|
(Message.value == CStr("size") ||
|
|
Message.value == CStr("z") ||
|
|
Message.value == CStr("absolute")))
|
|
{
|
|
GetScrollBar(0).SetX( m_CachedActualSize.right );
|
|
GetScrollBar(0).SetY( m_CachedActualSize.top );
|
|
GetScrollBar(0).SetZ( GetBufferedZ() );
|
|
GetScrollBar(0).SetLength( m_CachedActualSize.bottom - m_CachedActualSize.top );
|
|
}
|
|
|
|
// Update scrollbar
|
|
if (Message.value == CStr("scrollbar_style"))
|
|
{
|
|
CStr scrollbar_style;
|
|
GUI<CStr>::GetSetting(this, Message.value, scrollbar_style);
|
|
|
|
GetScrollBar(0).SetScrollBarStyle( scrollbar_style );
|
|
}
|
|
|
|
if (Message.value == CStr("size") ||
|
|
Message.value == CStr("z") ||
|
|
Message.value == CStr("font") ||
|
|
Message.value == CStr("absolute") ||
|
|
Message.value == CStr("caption") ||
|
|
Message.value == CStr("scrollbar") ||
|
|
Message.value == CStr("scrollbar_style"))
|
|
{
|
|
UpdateText();
|
|
}
|
|
|
|
if (Message.value == CStr("multiline"))
|
|
{
|
|
bool multiline;
|
|
GUI<bool>::GetSetting(this, "multiline", multiline);
|
|
|
|
if (multiline == false)
|
|
{
|
|
GetScrollBar(0).SetLength(0.f);
|
|
}
|
|
else
|
|
{
|
|
GetScrollBar(0).SetLength( m_CachedActualSize.bottom - m_CachedActualSize.top );
|
|
}
|
|
|
|
UpdateText();
|
|
}
|
|
|
|
}break;
|
|
|
|
case GUIM_MOUSE_PRESS_LEFT:
|
|
// Check if we're selecting the scrollbar:
|
|
{
|
|
bool scrollbar, multiline;
|
|
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
|
|
GUI<bool>::GetSetting(this, "multiline", multiline);
|
|
|
|
if (GetScrollBar(0).GetStyle() && multiline)
|
|
{
|
|
if (GetMousePos().x > m_CachedActualSize.right - GetScrollBar(0).GetStyle()->m_Width)
|
|
break;
|
|
}
|
|
|
|
// Okay, this section is about pressing the mouse and
|
|
// choosing where the point should be placed. For
|
|
// instance, if we press between a and b, the point
|
|
// should of course be placed accordingly. Other
|
|
// special cases are handled like the input box norms.
|
|
if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT])
|
|
{
|
|
m_iBufferPos = GetMouseHoveringTextPosition();
|
|
}
|
|
else
|
|
{
|
|
m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition();
|
|
}
|
|
|
|
m_SelectingText = true;
|
|
|
|
UpdateAutoScroll();
|
|
|
|
// If we immediately release the button it will just be seen as a click
|
|
// for the user though.
|
|
|
|
}break;
|
|
|
|
case GUIM_MOUSE_RELEASE_LEFT:
|
|
if (m_SelectingText)
|
|
{
|
|
m_SelectingText = false;
|
|
}
|
|
break;
|
|
case GUIM_MOUSE_MOTION:
|
|
// If we just pressed down and started to move before releasing
|
|
// this is one way of selecting larger portions of text.
|
|
if (m_SelectingText)
|
|
{
|
|
// Actually, first we need to re-check that the mouse button is
|
|
// really pressed (it can be released while outside the control.
|
|
if (!g_mouse_buttons[SDL_BUTTON_LEFT])
|
|
m_SelectingText = false;
|
|
else
|
|
m_iBufferPos = GetMouseHoveringTextPosition();
|
|
|
|
UpdateAutoScroll();
|
|
}
|
|
|
|
break;
|
|
|
|
case GUIM_MOUSE_WHEEL_DOWN:
|
|
GetScrollBar(0).ScrollPlus();
|
|
// Since the scroll was changed, let's simulate a mouse movement
|
|
// to check if scrollbar now is hovered
|
|
HandleMessage(SGUIMessage(GUIM_MOUSE_MOTION));
|
|
break;
|
|
|
|
case GUIM_MOUSE_WHEEL_UP:
|
|
GetScrollBar(0).ScrollMinus();
|
|
// Since the scroll was changed, let's simulate a mouse movement
|
|
// to check if scrollbar now is hovered
|
|
HandleMessage(SGUIMessage(GUIM_MOUSE_MOTION));
|
|
break;
|
|
|
|
case GUIM_LOAD:
|
|
{
|
|
GetScrollBar(0).SetX( m_CachedActualSize.right );
|
|
GetScrollBar(0).SetY( m_CachedActualSize.top );
|
|
GetScrollBar(0).SetZ( GetBufferedZ() );
|
|
GetScrollBar(0).SetLength( m_CachedActualSize.bottom - m_CachedActualSize.top );
|
|
|
|
CStr scrollbar_style;
|
|
GUI<CStr>::GetSetting(this, "scrollbar_style", scrollbar_style);
|
|
GetScrollBar(0).SetScrollBarStyle( scrollbar_style );
|
|
|
|
UpdateText();
|
|
}
|
|
break;
|
|
|
|
case GUIM_GOT_FOCUS:
|
|
//m_iBufferPos = 0; // TODO, a keeper?
|
|
|
|
break;
|
|
|
|
case GUIM_LOST_FOCUS:
|
|
m_iBufferPos = -1;
|
|
m_iBufferPos_Tail = -1;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CInput::Draw()
|
|
{
|
|
float bz = GetBufferedZ();
|
|
|
|
// First call draw on ScrollBarOwner
|
|
bool scrollbar;
|
|
float buffer_zone;
|
|
bool multiline;
|
|
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
|
|
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
|
|
GUI<bool>::GetSetting(this, "multiline", multiline);
|
|
|
|
if (scrollbar && multiline)
|
|
{
|
|
// Draw scrollbar
|
|
IGUIScrollBarOwner::Draw();
|
|
}
|
|
|
|
if (GetGUI())
|
|
{
|
|
CStr font_name;
|
|
CColor color, color_selected;
|
|
//CStrW caption;
|
|
GUI<CStr>::GetSetting(this, "font", font_name);
|
|
GUI<CColor>::GetSetting(this, "textcolor", color);
|
|
GUI<CColor>::GetSetting(this, "textcolor_selected", color_selected);
|
|
|
|
// Get pointer of caption, it might be very large, and we don't
|
|
// want to copy it continuously.
|
|
CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting;
|
|
|
|
CGUISpriteInstance *sprite=NULL, *sprite_selectarea=NULL;
|
|
int cell_id;
|
|
|
|
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite", sprite);
|
|
GUI<CGUISpriteInstance>::GetSettingPointer(this, "sprite_selectarea", sprite_selectarea);
|
|
|
|
GUI<int>::GetSetting(this, "cell_id", cell_id);
|
|
|
|
GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize);
|
|
|
|
float scroll=0.f;
|
|
if (scrollbar && multiline)
|
|
{
|
|
scroll = GetScrollBar(0).GetPos();
|
|
}
|
|
|
|
glEnable(GL_TEXTURE_2D);
|
|
glDisable(GL_CULL_FACE);
|
|
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glEnable(GL_BLEND);
|
|
glDisable(GL_ALPHA_TEST);
|
|
|
|
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
|
|
CFont font(font_name);
|
|
font.Bind();
|
|
|
|
glPushMatrix();
|
|
|
|
// We'll have to setup clipping manually, since we're doing the rendering manually.
|
|
CRect cliparea(m_CachedActualSize);
|
|
|
|
// First we'll figure out the clipping area, which is the cached actual size
|
|
// substracted by an optional scrollbar
|
|
if (scrollbar)
|
|
{
|
|
scroll = GetScrollBar(0).GetPos();
|
|
|
|
// substract scrollbar from cliparea
|
|
if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
|
|
cliparea.right <= GetScrollBar(0).GetOuterRect().right)
|
|
cliparea.right = GetScrollBar(0).GetOuterRect().left;
|
|
|
|
if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
|
|
cliparea.left < GetScrollBar(0).GetOuterRect().right)
|
|
cliparea.left = GetScrollBar(0).GetOuterRect().right;
|
|
}
|
|
|
|
if (cliparea != CRect())
|
|
{
|
|
double eq[4][4] =
|
|
{
|
|
{ 0.0, 1.0, 0.0, -cliparea.top },
|
|
{ 1.0, 0.0, 0.0, -cliparea.left },
|
|
{ 0.0, -1.0, 0.0, cliparea.bottom },
|
|
{ -1.0, 0.0, 0.0, cliparea.right }
|
|
};
|
|
|
|
for (int i=0; i<4; ++i)
|
|
{
|
|
glClipPlane(GL_CLIP_PLANE0+i, eq[i]);
|
|
glEnable(GL_CLIP_PLANE0+i);
|
|
}
|
|
}
|
|
|
|
// These are useful later.
|
|
int VirtualFrom, VirtualTo;
|
|
|
|
if (m_iBufferPos_Tail >= m_iBufferPos)
|
|
{
|
|
VirtualFrom = m_iBufferPos;
|
|
VirtualTo = m_iBufferPos_Tail;
|
|
}
|
|
else
|
|
{
|
|
VirtualFrom = m_iBufferPos_Tail;
|
|
VirtualTo = m_iBufferPos;
|
|
}
|
|
|
|
// Get the height of this font.
|
|
float h = (float)font.GetHeight();
|
|
float ls = (float)font.GetLineSpacing();
|
|
|
|
// Set the Z to somewhat more, so we can draw a selected area between the
|
|
// the control and the text.
|
|
glTranslatef((GLfloat)int(m_CachedActualSize.left) + buffer_zone,
|
|
(GLfloat)int(m_CachedActualSize.top+h) + buffer_zone, bz+0.1f);
|
|
|
|
//glColor4f(1.f, 1.f, 1.f, 1.f);
|
|
|
|
// U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE
|
|
// (sort of like a | which is aligned to the left of most characters)
|
|
|
|
float buffered_y = -scroll+buffer_zone;
|
|
|
|
// When selecting larger areas, we need to draw a rectangle box
|
|
// around it, and this is to keep track of where the box
|
|
// started, because we need to follow the iteration until we
|
|
// reach the end, before we can actually draw it.
|
|
bool drawing_box = false;
|
|
float box_x=0.f;
|
|
|
|
float x_pointer=0.f;
|
|
|
|
// If we have a selecting box (i.e. when you have selected letters, not just when
|
|
// the pointer is between two letters) we need to process all letters once
|
|
// before we do it the second time and render allt he text. We can't do it
|
|
// in the same loop because text will have been drawn, so it will disappear when
|
|
// drawn behind the text that has already been drawn. Confusing, well it's necessary
|
|
// (I think).
|
|
|
|
if (SelectingText())
|
|
{
|
|
// Now m_iBufferPos_Tail can be of both sides of m_iBufferPos,
|
|
// just like you can select from right to left, as you can
|
|
// left to right. Is there a difference? Yes, the pointer
|
|
// be placed accordingly, so that if you select shift and
|
|
// expand this selection, it will expand on appropriate side.
|
|
// Anyway, since the drawing procedure needs "To" to be
|
|
// greater than from, we need virtual values that might switch
|
|
// place.
|
|
|
|
int VirtualFrom, VirtualTo;
|
|
|
|
if (m_iBufferPos_Tail >= m_iBufferPos)
|
|
{
|
|
VirtualFrom = m_iBufferPos;
|
|
VirtualTo = m_iBufferPos_Tail;
|
|
}
|
|
else
|
|
{
|
|
VirtualFrom = m_iBufferPos_Tail;
|
|
VirtualTo = m_iBufferPos;
|
|
}
|
|
|
|
|
|
bool done = false;
|
|
for (list<SRow>::const_iterator it = m_CharacterPositions.begin();
|
|
it != m_CharacterPositions.end();
|
|
++it, buffered_y += ls, x_pointer = 0.f)
|
|
{
|
|
if (multiline)
|
|
{
|
|
if (buffered_y > m_CachedActualSize.GetHeight())
|
|
break;
|
|
}
|
|
|
|
// We might as well use 'i' here to iterate, because we need it
|
|
// (often compared against ints, so don't make it size_t)
|
|
for (int i=0; i < (int)it->m_ListOfX.size()+2; ++i)
|
|
{
|
|
if (it->m_ListStart + i == VirtualFrom)
|
|
{
|
|
// we won't actually draw it now, because we don't
|
|
// know the width of each glyph to that position.
|
|
// we need to go along with the iteration, and
|
|
// make a mark where the box started:
|
|
drawing_box = true; // will turn false when finally rendered.
|
|
|
|
// Get current x position
|
|
box_x = x_pointer;
|
|
}
|
|
|
|
// no else!
|
|
|
|
const bool at_end = (i == (int)it->m_ListOfX.size()+1);
|
|
|
|
if (drawing_box == true &&
|
|
(it->m_ListStart + i == VirtualTo || at_end))
|
|
{
|
|
// Depending on if it's just a row change, or if it's
|
|
// the end of the select box, do slightly different things.
|
|
if (at_end)
|
|
{
|
|
if (it->m_ListStart + i != VirtualFrom)
|
|
{
|
|
// and actually add a white space! yes, this is done in any common input
|
|
x_pointer += (float)font.GetCharacterWidth(L' ');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
drawing_box = false;
|
|
done = true;
|
|
}
|
|
|
|
CRect rect;
|
|
// Set 'rect' depending on if it's a multiline control, or a one-line control
|
|
if (multiline)
|
|
{
|
|
rect = CRect(m_CachedActualSize.left+box_x+buffer_zone,
|
|
m_CachedActualSize.top+buffered_y,
|
|
m_CachedActualSize.left+x_pointer+buffer_zone,
|
|
m_CachedActualSize.top+buffered_y+ls);
|
|
|
|
if (rect.bottom < m_CachedActualSize.top)
|
|
continue;
|
|
|
|
if (rect.top < m_CachedActualSize.top)
|
|
rect.top = m_CachedActualSize.top;
|
|
|
|
if (rect.bottom > m_CachedActualSize.bottom)
|
|
rect.bottom = m_CachedActualSize.bottom;
|
|
}
|
|
else // if one-line
|
|
{
|
|
rect = CRect(m_CachedActualSize.left+box_x+buffer_zone-m_HorizontalScroll,
|
|
m_CachedActualSize.top+buffered_y,
|
|
m_CachedActualSize.left+x_pointer+buffer_zone-m_HorizontalScroll,
|
|
m_CachedActualSize.top+buffered_y+ls);
|
|
|
|
if (rect.left < m_CachedActualSize.left)
|
|
rect.left = m_CachedActualSize.left;
|
|
|
|
if (rect.right > m_CachedActualSize.right)
|
|
rect.right = m_CachedActualSize.right;
|
|
}
|
|
|
|
glPushMatrix();
|
|
guiLoadIdentity();
|
|
glEnable(GL_BLEND);
|
|
glDisable(GL_TEXTURE_2D);
|
|
|
|
if (sprite_selectarea)
|
|
GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect);
|
|
|
|
// Blend can have been reset
|
|
glEnable(GL_BLEND);
|
|
glEnable(GL_TEXTURE_2D);
|
|
glDisable(GL_ALPHA_TEST);
|
|
|
|
glPopMatrix();
|
|
}
|
|
|
|
if (i < (int)it->m_ListOfX.size())
|
|
x_pointer += (float)font.GetCharacterWidth((*pCaption)[it->m_ListStart + i]);
|
|
}
|
|
|
|
if (done)
|
|
break;
|
|
|
|
// If we're about to draw a box, and all of a sudden changes
|
|
// line, we need to draw that line's box, and then reset
|
|
// the box drawing to the beginning of the new line.
|
|
if (drawing_box)
|
|
{
|
|
box_x = 0.f;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset some from previous run
|
|
buffered_y = -scroll;
|
|
|
|
// Setup initial color (then it might change and change back, when drawing selected area)
|
|
glColor4f(color.r, color.g, color.b, color.a);
|
|
|
|
bool using_selected_color = false;
|
|
|
|
for (list<SRow>::const_iterator it = m_CharacterPositions.begin();
|
|
it != m_CharacterPositions.end();
|
|
++it, buffered_y += ls)
|
|
{
|
|
if (buffered_y + buffer_zone >= -ls || !multiline)
|
|
{
|
|
if (multiline)
|
|
{
|
|
if (buffered_y + buffer_zone > m_CachedActualSize.GetHeight())
|
|
break;
|
|
}
|
|
|
|
glPushMatrix();
|
|
|
|
// Text must always be drawn in integer values. So we have to convert scroll
|
|
if (multiline)
|
|
glTranslatef(0.f, -(float)(int)scroll, 0.f);
|
|
else
|
|
glTranslatef(-(float)(int)m_HorizontalScroll, 0.f, 0.f);
|
|
|
|
// We might as well use 'i' here, because we need it
|
|
// (often compared against ints, so don't make it size_t)
|
|
for (int i=0; i < (int)it->m_ListOfX.size()+1; ++i)
|
|
{
|
|
if (!multiline && i < (int)it->m_ListOfX.size())
|
|
{
|
|
if (it->m_ListOfX[i] - m_HorizontalScroll < -buffer_zone)
|
|
{
|
|
// We still need to translate the OpenGL matrix
|
|
if (i == 0)
|
|
glTranslatef(it->m_ListOfX[i], 0.f, 0.f);
|
|
else
|
|
glTranslatef(it->m_ListOfX[i] - it->m_ListOfX[i-1], 0.f, 0.f);
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// End of selected area, change back color
|
|
if (SelectingText() &&
|
|
it->m_ListStart + i == VirtualTo)
|
|
{
|
|
using_selected_color = false;
|
|
glColor4f(color.r, color.g, color.b, color.a);
|
|
}
|
|
|
|
if (i != (int)it->m_ListOfX.size() &&
|
|
it->m_ListStart + i == m_iBufferPos)
|
|
{
|
|
// selecting only one, then we need only to draw a vertical line glyph.
|
|
glPushMatrix();
|
|
glwprintf(L"%lc", 0xFE33);
|
|
glPopMatrix();
|
|
}
|
|
|
|
// Drawing selected area
|
|
if (SelectingText() &&
|
|
it->m_ListStart + i >= VirtualFrom &&
|
|
it->m_ListStart + i < VirtualTo &&
|
|
using_selected_color == false)
|
|
{
|
|
using_selected_color = true;
|
|
glColor4f(color_selected.r, color_selected.g, color_selected.b, color_selected.a);
|
|
}
|
|
|
|
if (i != (int)it->m_ListOfX.size())
|
|
glwprintf(L"%lc", (*pCaption)[it->m_ListStart + i]);
|
|
|
|
// check it's now outside a one-liner, then we'll break
|
|
if (!multiline && i < (int)it->m_ListOfX.size())
|
|
{
|
|
if (it->m_ListOfX[i] - m_HorizontalScroll > m_CachedActualSize.GetWidth()-buffer_zone)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (it->m_ListStart + (int)it->m_ListOfX.size() == m_iBufferPos)
|
|
{
|
|
glColor4f(color.r, color.g, color.b, color.a);
|
|
glwprintf(L"%lc", 0xFE33);
|
|
|
|
if (using_selected_color)
|
|
{
|
|
glColor4f(color_selected.r, color_selected.g, color_selected.b, color_selected.a);
|
|
}
|
|
}
|
|
|
|
glPopMatrix();
|
|
}
|
|
glTranslatef(0.f, ls, 0.f);
|
|
}
|
|
|
|
glPopMatrix();
|
|
|
|
// Disable clipping
|
|
for (int i=0; i<4; ++i)
|
|
glDisable(GL_CLIP_PLANE0+i);
|
|
glDisable(GL_TEXTURE_2D);
|
|
}
|
|
}
|
|
|
|
void CInput::UpdateText(int from, int to_before, int to_after)
|
|
{
|
|
CStrW caption;
|
|
CStr font_name;
|
|
float buffer_zone;
|
|
bool multiline;
|
|
GUI<CStr>::GetSetting(this, "font", font_name);
|
|
GUI<CStrW>::GetSetting(this, "caption", caption);
|
|
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
|
|
GUI<bool>::GetSetting(this, "multiline", multiline);
|
|
|
|
if (font_name == CStr())
|
|
{
|
|
// Destroy everything stored, there's no font, so there can be
|
|
// no data.
|
|
m_CharacterPositions.clear();
|
|
return;
|
|
}
|
|
|
|
SRow row;
|
|
row.m_ListStart = 0;
|
|
|
|
int to = 0; // make sure it's initialized
|
|
|
|
if (to_before == -1)
|
|
to = (int)caption.Length();
|
|
|
|
CFont font(font_name);
|
|
|
|
list<SRow>::iterator current_line;
|
|
|
|
// used to replace the last updated copy, because it might contain a "space"
|
|
// in the end, which shouldn't be there because of word-wrapping. the only
|
|
// way to know is to keep on going, but we don't want that, so we'll store
|
|
// a copy.
|
|
SRow copy;
|
|
bool copy_used=false;
|
|
|
|
// Used to ... TODO
|
|
int check_point_row_start = -1;
|
|
int check_point_row_end = -1;
|
|
|
|
// Reset
|
|
if (from == 0 && to_before == -1)
|
|
{
|
|
m_CharacterPositions.clear();
|
|
current_line = m_CharacterPositions.begin();
|
|
}
|
|
else
|
|
{
|
|
debug_assert(to_before != -1);
|
|
|
|
list<SRow>::iterator destroy_row_from, destroy_row_to;
|
|
// Used to check if the above has been set to anything,
|
|
// previously a comparison like:
|
|
// destroy_row_from == list<SRow>::iterator()
|
|
// ... was used, but it didn't work with GCC.
|
|
bool destroy_row_from_used=false, destroy_row_to_used=false;
|
|
|
|
// Iterate, and remove everything between 'from' and 'to_before'
|
|
// actually remove the entire lines they are on, it'll all have
|
|
// to be redone. And when going along, we'll delete a row at a time
|
|
// when continuing to see how much more after 'to' we need to remake.
|
|
int i=0;
|
|
for (list<SRow>::iterator it=m_CharacterPositions.begin();
|
|
it!=m_CharacterPositions.end(); ++it, ++i)
|
|
{
|
|
if (destroy_row_from_used == false &&
|
|
it->m_ListStart > from)
|
|
{
|
|
// Destroy the previous line, and all to 'to_before'
|
|
destroy_row_from = it;
|
|
--destroy_row_from;
|
|
|
|
destroy_row_from_used = true;
|
|
|
|
// For the rare case that we might remove characters to a word
|
|
// so that it suddenly fits on the previous row,
|
|
// we need to by standards re-do the whole previous line too
|
|
// (if one exists)
|
|
if (destroy_row_from != m_CharacterPositions.begin())
|
|
--destroy_row_from;
|
|
}
|
|
|
|
if (destroy_row_to_used == false &&
|
|
it->m_ListStart > to_before)
|
|
{
|
|
destroy_row_to = it;
|
|
|
|
destroy_row_to_used = true;
|
|
|
|
// If it isn't the last row, we'll add another row to delete,
|
|
// just so we can see if the last restorted line is
|
|
// identical to what it was before. If it isn't, then we'll
|
|
// have to continue.
|
|
// 'check_point_row_start' is where we store how the that
|
|
// line looked.
|
|
if (destroy_row_to != m_CharacterPositions.end())
|
|
{
|
|
check_point_row_start = destroy_row_to->m_ListStart;
|
|
check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size();
|
|
if (destroy_row_to->m_ListOfX.empty())
|
|
++check_point_row_end;
|
|
}
|
|
|
|
++destroy_row_to;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (destroy_row_from_used == false)
|
|
{
|
|
destroy_row_from = m_CharacterPositions.end();
|
|
--destroy_row_from;
|
|
|
|
// As usual, let's destroy another row back
|
|
if (destroy_row_from != m_CharacterPositions.begin())
|
|
--destroy_row_from;
|
|
|
|
destroy_row_from_used = true;
|
|
|
|
current_line = destroy_row_from;
|
|
}
|
|
|
|
if (destroy_row_to_used == false)
|
|
{
|
|
destroy_row_to = m_CharacterPositions.end();
|
|
check_point_row_start = -1;
|
|
|
|
destroy_row_from_used = true;
|
|
}
|
|
|
|
// set 'from' to the row we'll destroy from
|
|
// and 'to' to the row we'll destroy to
|
|
from = destroy_row_from->m_ListStart;
|
|
|
|
if (destroy_row_to != m_CharacterPositions.end())
|
|
to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to.
|
|
else
|
|
to = (int)caption.Length();
|
|
|
|
|
|
// Setup the first row
|
|
row.m_ListStart = destroy_row_from->m_ListStart;
|
|
|
|
// Set current line, new rows will be added before current_line, so
|
|
// we'll choose the destroy_row_to, because it won't be deleted
|
|
// in the coming erase.
|
|
current_line = destroy_row_to;
|
|
|
|
list<SRow>::iterator temp_it = destroy_row_to;
|
|
--temp_it;
|
|
|
|
CStr c_caption1(caption.GetSubstring(destroy_row_from->m_ListStart, (temp_it->m_ListStart + temp_it->m_ListOfX.size()) -destroy_row_from->m_ListStart));
|
|
|
|
m_CharacterPositions.erase(destroy_row_from, destroy_row_to);
|
|
|
|
// If there has been a change in number of characters
|
|
// we need to change all m_ListStart that comes after
|
|
// the interval we just destroyed. We'll change all
|
|
// values with the delta change of the string length.
|
|
int delta = to_after - to_before;
|
|
if (delta != 0)
|
|
{
|
|
for (list<SRow>::iterator it=current_line;
|
|
it!=m_CharacterPositions.end();
|
|
++it)
|
|
{
|
|
it->m_ListStart += delta;
|
|
}
|
|
|
|
// Update our check point too!
|
|
check_point_row_start += delta;
|
|
check_point_row_end += delta;
|
|
|
|
if (to != (int)caption.Length())
|
|
to += delta;
|
|
}
|
|
}
|
|
|
|
int last_word_started=from;
|
|
//int last_list_start=-1; // unused
|
|
float x_pos = 0.f;
|
|
|
|
//if (to_before != -1)
|
|
// return;
|
|
|
|
for (int i=from; i<to; ++i)
|
|
{
|
|
if (caption[i] == L'\n' && multiline)
|
|
{
|
|
if (i==to-1 && to != (int)caption.Length())
|
|
break; // it will be added outside
|
|
|
|
CStr c_caption1(caption.GetSubstring(row.m_ListStart, row.m_ListOfX.size()));
|
|
|
|
current_line = m_CharacterPositions.insert( current_line, row );
|
|
++current_line;
|
|
|
|
|
|
// Setup the next row:
|
|
row.m_ListOfX.clear();
|
|
row.m_ListStart = i+1;
|
|
x_pos = 0.f;
|
|
|
|
}
|
|
else
|
|
{
|
|
if (caption[i] == L' '/* || TODO Gee (2004-10-13): the '-' disappears, fix.
|
|
caption[i] == L'-'*/)
|
|
last_word_started = i+1;
|
|
|
|
x_pos += (float)font.GetCharacterWidth(caption[i]);
|
|
|
|
if (x_pos >= GetTextAreaWidth() && multiline)
|
|
{
|
|
// The following decides whether it will word-wrap a word,
|
|
// or if it's only one word on the line, where it has to
|
|
// break the word apart.
|
|
if (last_word_started == row.m_ListStart)
|
|
{
|
|
last_word_started = i;
|
|
row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started));
|
|
//row.m_ListOfX.push_back( x_pos );
|
|
//continue;
|
|
}
|
|
else
|
|
{
|
|
// regular word-wrap
|
|
row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started+1));
|
|
}
|
|
|
|
// Now, create a new line:
|
|
// notice: when we enter a newline, you can stand with the cursor
|
|
// both before and after that character, being on different
|
|
// rows. With automatic word-wrapping, that is not possible. Which
|
|
// is intuitively correct.
|
|
CStr c_caption1(caption.GetSubstring(row.m_ListStart, row.m_ListOfX.size()));
|
|
|
|
current_line = m_CharacterPositions.insert( current_line, row );
|
|
++current_line;
|
|
|
|
// Setup the next row:
|
|
row.m_ListOfX.clear();
|
|
row.m_ListStart = last_word_started;
|
|
|
|
i=last_word_started-1;
|
|
|
|
x_pos = 0.f;
|
|
}
|
|
else
|
|
// Get width of this character:
|
|
row.m_ListOfX.push_back( x_pos );
|
|
}
|
|
|
|
// Check if it's the last iteration, and we're not revising the whole string
|
|
// because in that case, more word-wrapping might be needed.
|
|
// also check if the current line isn't the end
|
|
if (to_before != -1 && i == to-1 && current_line != m_CharacterPositions.end())
|
|
{
|
|
// check all rows and see if any existing
|
|
if (row.m_ListStart != check_point_row_start)
|
|
{
|
|
list<SRow>::iterator destroy_row_from, destroy_row_to;
|
|
// Are used to check if the above has been set to anything,
|
|
// previously a comparison like:
|
|
// destroy_row_from == list<SRow>::iterator()
|
|
// was used, but it didn't work with GCC.
|
|
bool destroy_row_from_used=false, destroy_row_to_used=false;
|
|
|
|
// Iterate, and remove everything between 'from' and 'to_before'
|
|
// actually remove the entire lines they are on, it'll all have
|
|
// to be redone. And when going along, we'll delete a row at a time
|
|
// when continuing to see how much more after 'to' we need to remake.
|
|
int i=0;
|
|
for (list<SRow>::iterator it=m_CharacterPositions.begin();
|
|
it!=m_CharacterPositions.end(); ++it, ++i)
|
|
{
|
|
if (destroy_row_from_used == false &&
|
|
it->m_ListStart > check_point_row_start)
|
|
{
|
|
// Destroy the previous line, and all to 'to_before'
|
|
//if (i >= 2)
|
|
// destroy_row_from = it-2;
|
|
//else
|
|
// destroy_row_from = it-1;
|
|
destroy_row_from = it;
|
|
destroy_row_from_used = true;
|
|
//--destroy_row_from;
|
|
}
|
|
|
|
if (destroy_row_to_used == false &&
|
|
it->m_ListStart > check_point_row_end)
|
|
{
|
|
destroy_row_to = it;
|
|
destroy_row_to_used = true;
|
|
|
|
// If it isn't the last row, we'll add another row to delete,
|
|
// just so we can see if the last restorted line is
|
|
// identical to what it was before. If it isn't, then we'll
|
|
// have to continue.
|
|
// 'check_point_row_start' is where we store how the that
|
|
// line looked.
|
|
// if (destroy_row_to !=
|
|
if (destroy_row_to != m_CharacterPositions.end())
|
|
{
|
|
check_point_row_start = destroy_row_to->m_ListStart;
|
|
check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size();
|
|
if (destroy_row_to->m_ListOfX.empty())
|
|
++check_point_row_end;
|
|
}
|
|
else
|
|
check_point_row_start = check_point_row_end = -1;
|
|
|
|
++destroy_row_to;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (destroy_row_from_used == false)
|
|
{
|
|
destroy_row_from = m_CharacterPositions.end();
|
|
--destroy_row_from;
|
|
|
|
destroy_row_from_used = true;
|
|
|
|
current_line = destroy_row_from;
|
|
}
|
|
|
|
if (destroy_row_to_used == false)
|
|
{
|
|
destroy_row_to = m_CharacterPositions.end();
|
|
check_point_row_start = check_point_row_end = -1;
|
|
|
|
destroy_row_to_used = true;
|
|
}
|
|
|
|
// set 'from' to the from row we'll destroy
|
|
// and 'to' to 'to_after'
|
|
from = destroy_row_from->m_ListStart;
|
|
|
|
if (destroy_row_to != m_CharacterPositions.end())
|
|
to = destroy_row_to->m_ListStart; // notice it will iterate [from, to[, so it will never reach to.
|
|
else
|
|
to = (int)caption.Length();
|
|
|
|
|
|
// Set current line, new rows will be added before current_line, so
|
|
// we'll choose the destroy_row_to, because it won't be deleted
|
|
// in the coming erase.
|
|
current_line = destroy_row_to;
|
|
|
|
copy_used = true;
|
|
|
|
list<SRow>::iterator temp = destroy_row_to;
|
|
|
|
--temp;
|
|
|
|
copy = *temp;
|
|
|
|
m_CharacterPositions.erase(destroy_row_from, destroy_row_to);
|
|
|
|
CStr c_caption(caption.GetSubstring(from, to-from));
|
|
}
|
|
// else, the for loop will end naturally.
|
|
}
|
|
}
|
|
// This is kind of special, when we renew a some lines, then the last
|
|
// one will sometimes end with a space (' '), that really should
|
|
// be omitted when word-wrapping. So we'll check if the last row
|
|
// we'll add has got the same value as the next row.
|
|
if (current_line != m_CharacterPositions.end())
|
|
{
|
|
if (row.m_ListStart + (int)row.m_ListOfX.size() == current_line->m_ListStart)
|
|
row.m_ListOfX.resize( row.m_ListOfX.size()-1 );
|
|
}
|
|
|
|
// add the final row (even if empty)
|
|
m_CharacterPositions.insert( current_line, row );
|
|
|
|
bool scrollbar;
|
|
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
|
|
// Update scollbar
|
|
if (scrollbar)
|
|
{
|
|
GetScrollBar(0).SetScrollRange( m_CharacterPositions.size() * font.GetLineSpacing() + buffer_zone*2.f );
|
|
GetScrollBar(0).SetScrollSpace( m_CachedActualSize.GetHeight() );
|
|
}
|
|
}
|
|
|
|
int CInput::GetMouseHoveringTextPosition()
|
|
{
|
|
if (m_CharacterPositions.empty())
|
|
return 0;
|
|
|
|
// Return position
|
|
int RetPosition;
|
|
|
|
float buffer_zone;
|
|
bool multiline;
|
|
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
|
|
GUI<bool>::GetSetting(this, "multiline", multiline);
|
|
|
|
list<SRow>::iterator current = m_CharacterPositions.begin();
|
|
|
|
CPos mouse = GetMousePos();
|
|
|
|
if (multiline)
|
|
{
|
|
CStr font_name;
|
|
bool scrollbar;
|
|
GUI<CStr>::GetSetting(this, "font", font_name);
|
|
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
|
|
|
|
float scroll=0.f;
|
|
if (scrollbar)
|
|
{
|
|
scroll = GetScrollBar(0).GetPos();
|
|
}
|
|
|
|
// Pointer to caption, will come in handy
|
|
CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting;
|
|
UNUSED2(pCaption);
|
|
|
|
// Now get the height of the font.
|
|
// TODO: Get the real font
|
|
CFont font(font_name);
|
|
float spacing = (float)font.GetLineSpacing();
|
|
//float height = (float)font.GetHeight(); // unused
|
|
|
|
// Change mouse position relative to text.
|
|
mouse -= m_CachedActualSize.TopLeft();
|
|
mouse.x -= buffer_zone;
|
|
mouse.y += scroll - buffer_zone;
|
|
|
|
//if ((m_CharacterPositions.size()-1) * spacing + height < mouse.y)
|
|
// m_iBufferPos = pCaption->Length();
|
|
int row = (int)((mouse.y) / spacing);//m_CharachterPositions.size()
|
|
|
|
if (row < 0)
|
|
row = 0;
|
|
|
|
if (row > (int)m_CharacterPositions.size()-1)
|
|
row = (int)m_CharacterPositions.size()-1;
|
|
|
|
// TODO Gee (2004-11-21): Okay, I need a 'list' for some reasons, but I would really like to
|
|
// be able to get the specific element here. This is hopefully a temporary hack.
|
|
|
|
for (int i=0; i<row; ++i)
|
|
++current;
|
|
}
|
|
else
|
|
{
|
|
// current is already set to begin,
|
|
// but we'll change the mouse.x to fit our horizontal scrolling
|
|
mouse -= m_CachedActualSize.TopLeft();
|
|
mouse.x -= buffer_zone - m_HorizontalScroll;
|
|
// mouse.y is moot
|
|
}
|
|
|
|
//m_iBufferPos = m_CharacterPositions.get.m_ListStart;
|
|
RetPosition = current->m_ListStart;
|
|
|
|
// Okay, now loop through the glyphs to find the appropriate X position
|
|
float dummy;
|
|
RetPosition += GetXTextPosition(current, mouse.x, dummy);
|
|
|
|
return RetPosition;
|
|
}
|
|
|
|
// Does not process horizontal scrolling, 'x' must be modified before inputted.
|
|
int CInput::GetXTextPosition(const list<SRow>::iterator ¤t, const float &x, float &wanted)
|
|
{
|
|
int Ret=0;
|
|
|
|
float previous=0.f;
|
|
int i=0;
|
|
|
|
for (vector<float>::iterator it=current->m_ListOfX.begin();
|
|
it!=current->m_ListOfX.end();
|
|
++it, ++i)
|
|
{
|
|
if (*it >= x)
|
|
{
|
|
if (x - previous >= *it - x)
|
|
Ret += i+1;
|
|
else
|
|
Ret += i;
|
|
|
|
break;
|
|
}
|
|
previous = *it;
|
|
}
|
|
// If a position wasn't found, we will assume the last
|
|
// character of that line.
|
|
if (i == (int)current->m_ListOfX.size())
|
|
{
|
|
Ret += i;
|
|
wanted = x;
|
|
}
|
|
else wanted = 0.f;
|
|
|
|
return Ret;
|
|
}
|
|
|
|
void CInput::DeleteCurSelection()
|
|
{
|
|
CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting;
|
|
|
|
int VirtualFrom, VirtualTo;
|
|
|
|
if (m_iBufferPos_Tail >= m_iBufferPos)
|
|
{
|
|
VirtualFrom = m_iBufferPos;
|
|
VirtualTo = m_iBufferPos_Tail;
|
|
}
|
|
else
|
|
{
|
|
VirtualFrom = m_iBufferPos_Tail;
|
|
VirtualTo = m_iBufferPos;
|
|
}
|
|
|
|
*pCaption = pCaption->Left( VirtualFrom ) +
|
|
pCaption->Right( (long) pCaption->Length()-(VirtualTo) );
|
|
|
|
UpdateText(VirtualFrom, VirtualTo, VirtualFrom);
|
|
|
|
// Remove selection
|
|
m_iBufferPos_Tail = -1;
|
|
m_iBufferPos = VirtualFrom;
|
|
}
|
|
|
|
bool CInput::SelectingText() const
|
|
{
|
|
return m_iBufferPos_Tail != -1 &&
|
|
m_iBufferPos_Tail != m_iBufferPos;
|
|
}
|
|
|
|
float CInput::GetTextAreaWidth()
|
|
{
|
|
bool scrollbar;
|
|
float buffer_zone;
|
|
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
|
|
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
|
|
|
|
if (scrollbar && GetScrollBar(0).GetStyle())
|
|
return m_CachedActualSize.GetWidth() - buffer_zone*2.f - GetScrollBar(0).GetStyle()->m_Width;
|
|
else
|
|
return m_CachedActualSize.GetWidth() - buffer_zone*2.f;
|
|
}
|
|
|
|
void CInput::UpdateAutoScroll()
|
|
{
|
|
float buffer_zone;
|
|
bool multiline;
|
|
GUI<float>::GetSetting(this, "buffer_zone", buffer_zone);
|
|
GUI<bool>::GetSetting(this, "multiline", multiline);
|
|
|
|
// Autoscrolling up and down
|
|
if (multiline)
|
|
{
|
|
CStr font_name;
|
|
bool scrollbar;
|
|
GUI<CStr>::GetSetting(this, "font", font_name);
|
|
GUI<bool>::GetSetting(this, "scrollbar", scrollbar);
|
|
|
|
float scroll=0.f;
|
|
if (!scrollbar)
|
|
return;
|
|
|
|
scroll = GetScrollBar(0).GetPos();
|
|
|
|
// Now get the height of the font.
|
|
// TODO: Get the real font
|
|
CFont font(font_name);
|
|
float spacing = (float)font.GetLineSpacing();
|
|
//float height = font.GetHeight();
|
|
|
|
// TODO Gee (2004-11-21): Okay, I need a 'list' for some reasons, but I would really like to
|
|
// be able to get the specific element here. This is hopefully a temporary hack.
|
|
|
|
list<SRow>::iterator current = m_CharacterPositions.begin();
|
|
int row=0;
|
|
while (current != m_CharacterPositions.end())
|
|
{
|
|
if (m_iBufferPos >= current->m_ListStart &&
|
|
m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size())
|
|
break;
|
|
|
|
++current;
|
|
++row;
|
|
}
|
|
|
|
// If scrolling down
|
|
if (-scroll + (float)(row+1) * spacing + buffer_zone*2.f > m_CachedActualSize.GetHeight())
|
|
{
|
|
// Scroll so the selected row is shown completely, also with buffer_zone length to the edge.
|
|
GetScrollBar(0).SetPos((float)(row+1) * spacing - m_CachedActualSize.GetHeight() + buffer_zone*2.f);
|
|
}
|
|
else
|
|
// If scrolling up
|
|
if (-scroll + (float)row * spacing < 0.f)
|
|
{
|
|
// Scroll so the selected row is shown completely, also with buffer_zone length to the edge.
|
|
GetScrollBar(0).SetPos((float)row * spacing);
|
|
}
|
|
}
|
|
else // autoscrolling left and right
|
|
{
|
|
// Get X position of position:
|
|
if (m_CharacterPositions.empty())
|
|
return;
|
|
|
|
float x_position = 0.f;
|
|
float x_total = 0.f;
|
|
if (!m_CharacterPositions.begin()->m_ListOfX.empty())
|
|
{
|
|
|
|
// Get position of m_iBufferPos
|
|
if ((int)m_CharacterPositions.begin()->m_ListOfX.size() >= m_iBufferPos &&
|
|
m_iBufferPos != 0)
|
|
x_position = m_CharacterPositions.begin()->m_ListOfX[m_iBufferPos-1];
|
|
|
|
// Get complete length:
|
|
x_total = m_CharacterPositions.begin()->m_ListOfX[ m_CharacterPositions.begin()->m_ListOfX.size()-1 ];
|
|
}
|
|
|
|
// Check if outside to the right
|
|
if (x_position - m_HorizontalScroll + buffer_zone*2.f > m_CachedActualSize.GetWidth())
|
|
m_HorizontalScroll = x_position - m_CachedActualSize.GetWidth() + buffer_zone*2.f;
|
|
|
|
// Check if outside to the left
|
|
if (x_position - m_HorizontalScroll < 0.f)
|
|
m_HorizontalScroll = x_position;
|
|
|
|
// Check if the text doesn't even fill up to the right edge even though scrolling is done.
|
|
if (m_HorizontalScroll != 0.f &&
|
|
x_total - m_HorizontalScroll + buffer_zone*2.f < m_CachedActualSize.GetWidth())
|
|
m_HorizontalScroll = x_total - m_CachedActualSize.GetWidth() + buffer_zone*2.f;
|
|
|
|
// Now this is the fail-safe, if x_total isn't even the length of the control,
|
|
// remove all scrolling
|
|
if (x_total + buffer_zone*2.f < m_CachedActualSize.GetWidth())
|
|
m_HorizontalScroll = 0.f;
|
|
}
|
|
}
|