Index: ps/trunk/source/ps/KeyName.h =================================================================== --- ps/trunk/source/ps/KeyName.h (revision 8443) +++ ps/trunk/source/ps/KeyName.h (revision 8444) @@ -1,46 +1,47 @@ /* 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 . */ #ifndef INCLUDED_KEYNAME #define INCLUDED_KEYNAME // Need SDLK_* enum values. #include "lib/external_libraries/sdl.h" class CStr8; extern void InitKeyNameMap(); extern CStr8 FindKeyName( int keycode ); extern int FindKeyCode( const CStr8& keyname ); enum { // 'Keycodes' for the mouse buttons MOUSE_LEFT = SDLK_LAST + SDL_BUTTON_LEFT, MOUSE_RIGHT = SDLK_LAST + SDL_BUTTON_RIGHT, MOUSE_MIDDLE = SDLK_LAST + SDL_BUTTON_MIDDLE, MOUSE_WHEELUP = SDLK_LAST + SDL_BUTTON_WHEELUP, MOUSE_WHEELDOWN = SDLK_LAST + SDL_BUTTON_WHEELDOWN, // 'Keycodes' for the unified modifier keys UNIFIED_SHIFT, UNIFIED_CTRL, UNIFIED_ALT, UNIFIED_META, - UNIFIED_SUPER + UNIFIED_SUPER, + UNIFIED_LAST }; #endif // #ifndef INCLUDED_KEYNAME Index: ps/trunk/source/ps/Hotkey.cpp =================================================================== --- ps/trunk/source/ps/Hotkey.cpp (revision 8443) +++ ps/trunk/source/ps/Hotkey.cpp (revision 8444) @@ -1,624 +1,368 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 . */ #include "precompiled.h" #include "Hotkey.h" #include "lib/input.h" #include "ConfigDB.h" #include "CLogger.h" #include "CConsole.h" #include "CStr.h" #include "ps/Globals.h" #include "KeyName.h" -extern CConsole* g_Console; +static bool unified[UNIFIED_LAST - UNIFIED_SHIFT]; -static bool unified[5]; - -/* SDL-type */ - -struct SHotkeyMapping +struct SKey { - int mapsTo; - bool negation; - std::vector requires; - SHotkeyMapping() : mapsTo(-1) {} + SDLKey code; // keycode or MOUSE_ or UNIFIED_ value + bool negated; // whether the key must be pressed (false) or unpressed (true) }; -typedef std::vector KeyMapping; - -/** - * HK_MAX_KEYCODES: Global maximum number of keycodes, including our "fake" keycodes for - * mouse buttons and unified modifiers. - */ -const int HK_MAX_KEYCODES = UNIFIED_SUPER + 1; - -// A mapping of keycodes onto sets of SDL event codes -static KeyMapping hotkeyMap[HK_MAX_KEYCODES]; - -// An array of the status of virtual keys -bool hotkeys[HOTKEY_LAST]; - - -struct SHotkeyInfo +// Hotkey data associated with an externally-specified 'primary' keycode +struct SHotkeyMapping { - int code; - const char* name; - int defaultmapping1, defaultmapping2; + CStr name; // name of the hotkey + bool negated; // whether the primary key must be pressed (false) or unpressed (true) + std::vector requires; // list of non-primary keys that must also be active }; -// Will phase out the default shortcuts at sometime in the near future -// (or, failing that, will update them so they can do the tricky stuff -// the config file can.) - -static SHotkeyInfo hotkeyInfo[] = -{ - { HOTKEY_EXIT, "exit", SDLK_ESCAPE, 0 }, - { HOTKEY_SCREENSHOT, "screenshot", SDLK_PRINT, 0 }, - { HOTKEY_BIGSCREENSHOT, "bigscreenshot", 0, 0 }, - { HOTKEY_WIREFRAME, "wireframe", SDLK_w, 0 }, - { HOTKEY_TOGGLEFULLSCREEN, "togglefullscreen", 0, 0 }, - { HOTKEY_CAMERA_RESET, "camera.reset", 0, 0 }, - { HOTKEY_CAMERA_FOLLOW, "camera.follow", 0, 0 }, - { HOTKEY_CAMERA_ZOOM_IN, "camera.zoom.in", SDLK_PLUS, SDLK_KP_PLUS }, - { HOTKEY_CAMERA_ZOOM_OUT, "camera.zoom.out", SDLK_MINUS, SDLK_KP_MINUS }, - { HOTKEY_CAMERA_ZOOM_WHEEL_IN, "camera.zoom.wheel.in", MOUSE_WHEELUP, 0 }, - { HOTKEY_CAMERA_ZOOM_WHEEL_OUT, "camera.zoom.wheel.out", MOUSE_WHEELDOWN, 0 }, - { HOTKEY_CAMERA_ROTATE_CW, "camera.rotate.cw", 0, 0 }, - { HOTKEY_CAMERA_ROTATE_CCW, "camera.rotate.ccw", 0, 0 }, - { HOTKEY_CAMERA_ROTATE_UP, "camera.rotate.up", 0, 0 }, - { HOTKEY_CAMERA_ROTATE_DOWN, "camera.rotate.down", 0, 0 }, - { HOTKEY_CAMERA_ROTATE_WHEEL_CW, "camera.rotate.wheel.cw", 0, 0 }, - { HOTKEY_CAMERA_ROTATE_WHEEL_CCW, "camera.rotate.wheel.ccw", 0, 0 }, - { HOTKEY_CAMERA_PAN, "camera.pan", MOUSE_MIDDLE, 0 }, - { HOTKEY_CAMERA_PAN_KEYBOARD, "camera.pan.keyboard", 0, 0 }, - { HOTKEY_CAMERA_LEFT, "camera.left", SDLK_LEFT, 0 }, - { HOTKEY_CAMERA_RIGHT, "camera.right", SDLK_RIGHT, 0 }, - { HOTKEY_CAMERA_UP, "camera.up", SDLK_UP, 0 }, - { HOTKEY_CAMERA_DOWN, "camera.down", SDLK_DOWN, 0 }, - { HOTKEY_CAMERA_CINEMA_ADD, "camera.cinema.add", SDLK_l, 0 }, - { HOTKEY_CAMERA_CINEMA_DELETE, "camera.cinema.delete", SDLK_u, 0 }, - { HOTKEY_CAMERA_CINEMA_DELETE_ALL, "camera.cinema.delete.all", SDLK_r, 0 }, - { HOTKEY_CAMERA_CINEMA_QUEUE, "camera.cinema.write", SDLK_i, 0}, - { HOTKEY_CONSOLE_TOGGLE, "console.toggle", SDLK_F1, 0 }, - { HOTKEY_CONSOLE_COPY, "console.copy", 0, 0 }, - { HOTKEY_CONSOLE_PASTE, "console.paste", 0, 0 }, - { HOTKEY_SELECTION_ADD, "selection.add", SDLK_LSHIFT, SDLK_RSHIFT }, - { HOTKEY_SELECTION_REMOVE, "selection.remove", SDLK_LCTRL, SDLK_RCTRL }, - { HOTKEY_SELECTION_GROUP_0, "selection.group.0", SDLK_0, 0, }, - { HOTKEY_SELECTION_GROUP_1, "selection.group.1", SDLK_1, 0, }, - { HOTKEY_SELECTION_GROUP_2, "selection.group.2", SDLK_2, 0, }, - { HOTKEY_SELECTION_GROUP_3, "selection.group.3", SDLK_3, 0, }, - { HOTKEY_SELECTION_GROUP_4, "selection.group.4", SDLK_4, 0, }, - { HOTKEY_SELECTION_GROUP_5, "selection.group.5", SDLK_5, 0, }, - { HOTKEY_SELECTION_GROUP_6, "selection.group.6", SDLK_6, 0, }, - { HOTKEY_SELECTION_GROUP_7, "selection.group.7", SDLK_7, 0, }, - { HOTKEY_SELECTION_GROUP_8, "selection.group.8", SDLK_8, 0, }, - { HOTKEY_SELECTION_GROUP_9, "selection.group.9", SDLK_9, 0, }, - { HOTKEY_SELECTION_GROUP_10, "selection.group.10", 0, 0, }, - { HOTKEY_SELECTION_GROUP_11, "selection.group.11", 0, 0, }, - { HOTKEY_SELECTION_GROUP_12, "selection.group.12", 0, 0, }, - { HOTKEY_SELECTION_GROUP_13, "selection.group.13", 0, 0, }, - { HOTKEY_SELECTION_GROUP_14, "selection.group.14", 0, 0, }, - { HOTKEY_SELECTION_GROUP_15, "selection.group.15", 0, 0, }, - { HOTKEY_SELECTION_GROUP_16, "selection.group.16", 0, 0, }, - { HOTKEY_SELECTION_GROUP_17, "selection.group.17", 0, 0, }, - { HOTKEY_SELECTION_GROUP_18, "selection.group.18", 0, 0, }, - { HOTKEY_SELECTION_GROUP_19, "selection.group.19", 0, 0, }, - { HOTKEY_SELECTION_GROUP_ADD, "selection.group.add", SDLK_LSHIFT, SDLK_RSHIFT }, - { HOTKEY_SELECTION_GROUP_SAVE, "selection.group.save", SDLK_LCTRL, SDLK_RCTRL }, - { HOTKEY_SELECTION_GROUP_SNAP, "selection.group.snap", SDLK_LALT, SDLK_RALT }, - { HOTKEY_SELECTION_SNAP, "selection.snap", SDLK_HOME, 0 }, - { HOTKEY_ORDER_QUEUE, "order.queue", SDLK_LSHIFT, SDLK_RSHIFT }, - { HOTKEY_CONTEXTORDER_NEXT, "contextorder.next", SDLK_RIGHTBRACKET, 0 }, - { HOTKEY_CONTEXTORDER_PREVIOUS, "contextorder.previous", SDLK_LEFTBRACKET, 0 }, - { HOTKEY_HIGHLIGHTALL, "highlightall", SDLK_o, 0 }, - { HOTKEY_PROFILE_TOGGLE, "profile.toggle", SDLK_F11, 0 }, - { HOTKEY_PROFILE_SAVE, "profile.save", 0, 0 }, - { HOTKEY_PLAYMUSIC, "playmusic", SDLK_p, 0 }, - { HOTKEY_PAUSE, "pause", SDLK_PAUSE, 0 }, - { HOTKEY_SPEED_INCREASE, "speed.increase", 0, 0 }, - { HOTKEY_SPEED_DECREASE, "speed.decrease", 0, 0 }, - { HOTKEY_KILL, "killUnit", 0, 0 }, - { HOTKEY_CHAT, "chat", 0, 0 } -}; +typedef std::vector KeyMapping; -/* SDL-type ends */ +// A mapping of keycodes onto the hotkeys that are associated with that key. +// (A hotkey triggered by a combination of multiple keys will be in this map +// multiple times.) +static std::map g_HotkeyMap; -/* GUI-type */ +// The current pressed status of hotkeys +std::map g_HotkeyStatus; -struct SHotkeyMappingGui +// Look up each key binding in the config file and set the mappings for +// all key combinations that trigger it. +static void LoadConfigBindings() { - CStr mapsTo; - bool negation; - std::vector requires; - SHotkeyMappingGui() : mapsTo(-1) {} -}; - -typedef std::vector GuiMapping; - -// A mapping of keycodes onto sets of hotkey name strings (e.g. '[hotkey.]camera.reset') -static GuiMapping hotkeyMapGui[HK_MAX_KEYCODES]; - -typedef std::vector GuiObjectList; // A list of GUI objects -typedef std::map GuiHotkeyMap; // A mapping of name strings to lists of GUI objects that they trigger + std::vector > bindings = g_ConfigDB.GetValuesWithPrefix( CFG_USER, CStr( "hotkey." )); -static GuiHotkeyMap guiHotkeyMap; + CParser multikeyParser; + multikeyParser.InputTaskType( "multikey", "<[~$arg(_negate)]$value_+_>_[~$arg(_negate)]$value" ); -// Look up a key binding in the config file and set the mappings for -// all key combinations that trigger it. -static void setBindings( const CStr& hotkeyName, int integerMapping = -1 ) -{ - CConfigValueSet* binding = g_ConfigDB.GetValues( CFG_USER, CStr( "hotkey." ) + hotkeyName ); - if( binding ) + for( std::vector >::iterator bindingsIt = bindings.begin(); bindingsIt != bindings.end(); ++bindingsIt ) { - int mapping; - - CConfigValueSet::iterator it; - CParser multikeyParser; - multikeyParser.InputTaskType( "multikey", "<[!$arg(_negate)][~$arg(_negate)]$value_+_>_[!$arg(_negate)][~$arg(_negate)]$value" ); + std::string hotkeyName = bindingsIt->first.substr(7); // strip the "hotkey." prefix - // Iterate through the bindings for this event... - - for( it = binding->begin(); it != binding->end(); it++ ) + for( CConfigValueSet::iterator it = bindingsIt->second.begin(); it != bindingsIt->second.end(); ++it ) { std::string hotkey; if( it->GetString( hotkey ) ) { - std::vector keyCombination; + std::vector keyCombination; CParserLine multikeyIdentifier; multikeyIdentifier.ParseString( multikeyParser, hotkey ); // Iterate through multiple-key bindings (e.g. Ctrl+I) bool negateNext = false; for( size_t t = 0; t < multikeyIdentifier.GetArgCount(); t++ ) { - + if( multikeyIdentifier.GetArgString( (int)t, hotkey ) ) { if( hotkey == "_negate" ) { negateNext = true; continue; } // Attempt decode as key name - mapping = FindKeyCode( hotkey ); + int mapping = FindKeyCode( hotkey ); // Attempt to decode as a negation of a keyname // Yes, it's going a bit far, perhaps. // Too powerful for most uses, probably. // However, it got some hardcoding out of the engine. // Thus it makes me happy. - + if( !mapping ) - if( !it->GetInt( mapping ) ) // Attempt decode as key code - { - LOG(CLogger::Warning, L"hotkey", L"Couldn't map '%hs'", hotkey.c_str() ); - continue; - } + { + LOGWARNING(L"Hotkey mapping used invalid key '%hs'", hotkey.c_str() ); + continue; + } - if( negateNext ) mapping |= HOTKEY_NEGATION_FLAG; + SKey key = { (SDLKey)mapping, negateNext }; + keyCombination.push_back(key); negateNext = false; - keyCombination.push_back( mapping ); } } - - std::vector::iterator itKey, itKey2; - SHotkeyMapping bindCode; - SHotkeyMappingGui bindName; + std::vector::iterator itKey, itKey2; for( itKey = keyCombination.begin(); itKey != keyCombination.end(); itKey++ ) { - bindName.mapsTo = hotkeyName; - bindName.negation = ( ( *itKey & HOTKEY_NEGATION_FLAG ) ? true : false ); - bindName.requires.clear(); - if( integerMapping != -1 ) - { - bindCode.mapsTo = integerMapping; - bindCode.negation = ( ( *itKey & HOTKEY_NEGATION_FLAG ) ? true : false ); - bindCode.requires.clear(); - } + SHotkeyMapping bindCode; + + bindCode.name = hotkeyName; + bindCode.negated = itKey->negated; + for( itKey2 = keyCombination.begin(); itKey2 != keyCombination.end(); itKey2++ ) { // Push any auxiliary keys. if( itKey != itKey2 ) - { - bindName.requires.push_back( *itKey2 ); - if( integerMapping != -1 ) - bindCode.requires.push_back( *itKey2 ); - } + bindCode.requires.push_back( *itKey2 ); } - hotkeyMapGui[*itKey & ~HOTKEY_NEGATION_FLAG].push_back( bindName ); - if( integerMapping != -1 ) - hotkeyMap[*itKey & ~HOTKEY_NEGATION_FLAG].push_back( bindCode ); + g_HotkeyMap[itKey->code].push_back( bindCode ); } } } } - else if( integerMapping != -1 ) - { - SHotkeyMapping bind[2]; - bind[0].mapsTo = integerMapping; - bind[1].mapsTo = integerMapping; - bind[0].requires.clear(); - bind[1].requires.clear(); - bind[0].negation = false; - bind[1].negation = false; - hotkeyMap[ hotkeyInfo[integerMapping].defaultmapping1 ].push_back( bind[0] ); - if( hotkeyInfo[integerMapping].defaultmapping2 ) - hotkeyMap[ hotkeyInfo[integerMapping].defaultmapping2 ].push_back( bind[1] ); - } } + void LoadHotkeys() { InitKeyNameMap(); - for(int i = 0; i < HOTKEY_LAST; i++ ) - setBindings( hotkeyInfo[i].name, i ); - + LoadConfigBindings(); + // Set up the state of the hotkeys given no key is down. // i.e. find those hotkeys triggered by all negations. - std::vector::iterator it; - std::vector::iterator j; - bool allNegated; - - for(int i = 1; i < HK_MAX_KEYCODES; i++ ) + for( std::map::iterator mapIt = g_HotkeyMap.begin(); mapIt != g_HotkeyMap.end(); ++mapIt ) { - for( it = hotkeyMap[i].begin(); it != hotkeyMap[i].end(); it++ ) + KeyMapping& hotkeyMap = mapIt->second; + + for( std::vector::iterator it = hotkeyMap.begin(); it != hotkeyMap.end(); it++ ) { - if( !it->negation ) + if( !it->negated ) continue; - allNegated = true; + bool allNegated = true; - for( j = it->requires.begin(); j != it->requires.end(); j++ ) - if( !( *j & HOTKEY_NEGATION_FLAG ) ) + for( std::vector::iterator j = it->requires.begin(); j != it->requires.end(); j++ ) + if( !j->negated ) allNegated = false; - debug_assert((size_t)it->mapsTo < ARRAY_SIZE(hotkeys)); - if( allNegated ) - hotkeys[it->mapsTo] = true; + g_HotkeyStatus[it->name] = true; } } } -void HotkeyRegisterGuiObject( const CStr& objName, const CStr& hotkeyName ) -{ - GuiObjectList& boundTo = guiHotkeyMap[hotkeyName]; - if( boundTo.empty() ) - { - // Load keybindings from the config file - setBindings( hotkeyName ); - } - boundTo.push_back( objName ); -} - InReaction HotkeyInputHandler( const SDL_Event_* ev ) { int keycode = 0; switch( ev->ev.type ) { case SDL_KEYDOWN: case SDL_KEYUP: keycode = (int)ev->ev.key.keysym.sym; break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: if ((int)ev->ev.button.button <= SDL_BUTTON_WHEELDOWN) { keycode = SDLK_LAST + (int)ev->ev.button.button; break; } // fall through default: return IN_PASS; } // Somewhat hackish: // Create phantom 'unified-modifier' events when left- or right- modifier keys are pressed // Just send them to this handler; don't let the imaginary event codes leak back to real SDL. SDL_Event_ phantom; phantom.ev.type = ( ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) ) ? SDL_KEYDOWN : SDL_KEYUP; if( ( keycode == SDLK_LSHIFT ) || ( keycode == SDLK_RSHIFT ) ) { phantom.ev.key.keysym.sym = (SDLKey)UNIFIED_SHIFT; unified[0] = ( phantom.ev.type == SDL_KEYDOWN ); HotkeyInputHandler( &phantom ); } else if( ( keycode == SDLK_LCTRL ) || ( keycode == SDLK_RCTRL ) ) { phantom.ev.key.keysym.sym = (SDLKey)UNIFIED_CTRL; unified[1] = ( phantom.ev.type == SDL_KEYDOWN ); HotkeyInputHandler( &phantom ); } else if( ( keycode == SDLK_LALT ) || ( keycode == SDLK_RALT ) ) { phantom.ev.key.keysym.sym = (SDLKey)UNIFIED_ALT; unified[2] = ( phantom.ev.type == SDL_KEYDOWN ); HotkeyInputHandler( &phantom ); } else if( ( keycode == SDLK_LMETA ) || ( keycode == SDLK_RMETA ) ) { phantom.ev.key.keysym.sym = (SDLKey)UNIFIED_META; unified[3] = ( phantom.ev.type == SDL_KEYDOWN ); HotkeyInputHandler( &phantom ); } else if( ( keycode == SDLK_LSUPER ) || ( keycode == SDLK_RSUPER ) ) { phantom.ev.key.keysym.sym = (SDLKey)UNIFIED_SUPER; unified[4] = ( phantom.ev.type == SDL_KEYDOWN ); HotkeyInputHandler( &phantom ); } + // Check whether we have any hotkeys registered for this particular keycode + if( g_HotkeyMap.find(keycode) == g_HotkeyMap.end() ) + return( IN_PASS ); + // Inhibit the dispatch of hotkey events caused by printable or control keys // while the console is up. (But allow multiple-key - 'Ctrl+F' events, and whatever // key toggles the console.) - bool consoleCapture = false, isCapturable; + bool consoleCapture = false; if( g_Console->IsActive() && ( ( keycode == 8 ) || ( keycode == 9 ) || ( keycode == 13 ) || /* Editing */ ( ( keycode >= 32 ) && ( keycode < 273 ) ) || /* Printable (<128), 'World' (<256) */ ( ( keycode >= 273 ) && ( keycode < 282 ) && /* Numeric keypad (<273), navigation */ ( keycode != SDLK_INSERT ) ) ) ) /* keys (<282) except insert */ consoleCapture = true; - std::vector::iterator it; - std::vector::iterator itGUI; - - SDL_Event hotkeyNotification; - // Here's an interesting bit: // If you have an event bound to, say, 'F', and another to, say, 'Ctrl+F', pressing // 'F' while control is down would normally fire off both. // To avoid this, set the modifier keys for /all/ events this key would trigger // (Ctrl, for example, is both group-save and bookmark-save) // but only send a HotkeyDown event for the event with bindings most precisely // matching the conditions (i.e. the event with the highest number of auxiliary // keys, providing they're all down) bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ); // -- KEYDOWN SECTION -- - // SDL-events bit - - size_t closestMap = 0; // avoid "uninitialized" warning + const char* closestMapName = NULL; size_t closestMapMatch = 0; - for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ ) + for( std::vector::iterator it = g_HotkeyMap[keycode].begin(); it < g_HotkeyMap[keycode].end(); it++ ) { // If a key has been pressed, and this event triggers on it's release, skip it. // Similarly, if the key's been released and the event triggers on a keypress, skip it. - if( it->negation == typeKeyDown ) + if( it->negated == typeKeyDown ) continue; // Check to see if all auxiliary keys are down - std::vector::iterator itKey; bool accept = true; - isCapturable = true; + bool isCapturable = true; - for( itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ ) + for( std::vector::iterator itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ ) { - int keyCode = *itKey & ~HOTKEY_NEGATION_FLAG; // Clear the negation-modifier bit - bool rqdState = ( *itKey & HOTKEY_NEGATION_FLAG ) == 0; + bool rqdState = !itKey->negated; - // debug_assert( !rqdState ); - - if( keyCode < SDLK_LAST ) - { - if( g_keys[keyCode] != rqdState ) accept = false; - } - else if( keyCode < UNIFIED_SHIFT ) + if( (int)itKey->code < SDLK_LAST ) { - if( g_mouse_buttons[keyCode-SDLK_LAST] != rqdState ) accept = false; + if( g_keys[itKey->code] != rqdState ) accept = false; } - else if( (size_t)(keyCode-UNIFIED_SHIFT) < ARRAY_SIZE(unified) ) + else if( (int)itKey->code < UNIFIED_SHIFT ) { - if( unified[keyCode-UNIFIED_SHIFT] != rqdState ) accept = false; + if( g_mouse_buttons[itKey->code - SDLK_LAST] != rqdState ) accept = false; } - else + else if( (int)itKey->code < UNIFIED_LAST ) { - debug_printf(L"keyCode = %i\n", keyCode); - debug_warn(L"keyCode out of range in GUI hotkey requirements"); + if( unified[itKey->code - UNIFIED_SHIFT] != rqdState ) accept = false; } // If this event requires a multiple keypress (with the exception // of shift+key combinations) the console won't inhibit it. - if( rqdState && ( *itKey != SDLK_RSHIFT ) && ( *itKey != SDLK_LSHIFT ) ) + if( rqdState && ( itKey->code != SDLK_RSHIFT ) && ( itKey->code != SDLK_LSHIFT ) ) isCapturable = false; } - if( it->mapsTo == HOTKEY_CONSOLE_TOGGLE ) isCapturable = false; // Because that would be silly. + if( it->name == "console.toggle" ) isCapturable = false; // Because that would be silly. - debug_assert((size_t)it->mapsTo < ARRAY_SIZE(hotkeys)); - if( accept && !( isCapturable && consoleCapture ) ) { - hotkeys[it->mapsTo] = true; + g_HotkeyStatus[it->name] = true; if( it->requires.size() >= closestMapMatch ) { // Only if it's a more precise match, and it either isn't capturable or the console won't capture it. - closestMap = it->mapsTo; + closestMapName = it->name.c_str(); closestMapMatch = it->requires.size() + 1; } } } if( closestMapMatch ) { + SDL_Event hotkeyNotification; hotkeyNotification.type = SDL_HOTKEYDOWN; - hotkeyNotification.user.code = (int)closestMap; + hotkeyNotification.user.data1 = const_cast(closestMapName); SDL_PushEvent( &hotkeyNotification ); } - // GUI bit... could do with some optimization later. - - CStr closestMapName = -1; - closestMapMatch = 0; - - for( itGUI = hotkeyMapGui[keycode].begin(); itGUI != hotkeyMapGui[keycode].end(); itGUI++ ) - { - // If a key has been pressed, and this event triggers on it's release, skip it. - // Similarly, if the key's been released and the event triggers on a keypress, skip it. - if( itGUI->negation == typeKeyDown ) - continue; - - // Check to see if all auxiliary keys are down - - std::vector::iterator itKey; - bool accept = true; - isCapturable = true; - - for( itKey = itGUI->requires.begin(); itKey != itGUI->requires.end(); itKey++ ) - { - int keyCode = *itKey & ~HOTKEY_NEGATION_FLAG; // Clear the negation-modifier bit - bool rqdState = ( *itKey & HOTKEY_NEGATION_FLAG ) == 0; - - if( keyCode < SDLK_LAST ) - { - if( g_keys[keyCode] != rqdState ) accept = false; - } - else if( keyCode < UNIFIED_SHIFT ) - { - if( g_mouse_buttons[keyCode-SDLK_LAST] != rqdState ) accept = false; - } - else if( (size_t)(keyCode-UNIFIED_SHIFT) < ARRAY_SIZE(unified) ) - { - if( unified[keyCode-UNIFIED_SHIFT] != rqdState ) accept = false; - } - else - { - debug_printf(L"keyCode = %i\n", keyCode); - debug_warn(L"keyCode out of range in GUI hotkey requirements"); - } - - // If this event requires a multiple keypress (with the exception - // of shift+key combinations) the console won't inhibit it. - if( rqdState && ( *itKey != SDLK_RSHIFT ) && ( *itKey != SDLK_LSHIFT ) ) - isCapturable = false; - } - - if( accept && !( isCapturable && consoleCapture ) ) - { - if( itGUI->requires.size() >= closestMapMatch ) - { - closestMapName = itGUI->mapsTo; - closestMapMatch = itGUI->requires.size() + 1; - } - } - } - // GUI-objects bit - // This fragment is an obvious candidate for rewriting when speed becomes an issue. - - if( closestMapMatch ) - { - GuiHotkeyMap::iterator map_it; - GuiObjectList::iterator obj_it; - map_it = guiHotkeyMap.find( closestMapName ); - if( map_it != guiHotkeyMap.end() ) - { - GuiObjectList& targets = map_it->second; - for( obj_it = targets.begin(); obj_it != targets.end(); obj_it++ ) - { - hotkeyNotification.type = SDL_GUIHOTKEYPRESS; - hotkeyNotification.user.data1 = &(*obj_it); - SDL_PushEvent( &hotkeyNotification ); - } - } - } // -- KEYUP SECTION -- - for( it = hotkeyMap[keycode].begin(); it < hotkeyMap[keycode].end(); it++ ) + for( std::vector::iterator it = g_HotkeyMap[keycode].begin(); it < g_HotkeyMap[keycode].end(); it++ ) { // If it's a keydown event, won't cause HotKeyUps in anything that doesn't // use this key negated => skip them // If it's a keyup event, won't cause HotKeyUps in anything that does use // this key negated => skip them too. - if( it->negation != typeKeyDown ) + if( it->negated != typeKeyDown ) continue; // Check to see if all auxiliary keys are down - std::vector::iterator itKey; bool accept = true; - for( itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ ) + for( std::vector::iterator itKey = it->requires.begin(); itKey != it->requires.end(); itKey++ ) { - if( *itKey < SDLK_LAST ) + bool rqdState = !itKey->negated; + + if( (int)itKey->code < SDLK_LAST ) { - if( !g_keys[*itKey] ) accept = false; + if( g_keys[itKey->code] != rqdState ) accept = false; } - else if( *itKey < UNIFIED_SHIFT ) + else if( (int)itKey->code < UNIFIED_SHIFT ) { - if( !g_mouse_buttons[(*itKey)-SDLK_LAST] ) accept = false; + if( g_mouse_buttons[itKey->code - SDLK_LAST] != rqdState ) accept = false; } - else if( *itKey < HOTKEY_NEGATION_FLAG ) + else if( (int)itKey->code < UNIFIED_LAST ) { - if( !unified[(*itKey)-UNIFIED_SHIFT] ) accept = false; + if( unified[itKey->code - UNIFIED_SHIFT] != rqdState ) accept = false; } } - debug_assert((size_t)it->mapsTo < ARRAY_SIZE(hotkeys)); - if( accept ) { - hotkeys[it->mapsTo] = false; + g_HotkeyStatus[it->name] = false; + SDL_Event hotkeyNotification; hotkeyNotification.type = SDL_HOTKEYUP; - hotkeyNotification.user.code = it->mapsTo; + hotkeyNotification.user.data1 = const_cast(it->name.c_str()); SDL_PushEvent( &hotkeyNotification ); } } return( IN_PASS ); } -CStr HotkeyGetName(int hotkey) -{ - if (hotkey < 0 || hotkey >= HOTKEY_LAST) - return ""; - return hotkeyInfo[hotkey].name; -} - -bool HotkeyRespondsTo(int hotkey, int sdlkey) -{ - for (KeyMapping::iterator it = hotkeyMap[sdlkey].begin(); it != hotkeyMap[sdlkey].end(); ++it) - if (it->mapsTo == hotkey) - return true; - return false; -} - - bool HotkeyIsPressed(const CStr& keyname) { - return hotkeys[FindKeyCode(keyname)]; + return g_HotkeyStatus[keyname]; } Index: ps/trunk/source/ps/ConfigDB.cpp =================================================================== --- ps/trunk/source/ps/ConfigDB.cpp (revision 8443) +++ ps/trunk/source/ps/ConfigDB.cpp (revision 8444) @@ -1,371 +1,401 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 . */ #include "precompiled.h" #include "Pyrogenesis.h" #include "Parser.h" #include "ConfigDB.h" #include "CLogger.h" #include "Filesystem.h" #include "scripting/ScriptingHost.h" +#include + typedef std::map TConfigMap; TConfigMap CConfigDB::m_Map[CFG_LAST]; CStrW CConfigDB::m_ConfigFile[CFG_LAST]; bool CConfigDB::m_UseVFS[CFG_LAST]; #define GET_NS_PRIVATE(cx, obj) (EConfigNamespace)((intptr_t)JS_GetPrivate(cx, obj) >> 1) namespace ConfigNamespace_JS { JSBool GetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; CStr propName = g_ScriptingHost.ValueToString(id); CConfigValue *val=g_ConfigDB.GetValue(cfgNs, propName); if (val) { JSString *js_str=JS_NewStringCopyN(cx, val->m_String.c_str(), val->m_String.size()); *vp = STRING_TO_JSVAL(js_str); } return JS_TRUE; } JSBool SetProperty( JSContext* cx, JSObject* obj, jsval id, jsval* vp ) { EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; CStr propName = g_ScriptingHost.ValueToString(id); CConfigValue *val=g_ConfigDB.CreateValue(cfgNs, propName); char *str; if (JS_ConvertArguments(cx, 1, vp, "s", &str)) { val->m_String=str; return JS_TRUE; } else return JS_FALSE; } JSClass Class = { "ConfigNamespace", JSCLASS_HAS_PRIVATE, JS_PropertyStub, JS_PropertyStub, GetProperty, SetProperty, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; JSBool Construct( JSContext* cx, JSObject* obj, uintN argc, jsval* UNUSED(argv), jsval* rval ) { if (argc != 0) return JS_FALSE; JSObject *newObj=JS_NewObject(cx, &Class, NULL, obj); *rval=OBJECT_TO_JSVAL(newObj); return JS_TRUE; } void SetNamespace(JSContext *cx, JSObject *obj, EConfigNamespace cfgNs) { JS_SetPrivate(cx, obj, (void *)((uintptr_t)cfgNs << 1)); // JS requires bottom bit = 0 } JSBool WriteFile( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* rval ) { EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; if (argc != 2) return JS_FALSE; JSBool useVFS; char *path; if (JS_ConvertArguments(cx, 2, argv, "bs", &useVFS, &path)) { JSBool res=g_ConfigDB.WriteFile(cfgNs, useVFS?true:false, CStrW(path)); *rval = BOOLEAN_TO_JSVAL(res); return JS_TRUE; } else return JS_FALSE; } JSBool Reload( JSContext* cx, JSObject* obj, uintN argc, jsval* UNUSED(argv), jsval* rval ) { if (argc != 0) return JS_FALSE; EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; JSBool ret=g_ConfigDB.Reload(cfgNs); *rval = BOOLEAN_TO_JSVAL(ret); return JS_TRUE; } JSBool SetFile( JSContext* cx, JSObject* obj, uintN argc, jsval* argv, jsval* UNUSED(rval) ) { if (argc != 0) return JS_FALSE; EConfigNamespace cfgNs = GET_NS_PRIVATE(cx, obj); if (cfgNs < 0 || cfgNs >= CFG_LAST) return JS_FALSE; JSBool useVFS; char *path; if (JS_ConvertArguments(cx, 2, argv, "bs", &useVFS, &path)) { g_ConfigDB.SetConfigFile(cfgNs, useVFS?true:false, CStrW(path)); return JS_TRUE; } else return JS_FALSE; } JSFunctionSpec Funcs[] = { { "writeFile", WriteFile, 2, 0, 0}, { "reload", Reload, 0, 0, 0}, { "setFile", SetFile, 2, 0, 0}, {0} }; }; namespace ConfigDB_JS { JSClass Class = { "ConfigDB", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub }; JSPropertySpec Props[] = { {0} }; JSFunctionSpec Funcs[] = { {0} }; JSBool Construct( JSContext* cx, JSObject* obj, uintN argc, jsval* UNUSED(argv), jsval* rval ) { if (argc != 0) return JS_FALSE; JSObject *newObj=JS_NewObject(cx, &Class, NULL, obj); *rval=OBJECT_TO_JSVAL(newObj); int flags=JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT; #define cfg_ns(_propname, _enum) STMT (\ JSObject *nsobj=g_ScriptingHost.CreateCustomObject("ConfigNamespace"); \ debug_assert(nsobj); \ ConfigNamespace_JS::SetNamespace(cx, nsobj, _enum); \ debug_assert(JS_DefineProperty(cx, newObj, _propname, OBJECT_TO_JSVAL(nsobj), NULL, NULL, flags)); ) cfg_ns("default", CFG_DEFAULT); cfg_ns("system", CFG_SYSTEM); cfg_ns("user", CFG_USER); cfg_ns("mod", CFG_MOD); #undef cfg_ns return JS_TRUE; } }; CConfigDB::CConfigDB() { g_ScriptingHost.DefineCustomObjectType(&ConfigDB_JS::Class, ConfigDB_JS::Construct, 0, ConfigDB_JS::Props, ConfigDB_JS::Funcs, NULL, NULL); g_ScriptingHost.DefineCustomObjectType(&ConfigNamespace_JS::Class, ConfigNamespace_JS::Construct, 0, NULL, ConfigNamespace_JS::Funcs, NULL, NULL); JSObject *js_ConfigDB=g_ScriptingHost.CreateCustomObject("ConfigDB"); g_ScriptingHost.SetGlobal("g_ConfigDB", OBJECT_TO_JSVAL(js_ConfigDB)); } CConfigValue *CConfigDB::GetValue(EConfigNamespace ns, const CStr& name) { CConfigValueSet* values = GetValues( ns, name ); if( !values ) return( NULL ); return &( (*values)[0] ); } -CConfigValueSet *CConfigDB::GetValues(EConfigNamespace ns, const CStr& name ) +CConfigValueSet *CConfigDB::GetValues(EConfigNamespace ns, const CStr& name) { if (ns < 0 || ns >= CFG_LAST) { debug_warn(L"CConfigDB: Invalid ns value"); return NULL; } TConfigMap::iterator it = m_Map[CFG_COMMAND].find( name ); if( it != m_Map[CFG_COMMAND].end() ) return &( it->second ); for( int search_ns = ns; search_ns >= 0; search_ns-- ) { TConfigMap::iterator it = m_Map[search_ns].find(name); if (it != m_Map[search_ns].end()) return &( it->second ); } return( NULL ); } +std::vector > CConfigDB::GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix) +{ + std::vector > ret; + + if (ns < 0 || ns >= CFG_LAST) + { + debug_warn(L"CConfigDB: Invalid ns value"); + return ret; + } + + for (TConfigMap::iterator it = m_Map[CFG_COMMAND].begin(); it != m_Map[CFG_COMMAND].end(); ++it) + { + if (boost::algorithm::starts_with(it->first, prefix)) + ret.push_back(std::make_pair(it->first, it->second)); + } + + for (int search_ns = ns; search_ns >= 0; search_ns--) + { + for (TConfigMap::iterator it = m_Map[search_ns].begin(); it != m_Map[search_ns].end(); ++it) + { + if (boost::algorithm::starts_with(it->first, prefix)) + ret.push_back(std::make_pair(it->first, it->second)); + } + } + + return ret; +} + CConfigValue *CConfigDB::CreateValue(EConfigNamespace ns, const CStr& name) { if (ns < 0 || ns >= CFG_LAST) { debug_warn(L"CConfigDB: Invalid ns value"); return NULL; } CConfigValue *ret=GetValue(ns, name); if (ret) return ret; TConfigMap::iterator it=m_Map[ns].insert(m_Map[ns].begin(), make_pair(name, CConfigValueSet( 1 ))); return &(it->second[0]); } void CConfigDB::SetConfigFile(EConfigNamespace ns, bool useVFS, const CStrW& path) { if (ns < 0 || ns >= CFG_LAST) { debug_warn(L"CConfigDB: Invalid ns value"); return; } m_ConfigFile[ns]=path; m_UseVFS[ns]=useVFS; } bool CConfigDB::Reload(EConfigNamespace ns) { // Set up CParser CParser parser; CParserLine parserLine; parser.InputTaskType("Assignment", "_$ident_=<_[-$arg(_minus)]_$value_,>_[-$arg(_minus)]_$value[[;]$rest]"); parser.InputTaskType("CommentOrBlank", "_[;[$rest]]"); // Open file with VFS shared_ptr buffer; size_t buflen; { // Handle missing files quietly if (g_VFS->GetFileInfo(m_ConfigFile[ns], NULL) < 0) { LOGMESSAGE(L"Cannot find config file \"%ls\" - ignoring", m_ConfigFile[ns].c_str()); return false; } else { LOGMESSAGE(L"Loading config file \"%ls\"", m_ConfigFile[ns].c_str()); LibError ret = g_VFS->LoadFile(m_ConfigFile[ns], buffer, buflen); if (ret != INFO::OK) { LOGERROR(L"CConfigDB::Reload(): vfs_load for \"%ls\" failed: return was %ld", m_ConfigFile[ns].c_str(), ret); return false; } } } TConfigMap newMap; char *filebuf=(char *)buffer.get(); char *filebufend=filebuf+buflen; // Read file line by line char *next=filebuf-1; do { char *pos=next+1; next=(char *)memchr(pos, '\n', filebufend-pos); if (!next) next=filebufend; char *lend=next; if (*(lend-1) == '\r') lend--; // Send line to parser bool parseOk=parserLine.ParseString(parser, std::string(pos, lend)); // Get name and value from parser std::string name; std::string value; if (parseOk && parserLine.GetArgCount()>=2 && parserLine.GetArgString(0, name) && parserLine.GetArgString(1, value)) { // Add name and value to the map size_t argCount = parserLine.GetArgCount(); newMap[name].clear(); for( size_t t = 0; t < argCount; t++ ) { if( !parserLine.GetArgString( (int)t + 1, value ) ) continue; CConfigValue argument; argument.m_String = value; newMap[name].push_back( argument ); LOGMESSAGE(L"Loaded config string \"%hs\" = \"%hs\"", name.c_str(), value.c_str()); } } } while (next < filebufend); m_Map[ns].swap(newMap); return true; } bool CConfigDB::WriteFile(EConfigNamespace ns, bool useVFS, const CStrW& path) { debug_assert(useVFS); if (ns < 0 || ns >= CFG_LAST) { debug_warn(L"CConfigDB: Invalid ns value"); return false; } shared_ptr buf = io_Allocate(1*MiB); char* pos = (char*)buf.get(); TConfigMap &map=m_Map[ns]; for(TConfigMap::const_iterator it = map.begin(); it != map.end(); ++it) { pos += sprintf(pos, "%s = \"%s\"\n", it->first.c_str(), it->second[0].m_String.c_str()); } const size_t len = pos - (char*)buf.get(); LibError ret = g_VFS->CreateFile(path, buf, len); if(ret < 0) { LOGERROR(L"CConfigDB::WriteFile(): CreateFile \"%ls\" failed (error: %d)", path.c_str(), (int)ret); return false; } return true; } Index: ps/trunk/source/ps/CConsole.cpp =================================================================== --- ps/trunk/source/ps/CConsole.cpp (revision 8443) +++ ps/trunk/source/ps/CConsole.cpp (revision 8444) @@ -1,801 +1,803 @@ /* 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 . */ /* * Implements the in-game console with scripting support. */ #include "precompiled.h" #include #include "CConsole.h" #include "lib/ogl.h" #include "lib/res/graphics/unifont.h" #include "lib/sysdep/clipboard.h" #include "maths/MathUtil.h" #include "network/NetClient.h" #include "network/NetServer.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Font.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Pyrogenesis.h" #include "scripting/ScriptingHost.h" #define LOG_CATEGORY L"Console" CConsole* g_Console = 0; CConsole::CConsole() { m_bToggle = false; m_bVisible = false; m_fVisibleFrac = 0.0f; m_szBuffer = new wchar_t[CONSOLE_BUFFER_SIZE]; FlushBuffer(); m_iMsgHistPos = 1; m_charsPerPage=0; InsertMessage(L"[ 0 A.D. Console v0.12 ] type \"\\info\" for help"); InsertMessage(L""); if (FileExists(L"gui/text/help.txt")) { shared_ptr buf; size_t size; if ( g_VFS->LoadFile(L"gui/text/help.txt", buf, size) < 0 ) { LOG(CLogger::Error, LOG_CATEGORY, L"Help file not found for console"); return; } // TODO: read in text mode, or at least get rid of the \r\n somehow // TODO: maybe the help file should be UTF-8 - we assume it's iso-8859-1 here m_helpText = CStrW((const char*)buf.get()); } else { InsertMessage(L"No help file found."); } } CConsole::~CConsole() { m_deqMsgHistory.clear(); m_deqBufHistory.clear(); delete[] m_szBuffer; } void CConsole::SetSize(float X, float Y, float W, float H) { m_fX = X; m_fY = Y; m_fWidth = W; m_fHeight = H; } void CConsole::UpdateScreenSize(int w, int h) { float height = h * 0.6f; SetSize(0, h-height, (float)w, height); } void CConsole::ToggleVisible() { m_bToggle = true; m_bVisible = !m_bVisible; } void CConsole::SetVisible( bool visible ) { if( visible != m_bVisible ) m_bToggle = true; m_bVisible = visible; } void CConsole::FlushBuffer(void) { // Clear the buffer and set the cursor and length to 0 memset(m_szBuffer, '\0', sizeof(wchar_t) * CONSOLE_BUFFER_SIZE); m_iBufferPos = m_iBufferLength = 0; } void CConsole::ToLower(wchar_t* szMessage, size_t iSize) { size_t L = (size_t)wcslen(szMessage); if (L <= 0) return; if (iSize && iSize < L) L = iSize; for(size_t i = 0; i < L; i++) szMessage[i] = towlower(szMessage[i]); } void CConsole::Trim(wchar_t* szMessage, const wchar_t cChar, size_t iSize) { size_t L = wcslen(szMessage); if(!L) return; if (iSize && iSize < L) L = iSize; wchar_t szChar[2] = { cChar, 0 }; // Find the first point at which szChar does not // exist in the message size_t ofs = wcsspn(szMessage, szChar); if(ofs == 0) // no leading chars - we're done return; // move everything chars left, replacing leading cChar chars L -= ofs; memmove(szMessage, szMessage+ofs, L*sizeof(wchar_t)); for(ssize_t i = (ssize_t)L; i >= 0; i--) { szMessage[i] = '\0'; if (szMessage[i - 1] != cChar) break; } } void CConsole::Update(const float DeltaTime) { if(m_bToggle) { const float AnimateTime = .30f; const float Delta = DeltaTime / AnimateTime; if(m_bVisible) { m_fVisibleFrac += Delta; if(m_fVisibleFrac > 1.0f) { m_fVisibleFrac = 1.0f; m_bToggle = false; } } else { m_fVisibleFrac -= Delta; if(m_fVisibleFrac < 0.0f) { m_fVisibleFrac = 0.0f; m_bToggle = false; } } } } //Render Manager. void CConsole::Render() { if (! (m_bVisible || m_bToggle) ) return; CFont font(CONSOLE_FONT); font.Bind(); // animation: slide in from top of screen const float MaxY = m_fHeight; const float DeltaY = (1.0f - m_fVisibleFrac) * MaxY; glPushMatrix(); glTranslatef(m_fX, m_fY + DeltaY, 0.0f); //Move to window position glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); DrawWindow(); DrawHistory(); DrawBuffer(); glDisable(GL_BLEND); glPopMatrix(); } void CConsole::DrawWindow(void) { // TODO: Add texturing glDisable(GL_TEXTURE_2D); // Draw Background // Set the color to a translucent blue glColor4f(0.0f, 0.0f, 0.5f, 0.6f); glBegin(GL_QUADS); glVertex2f(0.0f, 0.0f); glVertex2f(m_fWidth-1.0f, 0.0f); glVertex2f(m_fWidth-1.0f, m_fHeight-1.0f); glVertex2f(0.0f, m_fHeight-1.0f); glEnd(); // Draw Border // Set the color to a translucent yellow glColor4f(0.5f, 0.5f, 0.0f, 0.6f); glBegin(GL_LINE_LOOP); glVertex2f(0.0f, 0.0f); glVertex2f(m_fWidth-1.0f, 0.0f); glVertex2f(m_fWidth-1.0f, m_fHeight-1.0f); glVertex2f(0.0f, m_fHeight-1.0f); glEnd(); if (m_fHeight > m_iFontHeight + 4) { glBegin(GL_LINES); glVertex2f(0.0f, (GLfloat)(m_iFontHeight + 4)); glVertex2f(m_fWidth, (GLfloat)(m_iFontHeight + 4)); glEnd(); } glEnable(GL_TEXTURE_2D); } void CConsole::DrawHistory(void) { int i = 1; std::deque::iterator Iter; //History iterator glPushMatrix(); glColor3f(1.0f, 1.0f, 1.0f); //Set color of text glTranslatef(9.0f, (float)m_iFontOffset, 0.0f); //move away from the border // Draw the text upside-down, because it's aligned with // the GUI (which uses the top-left as (0,0)) glScalef(1.0f, -1.0f, 1.0f); for (Iter = m_deqMsgHistory.begin(); Iter != m_deqMsgHistory.end() && (((i - m_iMsgHistPos + 1) * m_iFontHeight) < m_fHeight); Iter++) { if (i >= m_iMsgHistPos){ glTranslatef(0.0f, -(float)m_iFontHeight, 0.0f); glPushMatrix(); glwprintf(L"%ls", Iter->c_str()); glPopMatrix(); } i++; } glPopMatrix(); } //Renders the buffer to the screen. void CConsole::DrawBuffer(void) { if (m_fHeight < m_iFontHeight) return; glPushMatrix(); glColor3f(1.0f, 1.0f, 0.0f); glTranslatef(2.0f, (float)m_iFontOffset, 0); glScalef(1.0f, -1.0f, 1.0f); glwprintf(L"]"); glColor3f(1.0f, 1.0f, 1.0f); if (m_iBufferPos==0) DrawCursor(); for (int i = 0; i < m_iBufferLength; i++){ glwprintf(L"%lc", m_szBuffer[i]); if (m_iBufferPos-1==i) DrawCursor(); } glPopMatrix(); } void CConsole::DrawCursor(void) { // (glPushMatrix is necessary because glwprintf does glTranslatef) glPushMatrix(); // Slightly translucent yellow glColor4f(1.0f, 1.0f, 0.0f, 0.8f); // Cursor character is chosen to be an underscore glwprintf(L"_"); // Revert to the standard text colour glColor3f(1.0f, 1.0f, 1.0f); glPopMatrix(); } //Inserts a character into the buffer. void CConsole::InsertChar(const int szChar, const wchar_t cooked ) { static int iHistoryPos = -1; if (!m_bVisible) return; switch (szChar){ case SDLK_RETURN: iHistoryPos = -1; m_iMsgHistPos = 1; ProcessBuffer(m_szBuffer); FlushBuffer(); return; case SDLK_TAB: // Auto Complete return; case SDLK_BACKSPACE: if (IsEmpty() || IsBOB()) return; if (m_iBufferPos == m_iBufferLength) m_szBuffer[m_iBufferPos - 1] = '\0'; else{ for(int j=m_iBufferPos-1; j= m_iBufferPos) { bool bad = false; for(int i=0; i 0 ) { int oldHistoryPos = iHistoryPos; while( iHistoryPos != 0) { iHistoryPos--; std::wstring& histString = m_deqBufHistory.at(iHistoryPos); if((int)histString.length() >= m_iBufferPos) { bool bad = false; for(int i=0; im_iBufferPos; i--) m_szBuffer[i] = m_szBuffer[i-1]; // move chars to right m_szBuffer[i] = cooked; } m_iBufferPos++; m_iBufferLength++; return; } } void CConsole::InsertMessage(const wchar_t* szMessage, ...) { va_list args; wchar_t szBuffer[CONSOLE_MESSAGE_SIZE]; va_start(args, szMessage); if (vswprintf(szBuffer, CONSOLE_MESSAGE_SIZE, szMessage, args) == -1) { debug_printf(L"Error printfing console message (buffer size exceeded?)\n"); // Make it obvious that the text was trimmed (assuming it was) wcscpy(szBuffer+CONSOLE_MESSAGE_SIZE-4, L"..."); } va_end(args); InsertMessageRaw(szBuffer); } void CConsole::InsertMessageRaw(const CStrW& message) { // (TODO: this text-wrapping is rubbish since we now use variable-width fonts) //Insert newlines to wraparound text where needed CStrW wrapAround(message); CStrW newline(L'\n'); size_t oldNewline=0; size_t distance; //make sure everything has been initialized if ( m_charsPerPage != 0 ) { while ( oldNewline+m_charsPerPage < wrapAround.length() ) { distance = wrapAround.find(newline, oldNewline) - oldNewline; if ( distance > m_charsPerPage ) { oldNewline += m_charsPerPage; wrapAround.insert( oldNewline++, newline ); } else oldNewline += distance+1; } } // Split into lines and add each one individually oldNewline = 0; while ( (distance = wrapAround.find(newline, oldNewline)) != wrapAround.npos) { distance -= oldNewline; m_deqMsgHistory.push_front(wrapAround.substr(oldNewline, distance)); oldNewline += distance+1; } m_deqMsgHistory.push_front(wrapAround.substr(oldNewline)); } const wchar_t* CConsole::GetBuffer() { m_szBuffer[m_iBufferLength] = 0; return( m_szBuffer ); } void CConsole::SetBuffer(const wchar_t* szMessage) { int oldBufferPos = m_iBufferPos; // remember since FlushBuffer will set it to 0 FlushBuffer(); wcsncpy(m_szBuffer, szMessage, CONSOLE_BUFFER_SIZE); m_iBufferLength = (int)wcslen(m_szBuffer); m_iBufferPos = std::min(oldBufferPos, m_iBufferLength); } void CConsole::UseHistoryFile(const VfsPath& filename, int max_history_lines) { m_MaxHistoryLines = max_history_lines; m_sHistoryFile = filename; LoadHistory(); } void CConsole::ProcessBuffer(const wchar_t* szLine) { if (szLine == NULL) return; if (wcslen(szLine) <= 0) return; debug_assert(wcslen(szLine) < CONSOLE_BUFFER_SIZE); m_deqBufHistory.push_front(szLine); SaveHistory(); // Do this each line for the moment; if a script causes // a crash it's a useful record. wchar_t szCommand[CONSOLE_BUFFER_SIZE] = { 0 }; if (szLine[0] == '\\') { if (swscanf(szLine, L"\\%ls", szCommand) != 1) return; Trim(szCommand); ToLower(szCommand); if (!wcscmp(szCommand, L"info")) { InsertMessage(L""); InsertMessage(L"[Information]"); InsertMessage(L" -View commands \"\\commands\""); InsertMessage(L" -Call command \"\\\""); InsertMessage(L" -Say \"\""); InsertMessage(L" -Help - Lists functions usable from console"); InsertMessage(L""); } else if (!wcscmp(szCommand, L"commands")) { InsertMessage(L""); InsertMessage(L"[Commands]"); InsertMessage(L" (none registered)"); InsertMessage(L""); } else if (! (wcscmp(szCommand, L"Help") && wcscmp(szCommand, L"help")) ) { InsertMessage(L""); InsertMessage(L"[Help]"); InsertMessageRaw(m_helpText); } else { InsertMessage(L"unknown command <%ls>", szCommand); } } else if (szLine[0] == ':' || szLine[0] == '?') { // Process it as JavaScript jsval rval = g_ScriptingHost.ExecuteScript( szLine+1, L"Console" ); if (szLine[0] == '?' && rval) { try { InsertMessage( L"%ls", g_ScriptingHost.ValueToUCString( rval ).c_str() ); } catch (PSERROR_Scripting_ConversionFailed) { InsertMessage( L"%hs", "" ); } } } else { SendChatMessage(szLine); } } void CConsole::LoadHistory() { // note: we don't care if this file doesn't exist or can't be read; // just don't load anything in that case. // do this before LoadFile to avoid an error message if file not found. if (!FileExists(m_sHistoryFile)) return; shared_ptr buf; size_t buflen; if (g_VFS->LoadFile(m_sHistoryFile, buf, buflen) < 0) return; CStr bytes ((char*)buf.get(), buflen); CStrW str (bytes.FromUTF8()); size_t pos = 0; while (pos != CStrW::npos) { pos = str.find('\n'); if (pos != CStrW::npos) { if (pos > 0) m_deqBufHistory.push_front(str.Left(str[pos-1] == '\r' ? pos - 1 : pos)); str = str.substr(pos + 1); } else if (str.length() > 0) m_deqBufHistory.push_front(str); } } void CConsole::SaveHistory() { WriteBuffer buffer; const int linesToSkip = (int)m_deqBufHistory.size() - m_MaxHistoryLines; std::deque::reverse_iterator it = m_deqBufHistory.rbegin(); if(linesToSkip > 0) std::advance(it, linesToSkip); for (; it != m_deqBufHistory.rend(); ++it) { CStr8 line = CStrW(*it).ToUTF8(); buffer.Append(line.data(), line.length()); static const char newline = '\n'; buffer.Append(&newline, 1); } g_VFS->CreateFile(m_sHistoryFile, buffer.Data(), buffer.Size()); } void CConsole::SendChatMessage(const wchar_t *pText) { if (g_NetClient) { // TODO // g_NetClient3->SendChatMessage(pText); } } void CConsole::ReceivedChatMessage(const wchar_t *szSender, const wchar_t *szMessage) { InsertMessage(L"%ls: %ls", szSender, szMessage); } static bool isUnprintableChar(SDL_keysym key) { // U+0000 to U+001F are control characters if (key.unicode < 0x20) { switch (key.sym) { // We want to allow some, which are handled specially case SDLK_RETURN: case SDLK_TAB: case SDLK_BACKSPACE: case SDLK_DELETE: case SDLK_HOME: case SDLK_END: case SDLK_LEFT: case SDLK_RIGHT: case SDLK_UP: case SDLK_DOWN: case SDLK_PAGEUP: case SDLK_PAGEDOWN: return false; // Ignore the others default: return true; } } return false; } InReaction conInputHandler(const SDL_Event_* ev) { if( ev->ev.type == SDL_HOTKEYDOWN ) { - if( ev->ev.user.code == HOTKEY_CONSOLE_TOGGLE ) + std::string hotkey = static_cast(ev->ev.user.data1); + + if( hotkey == "console.toggle" ) { g_Console->ToggleVisible(); return IN_HANDLED; } - else if( ev->ev.user.code == HOTKEY_CONSOLE_COPY ) + else if( hotkey == "console.copy" ) { sys_clipboard_set( g_Console->GetBuffer() ); return IN_HANDLED; } - else if( ev->ev.user.code == HOTKEY_CONSOLE_PASTE ) + else if( hotkey == "console.paste" ) { wchar_t* text = sys_clipboard_get(); if(text) { for(wchar_t* c = text; *c; c++) g_Console->InsertChar(0, *c); sys_clipboard_free(text); } return IN_HANDLED; } } if( ev->ev.type != SDL_KEYDOWN) return IN_PASS; SDLKey sym = ev->ev.key.keysym.sym; if(!g_Console->IsActive()) return IN_PASS; // Stop unprintable characters (ctrl+, alt+ and escape), // also prevent ` and/or ~ appearing in console every time it's toggled. if( !isUnprintableChar(ev->ev.key.keysym) && - !hotkeys[HOTKEY_CONSOLE_TOGGLE] ) + !HotkeyIsPressed("console.toggle") ) g_Console->InsertChar(sym, (wchar_t)ev->ev.key.keysym.unicode ); return IN_PASS; } Index: ps/trunk/source/ps/Hotkey.h =================================================================== --- ps/trunk/source/ps/Hotkey.h (revision 8443) +++ ps/trunk/source/ps/Hotkey.h (revision 8444) @@ -1,149 +1,49 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 . */ -#ifndef PS_HOTKEY_H -#define PS_HOTKEY_H +#ifndef INCLUDED_HOTKEY +#define INCLUDED_HOTKEY -// Hotkey.h -// -// Constant definitions and a couple of exports for the hotkey processor -// -// Hotkeys can be mapped onto SDL events (for use internal to the engine), -// or used to trigger activation of GUI buttons. -// -// Adding a hotkey (SDL event type): -// -// - Define your constant in the enum, just below; -// - Add an entry to hotkeyInfo[], in Hotkey.cpp -// first column is this constant, second is the config string (minus 'hotkey.') it maps to -// third and fourth are the default keys, used if the config file doesn't contain that string. -// - Create an input handler for SDL_HOTKEYDOWN, SDL_HOTKEYUP, or poll the hotkeys[] array. -// For SDL_HOTKEYDOWN, SDL_HOTKEYUP, the constant is passed in as ev->ev.user.code. -// - Add some bindings to the config file. +/** + * @file + * Hotkey system. + * + * Hotkeys consist of a name (an arbitrary string), and a key mapping. + * The names and mappings are loaded from the config system (any + * config setting with the name prefix "hotkey."). + * When a hotkey is pressed or released, SDL_HOTKEYDOWN and SDL_HOTKEYUP + * events are triggered, with the hotkey name stored in ev.user.data1 + * as a const char*. + */ #include "CStr.h" #include "lib/input.h" #include "lib/external_libraries/sdl.h" // see note below // note: we need the real SDL header - it defines SDL_USEREVENT, which is // required for our HOTKEY event type definition. this is OK since // hotkey.h is not included from any headers. const int SDL_HOTKEYDOWN = SDL_USEREVENT; const int SDL_HOTKEYUP = SDL_USEREVENT + 1; -const int SDL_GUIHOTKEYPRESS = SDL_USEREVENT + 2; - -enum -{ - HOTKEY_EXIT, - HOTKEY_SCREENSHOT, - HOTKEY_BIGSCREENSHOT, - HOTKEY_WIREFRAME, - HOTKEY_TOGGLEFULLSCREEN, - HOTKEY_CAMERA_RESET, - HOTKEY_CAMERA_FOLLOW, - HOTKEY_CAMERA_ZOOM_IN, - HOTKEY_CAMERA_ZOOM_OUT, - HOTKEY_CAMERA_ZOOM_WHEEL_IN, - HOTKEY_CAMERA_ZOOM_WHEEL_OUT, - HOTKEY_CAMERA_ROTATE_CW, - HOTKEY_CAMERA_ROTATE_CCW, - HOTKEY_CAMERA_ROTATE_UP, - HOTKEY_CAMERA_ROTATE_DOWN, - HOTKEY_CAMERA_ROTATE_WHEEL_CW, - HOTKEY_CAMERA_ROTATE_WHEEL_CCW, - HOTKEY_CAMERA_PAN, - HOTKEY_CAMERA_PAN_KEYBOARD, - HOTKEY_CAMERA_LEFT, - HOTKEY_CAMERA_RIGHT, - HOTKEY_CAMERA_UP, - HOTKEY_CAMERA_DOWN, - HOTKEY_CAMERA_CINEMA_ADD, - HOTKEY_CAMERA_CINEMA_DELETE, - HOTKEY_CAMERA_CINEMA_DELETE_ALL, - HOTKEY_CAMERA_CINEMA_QUEUE, - HOTKEY_CONSOLE_TOGGLE, - HOTKEY_CONSOLE_COPY, - HOTKEY_CONSOLE_PASTE, - HOTKEY_SELECTION_ADD, - HOTKEY_SELECTION_REMOVE, - HOTKEY_SELECTION_GROUP_0, - HOTKEY_SELECTION_GROUP_1, - HOTKEY_SELECTION_GROUP_2, - HOTKEY_SELECTION_GROUP_3, - HOTKEY_SELECTION_GROUP_4, - HOTKEY_SELECTION_GROUP_5, - HOTKEY_SELECTION_GROUP_6, - HOTKEY_SELECTION_GROUP_7, - HOTKEY_SELECTION_GROUP_8, - HOTKEY_SELECTION_GROUP_9, - HOTKEY_SELECTION_GROUP_10, - HOTKEY_SELECTION_GROUP_11, - HOTKEY_SELECTION_GROUP_12, - HOTKEY_SELECTION_GROUP_13, - HOTKEY_SELECTION_GROUP_14, - HOTKEY_SELECTION_GROUP_15, - HOTKEY_SELECTION_GROUP_16, - HOTKEY_SELECTION_GROUP_17, - HOTKEY_SELECTION_GROUP_18, - HOTKEY_SELECTION_GROUP_19, - HOTKEY_SELECTION_GROUP_ADD, - HOTKEY_SELECTION_GROUP_SAVE, - HOTKEY_SELECTION_GROUP_SNAP, - HOTKEY_SELECTION_SNAP, - HOTKEY_ORDER_QUEUE, - HOTKEY_CONTEXTORDER_NEXT, - HOTKEY_CONTEXTORDER_PREVIOUS, - HOTKEY_HIGHLIGHTALL, - HOTKEY_PROFILE_TOGGLE, - HOTKEY_PROFILE_SAVE, - HOTKEY_PLAYMUSIC, - HOTKEY_PAUSE, - HOTKEY_SPEED_INCREASE, - HOTKEY_SPEED_DECREASE, - HOTKEY_KILL, - HOTKEY_CHAT, - - HOTKEY_LAST, - - HOTKEY_NEGATION_FLAG = 65536 -}; extern void LoadHotkeys(); extern InReaction HotkeyInputHandler(const SDL_Event_* ev); -extern void HotkeyRegisterGuiObject(const CStr& objName, const CStr& hotkeyName); -/** - * @return the name of the specified HOTKEY_*, or empty string if not defined - **/ -extern CStr HotkeyGetName(int hotkey); - -/** - * @return whether the specified HOTKEY_* responds to the specified SDLK_* - * (mainly for the screenshot system to know whether it needs to override - * the printscreen screen). Ignores modifier keys. - **/ -extern bool HotkeyRespondsTo(int hotkey, int sdlkey); - -/** - * @return whether one of the key combinations for the given hotkey is pressed - **/ extern bool HotkeyIsPressed(const CStr& keyname); -extern bool hotkeys[HOTKEY_LAST]; - -#endif // PS_HOTKEY_H +#endif // INCLUDED_HOTKEY Index: ps/trunk/source/ps/ConfigDB.h =================================================================== --- ps/trunk/source/ps/ConfigDB.h (revision 8443) +++ ps/trunk/source/ps/ConfigDB.h (revision 8444) @@ -1,158 +1,171 @@ -/* Copyright (C) 2009 Wildfire Games. +/* 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 . */ /* CConfigDB - Load, access and store configuration variables TDD : http://forums.wildfiregames.com/0ad/index.php?showtopic=1125 OVERVIEW: JavaScript: All the javascript interfaces are provided through the global object g_ConfigDB. g_ConfigDB Properties: system: All CFG_SYSTEM values are linked to properties of this object. a=g_ConfigDB.system.foo; is equivalent to C++ code g_ConfigDB.GetValue(CFG_SYSTEM, "foo"); mod: Ditto, but linked to CFG_MOD user: Ditto, but linked to CFG_USER default: Ditto, but linked to CFG_DEFAULT g_ConfigDB Functions: None so far ConfigNamespace Functions: (applicable to the system, mod or user properties of g_ConfigDB) boolean WriteFile(boolean useVFS, string path): JS interface to g_ConfigDB.WriteFile - should work exactly the same. boolean Reload() => g_ConfigDB.Reload() void SetFile() => g_ConfigDB.SetConfigFile() */ #ifndef INCLUDED_CONFIGDB #define INCLUDED_CONFIGDB #include "Parser.h" #include "CStr.h" #include "Singleton.h" // Namespace priorities: User supersedes mod supersedes system. // Command-line arguments override everything. enum EConfigNamespace { CFG_DEFAULT, CFG_SYSTEM, CFG_MOD, CFG_USER, CFG_COMMAND, CFG_LAST }; typedef CParserValue CConfigValue; typedef std::vector CConfigValueSet; #define g_ConfigDB CConfigDB::GetSingleton() class CConfigDB: public Singleton { static std::map m_Map[]; static CStrW m_ConfigFile[]; static bool m_UseVFS[]; public: // NOTE: Construct the Singleton Object *after* JavaScript init, so that // the JS interface can be registered. CConfigDB(); - // GetValue() - // Attempt to find a config variable with the given name; will search all - // namespaces from system up to the specified namespace. - // - // Returns a pointer to the config value structure for the variable, or - // NULL if such a variable could not be found + /** + * Attempt to find a config variable with the given name; will search all + * namespaces from system up to the specified namespace. + * + * Returns a pointer to the config value structure for the variable, or + * NULL if such a variable could not be found + */ CConfigValue *GetValue(EConfigNamespace ns, const CStr& name); - // GetValues() - // Attempt to retrieve a vector of values corresponding to the given setting; - // will search all namespaces from system up to the specified namespace. - // - // Returns a pointer to the vector, or NULL if the setting could not be found. + /** + * Attempt to retrieve a vector of values corresponding to the given setting; + * will search all namespaces from system up to the specified namespace. + * + * Returns a pointer to the vector, or NULL if the setting could not be found. + */ CConfigValueSet *GetValues(EConfigNamespace ns, const CStr& name); - // CreateValue() - // Create a new config value in the specified namespace. If such a - // variable already exists, the old value is returned and the effect is - // exactly the same as that of GetValue() - // - // Returns a pointer to the value of the newly created config variable, or - // that of the already existing config variable. + /** + * Retrieve a vector of values corresponding to settings whose names begin + * with the given prefix; + * will search all namespaces from system up to the specified namespace. + */ + std::vector > GetValuesWithPrefix(EConfigNamespace ns, const CStr& prefix); + + /** + * Create a new config value in the specified namespace. If such a + * variable already exists, the old value is returned and the effect is + * exactly the same as that of GetValue() + * + * Returns a pointer to the value of the newly created config variable, or + * that of the already existing config variable. + */ CConfigValue *CreateValue(EConfigNamespace ns, const CStr& name); - // SetConfigFile() - // Set the path to the config file used to populate the specified namespace - // Note that this function does not actually load the config file. Use - // the Reload() method if you want to read the config file at the same time. - // - // 'path': The path to the config file. - // VFS: relative to VFS root - // non-VFS: relative to current working directory (binaries/data/) - // 'useVFS': true if the path is a VFS path, false if it is a real path + /** + * Set the path to the config file used to populate the specified namespace + * Note that this function does not actually load the config file. Use + * the Reload() method if you want to read the config file at the same time. + * + * 'path': The path to the config file. + * VFS: relative to VFS root + * non-VFS: relative to current working directory (binaries/data/) + * 'useVFS': true if the path is a VFS path, false if it is a real path + */ void SetConfigFile(EConfigNamespace ns, bool useVFS, const CStrW& path); - // Reload() - // Reload the config file associated with the specified config namespace - // (the last config file path set with SetConfigFile) - // - // Returns: - // true: if the reload succeeded, - // false: if the reload failed + /** + * Reload the config file associated with the specified config namespace + * (the last config file path set with SetConfigFile) + * + * Returns: + * true: if the reload succeeded, + * false: if the reload failed + */ bool Reload(EConfigNamespace); - // WriteFile() - // Write the current state of the specified config namespace to the file - // specified by 'path' - // - // Returns: - // true: if the config namespace was successfully written to the file - // false: if an error occured + /** + * Write the current state of the specified config namespace to the file + * specified by 'path' + * + * Returns: + * true: if the config namespace was successfully written to the file + * false: if an error occurred + */ bool WriteFile(EConfigNamespace ns, bool useVFS, const CStrW& path); }; // stores the value of the given key into . this quasi-template // convenience wrapper on top of CConfigValue::Get* simplifies user code and // avoids "assignment within condition expression" warnings. #define CFG_GET_SYS_VAL(name, type, destination)\ STMT(\ CConfigValue* val = g_ConfigDB.GetValue(CFG_SYSTEM, name);\ if(val)\ val->Get##type(destination);\ ) #define CFG_GET_USER_VAL(name, type, destination)\ STMT(\ CConfigValue* val = g_ConfigDB.GetValue(CFG_USER, name);\ if(val)\ val->Get##type(destination);\ ) #endif Index: ps/trunk/source/ps/ProfileViewer.cpp =================================================================== --- ps/trunk/source/ps/ProfileViewer.cpp (revision 8443) +++ ps/trunk/source/ps/ProfileViewer.cpp (revision 8444) @@ -1,483 +1,485 @@ /* 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 . */ /* * Implementation of profile display (containing only display routines, * the data model(s) are implemented elsewhere). */ #include "precompiled.h" #include #include #include "ProfileViewer.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Font.h" #include "ps/Hotkey.h" #include "ps/Profile.h" #include "lib/utf8.h" #include "lib/external_libraries/sdl.h" #include "lib/res/graphics/unifont.h" #include "renderer/Renderer.h" extern int g_xres, g_yres; struct CProfileViewerInternals { CProfileViewerInternals() {} /// Whether the profiling display is currently visible bool profileVisible; /// List of root tables std::vector rootTables; /// Path from a root table (path[0]) to the currently visible table (path[size-1]) std::vector path; /// Helper functions void TableIsDeleted(AbstractProfileTable* table); void NavigateTree(int id); /// File for saved profile output (reset when the game is restarted) std::ofstream outputStream; private: // Cannot be copied/assigned, because of the ofstream CProfileViewerInternals(const CProfileViewerInternals& rhs); const CProfileViewerInternals& operator=(const CProfileViewerInternals& rhs); }; /////////////////////////////////////////////////////////////////////////////////////////////// // AbstractProfileTable implementation AbstractProfileTable::~AbstractProfileTable() { if (CProfileViewer::IsInitialised()) { g_ProfileViewer.m->TableIsDeleted(this); } } /////////////////////////////////////////////////////////////////////////////////////////////// // CProfileViewer implementation // AbstractProfileTable got deleted, make sure we have no dangling pointers void CProfileViewerInternals::TableIsDeleted(AbstractProfileTable* table) { for(int idx = (int)rootTables.size()-1; idx >= 0; --idx) { if (rootTables[idx] == table) rootTables.erase(rootTables.begin() + idx); } for(size_t idx = 0; idx < path.size(); ++idx) { if (path[idx] != table) continue; path.erase(path.begin() + idx, path.end()); if (path.size() == 0) profileVisible = false; } } // Move into child tables or return to parent tables based on the given number void CProfileViewerInternals::NavigateTree(int id) { if (id == 0) { if (path.size() > 1) path.pop_back(); } else { AbstractProfileTable* table = path[path.size() - 1]; size_t numrows = table->GetNumberRows(); for(size_t row = 0; row < numrows; ++row) { AbstractProfileTable* child = table->GetChild(row); if (!child) continue; --id; if (id == 0) { path.push_back(child); break; } } } } // Construction/Destruction CProfileViewer::CProfileViewer() { m = new CProfileViewerInternals; m->profileVisible = false; } CProfileViewer::~CProfileViewer() { delete m; } // Render void CProfileViewer::RenderProfile() { if (!m->profileVisible) return; if (!m->path.size()) { m->profileVisible = false; return; } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); AbstractProfileTable* table = m->path[m->path.size() - 1]; const std::vector& columns = table->GetColumns(); size_t numrows = table->GetNumberRows(); CFont font(L"mono-stroke-10"); font.Bind(); int lineSpacing = font.GetLineSpacing(); // Render background GLint estimate_height; GLint estimate_width; estimate_width = 50; for(size_t i = 0; i < columns.size(); ++i) estimate_width += (GLint)columns[i].width; estimate_height = 3 + (GLint)numrows; if (m->path.size() > 1) estimate_height += 2; estimate_height = lineSpacing*estimate_height; glDisable(GL_TEXTURE_2D); glColor4ub(0,0,0,128); glBegin(GL_QUADS); glVertex2i(0, g_yres); glVertex2i(estimate_width, g_yres); glVertex2i(estimate_width, g_yres-estimate_height); glVertex2i(0, g_yres-estimate_height); glEnd(); glEnable(GL_TEXTURE_2D); // Print table and column titles glPushMatrix(); glTranslatef(2.0f, g_yres - lineSpacing, 0.0f ); glScalef(1.0f, -1.0f, 1.0f); glColor3ub(255, 255, 255); glPushMatrix(); glwprintf(L"%hs", table->GetTitle().c_str()); glPopMatrix(); glTranslatef( 20.0f, lineSpacing, 0.0f ); glPushMatrix(); for(size_t col = 0; col < columns.size(); ++col) { glPushMatrix(); glwprintf(L"%hs", columns[col].title.c_str()); glPopMatrix(); glTranslatef(columns[col].width, 0, 0); } glPopMatrix(); glTranslatef( 0.0f, lineSpacing, 0.0f ); // Print rows int currentExpandId = 1; for(size_t row = 0; row < numrows; ++row) { glPushMatrix(); if (table->IsHighlightRow(row)) glColor3ub(255, 128, 128); else glColor3ub(255, 255, 255); if (table->GetChild(row)) { glPushMatrix(); glTranslatef( -15.0f, 0.0f, 0.0f ); glwprintf(L"%d", currentExpandId); glPopMatrix(); currentExpandId++; } for(size_t col = 0; col < columns.size(); ++col) { glPushMatrix(); glwprintf(L"%hs", table->GetCellText(row, col).c_str()); glPopMatrix(); glTranslatef(columns[col].width, 0, 0); } glPopMatrix(); glTranslatef( 0.0f, lineSpacing, 0.0f ); } glColor3ub(255, 255, 255); if (m->path.size() > 1) { glTranslatef( 0.0f, lineSpacing, 0.0f ); glPushMatrix(); glPushMatrix(); glTranslatef( -15.0f, 0.0f, 0.0f ); glwprintf( L"0" ); glPopMatrix(); glwprintf( L"back to parent" ); glPopMatrix(); } glPopMatrix(); glDisable(GL_BLEND); } // Handle input InReaction CProfileViewer::Input(const SDL_Event_* ev) { switch(ev->ev.type) { case SDL_KEYDOWN: { if (!m->profileVisible) break; int k = ev->ev.key.keysym.sym - SDLK_0; if (k >= 0 && k <= 9) { m->NavigateTree(k); return IN_HANDLED; } break; } case SDL_HOTKEYDOWN: - if( ev->ev.user.code == HOTKEY_PROFILE_TOGGLE ) + std::string hotkey = static_cast(ev->ev.user.data1); + + if( hotkey == "profile.toggle" ) { if (!m->profileVisible) { if (m->rootTables.size()) { m->profileVisible = true; m->path.push_back(m->rootTables[0]); } } else { size_t i; for(i = 0; i < m->rootTables.size(); ++i) { if (m->rootTables[i] == m->path[0]) break; } i++; m->path.clear(); if (i < m->rootTables.size()) { m->path.push_back(m->rootTables[i]); } else { m->profileVisible = false; } } return( IN_HANDLED ); } - else if( ev->ev.user.code == HOTKEY_PROFILE_SAVE ) + else if( hotkey == "profile.save" ) { SaveToFile(); return( IN_HANDLED ); } break; } return( IN_PASS ); } InReaction CProfileViewer::InputThunk(const SDL_Event_* ev) { if (CProfileViewer::IsInitialised()) return g_ProfileViewer.Input(ev); return IN_PASS; } // Add a table to the list of roots void CProfileViewer::AddRootTable(AbstractProfileTable* table, bool front) { if (front) m->rootTables.insert(m->rootTables.begin(), table); else m->rootTables.push_back(table); } namespace { struct WriteTable { std::ofstream& f; WriteTable(std::ofstream& f) : f(f) {} void operator() (AbstractProfileTable* table) { std::vector data; // 2d array of (rows+head)*columns elements // Add column headers to 'data' for (std::vector::const_iterator col_it = table->GetColumns().begin(); col_it != table->GetColumns().end(); ++col_it) data.push_back(col_it->title); // Recursively add all profile data to 'data' WriteRows(1, table, data); // Calculate the width of each column ( = the maximum width of // any value in that column) std::vector columnWidths; size_t cols = table->GetColumns().size(); for (size_t c = 0; c < cols; ++c) { size_t max = 0; for (size_t i = c; i < data.size(); i += cols) max = std::max(max, data[i].length()); columnWidths.push_back(max); } // Output data as a formatted table: f << "\n\n" << table->GetTitle() << "\n"; for (size_t r = 0; r < data.size()/cols; ++r) { for (size_t c = 0; c < cols; ++c) f << (c ? " | " : "\n") << data[r*cols + c].Pad(PS_TRIM_RIGHT, columnWidths[c]); // Add dividers under some rows. (Currently only the first, since // that contains the column headers.) if (r == 0) for (size_t c = 0; c < cols; ++c) f << (c ? "-|-" : "\n") << CStr::Repeat("-", columnWidths[c]); } } void WriteRows(int indent, AbstractProfileTable* table, std::vector& data) { for (size_t r = 0; r < table->GetNumberRows(); ++r) { // Do pretty tree-structure indenting CStr indentation = CStr::Repeat("| ", indent-1); if (r+1 == table->GetNumberRows()) indentation += "'-"; else indentation += "|-"; for (size_t c = 0; c < table->GetColumns().size(); ++c) if (c == 0) data.push_back(indentation + table->GetCellText(r, c)); else data.push_back(table->GetCellText(r, c)); if (table->GetChild(r)) WriteRows(indent+1, table->GetChild(r), data); } } private: const WriteTable& operator=(const WriteTable&); }; bool SortByName(AbstractProfileTable* a, AbstractProfileTable* b) { return (a->GetName() < b->GetName()); } } void CProfileViewer::SaveToFile() { // Open the file, if necessary. If this method is called several times, // the profile results will be appended to the previous ones from the same // run. if (! m->outputStream.is_open()) { // Open the file. (It will be closed when the CProfileViewer // destructor is called.) fs::wpath path(psLogDir()/L"profile.txt"); m->outputStream.open(utf8_from_wstring(path.string()).c_str(), std::ofstream::out | std::ofstream::trunc); if (m->outputStream.fail()) { LOGERROR(L"Failed to open profile log file"); return; } } time_t t; time(&t); m->outputStream << "================================================================\n\n"; m->outputStream << "PS profiler snapshot - " << asctime(localtime(&t)); std::vector tables = m->rootTables; sort(tables.begin(), tables.end(), SortByName); for_each(tables.begin(), tables.end(), WriteTable(m->outputStream)); m->outputStream << "\n\n================================================================\n"; m->outputStream.flush(); } void CProfileViewer::ShowTable(const CStr& table) { m->path.clear(); if (table.length() > 0) { for (size_t i = 0; i < m->rootTables.size(); ++i) { if (m->rootTables[i]->GetName() == table) { m->path.push_back(m->rootTables[i]); m->profileVisible = true; return; } } } // No matching table found, so don't display anything m->profileVisible = false; } Index: ps/trunk/source/gui/CInput.cpp =================================================================== --- ps/trunk/source/gui/CInput.cpp (revision 8443) +++ ps/trunk/source/gui/CInput.cpp (revision 8444) @@ -1,1678 +1,1679 @@ /* 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" #define LOG_CATEGORY L"gui" //------------------------------------------------------------------- // 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_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->ev.type == SDL_HOTKEYDOWN) { - if (ev->ev.user.code == HOTKEY_CONSOLE_PASTE) + 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; } } else if (ev->ev.type == SDL_KEYDOWN) { int szChar = ev->ev.key.keysym.sym; wchar_t cooked = (wchar_t)ev->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->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 ); --m_iBufferPos; UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); } UpdateAutoScroll(); break; case SDLK_DELETE: m_WantedX=0.f; // If selection: if (SelectingText()) { DeleteCurSelection(); } else { if (pCaption->empty() || 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; } 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]) { // 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) { HandleMessage(GUIM_PRESSED); ScriptEvent("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; 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; } return IN_PASS; } void CInput::HandleMessage(const 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("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_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::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::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]); } 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()) 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 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 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); 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(); 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; std::list::iterator temp_it = destroy_row_to; --temp_it; CStr c_caption1(caption.substr(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 (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. CStr c_caption1(caption.substr(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) { 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; copy_used = true; std::list::iterator temp = destroy_row_to; --temp; copy = *temp; m_CharacterPositions.erase(destroy_row_from, destroy_row_to); CStr c_caption(caption.substr(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::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::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; // 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 std::list::iterator ¤t, const float &x, float &wanted) { 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; 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::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/scripting/GuiScriptConversions.cpp =================================================================== --- ps/trunk/source/gui/scripting/GuiScriptConversions.cpp (revision 8443) +++ ps/trunk/source/gui/scripting/GuiScriptConversions.cpp (revision 8444) @@ -1,144 +1,136 @@ /* 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 . */ #include "precompiled.h" #include "scriptinterface/ScriptInterface.h" #include "gui/IGUIObject.h" #include "lib/external_libraries/sdl.h" #include "ps/Hotkey.h" #include "js/jsapi.h" #define SET(obj, name, value) STMT(jsval v_ = ToJSVal(cx, (value)); JS_SetProperty(cx, (obj), (name), &v_)) // ignore JS_SetProperty return value, because errors should be impossible // and we can't do anything useful in the case of errors anyway template<> jsval ScriptInterface::ToJSVal(JSContext* cx, SDL_Event_ const& val) { const char* typeName; switch (val.ev.type) { case SDL_ACTIVEEVENT: typeName = "activeevent"; break; case SDL_KEYDOWN: typeName = "keydown"; break; case SDL_KEYUP: typeName = "keyup"; break; case SDL_MOUSEMOTION: typeName = "mousemotion"; break; case SDL_MOUSEBUTTONDOWN: typeName = "mousebuttondown"; break; case SDL_MOUSEBUTTONUP: typeName = "mousebuttonup"; break; case SDL_HOTKEYDOWN: typeName = "hotkeydown"; break; case SDL_HOTKEYUP: typeName = "hotkeyup"; break; - case SDL_GUIHOTKEYPRESS: typeName = "guihotkeypress"; break; default: typeName = "(unknown)"; break; } ScriptInterface::LocalRootScope scope(cx); if (! scope.OK()) return JSVAL_VOID; JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); if (! obj) return JSVAL_VOID; SET(obj, "type", typeName); switch (val.ev.type) { case SDL_ACTIVEEVENT: { SET(obj, "gain", (int)val.ev.active.gain); SET(obj, "state", (int)val.ev.active.state); break; } case SDL_KEYDOWN: case SDL_KEYUP: { // SET(obj, "which", (int)val.ev.key.which); // (not in wsdl.h) // SET(obj, "state", (int)val.ev.key.state); // (not in wsdl.h) JSObject* keysym = JS_NewObject(cx, NULL, NULL, NULL); if (! keysym) return JSVAL_VOID; jsval keysymVal = OBJECT_TO_JSVAL(keysym); JS_SetProperty(cx, obj, "keysym", &keysymVal); // SET(keysym, "scancode", (int)val.ev.key.keysym.scancode); // (not in wsdl.h) SET(keysym, "sym", (int)val.ev.key.keysym.sym); // SET(keysym, "mod", (int)val.ev.key.keysym.mod); // (not in wsdl.h) if (val.ev.key.keysym.unicode) { std::wstring unicode(1, (wchar_t)val.ev.key.keysym.unicode); SET(keysym, "unicode", unicode); } else { SET(keysym, "unicode", CScriptVal(JSVAL_VOID)); } // TODO: scripts have no idea what all the key/mod enum values are; // we should probably expose them as constants if we expect scripts to use them break; } case SDL_MOUSEMOTION: { // SET(obj, "which", (int)val.ev.motion.which); // (not in wsdl.h) // SET(obj, "state", (int)val.ev.motion.state); // (not in wsdl.h) SET(obj, "x", (int)val.ev.motion.x); SET(obj, "y", (int)val.ev.motion.y); // SET(obj, "xrel", (int)val.ev.motion.xrel); // (not in wsdl.h) // SET(obj, "yrel", (int)val.ev.motion.yrel); // (not in wsdl.h) break; } case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { // SET(obj, "which", (int)val.ev.button.which); // (not in wsdl.h) SET(obj, "button", (int)val.ev.button.button); SET(obj, "state", (int)val.ev.button.state); SET(obj, "x", (int)val.ev.button.x); SET(obj, "y", (int)val.ev.button.y); break; } case SDL_HOTKEYDOWN: case SDL_HOTKEYUP: { - CStr name = HotkeyGetName(val.ev.user.code); - SET(obj, "hotkey", name.c_str()); - break; - } - case SDL_GUIHOTKEYPRESS: - { - CStr* name = static_cast(val.ev.user.data1); - SET(obj, "object", name->c_str()); + SET(obj, "hotkey", static_cast(val.ev.user.data1)); break; } } jsval rval = OBJECT_TO_JSVAL(obj); scope.LeaveWithResult(rval); return rval; } template<> jsval ScriptInterface::ToJSVal(JSContext* UNUSED(cx), IGUIObject* const& val) { if (val == NULL) return JSVAL_NULL; return OBJECT_TO_JSVAL(val->GetJSObject()); } Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp (revision 8443) +++ ps/trunk/source/gui/CGUI.cpp (revision 8444) @@ -1,1859 +1,1858 @@ /* 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 . */ /* CGUI */ #include "precompiled.h" #include #include #include "lib/res/graphics/unifont.h" #include "GUI.h" // Types - when including them into the engine. #include "CButton.h" #include "CImage.h" #include "CText.h" #include "CCheckBox.h" #include "CRadioButton.h" #include "CInput.h" #include "CList.h" #include "CDropDown.h" #include "CProgressBar.h" #include "CTooltip.h" #include "MiniMap.h" #include "scripting/JSInterface_GUITypes.h" #include "ps/XML/Xeromyces.h" #include "ps/Font.h" #include "ps/Pyrogenesis.h" #include "lib/input.h" #include "lib/bits.h" #include "lib/timer.h" #include "lib/utf8.h" #include "lib/sysdep/sysdep.h" // TODO Gee: Whatever include CRect/CPos/CSize #include "ps/Overlay.h" #include "ps/Profile.h" #include "scripting/ScriptingHost.h" #include "ps/Hotkey.h" #include "ps/Globals.h" #include "ps/Filesystem.h" const double SELECT_DBLCLICK_RATE = 0.5; #include "ps/CLogger.h" #define LOG_CATEGORY L"gui" void CGUI::ScriptingInit() { JSI_IGUIObject::init(); JSI_GUITypes::init(); } InReaction CGUI::HandleEvent(const SDL_Event_* ev) { InReaction ret = IN_PASS; - if (ev->ev.type == SDL_GUIHOTKEYPRESS) + if (ev->ev.type == SDL_HOTKEYDOWN) { - const CStr& objectName = *(CStr*) ev->ev.user.data1; - IGUIObject* object = FindObjectByName(objectName); - if (! object) + const char* hotkey = static_cast(ev->ev.user.data1); + std::map >::iterator it = m_HotkeyObjects.find(hotkey); + if (it != m_HotkeyObjects.end()) { - LOG(CLogger::Error, LOG_CATEGORY, L"Cannot find hotkeyed object '%hs'", objectName.c_str()); - } - else - { - object->HandleMessage( SGUIMessage( GUIM_PRESSED ) ); - object->ScriptEvent("press"); + for (size_t i = 0; i < it->second.size(); ++i) + { + it->second[i]->HandleMessage(SGUIMessage(GUIM_PRESSED)); + it->second[i]->ScriptEvent("press"); + } } } else if (ev->ev.type == SDL_MOUSEMOTION) { // Yes the mouse position is stored as float to avoid // constant conversions when operating in a // float-based environment. m_MousePos = CPos((float)ev->ev.motion.x, (float)ev->ev.motion.y); GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::HandleMessage, SGUIMessage(GUIM_MOUSE_MOTION)); } // Update m_MouseButtons. (BUTTONUP is handled later.) else if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons |= Bit(ev->ev.button.button); break; default: break; } } // Only one object can be hovered IGUIObject *pNearest = NULL; // TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress! try { PROFILE( "mouse events" ); // TODO Gee: Optimizations needed! // these two recursive function are quite overhead heavy. // pNearest will after this point at the hovered object, possibly NULL pNearest = FindObjectUnderMouse(); // Is placed in the UpdateMouseOver function //if (ev->ev.type == SDL_MOUSEMOTION && pNearest) // pNearest->ScriptEvent("mousemove"); // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: // Focus the clicked object (or focus none if nothing clicked on) SetFocusedObject(pNearest); if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_PRESS_LEFT)); pNearest->ScriptEvent("mouseleftpress"); // Block event, so things on the map (behind the GUI) won't be pressed ret = IN_HANDLED; } break; case SDL_BUTTON_RIGHT: if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_PRESS_RIGHT)); pNearest->ScriptEvent("mouserightpress"); ret = IN_HANDLED; } break; case SDL_BUTTON_WHEELDOWN: // wheel down if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_DOWN)); pNearest->ScriptEvent("mousewheeldown"); ret = IN_HANDLED; } break; case SDL_BUTTON_WHEELUP: // wheel up if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_UP)); pNearest->ScriptEvent("mousewheelup"); ret = IN_HANDLED; } break; default: break; } } else if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT]; pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time(); //Double click? if (timeElapsed < SELECT_DBLCLICK_RATE) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_DBLCLICK_LEFT)); pNearest->ScriptEvent("mouseleftdoubleclick"); } else { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_RELEASE_LEFT)); pNearest->ScriptEvent("mouseleftrelease"); } ret = IN_HANDLED; } break; case SDL_BUTTON_RIGHT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT]; pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time(); //Double click? if (timeElapsed < SELECT_DBLCLICK_RATE) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_DBLCLICK_RIGHT)); //pNearest->ScriptEvent("mouserightdoubleclick"); } else { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_RELEASE_RIGHT)); //pNearest->ScriptEvent("mouserightrelease"); } ret = IN_HANDLED; } break; } // Reset all states on all visible objects GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ResetStates); // It will have reset the mouse over of the current hovered, so we'll // have to restore that if (pNearest) pNearest->m_MouseHovering = true; } } catch (PSERROR_GUI& e) { UNUSED2(e); debug_warn(L"CGUI::HandleEvent error"); // TODO Gee: Handle } // JW: what's the difference between mPress and mDown? what's the code below responsible for? /* // Generally if just mouse is clicked if (m_pInput->mDown(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_DOWN_LEFT); } */ // BUTTONUP's effect on m_MouseButtons is handled after // everything else, so that e.g. 'press' handlers (activated // on button up) see which mouse button had been pressed. if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons &= ~Bit(ev->ev.button.button); break; default: break; } } // Handle keys for input boxes if (GetFocusedObject()) { if ( (ev->ev.type == SDL_KEYDOWN && ev->ev.key.keysym.sym != SDLK_ESCAPE && !g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] && !g_keys[SDLK_LALT] && !g_keys[SDLK_RALT]) || ev->ev.type == SDL_HOTKEYDOWN ) { ret = GetFocusedObject()->ManuallyHandleEvent(ev); } // else will return IN_PASS because we never used the button. } return ret; } void CGUI::TickObjects() { CStr action = "tick"; GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, action); // Also update tooltips: m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, this); } void CGUI::SendEventToAll(const CStr& EventName) { // janwas 2006-03-03: spoke with Ykkrosh about EventName case. // when registering, case is converted to lower - this avoids surprise // if someone were to get the case wrong and then not notice their // handler is never called. however, until now, the other end // (sending events here) wasn't converting to lower case, // leading to a similar problem. // now fixed; case is irrelevant since all are converted to lower. GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, EventName.LowerCase()); } //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- CGUI::CGUI() : m_MouseButtons(0), m_FocusedObject(NULL), m_InternalNameNumber(0) { m_BaseObject = new CGUIDummyObject; m_BaseObject->SetGUI(this); // Construct the parent object for all GUI JavaScript things m_ScriptObject = JS_NewObject(g_ScriptingHost.getContext(), NULL, g_ScriptingHost.GetGlobalObject(), NULL); debug_assert(m_ScriptObject != NULL); // How should it handle errors? JS_AddRoot(g_ScriptingHost.getContext(), &m_ScriptObject); } CGUI::~CGUI() { Destroy(); if (m_BaseObject) delete m_BaseObject; if (m_ScriptObject) { // Let it be garbage-collected JS_RemoveRoot(g_ScriptingHost.getContext(), &m_ScriptObject); } } //------------------------------------------------------------------- // Functions //------------------------------------------------------------------- IGUIObject *CGUI::ConstructObject(const CStr& str) { if (m_ObjectTypes.count(str) > 0) return (*m_ObjectTypes[str])(); else { // Error reporting will be handled with the NULL return. return NULL; } } void CGUI::Initialize() { // Add base types! // You can also add types outside the GUI to extend the flexibility of the GUI. // Pyrogenesis though will have all the object types inserted from here. AddObjectType("empty", &CGUIDummyObject::ConstructObject); AddObjectType("button", &CButton::ConstructObject); AddObjectType("image", &CImage::ConstructObject); AddObjectType("text", &CText::ConstructObject); AddObjectType("checkbox", &CCheckBox::ConstructObject); AddObjectType("radiobutton", &CRadioButton::ConstructObject); AddObjectType("progressbar", &CProgressBar::ConstructObject); AddObjectType("minimap", &CMiniMap::ConstructObject); AddObjectType("input", &CInput::ConstructObject); AddObjectType("list", &CList::ConstructObject); AddObjectType("dropdown", &CDropDown::ConstructObject); } void CGUI::Draw() { // Clear the depth buffer, so the GUI is // drawn on top of everything else glClear(GL_DEPTH_BUFFER_BIT); glPushMatrix(); guiLoadIdentity(); try { // Recurse IGUIObject::Draw() with restriction: hidden // meaning all hidden objects won't call Draw (nor will it recurse its children) GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::Draw); } catch (PSERROR_GUI& e) { LOGERROR(L"GUI draw error: %hs", e.what()); } glPopMatrix(); } void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, int CellID, const float& Z, const CRect& Rect, const CRect& UNUSED(Clipping)) { // If the sprite doesn't exist (name == ""), don't bother drawing anything if (Sprite.IsEmpty()) return; // TODO: Clipping? glPushMatrix(); glTranslatef(0.0f, 0.0f, Z); Sprite.Draw(Rect, CellID, m_Sprites); glPopMatrix(); } void CGUI::Destroy() { // We can use the map to delete all // now we don't want to cancel all if one Destroy fails map_pObjects::iterator it; for (it = m_pAllObjects.begin(); it != m_pAllObjects.end(); ++it) { try { it->second->Destroy(); } catch (PSERROR_GUI& e) { UNUSED2(e); debug_warn(L"CGUI::Destroy error"); // TODO Gee: Handle } delete it->second; } for (std::map::iterator it2 = m_Sprites.begin(); it2 != m_Sprites.end(); ++it2) for (std::vector::iterator it3 = it2->second.m_Images.begin(); it3 != it2->second.m_Images.end(); ++it3) delete it3->m_Effects; // Clear all m_pAllObjects.clear(); m_Sprites.clear(); m_Icons.clear(); } void CGUI::UpdateResolution() { // Update ALL cached GUI<>::RecurseObject(0, m_BaseObject, &IGUIObject::UpdateCachedSize ); } void CGUI::AddObject(IGUIObject* pObject) { try { // Add CGUI pointer GUI::RecurseObject(0, pObject, &IGUIObject::SetGUI, this); // Add child to base object m_BaseObject->AddChild(pObject); // can throw // Cache tree GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize); // Loaded GUI::RecurseObject(0, pObject, &IGUIObject::HandleMessage, SGUIMessage(GUIM_LOAD)); } catch (PSERROR_GUI&) { throw; } } void CGUI::UpdateObjects() { // We'll fill a temporary map until we know everything // succeeded map_pObjects AllObjects; try { // Fill freshly GUI< map_pObjects >::RecurseObject(0, m_BaseObject, &IGUIObject::AddToPointersMap, AllObjects ); } catch (PSERROR_GUI&) { // Throw the same error throw; } // Else actually update the real one m_pAllObjects.swap(AllObjects); } bool CGUI::ObjectExists(const CStr& Name) const { return m_pAllObjects.count(Name) != 0; } IGUIObject* CGUI::FindObjectByName(const CStr& Name) const { map_pObjects::const_iterator it = m_pAllObjects.find(Name); if (it == m_pAllObjects.end()) return NULL; else return it->second; } IGUIObject* CGUI::FindObjectUnderMouse() const { IGUIObject* pNearest = NULL; GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest); return pNearest; } void CGUI::SetFocusedObject(IGUIObject* pObject) { if (pObject == m_FocusedObject) return; if (m_FocusedObject) m_FocusedObject->HandleMessage(SGUIMessage(GUIM_LOST_FOCUS)); m_FocusedObject = pObject; if (m_FocusedObject) m_FocusedObject->HandleMessage(SGUIMessage(GUIM_GOT_FOCUS)); } // private struct used only in GenerateText(...) struct SGenerateTextImage { float m_YFrom, // The image's starting location in Y m_YTo, // The image's end location in Y m_Indentation; // The image width in other words // Some help functions // TODO Gee: CRect => CPoint ? void SetupSpriteCall(const bool Left, SGUIText::SSpriteCall &SpriteCall, const float width, const float y, const CSize &Size, const CStr& TextureName, const float BufferZone, const int CellID) { // TODO Gee: Temp hardcoded values SpriteCall.m_Area.top = y+BufferZone; SpriteCall.m_Area.bottom = y+BufferZone + Size.cy; if (Left) { SpriteCall.m_Area.left = BufferZone; SpriteCall.m_Area.right = Size.cx+BufferZone; } else { SpriteCall.m_Area.left = width-BufferZone - Size.cx; SpriteCall.m_Area.right = width-BufferZone; } SpriteCall.m_CellID = CellID; SpriteCall.m_Sprite = TextureName; m_YFrom = SpriteCall.m_Area.top-BufferZone; m_YTo = SpriteCall.m_Area.bottom+BufferZone; m_Indentation = Size.cx+BufferZone*2; } }; SGUIText CGUI::GenerateText(const CGUIString &string, const CStr& Font, const float &Width, const float &BufferZone, const IGUIObject *pObject) { SGUIText Text; // object we're generating if (string.m_Words.size() == 0) return Text; float x=BufferZone, y=BufferZone; // drawing pointer int from=0; bool done=false; bool FirstLine = true; // Necessary because text in the first line is shorter // (it doesn't count the line spacing) // Images on the left or the right side. std::vector Images[2]; int pos_last_img=-1; // Position in the string where last img (either left or right) were encountered. // in order to avoid duplicate processing. // Easier to read. bool WordWrapping = (Width != 0); // Go through string word by word for (int i=0; i<(int)string.m_Words.size()-1 && !done; ++i) { // Pre-process each line one time, so we know which floating images // will be added for that line. // Generated stuff is stored in Feedback. CGUIString::SFeedback Feedback; // Preliminary line_height, used for word-wrapping with floating images. float prelim_line_height=0.f; // Width and height of all text calls generated. string.GenerateTextCall(Feedback, Font, string.m_Words[i], string.m_Words[i+1], FirstLine); // Loop through our images queues, to see if images has been added. // Check if this has already been processed. // Also, floating images are only applicable if Word-Wrapping is on if (WordWrapping && i > pos_last_img) { // Loop left/right for (int j=0; j<2; ++j) { for (std::vector::const_iterator it = Feedback.m_Images[j].begin(); it != Feedback.m_Images[j].end(); ++it) { SGUIText::SSpriteCall SpriteCall; SGenerateTextImage Image; // Y is if no other floating images is above, y. Else it is placed // after the last image, like a stack downwards. float _y; if (Images[j].size() > 0) _y = std::max(y, Images[j].back().m_YTo); else _y = y; // Get Size from Icon database SGUIIcon icon = GetIcon(*it); CSize size = icon.m_Size; Image.SetupSpriteCall((j==CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, icon.m_SpriteName, BufferZone, icon.m_CellID); // Check if image is the lowest thing. Text.m_Size.cy = std::max(Text.m_Size.cy, Image.m_YTo); Images[j].push_back(Image); Text.m_SpriteCalls.push_back(SpriteCall); } } } pos_last_img = std::max(pos_last_img, i); x += Feedback.m_Size.cx; prelim_line_height = std::max(prelim_line_height, Feedback.m_Size.cy); // If Width is 0, then there's no word-wrapping, disable NewLine. if ((WordWrapping && (x > Width-BufferZone || Feedback.m_NewLine)) || i == (int)string.m_Words.size()-2) { // Change 'from' to 'i', but first keep a copy of its value. int temp_from = from; from = i; static const int From=0, To=1; //int width_from=0, width_to=width; float width_range[2]; width_range[From] = BufferZone; width_range[To] = Width - BufferZone; // Floating images are only applicable if word-wrapping is enabled. if (WordWrapping) { // Decide width of the line. We need to iterate our floating images. // this won't be exact because we're assuming the line_height // will be as our preliminary calculation said. But that may change, // although we'd have to add a couple of more loops to try straightening // this problem out, and it is very unlikely to happen noticeably if one // structures his text in a stylistically pure fashion. Even if not, it // is still quite unlikely it will happen. // Loop through left and right side, from and to. for (int j=0; j<2; ++j) { for (std::vector::const_iterator it = Images[j].begin(); it != Images[j].end(); ++it) { // We're working with two intervals here, the image's and the line height's. // let's find the union of these two. float union_from, union_to; union_from = std::max(y, it->m_YFrom); union_to = std::min(y+prelim_line_height, it->m_YTo); // The union is not empty if (union_to > union_from) { if (j == From) width_range[From] = std::max(width_range[From], it->m_Indentation); else width_range[To] = std::min(width_range[To], Width - it->m_Indentation); } } } } // Reset X for the next loop x = width_range[From]; // Now we'll do another loop to figure out the height of // the line (the height of the largest character). This // couldn't be determined in the first loop (main loop) // because it didn't regard images, so we don't know // if all characters processed, will actually be involved // in that line. float line_height=0.f; for (int j=temp_from; j<=i; ++j) { // We don't want to use Feedback now, so we'll have to use // another. CGUIString::SFeedback Feedback2; // Don't attach object, it'll suppress the errors // we want them to be reported in the final GenerateTextCall() // so that we don't get duplicates. string.GenerateTextCall(Feedback2, Font, string.m_Words[j], string.m_Words[j+1], FirstLine); // Append X value. x += Feedback2.m_Size.cx; if (WordWrapping && x > width_range[To] && j!=temp_from && !Feedback2.m_NewLine) break; // Let line_height be the maximum m_Height we encounter. line_height = std::max(line_height, Feedback2.m_Size.cy); if (WordWrapping && Feedback2.m_NewLine) break; } // Reset x once more x = width_range[From]; // Move down, because font drawing starts from the baseline y += line_height; // Do the real processing now for (int j=temp_from; j<=i; ++j) { // We don't want to use Feedback now, so we'll have to use // another one. CGUIString::SFeedback Feedback2; // Defaults string.GenerateTextCall(Feedback2, Font, string.m_Words[j], string.m_Words[j+1], FirstLine, pObject); // Iterate all and set X/Y values // Since X values are not set, we need to make an internal // iteration with an increment that will append the internal // x, that is what x_pointer is for. float x_pointer=0.f; std::vector::iterator it; for (it = Feedback2.m_TextCalls.begin(); it != Feedback2.m_TextCalls.end(); ++it) { it->m_Pos = CPos(x + x_pointer, y); x_pointer += it->m_Size.cx; if (it->m_pSpriteCall) { it->m_pSpriteCall->m_Area += it->m_Pos - CSize(0,it->m_pSpriteCall->m_Area.GetHeight()); } } // Append X value. x += Feedback2.m_Size.cx; Text.m_Size.cx = std::max(Text.m_Size.cx, x+BufferZone); // The first word overrides the width limit, what we // do, in those cases, are just drawing that word even // though it'll extend the object. if (WordWrapping) // only if word-wrapping is applicable { if (Feedback2.m_NewLine) { from = j+1; // Sprite call can exist within only a newline segment, // therefore we need this. Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end()); break; } else if (x > width_range[To] && j==temp_from) { from = j+1; // do not break, since we want it to be added to m_TextCalls } else if (x > width_range[To]) { from = j; break; } } // Add the whole Feedback2.m_TextCalls to our m_TextCalls. Text.m_TextCalls.insert(Text.m_TextCalls.end(), Feedback2.m_TextCalls.begin(), Feedback2.m_TextCalls.end()); Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end()); if (j == (int)string.m_Words.size()-2) done = true; } // Reset X x = 0.f; // Update height of all Text.m_Size.cy = std::max(Text.m_Size.cy, y+BufferZone); FirstLine = false; // Now if we entered as from = i, then we want // i being one minus that, so that it will become // the same i in the next loop. The difference is that // we're on a new line now. i = from-1; } } return Text; } void CGUI::DrawText(SGUIText &Text, const CColor &DefaultColor, const CPos &pos, const float &z, const CRect &clipping) { // TODO Gee: All these really necessary? Some // are defaults and if you changed them // the opposite value at the end of the functions, // some things won't be drawn correctly. 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); if (clipping != CRect()) { double eq[4][4] = { { 0.0, 1.0, 0.0, -clipping.top }, { 1.0, 0.0, 0.0, -clipping.left }, { 0.0, -1.0, 0.0, clipping.bottom }, { -1.0, 0.0, 0.0, clipping.right } }; for (int i=0; i<4; ++i) { glClipPlane(GL_CLIP_PLANE0+i, eq[i]); glEnable(GL_CLIP_PLANE0+i); } } CFont* font = NULL; CStrW LastFontName; for (std::vector::const_iterator it = Text.m_TextCalls.begin(); it != Text.m_TextCalls.end(); ++it) { // If this is just a placeholder for a sprite call, continue if (it->m_pSpriteCall) continue; // Switch fonts when necessary, but remember the last one used if (it->m_Font != LastFontName) { delete font; font = new CFont(it->m_Font); font->Bind(); LastFontName = it->m_Font; } CColor color = it->m_UseCustomColor ? it->m_Color : DefaultColor; glPushMatrix(); // TODO Gee: (2004-09-04) Why are font corrupted if inputted float value? glTranslatef((GLfloat)int(pos.x+it->m_Pos.x), (GLfloat)int(pos.y+it->m_Pos.y), z); glColor4fv(color.FloatArray()); glwprintf(L"%ls", it->m_String.c_str()); // "%ls" is necessary in case m_String contains % symbols glPopMatrix(); } if (font) delete font; for (std::list::iterator it=Text.m_SpriteCalls.begin(); it!=Text.m_SpriteCalls.end(); ++it) { DrawSprite(it->m_Sprite, it->m_CellID, z, it->m_Area + pos); } // TODO To whom it may concern: Thing were not reset, so // I added this line, modify if incorrect -- if (clipping != CRect()) { for (int i=0; i<4; ++i) glDisable(GL_CLIP_PLANE0+i); } glDisable(GL_TEXTURE_2D); // -- GL } bool CGUI::GetPreDefinedColor(const CStr& name, CColor &Output) { if (m_PreDefinedColors.count(name) == 0) { return false; } else { Output = m_PreDefinedColors[name]; return true; } } /** * @callgraph */ void CGUI::LoadXmlFile(const VfsPath& Filename, std::set& Paths) { Paths.insert(Filename); CXeromyces XeroFile; if (XeroFile.Load(g_VFS, Filename) != PSRETURN_OK) // Fail silently return; XMBElement node = XeroFile.GetRoot(); // Check root element's (node) name so we know what kind of // data we'll be expecting CStr root_name (XeroFile.GetElementString(node.GetNodeName())); try { if (root_name == "objects") { Xeromyces_ReadRootObjects(node, &XeroFile, Paths); // Re-cache all values so these gets cached too. //UpdateResolution(); } else if (root_name == "sprites") { Xeromyces_ReadRootSprites(node, &XeroFile); } else if (root_name == "styles") { Xeromyces_ReadRootStyles(node, &XeroFile); } else if (root_name == "setup") { Xeromyces_ReadRootSetup(node, &XeroFile); } else { debug_warn(L"CGUI::LoadXmlFile error"); // TODO Gee: Output in log } } catch (PSERROR_GUI& e) { LOG(CLogger::Error, LOG_CATEGORY, L"Errors loading GUI file %ls (%d)", Filename.string().c_str(), e.getCode()); return; } } //=================================================================== // XML Reading Xeromyces Specific Sub-Routines //=================================================================== void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, std::set& Paths) { int el_script = pFile->GetElementID("script"); std::vector > subst; // Iterate main children // they should all be or