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 StatusMenuItem* item 301 = new StatusMenuItem(B_TRANSLATE_NOCOLLECT(_KeyToString(key)), message); 302 menu->AddItem(item, key); 303 } 304 305 menu->SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_VERTICAL_UNSET)); 306 307 BMenuField* menuField = new StatusMenuField(label, menu); 308 menuField->SetAlignment(B_ALIGN_RIGHT); 309 310 *_menu = menu; 311 *_menuField = menuField; 312 } 313 314 315 void 316 ModifierKeysWindow::_MarkMenuItems() 317 { 318 _MarkMenuItem("caps lock", fCapsMenu, fCurrentMap->caps_key, fCurrentMap->caps_key); 319 // mark but don't set unmatched for Caps Lock 320 fShiftField->SetUnmatched(_MarkMenuItem("shift", fShiftMenu, 321 fCurrentMap->left_shift_key, fCurrentMap->right_shift_key) == false); 322 fControlField->SetUnmatched(_MarkMenuItem("control", fControlMenu, 323 fCurrentMap->left_control_key, fCurrentMap->right_control_key) == false); 324 fOptionField->SetUnmatched(_MarkMenuItem("option", fOptionMenu, 325 fCurrentMap->left_option_key, fCurrentMap->right_option_key) == false); 326 fCommandField->SetUnmatched(_MarkMenuItem("command", fCommandMenu, 327 fCurrentMap->left_command_key, fCurrentMap->right_command_key) == false); 328 } 329 330 331 bool 332 ModifierKeysWindow::_MarkMenuItem(const char* role, BPopUpMenu* menu, uint32 left, uint32 right) 333 { 334 for (int32 key = MENU_ITEM_FIRST; key <= MENU_ITEM_LAST; key++) { 335 if (key == MENU_ITEM_SEPARATOR) 336 continue; 337 338 uint32 leftKey = _KeyToKeyCode(key); 339 uint32 rightKey = _KeyToKeyCode(key, true); 340 if (leftKey != rightKey && key == MENU_ITEM_CAPS) { 341 // mark Caps Lock on Caps Lock role if either left or right is caps 342 // otherwise mark Caps Lock if left side is caps 343 uint32 capsKey = _KeyToKeyCode(MENU_ITEM_CAPS); 344 if (strcmp(role, "caps lock") == 0 && (left == capsKey || right == capsKey)) 345 menu->ItemAt(key)->SetMarked(true); 346 else if (left == capsKey) 347 menu->ItemAt(key)->SetMarked(true); 348 } else if (left == leftKey && right == rightKey) 349 menu->ItemAt(key)->SetMarked(true); 350 } 351 352 return menu->FindMarked() != NULL; 353 } 354 355 356 // get the string for a modifier key 357 const char* 358 ModifierKeysWindow::_KeyToString(int32 key) 359 { 360 switch (key) { 361 case MENU_ITEM_CAPS: 362 return B_TRANSLATE_COMMENT("Caps Lock key", 363 "Label of key above Shift, usually Caps Lock"); 364 365 case MENU_ITEM_SHIFT: 366 return B_TRANSLATE_COMMENT("Shift key", 367 "Label of key above Ctrl, usually Shift"); 368 369 case MENU_ITEM_CONTROL: 370 return B_TRANSLATE_COMMENT("Ctrl key", 371 "Label of key farthest from the spacebar, usually Ctrl" 372 "e.g. Strg for German keyboard"); 373 374 case MENU_ITEM_OPTION: 375 return B_TRANSLATE_COMMENT("Win/Cmd key", 376 "Label of the \"Windows\" key (PC)/Command key (Mac)"); 377 378 case MENU_ITEM_COMMAND: 379 return B_TRANSLATE_COMMENT("Alt/Opt key", 380 "Label of Alt key (PC)/Option key (Mac)"); 381 382 case MENU_ITEM_DISABLED: 383 return B_TRANSLATE_COMMENT("Disabled", "Do nothing"); 384 } 385 386 return B_EMPTY_STRING; 387 } 388 389 390 // get the keycode for a modifier key 391 int32 392 ModifierKeysWindow::_KeyToKeyCode(int32 key, bool right) 393 { 394 switch (key) { 395 case MENU_ITEM_CAPS: 396 return 0x3b; 397 398 case MENU_ITEM_SHIFT: 399 if (right) 400 return 0x56; 401 return 0x4b; 402 403 case MENU_ITEM_CONTROL: 404 if (right) 405 return 0x60; 406 return 0x5c; 407 408 case MENU_ITEM_OPTION: 409 if (right) 410 return 0x67; 411 return 0x66; 412 413 case MENU_ITEM_COMMAND: 414 if (right) 415 return 0x5f; 416 return 0x5d; 417 418 case MENU_ITEM_DISABLED: 419 return kDisabled; 420 } 421 422 return kUnset; 423 } 424 425 426 // validate duplicate keys 427 void 428 ModifierKeysWindow::_ValidateDuplicateKeys() 429 { 430 uint32 dupMask = _DuplicateKeys(); 431 _ValidateDuplicateKey(fCapsField, CAPS_KEY & dupMask); 432 _ValidateDuplicateKey(fShiftField, SHIFT_KEY & dupMask); 433 _ValidateDuplicateKey(fControlField, CONTROL_KEY & dupMask); 434 _ValidateDuplicateKey(fOptionField, OPTION_KEY & dupMask); 435 _ValidateDuplicateKey(fCommandField, COMMAND_KEY & dupMask); 436 fOkButton->SetEnabled(dupMask == 0); 437 } 438 439 440 void 441 ModifierKeysWindow::_ValidateDuplicateKey(StatusMenuField* field, uint32 mask) 442 { 443 if (mask != 0) // don't override if false 444 field->SetDuplicate(true); 445 } 446 447 448 // return a mask marking which keys are duplicates of each other for 449 // validation. 450 uint32 451 ModifierKeysWindow::_DuplicateKeys() 452 { 453 uint32 duplicateMask = 0; 454 455 int32 testLeft, testRight, left, right; 456 for (int32 testKey = MENU_ITEM_FIRST; testKey < MENU_ITEM_SEPARATOR; testKey++) { 457 testLeft = kUnset; 458 testRight = kUnset; 459 460 switch (testKey) { 461 case MENU_ITEM_CAPS: 462 testLeft = fCurrentMap->caps_key; 463 testRight = kDisabled; 464 break; 465 466 case MENU_ITEM_SHIFT: 467 testLeft = fCurrentMap->left_shift_key; 468 testRight = fCurrentMap->right_shift_key; 469 break; 470 471 case MENU_ITEM_CONTROL: 472 testLeft = fCurrentMap->left_control_key; 473 testRight = fCurrentMap->right_control_key; 474 break; 475 476 case MENU_ITEM_OPTION: 477 testLeft = fCurrentMap->left_option_key; 478 testRight = fCurrentMap->right_option_key; 479 break; 480 481 case MENU_ITEM_COMMAND: 482 testLeft = fCurrentMap->left_command_key; 483 testRight = fCurrentMap->right_command_key; 484 break; 485 } 486 487 if (testLeft == kUnset && (testRight == kUnset || testRight == kDisabled)) 488 continue; 489 490 for (int32 key = MENU_ITEM_FIRST; key < MENU_ITEM_SEPARATOR; key++) { 491 if (key == testKey) { 492 // skip over yourself 493 continue; 494 } 495 496 left = kUnset; 497 right = kUnset; 498 499 switch (key) { 500 case MENU_ITEM_CAPS: 501 left = fCurrentMap->caps_key; 502 right = kDisabled; 503 break; 504 505 case MENU_ITEM_SHIFT: 506 left = fCurrentMap->left_shift_key; 507 right = fCurrentMap->right_shift_key; 508 break; 509 510 case MENU_ITEM_CONTROL: 511 left = fCurrentMap->left_control_key; 512 right = fCurrentMap->right_control_key; 513 break; 514 515 case MENU_ITEM_OPTION: 516 left = fCurrentMap->left_option_key; 517 right = fCurrentMap->right_option_key; 518 break; 519 520 case MENU_ITEM_COMMAND: 521 left = fCurrentMap->left_command_key; 522 right = fCurrentMap->right_command_key; 523 break; 524 } 525 526 if (left == kUnset && (right == kUnset || right == kDisabled)) 527 continue; 528 529 // left or right is set 530 531 if (left == testLeft || right == testRight) { 532 duplicateMask |= 1 << testKey; 533 duplicateMask |= 1 << key; 534 } 535 } 536 } 537 538 return duplicateMask; 539 } 540 541 542 void 543 ModifierKeysWindow::_UpdateStatus() 544 { 545 // the order is important 546 _MarkMenuItems(); 547 _ValidateDuplicateKeys(); 548 } 549