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