From d07ef7df92cbc9c2fb16dcd0ddd0322665d440eb Mon Sep 17 00:00:00 2001
From: Andrey Kleshchev <andreykproductengine@lindenlab.com>
Date: Mon, 15 Jun 2020 18:13:46 +0300
Subject: [PATCH] SL-13418 Added converter from old mouse binding settings to
 new ones

---
 indra/llcommon/llkeybind.h                  |  2 +-
 indra/newview/app_settings/key_bindings.xml |  1 -
 indra/newview/llappviewer.cpp               | 99 +++++++++++++++++++++
 indra/newview/llkeyconflict.cpp             | 67 +++++++++++++-
 indra/newview/llkeyconflict.h               | 16 ++++
 5 files changed, 182 insertions(+), 3 deletions(-)

diff --git a/indra/llcommon/llkeybind.h b/indra/llcommon/llkeybind.h
index ad0ebec67cd..c6b4bd970f4 100644
--- a/indra/llcommon/llkeybind.h
+++ b/indra/llcommon/llkeybind.h
@@ -53,7 +53,7 @@ class LL_COMMON_API LLKeyData
     EMouseClickType mMouse;
     KEY mKey;
     MASK mMask;
-    // Either to expect exact match or ignore not expected masks
+    // Either to expect exact match or ignore not expected masks as long as expected mask-bit is present
     bool mIgnoreMasks; 
 };
 
diff --git a/indra/newview/app_settings/key_bindings.xml b/indra/newview/app_settings/key_bindings.xml
index 81423c4716e..4f6deb1f985 100644
--- a/indra/newview/app_settings/key_bindings.xml
+++ b/indra/newview/app_settings/key_bindings.xml
@@ -224,7 +224,6 @@
     <binding key="DIVIDE" mask="NONE" command="start_gesture"/>
 
     <binding key="" mask="NONE" mouse="MMB" command="toggle_voice"/>
-    <binding key="" mask="NONE" mouse="LMB" command="walk_to"/>
   </sitting>
   <edit_avatar>
     <!--Avatar editing camera controls-->
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 5630bb1a3f4..775b6db94ba 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -49,6 +49,7 @@
 #include "llwindow.h"
 #include "llviewerstats.h"
 #include "llviewerstatsrecorder.h"
+#include "llkeyconflict.h" // for legacy keybinding support, remove later
 #include "llmarketplacefunctions.h"
 #include "llmarketplacenotifications.h"
 #include "llmd5.h"
@@ -1004,6 +1005,104 @@ bool LLAppViewer::init()
 
 	// Load User's bindings
 	std::string key_bindings_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "key_bindings.xml");
