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
ModifierKeysWindow()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
~ModifierKeysWindow()158 ModifierKeysWindow::~ModifierKeysWindow()
159 {
160 be_app->PostMessage(kMsgCloseModifierKeysWindow);
161 }
162
163
164 void
MessageReceived(BMessage * message)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
_CreateMenuField(BPopUpMenu ** _menu,BMenuField ** _menuField,uint32 key,const char * label)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
_MarkMenuItems()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
_MarkMenuItem(const char * role,BPopUpMenu * menu,uint32 left,uint32 right)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*
_KeyToString(int32 key)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
_KeyToKeyCode(int32 key,bool right)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
_ValidateDuplicateKeys()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
_ValidateDuplicateKey(StatusMenuField * field,uint32 mask)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
_DuplicateKeys()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
_UpdateStatus()543 ModifierKeysWindow::_UpdateStatus()
544 {
545 // the order is important
546 _MarkMenuItems();
547 _ValidateDuplicateKeys();
548 }
549