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 <Button.h> 18 #include <Catalog.h> 19 #include <ControlLook.h> 20 #include <FindDirectory.h> 21 #include <LayoutBuilder.h> 22 #include <Locale.h> 23 #include <MenuField.h> 24 #include <MenuItem.h> 25 #include <Message.h> 26 #include <Path.h> 27 #include <PopUpMenu.h> 28 #include <Resources.h> 29 #include <SeparatorView.h> 30 #include <Size.h> 31 #include <StringView.h> 32 33 #include "KeymapApplication.h" 34 #include "StatusMenuField.h" 35 36 37 enum { 38 CAPS_KEY = 0x00000001, 39 SHIFT_KEY = 0x00000002, 40 CONTROL_KEY = 0x00000004, 41 OPTION_KEY = 0x00000008, 42 COMMAND_KEY = 0x00000010, 43 }; 44 45 enum { 46 MENU_ITEM_CAPS, 47 MENU_ITEM_SHIFT, 48 MENU_ITEM_CONTROL, 49 MENU_ITEM_OPTION, 50 MENU_ITEM_COMMAND, 51 MENU_ITEM_SEPARATOR, 52 MENU_ITEM_DISABLED, 53 54 MENU_ITEM_FIRST = MENU_ITEM_CAPS, 55 MENU_ITEM_LAST = MENU_ITEM_DISABLED 56 }; 57 58 59 static const uint32 kMsgUpdateStatus = 'stat'; 60 static const uint32 kMsgUpdateModifier = 'upmd'; 61 static const uint32 kMsgApplyModifiers = 'apmd'; 62 static const uint32 kMsgRevertModifiers = 'rvmd'; 63 64 static const int32 kUnset = 0; 65 static const int32 kDisabled = -1; 66 67 68 #undef B_TRANSLATION_CONTEXT 69 #define B_TRANSLATION_CONTEXT "Modifier keys window" 70 71 72 ModifierKeysWindow::ModifierKeysWindow() 73 : 74 BWindow(BRect(0, 0, 360, 220), B_TRANSLATE("Modifier keys"), 75 B_FLOATING_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS) 76 { 77 get_key_map(&fCurrentMap, &fCurrentBuffer); 78 get_key_map(&fSavedMap, &fSavedBuffer); 79 80 BStringView* keyRole = new BStringView("key role", 81 B_TRANSLATE_COMMENT("Role", "As in the role of a modifier key")); 82 keyRole->SetAlignment(B_ALIGN_RIGHT); 83 keyRole->SetFont(be_bold_font); 84 85 BStringView* keyLabel = new BStringView("key label", 86 B_TRANSLATE_COMMENT("Key", "As in a computer keyboard key")); 87 keyLabel->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 88 keyLabel->SetFont(be_bold_font); 89 90 _CreateMenuField(&fCapsMenu, (BMenuField**)&fCapsField, MENU_ITEM_CAPS, 91 B_TRANSLATE_COMMENT("Caps Lock:", "Caps Lock key role name")); 92 _CreateMenuField(&fShiftMenu, (BMenuField**)&fShiftField, MENU_ITEM_SHIFT, 93 B_TRANSLATE_COMMENT("Shift:", "Shift key role name")); 94 _CreateMenuField(&fControlMenu, (BMenuField**)&fControlField, MENU_ITEM_CONTROL, 95 B_TRANSLATE_COMMENT("Control:", "Control key role name")); 96 _CreateMenuField(&fOptionMenu,(BMenuField**) &fOptionField, MENU_ITEM_OPTION, 97 B_TRANSLATE_COMMENT("Option:", "Option key role name")); 98 _CreateMenuField(&fCommandMenu, (BMenuField**)&fCommandField, MENU_ITEM_COMMAND, 99 B_TRANSLATE_COMMENT("Command:", "Command key role name")); 100 101 fCancelButton = new BButton("cancelButton", B_TRANSLATE("Cancel"), 102 new BMessage(B_QUIT_REQUESTED)); 103 104 fRevertButton = new BButton("revertButton", B_TRANSLATE("Revert"), 105 new BMessage(kMsgRevertModifiers)); 106 fRevertButton->SetEnabled(false); 107 108 fOkButton = new BButton("okButton", B_TRANSLATE("Set modifier keys"), 109 new BMessage(kMsgApplyModifiers)); 110 fOkButton->MakeDefault(true); 111 112 BLayoutBuilder::Group<>(this, B_VERTICAL, B_USE_DEFAULT_SPACING) 113 .AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING) 114 .Add(keyRole, 0, 0) 115 .Add(keyLabel, 1, 0) 116 117 .Add(fCapsField->CreateLabelLayoutItem(), 0, 1) 118 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 1) 119 .Add(fCapsField->CreateMenuBarLayoutItem()) 120 .End() 121 122 .Add(fShiftField->CreateLabelLayoutItem(), 0, 2) 123 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 2) 124 .Add(fShiftField->CreateMenuBarLayoutItem()) 125 .End() 126 127 .Add(fControlField->CreateLabelLayoutItem(), 0, 3) 128 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 3) 129 .Add(fControlField->CreateMenuBarLayoutItem()) 130 .End() 131 132 .Add(fOptionField->CreateLabelLayoutItem(), 0, 4) 133 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 4) 134 .Add(fOptionField->CreateMenuBarLayoutItem()) 135 .End() 136 137 .Add(fCommandField->CreateLabelLayoutItem(), 0, 5) 138 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING, 1, 5) 139 .Add(fCommandField->CreateMenuBarLayoutItem()) 140 .End() 141 142 .End() 143 .AddGlue() 144 .AddGroup(B_HORIZONTAL) 145 .Add(fRevertButton) 146 .AddGlue() 147 .Add(fCancelButton) 148 .Add(fOkButton) 149 .End() 150 .SetInsets(B_USE_WINDOW_SPACING) 151 .End(); 152 153 // mark menu items and update status icons 154 _UpdateStatus(); 155 } 156 157 158 ModifierKeysWindow::~ModifierKeysWindow() 159 { 160 be_app->PostMessage(kMsgCloseModifierKeysWindow); 161 } 162 163 164 void 165 ModifierKeysWindow::MessageReceived(BMessage* message) 166 { 167 switch (message->what) { 168 case kMsgUpdateModifier: 169 { 170 int32 menuitem = MENU_ITEM_FIRST; 171 int32 key = kDisabled; 172 173 for (; menuitem <= MENU_ITEM_LAST; menuitem++) { 174 if (message->FindInt32(_KeyToString(menuitem), &key) == B_OK) 175 break; 176 } 177 178 if (key == kDisabled) 179 return; 180 181 // menuitem contains the item we want to set 182 // key contains the item we want to set it to. 183 184 uint32 leftKey = _KeyToKeyCode(key); 185 uint32 rightKey = _KeyToKeyCode(key, true); 186 187 switch (menuitem) { 188 case MENU_ITEM_CAPS: 189 fCurrentMap->caps_key = leftKey; 190 break; 191 192 case MENU_ITEM_SHIFT: 193 fCurrentMap->left_shift_key = leftKey; 194 fCurrentMap->right_shift_key = rightKey; 195 break; 196 197 case MENU_ITEM_CONTROL: 198 fCurrentMap->left_control_key = leftKey; 199 fCurrentMap->right_control_key = rightKey; 200 break; 201 202 case MENU_ITEM_OPTION: 203 fCurrentMap->left_option_key = leftKey; 204 fCurrentMap->right_option_key = rightKey; 205 break; 206 207 case MENU_ITEM_COMMAND: 208 fCurrentMap->left_command_key = leftKey; 209 fCurrentMap->right_command_key = rightKey; 210 break; 211 } 212 213 _UpdateStatus(); 214 215 // enable/disable revert button 216 fRevertButton->SetEnabled(memcmp(fCurrentMap, fSavedMap, sizeof(key_map))); 217 break; 218 } 219 220 // OK button 221 case kMsgApplyModifiers: 222 { 223 // if duplicate modifiers are found, don't update 224 if (_DuplicateKeys() != 0) 225 break; 226 227 BMessage* updateModifiers = new BMessage(kMsgUpdateModifierKeys); 228 229 if (fCurrentMap->caps_key != fSavedMap->caps_key) 230 updateModifiers->AddUInt32("caps_key", fCurrentMap->caps_key); 231 232 if (fCurrentMap->left_shift_key != fSavedMap->left_shift_key) 233 updateModifiers->AddUInt32("left_shift_key", fCurrentMap->left_shift_key); 234 235 if (fCurrentMap->right_shift_key != fSavedMap->right_shift_key) 236 updateModifiers->AddUInt32("right_shift_key", fCurrentMap->right_shift_key); 237 238 if (fCurrentMap->left_control_key != fSavedMap->left_control_key) 239 updateModifiers->AddUInt32("left_control_key", fCurrentMap->left_control_key); 240 241 if (fCurrentMap->right_control_key != fSavedMap->right_control_key) 242 updateModifiers->AddUInt32("right_control_key", fCurrentMap->right_control_key); 243 244 if (fCurrentMap->left_option_key != fSavedMap->left_option_key) 245 updateModifiers->AddUInt32("left_option_key", fCurrentMap->left_option_key); 246 247 if (fCurrentMap->right_option_key != fSavedMap->right_option_key) 248 updateModifiers->AddUInt32("right_option_key", fCurrentMap->right_option_key); 249 250 if (fCurrentMap->left_command_key != fSavedMap->left_command_key) 251 updateModifiers->AddUInt32("left_command_key", fCurrentMap->left_command_key); 252 253 if (fCurrentMap->right_command_key != fSavedMap->right_command_key) 254 updateModifiers->AddUInt32("right_command_key", fCurrentMap->right_command_key); 255 256 // KeymapWindow updates the modifiers 257 be_app->PostMessage(updateModifiers); 258 259 // we are done here, close the window 260 this->PostMessage(B_QUIT_REQUESTED); 261 break; 262 } 263 264 // Revert button 265 case kMsgRevertModifiers: 266 memcpy(fCurrentMap, fSavedMap, sizeof(key_map)); 267 268 _UpdateStatus(); 269 270 fRevertButton->SetEnabled(false); 271 break; 272 273 default: 274 BWindow::MessageReceived(message); 275 } 276 } 277 278 279 // #pragma mark - ModifierKeysWindow private methods 280 281 282 void 283 ModifierKeysWindow::_CreateMenuField(BPopUpMenu** _menu, BMenuField** _menuField, uint32 key, 284 const char* label) 285 { 286 const char* keyName = _KeyToString(key); 287 const char* name = B_TRANSLATE_NOCOLLECT(keyName); 288 BPopUpMenu* menu = new BPopUpMenu(name, true, true); 289 290 for (int32 key = MENU_ITEM_FIRST; key <= MENU_ITEM_LAST; key++) { 291 if (key == MENU_ITEM_SEPARATOR) { 292 // add separator item 293 BSeparatorItem* separator = new BSeparatorItem; 294 menu->AddItem(separator, MENU_ITEM_SEPARATOR); 295 continue; 296 } 297 298 BMessage* message = new BMessage(kMsgUpdateModifier); 299 message->AddInt32(keyName, key); 300 BMenuItem* item = new BMenuItem(B_TRANSLATE_NOCOLLECT(_KeyToString(key)), message); 301 menu->AddItem(item, key); 302 } 303 304 menu->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_VERTICAL_UNSET)); 305 306 BMenuField* menuField = new StatusMenuField(label, menu); 307 menuField->SetAlignment(B_ALIGN_RIGHT); 308 309 *_menu = menu; 310 *_menuField = menuField; 311 } 312 313 314 void 315 ModifierKeysWindow::_MarkMenuItems() 316 { 317 _MarkMenuItem("caps lock", fCapsMenu, fCurrentMap->caps_key, fCurrentMap->caps_key); 318 // mark but don't set unmatched for Caps Lock 319 fShiftField->SetUnmatched(_MarkMenuItem("shift", fShiftMenu, 320 fCurrentMap->left_shift_key, fCurrentMap->right_shift_key) == false); 321 fControlField->SetUnmatched(_MarkMenuItem("control", fControlMenu, 322 fCurrentMap->left_control_key, fCurrentMap->right_control_key) == false); 323 fOptionField->SetUnmatched(_MarkMenuItem("option", fOptionMenu, 324 fCurrentMap->left_option_key, fCurrentMap->right_option_key) == false); 325 fCommandField->SetUnmatched(_MarkMenuItem("command", fCommandMenu, 326 fCurrentMap->left_command_key, fCurrentMap->right_command_key) == false); 327 } 328 329 330 bool 331 ModifierKeysWindow::_MarkMenuItem(const char* role, BPopUpMenu* menu, uint32 left, uint32 right) 332 { 333 for (int32 key = MENU_ITEM_FIRST; key <= MENU_ITEM_LAST; key++) { 334 if (key == MENU_ITEM_SEPARATOR) 335 continue; 336 337 uint32 leftKey = _KeyToKeyCode(key); 338 uint32 rightKey = _KeyToKeyCode(key, true); 339 if (leftKey != rightKey && key == MENU_ITEM_CAPS) { 340 // mark Caps Lock on Caps Lock role if either left or right is caps 341 // otherwise mark Caps Lock if left side is caps 342 uint32 capsKey = _KeyToKeyCode(MENU_ITEM_CAPS); 343 if (strcmp(role, "caps lock") == 0 && (left == capsKey || right == capsKey)) 344 menu->ItemAt(key)->SetMarked(true); 345 else if (left == capsKey) 346 menu->ItemAt(key)->SetMarked(true); 347 } else if (left == leftKey && right == rightKey) 348 menu->ItemAt(key)->SetMarked(true); 349 } 350 351 return menu->FindMarked() != NULL; 352 } 353 354 355 // get the string for a modifier key 356 const char* 357 ModifierKeysWindow::_KeyToString(int32 key) 358 { 359 switch (key) { 360 case MENU_ITEM_CAPS: 361 return B_TRANSLATE_COMMENT("Caps Lock key", 362 "Label of key above Shift, usually Caps Lock"); 363 364 case MENU_ITEM_SHIFT: 365 return B_TRANSLATE_COMMENT("Shift key", 366 "Label of key above Ctrl, usually Shift"); 367 368 case MENU_ITEM_CONTROL: 369 return B_TRANSLATE_COMMENT("Ctrl key", 370 "Label of key farthest from the spacebar, usually Ctrl" 371 "e.g. Strg for German keyboard"); 372 373 case MENU_ITEM_OPTION: 374 return B_TRANSLATE_COMMENT("Win/Cmd key", 375 "Label of the \"Windows\" key (PC)/Command key (Mac)"); 376 377 case MENU_ITEM_COMMAND: 378 return B_TRANSLATE_COMMENT("Alt/Opt key", 379 "Label of Alt key (PC)/Option key (Mac)"); 380 381 case MENU_ITEM_DISABLED: 382 return B_TRANSLATE_COMMENT("Disabled", "Do nothing"); 383 } 384 385 return B_EMPTY_STRING; 386 } 387 388 389 // get the keycode for a modifier key 390 int32 391 ModifierKeysWindow::_KeyToKeyCode(int32 key, bool right) 392 { 393 switch (key) { 394 case MENU_ITEM_CAPS: 395 return 0x3b; 396 397 case MENU_ITEM_SHIFT: 398 if (right) 399 return 0x56; 400 return 0x4b; 401 402 case MENU_ITEM_CONTROL: 403 if (right) 404 return 0x60; 405 return 0x5c; 406 407 case MENU_ITEM_OPTION: 408 if (right) 409 return 0x67; 410 return 0x66; 411 412 case MENU_ITEM_COMMAND: 413 if (right) 414 return 0x5f; 415 return 0x5d; 416 417 case MENU_ITEM_DISABLED: 418 return kDisabled; 419 } 420 421 return kUnset; 422 } 423 424 425 // validate duplicate keys 426 void 427 ModifierKeysWindow::_ValidateDuplicateKeys() 428 { 429 uint32 dupMask = _DuplicateKeys(); 430 _ValidateDuplicateKey(fCapsField, CAPS_KEY & dupMask); 431 _ValidateDuplicateKey(fShiftField, SHIFT_KEY & dupMask); 432 _ValidateDuplicateKey(fControlField, CONTROL_KEY & dupMask); 433 _ValidateDuplicateKey(fOptionField, OPTION_KEY & dupMask); 434 _ValidateDuplicateKey(fCommandField, COMMAND_KEY & dupMask); 435 fOkButton->SetEnabled(dupMask == 0); 436 } 437 438 439 void 440 ModifierKeysWindow::_ValidateDuplicateKey(StatusMenuField* field, uint32 mask) 441 { 442 if (mask != 0) // don't override if false 443 field->SetDuplicate(true); 444 } 445 446 447 // return a mask marking which keys are duplicates of each other for 448 // validation. 449 uint32 450 ModifierKeysWindow::_DuplicateKeys() 451 { 452 uint32 duplicateMask = 0; 453 454 int32 testLeft, testRight, left, right; 455 for (int32 testKey = MENU_ITEM_FIRST; testKey < MENU_ITEM_SEPARATOR; testKey++) { 456 testLeft = kUnset; 457 testRight = kUnset; 458 459 switch (testKey) { 460 case MENU_ITEM_CAPS: 461 testLeft = fCurrentMap->caps_key; 462 testRight = kDisabled; 463 break; 464 465 case MENU_ITEM_SHIFT: 466 testLeft = fCurrentMap->left_shift_key; 467 testRight = fCurrentMap->right_shift_key; 468 break; 469 470 case MENU_ITEM_CONTROL: 471 testLeft = fCurrentMap->left_control_key; 472 testRight = fCurrentMap->right_control_key; 473 break; 474 475 case MENU_ITEM_OPTION: 476 testLeft = fCurrentMap->left_option_key; 477 testRight = fCurrentMap->right_option_key; 478 break; 479 480 case MENU_ITEM_COMMAND: 481 testLeft = fCurrentMap->left_command_key; 482 testRight = fCurrentMap->right_command_key; 483 break; 484 } 485 486 if (testLeft == kUnset && (testRight == kUnset || testRight == kDisabled)) 487 continue; 488 489 for (int32 key = MENU_ITEM_FIRST; key < MENU_ITEM_SEPARATOR; key++) { 490 if (key == testKey) { 491 // skip over yourself 492 continue; 493 } 494 495 left = kUnset; 496 right = kUnset; 497 498 switch (key) { 499 case MENU_ITEM_CAPS: 500 left = fCurrentMap->caps_key; 501 right = kDisabled; 502 break; 503 504 case MENU_ITEM_SHIFT: 505 left = fCurrentMap->left_shift_key; 506 right = fCurrentMap->right_shift_key; 507 break; 508 509 case MENU_ITEM_CONTROL: 510 left = fCurrentMap->left_control_key; 511 right = fCurrentMap->right_control_key; 512 break; 513 514 case MENU_ITEM_OPTION: 515 left = fCurrentMap->left_option_key; 516 right = fCurrentMap->right_option_key; 517 break; 518 519 case MENU_ITEM_COMMAND: 520 left = fCurrentMap->left_command_key; 521 right = fCurrentMap->right_command_key; 522 break; 523 } 524 525 if (left == kUnset && (right == kUnset || right == kDisabled)) 526 continue; 527 528 // left or right is set 529 530 if (left == testLeft || right == testRight) { 531 duplicateMask |= 1 << testKey; 532 duplicateMask |= 1 << key; 533 } 534 } 535 } 536 537 return duplicateMask; 538 } 539 540 541 void 542 ModifierKeysWindow::_UpdateStatus() 543 { 544 // the order is important 545 _MarkMenuItems(); 546 _ValidateDuplicateKeys(); 547 } 548