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