/ src / modules / keyboardmanager / KeyboardManagerEditorLibrary / BufferValidationHelpers.cpp
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  }