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