795 lines
17 KiB
C++
795 lines
17 KiB
C++
/*
|
|
* Copyright 2009-2010, Axel Dörfler, axeld@pinc-software.de.
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
|
|
#include "CharacterView.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <Bitmap.h>
|
|
#include <Catalog.h>
|
|
#include <Clipboard.h>
|
|
#include <LayoutUtils.h>
|
|
#include <MenuItem.h>
|
|
#include <PopUpMenu.h>
|
|
#include <ScrollBar.h>
|
|
#include <Window.h>
|
|
|
|
#include "UnicodeBlocks.h"
|
|
|
|
#undef B_TRANSLATION_CONTEXT
|
|
#define B_TRANSLATION_CONTEXT "CharacterView"
|
|
|
|
static const uint32 kMsgCopyAsEscapedString = 'cesc';
|
|
|
|
|
|
CharacterView::CharacterView(const char* name)
|
|
: BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS
|
|
| B_SCROLL_VIEW_AWARE),
|
|
fTargetCommand(0),
|
|
fClickPoint(-1, 0),
|
|
fHasCharacter(false),
|
|
fShowPrivateBlocks(false),
|
|
fShowContainedBlocksOnly(false)
|
|
{
|
|
fTitleTops = new int32[kNumUnicodeBlocks];
|
|
fCharacterFont.SetSize(fCharacterFont.Size() * 1.5f);
|
|
|
|
_UpdateFontSize();
|
|
DoLayout();
|
|
}
|
|
|
|
|
|
CharacterView::~CharacterView()
|
|
{
|
|
delete[] fTitleTops;
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::SetTarget(BMessenger target, uint32 command)
|
|
{
|
|
fTarget = target;
|
|
fTargetCommand = command;
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::SetCharacterFont(const BFont& font)
|
|
{
|
|
fCharacterFont = font;
|
|
fUnicodeBlocks = fCharacterFont.Blocks();
|
|
InvalidateLayout();
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::ShowPrivateBlocks(bool show)
|
|
{
|
|
if (fShowPrivateBlocks == show)
|
|
return;
|
|
|
|
fShowPrivateBlocks = show;
|
|
InvalidateLayout();
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::ShowContainedBlocksOnly(bool show)
|
|
{
|
|
if (fShowContainedBlocksOnly == show)
|
|
return;
|
|
|
|
fShowContainedBlocksOnly = show;
|
|
InvalidateLayout();
|
|
}
|
|
|
|
|
|
bool
|
|
CharacterView::IsShowingBlock(int32 blockIndex) const
|
|
{
|
|
if (blockIndex < 0 || blockIndex >= (int32)kNumUnicodeBlocks)
|
|
return false;
|
|
|
|
if (!fShowPrivateBlocks && kUnicodeBlocks[blockIndex].private_block)
|
|
return false;
|
|
|
|
// the reason for two checks is BeOS compatibility.
|
|
// The Includes method checks for unicode blocks as
|
|
// defined by Be, but there are only 71 such blocks.
|
|
// The rest of the blocks (denoted by kNoBlock) need to
|
|
// be queried by searching for the start and end codepoints
|
|
// via the IncludesBlock method.
|
|
if (fShowContainedBlocksOnly) {
|
|
if (kUnicodeBlocks[blockIndex].block != kNoBlock
|
|
&& !fUnicodeBlocks.Includes(
|
|
kUnicodeBlocks[blockIndex].block))
|
|
return false;
|
|
|
|
if (!fCharacterFont.IncludesBlock(
|
|
kUnicodeBlocks[blockIndex].start,
|
|
kUnicodeBlocks[blockIndex].end))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::ScrollToBlock(int32 blockIndex)
|
|
{
|
|
// don't scroll if the selected block is already in view.
|
|
// this prevents distracting jumps when crossing a block
|
|
// boundary in the character view.
|
|
if (IsBlockVisible(blockIndex))
|
|
return;
|
|
|
|
if (blockIndex < 0)
|
|
blockIndex = 0;
|
|
else if (blockIndex >= (int32)kNumUnicodeBlocks)
|
|
blockIndex = kNumUnicodeBlocks - 1;
|
|
|
|
BView::ScrollTo(0.0f, fTitleTops[blockIndex]);
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::ScrollToCharacter(uint32 c)
|
|
{
|
|
if (IsCharacterVisible(c))
|
|
return;
|
|
|
|
BRect frame = _FrameFor(c);
|
|
BView::ScrollTo(0.0f, frame.top);
|
|
}
|
|
|
|
|
|
bool
|
|
CharacterView::IsCharacterVisible(uint32 c) const
|
|
{
|
|
return Bounds().Contains(_FrameFor(c));
|
|
}
|
|
|
|
|
|
bool
|
|
CharacterView::IsBlockVisible(int32 block) const
|
|
{
|
|
int32 topBlock = _BlockAt(BPoint(Bounds().left, Bounds().top));
|
|
int32 bottomBlock = _BlockAt(BPoint(Bounds().right, Bounds().bottom));
|
|
|
|
if (block >= topBlock && block <= bottomBlock)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/*static*/ void
|
|
CharacterView::UnicodeToUTF8(uint32 c, char* text, size_t textSize)
|
|
{
|
|
if (textSize < 5) {
|
|
if (textSize > 0)
|
|
text[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
char* s = text;
|
|
|
|
if (c < 0x80)
|
|
*(s++) = c;
|
|
else if (c < 0x800) {
|
|
*(s++) = 0xc0 | (c >> 6);
|
|
*(s++) = 0x80 | (c & 0x3f);
|
|
} else if (c < 0x10000) {
|
|
*(s++) = 0xe0 | (c >> 12);
|
|
*(s++) = 0x80 | ((c >> 6) & 0x3f);
|
|
*(s++) = 0x80 | (c & 0x3f);
|
|
} else if (c <= 0x10ffff) {
|
|
*(s++) = 0xf0 | (c >> 18);
|
|
*(s++) = 0x80 | ((c >> 12) & 0x3f);
|
|
*(s++) = 0x80 | ((c >> 6) & 0x3f);
|
|
*(s++) = 0x80 | (c & 0x3f);
|
|
}
|
|
|
|
s[0] = '\0';
|
|
}
|
|
|
|
|
|
/*static*/ void
|
|
CharacterView::UnicodeToUTF8Hex(uint32 c, char* text, size_t textSize)
|
|
{
|
|
char character[16];
|
|
CharacterView::UnicodeToUTF8(c, character, sizeof(character));
|
|
|
|
int size = 0;
|
|
for (int32 i = 0; character[i] && size < (int)textSize; i++) {
|
|
size += snprintf(text + size, textSize - size, "\\x%02x",
|
|
(uint8)character[i]);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::MessageReceived(BMessage* message)
|
|
{
|
|
switch (message->what) {
|
|
case kMsgCopyAsEscapedString:
|
|
case B_COPY:
|
|
{
|
|
uint32 character;
|
|
if (message->FindInt32("character", (int32*)&character) != B_OK) {
|
|
if (!fHasCharacter)
|
|
break;
|
|
|
|
character = fCurrentCharacter;
|
|
}
|
|
|
|
char text[16];
|
|
if (message->what == kMsgCopyAsEscapedString)
|
|
UnicodeToUTF8Hex(character, text, sizeof(text));
|
|
else
|
|
UnicodeToUTF8(character, text, sizeof(text));
|
|
|
|
_CopyToClipboard(text);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
BView::MessageReceived(message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::AttachedToWindow()
|
|
{
|
|
Window()->AddShortcut('C', B_SHIFT_KEY,
|
|
new BMessage(kMsgCopyAsEscapedString), this);
|
|
SetViewColor(255, 255, 255, 255);
|
|
SetLowColor(ViewColor());
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::DetachedFromWindow()
|
|
{
|
|
}
|
|
|
|
|
|
BSize
|
|
CharacterView::MinSize()
|
|
{
|
|
return BLayoutUtils::ComposeSize(ExplicitMinSize(),
|
|
BSize(fCharacterHeight, fCharacterHeight + fTitleHeight));
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::FrameResized(float width, float height)
|
|
{
|
|
// Scroll to character
|
|
|
|
if (!fHasTopCharacter)
|
|
return;
|
|
|
|
BRect frame = _FrameFor(fTopCharacter);
|
|
if (!frame.IsValid())
|
|
return;
|
|
|
|
BView::ScrollTo(0, frame.top - fTopOffset);
|
|
fHasTopCharacter = false;
|
|
}
|
|
|
|
|
|
class PreviewItem: public BMenuItem
|
|
{
|
|
public:
|
|
PreviewItem(const char* text, float width, float height)
|
|
: BMenuItem(text, NULL),
|
|
fWidth(width * 2),
|
|
fHeight(height * 2)
|
|
{
|
|
}
|
|
|
|
void GetContentSize(float* width, float* height)
|
|
{
|
|
*width = fWidth;
|
|
*height = fHeight;
|
|
}
|
|
|
|
void Draw()
|
|
{
|
|
BMenu* menu = Menu();
|
|
BRect box = Frame();
|
|
|
|
menu->PushState();
|
|
menu->SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR);
|
|
menu->SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
|
|
menu->SetHighUIColor(B_DOCUMENT_TEXT_COLOR);
|
|
menu->FillRect(box, B_SOLID_LOW);
|
|
|
|
// Draw the character in the center of the menu
|
|
float charWidth = menu->StringWidth(Label());
|
|
font_height fontHeight;
|
|
menu->GetFontHeight(&fontHeight);
|
|
|
|
box.left += (box.Width() - charWidth) / 2;
|
|
box.bottom -= (box.Height() - fontHeight.ascent
|
|
+ fontHeight.descent) / 2;
|
|
|
|
menu->DrawString(Label(), BPoint(box.left, box.bottom));
|
|
|
|
menu->PopState();
|
|
}
|
|
|
|
private:
|
|
float fWidth;
|
|
float fHeight;
|
|
};
|
|
|
|
|
|
class NoMarginMenu: public BPopUpMenu
|
|
{
|
|
public:
|
|
NoMarginMenu()
|
|
: BPopUpMenu(B_EMPTY_STRING, false, false)
|
|
{
|
|
// Try to have the size right (should be exactly 2x the cell width)
|
|
// and the item text centered in it.
|
|
float left, top, bottom, right;
|
|
GetItemMargins(&left, &top, &bottom, &right);
|
|
SetItemMargins(left, top, bottom, left);
|
|
}
|
|
};
|
|
|
|
|
|
void
|
|
CharacterView::MouseDown(BPoint where)
|
|
{
|
|
if (!fHasCharacter
|
|
|| Window()->CurrentMessage() == NULL)
|
|
return;
|
|
|
|
int32 buttons;
|
|
if (Window()->CurrentMessage()->FindInt32("buttons", &buttons) == B_OK) {
|
|
if ((buttons & B_PRIMARY_MOUSE_BUTTON) != 0) {
|
|
// Memorize click point for dragging
|
|
fClickPoint = where;
|
|
|
|
char text[16];
|
|
UnicodeToUTF8(fCurrentCharacter, text, sizeof(text));
|
|
|
|
fMenu = new NoMarginMenu();
|
|
fMenu->AddItem(new PreviewItem(text, fCharacterWidth,
|
|
fCharacterHeight));
|
|
fMenu->SetFont(&fCharacterFont);
|
|
fMenu->SetFontSize(fCharacterFont.Size() * 2.5);
|
|
|
|
uint32 character;
|
|
BRect rect;
|
|
|
|
// Position the menu exactly above the character
|
|
_GetCharacterAt(where, character, &rect);
|
|
fMenu->DoLayout();
|
|
where = rect.LeftTop();
|
|
where.x += (rect.Width() - fMenu->Frame().Width()) / 2;
|
|
where.y += (rect.Height() - fMenu->Frame().Height()) / 2;
|
|
|
|
ConvertToScreen(&where);
|
|
fMenu->Go(where, true, true, true);
|
|
} else {
|
|
// Show context menu
|
|
BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
|
|
menu->SetFont(be_plain_font);
|
|
|
|
BMessage* message = new BMessage(B_COPY);
|
|
message->AddInt32("character", fCurrentCharacter);
|
|
menu->AddItem(new BMenuItem(B_TRANSLATE("Copy character"), message,
|
|
'C'));
|
|
|
|
message = new BMessage(kMsgCopyAsEscapedString);
|
|
message->AddInt32("character", fCurrentCharacter);
|
|
menu->AddItem(new BMenuItem(
|
|
B_TRANSLATE("Copy as escaped byte string"),
|
|
message, 'C', B_SHIFT_KEY));
|
|
|
|
menu->SetTargetForItems(this);
|
|
|
|
ConvertToScreen(&where);
|
|
menu->Go(where, true, true, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::MouseUp(BPoint where)
|
|
{
|
|
fClickPoint.x = -1;
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::MouseMoved(BPoint where, uint32 transit,
|
|
const BMessage* dragMessage)
|
|
{
|
|
if (dragMessage != NULL)
|
|
return;
|
|
|
|
BRect frame;
|
|
uint32 character;
|
|
bool hasCharacter = _GetCharacterAt(where, character, &frame);
|
|
|
|
if (fHasCharacter && (character != fCurrentCharacter || !hasCharacter))
|
|
Invalidate(fCurrentCharacterFrame);
|
|
|
|
if (hasCharacter && (character != fCurrentCharacter || !fHasCharacter)) {
|
|
BMessage update(fTargetCommand);
|
|
update.AddInt32("character", character);
|
|
fTarget.SendMessage(&update);
|
|
|
|
Invalidate(frame);
|
|
}
|
|
|
|
fHasCharacter = hasCharacter;
|
|
fCurrentCharacter = character;
|
|
fCurrentCharacterFrame = frame;
|
|
|
|
if (fClickPoint.x >= 0 && (fabs(where.x - fClickPoint.x) > 4
|
|
|| fabs(where.y - fClickPoint.y) > 4)) {
|
|
// Start dragging
|
|
|
|
// Update character - we want to drag the one we originally clicked
|
|
// on, not the one the mouse might be over now.
|
|
if (!_GetCharacterAt(fClickPoint, character, &frame))
|
|
return;
|
|
|
|
BPoint offset = fClickPoint - frame.LeftTop();
|
|
frame.OffsetTo(B_ORIGIN);
|
|
|
|
BBitmap* bitmap = new BBitmap(frame, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
|
|
if (bitmap->InitCheck() != B_OK) {
|
|
delete bitmap;
|
|
return;
|
|
}
|
|
bitmap->Lock();
|
|
|
|
BView* view = new BView(frame, "drag", 0, 0);
|
|
bitmap->AddChild(view);
|
|
|
|
view->SetLowColor(B_TRANSPARENT_COLOR);
|
|
view->FillRect(frame, B_SOLID_LOW);
|
|
|
|
// Draw character
|
|
char text[16];
|
|
UnicodeToUTF8(character, text, sizeof(text));
|
|
|
|
view->SetDrawingMode(B_OP_ALPHA);
|
|
view->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
|
|
view->SetFont(&fCharacterFont);
|
|
view->DrawString(text,
|
|
BPoint((fCharacterWidth - view->StringWidth(text)) / 2,
|
|
fCharacterBase));
|
|
|
|
view->Sync();
|
|
bitmap->RemoveChild(view);
|
|
bitmap->Unlock();
|
|
|
|
BMessage drag(B_MIME_DATA);
|
|
if ((modifiers() & (B_SHIFT_KEY | B_OPTION_KEY)) != 0) {
|
|
// paste UTF-8 hex string
|
|
CharacterView::UnicodeToUTF8Hex(character, text, sizeof(text));
|
|
}
|
|
drag.AddData("text/plain", B_MIME_DATA, text, strlen(text));
|
|
|
|
DragMessage(&drag, bitmap, B_OP_ALPHA, offset);
|
|
fClickPoint.x = -1;
|
|
|
|
fHasCharacter = false;
|
|
Invalidate(fCurrentCharacterFrame);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::Draw(BRect updateRect)
|
|
{
|
|
const int32 kXGap = fGap / 2;
|
|
|
|
BFont font;
|
|
GetFont(&font);
|
|
|
|
rgb_color color = (rgb_color){0, 0, 0, 255};
|
|
rgb_color highlight = (rgb_color){220, 220, 220, 255};
|
|
rgb_color enclose = mix_color(highlight,
|
|
ui_color(B_CONTROL_HIGHLIGHT_COLOR), 128);
|
|
|
|
for (int32 i = _BlockAt(updateRect.LeftTop()); i < (int32)kNumUnicodeBlocks;
|
|
i++) {
|
|
if (!IsShowingBlock(i))
|
|
continue;
|
|
|
|
int32 y = fTitleTops[i];
|
|
if (y > updateRect.bottom)
|
|
break;
|
|
|
|
SetHighColor(color);
|
|
DrawString(kUnicodeBlocks[i].name, BPoint(3, y + fTitleBase));
|
|
|
|
y += fTitleHeight;
|
|
int32 x = kXGap;
|
|
SetFont(&fCharacterFont);
|
|
|
|
for (uint32 c = kUnicodeBlocks[i].start; c <= kUnicodeBlocks[i].end;
|
|
c++) {
|
|
if (y + fCharacterHeight > updateRect.top
|
|
&& y < updateRect.bottom) {
|
|
// Stroke frame around the active character
|
|
if (fHasCharacter && fCurrentCharacter == c) {
|
|
SetHighColor(highlight);
|
|
FillRect(BRect(x, y, x + fCharacterWidth,
|
|
y + fCharacterHeight - fGap));
|
|
SetHighColor(enclose);
|
|
StrokeRect(BRect(x, y, x + fCharacterWidth,
|
|
y + fCharacterHeight - fGap));
|
|
|
|
SetHighColor(color);
|
|
SetLowColor(highlight);
|
|
}
|
|
|
|
// Draw character
|
|
char character[16];
|
|
UnicodeToUTF8(c, character, sizeof(character));
|
|
|
|
DrawString(character,
|
|
BPoint(x + (fCharacterWidth - StringWidth(character)) / 2,
|
|
y + fCharacterBase));
|
|
}
|
|
|
|
x += fCharacterWidth + fGap;
|
|
if (x + fCharacterWidth + kXGap >= fDataRect.right) {
|
|
y += fCharacterHeight;
|
|
x = kXGap;
|
|
}
|
|
}
|
|
|
|
if (x != kXGap)
|
|
y += fCharacterHeight;
|
|
y += fTitleGap;
|
|
|
|
SetFont(&font);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::DoLayout()
|
|
{
|
|
fHasTopCharacter = _GetTopmostCharacter(fTopCharacter, fTopOffset);
|
|
_UpdateSize();
|
|
}
|
|
|
|
|
|
int32
|
|
CharacterView::_BlockAt(BPoint point) const
|
|
{
|
|
uint32 min = 0;
|
|
uint32 max = kNumUnicodeBlocks;
|
|
uint32 guess = (max + min) / 2;
|
|
|
|
while ((max >= min) && (guess < kNumUnicodeBlocks - 1 )) {
|
|
if (fTitleTops[guess] <= point.y && fTitleTops[guess + 1] >= point.y) {
|
|
if (!IsShowingBlock(guess))
|
|
return -1;
|
|
else
|
|
return guess;
|
|
}
|
|
|
|
if (fTitleTops[guess + 1] < point.y) {
|
|
min = guess + 1;
|
|
} else {
|
|
max = guess - 1;
|
|
}
|
|
|
|
guess = (max + min) / 2;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
bool
|
|
CharacterView::_GetCharacterAt(BPoint point, uint32& character,
|
|
BRect* _frame) const
|
|
{
|
|
int32 i = _BlockAt(point);
|
|
if (i == -1)
|
|
return false;
|
|
|
|
int32 y = fTitleTops[i] + fTitleHeight;
|
|
if (y > point.y)
|
|
return false;
|
|
|
|
const int32 startX = fGap / 2;
|
|
if (startX > point.x)
|
|
return false;
|
|
|
|
int32 endX = startX + fCharactersPerLine * (fCharacterWidth + fGap);
|
|
if (endX < point.x)
|
|
return false;
|
|
|
|
for (uint32 c = kUnicodeBlocks[i].start; c <= kUnicodeBlocks[i].end;
|
|
c += fCharactersPerLine, y += fCharacterHeight) {
|
|
if (y + fCharacterHeight <= point.y)
|
|
continue;
|
|
|
|
int32 pos = (int32)((point.x - startX) / (fCharacterWidth + fGap));
|
|
if (c + pos > kUnicodeBlocks[i].end)
|
|
return false;
|
|
|
|
// Found character at position
|
|
|
|
character = c + pos;
|
|
|
|
if (_frame != NULL) {
|
|
_frame->Set(startX + pos * (fCharacterWidth + fGap),
|
|
y, startX + (pos + 1) * (fCharacterWidth + fGap) - 1,
|
|
y + fCharacterHeight);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::_UpdateFontSize()
|
|
{
|
|
font_height fontHeight;
|
|
GetFontHeight(&fontHeight);
|
|
fTitleHeight = (int32)ceilf(fontHeight.ascent + fontHeight.descent
|
|
+ fontHeight.leading) + 2;
|
|
fTitleBase = (int32)ceilf(fontHeight.ascent);
|
|
|
|
// Find widest character
|
|
fCharacterWidth = (int32)ceilf(fCharacterFont.StringWidth("W") * 1.5f);
|
|
|
|
if (fCharacterFont.IsFullAndHalfFixed()) {
|
|
// TODO: improve this!
|
|
fCharacterWidth = (int32)ceilf(fCharacterWidth * 1.4);
|
|
}
|
|
|
|
fCharacterFont.GetHeight(&fontHeight);
|
|
fCharacterHeight = (int32)ceilf(fontHeight.ascent + fontHeight.descent
|
|
+ fontHeight.leading);
|
|
fCharacterBase = (int32)ceilf(fontHeight.ascent);
|
|
|
|
fGap = (int32)roundf(fCharacterHeight / 8.0);
|
|
if (fGap < 3)
|
|
fGap = 3;
|
|
|
|
fCharacterHeight += fGap;
|
|
fTitleGap = fGap * 3;
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::_UpdateSize()
|
|
{
|
|
// Compute data rect
|
|
|
|
BRect bounds = Bounds();
|
|
|
|
_UpdateFontSize();
|
|
|
|
fDataRect.right = bounds.Width();
|
|
fDataRect.bottom = 0;
|
|
|
|
fCharactersPerLine = int32(bounds.Width() / (fGap + fCharacterWidth));
|
|
if (fCharactersPerLine == 0)
|
|
fCharactersPerLine = 1;
|
|
|
|
for (uint32 i = 0; i < kNumUnicodeBlocks; i++) {
|
|
fTitleTops[i] = (int32)ceilf(fDataRect.bottom);
|
|
|
|
if (!IsShowingBlock(i))
|
|
continue;
|
|
|
|
int32 lines = (kUnicodeBlocks[i].Count() + fCharactersPerLine - 1)
|
|
/ fCharactersPerLine;
|
|
fDataRect.bottom += lines * fCharacterHeight + fTitleHeight + fTitleGap;
|
|
}
|
|
|
|
// Update scroll bars
|
|
|
|
BScrollBar* scroller = ScrollBar(B_VERTICAL);
|
|
if (scroller == NULL)
|
|
return;
|
|
|
|
if (bounds.Height() > fDataRect.Height()) {
|
|
// no scrolling
|
|
scroller->SetRange(0.0f, 0.0f);
|
|
scroller->SetValue(0.0f);
|
|
} else {
|
|
scroller->SetRange(0.0f, fDataRect.Height() - bounds.Height() - 1.0f);
|
|
scroller->SetProportion(bounds.Height () / fDataRect.Height());
|
|
scroller->SetSteps(fCharacterHeight,
|
|
Bounds().Height() - fCharacterHeight);
|
|
|
|
// scroll up if there is empty room on bottom
|
|
if (fDataRect.Height() < bounds.bottom)
|
|
ScrollBy(0.0f, bounds.bottom - fDataRect.Height());
|
|
}
|
|
|
|
Invalidate();
|
|
}
|
|
|
|
|
|
bool
|
|
CharacterView::_GetTopmostCharacter(uint32& character, int32& offset) const
|
|
{
|
|
int32 top = (int32)Bounds().top;
|
|
|
|
int32 i = _BlockAt(BPoint(0, top));
|
|
if (i == -1)
|
|
return false;
|
|
|
|
int32 characterTop = fTitleTops[i] + fTitleHeight;
|
|
if (characterTop > top) {
|
|
character = kUnicodeBlocks[i].start;
|
|
offset = characterTop - top;
|
|
return true;
|
|
}
|
|
|
|
int32 lines = (top - characterTop + fCharacterHeight - 1)
|
|
/ fCharacterHeight;
|
|
|
|
character = kUnicodeBlocks[i].start + lines * fCharactersPerLine;
|
|
offset = top - characterTop - lines * fCharacterHeight;
|
|
return true;
|
|
}
|
|
|
|
|
|
BRect
|
|
CharacterView::_FrameFor(uint32 character) const
|
|
{
|
|
// find block containing the character
|
|
int32 blockNumber = BlockForCharacter(character);
|
|
|
|
if (blockNumber > 0) {
|
|
int32 diff = character - kUnicodeBlocks[blockNumber].start;
|
|
int32 y = fTitleTops[blockNumber] + fTitleHeight
|
|
+ (diff / fCharactersPerLine) * fCharacterHeight;
|
|
int32 x = fGap / 2 + diff % fCharactersPerLine;
|
|
|
|
return BRect(x, y, x + fCharacterWidth + fGap, y + fCharacterHeight);
|
|
}
|
|
|
|
return BRect();
|
|
}
|
|
|
|
|
|
void
|
|
CharacterView::_CopyToClipboard(const char* text)
|
|
{
|
|
if (!be_clipboard->Lock())
|
|
return;
|
|
|
|
be_clipboard->Clear();
|
|
|
|
BMessage* clip = be_clipboard->Data();
|
|
if (clip != NULL) {
|
|
clip->AddData("text/plain", B_MIME_TYPE, text, strlen(text));
|
|
be_clipboard->Commit();
|
|
}
|
|
|
|
be_clipboard->Unlock();
|
|
}
|