+#if 1
+    // Legacy support
+    // Remove #if-#endif section half a year after DRTVWR-501 releases.
+    // Mouse actions are part of keybinding file since DRTVWR-501 instead of being stored in
+    // settings.xml. To support legacy viewers that were storing in  settings.xml we need to
+    // transfer old variables to new format.
+    // Also part of backward compatibility is present in LLKeyConflictHandler to modify
+    // legacy variables on changes in new system (to make sure we won't enforce
+    // legacy values again if user dropped to defaults in new system)
+    if (mIsFirstRun
+        && !gDirUtilp->fileExists(key_bindings_file)) // if file is missing, assume that there were no changes by user yet
+    {
+        // copy mouse actions and voice key changes to new file
+        LL_INFOS("InitInfo") << "Converting legacy mouse bindings to new format" << LL_ENDL;
+        // Load settings from file
+        LLKeyConflictHandler third_person_view(LLKeyConflictHandler::MODE_THIRD_PERSON);
+
+        // Since we are only modifying keybindings if personal file doesn't exist yet,
+        // it should be safe to just overwrite the value
+        // If key is already in use somewhere by default, LLKeyConflictHandler should resolve it.
+        BOOL value = gSavedSettings.getBOOL("DoubleClickAutoPilot");
+        third_person_view.registerControl("walk_to",
+                                          0,
+                                          value ? EMouseClickType::CLICK_DOUBLELEFT : EMouseClickType::CLICK_NONE,
+                                          KEY_NONE,
+                                          MASK_NONE,
+                                          value);
+
+        U32 index = value ? 1 : 0; // we can store multiple combinations per action, so if first is in use by doubleclick, go to second
+        value = gSavedSettings.getBOOL("ClickToWalk");
+        third_person_view.registerControl("walk_to",
+                                          index,
+                                          value ? EMouseClickType::CLICK_LEFT : EMouseClickType::CLICK_NONE,
+                                          KEY_NONE,
+                                          MASK_NONE,
+                                          value);
+
+        value = gSavedSettings.getBOOL("DoubleClickTeleport");
+        third_person_view.registerControl("teleport_to",
+                                          0,
+                                          value ? EMouseClickType::CLICK_DOUBLELEFT : EMouseClickType::CLICK_NONE,
+                                          KEY_NONE,
+                                          MASK_NONE,
+                                          value);
+
+        std::string key_string = gSavedSettings.getString("PushToTalkButton");
+        EMouseClickType mouse = EMouseClickType::CLICK_NONE;
+        KEY key = KEY_NONE;
+        if (key_string == "MiddleMouse")
+        {
+            mouse = EMouseClickType::CLICK_MIDDLE;
+        }
+        else if (key_string == "MouseButton4")
+        {
+            mouse = EMouseClickType::CLICK_BUTTON4;
+        }
+        else if (key_string == "MouseButton5")
+        {
+            mouse = EMouseClickType::CLICK_BUTTON5;
+        }
+        else
+        {
+            LLKeyboard::keyFromString(key_string, &key);
+        }
+
+        value = gSavedSettings.getBOOL("PushToTalkToggle");
+        std::string control_name = value ? "toggle_voice" : "voice_follow_key";
+        third_person_view.registerControl(control_name, 0, mouse, key, MASK_NONE, true);
+
+        if (third_person_view.hasUnsavedChanges())
+        {
+            // calls loadBindingsXML()
+            third_person_view.saveToSettings();
+        }
+
+        // in case of voice we need to repeat this in other modes (teleports and
+        // autopilot are not entirely practical when sitting or editing)
+
+        for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i)
+        {
+            if (i != LLKeyConflictHandler::MODE_THIRD_PERSON)
+            {
+                LLKeyConflictHandler handler((LLKeyConflictHandler::ESourceMode)i);
+
+                handler.registerControl(control_name, 0, mouse, key, MASK_NONE, true);
+
+                if (handler.hasUnsavedChanges())
+                {
+                    // calls loadBindingsXML()
+                    handler.saveToSettings();
+                }
+            }
+        }
+    }
+    // since something might have gone wrong or there might have been nothing to save
+    // (and because otherwise following code will have to be encased in else{}),
+    // load everything one last time
+#endif
 	if (!gDirUtilp->fileExists(key_bindings_file) || !gViewerInput.loadBindingsXML(key_bindings_file))
 	{
 		// Failed to load custom bindings, try default ones
diff --git a/indra/newview/llkeyconflict.cpp b/indra/newview/llkeyconflict.cpp
index e44f79000ee..5f966624ca1 100644
--- a/indra/newview/llkeyconflict.cpp
+++ b/indra/newview/llkeyconflict.cpp
@@ -427,7 +427,7 @@ void LLKeyConflictHandler::saveToSettings(bool temporary)
             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 this one
+                // 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);
             }
@@ -598,6 +598,71 @@ void LLKeyConflictHandler::saveToSettings(bool temporary)
         // will remove any temporary file if there were any
         clearUnsavedChanges();
     }
