BufferValidationHelpers.cpp
1 #include "pch.h" 2 #include "BufferValidationHelpers.h" 3 4 #include <common/interop/shared_constants.h> 5 #include <keyboardmanager/common/KeyboardManagerConstants.h> 6 #include <keyboardmanager/common/Helpers.h> 7 8 #include "KeyboardManagerEditorStrings.h" 9 #include "KeyDropDownControl.h" 10 #include "UIHelpers.h" 11 #include "EditorHelpers.h" 12 #include "EditorConstants.h" 13 14 namespace BufferValidationHelpers 15 { 16 // Helper function to verify if a key is being remapped to/from its combined key 17 bool IsKeyRemappingToItsCombinedKey(DWORD keyCode1, DWORD keyCode2) 18 { 19 return (keyCode1 == Helpers::GetCombinedKey(keyCode1) || keyCode2 == Helpers::GetCombinedKey(keyCode2)) && 20 Helpers::GetCombinedKey(keyCode1) == Helpers::GetCombinedKey(keyCode2); 21 } 22 23 // Function to validate and update an element of the key remap buffer when the selection has changed 24 ShortcutErrorType ValidateAndUpdateKeyBufferElement(int rowIndex, int colIndex, int selectedKeyCode, RemapBuffer& remapBuffer) 25 { 26 ShortcutErrorType errorType = ShortcutErrorType::NoError; 27 28 // Check if the element was not found or the index exceeds the known keys 29 if (selectedKeyCode != -1) 30 { 31 // Check if the value being set is the same as the other column 32 if (remapBuffer[rowIndex].mapping[std::abs(colIndex - 1)].index() == 0) 33 { 34 DWORD otherColumnKeyCode = std::get<DWORD>(remapBuffer[rowIndex].mapping[std::abs(colIndex - 1)]); 35 if (otherColumnKeyCode == selectedKeyCode || IsKeyRemappingToItsCombinedKey(selectedKeyCode, otherColumnKeyCode)) 36 { 37 errorType = ShortcutErrorType::MapToSameKey; 38 } 39 } 40 41 // If one column is shortcut and other is key no warning required 42 43 if (errorType == ShortcutErrorType::NoError && colIndex == 0) 44 { 45 // Check if the key is already remapped to something else 46 for (int i = 0; i < remapBuffer.size(); i++) 47 { 48 if (i != rowIndex) 49 { 50 if (remapBuffer[i].mapping[colIndex].index() == 0) 51 { 52 ShortcutErrorType result = EditorHelpers::DoKeysOverlap(std::get<DWORD>(remapBuffer[i].mapping[colIndex]), selectedKeyCode); 53 if (result != ShortcutErrorType::NoError) 54 { 55 errorType = result; 56 break; 57 } 58 } 59 60 // If one column is shortcut and other is key no warning required 61 } 62 } 63 } 64 65 // If there is no error, set the buffer 66 if (errorType == ShortcutErrorType::NoError) 67 { 68 remapBuffer[rowIndex].mapping[colIndex] = (DWORD)selectedKeyCode; 69 } 70 else 71 { 72 remapBuffer[rowIndex].mapping[colIndex] = (DWORD)0; 73 } 74 } 75 else 76 { 77 // Reset to null if the key is not found 78 remapBuffer[rowIndex].mapping[colIndex] = (DWORD)0; 79 } 80 81 return errorType; 82 } 83 84 // Function to validate an element of the shortcut remap buffer when the selection has changed 85 std::pair<ShortcutErrorType, DropDownAction> ValidateShortcutBufferElement(int rowIndex, int colIndex, uint32_t dropDownIndex, const std::vector<int32_t>& selectedCodes, std::wstring appName, bool isHybridControl, const RemapBuffer& remapBuffer, bool dropDownFound) 86 { 87 BufferValidationHelpers::DropDownAction dropDownAction = BufferValidationHelpers::DropDownAction::NoAction; 88 ShortcutErrorType errorType = ShortcutErrorType::NoError; 89 size_t dropDownCount = selectedCodes.size(); 90 DWORD selectedKeyCode = dropDownFound ? selectedCodes[dropDownIndex] : -1; 91 92 if (selectedKeyCode != -1 && dropDownFound) 93 { 94 // If only 1 drop down and action key is chosen: Warn that a modifier must be chosen (if the drop down is not for a hybrid scenario) 95 if (dropDownCount == 1 && !Helpers::IsModifierKey(selectedKeyCode) && !isHybridControl) 96 { 97 // warn and reset the drop down 98 errorType = ShortcutErrorType::ShortcutStartWithModifier; 99 } 100 else if (dropDownIndex == dropDownCount - 1) 101 { 102 // If it is the last drop down 103 // If last drop down and a modifier is selected: add a new drop down (max drop down count should be enforced) 104 if (Helpers::IsModifierKey(selectedKeyCode) && dropDownCount < EditorConstants::MaxShortcutSize) 105 { 106 // If it matched any of the previous modifiers then reset that drop down 107 if (EditorHelpers::CheckRepeatedModifier(selectedCodes, selectedKeyCode)) 108 { 109 // warn and reset the drop down 110 errorType = ShortcutErrorType::ShortcutCannotHaveRepeatedModifier; 111 } 112 else 113 { 114 // If not, add a new drop down 115 dropDownAction = BufferValidationHelpers::DropDownAction::AddDropDown; 116 } 117 } 118 else if (Helpers::IsModifierKey(selectedKeyCode) && dropDownCount >= EditorConstants::MaxShortcutSize) 119 { 120 // If last drop down and a modifier is selected but there are already max drop downs: warn the user 121 // warn and reset the drop down 122 errorType = ShortcutErrorType::ShortcutMaxShortcutSizeOneActionKey; 123 } 124 else if (selectedKeyCode == 0) 125 { 126 // If None is selected but it's the last index: warn 127 // If it is a hybrid control and there are 2 drop downs then deletion is allowed 128 if (isHybridControl && dropDownCount == EditorConstants::MinShortcutSize) 129 { 130 // set delete drop down flag 131 dropDownAction = BufferValidationHelpers::DropDownAction::DeleteDropDown; 132 // do not delete the drop down now since there may be some other error which would cause the drop down to be invalid after removal 133 } 134 else 135 { 136 // warn and reset the drop down 137 errorType = ShortcutErrorType::ShortcutOneActionKey; 138 } 139 } 140 else if (selectedKeyCode == CommonSharedConstants::VK_DISABLED && dropDownIndex) 141 { 142 // Disable cannot be selected if one modifier key has already been selected 143 errorType = ShortcutErrorType::ShortcutDisableAsActionKey; 144 } 145 // If none of the above, then the action key will be set 146 } 147 else 148 { 149 // If it is not the last drop down 150 if (Helpers::IsModifierKey(selectedKeyCode)) 151 { 152 // If it matched any of the previous modifiers then reset that drop down 153 if (EditorHelpers::CheckRepeatedModifier(selectedCodes, selectedKeyCode)) 154 { 155 // warn and reset the drop down 156 errorType = ShortcutErrorType::ShortcutCannotHaveRepeatedModifier; 157 } 158 // If not, the modifier key will be set 159 } 160 else if (selectedKeyCode == 0 && dropDownCount > EditorConstants::MinShortcutSize) 161 { 162 // If None is selected and there are more than 2 drop downs 163 // set delete drop down flag 164 dropDownAction = BufferValidationHelpers::DropDownAction::DeleteDropDown; 165 // do not delete the drop down now since there may be some other error which would cause the drop down to be invalid after removal 166 } 167 else if (selectedKeyCode == 0 && dropDownCount <= EditorConstants::MinShortcutSize) 168 { 169 // If it is a hybrid control and there are 2 drop downs then deletion is allowed 170 if (isHybridControl && dropDownCount == EditorConstants::MinShortcutSize) 171 { 172 // set delete drop down flag 173 dropDownAction = BufferValidationHelpers::DropDownAction::DeleteDropDown; 174 // do not delete the drop down now since there may be some other error which would cause the drop down to be invalid after removal 175 } 176 else 177 { 178 // warn and reset the drop down 179 errorType = ShortcutErrorType::ShortcutAtleast2Keys; 180 } 181 } 182 else if (selectedKeyCode == CommonSharedConstants::VK_DISABLED && dropDownIndex) 183 { 184 // Allow selection of VK_DISABLE only in first dropdown 185 errorType = ShortcutErrorType::ShortcutDisableAsActionKey; 186 } 187 else if (dropDownIndex != 0 || isHybridControl) 188 { 189 // If the user tries to set an action key check if all drop down menus after this are empty if it is not the first key. 190 // If it is a hybrid control, this can be done even on the first key 191 bool isClear = true; 192 for (int i = dropDownIndex + 1; i < static_cast<int>(dropDownCount); i++) 193 { 194 if (selectedCodes[i] != -1) 195 { 196 isClear = false; 197 break; 198 } 199 } 200 201 if (isClear) 202 { 203 dropDownAction = BufferValidationHelpers::DropDownAction::ClearUnusedDropDowns; 204 } 205 else 206 { 207 // this used to "warn and reset the drop down" but for now, since we will allow Chords, we do allow this 208 // leaving the here and commented out for posterity, for now. 209 // errorType = ShortcutErrorType::ShortcutNotMoreThanOneActionKey; 210 } 211 } 212 else 213 { 214 // If there an action key is chosen on the first drop down and there are more than one drop down menus 215 // warn and reset the drop down 216 errorType = ShortcutErrorType::ShortcutStartWithModifier; 217 } 218 } 219 } 220 221 // After validating the shortcut, now for errors like remap to same shortcut, remap shortcut more than once, Win L and Ctrl Alt Del 222 if (errorType == ShortcutErrorType::NoError) 223 { 224 KeyShortcutTextUnion tempShortcut; 225 if (isHybridControl && KeyDropDownControl::GetNumberOfSelectedKeys(selectedCodes) == 1) 226 { 227 tempShortcut = (DWORD)*std::find_if(selectedCodes.begin(), selectedCodes.end(), [](int32_t a) { return a != -1 && a != 0; }); 228 } 229 else 230 { 231 tempShortcut = Shortcut(); 232 std::get<Shortcut>(tempShortcut).SetKeyCodes(selectedCodes); 233 } 234 235 // Convert app name to lower case 236 std::transform(appName.begin(), appName.end(), appName.begin(), towlower); 237 std::wstring lowercaseDefAppName = KeyboardManagerEditorStrings::DefaultAppName(); 238 std::transform(lowercaseDefAppName.begin(), lowercaseDefAppName.end(), lowercaseDefAppName.begin(), towlower); 239 if (appName == lowercaseDefAppName) 240 { 241 appName = L""; 242 } 243 244 // Check if the value being set is the same as the other column - index of other column does not have to be checked since only one column is hybrid 245 if (tempShortcut.index() == 1) 246 { 247 // If shortcut to shortcut 248 if (remapBuffer[rowIndex].mapping[std::abs(colIndex - 1)].index() == 1) 249 { 250 auto& shortcut = std::get<Shortcut>(remapBuffer[rowIndex].mapping[std::abs(colIndex - 1)]); 251 if (shortcut == std::get<Shortcut>(tempShortcut) && EditorHelpers::IsValidShortcut(shortcut) && EditorHelpers::IsValidShortcut(std::get<Shortcut>(tempShortcut))) 252 { 253 errorType = ShortcutErrorType::MapToSameShortcut; 254 } 255 } 256 257 // If one column is shortcut and other is key no warning required 258 } 259 else 260 { 261 // If key to key 262 if (remapBuffer[rowIndex].mapping[std::abs(colIndex - 1)].index() == 0) 263 { 264 DWORD otherColumnKeyCode = std::get<DWORD>(remapBuffer[rowIndex].mapping[std::abs(colIndex - 1)]); 265 DWORD shortcutKeyCode = std::get<DWORD>(tempShortcut); 266 if ((otherColumnKeyCode == shortcutKeyCode || IsKeyRemappingToItsCombinedKey(otherColumnKeyCode, shortcutKeyCode)) && otherColumnKeyCode != NULL && shortcutKeyCode != NULL) 267 { 268 errorType = ShortcutErrorType::MapToSameKey; 269 } 270 } 271 272 // If one column is shortcut and other is key no warning required 273 } 274 275 if (errorType == ShortcutErrorType::NoError && colIndex == 0) 276 { 277 // Check if the key is already remapped to something else for the same target app 278 for (int i = 0; i < remapBuffer.size(); i++) 279 { 280 std::wstring currAppName = remapBuffer[i].appName; 281 std::transform(currAppName.begin(), currAppName.end(), currAppName.begin(), towlower); 282 283 if (i != rowIndex && currAppName == appName) 284 { 285 ShortcutErrorType result = ShortcutErrorType::NoError; 286 if (!isHybridControl) 287 { 288 result = EditorHelpers::DoShortcutsOverlap(std::get<Shortcut>(remapBuffer[i].mapping[colIndex]), std::get<Shortcut>(tempShortcut)); 289 } 290 else 291 { 292 if (tempShortcut.index() == 0 && remapBuffer[i].mapping[colIndex].index() == 0) 293 { 294 if (std::get<DWORD>(tempShortcut) != NULL && std::get<DWORD>(remapBuffer[i].mapping[colIndex]) != NULL) 295 { 296 result = EditorHelpers::DoKeysOverlap(std::get<DWORD>(remapBuffer[i].mapping[colIndex]), std::get<DWORD>(tempShortcut)); 297 } 298 } 299 else if (tempShortcut.index() == 1 && remapBuffer[i].mapping[colIndex].index() == 1) 300 { 301 auto& shortcut = std::get<Shortcut>(remapBuffer[i].mapping[colIndex]); 302 if (EditorHelpers::IsValidShortcut(std::get<Shortcut>(tempShortcut)) && EditorHelpers::IsValidShortcut(shortcut)) 303 { 304 result = EditorHelpers::DoShortcutsOverlap(std::get<Shortcut>(remapBuffer[i].mapping[colIndex]), std::get<Shortcut>(tempShortcut)); 305 } 306 } 307 // Other scenarios not possible since key to shortcut is with key to key, and shortcut to key is with shortcut to shortcut 308 } 309 if (result != ShortcutErrorType::NoError) 310 { 311 errorType = result; 312 break; 313 } 314 } 315 } 316 } 317 318 if (errorType == ShortcutErrorType::NoError && tempShortcut.index() == 1) 319 { 320 errorType = EditorHelpers::IsShortcutIllegal(std::get<Shortcut>(tempShortcut)); 321 } 322 } 323 324 return std::make_pair(errorType, dropDownAction); 325 } 326 }