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