/* Copyright (C) 2021 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 "CHotkeyPicker.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "lib/timer.h"
#include "ps/CLogger.h"
#include "ps/Hotkey.h"
#include "ps/KeyName.h"
#include "scriptinterface/ScriptConversions.h"
const CStr CHotkeyPicker::EventNameCombination = "Combination";
const CStr CHotkeyPicker::EventNameKeyChange = "KeyChange";
// Don't send the scancode, JS doesn't care.
template<> void Script::ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue ret, const CHotkeyPicker::Key& val)
{
Script::ToJSVal(rq, ret, val.scancodeName);
}
// Unused, but JSVAL_VECTOR requires it.
template<> bool Script::FromJSVal(const ScriptRequest&, const JS::HandleValue, CHotkeyPicker::Key&)
{
LOGWARNING("FromJSVal: Not implemented");
return false;
}
JSVAL_VECTOR(CHotkeyPicker::Key);
CHotkeyPicker::CHotkeyPicker(CGUI& pGUI) : IGUIObject(pGUI), m_TimeToCombination(this, "time_to_combination", 1.f)
{
// 8 keys at the same time is probably more than we'll ever need.
m_KeysPressed.reserve(8);
}
CHotkeyPicker::~CHotkeyPicker()
{
}
void CHotkeyPicker::FireEvent(const CStr& event)
{
ScriptRequest rq(*m_pGUI.GetScriptInterface());
JS::RootedValueArray<1> args(rq.cx);
JS::RootedValue keys(rq.cx);
Script::ToJSVal(rq, &keys, m_KeysPressed);
args[0].set(keys);
ScriptEvent(event, args);
}
void CHotkeyPicker::Tick()
{
if (m_KeysPressed.size() == 0)
return;
double time = timer_Time();
if (time - m_LastKeyChange < m_TimeToCombination)
return;
FireEvent(EventNameCombination);
return;
}
void CHotkeyPicker::HandleMessage(SGUIMessage& Message)
{
IGUIObject::HandleMessage(Message);
switch (Message.type)
{
case GUIM_GOT_FOCUS:
case GUIM_LOST_FOCUS:
{
m_KeysPressed.clear();
m_LastKeyChange = timer_Time();
break;
}
default:
break;
}
}
InReaction CHotkeyPicker::PreemptEvent(const SDL_Event_* ev)
{
switch (ev->ev.type)
{
// Handle the same mouse events that hotkeys handle
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEWHEEL:
{
SDL_Scancode scancode;
if (ev->ev.type != SDL_MOUSEWHEEL)
{
// Wait a little bit -> this gets triggered when clicking on a button,
// but after the button click is processed, thus immediately triggering...
if (timer_Time()-m_LastKeyChange < 0.2)
return IN_HANDLED;
// This is from hotkeyHandler - not sure what it does in all honesty.
if(ev->ev.button.button >= SDL_BUTTON_X1)
scancode = static_cast(MOUSE_BASE + (int)ev->ev.button.button + 2);
else
scancode = static_cast(MOUSE_BASE + (int)ev->ev.button.button);
}
else
{
if (ev->ev.wheel.y > 0)
scancode = static_cast(MOUSE_WHEELUP);
else if (ev->ev.wheel.y < 0)
scancode = static_cast(MOUSE_WHEELDOWN);
else if (ev->ev.wheel.x > 0)
scancode = static_cast(MOUSE_X2);
else if (ev->ev.wheel.x < 0)
scancode = static_cast(MOUSE_X1);
else
return IN_HANDLED;
}
// Don't handle keys and mouse together except for modifiers.
m_KeysPressed.erase(std::remove_if(m_KeysPressed.begin(), m_KeysPressed.end(), [](const Key& k) {
return static_cast(k.code) < UNIFIED_SHIFT || static_cast(k.code) >= UNIFIED_LAST; } ), m_KeysPressed.end());
m_KeysPressed.emplace_back(Key{scancode, FindScancodeName(scancode)});
// For mouse events, assume we immediately want to return.
FireEvent(EventNameCombination);
return IN_HANDLED;
}
case SDL_KEYDOWN:
case SDL_KEYUP:
{
SDL_Scancode scancode = ev->ev.key.keysym.scancode;
// Don't handle caps-lock, it doesn't really work in-game and it's a weird hotkey.
if (scancode == SDL_SCANCODE_CAPSLOCK)
return IN_PASS;
if (scancode == SDL_SCANCODE_LSHIFT || scancode == SDL_SCANCODE_RSHIFT)
scancode = static_cast(UNIFIED_SHIFT);
else if (scancode == SDL_SCANCODE_LCTRL || scancode == SDL_SCANCODE_RCTRL)
scancode = static_cast(UNIFIED_CTRL);
else if (scancode == SDL_SCANCODE_LALT || scancode == SDL_SCANCODE_RALT)
scancode = static_cast(UNIFIED_ALT);
else if (scancode == SDL_SCANCODE_LGUI || scancode == SDL_SCANCODE_RGUI)
scancode = static_cast(UNIFIED_SUPER);
if (ev->ev.type == SDL_KEYDOWN)
{
std::vector::const_iterator it = \
std::find_if(m_KeysPressed.begin(), m_KeysPressed.end(), [&scancode](Key& k) { return k.code == scancode; });
// Can happen if multiple keys are mapped the same.
if (it != m_KeysPressed.end())
return IN_HANDLED;
m_KeysPressed.emplace_back(Key{scancode, FindScancodeName(scancode)});
}
else
{
std::vector::const_iterator it = \
std::find_if(m_KeysPressed.begin(), m_KeysPressed.end(), [&scancode](Key& k) { return k.code == scancode; });
// Might happen if a key was down before this object is created.
if (it == m_KeysPressed.end())
return IN_HANDLED;
m_KeysPressed.erase(it);
}
FireEvent(EventNameKeyChange);
// Register after-JS in case this takes a while (probably not but it doesn't hurt).
m_LastKeyChange = timer_Time();
return IN_HANDLED;
}
default:
{
return IN_PASS;
}
}
}