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
CharacterView(const char * name)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
~CharacterView()46 CharacterView::~CharacterView()
47 {
48 delete[] fTitleTops;
49 }
50
51
52 void
SetTarget(BMessenger target,uint32 command)53 CharacterView::SetTarget(BMessenger target, uint32 command)
54 {
55 fTarget = target;
56 fTargetCommand = command;
57 }
58
59
60 void
SetCharacterFont(const BFont & font)61 CharacterView::SetCharacterFont(const BFont& font)
62 {
63 fCharacterFont = font;
64 fUnicodeBlocks = fCharacterFont.Blocks();
65 InvalidateLayout();
66 }
67
68
69 void
ShowPrivateBlocks(bool show)70 CharacterView::ShowPrivateBlocks(bool show)
71 {
72 if (fShowPrivateBlocks == show)
73 return;
74
75 fShowPrivateBlocks = show;
76 InvalidateLayout();
77 }
78
79
80 void
ShowContainedBlocksOnly(bool show)81 CharacterView::ShowContainedBlocksOnly(bool show)
82 {
83 if (fShowContainedBlocksOnly == show)
84 return;
85
86 fShowContainedBlocksOnly = show;
87 InvalidateLayout();
88 }
89
90
91 bool
IsShowingBlock(int32 blockIndex) const92 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
ScrollToBlock(int32 blockIndex)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
ScrollToCharacter(uint32 c)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
IsCharacterVisible(uint32 c) const150 CharacterView::IsCharacterVisible(uint32 c) const
151 {
152 return Bounds().Contains(_FrameFor(c));
153 }
154
155
156 bool
IsBlockVisible(int32 block) const157 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
UnicodeToUTF8(uint32 c,char * text,size_t textSize)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
UnicodeToUTF8Hex(uint32 c,char * text,size_t textSize)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
MessageReceived(BMessage * message)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
AttachedToWindow()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
DetachedFromWindow()262 CharacterView::DetachedFromWindow()
263 {
264 }
265
266
267 BSize
MinSize()268 CharacterView::MinSize()
269 {
270 return BLayoutUtils::ComposeSize(ExplicitMinSize(),
271 BSize(fCharacterHeight, fCharacterHeight + fTitleHeight));
272 }
273
274
275 void
FrameResized(float width,float height)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:
PreviewItem(const char * text,float width,float height)295 PreviewItem(const char* text, float width, float height)
296 : BMenuItem(text, NULL),
297 fWidth(width * 2),
298 fHeight(height * 2)
299 {
300 }
301
GetContentSize(float * width,float * height)302 void GetContentSize(float* width, float* height)
303 {
304 *width = fWidth;
305 *height = fHeight;
306 }
307
Draw()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:
NoMarginMenu()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
MouseDown(BPoint where)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
MouseUp(BPoint where)421 CharacterView::MouseUp(BPoint where)
422 {
423 fClickPoint.x = -1;
424 }
425
426
427 void
MouseMoved(BPoint where,uint32 transit,const BMessage * dragMessage)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
Draw(BRect updateRect)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
DoLayout()588 CharacterView::DoLayout()
589 {
590 fHasTopCharacter = _GetTopmostCharacter(fTopCharacter, fTopOffset);
591 _UpdateSize();
592 }
593
594
595 int32
_BlockAt(BPoint point) const596 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
_GetCharacterAt(BPoint point,uint32 & character,BRect * _frame) const624 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
_UpdateFontSize()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
_UpdateSize()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
_GetTopmostCharacter(uint32 & character,int32 & offset) const753 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
_FrameFor(uint32 character) const778 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
_CopyToClipboard(const char * text)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
_HasGlyphForCharacter(const char * character) const815 CharacterView::_HasGlyphForCharacter(const char* character) const
816 {
817 bool hasGlyph;
818 fCharacterFont.GetHasGlyphs(character, 1, &hasGlyph, false);
819 return hasGlyph;
820 }
821