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 SDL_VERSION_ATLEAST(2, 0, 0) if ((int)ev->ev.button.button <= SDL_BUTTON_RIGHT) #elif SDL_VERSION_ATLEAST(1, 2, 13) if ((int)ev->ev.button.button <= SDL_BUTTON_X2) #else if ((int)ev->ev.button.button <= SDL_BUTTON_WHEELDOWN) #endif { keycode = CUSTOM_SDL_KEYCODE + (int)ev->ev.button.button; break; } return IN_PASS; #if SDL_VERSION_ATLEAST(2, 0, 0) case SDL_MOUSEWHEEL: if (ev->ev.wheel.y > 0) { keycode = MOUSE_WHEELUP; break; } else if (ev->ev.wheel.y < 0) { keycode = MOUSE_WHEELDOWN; break; } return IN_PASS; #endif case SDL_HOTKEYDOWN: g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = true; return IN_PASS; case SDL_HOTKEYUP: g_HotkeyStatus[static_cast<const char*>(ev->ev.user.data1)] = false; return IN_PASS; default: return IN_PASS; } // Rather ugly hack to make the '"' key work better on a MacBook Pro on Windows so it doesn't // always close the console. (Maybe this would be better handled in wsdl or something?) if (keycode == SDLK_BACKQUOTE && (ev->ev.key.keysym.unicode == '\'' || ev->ev.key.keysym.unicode == '"')) keycode = ev->ev.key.keysym.unicode; // 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 ); } #if SDL_VERSION_ATLEAST(2, 0, 0) else if( ( keycode == SDLK_LGUI ) || ( keycode == SDLK_RGUI ) ) #else // SDL 1.2 else if( ( keycode == SDLK_LSUPER ) || ( keycode == SDLK_RSUPER ) || ( keycode == SDLK_LMETA ) || ( keycode == SDLK_RMETA) ) #endif { phantom.ev.key.keysym.sym = (SDLKEY)UNIFIED_SUPER; unified[3] = ( 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 real keys (not fake mouse button // events) while the console is up. bool consoleCapture = false; if( g_Console->IsActive() && keycode < CUSTOM_SDL_KEYCODE ) consoleCapture = true; // 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) #if SDL_VERSION_ATLEAST(2, 0, 0) bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ) || (ev->ev.type == SDL_MOUSEWHEEL); #else bool typeKeyDown = ( ev->ev.type == SDL_KEYDOWN ) || ( ev->ev.type == SDL_MOUSEBUTTONDOWN ); #endif // -- KEYDOWN SECTION -- std::vector<const char*> closestMapNames; size_t closestMapMatch = 0; for( std::vector<SHotkeyMapping>::iterator it = g_HotkeyMap[keycode].begin(); it < g_HotkeyMap[keycode].end(); ++it ) { // If a key has been pressed, and this event triggers on its release, skip it. // Similarly, if the key's been released and the event triggers on a keypress, skip it. if( it->negated == typeKeyDown ) continue; // Check to see if all auxiliary keys are down bool accept = true; for( std::vector<SKey>::iterator itKey = it->requires.begin(); itKey != it->requires.end(); ++itKey ) { bool rqdState = !itKey->negated; if( (int)itKey->code < CUSTOM_SDL_KEYCODE ) { if( g_keys[itKey->code] != rqdState ) accept = false; } else if( (int)itKey->code < UNIFIED_SHIFT ) { if( g_mouse_buttons[itKey->code - CUSTOM_SDL_KEYCODE] != rqdState ) accept = false; } else if( (int)itKey->code < UNIFIED_LAST ) { if( unified[itKey->code - UNIFIED_SHIFT] != rqdState ) accept = false; } } if( accept && !( consoleCapture && it->name != "console.toggle" ) ) { // Check if this is an equally precise or more precise match if( it->requires.size() + 1 >= closestMapMatch ) { // Check if more precise if( it->requires.size() + 1 > closestMapMatch ) { // Throw away the old less-precise matches closestMapNames.clear(); closestMapMatch = it->requires.size() + 1; } closestMapNames.push_back(it->name.c_str()); } } } for (size_t i = 0; i < closestMapNames.size(); ++i) { SDL_Event_ hotkeyNotification; hotkeyNotification.ev.type = SDL_HOTKEYDOWN; hotkeyNotification.ev.user.data1 = const_cast<char*>(closestMapNames[i]); in_push_priority_event(&hotkeyNotification); } // -- KEYUP SECTION -- for( std::vector<SHotkeyMapping>::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->negated != typeKeyDown ) continue; // Check to see if all auxiliary keys are down bool accept = true; for( std::vector<SKey>::iterator itKey = it->requires.begin(); itKey != it->requires.end(); ++itKey ) { bool rqdState = !itKey->negated; if( (int)itKey->code < CUSTOM_SDL_KEYCODE ) { if( g_keys[itKey->code] != rqdState ) accept = false; } else if( (int)itKey->code < UNIFIED_SHIFT ) { if( g_mouse_buttons[itKey->code - CUSTOM_SDL_KEYCODE] != rqdState ) accept = false; } else if( (int)itKey->code < UNIFIED_LAST ) { if( unified[itKey->code - UNIFIED_SHIFT] != rqdState ) accept = false; } } if( accept ) { SDL_Event_ hotkeyNotification; hotkeyNotification.ev.type = SDL_HOTKEYUP; hotkeyNotification.ev.user.data1 = const_cast<char*>(it->name.c_str()); in_push_priority_event(&hotkeyNotification); } } return( IN_PASS ); }
InReaction CGUI::HandleEvent(const SDL_Event_* ev) { InReaction ret = IN_PASS; if (ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_HOTKEYUP) { const char* hotkey = static_cast<const char*>(ev->ev.user.data1); std::map<CStr, std::vector<IGUIObject*> >::iterator it = m_HotkeyObjects.find(hotkey); if (it != m_HotkeyObjects.end()) for (IGUIObject* const& obj : it->second) { // Update hotkey status before sending the event, // else the status will be outdated when processing the GUI event. HotkeyInputHandler(ev); ret = IN_HANDLED; if (ev->ev.type == SDL_HOTKEYDOWN) obj->SendEvent(GUIM_PRESSED, "press"); else obj->SendEvent(GUIM_RELEASED, "release"); } } 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 * g_GuiScale, (float)ev->ev.motion.y * g_GuiScale); SGUIMessage msg(GUIM_MOUSE_MOTION); GUI<SGUIMessage>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::HandleMessage, msg); } // 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<unsigned int>(ev->ev.button.button); break; default: break; } } // Update m_MousePos (for delayed mouse button events) CPos oldMousePos = m_MousePos; if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) { m_MousePos = CPos((float)ev->ev.button.x * g_GuiScale, (float)ev->ev.button.y * g_GuiScale); } // 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(); // 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<IGUIObject*>::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) ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_LEFT, "mouseleftpress"); break; case SDL_BUTTON_RIGHT: if (pNearest) ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_RIGHT, "mouserightpress"); break; default: break; } } else if (ev->ev.type == SDL_MOUSEWHEEL && pNearest) { if (ev->ev.wheel.y < 0) ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_DOWN, "mousewheeldown"); else if (ev->ev.wheel.y > 0) ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_UP, "mousewheelup"); } 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(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT, "mouseleftdoubleclick"); else ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_LEFT, "mouseleftrelease"); } 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(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_RIGHT, "mouserightdoubleclick"); else ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_RIGHT, "mouserightrelease"); } break; } // Reset all states on all visible objects GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ResetStates); // Since the hover state will have been reset, we reload it. GUI<IGUIObject*>::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); } } catch (PSERROR_GUI& e) { UNUSED2(e); debug_warn(L"CGUI::HandleEvent error"); // TODO Gee: Handle } // 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<unsigned int>(ev->ev.button.button); break; default: break; } } // Restore m_MousePos (for delayed mouse button events) if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) m_MousePos = oldMousePos; // 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 || ev->ev.type == SDL_TEXTINPUT || ev->ev.type == SDL_TEXTEDITING) { ret = GetFocusedObject()->ManuallyHandleEvent(ev); } // else will return IN_PASS because we never used the button. } return ret; }