Index: ps/trunk/source/gui/CInput.cpp =================================================================== --- ps/trunk/source/gui/CInput.cpp (revision 9645) +++ ps/trunk/source/gui/CInput.cpp (revision 9646) @@ -1,1680 +1,1991 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* CInput */ #include "precompiled.h" #include "GUI.h" #include "CInput.h" #include "CGUIScrollBarVertical.h" #include "ps/Font.h" #include "lib/ogl.h" #include "lib/res/graphics/unifont.h" #include "lib/sysdep/clipboard.h" #include "ps/Hotkey.h" #include "ps/CLogger.h" #include "ps/Globals.h" +#include //------------------------------------------------------------------- // 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_CStrW, "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_CStrW, "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) { ENSURE(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->ev.type == SDL_HOTKEYDOWN) { - std::string hotkey = static_cast(ev->ev.user.data1); - if (hotkey == "console.paste") - { - wchar_t* text = sys_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); - - sys_clipboard_free(text); - } - - return IN_HANDLED; - } + return(ManuallyHandleHotkeyEvent(ev)); } else if (ev->ev.type == SDL_KEYDOWN) { + // 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; + bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; + int szChar = ev->ev.key.keysym.sym; wchar_t cooked = (wchar_t)ev->ev.key.keysym.unicode; switch (szChar) { - case '\t': + case SDLK_TAB: // '\t' /* Auto Complete */ // TODO Gee: (2004-09-07) What to do with tab? break; - case '\b': - m_WantedX=0.f; + case SDLK_BACKSPACE: // '\b' + m_WantedX=0.0f; if (SelectingText()) DeleteCurSelection(); else { m_iBufferPos_Tail = -1; - if (pCaption->empty() || - m_iBufferPos == 0) + if (pCaption->empty() || 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 ); + { + 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 ); - --m_iBufferPos; + --m_iBufferPos; - UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); + UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); + } } UpdateAutoScroll(); break; case SDLK_DELETE: - m_WantedX=0.f; + m_WantedX=0.0f; // If selection: if (SelectingText()) { DeleteCurSelection(); } else { - if (pCaption->empty() || - m_iBufferPos == (int)pCaption->length()) + if (pCaption->empty() || m_iBufferPos == (int)pCaption->length()) + { break; + } + else + { + *pCaption = pCaption->Left( m_iBufferPos ) + + pCaption->Right( (long) pCaption->length()-(m_iBufferPos+1) ); - *pCaption = pCaption->Left( m_iBufferPos ) + - pCaption->Right( (long) pCaption->length()-(m_iBufferPos+1) ); - - UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); + 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]) + if (!shiftKeyPressed) { // 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; + m_WantedX=0.0f; 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]) + if (!shiftKeyPressed) { // 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; + m_WantedX=0.0f; 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 (shiftKeyPressed || !SelectingText()) { - // If there's not a selection, we should create one now - if (!SelectingText() && !g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT]) + if (!shiftKeyPressed) { - // 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) + if (m_iBufferPos > 0) --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; + m_WantedX=0.0f; - if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT] || - !SelectingText()) + if (shiftKeyPressed || !SelectingText()) { - // If there's not a selection, we should create one now - if (!SelectingText() && !g_keys[SDLK_RSHIFT] && !g_keys[SDLK_LSHIFT]) + if (!shiftKeyPressed) { - // 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()) + 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]) + if (!shiftKeyPressed) { - // 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; } std::list::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]) + if (!shiftKeyPressed) { - // 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; } std::list::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 a Press event for single liners (e.g. submitting forms) // otherwise a '\n' character will be added. { bool multiline; GUI::GetSetting(this, "multiline", multiline); if (!multiline) { SendEvent(GUIM_PRESSED, "press"); 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::GetSetting(this, "max_length", max_length); if (max_length != 0 && (int)pCaption->length() >= max_length) break; - m_WantedX=0.f; + m_WantedX=0.0f; if (SelectingText()) DeleteCurSelection(); m_iBufferPos_Tail = -1; if (m_iBufferPos == (int)pCaption->length()) *pCaption += cooked; else *pCaption = pCaption->Left(m_iBufferPos) + cooked + pCaption->Right((long) pCaption->length()-m_iBufferPos); UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); ++m_iBufferPos; UpdateAutoScroll(); } break; } return IN_HANDLED; } return IN_PASS; } + +InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev) +{ + CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; + bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; + + std::string hotkey = static_cast(ev->ev.user.data1); + if (hotkey == "console.paste") + { + m_WantedX=0.0f; + + wchar_t* text = sys_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); + + sys_clipboard_free(text); + } + + return IN_HANDLED; + } + else if (hotkey == "console.copy" || hotkey == "console.cut") + { + m_WantedX=0.0f; + + if (SelectingText()) + { + int virtualFrom; + int virtualTo; + + if (m_iBufferPos_Tail >= m_iBufferPos) + { + virtualFrom = m_iBufferPos; + virtualTo = m_iBufferPos_Tail; + } + else + { + virtualFrom = m_iBufferPos_Tail; + virtualTo = m_iBufferPos; + } + + CStrW text = (pCaption->Left(virtualTo)).Right(virtualTo - virtualFrom); + + sys_clipboard_set(&text[0]); + + if (hotkey == "console.cut") + { + DeleteCurSelection(); + } + } + + return IN_HANDLED; + } + else if (hotkey == "text.delete.word.left") + { + m_WantedX=0.0f; + + if (SelectingText()) + { + DeleteCurSelection(); + } + if (!pCaption->empty() && !m_iBufferPos == 0) + { + m_iBufferPos_Tail = m_iBufferPos; + CStrW searchString = pCaption->Left( m_iBufferPos ); + + // If we are starting in whitespace, adjust position until we get a non whitespace + while (m_iBufferPos > 0) + { + if (!iswspace(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + + // If we end up on a puctuation char we just delete it (treat punct like a word) + if (iswpunct(searchString[m_iBufferPos - 1])) + m_iBufferPos--; + else + { + // Now we are on a non white space character, adjust position to char after next whitespace char is found + while (m_iBufferPos > 0) + { + if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + } + + DeleteCurSelection(); + } + return IN_HANDLED; + } + else if (hotkey == "text.delete.word.right") + { + m_WantedX=0.0f; + + if (SelectingText()) + { + DeleteCurSelection(); + } + if (!pCaption->empty() && m_iBufferPos < (int)pCaption->length()) + { + // Delete the word to the right of the cursor + m_iBufferPos_Tail = m_iBufferPos; + + // Delete chars to the right unit we hit whitespace + while (++m_iBufferPos < (int)pCaption->length()) + { + if (iswspace((*pCaption)[m_iBufferPos]) || iswpunct((*pCaption)[m_iBufferPos])) + break; + } + + // Eliminate any whitespace behind the word we just deleted + while (m_iBufferPos < (int)pCaption->length()) + { + if (!iswspace((*pCaption)[m_iBufferPos])) + break; + + m_iBufferPos++; + } + DeleteCurSelection(); + } + return IN_HANDLED; + } + else if (hotkey == "text.move.word.left") + { + m_WantedX=0.0f; + + if (shiftKeyPressed || !SelectingText()) + { + if (!shiftKeyPressed) + { + m_iBufferPos_Tail = -1; + } + else if (!SelectingText()) + { + m_iBufferPos_Tail = m_iBufferPos; + } + + if (!pCaption->empty() && !m_iBufferPos == 0) + { + CStrW searchString = pCaption->Left( m_iBufferPos ); + + // If we are starting in whitespace, adjust position until we get a non whitespace + while (m_iBufferPos > 0) + { + if (!iswspace(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + + // If we end up on a puctuation char we just select it (treat punct like a word) + if (iswpunct(searchString[m_iBufferPos - 1])) + m_iBufferPos--; + else + { + // Now we are on a non white space character, adjust position to char after next whitespace char is found + while (m_iBufferPos > 0) + { + if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + } + } + } + else + { + if (m_iBufferPos_Tail < m_iBufferPos) + m_iBufferPos = m_iBufferPos_Tail; + + m_iBufferPos_Tail = -1; + } + + UpdateAutoScroll(); + + return IN_HANDLED; + } + else if (hotkey == "text.move.word.right") + { + m_WantedX=0.0f; + + if (shiftKeyPressed || !SelectingText()) + { + if (!shiftKeyPressed) + { + m_iBufferPos_Tail = -1; + } + else if (!SelectingText()) + { + m_iBufferPos_Tail = m_iBufferPos; + } + + if (!pCaption->empty() && m_iBufferPos < (int)pCaption->length()) + { + CStrW searchString = *pCaption; + + // Select chars to the right until we hit whitespace + while (++m_iBufferPos < (int)pCaption->length()) + { + if (iswspace((*pCaption)[m_iBufferPos]) || iswpunct((*pCaption)[m_iBufferPos])) + break; + } + + // Also select any whitespace following the word we just selected + while (m_iBufferPos < (int)pCaption->length()) + { + if (!iswspace((*pCaption)[m_iBufferPos])) + break; + + m_iBufferPos++; + } + } + } + else + { + if (m_iBufferPos_Tail > m_iBufferPos) + m_iBufferPos = m_iBufferPos_Tail; + + m_iBufferPos_Tail = -1; + } + + UpdateAutoScroll(); + + return IN_HANDLED; + } + else + { + return IN_PASS; + } +} + + void CInput::HandleMessage(SGUIMessage &Message) { // TODO Gee: IGUIScrollBarOwner::HandleMessage(Message); switch (Message.type) { case GUIM_SETTINGS_UPDATED: { bool scrollbar; GUI::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("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::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::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::GetSetting(this, "scrollbar", scrollbar); GUI::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_DBLCLICK_LEFT: + { + CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; + m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); + + // See if we are clicking over whitespace + if (iswspace((*pCaption)[m_iBufferPos])) + { + // see if we are in a section of whitespace greater than one character + if ((m_iBufferPos + 1 < (int) pCaption->length() && iswspace((*pCaption)[m_iBufferPos + 1])) || + (m_iBufferPos - 1 > 0 && iswspace((*pCaption)[m_iBufferPos - 1]))) + { + // + // We are clicking in an area with more than one whitespace character + // so we select both the word to the left and then the word to the right + // + // [1] First the left + // skip the whitespace + while (m_iBufferPos > 0) + { + if (!iswspace((*pCaption)[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + } + // now go until we hit white space or punctuation + while (m_iBufferPos > 0) + { + if (iswspace((*pCaption)[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + + if (iswpunct((*pCaption)[m_iBufferPos])) + break; + } + + // [2] Then the right + // go right until we are not in whitespace + while (++m_iBufferPos_Tail < (int)pCaption->length()) + { + if (!iswspace((*pCaption)[m_iBufferPos_Tail])) + break; + } + // now go to the right until we hit whitespace or punctuation + while (++m_iBufferPos_Tail < (int)pCaption->length()) + { + if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) + break; + } + } + else + { + // single whitespace so select word to the right + while (++m_iBufferPos_Tail < (int)pCaption->length()) + { + if (!iswspace((*pCaption)[m_iBufferPos_Tail])) + break; + } + + // Don't include the leading whitespace + m_iBufferPos = m_iBufferPos_Tail; + + // now go to the right until we hit whitespace or punctuation + while (++m_iBufferPos_Tail < (int)pCaption->length()) + { + if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) + break; + } + } + } + else + { + // clicked on non-whitespace so select current word + // go until we hit white space or punctuation + while (m_iBufferPos > 0) + { + if (iswspace((*pCaption)[m_iBufferPos - 1])) + break; + + m_iBufferPos--; + + if (iswpunct((*pCaption)[m_iBufferPos])) + break; + } + // go to the right until we hit whitespace or punctuation + while (++m_iBufferPos_Tail < (int)pCaption->length()) + { + if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) + break; + } + } + } + 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 SGUIMessage msg(GUIM_MOUSE_MOTION); HandleMessage(msg); 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 SGUIMessage msg(GUIM_MOUSE_MOTION); HandleMessage(msg); 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::GetSetting(this, "scrollbar_style", scrollbar_style); GetScrollBar(0).SetScrollBarStyle( scrollbar_style ); UpdateText(); } break; case GUIM_GOT_FOCUS: m_iBufferPos = 0; break; case GUIM_LOST_FOCUS: m_iBufferPos = -1; m_iBufferPos_Tail = -1; break; default: break; } } void CInput::UpdateCachedSize() { // If an ancestor's size changed, this will let us intercept the change and // update our scrollbar positions IGUIObject::UpdateCachedSize(); bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); if (scrollbar) { 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); } } void CInput::Draw() { float bz = GetBufferedZ(); // First call draw on ScrollBarOwner bool scrollbar; float buffer_zone; bool multiline; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); if (scrollbar && multiline) { // Draw scrollbar IGUIScrollBarOwner::Draw(); } if (GetGUI()) { CStrW font_name; CColor color, color_selected; //CStrW caption; GUI::GetSetting(this, "font", font_name); GUI::GetSetting(this, "textcolor", color); GUI::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::GetSettingPointer(this, "sprite", sprite); GUI::GetSettingPointer(this, "sprite_selectarea", sprite_selectarea); GUI::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 all the 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 (std::list::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+(h-ls)/2, m_CachedActualSize.left+x_pointer+buffer_zone, m_CachedActualSize.top+buffered_y+(h+ls)/2); 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+(h-ls)/2, m_CachedActualSize.left+x_pointer+buffer_zone-m_HorizontalScroll, m_CachedActualSize.top+buffered_y+(h+ls)/2); 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]); + 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 (std::list::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()) + 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); + 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 cursor. glPushMatrix(); glwprintf(L"_"); 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"_"); 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; CStrW font_name; float buffer_zone; bool multiline; GUI::GetSetting(this, "font", font_name); GUI::GetSetting(this, "caption", caption); GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); // Ensure positions are valid after caption changes m_iBufferPos = std::min(m_iBufferPos, (int)caption.size()); m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, (int)caption.size()); if (font_name == CStrW()) { // 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); std::list::iterator current_line; // 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 { ENSURE(to_before != -1); std::list::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 == std::list::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 (std::list::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(); + 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. + 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; std::list::iterator temp_it = destroy_row_to; --temp_it; 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 (std::list::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= 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. 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) { std::list::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 == std::list::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 (std::list::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; std::list::iterator temp = destroy_row_to; --temp; m_CharacterPositions.erase(destroy_row_from, destroy_row_to); } // 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::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; + int retPosition; float buffer_zone; bool multiline; GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); std::list::iterator current = m_CharacterPositions.begin(); CPos mouse = GetMousePos(); if (multiline) { CStrW font_name; bool scrollbar; GUI::GetSetting(this, "font", font_name); GUI::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 'std::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; im_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); + retPosition += GetXTextPosition(current, mouse.x, dummy); - return RetPosition; + return retPosition; } // Does not process horizontal scrolling, 'x' must be modified before inputted. int CInput::GetXTextPosition(const std::list::iterator ¤t, const float &x, float &wanted) { - int Ret=0; - + int ret=0; float previous=0.f; int i=0; for (std::vector::iterator it=current->m_ListOfX.begin(); it!=current->m_ListOfX.end(); ++it, ++i) { if (*it >= x) { if (x - previous >= *it - x) - Ret += i+1; + ret += i+1; else - Ret += i; + 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; + ret += i; wanted = x; } else wanted = 0.f; - return Ret; + return ret; } void CInput::DeleteCurSelection() { CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; - int VirtualFrom, VirtualTo; + int virtualFrom; + int virtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { - VirtualFrom = m_iBufferPos; - VirtualTo = m_iBufferPos_Tail; + virtualFrom = m_iBufferPos; + virtualTo = m_iBufferPos_Tail; } else { - VirtualFrom = m_iBufferPos_Tail; - VirtualTo = m_iBufferPos; + virtualFrom = m_iBufferPos_Tail; + virtualTo = m_iBufferPos; } - *pCaption = pCaption->Left( VirtualFrom ) + - pCaption->Right( (long) pCaption->length()-(VirtualTo) ); + *pCaption = pCaption->Left( virtualFrom ) + + pCaption->Right( (long) pCaption->length() - (virtualTo) ); - UpdateText(VirtualFrom, VirtualTo, VirtualFrom); + UpdateText(virtualFrom, virtualTo, virtualFrom); // Remove selection m_iBufferPos_Tail = -1; - m_iBufferPos = VirtualFrom; + 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::GetSetting(this, "scrollbar", scrollbar); GUI::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::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); // Autoscrolling up and down if (multiline) { CStrW font_name; bool scrollbar; GUI::GetSetting(this, "font", font_name); GUI::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 'std::list' for some reasons, but I would really like to // be able to get the specific element here. This is hopefully a temporary hack. std::list::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; } } Index: ps/trunk/source/gui/CInput.h =================================================================== --- ps/trunk/source/gui/CInput.h (revision 9645) +++ ps/trunk/source/gui/CInput.h (revision 9646) @@ -1,182 +1,187 @@ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* GUI Object - Input [box] --Overview-- GUI Object representing a text field you can edit. --More info-- Check GUI.h */ #ifndef INCLUDED_CINPUT #define INCLUDED_CINPUT //-------------------------------------------------------- // Includes / Compiler directives //-------------------------------------------------------- #include "GUI.h" // TODO Gee: Remove class IGUIScrollBar; //-------------------------------------------------------- // Macros //-------------------------------------------------------- //-------------------------------------------------------- // Types //-------------------------------------------------------- //-------------------------------------------------------- // Declarations //-------------------------------------------------------- /** * Text field where you can input and edit the text. * * It doesn't use IGUITextOwner, because we don't need * any other features than word-wrapping, and we need to be * able to rapidly change the string. * * @see IGUIObject */ class CInput : public IGUIScrollBarOwner { GUI_OBJECT(CInput) protected: // forwards struct SRow; public: CInput(); virtual ~CInput(); /** * @see IGUIObject#ResetStates() */ virtual void ResetStates() { IGUIScrollBarOwner::ResetStates(); } // Check where the mouse is hovering, and get the appropriate text position. // return is the text-position index. // const in philosophy, but I need to retrieve the caption in a non-const way. int GetMouseHoveringTextPosition(); // Same as above, but only on one row in X, and a given value, not the mouse's // wanted is filled with x if the row didn't extend as far as we int GetXTextPosition(const std::list::iterator &c, const float &x, float &wanted); protected: /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage &Message); /** * Handle events manually to catch keyboard inputting. */ virtual InReaction ManuallyHandleEvent(const SDL_Event_* ev); /** + * Handle hotkey events (called by ManuallyHandleEvent) + */ + virtual InReaction ManuallyHandleHotkeyEvent(const SDL_Event_* ev); + + /** * @see IGUIObject#UpdateCachedSize() */ virtual void UpdateCachedSize(); /** * Draws the Text */ virtual void Draw(); // Calculate m_CharacterPosition // the main task for this function is to perfom word-wrapping // You input from which character it has been changed, because // if we add a character to the very last end, we don't want // process everything all over again! Also notice you can // specify a 'to' also, it will only be used though if a '\n' // appears, because then the word-wrapping won't change after // that. void UpdateText(int from=0, int to_before=-1, int to_after=-1); // Delete the current selection. Also places the pointer at the // crack between the two segments kept. void DeleteCurSelection(); // Is text selected? It can be denote two ways, m_iBufferPos_Tail // being -1 or the same as m_iBufferPos. This makes for clearer // code. bool SelectingText() const; // Get area of where text can be drawn. float GetTextAreaWidth(); // Called every time the auto-scrolling should be checked. void UpdateAutoScroll(); protected: // Cursor position // (the second one is for selection of larger areas, -1 if not used) // A note on 'Tail', it was first called 'To', and the natural order // of X and X_To was X then X_To. Now Tail is called so, because it // can be placed both before and after, but the important things is // that m_iBufferPos is ALWAYS where the edit pointer is. Yes, there // is an edit pointer even though you select a larger area. For instance // if you want to resize the selection with Shift+Left/Right, there // are always two ways a selection can look. Check any OS GUI and you'll // see. int m_iBufferPos, m_iBufferPos_Tail; // the outer vector is lines, and the inner is X positions // in a row. So that we can determine where characters are // placed. It's important because we need to know where the // pointer should be placed when the input control is pressed. struct SRow { int m_ListStart; // Where does the Row starts std::vector m_ListOfX; // List of X values for each character. }; // List of rows, list because I use a lot of iterators, and change // its size continuously, it's easier and safer if I know the // iterators never gets invalidated. // For one-liners, only one row is used. std::list< SRow > m_CharacterPositions; // *** Things for a multi-lined input control *** // // This is when you change row with up/down, and the row you jump // to doesn't have anything at that X position, then it will // keep the WantedX position in mind when switching to the next // row. It will keep on being used until it reach a row which meets // the requirements. // 0.0f means not in use. float m_WantedX; // If we are in the process of selecting a larger selection of text // using the mouse click (hold) and drag, this is true. bool m_SelectingText; // *** Things for one-line input control *** // float m_HorizontalScroll; }; #endif Index: ps/trunk/source/lib/sysdep/os/win/wclipboard.cpp =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wclipboard.cpp (revision 9645) +++ ps/trunk/source/lib/sysdep/os/win/wclipboard.cpp (revision 9646) @@ -1,114 +1,123 @@ /* Copyright (c) 2010 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "precompiled.h" #include "lib/sysdep/clipboard.h" #include "lib/sysdep/os/win/win.h" #include "lib/sysdep/os/win/wutil.h" -// caller is responsible for freeing *hMem. -static Status SetClipboardText(const wchar_t* text, HGLOBAL* hMem) + +// caller is responsible for freeing hMem. +static Status SetClipboardText(const wchar_t* text, HGLOBAL& hMem) { const size_t numChars = wcslen(text); - *hMem = GlobalAlloc(GMEM_MOVEABLE, (numChars+1) * sizeof(wchar_t)); - if(!*hMem) + hMem = GlobalAlloc(GMEM_MOVEABLE|GMEM_ZEROINIT, (numChars + 1) * sizeof(wchar_t)); + if(!hMem) WARN_RETURN(ERR::NO_MEM); - wchar_t* lockedText = (wchar_t*)GlobalLock(*hMem); + wchar_t* lockedText = (wchar_t*)GlobalLock(hMem); if(!lockedText) WARN_RETURN(ERR::NO_MEM); wcscpy_s(lockedText, numChars+1, text); - GlobalUnlock(*hMem); + GlobalUnlock(hMem); - HANDLE hData = SetClipboardData(CF_UNICODETEXT, *hMem); + HANDLE hData = SetClipboardData(CF_UNICODETEXT, hMem); if(!hData) // failed WARN_RETURN(ERR::FAIL); return INFO::OK; } -// "copy" text into the clipboard. replaces previous contents. -Status sys_clipboard_set(const wchar_t* text) + +// @return INFO::OK iff text has been assigned a pointer (which the +// caller must free via sys_clipboard_free) to the clipboard text. +static Status GetClipboardText(wchar_t*& text) { - // note: MSDN claims that the window handle must not be 0; - // that does actually work on WinXP, but we'll play it safe. - if(!OpenClipboard(wutil_AppWindow())) + // NB: Windows NT/2000+ auto convert CF_UNICODETEXT <-> CF_TEXT. + + if(!IsClipboardFormatAvailable(CF_UNICODETEXT)) + return INFO::CANNOT_HANDLE; + + HGLOBAL hMem = GetClipboardData(CF_UNICODETEXT); + if(!hMem) WARN_RETURN(ERR::FAIL); - EmptyClipboard(); - HGLOBAL hMem; - Status ret = SetClipboardText(text, &hMem); + const wchar_t* lockedText = (const wchar_t*)GlobalLock(hMem); + if(!lockedText) + WARN_RETURN(ERR::NO_MEM); - CloseClipboard(); + const size_t size = GlobalSize(hMem); + text = (wchar_t*)malloc(size); + if(!text) + WARN_RETURN(ERR::NO_MEM); + wcscpy_s(text, size/sizeof(wchar_t), lockedText); - // note: MSDN's SetClipboardData documentation says hMem must not be - // freed until after CloseClipboard. however, GlobalFree still fails - // after the successful completion of both. we'll leave it in to avoid - // memory leaks, but ignore its return value. - (void)GlobalFree(hMem); + (void)GlobalUnlock(hMem); - return ret; + return INFO::OK; } -static wchar_t* CopyClipboardContents() +// OpenClipboard parameter. +// NB: using wutil_AppWindow() causes GlobalLock to fail. +static const HWND hWndNewOwner = 0; // MSDN: associate with "current task" + +Status sys_clipboard_set(const wchar_t* text) { - // Windows NT/2000+ auto convert UNICODETEXT <-> TEXT - HGLOBAL hMem = GetClipboardData(CF_UNICODETEXT); - if(!hMem) - return 0; + if(!OpenClipboard(hWndNewOwner)) + WARN_RETURN(ERR::FAIL); - const wchar_t* lockedText = (const wchar_t*)GlobalLock(hMem); - if(!lockedText) - return 0; + WARN_IF_FALSE(EmptyClipboard()); - const size_t numChars = GlobalSize(hMem)/sizeof(wchar_t) - 1; - wchar_t* text = new wchar_t[numChars+1]; - wcscpy_s(text, numChars+1, lockedText); + // NB: to enable copy/pasting something other than text, add + // message handlers for WM_RENDERFORMAT and WM_RENDERALLFORMATS. + HGLOBAL hMem; + Status ret = SetClipboardText(text, hMem); - GlobalUnlock(hMem); + WARN_IF_FALSE(CloseClipboard()); // must happen before GlobalFree + + ENSURE(GlobalFree(hMem) == 0); // (0 indicates success) - return text; + return ret; } -// allow "pasting" from clipboard. returns the current contents if they -// can be represented as text, otherwise 0. -// when it is no longer needed, the returned pointer must be freed via -// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt) + wchar_t* sys_clipboard_get() { - if(!OpenClipboard(wutil_AppWindow())) + if(!OpenClipboard(hWndNewOwner)) return 0; - wchar_t* const ret = CopyClipboardContents(); - CloseClipboard(); - return ret; + + wchar_t* text; + Status ret = GetClipboardText(text); + + WARN_IF_FALSE(CloseClipboard()); + + return (ret == INFO::OK)? text : 0; } -// frees memory used by , which must have been returned by -// sys_clipboard_get. see note above. Status sys_clipboard_free(wchar_t* text) { - delete[] text; + free(text); return INFO::OK; } Index: ps/trunk/source/lib/sysdep/clipboard.h =================================================================== --- ps/trunk/source/lib/sysdep/clipboard.h (revision 9645) +++ ps/trunk/source/lib/sysdep/clipboard.h (revision 9646) @@ -1,34 +1,38 @@ -/* Copyright (c) 2010 Wildfire Games +/* Copyright (c) 2011 Wildfire Games * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#ifndef INCLUDED_SYSDEP_CLIPBOARD +#define INCLUDED_SYSDEP_CLIPBOARD + // "copy" text into the clipboard. replaces previous contents. extern Status sys_clipboard_set(const wchar_t* text); -// allow "pasting" from clipboard. returns the current contents if they -// can be represented as text, otherwise 0. -// when it is no longer needed, the returned pointer must be freed via -// sys_clipboard_free. (NB: not necessary if zero, but doesn't hurt) +// allow "pasting" from clipboard. +// @return current clipboard text or 0 if not representable as text. +// callers are responsible for passing this pointer to sys_clipboard_free. extern wchar_t* sys_clipboard_get(); -// frees memory used by , which must have been returned by -// sys_clipboard_get. see note above. +// free memory returned by sys_clipboard_get. +// @param copy is ignored if 0. extern Status sys_clipboard_free(wchar_t* copy); + +#endif // #ifndef INCLUDED_SYSDEP_CLIPBOARD Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg (revision 9645) +++ ps/trunk/binaries/data/config/default.cfg (revision 9646) @@ -1,201 +1,208 @@ ; Global Configuration Settings ; ; ************************************************************** ; * DO NOT EDIT THIS FILE if you want personal customisations: * ; * create a text file called "local.cfg" instead, and copy * ; * the lines from this file that you want to change. * ; * * ; * On Linux / OS X, create: * ; * ~/.config/0ad/config/local.cfg * ; * * ; * On Windows, create: * ; * %appdata%/0ad/config/local.cfg * ; * * ; ************************************************************** ; Enable/disable windowed mode by default. (Use Alt+Enter to toggle in the game.) windowed = false ; Force a particular resolution. (If these are 0, the default is ; to keep the current desktop resolution in fullscreen mode or to ; use 1024x768 in windowed mode.) xres = 0 yres = 0 ; Force a non-standard bit depth (if 0 then use the current desktop bit depth) bpp = 0 ; System settings: fancywater = true shadows = true vsync = false nos3tc = false noautomipmap = true novbo = false noframebufferobject = false ; Linux only: Set the driconf force_s3tc_enable option at startup, ; for compressed texture support force_s3tc_enable = true ; Specify the render path. This can be one of: ; default Automatically select one of the below, depending on system capabilities ; fixed Only use OpenGL fixed function pipeline ; shader Use vertex/fragment shaders for transform and lighting where possible ; Using 'fixed' instead of 'default' may work around some graphics-related problems, ; but will reduce performance and features when a modern graphics card is available. renderpath = default ; Adjusts how OpenGL calculates mipmap level of detail. 0.0f is the default (blurry) value. ; Lower values sharpen/extend, and higher values blur/decrease. Clamped at -3.0 to 3.0. ; -1.0 to -1.5 recommended for good results. lodbias = 0 ; Opt-in online user reporting system userreport.url = "http://feedback.wildfiregames.com/report/upload/v1/" ; Font mappings: font.console = console font.default = palatino12 font.misc = verdana16 ; Colour of the sky (in "r g b" format) skycolor = "0 0 0" ; GENERAL PREFERENCES: sound.mastergain = 0.5 ; Camera control settings view.scroll.speed = 120.0 view.rotate.x.speed = 1.2 view.rotate.x.min = 20 view.rotate.x.max = 60 view.rotate.x.default = 30 view.rotate.y.speed = 2.0 view.rotate.y.speed.wheel = 0.45 view.rotate.y.default = 0.0 view.drag.speed = 0.5 view.zoom.speed = 256.0 view.zoom.speed.wheel = 32.0 view.zoom.min = 96.0 view.zoom.max = 256.0 view.zoom.default = 192.0 view.pos.smoothness = 0.1 view.zoom.smoothness = 0.4 view.rotate.x.smoothness = 0.5 view.rotate.y.smoothness = 0.3 ; HOTKEY MAPPINGS: ; Each one of the specified keys will trigger the action on the left ; for multiple-key combinations, separate keys with '+' and enclose the entire thing ; in doublequotes. ; See keys.txt for the list of key names. ; > SYSTEM SETTINGS hotkey.exit = "Alt+F4", "Ctrl+Break" ; Exit to desktop hotkey.leave = Escape ; End current game or Exit hotkey.pause = Pause ; Pause/unpause game hotkey.screenshot = F2 ; Take PNG screenshot hotkey.bigscreenshot = "Shift+F2" ; Take large BMP screenshot hotkey.togglefullscreen = "Alt+Return" ; Toggle fullscreen/windowed mode hotkey.screenshot.watermark = "K" ; Toggle product/company watermark for official screenshots hotkey.wireframe = "Alt+W" ; Toggle wireframe mode ; > CAMERA SETTINGS hotkey.camera.reset = "H" ; Reset camera rotation to default. hotkey.camera.follow = "F" ; Follow the first unit in the selection hotkey.camera.zoom.in = Plus, Equals, NumPlus ; Zoom camera in (continuous control) hotkey.camera.zoom.out = Minus, NumMinus ; Zoom camera out (continuous control) hotkey.camera.zoom.wheel.in = WheelUp ; Zoom camera in (stepped control) hotkey.camera.zoom.wheel.out = WheelDown ; Zoom camera out (stepped control) hotkey.camera.rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards hotkey.camera.rotate.down = "Ctrl+DownArrow", "Ctrl+S" ; Rotate camera to look downwards hotkey.camera.rotate.cw = "Ctrl+LeftArrow", "Ctrl+A", Q ; Rotate camera clockwise around terrain hotkey.camera.rotate.ccw = "Ctrl+RightArrow", "Ctrl+D", E ; Rotate camera anticlockwise around terrain hotkey.camera.rotate.wheel.cw = "Shift+WheelUp", MouseX1 ; Rotate camera clockwise around terrain (stepped control) hotkey.camera.rotate.wheel.ccw = "Shift+WheelDown", MouseX2 ; Rotate camera anticlockwise around terrain (stepped control) hotkey.camera.pan = MouseMiddle, ForwardSlash ; Enable scrolling by moving mouse hotkey.camera.left = A, LeftArrow ; Scroll or rotate left hotkey.camera.right = D, RightArrow ; Scroll or rotate right hotkey.camera.up = W, UpArrow ; Scroll or rotate up/forwards hotkey.camera.down = S, DownArrow ; Scroll or rotate down/backwards ; > CONSOLE SETTINGS hotkey.console.toggle = BackQuote, F9 ; Open/close console hotkey.console.copy = "Ctrl+C" ; Copy from console to clipboard hotkey.console.paste = "Ctrl+V" ; Paste clipboard to console +hotkey.console.cut = "Ctrl+X" ; Cut the selected text and copy it to the clipboard ; > ENTITY SELECTION hotkey.selection.add = Shift ; Add units to selection hotkey.selection.remove = Ctrl ; Remove units from selection hotkey.selection.idle = Period ; Select next idle unit hotkey.selection.offscreen = Alt ; Include offscreen units in selection hotkey.selection.group.select.0 = 0 hotkey.selection.group.save.0 = "Ctrl+0" hotkey.selection.group.add.0 = "Shift+0" hotkey.selection.group.select.1 = 1 hotkey.selection.group.save.1 = "Ctrl+1" hotkey.selection.group.add.1 = "Shift+1" hotkey.selection.group.select.2 = 2 hotkey.selection.group.save.2 = "Ctrl+2" hotkey.selection.group.add.2 = "Shift+2" hotkey.selection.group.select.3 = 3 hotkey.selection.group.save.3 = "Ctrl+3" hotkey.selection.group.add.3 = "Shift+3" hotkey.selection.group.select.4 = 4 hotkey.selection.group.save.4 = "Ctrl+4" hotkey.selection.group.add.4 = "Shift+4" hotkey.selection.group.select.5 = 5 hotkey.selection.group.save.5 = "Ctrl+5" hotkey.selection.group.add.5 = "Shift+5" hotkey.selection.group.select.6 = 6 hotkey.selection.group.save.6 = "Ctrl+6" hotkey.selection.group.add.6 = "Shift+6" hotkey.selection.group.select.7 = 7 hotkey.selection.group.save.7 = "Ctrl+7" hotkey.selection.group.add.7 = "Shift+7" hotkey.selection.group.select.8 = 8 hotkey.selection.group.save.8 = "Ctrl+8" hotkey.selection.group.add.8 = "Shift+8" hotkey.selection.group.select.9 = 9 hotkey.selection.group.save.9 = "Ctrl+9" hotkey.selection.group.add.9 = "Shift+9" ; > SESSION CONTROLS hotkey.session.kill = Delete ; Destroy selected units hotkey.session.garrison = Ctrl ; Modifier to garrison when clicking on building hotkey.session.queue = Shift ; Modifier to queue unit orders instead of replacing hotkey.session.batchtrain = Shift ; Modifier to train units in batches hotkey.session.deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting hotkey.session.rotate.cw = RightBracket ; Rotate building placement preview clockwise hotkey.session.rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise hotkey.timewarp.fastforward = Space hotkey.timewarp.rewind = Backspace ; > OVERLAY KEYS hotkey.fps.toggle = "Shift+F" ; Toggle frame counter hotkey.session.devcommands.toggle = "Shift+D" ; Toggle developer commands panel hotkey.session.gui.toggle = "G" ; Toggle visibility of session GUI hotkey.menu.toggle = "F10" ; Toggle in-game menu ; > HOTKEYS ONLY hotkey.chat = Return ; Toggle chat window +; > GUI TEXTBOX HOTKEYS +hotkey.text.delete.word.left = "Ctrl+Backspace" ; Used in text input boxes to delete word to the left of cursor +hotkey.text.delete.word.right = "Ctrl+Del" ; Used in text input boxes to delete word to the right of cursor +hotkey.text.move.word.left = "Ctrl+LeftArrow" ; Move cursor to start of word to the left of cursor +hotkey.text.move.word.right = "Ctrl+RightArrow" ; Move cursor to start of word to the left of cursor + ; > PROFILER hotkey.profile.toggle = "F11" ; Enable/disable real-time profiler hotkey.profile.save = "Shift+F11" ; Save current profiler data to logs/profile.txt ; EXPERIMENTAL: joystick/gamepad settings joystick.enable = false joystick.deadzone = 8192 joystick.camera.pan.x = 0 joystick.camera.pan.y = 1 joystick.camera.rotate.x = 3 joystick.camera.rotate.y = 2 joystick.camera.zoom.in = 5 joystick.camera.zoom.out = 4