xref: /haiku/src/preferences/keymap/KeyboardLayoutView.cpp (revision a1163de83ea633463a79de234b8742ee106531b2)
1 /*
2  * Copyright 2009, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "KeyboardLayoutView.h"
8 
9 #include <stdio.h>
10 #include <stdlib.h>
11 
12 #include <Beep.h>
13 #include <Bitmap.h>
14 #include <ControlLook.h>
15 #include <LayoutUtils.h>
16 #include <Region.h>
17 #include <Window.h>
18 
19 #include "Keymap.h"
20 
21 
22 static const rgb_color kBrightColor = {230, 230, 230, 255};
23 static const rgb_color kDarkColor = {200, 200, 200, 255};
24 static const rgb_color kSecondDeadKeyColor = {240, 240, 150, 255};
25 static const rgb_color kDeadKeyColor = {152, 203, 255, 255};
26 static const rgb_color kLitIndicatorColor = {116, 212, 83, 255};
27 
28 
29 KeyboardLayoutView::KeyboardLayoutView(const char* name)
30 	: BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS),
31 	fOffscreenBitmap(NULL),
32 	fKeymap(NULL),
33 	fEditable(true),
34 	fModifiers(0),
35 	fDeadKey(0),
36 	fButtons(0),
37 	fDragKey(NULL),
38 	fDropTarget(NULL),
39 	fOldSize(0, 0)
40 {
41 	fLayout = new KeyboardLayout;
42 	memset(fKeyState, 0, sizeof(fKeyState));
43 
44 	SetEventMask(B_KEYBOARD_EVENTS);
45 }
46 
47 
48 KeyboardLayoutView::~KeyboardLayoutView()
49 {
50 	delete fOffscreenBitmap;
51 }
52 
53 
54 void
55 KeyboardLayoutView::SetKeyboardLayout(KeyboardLayout* layout)
56 {
57 	fLayout = layout;
58 	_LayoutKeyboard();
59 	Invalidate();
60 }
61 
62 
63 void
64 KeyboardLayoutView::SetKeymap(Keymap* keymap)
65 {
66 	fKeymap = keymap;
67 	Invalidate();
68 }
69 
70 
71 void
72 KeyboardLayoutView::SetTarget(BMessenger target)
73 {
74 	fTarget = target;
75 }
76 
77 
78 void
79 KeyboardLayoutView::SetFont(const BFont& font)
80 {
81 	fFont = font;
82 
83 	font_height fontHeight;
84 	fFont.GetHeight(&fontHeight);
85 	fBaseFontHeight = fontHeight.ascent + fontHeight.descent;
86 	fBaseFontSize = fFont.Size();
87 
88 	Invalidate();
89 }
90 
91 
92 void
93 KeyboardLayoutView::AttachedToWindow()
94 {
95 	SetViewColor(B_TRANSPARENT_COLOR);
96 
97 	SetFont(*be_plain_font);
98 	fSpecialFont = *be_fixed_font;
99 	fModifiers = modifiers();
100 }
101 
102 
103 void
104 KeyboardLayoutView::FrameResized(float width, float height)
105 {
106 	_InitOffscreen();
107 	_LayoutKeyboard();
108 }
109 
110 
111 void
112 KeyboardLayoutView::WindowActivated(bool active)
113 {
114 	if (active)
115 		Invalidate();
116 }
117 
118 
119 BSize
120 KeyboardLayoutView::MinSize()
121 {
122 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(100, 50));
123 }
124 
125 
126 void
127 KeyboardLayoutView::KeyDown(const char* bytes, int32 numBytes)
128 {
129 	_KeyChanged(Window()->CurrentMessage());
130 }
131 
132 
133 void
134 KeyboardLayoutView::KeyUp(const char* bytes, int32 numBytes)
135 {
136 	_KeyChanged(Window()->CurrentMessage());
137 }
138 
139 
140 void
141 KeyboardLayoutView::MouseDown(BPoint point)
142 {
143 	fClickPoint = point;
144 	fDragKey = NULL;
145 	fDropPoint.x = -1;
146 
147 	Key* key = _KeyAt(point);
148 	if (key == NULL)
149 		return;
150 
151 	int32 buttons = 0;
152 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
153 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
154 
155 	if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0
156 		&& (fButtons & B_TERTIARY_MOUSE_BUTTON) == 0) {
157 		// toggle the "deadness" of dead keys via middle mouse button
158 		if (fKeymap != NULL) {
159 			bool isEnabled = false;
160 			uint8 deadKey
161 				= fKeymap->IsDeadKey(key->code, fModifiers, &isEnabled);
162 			if (deadKey > 0) {
163 				fKeymap->SetDeadKeyEnabled(key->code, fModifiers, !isEnabled);
164 				_InvalidateKey(key);
165 			}
166 		}
167 	} else {
168 		if (fKeymap != NULL && fKeymap->IsModifierKey(key->code)) {
169 			if (_KeyState(key->code)) {
170 				uint32 modifier = fKeymap->Modifier(key->code);
171 				if ((modifier & modifiers()) == 0) {
172 					_SetKeyState(key->code, false);
173 					fModifiers &= ~modifier;
174 					Invalidate();
175 				}
176 			} else {
177 				_SetKeyState(key->code, true);
178 				fModifiers |= fKeymap->Modifier(key->code);
179 				Invalidate();
180 			}
181 
182 			// TODO: if possible, we could handle the lock keys for real
183 		} else {
184 			_SetKeyState(key->code, true);
185 			_InvalidateKey(key);
186 		}
187 	}
188 
189 	fButtons = buttons;
190 }
191 
192 
193 void
194 KeyboardLayoutView::MouseUp(BPoint point)
195 {
196 	Key* key = _KeyAt(fClickPoint);
197 
198 	int32 buttons = 0;
199 	if (Looper() != NULL && Looper()->CurrentMessage() != NULL)
200 		Looper()->CurrentMessage()->FindInt32("buttons", &buttons);
201 
202 	if (key != NULL) {
203 		if ((fButtons & B_TERTIARY_MOUSE_BUTTON) != 0
204 			&& (buttons & B_TERTIARY_MOUSE_BUTTON) == 0) {
205 			_SetKeyState(key->code, false);
206 			_InvalidateKey(key);
207 			fButtons = buttons;
208 		} else {
209 			fButtons = buttons;
210 
211 			// modifier keys are sticky when used with the mouse
212 			if (fKeymap != NULL && fKeymap->IsModifierKey(key->code))
213 				return;
214 
215 			_SetKeyState(key->code, false);
216 
217 			if (_HandleDeadKey(key->code, fModifiers) && fDeadKey != 0)
218 				return;
219 
220 			_InvalidateKey(key);
221 
222 			if (fDragKey == NULL && fKeymap != NULL) {
223 				// Send fake key down message to target
224 				_SendFakeKeyDown(key);
225 			}
226 		}
227 	}
228 	fDragKey = NULL;
229 }
230 
231 
232 void
233 KeyboardLayoutView::MouseMoved(BPoint point, uint32 transit,
234 	const BMessage* dragMessage)
235 {
236 	if (fKeymap == NULL)
237 		return;
238 
239 	// rule out dragging for tertiary mouse button
240 	if ((fButtons & B_TERTIARY_MOUSE_BUTTON) != 0)
241 		return;
242 
243 	if (dragMessage != NULL) {
244 		if (fEditable) {
245 			_InvalidateKey(fDropTarget);
246 			fDropPoint = point;
247 
248 			_EvaluateDropTarget(point);
249 		}
250 
251 		return;
252 	}
253 
254 	int32 buttons;
255 	if (Window()->CurrentMessage() == NULL
256 		|| Window()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK
257 		|| buttons == 0)
258 		return;
259 
260 	if (fDragKey == NULL && (fabs(point.x - fClickPoint.x) > 4
261 		|| fabs(point.y - fClickPoint.y) > 4)) {
262 		// start dragging
263 		Key* key = _KeyAt(fClickPoint);
264 		if (key == NULL)
265 			return;
266 
267 		BRect frame = _FrameFor(key);
268 		BPoint offset = fClickPoint - frame.LeftTop();
269 		frame.OffsetTo(B_ORIGIN);
270 
271 		BRect rect = frame;
272 		rect.right--;
273 		rect.bottom--;
274 		BBitmap* bitmap = new BBitmap(rect, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
275 		bitmap->Lock();
276 
277 		BView* view = new BView(rect, "drag", 0, 0);
278 		bitmap->AddChild(view);
279 
280 		_DrawKey(view, frame, key, frame, false);
281 
282 		view->Sync();
283 		bitmap->RemoveChild(view);
284 		bitmap->Unlock();
285 
286 		// Make it transparent
287 		// TODO: is there a better way to do this?
288 		uint8* bits = (uint8*)bitmap->Bits();
289 		for (int32 i = 0; i < bitmap->BitsLength(); i += 4) {
290 			bits[i + 3] = 144;
291 		}
292 
293 		BMessage drag(B_MIME_DATA);
294 		drag.AddInt32("key", key->code);
295 
296 		char* string;
297 		int32 numBytes;
298 		fKeymap->GetChars(key->code, fModifiers, fDeadKey, &string,
299 			&numBytes);
300 		if (string != NULL) {
301 			drag.AddData("text/plain", B_MIME_DATA, string, numBytes);
302 			delete[] string;
303 		}
304 
305 		DragMessage(&drag, bitmap, B_OP_ALPHA, offset);
306 		fDragKey = key;
307 		fDragModifiers = fModifiers;
308 
309 		fKeyState[key->code / 8] &= ~(1 << (7 - (key->code & 7)));
310 		_InvalidateKey(key);
311 	}
312 }
313 
314 
315 void
316 KeyboardLayoutView::Draw(BRect updateRect)
317 {
318 	if (fOldSize != BSize(Bounds().Width(), Bounds().Height())) {
319 		_InitOffscreen();
320 		_LayoutKeyboard();
321 	}
322 
323 	BView* view;
324 	if (fOffscreenBitmap != NULL) {
325 		view = fOffscreenView;
326 		view->LockLooper();
327 	} else
328 		view = this;
329 
330 	// Draw background
331 
332 	if (Parent())
333 		view->SetLowColor(Parent()->ViewColor());
334 	else
335 		view->SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
336 
337 	view->FillRect(updateRect, B_SOLID_LOW);
338 
339 	// Draw keys
340 
341 	for (int32 i = 0; i < fLayout->CountKeys(); i++) {
342 		Key* key = fLayout->KeyAt(i);
343 
344 		_DrawKey(view, updateRect, key, _FrameFor(key),
345 			_IsKeyPressed(key->code));
346 	}
347 
348 	// Draw LED indicators
349 
350 	for (int32 i = 0; i < fLayout->CountIndicators(); i++) {
351 		Indicator* indicator = fLayout->IndicatorAt(i);
352 
353 		_DrawIndicator(view, updateRect, indicator, _FrameFor(indicator->frame),
354 			(fModifiers & indicator->modifier) != 0);
355 	}
356 
357 	if (fOffscreenBitmap != NULL) {
358 		view->Sync();
359 		view->UnlockLooper();
360 
361 		DrawBitmapAsync(fOffscreenBitmap, BPoint(0, 0));
362 	}
363 }
364 
365 
366 void
367 KeyboardLayoutView::MessageReceived(BMessage* message)
368 {
369 	if (message->WasDropped() && fEditable && fDropTarget != NULL
370 		&& fKeymap != NULL) {
371 		int32 keyCode;
372 		const char* data;
373 		ssize_t size;
374 		if (message->FindData("text/plain", B_MIME_DATA,
375 				(const void**)&data, &size) == B_OK) {
376 			// Automatically convert UTF-8 escaped strings (for example from
377 			// CharacterMap)
378 			int32 dataSize = 0;
379 			uint8 buffer[16];
380 			if (size > 3 && data[0] == '\\' && data[1] == 'x') {
381 				char tempBuffer[16];
382 				if (size > 15)
383 					size = 15;
384 				memcpy(tempBuffer, data, size);
385 				tempBuffer[size] = '\0';
386 				data = tempBuffer;
387 
388 				while (size > 3 && data[0] == '\\' && data[1] == 'x') {
389 					buffer[dataSize++] = strtoul(&data[2], NULL, 16);
390 					if ((buffer[dataSize - 1] & 0x80) == 0)
391 						break;
392 
393 					size -= 4;
394 					data += 4;
395 				}
396 				data = (const char*)buffer;
397 			} else if ((data[0] & 0xc0) != 0x80 && (data[0] & 0x80) != 0) {
398 				// only accept the first character UTF-8 character
399 				while (dataSize < size && (data[dataSize] & 0x80) != 0) {
400 					dataSize++;
401 				}
402 			} else if ((data[0] & 0x80) == 0) {
403 				// an ASCII character
404 				dataSize = 1;
405 			} else {
406 				// no valid character
407 				beep();
408 				return;
409 			}
410 
411 			int32 buttons;
412 			if (!message->IsSourceRemote()
413 				&& message->FindInt32("buttons", &buttons) == B_OK
414 				&& (buttons & B_SECONDARY_MOUSE_BUTTON) != 0
415 				&& message->FindInt32("key", &keyCode) == B_OK) {
416 				// switch keys if the dropped object came from us
417 				Key* key = _KeyForCode(keyCode);
418 				if (key == NULL
419 					|| (key == fDropTarget && fDragModifiers == fModifiers))
420 					return;
421 
422 				char* string;
423 				int32 numBytes;
424 				fKeymap->GetChars(fDropTarget->code, fModifiers, fDeadKey,
425 					&string, &numBytes);
426 				if (string != NULL) {
427 					// switch keys
428 					fKeymap->SetKey(fDropTarget->code, fModifiers, fDeadKey,
429 						(const char*)data, dataSize);
430 					fKeymap->SetKey(key->code, fDragModifiers, fDeadKey,
431 						string, numBytes);
432 					delete[] string;
433 				} else if (fKeymap->IsModifierKey(fDropTarget->code)) {
434 					// switch key with modifier
435 					fKeymap->SetModifier(key->code,
436 						fKeymap->Modifier(fDropTarget->code));
437 					fKeymap->SetKey(fDropTarget->code, fModifiers, fDeadKey,
438 						(const char*)data, dataSize);
439 				}
440 			} else {
441 				// Send the old key to the target, so it's not lost entirely
442 				_SendFakeKeyDown(fDropTarget);
443 
444 				fKeymap->SetKey(fDropTarget->code, fModifiers, fDeadKey,
445 					(const char*)data, dataSize);
446 			}
447 		} else if (!message->IsSourceRemote()
448 			&& message->FindInt32("key", &keyCode) == B_OK) {
449 			// Switch an unmapped key
450 
451 			Key* key = _KeyForCode(keyCode);
452 			if (key != NULL && key == fDropTarget)
453 				return;
454 
455 			uint32 modifier = fKeymap->Modifier(keyCode);
456 
457 			char* string;
458 			int32 numBytes;
459 			fKeymap->GetChars(fDropTarget->code, fModifiers, fDeadKey,
460 				&string, &numBytes);
461 			if (string != NULL) {
462 				// switch key with modifier
463 				fKeymap->SetModifier(fDropTarget->code, modifier);
464 				fKeymap->SetKey(keyCode, fDragModifiers, fDeadKey,
465 					string, numBytes);
466 				delete[] string;
467 			} else {
468 				// switch modifier keys
469 				fKeymap->SetModifier(keyCode,
470 					fKeymap->Modifier(fDropTarget->code));
471 				fKeymap->SetModifier(fDropTarget->code, modifier);
472 			}
473 		}
474 
475 		_InvalidateKey(fDropTarget);
476 		fDropTarget = NULL;
477 		fDropPoint.x = -1;
478 	}
479 
480 	switch (message->what) {
481 		case B_UNMAPPED_KEY_DOWN:
482 		case B_UNMAPPED_KEY_UP:
483 			_KeyChanged(message);
484 			break;
485 
486 		case B_MODIFIERS_CHANGED:
487 		{
488 			int32 newModifiers;
489 			if (message->FindInt32("modifiers", &newModifiers) == B_OK
490 				&& fModifiers != newModifiers) {
491 				fModifiers = newModifiers;
492 				_EvaluateDropTarget(fDropPoint);
493 				if (Window()->IsActive())
494 					Invalidate();
495 			}
496 			break;
497 		}
498 
499 		default:
500 			BView::MessageReceived(message);
501 			break;
502 	}
503 }
504 
505 
506 void
507 KeyboardLayoutView::_InitOffscreen()
508 {
509 	delete fOffscreenBitmap;
510 	fOffscreenView = NULL;
511 
512 	fOffscreenBitmap = new(std::nothrow) BBitmap(Bounds(),
513 		B_BITMAP_ACCEPTS_VIEWS, B_RGB32);
514 	if (fOffscreenBitmap != NULL && fOffscreenBitmap->IsValid()) {
515 		fOffscreenBitmap->Lock();
516 		fOffscreenView = new(std::nothrow) BView(Bounds(), "offscreen view",
517 			0, 0);
518 		if (fOffscreenView != NULL) {
519 			if (Parent() != NULL) {
520 				fOffscreenView->SetViewColor(Parent()->ViewColor());
521 			} else {
522 				fOffscreenView->SetViewColor(
523 					ui_color(B_PANEL_BACKGROUND_COLOR));
524 			}
525 
526 			fOffscreenView->SetLowColor(fOffscreenView->ViewColor());
527 			fOffscreenBitmap->AddChild(fOffscreenView);
528 		}
529 		fOffscreenBitmap->Unlock();
530 	}
531 
532 	if (fOffscreenView == NULL) {
533 		// something went wrong
534 		delete fOffscreenBitmap;
535 		fOffscreenBitmap = NULL;
536 	}
537 }
538 
539 
540 void
541 KeyboardLayoutView::_LayoutKeyboard()
542 {
543 	float factorX = Bounds().Width() / fLayout->Bounds().Width();
544 	float factorY = Bounds().Height() / fLayout->Bounds().Height();
545 
546 	fFactor = min_c(factorX, factorY);
547 	fOffset = BPoint((Bounds().Width() - fLayout->Bounds().Width()
548 			* fFactor) / 2,
549 		(Bounds().Height() - fLayout->Bounds().Height() * fFactor) / 2);
550 
551 	if (fLayout->DefaultKeySize().width < 11)
552 		fGap = 1;
553 	else
554 		fGap = 2;
555 
556 	fOldSize.width = Bounds().Width();
557 	fOldSize.height = Bounds().Height();
558 }
559 
560 
561 void
562 KeyboardLayoutView::_DrawKeyButton(BView* view, BRect& rect, BRect updateRect,
563 	rgb_color base, rgb_color background, bool pressed)
564 {
565 	be_control_look->DrawButtonFrame(view, rect, updateRect, base,
566 		background, pressed ? BControlLook::B_ACTIVATED : 0);
567 	be_control_look->DrawButtonBackground(view, rect, updateRect,
568 		base, pressed ? BControlLook::B_ACTIVATED : 0);
569 }
570 
571 
572 void
573 KeyboardLayoutView::_DrawKey(BView* view, BRect updateRect, const Key* key,
574 	BRect rect, bool pressed)
575 {
576 	rgb_color base = key->dark ? kDarkColor : kBrightColor;
577 	rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR);
578 	key_kind keyKind = kNormalKey;
579 	int32 deadKey = 0;
580 	bool secondDeadKey = false;
581 	bool isDeadKeyEnabled = true;
582 
583 	char text[32];
584 	if (fKeymap != NULL) {
585 		_GetKeyLabel(key, text, sizeof(text), keyKind);
586 		deadKey = fKeymap->IsDeadKey(key->code, fModifiers, &isDeadKeyEnabled);
587 		secondDeadKey = fKeymap->IsDeadSecondKey(key->code, fModifiers,
588 			fDeadKey);
589 	} else {
590 		// Show the key code if there is no keymap
591 		snprintf(text, sizeof(text), "%02lx", key->code);
592 	}
593 
594 	_SetFontSize(view, keyKind);
595 
596 	if (secondDeadKey)
597 		base = kSecondDeadKeyColor;
598 	else if (deadKey > 0 && isDeadKeyEnabled)
599 		base = kDeadKeyColor;
600 
601 	if (key->shape == kRectangleKeyShape) {
602 		_DrawKeyButton(view, rect, updateRect, base, background, pressed);
603 
604 		rect.InsetBy(1, 1);
605 
606 		be_control_look->DrawLabel(view, text, rect, updateRect,
607 			base, 0, BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE));
608 	} else if (key->shape == kEnterKeyShape) {
609 		BRegion region(rect);
610 		BRect originalRect = rect;
611 		BRect missingRect = rect;
612 
613 		// TODO: for some reason, this does not always equal the bottom of
614 		// the other keys...
615 		missingRect.top = floorf(rect.top
616 			+ fLayout->DefaultKeySize().height * fFactor - fGap - 1);
617 		missingRect.right = floorf(missingRect.left
618 			+ (key->frame.Width() - key->second_row) * fFactor - fGap - 2);
619 		region.Exclude(missingRect);
620 		view->ConstrainClippingRegion(&region);
621 
622 		_DrawKeyButton(view, rect, updateRect, base, background, pressed);
623 
624 		rect.left = missingRect.right;
625 		be_control_look->DrawLabel(view, text, rect, updateRect,
626 			base, 0, BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE));
627 
628 		missingRect.right--;
629 		missingRect.top -= 2;
630 		region.Set(missingRect);
631 		view->ConstrainClippingRegion(&region);
632 
633 		rect = originalRect;
634 		rect.bottom = missingRect.top + 2;
635 		_DrawKeyButton(view, rect, updateRect, base, background, pressed);
636 
637 		missingRect.left = missingRect.right;
638 		missingRect.right++;
639 		missingRect.top += 2;
640 		region.Set(missingRect);
641 		view->ConstrainClippingRegion(&region);
642 
643 		rect = originalRect;
644 		rect.left = missingRect.right - 2;
645 		rect.top = missingRect.top - 2;
646 		_DrawKeyButton(view, rect, updateRect, base, background, pressed);
647 
648 		view->ConstrainClippingRegion(NULL);
649 	}
650 }
651 
652 
653 void
654 KeyboardLayoutView::_DrawIndicator(BView* view, BRect updateRect,
655 	const Indicator* indicator, BRect rect, bool lit)
656 {
657 	float rectTop = rect.top;
658 	rect.top += 2 * rect.Height() / 3;
659 
660 	const char* label = NULL;
661 	if (indicator->modifier == B_CAPS_LOCK)
662 		label = "caps";
663 	else if (indicator->modifier == B_NUM_LOCK)
664 		label = "num";
665 	else if (indicator->modifier == B_SCROLL_LOCK)
666 		label = "scroll";
667 	if (label != NULL) {
668 		_SetFontSize(view, kIndicator);
669 
670 		font_height fontHeight;
671 		GetFontHeight(&fontHeight);
672 		if (ceilf(rect.top - fontHeight.ascent + fontHeight.descent - 2)
673 				>= rectTop) {
674 			view->SetHighColor(0, 0, 0);
675 			view->SetLowColor(ViewColor());
676 
677 			BString text(label);
678 			view->TruncateString(&text, B_TRUNCATE_END, rect.Width());
679 			view->DrawString(text.String(),
680 				BPoint(ceilf(rect.left + (rect.Width()
681 						- StringWidth(text.String())) / 2),
682 					ceilf(rect.top - fontHeight.descent - 2)));
683 		}
684 	}
685 
686 	rect.left += rect.Width() / 4;
687 	rect.right -= rect.Width() / 3;
688 
689 	rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR);
690 	rgb_color base = lit ? kLitIndicatorColor : kDarkColor;
691 
692 	be_control_look->DrawButtonFrame(view, rect, updateRect, base,
693 		background, BControlLook::B_DISABLED);
694 	be_control_look->DrawButtonBackground(view, rect, updateRect,
695 		base, BControlLook::B_DISABLED);
696 }
697 
698 
699 const char*
700 KeyboardLayoutView::_SpecialKeyLabel(const key_map& map, uint32 code)
701 {
702 	if (code == map.caps_key)
703 		return "CAPS LOCK";
704 	if (code == map.scroll_key)
705 		return "SCROLL";
706 	if (code == map.num_key)
707 		return "NUM LOCK";
708 	if (code == map.left_shift_key || code == map.right_shift_key)
709 		return "SHIFT";
710 	if (code == map.left_command_key || code == map.right_command_key)
711 		return "COMMAND";
712 	if (code == map.left_control_key || code == map.right_control_key)
713 		return "CONTROL";
714 	if (code == map.left_option_key || code == map.right_option_key)
715 		return "OPTION";
716 	if (code == map.menu_key)
717 		return "MENU";
718 	if (code == B_PRINT_KEY)
719 		return "PRINT";
720 	if (code == B_PAUSE_KEY)
721 		return "PAUSE";
722 
723 	return NULL;
724 }
725 
726 
727 const char*
728 KeyboardLayoutView::_SpecialMappedKeySymbol(const char* bytes, size_t numBytes)
729 {
730 	if (numBytes != 1)
731 		return NULL;
732 
733 	if (bytes[0] == B_TAB)
734 		return "\xe2\x86\xb9";
735 	if (bytes[0] == B_ENTER)
736 		return "\xe2\x86\xb5";
737 	if (bytes[0] == B_BACKSPACE)
738 		return "\xe2\x8c\xab";
739 
740 	if (bytes[0] == B_UP_ARROW)
741 		return "\xe2\x86\x91";
742 	if (bytes[0] == B_LEFT_ARROW)
743 		return "\xe2\x86\x90";
744 	if (bytes[0] == B_DOWN_ARROW)
745 		return "\xe2\x86\x93";
746 	if (bytes[0] == B_RIGHT_ARROW)
747 		return "\xe2\x86\x92";
748 
749 	return NULL;
750 }
751 
752 
753 const char*
754 KeyboardLayoutView::_SpecialMappedKeyLabel(const char* bytes, size_t numBytes)
755 {
756 	if (numBytes != 1)
757 		return NULL;
758 
759 	if (bytes[0] == B_ESCAPE)
760 		return "ESC";
761 
762 	if (bytes[0] == B_INSERT)
763 		return "INS";
764 	if (bytes[0] == B_DELETE)
765 		return "DEL";
766 	if (bytes[0] == B_HOME)
767 		return "HOME";
768 	if (bytes[0] == B_END)
769 		return "END";
770 	if (bytes[0] == B_PAGE_UP)
771 		return "PAGE \xe2\x86\x91";
772 	if (bytes[0] == B_PAGE_DOWN)
773 		return "PAGE \xe2\x86\x93";
774 
775 	return NULL;
776 }
777 
778 
779 bool
780 KeyboardLayoutView::_FunctionKeyLabel(uint32 code, char* text, size_t textSize)
781 {
782 	if (code >= B_F1_KEY && code <= B_F12_KEY) {
783 		snprintf(text, textSize, "F%ld", code + 1 - B_F1_KEY);
784 		return true;
785 	}
786 
787 	return false;
788 }
789 
790 
791 void
792 KeyboardLayoutView::_GetKeyLabel(const Key* key, char* text, size_t textSize,
793 	key_kind& keyKind)
794 {
795 	const key_map& map = fKeymap->Map();
796 	keyKind = kNormalKey;
797 	text[0] = '\0';
798 
799 	const char* special = _SpecialKeyLabel(map, key->code);
800 	if (special != NULL) {
801 		strlcpy(text, special, textSize);
802 		keyKind = kSpecialKey;
803 		return;
804 	}
805 
806 	if (_FunctionKeyLabel(key->code, text, textSize)) {
807 		keyKind = kSpecialKey;
808 		return;
809 	}
810 
811 	char* bytes = NULL;
812 	int32 numBytes;
813 	fKeymap->GetChars(key->code, fModifiers, fDeadKey, &bytes,
814 		&numBytes);
815 	if (bytes != NULL) {
816 		special = _SpecialMappedKeyLabel(bytes, numBytes);
817 		if (special != NULL) {
818 			strlcpy(text, special, textSize);
819 			keyKind = kSpecialKey;
820 			return;
821 		}
822 
823 		special = _SpecialMappedKeySymbol(bytes, numBytes);
824 		if (special != NULL) {
825 			strlcpy(text, special, textSize);
826 			keyKind = kSymbolKey;
827 			return;
828 		}
829 
830 		bool hasGlyphs;
831 		fFont.GetHasGlyphs(bytes, 1, &hasGlyphs);
832 		if (hasGlyphs)
833 			strlcpy(text, bytes, sizeof(text));
834 
835 		delete[] bytes;
836 	}
837 }
838 
839 
840 bool
841 KeyboardLayoutView::_IsKeyPressed(uint32 code)
842 {
843 	if (fDropTarget != NULL && fDropTarget->code == (int32)code)
844 		return true;
845 
846 	return _KeyState(code);
847 }
848 
849 
850 bool
851 KeyboardLayoutView::_KeyState(uint32 code) const
852 {
853 	if (code >= 16 * 8)
854 		return false;
855 
856 	return (fKeyState[code / 8] & (1 << (7 - (code & 7)))) != 0;
857 }
858 
859 
860 void
861 KeyboardLayoutView::_SetKeyState(uint32 code, bool pressed)
862 {
863 	if (code >= 16 * 8)
864 		return;
865 
866 	if (pressed)
867 		fKeyState[code / 8] |= (1 << (7 - (code & 7)));
868 	else
869 		fKeyState[code / 8] &= ~(1 << (7 - (code & 7)));
870 }
871 
872 
873 Key*
874 KeyboardLayoutView::_KeyForCode(uint32 code)
875 {
876 	// TODO: have a lookup array
877 
878 	for (int32 i = 0; i < fLayout->CountKeys(); i++) {
879 		Key* key = fLayout->KeyAt(i);
880 		if (key->code == (int32)code)
881 			return key;
882 	}
883 
884 	return NULL;
885 }
886 
887 
888 void
889 KeyboardLayoutView::_InvalidateKey(uint32 code)
890 {
891 	_InvalidateKey(_KeyForCode(code));
892 }
893 
894 
895 void
896 KeyboardLayoutView::_InvalidateKey(const Key* key)
897 {
898 	if (key != NULL)
899 		Invalidate(_FrameFor(key));
900 }
901 
902 
903 /*!	Updates the fDeadKey member, and invalidates the view if needed.
904 
905 	\return true if the view has been invalidated.
906 */
907 bool
908 KeyboardLayoutView::_HandleDeadKey(uint32 key, int32 modifiers)
909 {
910 	if (fKeymap == NULL || fKeymap->IsModifierKey(key))
911 		return false;
912 
913 	bool isEnabled = false;
914 	int32 deadKey = fKeymap->IsDeadKey(key, modifiers, &isEnabled);
915 	if (fDeadKey != deadKey) {
916 		if (isEnabled) {
917 			Invalidate();
918 			fDeadKey = deadKey;
919 			return true;
920 		}
921 	} else if (fDeadKey != 0) {
922 		Invalidate();
923 		fDeadKey = 0;
924 		return true;
925 	}
926 
927 	return false;
928 }
929 
930 
931 void
932 KeyboardLayoutView::_KeyChanged(const BMessage* message)
933 {
934 	const uint8* state;
935 	ssize_t size;
936 	int32 key;
937 	if (message->FindData("states", B_UINT8_TYPE, (const void**)&state, &size)
938 			!= B_OK
939 		|| message->FindInt32("key", &key) != B_OK)
940 		return;
941 
942 	// Update key state, and invalidate change keys
943 
944 	bool checkSingle = true;
945 
946 	if (message->what == B_KEY_DOWN || message->what == B_UNMAPPED_KEY_DOWN) {
947 		if (_HandleDeadKey(key, fModifiers))
948 			checkSingle = false;
949 
950 		if (_KeyForCode(key) == NULL)
951 			printf("no key for code %ld\n", key);
952 	}
953 
954 	for (int32 i = 0; i < 16; i++) {
955 		if (fKeyState[i] != state[i]) {
956 			uint8 diff = fKeyState[i] ^ state[i];
957 			fKeyState[i] = state[i];
958 
959 			if (!checkSingle || !Window()->IsActive())
960 				continue;
961 
962 			for (int32 j = 7; diff != 0; j--, diff >>= 1) {
963 				if (diff & 1) {
964 					_InvalidateKey(i * 8 + j);
965 				}
966 			}
967 		}
968 	}
969 }
970 
971 
972 Key*
973 KeyboardLayoutView::_KeyAt(BPoint point)
974 {
975 	// Find key candidate
976 
977 	BPoint keyPoint = point;
978 	keyPoint -= fOffset;
979 	keyPoint.x /= fFactor;
980 	keyPoint.y /= fFactor;
981 
982 	for (int32 i = 0; i < fLayout->CountKeys(); i++) {
983 		Key* key = fLayout->KeyAt(i);
984 		if (key->frame.Contains(keyPoint)) {
985 			BRect frame = _FrameFor(key);
986 			if (frame.Contains(point))
987 				return key;
988 
989 			return NULL;
990 		}
991 	}
992 
993 	return NULL;
994 }
995 
996 
997 BRect
998 KeyboardLayoutView::_FrameFor(BRect keyFrame)
999 {
1000 	BRect rect;
1001 	rect.left = ceilf(keyFrame.left * fFactor);
1002 	rect.right = floorf((keyFrame.Width()) * fFactor + rect.left - fGap - 1);
1003 	rect.top = ceilf(keyFrame.top * fFactor);
1004 	rect.bottom = floorf((keyFrame.Height()) * fFactor + rect.top - fGap - 1);
1005 	rect.OffsetBy(fOffset);
1006 
1007 	return rect;
1008 }
1009 
1010 
1011 BRect
1012 KeyboardLayoutView::_FrameFor(const Key* key)
1013 {
1014 	return _FrameFor(key->frame);
1015 }
1016 
1017 
1018 void
1019 KeyboardLayoutView::_SetFontSize(BView* view, key_kind keyKind)
1020 {
1021 	BSize size = fLayout->DefaultKeySize();
1022 	float fontSize = fBaseFontSize;
1023 	if (fBaseFontHeight >= size.height * fFactor * 0.5) {
1024 		fontSize *= (size.height * fFactor * 0.5) / fBaseFontHeight;
1025 		if (fontSize < 8)
1026 			fontSize = 8;
1027 	}
1028 
1029 	switch (keyKind) {
1030 		case kNormalKey:
1031 			fFont.SetSize(fontSize);
1032 			view->SetFont(&fFont);
1033 			break;
1034 		case kSpecialKey:
1035 			fSpecialFont.SetSize(fontSize * 0.7);
1036 			view->SetFont(&fSpecialFont);
1037 			break;
1038 		case kSymbolKey:
1039 			fSpecialFont.SetSize(fontSize * 1.6);
1040 			view->SetFont(&fSpecialFont);
1041 			break;
1042 
1043 		case kIndicator:
1044 		{
1045 			BFont font;
1046 			font.SetSize(fontSize * 0.8);
1047 			view->SetFont(&font);
1048 			break;
1049 		}
1050 	}
1051 }
1052 
1053 
1054 void
1055 KeyboardLayoutView::_EvaluateDropTarget(BPoint point)
1056 {
1057 	fDropTarget = _KeyAt(point);
1058 	if (fDropTarget != NULL) {
1059 		if (fDropTarget == fDragKey && fModifiers == fDragModifiers)
1060 			fDropTarget = NULL;
1061 		else
1062 			_InvalidateKey(fDropTarget);
1063 	}
1064 }
1065 
1066 
1067 void
1068 KeyboardLayoutView::_SendFakeKeyDown(const Key* key)
1069 {
1070 	BMessage message(B_KEY_DOWN);
1071 	message.AddInt64("when", system_time());
1072 	message.AddData("states", B_UINT8_TYPE, &fKeyState,
1073 		sizeof(fKeyState));
1074 	message.AddInt32("key", key->code);
1075 	message.AddInt32("modifiers", fModifiers);
1076 	message.AddPointer("keymap", fKeymap);
1077 
1078 	char* string;
1079 	int32 numBytes;
1080 	fKeymap->GetChars(key->code, fModifiers, fDeadKey, &string,
1081 		&numBytes);
1082 	if (string != NULL) {
1083 		message.AddString("bytes", string);
1084 		delete[] string;
1085 	}
1086 
1087 	fKeymap->GetChars(key->code, 0, 0, &string, &numBytes);
1088 	if (string != NULL) {
1089 		message.AddInt32("raw_char", string[0]);
1090 		message.AddInt8("byte", string[0]);
1091 		delete[] string;
1092 	}
1093 
1094 	fTarget.SendMessage(&message);
1095 }
1096