1/*
2 * Copyright 2009-2010, Axel D��rfler, axeld@pinc-software.de.
3 * Copyright 2013-2014 Haiku, Inc. All rights reserved.
4 * Distributed under the terms of the MIT License.
5 *
6 * Authors:
7 *		Axel D��rfler, axeld@pinc-software.de
8 *		John Scipione, jscipione@gmail.com
9 */
10
11
12#include "KeyboardLayoutView.h"
13
14#include <stdio.h>
15#include <stdlib.h>
16
17#include <Beep.h>
18#include <Bitmap.h>
19#include <ControlLook.h>
20#include <LayoutUtils.h>
21#include <InputServerDevice.h>
22#include <MenuItem.h>
23#include <PopUpMenu.h>
24#include <Region.h>
25#include <Window.h>
26
27#include "Keymap.h"
28#include "KeymapApplication.h"
29
30
31#undef B_TRANSLATION_CONTEXT
32#define B_TRANSLATION_CONTEXT "Keyboard Layout View"
33
34
35static const rgb_color kBrightColor = {230, 230, 230, 255};
36static const rgb_color kDarkColor = {200, 200, 200, 255};
37static const rgb_color kSecondDeadKeyColor = {240, 240, 150, 255};
38static const rgb_color kDeadKeyColor = {152, 203, 255, 255};
39static const rgb_color kLitIndicatorColor = {116, 212, 83, 255};
40
41
42static bool
43is_left_modifier_key(uint32 keyCode)
44{
45	return keyCode == 0x4b	// left shift
46		|| keyCode == 0x5d	// left command
47		|| keyCode == 0x5c	// left control
48		|| keyCode == 0x66;	// left option
49}
50
51
52static bool
53is_right_modifier_key(uint32 keyCode)
54{
55	return keyCode == 0x56	// right shift
56		|| keyCode == 0x5f	// right command
57		|| keyCode == 0x60	// right control
58		|| keyCode == 0x67	// right option
59		|| keyCode == 0x68;	// menu
60}
61
62
63static bool
64is_lock_key(uint32 keyCode)
65{
66	return keyCode == 0x3b	// caps lock
67		|| keyCode == 0x22	// num lock
68		|| keyCode == 0x0f;	// scroll lock
69}
70
71
72static bool
73is_mappable_to_modifier(uint32 keyCode)
74{
75	return is_left_modifier_key(keyCode)
76		|| is_right_modifier_key(keyCode)
77		|| is_lock_key(keyCode);
78}
79
80
81//	#pragma mark - KeyboardLayoutView
82
83
84KeyboardLayoutView::KeyboardLayoutView(const char* name, BInputServerDevice* dev)
85	:
86	BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS | B_TRANSPARENT_BACKGROUND),
87	fKeymap(NULL),
88	fEditable(dev == NULL),
89	fModifiers(0),
90	fDeadKey(0),
91	fButtons(0),
92	fDragKey(NULL),
93	fDropTarget(NULL),
94	fOldSize(0, 0),
95	fDevice(dev)
96{
97	fLayout = new KeyboardLayout;
98	memset(fKeyState, 0, sizeof(fKeyState));
99
100	SetEventMask(B_KEYBOARD_EVENTS);
101
102	SetViewColor(B_TRANSPARENT_COLOR);
103}
104
105
106KeyboardLayoutView::~KeyboardLayoutView()
107{
108}
109
110
111void
112KeyboardLayoutView::SetKeyboardLayout(KeyboardLayout* layout)
113{
114	fLayout = layout;
115	_LayoutKeyboard();
116	Invalidate();
117}
118
119
120void
121KeyboardLayoutView::SetKeymap(Keymap* keymap)
122{
123	fKeymap = keymap;
124	Invalidate();
125}
126
127
128void
129KeyboardLayoutView::SetTarget(BMessenger target)
130{
131	fTarget = target;
132}
133
134
135void
136KeyboardLayoutView::SetBaseFont(const BFont& font)
137{
138	fBaseFont = font;
139
140	font_height fontHeight;
141	fBaseFont.GetHeight(&fontHeight);
142	fBaseFontHeight = fontHeight.ascent + fontHeight.descent;
143	fBaseFontSize = fBaseFont.Size();
144
145	Invalidate();
146}
147
148
149void
150KeyboardLayoutView::AttachedToWindow()
151{
152	SetBaseFont(*be_plain_font);
153	fSpecialFont = *be_fixed_font;
154	fModifiers = modifiers();
155}
156
157
158void
159KeyboardLayoutView::FrameResized(float width, float height)
160{
161	_LayoutKeyboard();
162}
163
164
165void
166KeyboardLayoutView::WindowActivated(bool active)
167{
168	if (active)
169		Invalidate();
170}
171
172
173BSize
174KeyboardLayoutView::MinSize()
175{
176	return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(100, 50));
177}
178
179
180void
181KeyboardLayoutView::KeyDown(const char* bytes, int32 numBytes)
182{
183	_KeyChanged(Window()->CurrentMessage());
184}
185
186
187void
188KeyboardLayoutView::KeyUp(const char* bytes, int32 numBytes)
189{
190	_KeyChanged(Window()->CurrentMessage());
191}
192
193
194void
195KeyboardLayoutView::MouseDown(BPoint point)
196{
197	fClickPoint = point;
198	fDragKey = NULL;
199	fDropPoint.x = -1;
200
201	int32 buttons = 0;
202	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
203		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
204
205	Key* key = _KeyAt(point);
206	if (fKeymap == NULL || key == NULL) {
207		fButtons = buttons;
208		return;
209	}
210
211	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0
212			|| ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0
213		&& (modifiers() & B_CONTROL_KEY) != 0)) {
214		// secondary mouse button, pop up a swap context menu
215		if (fEditable && !is_mappable_to_modifier(key->code)) {
216			// ToDo: Pop up a list of alternative characters to map
217			// the key to. Currently we only add an option to remove the
218			// current key mapping.
219			BPopUpMenu* alternativesPopUp = new BPopUpMenu(
220				"Alternatives pop up", true, true, B_ITEMS_IN_COLUMN);
221			BMessage* message = new BMessage(kMsgUpdateNormalKeys);
222			message->AddUInt32("keyCode", key->code);
223			message->AddBool("unset", true);
224			alternativesPopUp->AddItem(new BMenuItem(B_TRANSLATE("Remove"),
225				message));
226			alternativesPopUp->SetAsyncAutoDestruct(true);
227			if (alternativesPopUp->SetTargetForItems(Window()) == B_OK)
228				alternativesPopUp->Go(ConvertToScreen(point), true);
229		} else if (fEditable) {
230			// pop up the modifier keys menu
231			BPopUpMenu* modifiersPopUp = new BPopUpMenu("Modifiers pop up",
232				true, true, B_ITEMS_IN_COLUMN);
233			const key_map& map = fKeymap->Map();
234			bool isLockKey = is_lock_key(key->code);
235			BMenuItem* item = NULL;
236
237			if (is_left_modifier_key(key->code) || isLockKey) {
238				item = _CreateSwapModifiersMenuItem(B_LEFT_SHIFT_KEY,
239					isLockKey ? B_LEFT_SHIFT_KEY : B_SHIFT_KEY,
240					map.left_shift_key, key->code);
241				modifiersPopUp->AddItem(item);
242				if (key->code == map.left_shift_key)
243					item->SetMarked(true);
244
245				item = _CreateSwapModifiersMenuItem(B_LEFT_CONTROL_KEY,
246					isLockKey ? B_LEFT_CONTROL_KEY : B_CONTROL_KEY,
247					map.left_control_key, key->code);
248				modifiersPopUp->AddItem(item);
249				if (key->code == map.left_control_key)
250					item->SetMarked(true);
251
252				item = _CreateSwapModifiersMenuItem(B_LEFT_OPTION_KEY,
253					isLockKey ? B_LEFT_OPTION_KEY : B_OPTION_KEY,
254					map.left_option_key, key->code);
255				modifiersPopUp->AddItem(item);
256				if (key->code == map.left_option_key)
257					item->SetMarked(true);
258
259				item = _CreateSwapModifiersMenuItem(B_LEFT_COMMAND_KEY,
260					isLockKey ? B_LEFT_COMMAND_KEY : B_COMMAND_KEY,
261					map.left_command_key, key->code);
262				modifiersPopUp->AddItem(item);
263				if (key->code == map.left_command_key)
264					item->SetMarked(true);
265			}
266
267			if (is_right_modifier_key(key->code) || isLockKey) {
268				if (isLockKey)
269					modifiersPopUp->AddSeparatorItem();
270
271				item = _CreateSwapModifiersMenuItem(B_RIGHT_SHIFT_KEY,
272					isLockKey ? B_RIGHT_SHIFT_KEY : B_SHIFT_KEY,
273					map.right_shift_key, key->code);
274				modifiersPopUp->AddItem(item);
275				if (key->code == map.right_shift_key)
276					item->SetMarked(true);
277
278				item = _CreateSwapModifiersMenuItem(B_RIGHT_CONTROL_KEY,
279					isLockKey ? B_RIGHT_CONTROL_KEY : B_CONTROL_KEY,
280					map.right_control_key, key->code);
281				modifiersPopUp->AddItem(item);
282				if (key->code == map.right_control_key)
283					item->SetMarked(true);
284			}
285
286			item = _CreateSwapModifiersMenuItem(B_MENU_KEY, B_MENU_KEY,
287				map.menu_key, key->code);
288			modifiersPopUp->AddItem(item);
289			if (key->code == map.menu_key)
290				item->SetMarked(true);
291
292			if (is_right_modifier_key(key->code) || isLockKey) {
293				item = _CreateSwapModifiersMenuItem(B_RIGHT_OPTION_KEY,
294					isLockKey ? B_RIGHT_OPTION_KEY : B_OPTION_KEY,
295					map.right_option_key, key->code);
296				modifiersPopUp->AddItem(item);
297				if (key->code == map.right_option_key)
298					item->SetMarked(true);
299
300				item = _CreateSwapModifiersMenuItem(B_RIGHT_COMMAND_KEY,
301					isLockKey ? B_RIGHT_COMMAND_KEY : B_COMMAND_KEY,
302					map.right_command_key, key->code);
303				modifiersPopUp->AddItem(item);
304				if (key->code == map.right_command_key)
305					item->SetMarked(true);
306			}
307
308			modifiersPopUp->AddSeparatorItem();
309
310			item = _CreateSwapModifiersMenuItem(B_CAPS_LOCK, B_CAPS_LOCK,
311				map.caps_key, key->code);
312			modifiersPopUp->AddItem(item);
313			if (key->code == map.caps_key)
314				item->SetMarked(true);
315
316			item = _CreateSwapModifiersMenuItem(B_NUM_LOCK, B_NUM_LOCK,
317				map.num_key, key->code);
318			modifiersPopUp->AddItem(item);
319			if (key->code == map.num_key)
320				item->SetMarked(true);
321
322			item = _CreateSwapModifiersMenuItem(B_SCROLL_LOCK, B_SCROLL_LOCK,
323				map.scroll_key, key->code);
324			modifiersPopUp->AddItem(item);
325			if (key->code == map.scroll_key)
326				item->SetMarked(true);
327
328			modifiersPopUp->SetAsyncAutoDestruct(true);
329			if (modifiersPopUp->SetTargetForItems(Window()) == B_OK)
330				modifiersPopUp->Go(ConvertToScreen(point), true);
331		}
332	} else if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0
333		&& (fButtons & B_TERTIARY_MOUSE_BUTTON) == 0) {
334		// tertiary mouse button, toggle the "deadness" of dead keys
335		bool isEnabled = false;
336		uint8 deadKey = fKeymap->DeadKey(key->code, fModifiers, &isEnabled);
337		if (deadKey > 0) {
338			fKeymap->SetDeadKeyEnabled(key->code, fModifiers, !isEnabled);
339			_InvalidateKey(key);
340		}
341	} else {
342		// primary mouse button
343		if (fKeymap->IsModifierKey(key->code)) {
344			if (_KeyState(key->code)) {
345				uint32 modifier = fKeymap->Modifier(key->code);
346				if ((modifier & modifiers()) == 0) {
347					_SetKeyState(key->code, false);
348					fModifiers &= ~modifier;
349					Invalidate();
350				}
351			} else {
352				_SetKeyState(key->code, true);
353				fModifiers |= fKeymap->Modifier(key->code);
354				Invalidate();
355			}
356
357			// TODO: if possible, we could handle the lock keys for real
358		} else {
359			_SetKeyState(key->code, true);
360			_InvalidateKey(key);
361		}
362	}
363
364	fButtons = buttons;
365}
366
367
368void
369KeyboardLayoutView::MouseUp(BPoint point)
370{
371	Key* key = _KeyAt(fClickPoint);
372
373	int32 buttons = 0;
374	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
375		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
376
377	if (fKeymap == NULL || key == NULL) {
378		fDragKey = NULL;
379		return;
380	}
381
382	if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0
383		|| ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0
384			&& (modifiers() & B_CONTROL_KEY) != 0)) {
385		; // do nothing
386	} else if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0
387		&& (fButtons & B_TERTIARY_MOUSE_BUTTON) == 0) {
388		// toggle the "deadness" of dead keys via middle mouse button
389		_SetKeyState(key->code, false);
390		_InvalidateKey(key);
391		fButtons = buttons;
392	} else {
393		// primary mouse button
394		fButtons = buttons;
395
396		// modifier keys are sticky when used with the mouse
397		if (fKeymap->IsModifierKey(key->code))
398			return;
399
400		_SetKeyState(key->code, false);
401
402		if (_HandleDeadKey(key->code, fModifiers) && fDeadKey != 0)
403			return;
404
405		_InvalidateKey(key);
406
407		if (fDragKey == NULL)
408			_SendKeyDown(key);
409	}
410
411	fDragKey = NULL;
412}
413
414
415void
416KeyboardLayoutView::MouseMoved(BPoint point, uint32 transit,
417	const BMessage* dragMessage)
418{
419	if (fKeymap == NULL)
420		return;
421
422	// Ignore mouse-moved events if we are acting as a real input device.
423	if (fDevice != NULL)
424		return;
425
426	// prevent dragging for tertiary mouse button
427	if ((fButtons & B_TERTIARY_MOUSE_BUTTON) != 0)
428		return;
429
430	if (dragMessage != NULL) {
431		if (fEditable) {
432			_InvalidateKey(fDropTarget);
433			fDropPoint = point;
434
435			_EvaluateDropTarget(point);
436		}
437
438		return;
439	}
440
441	int32 buttons;
442	if (Window()->CurrentMessage() == NULL
443		|| Window()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK
444		|| buttons == 0) {
445		return;
446	}
447
448	if (fDragKey != NULL || !(fabs(point.x - fClickPoint.x) > 4
449		|| fabs(point.y - fClickPoint.y) > 4)) {
450		return;
451	}
452
453	// start dragging
454	Key* key = _KeyAt(fClickPoint);
455	if (key == NULL)
456		return;
457
458	BRect frame = _FrameFor(key);
459	BPoint offset = fClickPoint - frame.LeftTop();
460	frame.OffsetTo(B_ORIGIN);
461
462	BRect rect = frame;
463	rect.right--;
464	rect.bottom--;
465	BBitmap* bitmap = new BBitmap(rect, B_RGBA32, true);
466	bitmap->Lock();
467
468	BView* view = new BView(rect, "drag", B_FOLLOW_NONE, 0);
469	bitmap->AddChild(view);
470
471	view->SetHighColor(0, 0, 0, 0);
472	view->FillRect(view->Bounds());
473	view->SetDrawingMode(B_OP_ALPHA);
474	view->SetHighColor(0, 0, 0, 128);
475	// set the level of transparency by value
476	view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
477	_DrawKey(view, frame, key, frame, false);
478
479	view->Sync();
480	bitmap->Unlock();
481
482	BMessage drag(B_MIME_DATA);
483	drag.AddInt32("key", key->code);
484
485	char* string;
486	int32 numBytes;
487	fKeymap->GetChars(key->code, fModifiers, fDeadKey, &string,
488		&numBytes);
489	if (string != NULL) {
490		drag.AddData("text/plain", B_MIME_DATA, string, numBytes);
491		delete[] string;
492	}
493
494	DragMessage(&drag, bitmap, B_OP_ALPHA, offset);
495	fDragKey = key;
496	fDragModifiers = fModifiers;
497
498	fKeyState[key->code / 8] &= ~(1 << (7 - (key->code & 7)));
499	_InvalidateKey(key);
500}
501
502
503void
504KeyboardLayoutView::Draw(BRect updateRect)
505{
506	if (fOldSize != BSize(Bounds().Width(), Bounds().Height())) {
507		_LayoutKeyboard();
508	}
509
510	// Draw keys
511
512	for (int32 i = 0; i < fLayout->CountKeys(); i++) {
513		Key* key = fLayout->KeyAt(i);
514
515		_DrawKey(this, updateRect, key, _FrameFor(key),
516			_IsKeyPressed(key->code));
517	}
518
519	// Draw LED indicators
520
521	for (int32 i = 0; i < fLayout->CountIndicators(); i++) {
522		Indicator* indicator = fLayout->IndicatorAt(i);
523
524		_DrawIndicator(this, updateRect, indicator, _FrameFor(indicator->frame),
525			(fModifiers & indicator->modifier) != 0);
526	}
527}
528
529
530void
531KeyboardLayoutView::MessageReceived(BMessage* message)
532{
533	if (message->WasDropped() && fEditable && fDropTarget != NULL
534		&& fKeymap != NULL) {
535		int32 keyCode;
536		const char* data;
537		ssize_t size;
538		if (message->FindData("text/plain", B_MIME_DATA,
539				(const void**)&data, &size) == B_OK) {
540			// Automatically convert UTF-8 escaped strings (for example from
541			// CharacterMap)
542			int32 dataSize = 0;
543			uint8 buffer[16];
544			if (size > 3 && data[0] == '\\' && data[1] == 'x') {
545				char tempBuffer[16];
546				if (size > 15)
547					size = 15;
548				memcpy(tempBuffer, data, size);
549				tempBuffer[size] = '\0';
550				data = tempBuffer;
551
552				while (size > 3 && data[0] == '\\' && data[1] == 'x') {
553					buffer[dataSize++] = strtoul(&data[2], NULL, 16);
554					if ((buffer[dataSize - 1] & 0x80) == 0)
555						break;
556
557					size -= 4;
558					data += 4;
559				}
560				data = (const char*)buffer;
561			} else if ((data[0] & 0xc0) != 0x80 && (data[0] & 0x80) != 0) {
562				// only accept the first character UTF-8 character
563				while (dataSize < size && (data[dataSize] & 0x80) != 0) {
564					dataSize++;
565				}
566			} else if ((data[0] & 0x80) == 0) {
567				// an ASCII character
568				dataSize = 1;
569			} else {
570				// no valid character
571				beep();
572				return;
573			}
574
575			int32 buttons;
576			if (!message->IsSourceRemote()
577				&& message->FindInt32("buttons", &buttons) == B_OK
578				&& (buttons & B_PRIMARY_MOUSE_BUTTON) != 0
579				&& message->FindInt32("key", &keyCode) == B_OK) {
580				// switch keys if the dropped object came from us
581				Key* key = _KeyForCode(keyCode);
582				if (key == NULL
583					|| (key == fDropTarget && fDragModifiers == fModifiers)) {
584					return;
585				}
586
587				char* string;
588				int32 numBytes;
589				fKeymap->GetChars(fDropTarget->code, fModifiers, fDeadKey,
590					&string, &numBytes);
591				if (string != NULL) {
592					// switch keys
593					fKeymap->SetKey(fDropTarget->code, fModifiers, fDeadKey,
594						(const char*)data, dataSize);
595					fKeymap->SetKey(key->code, fDragModifiers, fDeadKey,
596						string, numBytes);
597					delete[] string;
598				} else if (fKeymap->IsModifierKey(fDropTarget->code)) {
599					// switch key with modifier
600					fKeymap->SetModifier(key->code,
601						fKeymap->Modifier(fDropTarget->code));
602					fKeymap->SetKey(fDropTarget->code, fModifiers, fDeadKey,
603						(const char*)data, dataSize);
604				}
605			} else {
606				// Send the old key to the target, so it's not lost entirely
607				_SendKeyDown(fDropTarget);
608
609				fKeymap->SetKey(fDropTarget->code, fModifiers, fDeadKey,
610					(const char*)data, dataSize);
611			}
612		} else if (!message->IsSourceRemote()
613			&& message->FindInt32("key", &keyCode) == B_OK) {
614			// Switch an unmapped key
615
616			Key* key = _KeyForCode(keyCode);
617			if (key != NULL && key == fDropTarget)
618				return;
619
620			uint32 modifier = fKeymap->Modifier(keyCode);
621
622			char* string;
623			int32 numBytes;
624			fKeymap->GetChars(fDropTarget->code, fModifiers, fDeadKey,
625				&string, &numBytes);
626			if (string != NULL) {
627				// switch key with modifier
628				fKeymap->SetModifier(fDropTarget->code, modifier);
629				fKeymap->SetKey(keyCode, fDragModifiers, fDeadKey,
630					string, numBytes);
631				delete[] string;
632			} else {
633				// switch modifier keys
634				fKeymap->SetModifier(keyCode,
635					fKeymap->Modifier(fDropTarget->code));
636				fKeymap->SetModifier(fDropTarget->code, modifier);
637			}
638
639			_InvalidateKey(fDragKey);
640		}
641
642		_InvalidateKey(fDropTarget);
643		fDropTarget = NULL;
644		fDropPoint.x = -1;
645		return;
646	}
647
648	switch (message->what) {
649		case B_UNMAPPED_KEY_DOWN:
650		case B_UNMAPPED_KEY_UP:
651			_KeyChanged(message);
652			break;
653
654		case B_MODIFIERS_CHANGED:
655		{
656			int32 newModifiers;
657			if (message->FindInt32("modifiers", &newModifiers) == B_OK
658				&& fModifiers != newModifiers) {
659				fModifiers = newModifiers;
660				_EvaluateDropTarget(fDropPoint);
661				if (Window()->IsActive())
662					Invalidate();
663			}
664			break;
665		}
666
667		default:
668			BView::MessageReceived(message);
669			break;
670	}
671}
672
673
674void
675KeyboardLayoutView::_LayoutKeyboard()
676{
677	float factorX = Bounds().Width() / fLayout->Bounds().Width();
678	float factorY = Bounds().Height() / fLayout->Bounds().Height();
679
680	fFactor = min_c(factorX, factorY);
681	fOffset = BPoint(floorf((Bounds().Width() - fLayout->Bounds().Width()
682			* fFactor) / 2),
683		floorf((Bounds().Height() - fLayout->Bounds().Height() * fFactor) / 2));
684
685	if (fLayout->DefaultKeySize().width < 11)
686		fGap = 1;
687	else
688		fGap = 2;
689
690	fOldSize.width = Bounds().Width();
691	fOldSize.height = Bounds().Height();
692}
693
694
695void
696KeyboardLayoutView::_DrawKeyButton(BView* view, BRect& rect, BRect updateRect,
697	rgb_color base, rgb_color background, bool pressed)
698{
699	uint32 flags = pressed ? BControlLook::B_ACTIVATED : 0;
700
701	be_control_look->DrawButtonFrame(view, rect, updateRect, 4.0f, base,
702		background, flags);
703	be_control_look->DrawButtonBackground(view, rect, updateRect, 4.0f,
704		base, flags);
705}
706
707
708void
709KeyboardLayoutView::_DrawKey(BView* view, BRect updateRect, const Key* key,
710	BRect rect, bool pressed)
711{
712	rgb_color base = key->dark ? kDarkColor : kBrightColor;
713	rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR);
714	rgb_color keyLabelColor = make_color(0, 0, 0, 255);
715	key_kind keyKind = kNormalKey;
716	int32 deadKey = 0;
717	bool secondDeadKey = false;
718	bool isDeadKeyEnabled = true;
719
720	char text[32];
721	if (fKeymap != NULL) {
722		_GetKeyLabel(key, text, sizeof(text), keyKind);
723		deadKey = fKeymap->DeadKey(key->code, fModifiers, &isDeadKeyEnabled);
724		secondDeadKey = fKeymap->IsDeadSecondKey(key->code, fModifiers,
725			fDeadKey);
726	} else {
727		// Show the key code if there is no keymap
728		snprintf(text, sizeof(text), "%02" B_PRIx32, key->code);
729	}
730
731	_SetFontSize(view, keyKind);
732
733	uint32 flags = pressed ? BControlLook::B_ACTIVATED : 0;
734
735	if (secondDeadKey)
736		base = kSecondDeadKeyColor;
737	else if (deadKey > 0 && isDeadKeyEnabled)
738		base = kDeadKeyColor;
739
740	if (key->shape == kRectangleKeyShape) {
741		_DrawKeyButton(view, rect, updateRect, base, background, pressed);
742
743		rect.InsetBy(1, 1);
744
745		_GetAbbreviatedKeyLabelIfNeeded(view, rect, key, text, sizeof(text));
746		be_control_look->DrawLabel(view, text, rect, updateRect,
747			base, flags, BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE),
748			&keyLabelColor);
749	} else if (key->shape == kEnterKeyShape) {
750		BRect topLeft = rect;
751		BRect topRight = rect;
752		BRect bottomLeft = rect;
753		BRect bottomRight = rect;
754
755		// TODO: for some reason, this does not always equal the bottom of
756		// the other keys...
757		bottomLeft.top = floorf(rect.top
758			+ fLayout->DefaultKeySize().height * fFactor - fGap - 1);
759		bottomLeft.right = floorf(rect.left
760			+ (key->frame.Width() - key->second_row) * fFactor - fGap - 2);
761
762		topLeft.bottom = bottomLeft.top;
763		topLeft.right = bottomLeft.right + 1;
764			// add one to make the borders meet
765
766		topRight.bottom = topLeft.bottom;
767		topRight.left = topLeft.right;
768
769		bottomRight.top = bottomLeft.top;
770		bottomRight.left = bottomLeft.right;
771
772		// draw top left corner
773		be_control_look->DrawButtonFrame(view, topLeft, updateRect,
774			4.0f, 0.0f, 4.0f, 0.0f, base, background, flags,
775			BControlLook::B_LEFT_BORDER | BControlLook::B_TOP_BORDER
776				| BControlLook::B_BOTTOM_BORDER);
777		be_control_look->DrawButtonBackground(view, topLeft, updateRect,
778			4.0f, 0.0f, 4.0f, 0.0f, base, flags,
779			BControlLook::B_LEFT_BORDER | BControlLook::B_TOP_BORDER
780				| BControlLook::B_BOTTOM_BORDER);
781
782		// draw top right corner
783		be_control_look->DrawButtonFrame(view, topRight, updateRect,
784			0.0f, 4.0f, 0.0f, 0.0f, base, background, flags,
785			BControlLook::B_TOP_BORDER | BControlLook::B_RIGHT_BORDER);
786		be_control_look->DrawButtonBackground(view, topRight, updateRect,
787			0.0f, 4.0f, 0.0f, 0.0f, base, flags,
788			BControlLook::B_TOP_BORDER | BControlLook::B_RIGHT_BORDER);
789
790		// draw bottom right corner
791		be_control_look->DrawButtonFrame(view, bottomRight, updateRect,
792			0.0f, 0.0f, 4.0f, 4.0f, base, background, flags,
793			BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER
794				| BControlLook::B_BOTTOM_BORDER);
795		be_control_look->DrawButtonBackground(view, bottomRight, updateRect,
796			0.0f, 0.0f, 4.0f, 4.0f, base, flags,
797			BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER
798				| BControlLook::B_BOTTOM_BORDER);
799
800		// clip out the bottom left corner
801		bottomLeft.right += 1;
802		bottomLeft.top -= 2;
803		BRegion region(rect);
804		region.Exclude(bottomLeft);
805		view->ConstrainClippingRegion(&region);
806
807		// draw the button background
808		BRect bgRect = rect.InsetByCopy(2, 2);
809		be_control_look->DrawButtonBackground(view, bgRect, updateRect,
810			4.0f, 4.0f, 0.0f, 4.0f, base, flags);
811
812		rect.left = bottomLeft.right;
813		_GetAbbreviatedKeyLabelIfNeeded(view, rect, key, text, sizeof(text));
814
815		// draw the button label
816		be_control_look->DrawLabel(view, text, rect, updateRect,
817			base, flags, BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE),
818			&keyLabelColor);
819
820		// reset the clipping region
821		view->ConstrainClippingRegion(NULL);
822	}
823}
824
825
826void
827KeyboardLayoutView::_DrawIndicator(BView* view, BRect updateRect,
828	const Indicator* indicator, BRect rect, bool lit)
829{
830	float rectTop = rect.top;
831	rect.top += 2 * rect.Height() / 3;
832
833	const char* label = NULL;
834	if (indicator->modifier == B_CAPS_LOCK)
835		label = "caps";
836	else if (indicator->modifier == B_NUM_LOCK)
837		label = "num";
838	else if (indicator->modifier == B_SCROLL_LOCK)
839		label = "scroll";
840	if (label != NULL) {
841		_SetFontSize(view, kIndicator);
842
843		font_height fontHeight;
844		GetFontHeight(&fontHeight);
845		if (ceilf(rect.top - fontHeight.ascent + fontHeight.descent - 2)
846				>= rectTop) {
847			view->SetHighColor(0, 0, 0);
848			view->SetLowColor(ViewColor());
849
850			BString text(label);
851			view->TruncateString(&text, B_TRUNCATE_END, rect.Width());
852			view->DrawString(text.String(),
853				BPoint(ceilf(rect.left + (rect.Width()
854						- StringWidth(text.String())) / 2),
855					ceilf(rect.top - fontHeight.descent - 2)));
856		}
857	}
858
859	rect.left += rect.Width() / 4;
860	rect.right -= rect.Width() / 3;
861
862	rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR);
863	rgb_color base = lit ? kLitIndicatorColor : kDarkColor;
864
865	be_control_look->DrawButtonFrame(view, rect, updateRect, base,
866		background, BControlLook::B_DISABLED);
867	be_control_look->DrawButtonBackground(view, rect, updateRect,
868		base, BControlLook::B_DISABLED);
869}
870
871
872const char*
873KeyboardLayoutView::_SpecialKeyLabel(const key_map& map, uint32 code,
874	bool abbreviated)
875{
876	if (code == map.caps_key) {
877		return abbreviated
878			? B_TRANSLATE_COMMENT("CAPS", "Very short for 'caps lock'")
879			: B_TRANSLATE("CAPS LOCK");
880	}
881	if (code == map.scroll_key)
882		return B_TRANSLATE("SCROLL");
883	if (code == map.num_key) {
884		return abbreviated
885			? B_TRANSLATE_COMMENT("NUM", "Very short for 'num lock'")
886			: B_TRANSLATE("NUM LOCK");
887	}
888	if (code == map.left_shift_key || code == map.right_shift_key)
889		return B_TRANSLATE("SHIFT");
890	if (code == map.left_command_key || code == map.right_command_key) {
891		return abbreviated
892			? B_TRANSLATE_COMMENT("CMD", "Very short for 'command'")
893			: B_TRANSLATE("COMMAND");
894	}
895	if (code == map.left_control_key || code == map.right_control_key) {
896		return abbreviated
897			? B_TRANSLATE_COMMENT("CTRL", "Very short for 'control'")
898			: B_TRANSLATE("CONTROL");
899	}
900	if (code == map.left_option_key || code == map.right_option_key) {
901		return abbreviated
902			? B_TRANSLATE_COMMENT("OPT", "Very short for 'option'")
903			: B_TRANSLATE("OPTION");
904	}
905	if (code == map.menu_key)
906		return B_TRANSLATE("MENU");
907	if (code == B_PRINT_KEY)
908		return B_TRANSLATE("PRINT");
909	if (code == B_PAUSE_KEY)
910		return B_TRANSLATE("PAUSE");
911
912	return NULL;
913}
914
915
916const char*
917KeyboardLayoutView::_SpecialMappedKeySymbol(const char* bytes, size_t numBytes)
918{
919	if (numBytes != 1)
920		return NULL;
921
922	if (bytes[0] == B_TAB)
923		return "\xe2\x86\xb9";
924	if (bytes[0] == B_ENTER)
925		return "\xe2\x8f\x8e";
926	if (bytes[0] == B_BACKSPACE)
927		return "\xe2\x8c\xab";
928
929	if (bytes[0] == B_UP_ARROW)
930		return "\xe2\x86\x91";
931	if (bytes[0] == B_LEFT_ARROW)
932		return "\xe2\x86\x90";
933	if (bytes[0] == B_DOWN_ARROW)
934		return "\xe2\x86\x93";
935	if (bytes[0] == B_RIGHT_ARROW)
936		return "\xe2\x86\x92";
937
938	return NULL;
939}
940
941
942const char*
943KeyboardLayoutView::_SpecialMappedKeyLabel(const char* bytes, size_t numBytes,
944	bool abbreviated)
945{
946	if (numBytes != 1)
947		return NULL;
948	if (bytes[0] == B_ESCAPE)
949		return B_TRANSLATE("ESC");
950	if (bytes[0] == B_INSERT)
951		return B_TRANSLATE("INS");
952	if (bytes[0] == B_DELETE)
953		return B_TRANSLATE("DEL");
954	if (bytes[0] == B_HOME)
955		return B_TRANSLATE("HOME");
956	if (bytes[0] == B_END)
957		return B_TRANSLATE("END");
958	if (bytes[0] == B_PAGE_UP) {
959		return abbreviated
960			? B_TRANSLATE_COMMENT("PG \xe2\x86\x91",
961				"Very short for 'page up'")
962			: B_TRANSLATE("PAGE \xe2\x86\x91");
963	}
964	if (bytes[0] == B_PAGE_DOWN) {
965		return abbreviated
966			? B_TRANSLATE_COMMENT("PG \xe2\x86\x93",
967				"Very short for 'page down'")
968			: B_TRANSLATE("PAGE \xe2\x86\x93");
969	}
970
971	return NULL;
972}
973
974
975bool
976KeyboardLayoutView::_FunctionKeyLabel(uint32 code, char* text, size_t textSize)
977{
978	if (code >= B_F1_KEY && code <= B_F12_KEY) {
979		snprintf(text, textSize, "F%" B_PRId32, code + 1 - B_F1_KEY);
980		return true;
981	}
982
983	return false;
984}
985
986
987void
988KeyboardLayoutView::_GetAbbreviatedKeyLabelIfNeeded(BView* view, BRect rect,
989	const Key* key, char* text, size_t textSize)
990{
991	if (floorf(rect.Width()) > ceilf(view->StringWidth(text)))
992		return;
993
994	// Check if we have a shorter version of this key
995
996	const key_map& map = fKeymap->Map();
997
998	const char* special = _SpecialKeyLabel(map, key->code, true);
999	if (special != NULL) {
1000		strlcpy(text, special, textSize);
1001		return;
1002	}
1003
1004	char* bytes = NULL;
1005	int32 numBytes;
1006	fKeymap->GetChars(key->code, fModifiers, fDeadKey, &bytes, &numBytes);
1007	if (bytes != NULL) {
1008		special = _SpecialMappedKeyLabel(bytes, numBytes, true);
1009		if (special != NULL)
1010			strlcpy(text, special, textSize);
1011
1012		delete[] bytes;
1013	}
1014}
1015
1016
1017void
1018KeyboardLayoutView::_GetKeyLabel(const Key* key, char* text, size_t textSize,
1019	key_kind& keyKind)
1020{
1021	const key_map& map = fKeymap->Map();
1022	keyKind = kNormalKey;
1023	text[0] = '\0';
1024
1025	const char* special = _SpecialKeyLabel(map, key->code);
1026	if (special != NULL) {
1027		strlcpy(text, special, textSize);
1028		keyKind = kSpecialKey;
1029		return;
1030	}
1031
1032	if (_FunctionKeyLabel(key->code, text, textSize)) {
1033		keyKind = kSpecialKey;
1034		return;
1035	}
1036
1037	char* bytes = NULL;
1038	int32 numBytes;
1039	fKeymap->GetChars(key->code, fModifiers, fDeadKey, &bytes, &numBytes);
1040	if (bytes != NULL) {
1041		special = _SpecialMappedKeyLabel(bytes, numBytes);
1042		if (special != NULL) {
1043			strlcpy(text, special, textSize);
1044			keyKind = kSpecialKey;
1045		} else {
1046			special = _SpecialMappedKeySymbol(bytes, numBytes);
1047			if (special != NULL) {
1048				strlcpy(text, special, textSize);
1049				keyKind = kSymbolKey;
1050			} else {
1051				bool hasGlyphs;
1052				fBaseFont.GetHasGlyphs(bytes, 1, &hasGlyphs);
1053				if (hasGlyphs)
1054					strlcpy(text, bytes, textSize);
1055			}
1056		}
1057
1058		delete[] bytes;
1059	}
1060}
1061
1062
1063bool
1064KeyboardLayoutView::_IsKeyPressed(uint32 code)
1065{
1066	if (fDropTarget != NULL && fDropTarget->code == code)
1067		return true;
1068
1069	return _KeyState(code);
1070}
1071
1072
1073bool
1074KeyboardLayoutView::_KeyState(uint32 code) const
1075{
1076	if (code >= 16 * 8)
1077		return false;
1078
1079	return (fKeyState[code / 8] & (1 << (7 - (code & 7)))) != 0;
1080}
1081
1082
1083void
1084KeyboardLayoutView::_SetKeyState(uint32 code, bool pressed)
1085{
1086	if (code >= 16 * 8)
1087		return;
1088
1089	if (pressed)
1090		fKeyState[code / 8] |= (1 << (7 - (code & 7)));
1091	else
1092		fKeyState[code / 8] &= ~(1 << (7 - (code & 7)));
1093}
1094
1095
1096Key*
1097KeyboardLayoutView::_KeyForCode(uint32 code)
1098{
1099	// TODO: have a lookup array
1100
1101	for (int32 i = 0; i < fLayout->CountKeys(); i++) {
1102		Key* key = fLayout->KeyAt(i);
1103		if (key->code == code)
1104			return key;
1105	}
1106
1107	return NULL;
1108}
1109
1110
1111void
1112KeyboardLayoutView::_InvalidateKey(uint32 code)
1113{
1114	_InvalidateKey(_KeyForCode(code));
1115}
1116
1117
1118void
1119KeyboardLayoutView::_InvalidateKey(const Key* key)
1120{
1121	if (key != NULL)
1122		Invalidate(_FrameFor(key));
1123}
1124
1125
1126/*!	Updates the fDeadKey member, and invalidates the view if needed.
1127
1128	\return true if the view has been invalidated.
1129*/
1130bool
1131KeyboardLayoutView::_HandleDeadKey(uint32 key, int32 modifiers)
1132{
1133	if (fKeymap == NULL || fKeymap->IsModifierKey(key))
1134		return false;
1135
1136	bool isEnabled = false;
1137	int32 deadKey = fKeymap->DeadKey(key, modifiers, &isEnabled);
1138	if (fDeadKey != deadKey && isEnabled) {
1139		fDeadKey = deadKey;
1140		Invalidate();
1141		return true;
1142	} else if (fDeadKey != 0) {
1143		fDeadKey = 0;
1144		Invalidate();
1145		return true;
1146	}
1147
1148	return false;
1149}
1150
1151
1152void
1153KeyboardLayoutView::_KeyChanged(const BMessage* message)
1154{
1155	const uint8* state;
1156	ssize_t size;
1157	int32 key;
1158	if (message->FindInt32("key", &key) != B_OK
1159		|| message->FindData("states", B_UINT8_TYPE,
1160			(const void**)&state, &size) != B_OK) {
1161		return;
1162	}
1163
1164	// Update key state, and invalidate change keys
1165
1166	bool checkSingle = true;
1167
1168	if (message->what == B_KEY_UP || message->what == B_UNMAPPED_KEY_UP) {
1169		if (_HandleDeadKey(key, fModifiers))
1170			checkSingle = false;
1171
1172		if (_KeyForCode(key) == NULL)
1173			printf("no key for code %" B_PRId32 "\n", key);
1174	}
1175
1176	for (int32 i = 0; i < 16; i++) {
1177		if (fKeyState[i] != state[i]) {
1178			uint8 diff = fKeyState[i] ^ state[i];
1179			fKeyState[i] = state[i];
1180
1181			if (!checkSingle || !Window()->IsActive())
1182				continue;
1183
1184			for (int32 j = 7; diff != 0; j--, diff >>= 1) {
1185				if (diff & 1) {
1186					_InvalidateKey(i * 8 + j);
1187				}
1188			}
1189		}
1190	}
1191}
1192
1193
1194Key*
1195KeyboardLayoutView::_KeyAt(BPoint point)
1196{
1197	// Find key candidate
1198
1199	BPoint keyPoint = point;
1200	keyPoint -= fOffset;
1201	keyPoint.x /= fFactor;
1202	keyPoint.y /= fFactor;
1203
1204	for (int32 i = fLayout->CountKeys() - 1; i >= 0; i--) {
1205		Key* key = fLayout->KeyAt(i);
1206		if (key->frame.Contains(keyPoint)) {
1207			BRect frame = _FrameFor(key);
1208			if (frame.Contains(point))
1209				return key;
1210
1211			return NULL;
1212		}
1213	}
1214
1215	return NULL;
1216}
1217
1218
1219BRect
1220KeyboardLayoutView::_FrameFor(BRect keyFrame)
1221{
1222	BRect rect;
1223	rect.left	= ceilf(keyFrame.left * fFactor);
1224	rect.top	= ceilf(keyFrame.top * fFactor);
1225	rect.right	= floorf((keyFrame.Width()) * fFactor + rect.left - fGap - 1);
1226	rect.bottom	= floorf((keyFrame.Height()) * fFactor + rect.top - fGap - 1);
1227	rect.OffsetBy(fOffset);
1228
1229	return rect;
1230}
1231
1232
1233BRect
1234KeyboardLayoutView::_FrameFor(const Key* key)
1235{
1236	return _FrameFor(key->frame);
1237}
1238
1239
1240void
1241KeyboardLayoutView::_SetFontSize(BView* view, key_kind keyKind)
1242{
1243	BSize size = fLayout->DefaultKeySize();
1244	float fontSize = fBaseFontSize;
1245	if (fBaseFontHeight >= size.height * fFactor * 0.5) {
1246		fontSize *= (size.height * fFactor * 0.5) / fBaseFontHeight;
1247		if (fontSize < 8)
1248			fontSize = 8;
1249	}
1250
1251	switch (keyKind) {
1252		case kNormalKey:
1253			fBaseFont.SetSize(fontSize);
1254			view->SetFont(&fBaseFont);
1255			break;
1256		case kSpecialKey:
1257			fSpecialFont.SetSize(fontSize * 0.7);
1258			view->SetFont(&fSpecialFont);
1259			break;
1260		case kSymbolKey:
1261			fSpecialFont.SetSize(fontSize * 1.6);
1262			view->SetFont(&fSpecialFont);
1263			break;
1264
1265		case kIndicator:
1266		{
1267			BFont font;
1268			font.SetSize(fontSize * 0.8);
1269			view->SetFont(&font);
1270			break;
1271		}
1272	}
1273}
1274
1275
1276void
1277KeyboardLayoutView::_EvaluateDropTarget(BPoint point)
1278{
1279	fDropTarget = _KeyAt(point);
1280	if (fDropTarget != NULL) {
1281		if (fDropTarget == fDragKey && fModifiers == fDragModifiers)
1282			fDropTarget = NULL;
1283		else
1284			_InvalidateKey(fDropTarget);
1285	}
1286}
1287
1288
1289void
1290KeyboardLayoutView::_SendKeyDown(const Key* key)
1291{
1292	BMessage message(B_KEY_DOWN);
1293	message.AddInt64("when", system_time());
1294	message.AddData("states", B_UINT8_TYPE, &fKeyState,
1295		sizeof(fKeyState));
1296	message.AddInt32("key", key->code);
1297	message.AddInt32("modifiers", fModifiers);
1298	message.AddInt32("be:key_repeat", 1);
1299
1300	if (fDevice == NULL)
1301		message.AddPointer("keymap", fKeymap);
1302
1303	char* string;
1304	int32 numBytes;
1305	fKeymap->GetChars(key->code, fModifiers, fDeadKey, &string,
1306		&numBytes);
1307	if (string != NULL) {
1308		message.AddString("bytes", string);
1309		delete[] string;
1310	}
1311
1312	fKeymap->GetChars(key->code, 0, 0, &string, &numBytes);
1313	if (string != NULL) {
1314		message.AddInt32("raw_char", string[0]);
1315		message.AddInt8("byte", string[0]);
1316		delete[] string;
1317	}
1318
1319	if (fDevice == NULL) {
1320		fTarget.SendMessage(&message);
1321	} else {
1322#if defined(VIRTUAL_KEYBOARD_DEVICE)
1323		BMessage* deviceMessage = new BMessage(message);
1324		if (fDevice->EnqueueMessage(deviceMessage) != B_OK)
1325			delete deviceMessage;
1326#endif
1327	}
1328}
1329
1330
1331BMenuItem*
1332KeyboardLayoutView::_CreateSwapModifiersMenuItem(uint32 modifier,
1333	uint32 displayModifier, uint32 oldCode, uint32 newCode)
1334{
1335	int32 mask = B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY;
1336	const char* oldName = _NameForModifier(oldCode == 0x00 ? modifier
1337		: fKeymap->Modifier(oldCode) & ~mask, false);
1338	const char* newName = _NameForModifier(newCode == 0x00 ? modifier
1339		: fKeymap->Modifier(newCode) & ~mask, false);
1340
1341	BMessage* message = new BMessage(kMsgUpdateModifierKeys);
1342	if (newName != NULL)
1343		message->AddUInt32(newName, oldCode);
1344
1345	if (oldName != NULL)
1346		message->AddUInt32(oldName, newCode);
1347
1348	if (oldCode == newCode)
1349		message->AddBool("unset", true);
1350
1351	return new BMenuItem(_NameForModifier(displayModifier, true), message);
1352}
1353
1354
1355const char*
1356KeyboardLayoutView::_NameForModifier(uint32 modifier, bool pretty)
1357{
1358	if (modifier == B_CAPS_LOCK)
1359		return pretty ? B_TRANSLATE("Caps Lock") : "caps_key";
1360	else if (modifier == B_NUM_LOCK)
1361		return pretty ? B_TRANSLATE("Num Lock") : "num_key";
1362	else if (modifier == B_SCROLL_LOCK)
1363		return pretty ? B_TRANSLATE("Scroll Lock") : "scroll_key";
1364	else if (modifier == B_SHIFT_KEY) {
1365		return pretty ? B_TRANSLATE_COMMENT("Shift", "Shift key")
1366			: "shift_key";
1367	} else if (modifier == B_LEFT_SHIFT_KEY)
1368		return pretty ? B_TRANSLATE("Left Shift") : "left_shift_key";
1369	else if (modifier == B_RIGHT_SHIFT_KEY)
1370		return pretty ? B_TRANSLATE("Right Shift") : "right_shift_key";
1371	else if (modifier == B_COMMAND_KEY) {
1372		return pretty ? B_TRANSLATE_COMMENT("Command", "Command key")
1373			: "command_key";
1374	} else if (modifier == B_LEFT_COMMAND_KEY)
1375		return pretty ? B_TRANSLATE("Left Command") : "left_command_key";
1376	else if (modifier == B_RIGHT_COMMAND_KEY)
1377		return pretty ? B_TRANSLATE("Right Command") : "right_command_key";
1378	else if (modifier == B_CONTROL_KEY) {
1379		return pretty ? B_TRANSLATE_COMMENT("Control", "Control key")
1380			: "control_key";
1381	} else if (modifier == B_LEFT_CONTROL_KEY)
1382		return pretty ? B_TRANSLATE("Left Control") : "left_control_key";
1383	else if (modifier == B_RIGHT_CONTROL_KEY)
1384		return pretty ? B_TRANSLATE("Right Control") : "right_control_key";
1385	else if (modifier == B_OPTION_KEY) {
1386		return pretty ? B_TRANSLATE_COMMENT("Option", "Option key")
1387			: "option_key";
1388	} else if (modifier == B_LEFT_OPTION_KEY)
1389		return pretty ? B_TRANSLATE("Left Option") : "left_option_key";
1390	else if (modifier == B_RIGHT_OPTION_KEY)
1391		return pretty ? B_TRANSLATE("Right Option") : "right_option_key";
1392	else if (modifier == B_MENU_KEY)
1393		return pretty ? B_TRANSLATE_COMMENT("Menu", "Menu key") : "menu_key";
1394
1395	return NULL;
1396}
1397