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