+
+#if 1
+    // Legacy support
+    // Remove #if-#endif section half a year after DRTVWR-501 releases.
+    // Update legacy settings in settings.xml
+    // We only care for third person view since legacy settings can't store
+    // more than one mode.
+    // We are saving this even if we are in temporary mode - preferences
+    // will restore values on cancel
+    if (mLoadMode == MODE_THIRD_PERSON)
+    {
+        bool value = canHandleMouse("walk_to", CLICK_DOUBLELEFT, MASK_NONE);
+        gSavedSettings.setBOOL("DoubleClickAutoPilot", value);
+
+        value = canHandleMouse("walk_to", CLICK_LEFT, MASK_NONE);
+        gSavedSettings.setBOOL("ClickToWalk", value);
+
+        value = canHandleMouse("teleport_to", CLICK_DOUBLELEFT, MASK_NONE);
+        gSavedSettings.setBOOL("DoubleClickTeleport", value);
+
+        // new method can save both toggle and push-to-talk values simultaneously,
+        // but legacy one can save only one. It also doesn't support mask.
+        LLKeyData data = getControl("toggle_voice", 0);
+        bool can_toggle = !data.isEmpty();
+        if (!can_toggle)
+        {
+            data = getControl("voice_follow_key", 0);
+        }
+
+        gSavedSettings.setBOOL("PushToTalkToggle", can_toggle);
+        if (data.isEmpty())
+        {
+            // legacy viewer has a bug that might crash it if NONE value is assigned.
+            // just reset to default
+            gSavedSettings.getControl("PushToTalkButton")->resetToDefault(false);
+        }
+        else
+        {
+            if (data.mKey != KEY_NONE)
+            {
+                gSavedSettings.setString("PushToTalkButton", LLKeyboard::stringFromKey(data.mKey));
+            }
+            else
+            {
+                std::string ctrl_value;
+                switch (data.mMouse)
+                {
+                case CLICK_MIDDLE:
+                    ctrl_value = "MiddleMouse";
+                    break;
+                case CLICK_BUTTON4:
+                    ctrl_value = "MouseButton4";
+                    break;
+                case CLICK_BUTTON5:
+                    ctrl_value = "MouseButton5";
+                    break;
+                default:
+                    ctrl_value = "MiddleMouse";
+                    break;
+                }
+                gSavedSettings.setString("PushToTalkButton", ctrl_value);
+            }
+        }
+    }
+#endif
 }
 
 LLKeyData LLKeyConflictHandler::getDefaultControl(const std::string &control_name, U32 index)
diff --git a/indra/newview/llkeyconflict.h b/indra/newview/llkeyconflict.h
index 73d59cc2173..5451c16ae29 100644
--- a/indra/newview/llkeyconflict.h
+++ b/indra/newview/llkeyconflict.h
@@ -82,6 +82,18 @@ class LLKeyConflictHandler
     bool canAssignControl(const std::string &control_name);
     static bool isReservedByMenu(const KEY &key, const MASK &mask);
     static bool isReservedByMenu(const LLKeyData &data);
+
+    // @control_name - see REGISTER_KEYBOARD_ACTION in llviewerinput for avaliable options,
+    // usually this is just name of the function
+    // @data_index - single control (function) can have multiple key combinations trigering
+    // it, this index indicates combination function will change/add note that preferences
+    // floater can only display up to 3 options, but data_index can be bigger then that
+    // @mouse_ind - mouse action (middle click, MB5 etc)
+    // @key - keyboard key action
+    // @mask - shift/ctrl/alt flags
+    // @ignore_mask - Either to expect exact match (ctrl+K will not trigger if ctrl+shift+K
+    // is active) or ignore not expected masks as long as expected mask is present
+    // (ctrl+K will be triggered if ctrl+shift+K is active)
     bool registerControl(const std::string &control_name, U32 data_index, EMouseClickType mouse_ind, KEY key, MASK mask, bool ignore_mask); //todo: return conflicts?
 
     LLKeyData getControl(const std::string &control_name, U32 data_index);
@@ -101,6 +113,10 @@ class LLKeyConflictHandler
     // 'temporary' does not support gSavedSettings, those are handled
     // by preferences, so 'temporary' is such case will simply not
     // reset mHasUnsavedChanges
+    //
+    // 'temporary' exists to support ability of live-editing settings in
+    // preferences: temporary for testing changes 'live' without saving them,
+    // then hitting ok/cancel and save/discard values permanently.
     void saveToSettings(bool apply_temporary = false);
 
     LLKeyData getDefaultControl(const std::string &control_name, U32 data_index);
-- 
GitLab