xref: /haiku/src/apps/charactermap/CharacterView.cpp (revision d4e4909c6a3fe4290b78be2b78035c4774e3ff18)
1 /*
2  * Copyright 2009-2010, Axel Dörfler, axeld@pinc-software.de.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "CharacterView.h"
8 
9 #include <stdio.h>
10 #include <string.h>
11 
12 #include <Bitmap.h>
13 #include <Catalog.h>
14 #include <Clipboard.h>
15 #include <LayoutUtils.h>
16 #include <MenuItem.h>
17 #include <PopUpMenu.h>
18 #include <ScrollBar.h>
19 #include <Window.h>
20 
21 #include "UnicodeBlocks.h"
22 
23 #undef B_TRANSLATION_CONTEXT
24 #define B_TRANSLATION_CONTEXT "CharacterView"
25 
26 static const uint32 kMsgCopyAsEscapedString = 'cesc';
27 
28 
29 CharacterView::CharacterView(const char* name)
30 	: BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS
31 		| B_SCROLL_VIEW_AWARE),
32 	fTargetCommand(0),
33 	fClickPoint(-1, 0),
34 	fHasCharacter(false),
35 	fShowPrivateBlocks(false),
36 	fShowContainedBlocksOnly(false)
37 {
38 	fTitleTops = new int32[kNumUnicodeBlocks];
39 	fCharacterFont.SetSize(fCharacterFont.Size() * 1.5f);
40 
41 	_UpdateFontSize();
42 	DoLayout();
43 }
44 
45 
46 CharacterView::~CharacterView()
47 {
48 	delete[] fTitleTops;
49 }
50 
51 
52 void
53 CharacterView::SetTarget(BMessenger target, uint32 command)
54 {
55 	fTarget = target;
56 	fTargetCommand = command;
57 }
58 
59 
60 void
61 CharacterView::SetCharacterFont(const BFont& font)
62 {
63 	fCharacterFont = font;
64 	fUnicodeBlocks = fCharacterFont.Blocks();
65 	InvalidateLayout();
66 }
67 
68 
69 void
70 CharacterView::ShowPrivateBlocks(bool show)
71 {
72 	if (fShowPrivateBlocks == show)
73 		return;
74 
75 	fShowPrivateBlocks = show;
76 	InvalidateLayout();
77 }
78 
79 
80 void
81 CharacterView::ShowContainedBlocksOnly(bool show)
82 {
83 	if (fShowContainedBlocksOnly == show)
84 		return;
85 
86 	fShowContainedBlocksOnly = show;
87 	InvalidateLayout();
88 }
89 
90 
91 bool
92 CharacterView::IsShowingBlock(int32 blockIndex) const
93 {
94 	if (blockIndex < 0 || blockIndex >= (int32)kNumUnicodeBlocks)
95 		return false;
96 
97 	if (!fShowPrivateBlocks && kUnicodeBlocks[blockIndex].private_block)
98 		return false;
99 
100 	// the reason for two checks is BeOS compatibility.
101 	// The Includes method checks for unicode blocks as
102 	// defined by Be, but there are only 71 such blocks.
103 	// The rest of the blocks (denoted by kNoBlock) need to
104 	// be queried by searching for the start and end codepoints
105 	// via the IncludesBlock method.
106 	if (fShowContainedBlocksOnly) {
107 		if (kUnicodeBlocks[blockIndex].block != kNoBlock
108 			&& !fUnicodeBlocks.Includes(
109 				kUnicodeBlocks[blockIndex].block))
110 			return false;
111 
112 		if (!fCharacterFont.IncludesBlock(
113 				kUnicodeBlocks[blockIndex].start,
114 				kUnicodeBlocks[blockIndex].end))
115 			return false;
116 	}
117 
118 	return true;
119 }
120 
121 
122 void
123 CharacterView::ScrollToBlock(int32 blockIndex)
124 {
125 	// don't scroll if the selected block is already in view.
126 	// this prevents distracting jumps when crossing a block
127 	// boundary in the character view.
128 	if (IsBlockVisible(blockIndex))
129 		return;
130 
131 	if (blockIndex < 0)
132 		blockIndex = 0;
133 	else if (blockIndex >= (int32)kNumUnicodeBlocks)
134 		blockIndex = kNumUnicodeBlocks - 1;
135 
136 	BView::ScrollTo(0.0f, fTitleTops[blockIndex]);
137 }
138 
139 
140 void
141 CharacterView::ScrollToCharacter(uint32 c)
142 {
143 	if (IsCharacterVisible(c))
144 		return;
145 
146 	BRect frame = _FrameFor(c);
147 	BView::ScrollTo(0.0f, frame.top);
148 }
149 
150 
151 bool
152 CharacterView::IsCharacterVisible(uint32 c) const
153 {
154 	return Bounds().Contains(_FrameFor(c));
155 }
156 
157 
158 bool
159 CharacterView::IsBlockVisible(int32 block) const
160 {
161 	int32 topBlock = _BlockAt(BPoint(Bounds().left, Bounds().top));
162 	int32 bottomBlock = _BlockAt(BPoint(Bounds().right, Bounds().bottom));
163 
164 	if (block >= topBlock && block <= bottomBlock)
165 		return true;
166 
167 	return false;
168 }
169 
170 
171 /*static*/ void
172 CharacterView::UnicodeToUTF8(uint32 c, char* text, size_t textSize)
173 {
174 	if (textSize < 5) {
175 		if (textSize > 0)
176 			text[0] = '\0';
177 		return;
178 	}
179 
180 	char* s = text;
181 
182 	if (c < 0x80)
183 		*(s++) = c;
184 	else if (c < 0x800) {
185 		*(s++) = 0xc0 | (c >> 6);
186 		*(s++) = 0x80 | (c & 0x3f);
187 	} else if (c < 0x10000) {
188 		*(s++) = 0xe0 | (c >> 12);
189 		*(s++) = 0x80 | ((c >> 6) & 0x3f);
190 		*(s++) = 0x80 | (c & 0x3f);
191 	} else if (c <= 0x10ffff) {
192 		*(s++) = 0xf0 | (c >> 18);
193 		*(s++) = 0x80 | ((c >> 12) & 0x3f);
194 		*(s++) = 0x80 | ((c >> 6) & 0x3f);
195 		*(s++) = 0x80 | (c & 0x3f);
196 	}
197 
198 	s[0] = '\0';
199 }
200 
201 
202 /*static*/ void
203 CharacterView::UnicodeToUTF8Hex(uint32 c, char* text, size_t textSize)
204 {
205 	char character[16];
206 	CharacterView::UnicodeToUTF8(c, character, sizeof(character));
207 
208 	int size = 0;
209 	for (int32 i = 0; character[i] && size < (int)textSize; i++) {
210 		size += snprintf(text + size, textSize - size, "\\x%02x",
211 			(uint8)character[i]);
212 	}
213 }
214 
215 
216 void
217 CharacterView::MessageReceived(BMessage* message)
218 {
219 	switch (message->what) {
220 		case kMsgCopyAsEscapedString:
221 		case B_COPY:
222 		{
223 			uint32 character;
224 			if (message->FindInt32("character", (int32*)&character) != B_OK) {
225 				if (!fHasCharacter)
226 					break;
227 
228 				character = fCurrentCharacter;
229 			}
230 
231 			char text[16];
232 			if (message->what == kMsgCopyAsEscapedString)
233 				UnicodeToUTF8Hex(character, text, sizeof(text));
234 			else
235 				UnicodeToUTF8(character, text, sizeof(text));
236 
237 			_CopyToClipboard(text);
238 			break;
239 		}
240 
241 		default:
242 			BView::MessageReceived(message);
243 			break;
244 	}
245 }
246 
247 
248 void
249 CharacterView::AttachedToWindow()
250 {
251 	Window()->AddShortcut('C', B_SHIFT_KEY,
252 		new BMessage(kMsgCopyAsEscapedString), this);
253 	SetViewColor(255, 255, 255, 255);
254 	SetLowColor(ViewColor());
255 }
256 
257 
258 void
259 CharacterView::DetachedFromWindow()
260 {
261 }
262 
263 
264 BSize
265 CharacterView::MinSize()
266 {
267 	return BLayoutUtils::ComposeSize(ExplicitMinSize(),
268 		BSize(fCharacterHeight, fCharacterHeight + fTitleHeight));
269 }
270 
271 
272 void
273 CharacterView::FrameResized(float width, float height)
274 {
275 	// Scroll to character
276 
277 	if (!fHasTopCharacter)
278 		return;
279 
280 	BRect frame = _FrameFor(fTopCharacter);
281 	if (!frame.IsValid())
282 		return;
283 
284 	BView::ScrollTo(0, frame.top - fTopOffset);
285 	fHasTopCharacter = false;
286 }
287 
288 
289 class PreviewItem: public BMenuItem
290 {
291 	public:
292 		PreviewItem(const char* text, float width, float height)
293 			: BMenuItem(text, NULL),
294 			fWidth(width * 2),
295 			fHeight(height * 2)
296 		{
297 		}
298 
299 		void GetContentSize(float* width, float* height)
300 		{
301 			*width = fWidth;
302 			*height = fHeight;
303 		}
304 
305 		void Draw()
306 		{
307 			BMenu* menu = Menu();
308 			BRect box = Frame();
309 
310 			menu->PushState();
311 			menu->SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR);
312 			menu->SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
313 			menu->SetHighUIColor(B_DOCUMENT_TEXT_COLOR);
314 			menu->FillRect(box, B_SOLID_LOW);
315 
316 			// Draw the character in the center of the menu
317 			float charWidth = menu->StringWidth(Label());
318 			font_height fontHeight;
319 			menu->GetFontHeight(&fontHeight);
320 
321 			box.left += (box.Width() - charWidth) / 2;
322 			box.bottom -= (box.Height() - fontHeight.ascent
323 				+ fontHeight.descent) / 2;
324 
325 			menu->DrawString(Label(), BPoint(box.left, box.bottom));
326 
327 			menu->PopState();
328 		}
329 
330 	private:
331 		float fWidth;
332 		float fHeight;
333 };
334 
335 
336 class NoMarginMenu: public BPopUpMenu
337 {
338 	public:
339 		NoMarginMenu()
340 			: BPopUpMenu(B_EMPTY_STRING, false, false)
341 		{
342 			// Try to have the size right (should be exactly 2x the cell width)
343 			// and the item text centered in it.
344 			float left, top, bottom, right;
345 			GetItemMargins(&left, &top, &bottom, &right);
346 			SetItemMargins(left, top, bottom, left);
347 		}
348 };
349 
350 
351 void
352 CharacterView::MouseDown(BPoint where)
353 {
354 	if (!fHasCharacter
355 		|| Window()->CurrentMessage() == NULL)
356 		return;
357 
358 	int32 buttons;
359 	if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) == B_OK) {
360 		if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) {
361 			// Memorize click point for dragging
362 			fClickPoint = where;
363 
364 			char text[16];
365 			UnicodeToUTF8(fCurrentCharacter, text, sizeof(text));
366 
367 			fMenu = new NoMarginMenu();
368 			fMenu->AddItem(new PreviewItem(text, fCharacterWidth,
369 				fCharacterHeight));
370 			fMenu->SetFont(&fCharacterFont);
371 			fMenu->SetFontSize(fCharacterFont.Size() * 2.5);
372 
373 			uint32 character;
374 			BRect rect;
375 
376 			// Position the menu exactly above the character
377 			_GetCharacterAt(where, character, &rect);
378 			fMenu->DoLayout();
379 			where = rect.LeftTop();
380 			where.x += (rect.Width() - fMenu->Frame().Width()) / 2;
381 			where.y += (rect.Height() - fMenu->Frame().Height()) / 2;
382 
383 			ConvertToScreen(&where);
384 			fMenu->Go(where, true, true, true);
385 		} else {
386 			// Show context menu
387 			BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
388 			menu->SetFont(be_plain_font);
389 
390 			BMessage* message =  new BMessage(B_COPY);
391 			message->AddInt32("character", fCurrentCharacter);
392 			menu->AddItem(new BMenuItem(B_TRANSLATE("Copy character"), message,
393 				'C'));
394 
395 			message =  new BMessage(kMsgCopyAsEscapedString);
396 			message->AddInt32("character", fCurrentCharacter);
397 			menu->AddItem(new BMenuItem(
398 				B_TRANSLATE("Copy as escaped byte string"),
399 				message, 'C', B_SHIFT_KEY));
400 
401 			menu->SetTargetForItems(this);
402 
403 			ConvertToScreen(&where);
404 			menu->Go(where, true, true, true);
405 		}
406 	}
407 }
408 
409 
410 void
411 CharacterView::MouseUp(BPoint where)
412 {
413 	fClickPoint.x = -1;
414 }
415 
416 
417 void
418 CharacterView::MouseMoved(BPoint where, uint32 transit,
419 	const BMessage* dragMessage)
420 {
421 	if (dragMessage != NULL)
422 		return;
423 
424 	BRect frame;
425 	uint32 character;
426 	bool hasCharacter = _GetCharacterAt(where, character, &frame);
427 
428 	if (fHasCharacter && (character != fCurrentCharacter || !hasCharacter))
429 		Invalidate(fCurrentCharacterFrame);
430 
431 	if (hasCharacter && (character != fCurrentCharacter || !fHasCharacter)) {
432 		BMessage update(fTargetCommand);
433 		update.AddInt32("character", character);
434 		fTarget.SendMessage(&update);
435 
436 		Invalidate(frame);
437 	}
438 
439 	fHasCharacter = hasCharacter;
440 	fCurrentCharacter = character;
441 	fCurrentCharacterFrame = frame;
442 
443 	if (fClickPoint.x >= 0 && (fabs(where.x - fClickPoint.x) > 4
444 			|| fabs(where.y - fClickPoint.y) > 4)) {
445 		// Start dragging
446 
447 		// Update character - we want to drag the one we originally clicked
448 		// on, not the one the mouse might be over now.
449 		if (!_GetCharacterAt(fClickPoint, character, &frame))
450 			return;
451 
452 		BPoint offset = fClickPoint - frame.LeftTop();
453 		frame.OffsetTo(B_ORIGIN);
454 
455 		BBitmap* bitmap = new BBitmap(frame, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
456 		if (bitmap->InitCheck() != B_OK) {
457 			delete bitmap;
458 			return;
459 		}
460 		bitmap->Lock();
461 
462 		BView* view = new BView(frame, "drag", 0, 0);
463 		bitmap->AddChild(view);
464 
465 		view->SetLowColor(B_TRANSPARENT_COLOR);
466 		view->FillRect(frame, B_SOLID_LOW);
467 
468 		// Draw character
469 		char text[16];
470 		UnicodeToUTF8(character, text, sizeof(text));
471 
472 		view->SetDrawingMode(B_OP_ALPHA);
473 		view->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
474 		view->SetFont(&fCharacterFont);
475 		view->DrawString(text,
476 			BPoint((fCharacterWidth - view->StringWidth(text)) / 2,
477 				fCharacterBase));
478 
479 		view->Sync();
480 		bitmap->RemoveChild(view);
481 		bitmap->Unlock();
482 
483 		BMessage drag(B_MIME_DATA);
484 		if ((modifiers() & (B_SHIFT_KEY | B_OPTION_KEY)) != 0) {
485 			// paste UTF-8 hex string
486 			CharacterView::UnicodeToUTF8Hex(character, text, sizeof(text));
487 		}
488 		drag.AddData("text/plain", B_MIME_DATA, text, strlen(text));
489 
490 		DragMessage(&drag, bitmap, B_OP_ALPHA, offset);
491 		fClickPoint.x = -1;
492 
493 		fHasCharacter = false;
494 		Invalidate(fCurrentCharacterFrame);
495 	}
496 }
497 
498 
499 void
500 CharacterView::Draw(BRect updateRect)
501 {
502 	const int32 kXGap = fGap / 2;
503 
504 	BFont font;
505 	GetFont(&font);
506 
507 	rgb_color color = (rgb_color){0, 0, 0, 255};
508 	rgb_color highlight = (rgb_color){220, 220, 220, 255};
509 	rgb_color enclose = mix_color(highlight,
510 		ui_color(B_CONTROL_HIGHLIGHT_COLOR), 128);
511 
512 	for (int32 i = _BlockAt(updateRect.LeftTop()); i < (int32)kNumUnicodeBlocks;
513 			i++) {
514 		if (!IsShowingBlock(i))
515 			continue;
516 
517 		int32 y = fTitleTops[i];
518 		if (y > updateRect.bottom)
519 			break;
520 
521 		SetHighColor(color);
522 		DrawString(kUnicodeBlocks[i].name, BPoint(3, y + fTitleBase));
523 
524 		y += fTitleHeight;
525 		int32 x = kXGap;
526 		SetFont(&fCharacterFont);
527 
528 		for (uint32 c = kUnicodeBlocks[i].start; c <= kUnicodeBlocks[i].end;
529 				c++) {
530 			if (y + fCharacterHeight > updateRect.top
531 				&& y < updateRect.bottom) {
532 				// Stroke frame around the active character
533 				if (fHasCharacter && fCurrentCharacter == c) {
534 					SetHighColor(highlight);
535 					FillRect(BRect(x, y, x + fCharacterWidth,
536 						y + fCharacterHeight - fGap));
537 					SetHighColor(enclose);
538 					StrokeRect(BRect(x, y, x + fCharacterWidth,
539 						y + fCharacterHeight - fGap));
540 
541 					SetHighColor(color);
542 					SetLowColor(highlight);
543 				}
544 
545 				// Draw character
546 				char character[16];
547 				UnicodeToUTF8(c, character, sizeof(character));
548 
549 				DrawString(character,
550 					BPoint(x + (fCharacterWidth - StringWidth(character)) / 2,
551 						y + fCharacterBase));
552 			}
553 
554 			x += fCharacterWidth + fGap;
555 			if (x + fCharacterWidth + kXGap >= fDataRect.right) {
556 				y += fCharacterHeight;
557 				x = kXGap;
558 			}
559 		}
560 
561 		if (x != kXGap)
562 			y += fCharacterHeight;
563 		y += fTitleGap;
564 
565 		SetFont(&font);
566 	}
567 }
568 
569 
570 void
571 CharacterView::DoLayout()
572 {
573 	fHasTopCharacter = _GetTopmostCharacter(fTopCharacter, fTopOffset);
574 	_UpdateSize();
575 }
576 
577 
578 int32
579 CharacterView::_BlockAt(BPoint point) const
580 {
581 	uint32 min = 0;
582 	uint32 max = kNumUnicodeBlocks;
583 	uint32 guess = (max + min) / 2;
584 
585 	while ((max >= min) && (guess < kNumUnicodeBlocks - 1 )) {
586 		if (fTitleTops[guess] <= point.y && fTitleTops[guess + 1] >= point.y) {
587 			if (!IsShowingBlock(guess))
588 				return -1;
589 			else
590 				return guess;
591 		}
592 
593 		if (fTitleTops[guess + 1] < point.y) {
594 			min = guess + 1;
595 		} else {
596 			max = guess - 1;
597 		}
598 
599 		guess = (max + min) / 2;
600 	}
601 
602 	return -1;
603 }
604 
605 
606 bool
607 CharacterView::_GetCharacterAt(BPoint point, uint32& character,
608 	BRect* _frame) const
609 {
610 	int32 i = _BlockAt(point);
611 	if (i == -1)
612 		return false;
613 
614 	int32 y = fTitleTops[i] + fTitleHeight;
615 	if (y > point.y)
616 		return false;
617 
618 	const int32 startX = fGap / 2;
619 	if (startX > point.x)
620 		return false;
621 
622 	int32 endX = startX + fCharactersPerLine * (fCharacterWidth + fGap);
623 	if (endX < point.x)
624 		return false;
625 
626 	for (uint32 c = kUnicodeBlocks[i].start; c <= kUnicodeBlocks[i].end;
627 			c += fCharactersPerLine, y += fCharacterHeight) {
628 		if (y + fCharacterHeight <= point.y)
629 			continue;
630 
631 		int32 pos = (int32)((point.x - startX) / (fCharacterWidth + fGap));
632 		if (c + pos > kUnicodeBlocks[i].end)
633 			return false;
634 
635 		// Found character at position
636 
637 		character = c + pos;
638 
639 		if (_frame != NULL) {
640 			_frame->Set(startX + pos * (fCharacterWidth + fGap),
641 				y, startX + (pos + 1) * (fCharacterWidth + fGap) - 1,
642 				y + fCharacterHeight);
643 		}
644 
645 		return true;
646 	}
647 
648 	return false;
649 }
650 
651 
652 void
653 CharacterView::_UpdateFontSize()
654 {
655 	font_height fontHeight;
656 	GetFontHeight(&fontHeight);
657 	fTitleHeight = (int32)ceilf(fontHeight.ascent + fontHeight.descent
658 		+ fontHeight.leading) + 2;
659 	fTitleBase = (int32)ceilf(fontHeight.ascent);
660 
661 	// Find widest character
662 	fCharacterWidth = (int32)ceilf(fCharacterFont.StringWidth("W") * 1.5f);
663 
664 	if (fCharacterFont.IsFullAndHalfFixed()) {
665 		// TODO: improve this!
666 		fCharacterWidth = (int32)ceilf(fCharacterWidth * 1.4);
667 	}
668 
669 	fCharacterFont.GetHeight(&fontHeight);
670 	fCharacterHeight = (int32)ceilf(fontHeight.ascent + fontHeight.descent
671 		+ fontHeight.leading);
672 	fCharacterBase = (int32)ceilf(fontHeight.ascent);
673 
674 	fGap = (int32)roundf(fCharacterHeight / 8.0);
675 	if (fGap < 3)
676 		fGap = 3;
677 
678 	fCharacterHeight += fGap;
679 	fTitleGap = fGap * 3;
680 }
681 
682 
683 void
684 CharacterView::_UpdateSize()
685 {
686 	// Compute data rect
687 
688 	BRect bounds = Bounds();
689 
690 	_UpdateFontSize();
691 
692 	fDataRect.right = bounds.Width();
693 	fDataRect.bottom = 0;
694 
695 	fCharactersPerLine = int32(bounds.Width() / (fGap + fCharacterWidth));
696 	if (fCharactersPerLine == 0)
697 		fCharactersPerLine = 1;
698 
699 	for (uint32 i = 0; i < kNumUnicodeBlocks; i++) {
700 		fTitleTops[i] = (int32)ceilf(fDataRect.bottom);
701 
702 		if (!IsShowingBlock(i))
703 			continue;
704 
705 		int32 lines = (kUnicodeBlocks[i].Count() + fCharactersPerLine - 1)
706 			/ fCharactersPerLine;
707 		fDataRect.bottom += lines * fCharacterHeight + fTitleHeight + fTitleGap;
708 	}
709 
710 	// Update scroll bars
711 
712 	BScrollBar* scroller = ScrollBar(B_VERTICAL);
713 	if (scroller == NULL)
714 		return;
715 
716 	if (bounds.Height() > fDataRect.Height()) {
717 		// no scrolling
718 		scroller->SetRange(0.0f, 0.0f);
719 		scroller->SetValue(0.0f);
720 	} else {
721 		scroller->SetRange(0.0f, fDataRect.Height() - bounds.Height() - 1.0f);
722 		scroller->SetProportion(bounds.Height () / fDataRect.Height());
723 		scroller->SetSteps(fCharacterHeight,
724 			Bounds().Height() - fCharacterHeight);
725 
726 		// scroll up if there is empty room on bottom
727 		if (fDataRect.Height() < bounds.bottom)
728 			ScrollBy(0.0f, bounds.bottom - fDataRect.Height());
729 	}
730 
731 	Invalidate();
732 }
733 
734 
735 bool
736 CharacterView::_GetTopmostCharacter(uint32& character, int32& offset) const
737 {
738 	int32 top = (int32)Bounds().top;
739 
740 	int32 i = _BlockAt(BPoint(0, top));
741 	if (i == -1)
742 		return false;
743 
744 	int32 characterTop = fTitleTops[i] + fTitleHeight;
745 	if (characterTop > top) {
746 		character = kUnicodeBlocks[i].start;
747 		offset = characterTop - top;
748 		return true;
749 	}
750 
751 	int32 lines = (top - characterTop + fCharacterHeight - 1)
752 		/ fCharacterHeight;
753 
754 	character = kUnicodeBlocks[i].start + lines * fCharactersPerLine;
755 	offset = top - characterTop - lines * fCharacterHeight;
756 	return true;
757 }
758 
759 
760 BRect
761 CharacterView::_FrameFor(uint32 character) const
762 {
763 	// find block containing the character
764 	int32 blockNumber = BlockForCharacter(character);
765 
766 	if (blockNumber > 0) {
767 		int32 diff = character - kUnicodeBlocks[blockNumber].start;
768 		int32 y = fTitleTops[blockNumber] + fTitleHeight
769 			+ (diff / fCharactersPerLine) * fCharacterHeight;
770 		int32 x = fGap / 2 + diff % fCharactersPerLine;
771 
772 		return BRect(x, y, x + fCharacterWidth + fGap, y + fCharacterHeight);
773 	}
774 
775 	return BRect();
776 }
777 
778 
779 void
780 CharacterView::_CopyToClipboard(const char* text)
781 {
782 	if (!be_clipboard->Lock())
783 		return;
784 
785 	be_clipboard->Clear();
786 
787 	BMessage* clip = be_clipboard->Data();
788 	if (clip != NULL) {
789 		clip->AddData("text/plain", B_MIME_TYPE, text, strlen(text));
790 		be_clipboard->Commit();
791 	}
792 
793 	be_clipboard->Unlock();
794 }
795