1 /* 2 * Copyright 2011-2023 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * John Scipione, jscipione@gmail.com 7 * Jorge Acereda, jacereda@gmail.com 8 */ 9 10 11 #include "ModifierKeysWindow.h" 12 13 #include <stdio.h> 14 #include <stdlib.h> 15 #include <string.h> 16 17 #include <Bitmap.h> 18 #include <Button.h> 19 #include <Catalog.h> 20 #include <CheckBox.h> 21 #include <ControlLook.h> 22 #include <FindDirectory.h> 23 #include <IconUtils.h> 24 #include <InterfaceDefs.h> 25 #include <LayoutBuilder.h> 26 #include <Locale.h> 27 #include <MenuField.h> 28 #include <MenuItem.h> 29 #include <Message.h> 30 #include <Path.h> 31 #include <PopUpMenu.h> 32 #include <Resources.h> 33 #include <SeparatorView.h> 34 #include <Size.h> 35 #include <StringView.h> 36 37 #include "KeymapApplication.h" 38 39 40 #ifdef DEBUG_ALERT 41 # define FTRACE(x) fprintf(x) 42 #else 43 # define FTRACE(x) /* nothing */ 44 #endif 45 46 47 enum { 48 CAPS_KEY = 0x00000001, 49 SHIFT_KEY = 0x00000002, 50 CONTROL_KEY = 0x00000004, 51 OPTION_KEY = 0x00000008, 52 COMMAND_KEY = 0x00000010, 53 }; 54 55 enum { 56 MENU_ITEM_CAPS, 57 MENU_ITEM_SHIFT, 58 MENU_ITEM_CONTROL, 59 MENU_ITEM_OPTION, 60 MENU_ITEM_COMMAND, 61 MENU_ITEM_SEPARATOR, 62 MENU_ITEM_DISABLED, 63 64 MENU_ITEM_FIRST = MENU_ITEM_CAPS, 65 MENU_ITEM_LAST = MENU_ITEM_DISABLED 66 }; 67 68 69 static const uint32 kMsgUpdateModifier = 'upmd'; 70 static const uint32 kMsgApplyModifiers = 'apmd'; 71 static const uint32 kMsgRevertModifiers = 'rvmd'; 72 73 74 #undef B_TRANSLATION_CONTEXT 75 #define B_TRANSLATION_CONTEXT "Modifier keys window" 76 77 78 // #pragma mark - ConflictView 79 80 81 ConflictView::ConflictView(const char* name) 82 : 83 BView(BRect(BPoint(0, 0), be_control_look->ComposeIconSize(B_MINI_ICON)), 84 name, B_FOLLOW_NONE, B_WILL_DRAW), 85 fIcon(NULL), 86 fStopIcon(NULL), 87 fWarnIcon(NULL) 88 { 89 _FillIcons(); 90 } 91 92 93 ConflictView::~ConflictView() 94 { 95 delete fStopIcon; 96 delete fWarnIcon; 97 } 98 99 100 void 101 ConflictView::Draw(BRect updateRect) 102 { 103 // Draw background 104 if (Parent()) 105 SetLowColor(Parent()->ViewColor()); 106 else 107 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 108 109 FillRect(updateRect, B_SOLID_LOW); 110 111 if (fIcon == NULL) 112 return; 113 114 // Draw icon 115 SetDrawingMode(B_OP_ALPHA); 116 SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 117 DrawBitmapAsync(fIcon, BPoint(0, 0)); 118 } 119 120 121 // get the icon 122 BBitmap* 123 ConflictView::Icon() 124 { 125 return fIcon; 126 } 127 128 129 // show or hide the stop icon 130 void 131 ConflictView::SetStopIcon(bool show) 132 { 133 fIcon = show ? fStopIcon : NULL; 134 const char* tip = show ? B_TRANSLATE("Error: duplicate keys") : NULL; 135 SetToolTip(tip); 136 } 137 138 139 // show or hide the warn icon 140 void 141 ConflictView::SetWarnIcon(bool show) 142 { 143 fIcon = show ? fWarnIcon : NULL; 144 const char* tip = show ? B_TRANSLATE("Warning: left and right key roles do not match") : NULL; 145 SetToolTip(tip); 146 } 147 148 149 // #pragma mark - ConflictView private methods 150 151 152 // fill out the icons with the stop and warn symbols from app_server 153 void 154 ConflictView::_FillIcons() 155 { 156 if (fStopIcon == NULL) { 157 // Allocate the fStopIcon bitmap 158 fStopIcon = new (std::nothrow) BBitmap(Bounds(), 0, B_RGBA32); 159 if (fStopIcon->InitCheck() != B_OK) { 160 FTRACE((stderr, "_FillIcons() - No memory for stop bitmap\n")); 161 delete fStopIcon; 162 fStopIcon = NULL; 163 return; 164 } 165 166 // load dialog-error icon bitmap 167 if (BIconUtils::GetSystemIcon("dialog-error", fStopIcon) != B_OK) { 168 delete fStopIcon; 169 fStopIcon = NULL; 170 return; 171 } 172 } 173 174 if (fWarnIcon == NULL) { 175 // Allocate the fWarnIcon bitmap 176 fWarnIcon = new (std::nothrow) BBitmap(Bounds(), 0, B_RGBA32); 177 if (fWarnIcon->InitCheck() != B_OK) { 178 FTRACE((stderr, "_FillIcons() - No memory for warn bitmap\n")); 179 delete fWarnIcon; 180 fWarnIcon = NULL; 181 return; 182 } 183 184 // load dialog-warning icon bitmap 185 if (BIconUtils::GetSystemIcon("dialog-warning", fWarnIcon) != B_OK) { 186 delete fWarnIcon; 187 fWarnIcon = NULL; 188 return; 189 } 190 } 191 } 192 193 194 // #pragma mark - ModifierKeysWindow 195 196 197 ModifierKeysWindow::ModifierKeysWindow() 198 : 199 BWindow(BRect(0, 0, 360, 220), B_TRANSLATE("Modifier keys"), 200 B_FLOATING_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS) 201 { 202 get_key_map(&fCurrentMap, &fCurrentBuffer); 203 get_key_map(&fSavedMap, &fSavedBuffer); 204 205 BStringView* keyRole = new BStringView("key role", 206 B_TRANSLATE_COMMENT("Role", "As in the role of a modifier key")); 207 keyRole->SetAlignment(B_ALIGN_RIGHT); 208 keyRole->SetFont(be_bold_font); 209 210 BStringView* keyLabel = new BStringView("key label", 211 B_TRANSLATE_COMMENT("Key", "As in a computer keyboard key")); 212 keyLabel->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 213 keyLabel->SetFont(be_bold_font); 214 215 BMenuField* capsMenuField; 216 _CreateMenuField(&fCapsMenu, &capsMenuField, MENU_ITEM_CAPS, 217 B_TRANSLATE_COMMENT("Caps:", "Caps key role name")); 218 219 BMenuField* shiftMenuField; 220 _CreateMenuField(&fShiftMenu, &shiftMenuField, MENU_ITEM_SHIFT, 221 B_TRANSLATE_COMMENT("Shift:", "Shift key role name")); 222 223 BMenuField* controlMenuField; 224 _CreateMenuField(&fControlMenu, &controlMenuField, MENU_ITEM_CONTROL, 225 B_TRANSLATE_COMMENT("Control:", "Control key role name")); 226 227 BMenuField* optionMenuField; 228 _CreateMenuField(&fOptionMenu, &optionMenuField, MENU_ITEM_OPTION, 229 B_TRANSLATE_COMMENT("Option:", "Option key role name")); 230 231 BMenuField* commandMenuField; 232 _CreateMenuField(&fCommandMenu, &commandMenuField, MENU_ITEM_COMMAND, 233 B_TRANSLATE_COMMENT("Command:", "Command key role name")); 234 235 fCapsConflictView = new ConflictView("caps lock warning view"); 236 fCapsConflictView->SetExplicitMaxSize(fCapsConflictView->Bounds().Size()); 237 238 fShiftConflictView = new ConflictView("shift warning view"); 239 fShiftConflictView->SetExplicitMaxSize(fShiftConflictView->Bounds().Size()); 240 241 fControlConflictView = new ConflictView("control warning view"); 242 fControlConflictView->SetExplicitMaxSize(fControlConflictView->Bounds().Size()); 243 244 fOptionConflictView = new ConflictView("option warning view"); 245 fOptionConflictView->SetExplicitMaxSize(fOptionConflictView->Bounds().Size()); 246 247 fCommandConflictView = new ConflictView("command warning view"); 248 fCommandConflictView->SetExplicitMaxSize(fCommandConflictView->Bounds().Size()); 249 250 fCancelButton = new BButton("cancelButton", B_TRANSLATE("Cancel"), 251 new BMessage(B_QUIT_REQUESTED)); 252 253 fRevertButton = new BButton("revertButton", B_TRANSLATE("Revert"), 254 new BMessage(kMsgRevertModifiers)); 255 fRevertButton->SetEnabled(false); 256 257 fOkButton = new BButton("okButton", B_TRANSLATE("Set modifier keys"), 258 new BMessage(kMsgApplyModifiers)); 259 fOkButton->MakeDefault(true); 260 261 BLayoutBuilder::Group<>(this, B_VERTICAL, B_USE_DEFAULT_SPACING) 262 .AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING) 263 .Add(keyRole, 0, 0) 264 .Add(keyLabel, 1, 0) 265 266 .Add(capsMenuField->CreateLabelLayoutItem(), 0, 1) 267 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 1) 268 .Add(capsMenuField->CreateMenuBarLayoutItem()) 269 .Add(fCapsConflictView) 270 .End() 271 272 .Add(shiftMenuField->CreateLabelLayoutItem(), 0, 2) 273 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 2) 274 .Add(shiftMenuField->CreateMenuBarLayoutItem()) 275 .Add(fShiftConflictView) 276 .End() 277 278 .Add(controlMenuField->CreateLabelLayoutItem(), 0, 3) 279 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 3) 280 .Add(controlMenuField->CreateMenuBarLayoutItem()) 281 .Add(fControlConflictView) 282 .End() 283 284 .Add(optionMenuField->CreateLabelLayoutItem(), 0, 4) 285 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 4) 286 .Add(optionMenuField->CreateMenuBarLayoutItem()) 287 .Add(fOptionConflictView) 288 .End() 289 290 .Add(commandMenuField->CreateLabelLayoutItem(), 0, 5) 291 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 5) 292 .Add(commandMenuField->CreateMenuBarLayoutItem()) 293 .Add(fCommandConflictView) 294 .End() 295 296 .End() 297 .AddGlue() 298 .AddGroup(B_HORIZONTAL) 299 .Add(fCancelButton) 300 .AddGlue() 301 .Add(fRevertButton) 302 .Add(fOkButton) 303 .End() 304 .SetInsets(B_USE_WINDOW_SPACING) 305 .End(); 306 307 _MarkMenuItems(); 308 _ValidateDuplicateKeys(); 309 } 310 311 312 ModifierKeysWindow::~ModifierKeysWindow() 313 { 314 be_app->PostMessage(kMsgCloseModifierKeysWindow); 315 } 316 317 318 void 319 ModifierKeysWindow::MessageReceived(BMessage* message) 320 { 321 switch (message->what) { 322 case kMsgUpdateModifier: 323 { 324 int32 menuitem = MENU_ITEM_FIRST; 325 int32 key = -1; 326 327 for (; menuitem <= MENU_ITEM_LAST; menuitem++) { 328 if (message->FindInt32(_KeyToString(menuitem), &key) == B_OK) 329 break; 330 } 331 332 if (key == -1) 333 return; 334 335 // menuitem contains the item we want to set 336 // key contains the item we want to set it to. 337 338 uint32 leftKey = _KeyToKeyCode(key); 339 uint32 rightKey = _KeyToKeyCode(key, true); 340 341 switch (menuitem) { 342 case MENU_ITEM_CAPS: 343 fCurrentMap->caps_key = leftKey; 344 break; 345 346 case MENU_ITEM_SHIFT: 347 fCurrentMap->left_shift_key = leftKey; 348 fCurrentMap->right_shift_key = rightKey; 349 break; 350 351 case MENU_ITEM_CONTROL: 352 fCurrentMap->left_control_key = leftKey; 353 fCurrentMap->right_control_key = rightKey; 354 break; 355 356 case MENU_ITEM_OPTION: 357 fCurrentMap->left_option_key = leftKey; 358 fCurrentMap->right_option_key = rightKey; 359 break; 360 361 case MENU_ITEM_COMMAND: 362 fCurrentMap->left_command_key = leftKey; 363 fCurrentMap->right_command_key = rightKey; 364 break; 365 } 366 367 _MarkMenuItems(); 368 _ValidateDuplicateKeys(); 369 370 // enable/disable revert button 371 fRevertButton->SetEnabled(memcmp(fCurrentMap, fSavedMap, sizeof(key_map))); 372 break; 373 } 374 375 // OK button 376 case kMsgApplyModifiers: 377 { 378 // if duplicate modifiers are found, don't update 379 if (_DuplicateKeys() != 0) 380 break; 381 382 BMessage* updateModifiers = new BMessage(kMsgUpdateModifierKeys); 383 384 if (fCurrentMap->left_shift_key != fSavedMap->left_shift_key) 385 updateModifiers->AddUInt32("left_shift_key", fCurrentMap->left_shift_key); 386 387 if (fCurrentMap->right_shift_key != fSavedMap->right_shift_key) 388 updateModifiers->AddUInt32("right_shift_key", fCurrentMap->right_shift_key); 389 390 if (fCurrentMap->left_control_key != fSavedMap->left_control_key) 391 updateModifiers->AddUInt32("left_control_key", fCurrentMap->left_control_key); 392 393 if (fCurrentMap->right_control_key != fSavedMap->right_control_key) 394 updateModifiers->AddUInt32("right_control_key", fCurrentMap->right_control_key); 395 396 if (fCurrentMap->left_option_key != fSavedMap->left_option_key) 397 updateModifiers->AddUInt32("left_option_key", fCurrentMap->left_option_key); 398 399 if (fCurrentMap->right_option_key != fSavedMap->right_option_key) 400 updateModifiers->AddUInt32("right_option_key", fCurrentMap->right_option_key); 401 402 if (fCurrentMap->left_command_key != fSavedMap->left_command_key) 403 updateModifiers->AddUInt32("left_command_key", fCurrentMap->left_command_key); 404 405 if (fCurrentMap->right_command_key != fSavedMap->right_command_key) 406 updateModifiers->AddUInt32("right_command_key", fCurrentMap->right_command_key); 407 408 if (fCurrentMap->caps_key != fSavedMap->caps_key) 409 updateModifiers->AddUInt32("caps_key", fCurrentMap->caps_key); 410 411 // KeymapWindow updates the modifiers 412 be_app->PostMessage(updateModifiers); 413 414 // we are done here, close the window 415 this->PostMessage(B_QUIT_REQUESTED); 416 break; 417 } 418 419 // Revert button 420 case kMsgRevertModifiers: 421 memcpy(fCurrentMap, fSavedMap, sizeof(key_map)); 422 423 _MarkMenuItems(); 424 _ValidateDuplicateKeys(); 425 426 fRevertButton->SetEnabled(false); 427 break; 428 429 default: 430 BWindow::MessageReceived(message); 431 } 432 } 433 434 435 // #pragma mark - ModifierKeysWindow private methods 436 437 void 438 ModifierKeysWindow::_CreateMenuField( 439 BPopUpMenu** outMenu, BMenuField** outField, uint32 inKey, const char* comment) 440 { 441 const char* sKey = _KeyToString(inKey); 442 const char* tKey = B_TRANSLATE_NOCOLLECT(sKey); 443 BPopUpMenu* menu = new BPopUpMenu(tKey, true, true); 444 445 for (int32 key = MENU_ITEM_FIRST; key <= MENU_ITEM_LAST; key++) { 446 if (key == MENU_ITEM_SEPARATOR) { 447 // add separator item 448 BSeparatorItem* separator = new BSeparatorItem; 449 menu->AddItem(separator, MENU_ITEM_SEPARATOR); 450 continue; 451 } 452 453 BMessage* message = new BMessage(kMsgUpdateModifier); 454 message->AddInt32(sKey, key); 455 BMenuItem* item = new BMenuItem(B_TRANSLATE_NOCOLLECT(_KeyToString(key)), message); 456 menu->AddItem(item, key); 457 } 458 459 menu->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_VERTICAL_UNSET)); 460 461 BMenuField* menuField = new BMenuField(comment, menu); 462 menuField->SetAlignment(B_ALIGN_RIGHT); 463 464 *outMenu = menu; 465 *outField = menuField; 466 } 467 468 469 void 470 ModifierKeysWindow::_MarkMenuItems() 471 { 472 _MarkMenuItem(fShiftMenu, fShiftConflictView, 473 fCurrentMap->left_shift_key, fCurrentMap->right_shift_key); 474 _MarkMenuItem(fControlMenu, fControlConflictView, 475 fCurrentMap->left_control_key, fCurrentMap->right_control_key); 476 _MarkMenuItem(fOptionMenu, fOptionConflictView, 477 fCurrentMap->left_option_key, fCurrentMap->right_option_key); 478 _MarkMenuItem(fCommandMenu, fCommandConflictView, 479 fCurrentMap->left_command_key, fCurrentMap->right_command_key); 480 _MarkMenuItem(fCapsMenu, fCapsConflictView, 481 fCurrentMap->caps_key, fCurrentMap->caps_key); 482 } 483 484 485 void 486 ModifierKeysWindow::_MarkMenuItem(BPopUpMenu* menu, ConflictView* conflictView, 487 uint32 leftKey, uint32 rightKey) 488 { 489 for (int32 key = MENU_ITEM_FIRST; key <= MENU_ITEM_LAST; key++) { 490 if (key == MENU_ITEM_SEPARATOR) 491 continue; 492 493 if (leftKey == _KeyToKeyCode(key) && rightKey == _KeyToKeyCode(key, true)) 494 menu->ItemAt(key)->SetMarked(true); 495 } 496 497 // Set the warning icon if not marked 498 BBitmap* icon = conflictView->Icon(); 499 500 conflictView->SetWarnIcon(menu->FindMarked() == NULL); 501 502 // if there was a change invalidate the view 503 if (icon != conflictView->Icon()) 504 conflictView->Invalidate(); 505 } 506 507 508 // get the string for a modifier key 509 const char* 510 ModifierKeysWindow::_KeyToString(int32 key) 511 { 512 switch (key) { 513 case MENU_ITEM_CAPS: 514 return B_TRANSLATE_COMMENT("Caps key", 515 "Label of key above Shift, usually Caps Lock"); 516 517 case MENU_ITEM_SHIFT: 518 return B_TRANSLATE_COMMENT("Shift key", 519 "Label of key above Ctrl, usually Shift"); 520 521 case MENU_ITEM_CONTROL: 522 return B_TRANSLATE_COMMENT("Ctrl key", 523 "Label of key farthest from the spacebar, usually Ctrl" 524 "e.g. Strg for German keyboard"); 525 526 case MENU_ITEM_OPTION: 527 return B_TRANSLATE_COMMENT("Win/Cmd key", 528 "Label of the \"Windows\" key (PC)/Command key (Mac)"); 529 530 case MENU_ITEM_COMMAND: 531 return B_TRANSLATE_COMMENT("Alt/Opt key", 532 "Label of Alt key (PC)/Option key (Mac)"); 533 534 case MENU_ITEM_DISABLED: 535 return B_TRANSLATE_COMMENT("Disabled", "Do nothing"); 536 } 537 538 return ""; 539 } 540 541 542 // get the keycode for a modifier key 543 uint32 544 ModifierKeysWindow::_KeyToKeyCode(int32 key, bool right) 545 { 546 switch (key) { 547 case MENU_ITEM_CAPS: 548 return 0x3b; 549 550 case MENU_ITEM_SHIFT: 551 if (right) 552 return 0x56; 553 return 0x4b; 554 555 case MENU_ITEM_CONTROL: 556 if (right) 557 return 0x60; 558 return 0x5c; 559 560 case MENU_ITEM_OPTION: 561 if (right) 562 return 0x67; 563 return 0x66; 564 565 case MENU_ITEM_COMMAND: 566 if (right) 567 return 0x5f; 568 return 0x5d; 569 570 case MENU_ITEM_DISABLED: 571 return 0; 572 } 573 574 return 0; 575 } 576 577 578 // validate duplicate keys 579 void 580 ModifierKeysWindow::_ValidateDuplicateKeys() 581 { 582 uint32 dupMask = _DuplicateKeys(); 583 _ValidateDuplicateKey(fCapsConflictView, CAPS_KEY & dupMask); 584 _ValidateDuplicateKey(fShiftConflictView, SHIFT_KEY & dupMask); 585 _ValidateDuplicateKey(fControlConflictView, CONTROL_KEY & dupMask); 586 _ValidateDuplicateKey(fOptionConflictView, OPTION_KEY & dupMask); 587 _ValidateDuplicateKey(fCommandConflictView, COMMAND_KEY & dupMask); 588 fOkButton->SetEnabled(dupMask == 0); 589 } 590 591 592 void 593 ModifierKeysWindow::_ValidateDuplicateKey(ConflictView* view, uint32 mask) 594 { 595 BBitmap* icon = view->Icon(); 596 view->SetStopIcon(mask != 0); 597 // if there was a change invalidate the view 598 if (icon != view->Icon()) 599 view->Invalidate(); 600 } 601 602 603 // return a mask marking which keys are duplicates of each other for 604 // validation. 605 uint32 606 ModifierKeysWindow::_DuplicateKeys() 607 { 608 uint32 duplicateMask = 0; 609 610 for (int32 testKey = MENU_ITEM_FIRST; testKey <= MENU_ITEM_LAST; testKey++) { 611 uint32 testLeft = 0; 612 uint32 testRight = 0; 613 614 switch (testKey) { 615 case MENU_ITEM_CAPS: 616 testLeft = fCurrentMap->caps_key; 617 break; 618 619 case MENU_ITEM_SHIFT: 620 testLeft = fCurrentMap->left_shift_key; 621 testRight = fCurrentMap->right_shift_key; 622 break; 623 624 case MENU_ITEM_CONTROL: 625 testLeft = fCurrentMap->left_control_key; 626 testRight = fCurrentMap->right_control_key; 627 break; 628 629 case MENU_ITEM_OPTION: 630 testLeft = fCurrentMap->left_option_key; 631 testRight = fCurrentMap->right_option_key; 632 break; 633 634 case MENU_ITEM_COMMAND: 635 testLeft = fCurrentMap->left_command_key; 636 testRight = fCurrentMap->right_command_key; 637 break; 638 } 639 640 if (testLeft == 0 && testRight == 0) 641 continue; 642 643 for (int32 key = MENU_ITEM_FIRST; key <= MENU_ITEM_LAST; key++) { 644 if (key == testKey) { 645 // skip over yourself 646 continue; 647 } 648 649 uint32 left = 0; 650 uint32 right = 0; 651 652 switch (key) { 653 case MENU_ITEM_CAPS: 654 left = fCurrentMap->caps_key; 655 break; 656 657 case MENU_ITEM_SHIFT: 658 left = fCurrentMap->left_shift_key; 659 right = fCurrentMap->right_shift_key; 660 break; 661 662 case MENU_ITEM_CONTROL: 663 left = fCurrentMap->left_control_key; 664 right = fCurrentMap->right_control_key; 665 break; 666 667 case MENU_ITEM_OPTION: 668 left = fCurrentMap->left_option_key; 669 right = fCurrentMap->right_option_key; 670 break; 671 672 case MENU_ITEM_COMMAND: 673 left = fCurrentMap->left_command_key; 674 right = fCurrentMap->right_command_key; 675 break; 676 } 677 678 if (left == 0 && right == 0) 679 continue; 680 681 if (left == testLeft || right == testRight) { 682 duplicateMask |= 1 << testKey; 683 duplicateMask |= 1 << key; 684 } 685 } 686 } 687 688 return duplicateMask; 689 } 690