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