xref: /haiku/src/preferences/keymap/ModifierKeysWindow.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
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