phoenix-firestorm/indra/newview/llkeyconflict.cpp

979 lines
31 KiB
C++

/**
* @file llkeyconflict.cpp
* @brief
*
* $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$
*/
/*
* App-wide preferences. Note that these are not per-user,
* because we need to load many preferences before we have
* a login name.
*/
#include "llviewerprecompiledheaders.h"
#include "llkeyconflict.h"
#include "llinitparam.h"
#include "llkeyboard.h"
#include "lltrans.h"
#include "llviewercontrol.h"
#include "llviewerinput.h"
#include "llviewermenu.h"
#include "llxuiparser.h"
static const std::string saved_settings_key_controls[] = { "placeholder" }; // add settings from gSavedSettings here
static const std::string filename_default = "key_bindings.xml";
static const std::string filename_temporary = "key_bindings_tmp.xml"; // used to apply uncommited changes on the go.
// LLKeyboard::stringFromMask is meant for UI and is OS dependent,
// so this class uses it's own version
std::string string_from_mask(MASK mask)
{
std::string res;
if ((mask & MASK_CONTROL) != 0)
{
res = "CTL";
}
if ((mask & MASK_ALT) != 0)
{
if (!res.empty()) res += "_";
res += "ALT";
}
if ((mask & MASK_SHIFT) != 0)
{
if (!res.empty()) res += "_";
res += "SHIFT";
}
if (mask == MASK_NONE)
{
res = "NONE";
}
return res;
}
// LLKeyConflictHandler
S32 LLKeyConflictHandler::sTemporaryFileUseCount = 0;
LLKeyConflictHandler::LLKeyConflictHandler()
: mHasUnsavedChanges(false),
mUsesTemporaryFile(false),
mLoadMode(MODE_COUNT)
{
}
LLKeyConflictHandler::LLKeyConflictHandler(ESourceMode mode)
: mHasUnsavedChanges(false),
mUsesTemporaryFile(false),
mLoadMode(mode)
{
loadFromSettings(mode);
}
LLKeyConflictHandler::~LLKeyConflictHandler()
{
clearUnsavedChanges();
// Note: does not reset bindings if temporary file was used
}
bool LLKeyConflictHandler::canHandleControl(const std::string &control_name, EMouseClickType mouse_ind, KEY key, MASK mask)
{
return mControlsMap[control_name].canHandle(mouse_ind, key, mask);
}
bool LLKeyConflictHandler::canHandleKey(const std::string &control_name, KEY key, MASK mask)
{
return canHandleControl(control_name, CLICK_NONE, key, mask);
}
bool LLKeyConflictHandler::canHandleMouse(const std::string &control_name, EMouseClickType mouse_ind, MASK mask)
{
return canHandleControl(control_name, mouse_ind, KEY_NONE, mask);
}
bool LLKeyConflictHandler::canHandleMouse(const std::string &control_name, S32 mouse_ind, MASK mask)
{
return canHandleControl(control_name, (EMouseClickType)mouse_ind, KEY_NONE, mask);
}
bool LLKeyConflictHandler::canAssignControl(const std::string &control_name)
{
control_map_t::iterator iter = mControlsMap.find(control_name);
if (iter != mControlsMap.end())
{
return iter->second.mAssignable;
}
// If we don't know this control, means it wasn't assigned by user yet and thus is editable
return true;
}
// static
bool LLKeyConflictHandler::isReservedByMenu(const KEY &key, const MASK &mask)
{
if (key == KEY_NONE)
{
return false;
}
// At the moment controls are only applicable inworld,
// ignore gLoginMenuBarView
return gMenuBarView && gMenuBarView->hasAccelerator(key, mask);
}
// static
bool LLKeyConflictHandler::isReservedByMenu(const LLKeyData &data)
{
if (data.mMouse != CLICK_NONE || data.mKey == KEY_NONE)
{
return false;
}
return gMenuBarView && gMenuBarView->hasAccelerator(data.mKey, data.mMask);
}
bool LLKeyConflictHandler::registerControl(const std::string &control_name, U32 index, EMouseClickType mouse, KEY key, MASK mask, bool ignore_mask)
{
if (control_name.empty())
{
return false;
}
LLKeyConflict &type_data = mControlsMap[control_name];
if (!type_data.mAssignable)
{
// Example: user tried to assign camera spin to all modes, but first person mode doesn't support it
return false;
}
LLKeyData data(mouse, key, mask, ignore_mask);
if (type_data.mKeyBind.getKeyData(index) == data)
{
return true;
}
if (isReservedByMenu(data))
{
return false;
}
if (removeConflicts(data, type_data.mConflictMask))
{
type_data.mKeyBind.replaceKeyData(data, index);
mHasUnsavedChanges = true;
return true;
}
// control already in use/blocked
return false;
}
bool LLKeyConflictHandler::clearControl(const std::string &control_name, U32 data_index)
{
if (control_name.empty())
{
return false;
}
LLKeyConflict &type_data = mControlsMap[control_name];
if (!type_data.mAssignable)
{
// Example: user tried to assign camera spin to all modes, but first person mode doesn't support it
return false;
}
type_data.mKeyBind.resetKeyData(data_index);
mHasUnsavedChanges = true;
return true;
}
LLKeyData LLKeyConflictHandler::getControl(const std::string &control_name, U32 index)
{
if (control_name.empty())
{
return LLKeyData();
}
return mControlsMap[control_name].getKeyData(index);
}
bool LLKeyConflictHandler::isControlEmpty(const std::string &control_name)
{
if (control_name.empty())
{
return true;
}
return mControlsMap[control_name].mKeyBind.isEmpty();
}
// static
std::string LLKeyConflictHandler::getStringFromKeyData(const LLKeyData& keydata)
{
std::string result;
if (keydata.mMask != MASK_NONE && keydata.mKey != KEY_NONE)
{
result = LLKeyboard::stringFromAccelerator(keydata.mMask, keydata.mKey);
}
else if (keydata.mKey != KEY_NONE)
{
result = LLKeyboard::stringFromKey(keydata.mKey);
}
else if (keydata.mMask != MASK_NONE)
{
result = LLKeyboard::stringFromAccelerator(keydata.mMask);
}
result += LLKeyboard::stringFromMouse(keydata.mMouse);
return result;
}
std::string LLKeyConflictHandler::getControlString(const std::string &control_name, U32 index)
{
if (control_name.empty())
{
return "";
}
return getStringFromKeyData(mControlsMap[control_name].getKeyData(index));
}
void LLKeyConflictHandler::loadFromControlSettings(const std::string &name)
{
LLControlVariablePtr var = gSavedSettings.getControl(name);
if (var)
{
LLKeyBind bind(var->getValue());
LLKeyConflict key(bind, true, 0);
mControlsMap[name] = key;
}
}
void LLKeyConflictHandler::loadFromSettings(const LLViewerInput::KeyMode& keymode, control_map_t *destination)
{
for (LLInitParam::ParamIterator<LLViewerInput::KeyBinding>::const_iterator it = keymode.bindings.begin(),
end_it = keymode.bindings.end();
it != end_it;
++it)
{
KEY key;
MASK mask;
EMouseClickType mouse = CLICK_NONE;
if (it->mouse.isProvided())
{
LLViewerInput::mouseFromString(it->mouse.getValue(), &mouse);
}
if (it->key.getValue().empty())
{
key = KEY_NONE;
}
else
{
LLKeyboard::keyFromString(it->key, &key);
}
LLKeyboard::maskFromString(it->mask, &mask);
// Note: it->command is also the name of UI element, howhever xml we are loading from
// might not know all the commands, so UI will have to know what to fill by its own
// Assumes U32_MAX conflict mask, and is assignable by default,
// but assignability might have been overriden by generatePlaceholders.
LLKeyConflict &type_data = (*destination)[it->command];
type_data.mKeyBind.addKeyData(mouse, key, mask, true);
}
}
bool LLKeyConflictHandler::loadFromSettings(const ESourceMode &load_mode, const std::string &filename, control_map_t *destination)
{
if (filename.empty())
{
return false;
}
bool res = false;
LLViewerInput::Keys keys;
LLSimpleXUIParser parser;
if (parser.readXUI(filename, keys)
&& keys.validateBlock())
{
switch (load_mode)
{
case MODE_FIRST_PERSON:
if (keys.first_person.isProvided())
{
loadFromSettings(keys.first_person, destination);
res = true;
}
break;
case MODE_THIRD_PERSON:
if (keys.third_person.isProvided())
{
loadFromSettings(keys.third_person, destination);
res = true;
}
break;
case MODE_EDIT_AVATAR:
if (keys.edit_avatar.isProvided())
{
loadFromSettings(keys.edit_avatar, destination);
res = true;
}
break;
case MODE_SITTING:
if (keys.sitting.isProvided())
{
loadFromSettings(keys.sitting, destination);
res = true;
}
break;
default:
LL_ERRS() << "Not implememted mode " << load_mode << LL_ENDL;
break;
}
}
return res;
}
void LLKeyConflictHandler::loadFromSettings(ESourceMode load_mode)
{
mControlsMap.clear();
mDefaultsMap.clear();
// E.X. In case we need placeholder keys for conflict resolution.
generatePlaceholders(load_mode);
if (load_mode == MODE_SAVED_SETTINGS)
{
// load settings clss knows about, but it also possible to load settings by name separately
const S32 size = std::extent<decltype(saved_settings_key_controls)>::value;
for (U32 i = 0; i < size; i++)
{
loadFromControlSettings(saved_settings_key_controls[i]);
}
}
else
{
// load defaults
std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, filename_default);
if (!loadFromSettings(load_mode, filename, &mDefaultsMap))
{
LL_WARNS() << "Failed to load default settings, aborting" << LL_ENDL;
return;
}
// load user's
filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename_default);
if (!gDirUtilp->fileExists(filename) || !loadFromSettings(load_mode, filename, &mControlsMap))
{
// Mind placeholders
// Do not use mControlsMap.insert(mDefaultsMap) since mControlsMap has
// placeholders that won't be added over(to) by insert.
// Or instead move generatePlaceholders call to be after copying
control_map_t::iterator iter = mDefaultsMap.begin();
while (iter != mDefaultsMap.end())
{
mControlsMap[iter->first].mKeyBind = iter->second.mKeyBind;
iter++;
}
}
}
mLoadMode = load_mode;
}
void LLKeyConflictHandler::saveToSettings(bool temporary)
{
if (mControlsMap.empty())
{
return;
}
if (mLoadMode == MODE_SAVED_SETTINGS)
{
// Does not support 'temporary', preferences handle that themself
// so in case of saved settings we just do not clear mHasUnsavedChanges
control_map_t::iterator iter = mControlsMap.begin();
control_map_t::iterator end = mControlsMap.end();
for (; iter != end; ++iter)
{
if (iter->first.empty())
{
continue;
}
LLKeyConflict &key = iter->second;
key.mKeyBind.trimEmpty();
if (!key.mAssignable)
{
continue;
}
if (gSavedSettings.controlExists(iter->first))
{
gSavedSettings.setLLSD(iter->first, key.mKeyBind.asLLSD());
}
else if (!key.mKeyBind.empty())
{
// Note: this is currently not in use, might be better for load mechanics to ask for and retain control group
// otherwise settings loaded from other control groups will end in gSavedSettings
LL_INFOS() << "Creating new keybinding " << iter->first << LL_ENDL;
gSavedSettings.declareLLSD(iter->first, key.mKeyBind.asLLSD(), "comment", LLControlVariable::PERSIST_ALWAYS);
}
}
}
else
{
// Determine what file to load and load full copy of that file
std::string filename;
if (temporary)
{
filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename_temporary);
if (!gDirUtilp->fileExists(filename))
{
filename.clear();
}
}
if (filename.empty())
{
filename = gDirUtilp->findFile(filename_default,
gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, ""),
gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, ""));
}
LLViewerInput::Keys keys;
LLSimpleXUIParser parser;
if (parser.readXUI(filename, keys)
&& keys.validateBlock())
{
// replace category we edited
// mode is a HACK to correctly reset bindings without reparsing whole file and avoid doing
// own param container (which will face issues with inasseesible members of LLInitParam)
LLViewerInput::KeyMode mode;
LLViewerInput::KeyBinding binding;
control_map_t::iterator iter = mControlsMap.begin();
control_map_t::iterator end = mControlsMap.end();
for (; iter != end; ++iter)
{
// By default xml have (had) up to 6 elements per function
// eventually it will be cleaned up and UI will only shows 3 per function,
// so make sure to cleanup.
// Also this helps in keeping file small.
iter->second.mKeyBind.trimEmpty();
U32 size = iter->second.mKeyBind.getDataCount();
for (U32 i = 0; i < size; ++i)
{
if (iter->first.empty())
{
continue;
}
LLKeyConflict &key = iter->second;
key.mKeyBind.trimEmpty();
if (key.mKeyBind.empty() || !key.mAssignable)
{
continue;
}
LLKeyData data = key.mKeyBind.getKeyData(i);
// Still write empty LLKeyData to make sure we will maintain UI position
if (data.mKey == KEY_NONE)
{
// Might be better idea to be consistent and use NONE. LLViewerInput can work with both cases
binding.key = "";
}
else
{
binding.key = LLKeyboard::stringFromKey(data.mKey, false /*Do not localize*/);
}
binding.mask = string_from_mask(data.mMask);
if (data.mMouse == CLICK_NONE)
{
binding.mouse.setProvided(false);
}
else
{
// set() because 'optional', for compatibility purposes
// just copy old keys.xml and rename to key_bindings.xml, it should work
binding.mouse.set(LLKeyboard::stringFromMouse(data.mMouse, false), true);
}
binding.command = iter->first;
mode.bindings.add(binding);
}
}
switch (mLoadMode)
{
case MODE_FIRST_PERSON:
if (keys.first_person.isProvided())
{
keys.first_person.bindings.set(mode.bindings, true);
}
break;
case MODE_THIRD_PERSON:
if (keys.third_person.isProvided())
{
keys.third_person.bindings.set(mode.bindings, true);
}
break;
case MODE_EDIT_AVATAR:
if (keys.edit_avatar.isProvided())
{
keys.edit_avatar.bindings.set(mode.bindings, true);
}
break;
case MODE_SITTING:
if (keys.sitting.isProvided())
{
keys.sitting.bindings.set(mode.bindings, true);
}
break;
default:
LL_ERRS() << "Not implememted mode " << mLoadMode << LL_ENDL;
break;
}
keys.xml_version.set(keybindings_xml_version, true);
if (temporary)
{
// write to temporary xml and use it for gViewerInput
filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename_temporary);
if (!mUsesTemporaryFile)
{
mUsesTemporaryFile = true;
sTemporaryFileUseCount++;
}
}
else
{
// write back to user's xml and use it for gViewerInput
filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename_default);
// Don't reset mUsesTemporaryFile, it will be reset at cleanup stage
}
LLXMLNodePtr output_node = new LLXMLNode("keys", false);
LLXUIParser parser;
parser.writeXUI(output_node, keys);
// Write the resulting XML to file
if (!output_node->isNull())
{
LLFILE *fp = LLFile::fopen(filename, "w");
if (fp != NULL)
{
LLXMLNode::writeHeaderToFile(fp);
output_node->writeToFile(fp);
fclose(fp);
}
}
// Now force a rebind for keyboard
if (gDirUtilp->fileExists(filename))
{
// Ideally instead of rebinding immediately we should shedule
// the rebind since single file can have multiple handlers,
// one per mode, saving simultaneously.
// Or whatever uses LLKeyConflictHandler should control the process.
gViewerInput.loadBindingsXML(filename);
}
}
}
if (mLoadMode == MODE_THIRD_PERSON && mHasUnsavedChanges)
{
// Map floater should react to doubleclick if doubleclick for teleport is set
// Todo: Seems conterintuitive for map floater to share inworld controls,
// discuss with UI UX engineer if this should just be set to 1 by default
bool value = canHandleMouse("teleport_to", CLICK_DOUBLELEFT, MASK_NONE);
gSavedSettings.setBOOL("DoubleClickTeleport", value);
}
if (!temporary)
{
// will remove any temporary file if there were any
clearUnsavedChanges();
}
}
LLKeyData LLKeyConflictHandler::getDefaultControl(const std::string &control_name, U32 index)
{
if (control_name.empty())
{
return LLKeyData();
}
if (mLoadMode == MODE_SAVED_SETTINGS)
{
LLControlVariablePtr var = gSavedSettings.getControl(control_name);
if (var)
{
return LLKeyBind(var->getDefault()).getKeyData(index);
}
return LLKeyData();
}
else
{
control_map_t::iterator iter = mDefaultsMap.find(control_name);
if (iter != mDefaultsMap.end())
{
return iter->second.mKeyBind.getKeyData(index);
}
return LLKeyData();
}
}
void LLKeyConflictHandler::resetToDefault(const std::string &control_name, U32 index)
{
if (control_name.empty())
{
return;
}
LLKeyConflict &type_data = mControlsMap[control_name];
if (!type_data.mAssignable)
{
return;
}
LLKeyData data = getDefaultControl(control_name, index);
if (data != type_data.getKeyData(index))
{
// reset controls that might have been switched to our current control
removeConflicts(data, mControlsMap[control_name].mConflictMask);
mControlsMap[control_name].setKeyData(data, index);
mHasUnsavedChanges = true;
}
}
void LLKeyConflictHandler::resetToDefaultAndResolve(const std::string &control_name, bool ignore_conflicts)
{
if (control_name.empty())
{
return;
}
if (mLoadMode == MODE_SAVED_SETTINGS)
{
LLControlVariablePtr var = gSavedSettings.getControl(control_name);
if (var)
{
LLKeyBind bind(var->getDefault());
if (!ignore_conflicts)
{
for (S32 i = 0; i < bind.getDataCount(); ++i)
{
removeConflicts(bind.getKeyData(i), mControlsMap[control_name].mConflictMask);
}
}
mControlsMap[control_name].mKeyBind = bind;
}
else
{
mControlsMap[control_name].mKeyBind.clear();
}
}
else
{
control_map_t::iterator iter = mDefaultsMap.find(control_name);
if (iter != mDefaultsMap.end())
{
if (!ignore_conflicts)
{
for (S32 i = 0; i < iter->second.mKeyBind.getDataCount(); ++i)
{
removeConflicts(iter->second.mKeyBind.getKeyData(i), mControlsMap[control_name].mConflictMask);
}
}
mControlsMap[control_name].mKeyBind = iter->second.mKeyBind;
}
else
{
mControlsMap[control_name].mKeyBind.clear();
}
}
}
void LLKeyConflictHandler::resetToDefault(const std::string &control_name)
{
// reset specific binding without ignoring conflicts
resetToDefaultAndResolve(control_name, false);
}
void LLKeyConflictHandler::resetToDefaultsAndResolve()
{
if (mLoadMode == MODE_SAVED_SETTINGS)
{
control_map_t::iterator iter = mControlsMap.begin();
control_map_t::iterator end = mControlsMap.end();
for (; iter != end; ++iter)
{
resetToDefaultAndResolve(iter->first, true);
}
}
else
{
mControlsMap.clear();
// Set key combinations.
// Copy from mDefaultsMap before doing generatePlaceholders, otherwise
// insert() will fail to add some keys into pre-existing values from
// generatePlaceholders()
mControlsMap.insert(mDefaultsMap.begin(), mDefaultsMap.end());
// Set conflict masks and mark functions (un)assignable
generatePlaceholders(mLoadMode);
}
mHasUnsavedChanges = true;
}
void LLKeyConflictHandler::resetToDefaults()
{
if (!empty())
{
resetToDefaultsAndResolve();
}
else
{
// not optimal since:
// 1. We are not sure that mLoadMode was set
// 2. We are not sure if there are any changes in comparison to default
// 3. We are loading 'current' only to replace it
// but it is reliable and works Todo: consider optimizing.
loadFromSettings(mLoadMode);
resetToDefaultsAndResolve();
}
}
void LLKeyConflictHandler::clear()
{
if (clearUnsavedChanges())
{
// temporary file was removed, this means we were using it and need to reload keyboard's bindings
resetKeyboardBindings();
}
mControlsMap.clear();
mDefaultsMap.clear();
}
// static
void LLKeyConflictHandler::resetKeyboardBindings()
{
// Try to load User's bindings first
std::string key_bindings_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename_default);
if (!gDirUtilp->fileExists(key_bindings_file) || !gViewerInput.loadBindingsXML(key_bindings_file))
{
// Failed to load custom bindings, try default ones
key_bindings_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, filename_default);
if (!gViewerInput.loadBindingsXML(key_bindings_file))
{
LL_ERRS("InitInfo") << "Unable to open default key bindings from " << key_bindings_file << LL_ENDL;
}
}
}
void LLKeyConflictHandler::generatePlaceholders(ESourceMode load_mode)
{
// These placeholders are meant to cause conflict resolution when user tries to assign same control somewhere else
// also this can be used to pre-record controls that should not conflict or to assign conflict groups/masks
if (load_mode == MODE_FIRST_PERSON)
{
// First person view doesn't support camera controls
// Note: might be better idea to just load these from control_table_contents_camera.xml
// or to pass from floaterpreferences when it loads said file
registerTemporaryControl("look_up");
registerTemporaryControl("look_down");
registerTemporaryControl("move_forward");
registerTemporaryControl("move_backward");
registerTemporaryControl("move_forward_fast");
registerTemporaryControl("move_backward_fast");
registerTemporaryControl("spin_over");
registerTemporaryControl("spin_under");
registerTemporaryControl("pan_up");
registerTemporaryControl("pan_down");
registerTemporaryControl("pan_left");
registerTemporaryControl("pan_right");
registerTemporaryControl("pan_in");
registerTemporaryControl("pan_out");
registerTemporaryControl("spin_around_ccw");
registerTemporaryControl("spin_around_cw");
// control_table_contents_editing.xml
registerTemporaryControl("edit_avatar_spin_ccw");
registerTemporaryControl("edit_avatar_spin_cw");
registerTemporaryControl("edit_avatar_spin_over");
registerTemporaryControl("edit_avatar_spin_under");
registerTemporaryControl("edit_avatar_move_forward");
registerTemporaryControl("edit_avatar_move_backward");
// no autopilot or teleport
registerTemporaryControl("walk_to");
registerTemporaryControl("teleport_to");
}
if (load_mode == MODE_EDIT_AVATAR)
{
// no autopilot or teleport
registerTemporaryControl("walk_to");
registerTemporaryControl("teleport_to");
}
if (load_mode == MODE_SITTING)
{
// no autopilot
registerTemporaryControl("walk_to");
}
else
{
// sitting related functions should only be avaliable in sitting mode
registerTemporaryControl("move_forward_sitting");
registerTemporaryControl("move_backward_sitting");
registerTemporaryControl("spin_over_sitting");
registerTemporaryControl("spin_under_sitting");
registerTemporaryControl("spin_around_ccw_sitting");
registerTemporaryControl("spin_around_cw_sitting");
}
// Special case, mouse clicks passed to scripts have 'lowest' piority
// thus do not conflict, everything else has a chance before them
// also in ML they have highest priority, but only when script-grabbed,
// thus do not conflict
// (see AGENT_CONTROL_ML_LBUTTON_DOWN and CONTROL_LBUTTON_DOWN_INDEX)
LLKeyConflict *type_data = &mControlsMap[script_mouse_handler_name];
type_data->mAssignable = true;
type_data->mConflictMask = U32_MAX - CONFLICT_LMOUSE;
}
bool LLKeyConflictHandler::removeConflicts(const LLKeyData &data, U32 conlict_mask)
{
if (conlict_mask == CONFLICT_NOTHING)
{
// Can't conflict
return true;
}
if (data.mMouse == CLICK_LEFT
&& data.mMask == MASK_NONE
&& data.mKey == KEY_NONE)
{
if ((conlict_mask & CONFLICT_LMOUSE) == 0)
{
// Can't conflict
return true;
}
else
{
// simplify conflict mask
conlict_mask = CONFLICT_LMOUSE;
}
}
else
{
// simplify conflict mask
conlict_mask &= ~CONFLICT_LMOUSE;
}
std::map<std::string, S32> conflict_list;
control_map_t::iterator cntrl_iter = mControlsMap.begin();
control_map_t::iterator cntrl_end = mControlsMap.end();
for (; cntrl_iter != cntrl_end; ++cntrl_iter)
{
const U32 cmp_mask = cntrl_iter->second.mConflictMask;
if ((cmp_mask & conlict_mask) == 0)
{
// can't conflict
continue;
}
S32 index = cntrl_iter->second.mKeyBind.findKeyData(data);
if (index >= 0)
{
if (cntrl_iter->second.mAssignable)
{
// Potentially we can have multiple conflict flags conflicting
// including unassignable keys.
// So record the conflict and find all others before doing any changes.
// Assume that there is only one conflict per bind
conflict_list[cntrl_iter->first] = index;
}
else
{
return false;
}
}
}
std::map<std::string, S32>::iterator cnflct_iter = conflict_list.begin();
std::map<std::string, S32>::iterator cnflct_end = conflict_list.end();
for (; cnflct_iter != cnflct_end; ++cnflct_iter)
{
mControlsMap[cnflct_iter->first].mKeyBind.resetKeyData(cnflct_iter->second);
}
return true;
}
void LLKeyConflictHandler::registerTemporaryControl(const std::string &control_name, EMouseClickType mouse, KEY key, MASK mask, U32 conflict_mask)
{
LLKeyConflict *type_data = &mControlsMap[control_name];
type_data->mAssignable = false;
type_data->mConflictMask = conflict_mask;
type_data->mKeyBind.addKeyData(mouse, key, mask, false);
}
void LLKeyConflictHandler::registerTemporaryControl(const std::string &control_name, U32 conflict_mask)
{
LLKeyConflict *type_data = &mControlsMap[control_name];
type_data->mAssignable = false;
type_data->mConflictMask = conflict_mask;
}
bool LLKeyConflictHandler::clearUnsavedChanges()
{
bool result = false;
mHasUnsavedChanges = false;
if (mUsesTemporaryFile)
{
mUsesTemporaryFile = false;
sTemporaryFileUseCount--;
if (!sTemporaryFileUseCount)
{
result = clearTemporaryFile();
}
// else: might be usefull to overwrite content of temp file with defaults
// but at the moment there is no such need
}
return result;
}
//static
bool LLKeyConflictHandler::clearTemporaryFile()
{
// At the moment single file needs five handlers (one per mode), so doing this
// will remove file for all hadlers
std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename_temporary);
if (gDirUtilp->fileExists(filename))
{
LLFile::remove(filename);
return true;
}
return false;
}