xref: /haiku/src/apps/charactermap/CharacterView.cpp (revision 47c05920fde47c2618efccd24bd82f1e79cdf05a)
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 	if (c == 0) {
206 		snprintf(text, textSize, "\\x00");
207 		return;
208 	}
209 
210 	char character[16];
211 	CharacterView::UnicodeToUTF8(c, character, sizeof(character));
212 
213 	int size = 0;
214 	for (int32 i = 0; character[i] && size < (int)textSize; i++) {
215 		size += snprintf(text + size, textSize - size, "\\x%02x",
216 			(uint8)character[i]);
217 	}
218 }
219 
220 
221 void
222 CharacterView::MessageReceived(BMessage* message)
223 {
224 	switch (message->what) {
225 		case kMsgCopyAsEscapedString:
226 		case B_COPY:
227 		{
228 			uint32 character;
229 			if (message->FindInt32("character", (int32*)&character) != B_OK) {
230 				if (!fHasCharacter)
231 					break;
232 
233 				character = fCurrentCharacter;
234 			}
235 
236 			char text[17];
237 			if (message->what == kMsgCopyAsEscapedString)
238 				UnicodeToUTF8Hex(character, text, sizeof(text));
239 			else
240 				UnicodeToUTF8(character, text, sizeof(text));
241 
242 			_CopyToClipboard(text);
243 			break;
244 		}
245 
246 		default:
247 			BView::MessageReceived(message);
248 			break;
249 	}
250 }
251 
252 
253 void
254 CharacterView::AttachedToWindow()
255 {
256 	Window()->AddShortcut('C', B_SHIFT_KEY,
257 		new BMessage(kMsgCopyAsEscapedString), this);
258 	SetViewColor(255, 255, 255, 255);
259 	SetLowColor(ViewColor());
260 }
261 
262 
263 void
264 CharacterView::DetachedFromWindow()
265 {
266 }
267 
268 
269 BSize
270 CharacterView::MinSize()
271 {
272 	return BLayoutUtils::ComposeSize(ExplicitMinSize(),
273 		BSize(fCharacterHeight, fCharacterHeight + fTitleHeight));
274 }
275 
276 
277 void
278 CharacterView::FrameResized(float width, float height)
279 {
280 	// Scroll to character
281 
282 	if (!fHasTopCharacter)
283 		return;
284 
285 	BRect frame = _FrameFor(fTopCharacter);
286 	if (!frame.IsValid())
287 		return;
288 
289 	BView::ScrollTo(0, frame.top - fTopOffset);
290 	fHasTopCharacter = false;
291 }
292 
293 
294 class PreviewItem: public BMenuItem
295 {
296 	public:
297 		PreviewItem(const char* text, float width, float height)
298 			: BMenuItem(text, NULL),
299 			fWidth(width * 2),
300 			fHeight(height * 2)
301 		{
302 		}
303 
304 		void GetContentSize(float* width, float* height)
305 		{
306 			*width = fWidth;
307 			*height = fHeight;
308 		}
309 
310 		void Draw()
311 		{
312 			BMenu* menu = Menu();
313 			BRect box = Frame();
314 
315 			menu->PushState();
316 			menu->SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR);
317 			menu->SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
318 			menu->SetHighUIColor(B_DOCUMENT_TEXT_COLOR);
319 			menu->FillRect(box, B_SOLID_LOW);
320 
321 			// Draw the character in the center of the menu
322 			float charWidth = menu->StringWidth(Label());
323 			font_height fontHeight;
324 			menu->GetFontHeight(&fontHeight);
325 
326 			box.left += (box.Width() - charWidth) / 2;
327 			box.bottom -= (box.Height() - fontHeight.ascent
328 				+ fontHeight.descent) / 2;
329 
330 			menu->DrawString(Label(), BPoint(box.left, box.bottom));
331 
332 			menu->PopState();
333 		}
334 
335 	private:
336 		float fWidth;
337 		float fHeight;
338 };
339 
340 
341 class NoMarginMenu: public BPopUpMenu
342 {
343 	public:
344 		NoMarginMenu()
345 			: BPopUpMenu(B_EMPTY_STRING, false, false)
346 		{
347 			// Try to have the size right (should be exactly 2x the cell width)
348 			// and the item text centered in it.
349 			float left, top, bottom, right;
350 			GetItemMargins(&left, &top, &bottom, &right);
351 			SetItemMargins(left, top, bottom, left);
352 		}
353 };
354 
355 
356 void
357 CharacterView::MouseDown(BPoint where)
358 {
359 	if (!fHasCharacter
360 		|| Window()->CurrentMessage() == NULL)
361 		return;
362 
363 	int32 buttons;
364 	if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) == B_OK) {
365 		if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) {
366 			// Memorize click point for dragging
367 			fClickPoint = where;
368 
369 			char text[16];
370 			UnicodeToUTF8(fCurrentCharacter, text, sizeof(text));
371 
372 			fMenu = new NoMarginMenu();
373 			fMenu->AddItem(new PreviewItem(text, fCharacterWidth,
374 				fCharacterHeight));
375 			fMenu->SetFont(&fCharacterFont);
376 			fMenu->SetFontSize(fCharacterFont.Size() * 2.5);
377 
378 			uint32 character;
379 			BRect rect;
380 
381 			// Position the menu exactly above the character
382 			_GetCharacterAt(where, character, &rect);
383 			fMenu->DoLayout();
384 			where = rect.LeftTop();
385 			where.x += (rect.Width() - fMenu->Frame().Width()) / 2;
386 			where.y += (rect.Height() - fMenu->Frame().Height()) / 2;
387 
388 			ConvertToScreen(&where);
389 			fMenu->Go(where, true, true, true);
390 		} else {
391 			// Show context menu
392 			BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
393 			menu->SetFont(be_plain_font);
394 
395 			BMessage* message =  new BMessage(B_COPY);
396 			message->AddInt32("character", fCurrentCharacter);
397 			menu->AddItem(new BMenuItem(B_TRANSLATE("Copy character"), message,
398 				'C'));
399 
400 			message =  new BMessage(kMsgCopyAsEscapedString);
401 			message->AddInt32("character", fCurrentCharacter);
402 			menu->AddItem(new BMenuItem(
403 				B_TRANSLATE("Copy as escaped byte string"),
404 				message, 'C', B_SHIFT_KEY));
405 
406 			menu->SetTargetForItems(this);
407 
408 			ConvertToScreen(&where);
409 			menu->Go(where, true, true, true);
410 		}
411 	}
412 }
413 
414 
415 void
416 CharacterView::MouseUp(BPoint where)
417 {
418 	fClickPoint.x = -1;
419 }
420 
421 
422 void
423 CharacterView::MouseMoved(BPoint where, uint32 transit,
424 	const BMessage* dragMessage)
425 {
426 	if (dragMessage != NULL)
427 		return;
428 
429 	BRect frame;
430 	uint32 character;
431 	bool hasCharacter = _GetCharacterAt(where, character, &frame);
432 
433 	if (fHasCharacter && (character != fCurrentCharacter || !hasCharacter))
434 		Invalidate(fCurrentCharacterFrame);
435 
436 	if (hasCharacter && (character != fCurrentCharacter || !fHasCharacter)) {
437 		BMessage update(fTargetCommand);
438 		update.AddInt32("character", character);
439 		fTarget.SendMessage(&update);
440 
441 		Invalidate(frame);
442 	}
443 
444 	fHasCharacter = hasCharacter;
445 	fCurrentCharacter = character;
446 	fCurrentCharacterFrame = frame;
447 
448 	if (fClickPoint.x >= 0 && (fabs(where.x - fClickPoint.x) > 4
449 			|| fabs(where.y - fClickPoint.y) > 4)) {
450 		// Start dragging
451 
452 		// Update character - we want to drag the one we originally clicked
453 		// on, not the one the mouse might be over now.
454 		if (!_GetCharacterAt(fClickPoint, character, &frame))
455 			return;
456 
457 		BPoint offset = fClickPoint - frame.LeftTop();
458 		frame.OffsetTo(B_ORIGIN);
459 
460 		BBitmap* bitmap = new BBitmap(frame, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
461 		if (bitmap->InitCheck() != B_OK) {
462 			delete bitmap;
463 			return;
464 		}
465 		bitmap->Lock();
466 
467 		BView* view = new BView(frame, "drag", 0, 0);
468 		bitmap->AddChild(view);
469 
470 		view->SetLowColor(B_TRANSPARENT_COLOR);
471 		view->FillRect(frame, B_SOLID_LOW);
472 
473 		// Draw character
474 		char text[17];
475 		UnicodeToUTF8(character, text, sizeof(text));
476 
477 		view->SetDrawingMode(B_OP_ALPHA);
478 		view->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
479 		view->SetFont(&fCharacterFont);
480 		view->DrawString(text,
481 			BPoint((fCharacterWidth - view->StringWidth(text)) / 2,
482 				fCharacterBase));
483 
484 		view->Sync();
485 		bitmap->RemoveChild(view);
486 		bitmap->Unlock();
487 
488 		BMessage drag(B_MIME_DATA);
489 		if ((modifiers() & (B_SHIFT_KEY | B_OPTION_KEY)) != 0) {
490 			// paste UTF-8 hex string
491 			CharacterView::UnicodeToUTF8Hex(character, text, sizeof(text));
492 		}
493 		drag.AddData("text/plain", B_MIME_DATA, text, strlen(text));
494 
495 		DragMessage(&drag, bitmap, B_OP_ALPHA, offset);
496 		fClickPoint.x = -1;
497 
498 		fHasCharacter = false;
499 		Invalidate(fCurrentCharacterFrame);
500 	}
501 }
502 
503 
504 void
505 CharacterView::Draw(BRect updateRect)
506 {
507 	const int32 kXGap = fGap / 2;
508 
509 	BFont font;
510 	GetFont(&font);
511 
512 	rgb_color color = (rgb_color){0, 0, 0, 255};
513 	rgb_color highlight = (rgb_color){220, 220, 220, 255};
514 	rgb_color enclose = mix_color(highlight,
515 		ui_color(B_CONTROL_HIGHLIGHT_COLOR), 128);
516 
517 	for (int32 i = _BlockAt(updateRect.LeftTop()); i < (int32)kNumUnicodeBlocks;
518 			i++) {
519 		if (!IsShowingBlock(i))
520 			continue;
521 
522 		int32 y = fTitleTops[i];
523 		if (y > updateRect.bottom)
524 			break;
525 
526 		SetHighColor(color);
527 		DrawString(kUnicodeBlocks[i].name, BPoint(3, y + fTitleBase));
528 
529 		y += fTitleHeight;
530 		int32 x = kXGap;
531 		SetFont(&fCharacterFont);
532 
533 		for (uint32 c = kUnicodeBlocks[i].start; c <= kUnicodeBlocks[i].end;
534 				c++) {
535 			if (y + fCharacterHeight > updateRect.top
536 				&& y < updateRect.bottom) {
537 				// Stroke frame around the active character
538 				if (fHasCharacter && fCurrentCharacter == c) {
539 					SetHighColor(highlight);
540 					FillRect(BRect(x, y, x + fCharacterWidth,
541 						y + fCharacterHeight - fGap));
542 					SetHighColor(enclose);
543 					StrokeRect(BRect(x, y, x + fCharacterWidth,
544 						y + fCharacterHeight - fGap));
545 
546 					SetHighColor(color);
547 					SetLowColor(highlight);
548 				}
549 
550 				// Draw character
551 				char character[16];
552 				UnicodeToUTF8(c, character, sizeof(character));
553 
554 				DrawString(character,
555 					BPoint(x + (fCharacterWidth - StringWidth(character)) / 2,
556 						y + fCharacterBase));
557 			}
558 
559 			x += fCharacterWidth + fGap;
560 			if (x + fCharacterWidth + kXGap >= fDataRect.right) {
561 				y += fCharacterHeight;
562 				x = kXGap;
563 			}
564 		}
565 
566 		if (x != kXGap)
567 			y += fCharacterHeight;
568 		y += fTitleGap;
569 
570 		SetFont(&font);
571 	}
572 }
573 
574 
575 void
576 CharacterView::DoLayout()
577 {
578 	fHasTopCharacter = _GetTopmostCharacter(fTopCharacter, fTopOffset);
579 	_UpdateSize();
580 }
581 
582 
583 int32
584 CharacterView::_BlockAt(BPoint point) const
585 {
586 	uint32 min = 0;
587 	uint32 max = kNumUnicodeBlocks;
588 	uint32 guess = (max + min) / 2;
589 
590 	while ((max >= min) && (guess < kNumUnicodeBlocks - 1 )) {
591 		if (fTitleTops[guess] <= point.y && fTitleTops[guess + 1] >= point.y) {
592 			if (!IsShowingBlock(guess))
593 				return -1;
594 			else
595 				return guess;
596 		}
597 
598 		if (fTitleTops[guess + 1] < point.y) {
599 			min = guess + 1;
600 		} else {
601 			max = guess - 1;
602 		}
603 
604 		guess = (max + min) / 2;
605 	}
606 
607 	return -1;
608 }
609 
610 
611 bool
612 CharacterView::_GetCharacterAt(BPoint point, uint32& character,
613 	BRect* _frame) const
614 {
615 	int32 i = _BlockAt(point);
616 	if (i == -1)
617 		return false;
618 
619 	int32 y = fTitleTops[i] + fTitleHeight;
620 	if (y > point.y)
621 		return false;
622 
623 	const int32 startX = fGap / 2;
624 	if (startX > point.x)
625 		return false;
626 
627 	int32 endX = startX + fCharactersPerLine * (fCharacterWidth + fGap);
628 	if (endX < point.x)
629 		return false;
630 
631 	for (uint32 c = kUnicodeBlocks[i].start; c <= kUnicodeBlocks[i].end;
632 			c += fCharactersPerLine, y += fCharacterHeight) {
633 		if (y + fCharacterHeight <= point.y)
634 			continue;
635 
636 		int32 pos = (int32)((point.x - startX) / (fCharacterWidth + fGap));
637 		if (c + pos > kUnicodeBlocks[i].end)
638 			return false;
639 
640 		// Found character at position
641 
642 		character = c + pos;
643 
644 		if (_frame != NULL) {
645 			_frame->Set(startX + pos * (fCharacterWidth + fGap),
646 				y, startX + (pos + 1) * (fCharacterWidth + fGap) - 1,
647 				y + fCharacterHeight);
648 		}
649 
650 		return true;
651 	}
652 
653 	return false;
654 }
655 
656 
657 void
658 CharacterView::_UpdateFontSize()
659 {
660 	font_height fontHeight;
661 	GetFontHeight(&fontHeight);
662 	fTitleHeight = (int32)ceilf(fontHeight.ascent + fontHeight.descent
663 		+ fontHeight.leading) + 2;
664 	fTitleBase = (int32)ceilf(fontHeight.ascent);
665 
666 	// Find widest character
667 	fCharacterWidth = (int32)ceilf(fCharacterFont.StringWidth("W") * 1.5f);
668 
669 	if (fCharacterFont.IsFullAndHalfFixed()) {
670 		// TODO: improve this!
671 		fCharacterWidth = (int32)ceilf(fCharacterWidth * 1.4);
672 	}
673 
674 	fCharacterFont.GetHeight(&fontHeight);
675 	fCharacterHeight = (int32)ceilf(fontHeight.ascent + fontHeight.descent
676 		+ fontHeight.leading);
677 	fCharacterBase = (int32)ceilf(fontHeight.ascent);
678 
679 	fGap = (int32)roundf(fCharacterHeight / 8.0);
680 	if (fGap < 3)
681 		fGap = 3;
682 
683 	fCharacterHeight += fGap;
684 	fTitleGap = fGap * 3;
685 }
686 
687 
688 void
689 CharacterView::_UpdateSize()
690 {
691 	// Compute data rect
692 
693 	BRect bounds = Bounds();
694 
695 	_UpdateFontSize();
696 
697 	fDataRect.right = bounds.Width();
698 	fDataRect.bottom = 0;
699 
700 	fCharactersPerLine = int32(bounds.Width() / (fGap + fCharacterWidth));
701 	if (fCharactersPerLine == 0)
702 		fCharactersPerLine = 1;
703 
704 	for (uint32 i = 0; i < kNumUnicodeBlocks; i++) {
705 		fTitleTops[i] = (int32)ceilf(fDataRect.bottom);
706 
707 		if (!IsShowingBlock(i))
708 			continue;
709 
710 		int32 lines = (kUnicodeBlocks[i].Count() + fCharactersPerLine - 1)
711 			/ fCharactersPerLine;
712 		fDataRect.bottom += lines * fCharacterHeight + fTitleHeight + fTitleGap;
713 	}
714 
715 	// Update scroll bars
716 
717 	BScrollBar* scroller = ScrollBar(B_VERTICAL);
718 	if (scroller == NULL)
719 		return;
720 
721 	if (bounds.Height() > fDataRect.Height()) {
722 		// no scrolling
723 		scroller->SetRange(0.0f, 0.0f);
724 		scroller->SetValue(0.0f);
725 	} else {
726 		scroller->SetRange(0.0f, fDataRect.Height() - bounds.Height() - 1.0f);
727 		scroller->SetProportion(bounds.Height () / fDataRect.Height());
728 		scroller->SetSteps(fCharacterHeight,
729 			Bounds().Height() - fCharacterHeight);
730 
731 		// scroll up if there is empty room on bottom
732 		if (fDataRect.Height() < bounds.bottom)
733 			ScrollBy(0.0f, bounds.bottom - fDataRect.Height());
734 	}
735 
736 	Invalidate();
737 }
738 
739 
740 bool
741 CharacterView::_GetTopmostCharacter(uint32& character, int32& offset) const
742 {
743 	int32 top = (int32)Bounds().top;
744 
745 	int32 i = _BlockAt(BPoint(0, top));
746 	if (i == -1)
747 		return false;
748 
749 	int32 characterTop = fTitleTops[i] + fTitleHeight;
750 	if (characterTop > top) {
751 		character = kUnicodeBlocks[i].start;
752 		offset = characterTop - top;
753 		return true;
754 	}
755 
756 	int32 lines = (top - characterTop + fCharacterHeight - 1)
757 		/ fCharacterHeight;
758 
759 	character = kUnicodeBlocks[i].start + lines * fCharactersPerLine;
760 	offset = top - characterTop - lines * fCharacterHeight;
761 	return true;
762 }
763 
764 
765 BRect
766 CharacterView::_FrameFor(uint32 character) const
767 {
768 	// find block containing the character
769 	int32 blockNumber = BlockForCharacter(character);
770 
771 	if (blockNumber > 0) {
772 		int32 diff = character - kUnicodeBlocks[blockNumber].start;
773 		int32 y = fTitleTops[blockNumber] + fTitleHeight
774 			+ (diff / fCharactersPerLine) * fCharacterHeight;
775 		int32 x = fGap / 2 + diff % fCharactersPerLine;
776 
777 		return BRect(x, y, x + fCharacterWidth + fGap, y + fCharacterHeight);
778 	}
779 
780 	return BRect();
781 }
782 
783 
784 void
785 CharacterView::_CopyToClipboard(const char* text)
786 {
787 	if (!be_clipboard->Lock())
788 		return;
789 
790 	be_clipboard->Clear();
791 
792 	BMessage* clip = be_clipboard->Data();
793 	if (clip != NULL) {
794 		clip->AddData("text/plain", B_MIME_TYPE, text, strlen(text));
795 		be_clipboard->Commit();
796 	}
797 
798 	be_clipboard->Unlock();
799 }
800