378 lines
9.9 KiB
C++
378 lines
9.9 KiB
C++
/**
|
|
* @file llsetkeybinddialog.cpp
|
|
* @brief LLSetKeyBindDialog class implementation.
|
|
*
|
|
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2019, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "llsetkeybinddialog.h"
|
|
|
|
#include "llbutton.h"
|
|
#include "llcheckboxctrl.h"
|
|
#include "lleventtimer.h"
|
|
#include "llfloaterreg.h"
|
|
#include "llfocusmgr.h"
|
|
#include "llkeyconflict.h"
|
|
#include "llviewercontrol.h"
|
|
|
|
class LLSetKeyBindDialog::Updater : public LLEventTimer
|
|
{
|
|
public:
|
|
|
|
typedef boost::function<void(MASK)> callback_t;
|
|
|
|
Updater(callback_t cb, F32 period, MASK mask)
|
|
:LLEventTimer(period),
|
|
mMask(mask),
|
|
mCallback(cb)
|
|
{
|
|
mEventTimer.start();
|
|
}
|
|
|
|
virtual ~Updater(){}
|
|
|
|
protected:
|
|
BOOL tick()
|
|
{
|
|
mCallback(mMask);
|
|
// Deletes itseft after execution
|
|
return TRUE;
|
|
}
|
|
|
|
private:
|
|
MASK mMask;
|
|
callback_t mCallback;
|
|
};
|
|
|
|
bool LLSetKeyBindDialog::sRecordKeys = false;
|
|
|
|
LLSetKeyBindDialog::LLSetKeyBindDialog(const LLSD& key)
|
|
: LLModalDialog(key),
|
|
pParent(NULL),
|
|
mKeyFilterMask(DEFAULT_KEY_FILTER),
|
|
pUpdater(NULL),
|
|
mLastMaskKey(0),
|
|
mContextConeOpacity(0.f),
|
|
mContextConeInAlpha(CONTEXT_CONE_IN_ALPHA),
|
|
mContextConeOutAlpha(CONTEXT_CONE_OUT_ALPHA),
|
|
mContextConeFadeTime(CONTEXT_CONE_FADE_TIME)
|
|
{
|
|
}
|
|
|
|
LLSetKeyBindDialog::~LLSetKeyBindDialog()
|
|
{
|
|
}
|
|
|
|
//virtual
|
|
BOOL LLSetKeyBindDialog::postBuild()
|
|
{
|
|
childSetAction("SetEmpty", onBlank, this);
|
|
childSetAction("Default", onDefault, this);
|
|
childSetAction("Cancel", onCancel, this);
|
|
getChild<LLUICtrl>("Cancel")->setFocus(TRUE);
|
|
|
|
pCheckBox = getChild<LLCheckBoxCtrl>("apply_all");
|
|
pDescription = getChild<LLTextBase>("description");
|
|
|
|
gFocusMgr.setKeystrokesOnly(TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//virtual
|
|
void LLSetKeyBindDialog::onOpen(const LLSD& data)
|
|
{
|
|
sRecordKeys = true;
|
|
LLModalDialog::onOpen(data);
|
|
}
|
|
|
|
//virtual
|
|
void LLSetKeyBindDialog::onClose(bool app_quiting)
|
|
{
|
|
sRecordKeys = false;
|
|
if (pParent)
|
|
{
|
|
pParent->onCancelKeyBind();
|
|
pParent = NULL;
|
|
}
|
|
if (pUpdater)
|
|
{
|
|
// Doubleclick timer has't fired, delete it
|
|
delete pUpdater;
|
|
pUpdater = NULL;
|
|
}
|
|
LLModalDialog::onClose(app_quiting);
|
|
}
|
|
|
|
void LLSetKeyBindDialog::drawFrustum()
|
|
{
|
|
static LLCachedControl<F32> max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f);
|
|
drawConeToOwner(mContextConeOpacity, max_opacity, mFrustumOrigin.get(), mContextConeFadeTime, mContextConeInAlpha, mContextConeOutAlpha);
|
|
}
|
|
|
|
//virtual
|
|
void LLSetKeyBindDialog::draw()
|
|
{
|
|
drawFrustum();
|
|
LLModalDialog::draw();
|
|
}
|
|
|
|
void LLSetKeyBindDialog::setParent(LLKeyBindResponderInterface* parent, LLView* frustum_origin, U32 key_mask)
|
|
{
|
|
pParent = parent;
|
|
mFrustumOrigin = frustum_origin->getHandle();
|
|
mKeyFilterMask = key_mask;
|
|
|
|
std::string input;
|
|
if ((key_mask & ALLOW_MOUSE) != 0)
|
|
{
|
|
input = getString("mouse");
|
|
}
|
|
if ((key_mask & ALLOW_KEYS) != 0)
|
|
{
|
|
if (!input.empty())
|
|
{
|
|
input += ", ";
|
|
}
|
|
input += getString("keyboard");
|
|
}
|
|
pDescription->setText(getString("basic_description"));
|
|
pDescription->setTextArg("[INPUT]", input);
|
|
}
|
|
|
|
// static
|
|
bool LLSetKeyBindDialog::recordKey(KEY key, MASK mask, BOOL down)
|
|
{
|
|
if (sRecordKeys)
|
|
{
|
|
LLSetKeyBindDialog* dialog = LLFloaterReg::getTypedInstance<LLSetKeyBindDialog>("keybind_dialog", LLSD());
|
|
if (dialog && dialog->getVisible())
|
|
{
|
|
return dialog->recordAndHandleKey(key, mask, down);
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "Key recording was set despite no open dialog" << LL_ENDL;
|
|
sRecordKeys = false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LLSetKeyBindDialog::recordAndHandleKey(KEY key, MASK mask, BOOL down)
|
|
{
|
|
if ((key == 'Q' && mask == MASK_CONTROL)
|
|
|| key == KEY_ESCAPE)
|
|
{
|
|
sRecordKeys = false;
|
|
closeFloater();
|
|
return true;
|
|
}
|
|
|
|
if (key == KEY_DELETE)
|
|
{
|
|
setKeyBind(CLICK_NONE, KEY_NONE, MASK_NONE, false);
|
|
sRecordKeys = false;
|
|
closeFloater();
|
|
return false;
|
|
}
|
|
|
|
// forbidden keys
|
|
if (key == KEY_NONE
|
|
|| key == KEY_RETURN
|
|
|| key == KEY_BACKSPACE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (key == KEY_CONTROL || key == KEY_SHIFT || key == KEY_ALT)
|
|
{
|
|
// Mask keys get special treatment
|
|
if ((mKeyFilterMask & ALLOW_MASKS) == 0)
|
|
{
|
|
// Masks by themself are not allowed
|
|
return false;
|
|
}
|
|
if (down == TRUE)
|
|
{
|
|
// Most keys are handled on 'down' event because menu is handled on 'down'
|
|
// masks are exceptions to let other keys be handled
|
|
mLastMaskKey = key;
|
|
return false;
|
|
}
|
|
if (mLastMaskKey != key)
|
|
{
|
|
// This was mask+key combination that got rejected, don't handle mask's key
|
|
// Or user did something like: press shift, press ctrl, release shift
|
|
return false;
|
|
}
|
|
// Mask up event often generates things like 'shift key + shift mask', filter it out.
|
|
if (key == KEY_CONTROL)
|
|
{
|
|
mask &= ~MASK_CONTROL;
|
|
}
|
|
if (key == KEY_SHIFT)
|
|
{
|
|
mask &= ~MASK_SHIFT;
|
|
}
|
|
if (key == KEY_ALT)
|
|
{
|
|
mask &= ~MASK_ALT;
|
|
}
|
|
}
|
|
if ((mKeyFilterMask & ALLOW_KEYS) == 0)
|
|
{
|
|
// basic keys not allowed
|
|
return false;
|
|
}
|
|
else if ((mKeyFilterMask & ALLOW_MASK_KEYS) == 0 && mask != 0)
|
|
{
|
|
// masked keys not allowed
|
|
return false;
|
|
}
|
|
|
|
if (LLKeyConflictHandler::isReservedByMenu(key, mask))
|
|
{
|
|
pDescription->setText(getString("reserved_by_menu"));
|
|
pDescription->setTextArg("[KEYSTR]", LLKeyboard::stringFromAccelerator(mask,key));
|
|
mLastMaskKey = 0;
|
|
return true;
|
|
}
|
|
|
|
setKeyBind(CLICK_NONE, key, mask, pCheckBox->getValue().asBoolean());
|
|
// Note/Todo: To warranty zero interference we should also consume
|
|
// an 'up' event if we recorded on 'down', not just close floater
|
|
// on first recorded combination.
|
|
sRecordKeys = false;
|
|
closeFloater();
|
|
return true;
|
|
}
|
|
|
|
BOOL LLSetKeyBindDialog::handleAnyMouseClick(S32 x, S32 y, MASK mask, EMouseClickType clicktype, BOOL down)
|
|
{
|
|
BOOL result = FALSE;
|
|
if (!pParent)
|
|
{
|
|
// we already processed 'down' event, this is 'up', consume
|
|
closeFloater();
|
|
result = TRUE;
|
|
}
|
|
if (!result && clicktype == CLICK_LEFT)
|
|
{
|
|
// try handling buttons first
|
|
if (down)
|
|
{
|
|
result = LLView::handleMouseDown(x, y, mask);
|
|
}
|
|
else
|
|
{
|
|
result = LLView::handleMouseUp(x, y, mask);
|
|
}
|
|
if (result)
|
|
{
|
|
setFocus(TRUE);
|
|
gFocusMgr.setKeystrokesOnly(TRUE);
|
|
}
|
|
// ignore selection related combinations
|
|
else if (down && (mask & (MASK_SHIFT | MASK_CONTROL)) == 0)
|
|
{
|
|
// this can be a double click, wait a bit;
|
|
if (!pUpdater)
|
|
{
|
|
// Note: default doubleclick time is 500ms, but can stretch up to 5s
|
|
pUpdater = new Updater(boost::bind(&onClickTimeout, this, _1), 0.7f, mask);
|
|
result = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!result
|
|
&& (clicktype != CLICK_LEFT) // subcases were handled above
|
|
&& ((mKeyFilterMask & ALLOW_MOUSE) != 0)
|
|
&& (clicktype != CLICK_RIGHT || mask != 0) // reassigning menu button is not supported
|
|
&& ((mKeyFilterMask & ALLOW_MASK_MOUSE) != 0 || mask == 0)) // reserved for selection
|
|
{
|
|
setKeyBind(clicktype, KEY_NONE, mask, pCheckBox->getValue().asBoolean());
|
|
result = TRUE;
|
|
if (!down)
|
|
{
|
|
// wait for 'up' event before closing
|
|
// alternative: set pUpdater
|
|
closeFloater();
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//static
|
|
void LLSetKeyBindDialog::onCancel(void* user_data)
|
|
{
|
|
LLSetKeyBindDialog* self = (LLSetKeyBindDialog*)user_data;
|
|
self->closeFloater();
|
|
}
|
|
|
|
//static
|
|
void LLSetKeyBindDialog::onBlank(void* user_data)
|
|
{
|
|
LLSetKeyBindDialog* self = (LLSetKeyBindDialog*)user_data;
|
|
// tmp needs 'no key' button
|
|
self->setKeyBind(CLICK_NONE, KEY_NONE, MASK_NONE, false);
|
|
self->closeFloater();
|
|
}
|
|
|
|
//static
|
|
void LLSetKeyBindDialog::onDefault(void* user_data)
|
|
{
|
|
LLSetKeyBindDialog* self = (LLSetKeyBindDialog*)user_data;
|
|
if (self->pParent)
|
|
{
|
|
self->pParent->onDefaultKeyBind(self->pCheckBox->getValue().asBoolean());
|
|
self->pParent = NULL;
|
|
}
|
|
self->closeFloater();
|
|
}
|
|
|
|
//static
|
|
void LLSetKeyBindDialog::onClickTimeout(void* user_data, MASK mask)
|
|
{
|
|
LLSetKeyBindDialog* self = (LLSetKeyBindDialog*)user_data;
|
|
|
|
// timer will delete itself after timeout
|
|
self->pUpdater = NULL;
|
|
|
|
self->setKeyBind(CLICK_LEFT, KEY_NONE, mask, self->pCheckBox->getValue().asBoolean());
|
|
self->closeFloater();
|
|
}
|
|
|
|
void LLSetKeyBindDialog::setKeyBind(EMouseClickType click, KEY key, MASK mask, bool all_modes)
|
|
{
|
|
if (pParent)
|
|
{
|
|
pParent->onSetKeyBind(click, key, mask, all_modes);
|
|
pParent = NULL;
|
|
}
|
|
}
|
|
|