2 * Copyright 2009-2010, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
7 #include "CharacterView.h"
14 #include <Clipboard.h>
15 #include <LayoutUtils.h>
17 #include <PopUpMenu.h>
18 #include <ScrollBar.h>
21 #include "UnicodeBlocks.h"
23 #undef B_TRANSLATION_CONTEXT
24 #define B_TRANSLATION_CONTEXT "CharacterView"
26 static const uint32 kMsgCopyAsEscapedString
= 'cesc';
29 CharacterView::CharacterView(const char* name
)
30 : BView(name
, B_WILL_DRAW
| B_FULL_UPDATE_ON_RESIZE
| B_FRAME_EVENTS
),
34 fShowPrivateBlocks(false),
35 fShowContainedBlocksOnly(false)
37 fTitleTops
= new int32
[kNumUnicodeBlocks
];
38 fCharacterFont
.SetSize(fCharacterFont
.Size() * 1.5f
);
45 CharacterView::~CharacterView()
52 CharacterView::SetTarget(BMessenger target
, uint32 command
)
55 fTargetCommand
= command
;
60 CharacterView::SetCharacterFont(const BFont
& font
)
62 fCharacterFont
= font
;
69 CharacterView::ShowPrivateBlocks(bool show
)
71 if (fShowPrivateBlocks
== show
)
74 fShowPrivateBlocks
= show
;
80 CharacterView::ShowContainedBlocksOnly(bool show
)
82 if (fShowContainedBlocksOnly
== show
)
85 fShowContainedBlocksOnly
= show
;
91 CharacterView::IsShowingBlock(int32 blockIndex
) const
93 if (blockIndex
< 0 || blockIndex
>= (int32
)kNumUnicodeBlocks
)
96 if (!fShowPrivateBlocks
&& kUnicodeBlocks
[blockIndex
].private_block
)
99 if (fShowContainedBlocksOnly
100 && !fCharacterFont
.Blocks().Includes(
101 kUnicodeBlocks
[blockIndex
].block
)) {
110 CharacterView::ScrollToBlock(int32 blockIndex
)
112 // don't scroll if the selected block is already in view.
113 // this prevents distracting jumps when crossing a block
114 // boundary in the character view.
115 if (IsBlockVisible(blockIndex
))
120 else if (blockIndex
>= (int32
)kNumUnicodeBlocks
)
121 blockIndex
= kNumUnicodeBlocks
- 1;
123 BView::ScrollTo(0.0f
, fTitleTops
[blockIndex
]);
128 CharacterView::ScrollToCharacter(uint32 c
)
130 if (IsCharacterVisible(c
))
133 BRect frame
= _FrameFor(c
);
134 BView::ScrollTo(0.0f
, frame
.top
);
139 CharacterView::IsCharacterVisible(uint32 c
) const
141 return Bounds().Contains(_FrameFor(c
));
146 CharacterView::IsBlockVisible(int32 block
) const
148 int32 topBlock
= _BlockAt(BPoint(Bounds().left
, Bounds().top
));
149 int32 bottomBlock
= _BlockAt(BPoint(Bounds().right
, Bounds().bottom
));
151 if (block
>= topBlock
&& block
<= bottomBlock
)
159 CharacterView::UnicodeToUTF8(uint32 c
, char* text
, size_t textSize
)
171 else if (c
< 0x800) {
172 *(s
++) = 0xc0 | (c
>> 6);
173 *(s
++) = 0x80 | (c
& 0x3f);
174 } else if (c
< 0x10000) {
175 *(s
++) = 0xe0 | (c
>> 12);
176 *(s
++) = 0x80 | ((c
>> 6) & 0x3f);
177 *(s
++) = 0x80 | (c
& 0x3f);
178 } else if (c
<= 0x10ffff) {
179 *(s
++) = 0xf0 | (c
>> 18);
180 *(s
++) = 0x80 | ((c
>> 12) & 0x3f);
181 *(s
++) = 0x80 | ((c
>> 6) & 0x3f);
182 *(s
++) = 0x80 | (c
& 0x3f);
190 CharacterView::UnicodeToUTF8Hex(uint32 c
, char* text
, size_t textSize
)
193 CharacterView::UnicodeToUTF8(c
, character
, sizeof(character
));
196 for (int32 i
= 0; character
[i
] && size
< (int)textSize
; i
++) {
197 size
+= snprintf(text
+ size
, textSize
- size
, "\\x%02x",
198 (uint8
)character
[i
]);
204 CharacterView::MessageReceived(BMessage
* message
)
206 switch (message
->what
) {
207 case kMsgCopyAsEscapedString
:
211 if (message
->FindInt32("character", (int32
*)&character
) != B_OK
) {
215 character
= fCurrentCharacter
;
219 if (message
->what
== kMsgCopyAsEscapedString
)
220 UnicodeToUTF8Hex(character
, text
, sizeof(text
));
222 UnicodeToUTF8(character
, text
, sizeof(text
));
224 _CopyToClipboard(text
);
229 BView::MessageReceived(message
);
236 CharacterView::AttachedToWindow()
238 Window()->AddShortcut('C', B_SHIFT_KEY
,
239 new BMessage(kMsgCopyAsEscapedString
), this);
240 SetViewColor(255, 255, 255, 255);
241 SetLowColor(ViewColor());
246 CharacterView::DetachedFromWindow()
252 CharacterView::MinSize()
254 return BLayoutUtils::ComposeSize(ExplicitMinSize(),
255 BSize(fCharacterHeight
, fCharacterHeight
+ fTitleHeight
));
260 CharacterView::FrameResized(float width
, float height
)
262 // Scroll to character
264 if (!fHasTopCharacter
)
267 BRect frame
= _FrameFor(fTopCharacter
);
268 if (!frame
.IsValid())
271 BView::ScrollTo(0, frame
.top
- fTopOffset
);
272 fHasTopCharacter
= false;
277 CharacterView::MouseDown(BPoint where
)
281 || Window()->CurrentMessage() == NULL
282 || Window()->CurrentMessage()->FindInt32("buttons", &buttons
) != B_OK
283 || (buttons
& B_SECONDARY_MOUSE_BUTTON
) == 0) {
284 // Memorize click point for dragging
291 BPopUpMenu
*menu
= new BPopUpMenu(B_EMPTY_STRING
, false, false);
292 menu
->SetFont(be_plain_font
);
294 BMessage
* message
= new BMessage(B_COPY
);
295 message
->AddInt32("character", fCurrentCharacter
);
296 menu
->AddItem(new BMenuItem(B_TRANSLATE("Copy character"), message
, 'C'));
298 message
= new BMessage(kMsgCopyAsEscapedString
);
299 message
->AddInt32("character", fCurrentCharacter
);
300 menu
->AddItem(new BMenuItem(B_TRANSLATE("Copy as escaped byte string"),
301 message
, 'C', B_SHIFT_KEY
));
303 menu
->SetTargetForItems(this);
305 ConvertToScreen(&where
);
306 menu
->Go(where
, true, true, true);
311 CharacterView::MouseUp(BPoint where
)
318 CharacterView::MouseMoved(BPoint where
, uint32 transit
,
319 const BMessage
* dragMessage
)
321 if (dragMessage
!= NULL
)
326 bool hasCharacter
= _GetCharacterAt(where
, character
, &frame
);
328 if (fHasCharacter
&& (character
!= fCurrentCharacter
|| !hasCharacter
))
329 Invalidate(fCurrentCharacterFrame
);
331 if (hasCharacter
&& (character
!= fCurrentCharacter
|| !fHasCharacter
)) {
332 BMessage
update(fTargetCommand
);
333 update
.AddInt32("character", character
);
334 fTarget
.SendMessage(&update
);
339 fHasCharacter
= hasCharacter
;
340 fCurrentCharacter
= character
;
341 fCurrentCharacterFrame
= frame
;
343 if (fClickPoint
.x
>= 0 && (fabs(where
.x
- fClickPoint
.x
) > 4
344 || fabs(where
.y
- fClickPoint
.y
) > 4)) {
347 // Update character - we want to drag the one we originally clicked
348 // on, not the one the mouse might be over now.
349 if (!_GetCharacterAt(fClickPoint
, character
, &frame
))
352 BPoint offset
= fClickPoint
- frame
.LeftTop();
353 frame
.OffsetTo(B_ORIGIN
);
355 BBitmap
* bitmap
= new BBitmap(frame
, B_BITMAP_ACCEPTS_VIEWS
, B_RGBA32
);
356 if (bitmap
->InitCheck() != B_OK
) {
362 BView
* view
= new BView(frame
, "drag", 0, 0);
363 bitmap
->AddChild(view
);
365 view
->SetLowColor(B_TRANSPARENT_COLOR
);
366 view
->FillRect(frame
, B_SOLID_LOW
);
370 UnicodeToUTF8(character
, text
, sizeof(text
));
372 view
->SetDrawingMode(B_OP_ALPHA
);
373 view
->SetBlendingMode(B_PIXEL_ALPHA
, B_ALPHA_COMPOSITE
);
374 view
->SetFont(&fCharacterFont
);
375 view
->DrawString(text
,
376 BPoint((fCharacterWidth
- view
->StringWidth(text
)) / 2,
380 bitmap
->RemoveChild(view
);
383 BMessage
drag(B_MIME_DATA
);
384 if ((modifiers() & (B_SHIFT_KEY
| B_OPTION_KEY
)) != 0) {
385 // paste UTF-8 hex string
386 CharacterView::UnicodeToUTF8Hex(character
, text
, sizeof(text
));
388 drag
.AddData("text/plain", B_MIME_DATA
, text
, strlen(text
));
390 DragMessage(&drag
, bitmap
, B_OP_ALPHA
, offset
);
393 fHasCharacter
= false;
394 Invalidate(fCurrentCharacterFrame
);
400 CharacterView::Draw(BRect updateRect
)
402 const int32 kXGap
= fGap
/ 2;
407 rgb_color color
= (rgb_color
){0, 0, 0, 255};
408 rgb_color highlight
= (rgb_color
){220, 220, 220, 255};
409 rgb_color enclose
= mix_color(highlight
,
410 ui_color(B_CONTROL_HIGHLIGHT_COLOR
), 128);
412 for (int32 i
= _BlockAt(updateRect
.LeftTop()); i
< (int32
)kNumUnicodeBlocks
;
414 if (!IsShowingBlock(i
))
417 int32 y
= fTitleTops
[i
];
418 if (y
> updateRect
.bottom
)
422 DrawString(kUnicodeBlocks
[i
].name
, BPoint(3, y
+ fTitleBase
));
426 SetFont(&fCharacterFont
);
428 for (uint32 c
= kUnicodeBlocks
[i
].start
; c
<= kUnicodeBlocks
[i
].end
;
430 if (y
+ fCharacterHeight
> updateRect
.top
431 && y
< updateRect
.bottom
) {
432 // Stroke frame around the active character
433 if (fHasCharacter
&& fCurrentCharacter
== c
) {
434 SetHighColor(highlight
);
435 FillRect(BRect(x
, y
, x
+ fCharacterWidth
,
436 y
+ fCharacterHeight
- fGap
));
437 SetHighColor(enclose
);
438 StrokeRect(BRect(x
, y
, x
+ fCharacterWidth
,
439 y
+ fCharacterHeight
- fGap
));
442 SetLowColor(highlight
);
447 UnicodeToUTF8(c
, character
, sizeof(character
));
449 DrawString(character
,
450 BPoint(x
+ (fCharacterWidth
- StringWidth(character
)) / 2,
451 y
+ fCharacterBase
));
454 x
+= fCharacterWidth
+ fGap
;
455 if (x
+ fCharacterWidth
+ kXGap
>= fDataRect
.right
) {
456 y
+= fCharacterHeight
;
462 y
+= fCharacterHeight
;
471 CharacterView::DoLayout()
473 fHasTopCharacter
= _GetTopmostCharacter(fTopCharacter
, fTopOffset
);
479 CharacterView::_BlockAt(BPoint point
) const
481 // TODO: use binary search
482 for (uint32 i
= 0; i
< kNumUnicodeBlocks
; i
++) {
483 if (!IsShowingBlock(i
))
486 if (fTitleTops
[i
] <= point
.y
487 && (i
== kNumUnicodeBlocks
- 1 || fTitleTops
[i
+ 1] > point
.y
))
496 CharacterView::_GetCharacterAt(BPoint point
, uint32
& character
,
499 int32 i
= _BlockAt(point
);
503 int32 y
= fTitleTops
[i
] + fTitleHeight
;
507 const int32 startX
= fGap
/ 2;
508 if (startX
> point
.x
)
511 int32 endX
= startX
+ fCharactersPerLine
* (fCharacterWidth
+ fGap
);
515 for (uint32 c
= kUnicodeBlocks
[i
].start
; c
<= kUnicodeBlocks
[i
].end
;
516 c
+= fCharactersPerLine
, y
+= fCharacterHeight
) {
517 if (y
+ fCharacterHeight
<= point
.y
)
520 int32 pos
= (int32
)((point
.x
- startX
) / (fCharacterWidth
+ fGap
));
521 if (c
+ pos
> kUnicodeBlocks
[i
].end
)
524 // Found character at position
528 if (_frame
!= NULL
) {
529 _frame
->Set(startX
+ pos
* (fCharacterWidth
+ fGap
),
530 y
, startX
+ (pos
+ 1) * (fCharacterWidth
+ fGap
) - 1,
531 y
+ fCharacterHeight
);
542 CharacterView::_UpdateFontSize()
544 font_height fontHeight
;
545 GetFontHeight(&fontHeight
);
546 fTitleHeight
= (int32
)ceilf(fontHeight
.ascent
+ fontHeight
.descent
547 + fontHeight
.leading
) + 2;
548 fTitleBase
= (int32
)ceilf(fontHeight
.ascent
);
550 // Find widest character
551 fCharacterWidth
= (int32
)ceilf(fCharacterFont
.StringWidth("W") * 1.5f
);
553 if (fCharacterFont
.IsFullAndHalfFixed()) {
554 // TODO: improve this!
555 fCharacterWidth
= (int32
)ceilf(fCharacterWidth
* 1.4);
558 fCharacterFont
.GetHeight(&fontHeight
);
559 fCharacterHeight
= (int32
)ceilf(fontHeight
.ascent
+ fontHeight
.descent
560 + fontHeight
.leading
);
561 fCharacterBase
= (int32
)ceilf(fontHeight
.ascent
);
563 fGap
= (int32
)roundf(fCharacterHeight
/ 8.0);
567 fCharacterHeight
+= fGap
;
568 fTitleGap
= fGap
* 3;
573 CharacterView::_UpdateSize()
577 BRect bounds
= Bounds();
581 fDataRect
.right
= bounds
.Width();
582 fDataRect
.bottom
= 0;
584 fCharactersPerLine
= int32(bounds
.Width() / (fGap
+ fCharacterWidth
));
585 if (fCharactersPerLine
== 0)
586 fCharactersPerLine
= 1;
588 for (uint32 i
= 0; i
< kNumUnicodeBlocks
; i
++) {
589 fTitleTops
[i
] = (int32
)ceilf(fDataRect
.bottom
);
591 if (!IsShowingBlock(i
))
594 int32 lines
= (kUnicodeBlocks
[i
].Count() + fCharactersPerLine
- 1)
595 / fCharactersPerLine
;
596 fDataRect
.bottom
+= lines
* fCharacterHeight
+ fTitleHeight
+ fTitleGap
;
599 // Update scroll bars
601 BScrollBar
* scroller
= ScrollBar(B_VERTICAL
);
602 if (scroller
== NULL
)
605 if (bounds
.Height() > fDataRect
.Height()) {
607 scroller
->SetRange(0.0f
, 0.0f
);
608 scroller
->SetValue(0.0f
);
610 scroller
->SetRange(0.0f
, fDataRect
.Height() - bounds
.Height() - 1.0f
);
611 scroller
->SetProportion(bounds
.Height () / fDataRect
.Height());
612 scroller
->SetSteps(fCharacterHeight
,
613 Bounds().Height() - fCharacterHeight
);
615 // scroll up if there is empty room on bottom
616 if (fDataRect
.Height() < bounds
.bottom
)
617 ScrollBy(0.0f
, bounds
.bottom
- fDataRect
.Height());
625 CharacterView::_GetTopmostCharacter(uint32
& character
, int32
& offset
) const
627 int32 top
= (int32
)Bounds().top
;
629 int32 i
= _BlockAt(BPoint(0, top
));
633 int32 characterTop
= fTitleTops
[i
] + fTitleHeight
;
634 if (characterTop
> top
) {
635 character
= kUnicodeBlocks
[i
].start
;
636 offset
= characterTop
- top
;
640 int32 lines
= (top
- characterTop
+ fCharacterHeight
- 1)
643 character
= kUnicodeBlocks
[i
].start
+ lines
* fCharactersPerLine
;
644 offset
= top
- characterTop
- lines
* fCharacterHeight
;
650 CharacterView::_FrameFor(uint32 character
) const
652 // find block containing the character
654 // TODO: could use binary search here
656 for (uint32 i
= 0; i
< kNumUnicodeBlocks
; i
++) {
657 if (kUnicodeBlocks
[i
].end
< character
)
659 if (kUnicodeBlocks
[i
].start
> character
) {
660 // Character is not mapped
664 int32 diff
= character
- kUnicodeBlocks
[i
].start
;
665 int32 y
= fTitleTops
[i
] + fTitleHeight
666 + (diff
/ fCharactersPerLine
) * fCharacterHeight
;
667 int32 x
= fGap
/ 2 + diff
% fCharactersPerLine
;
669 return BRect(x
, y
, x
+ fCharacterWidth
+ fGap
, y
+ fCharacterHeight
);
677 CharacterView::_CopyToClipboard(const char* text
)
679 if (!be_clipboard
->Lock())
682 be_clipboard
->Clear();
684 BMessage
* clip
= be_clipboard
->Data();
686 clip
->AddData("text/plain", B_MIME_TYPE
, text
, strlen(text
));
687 be_clipboard
->Commit();
690 be_clipboard
->Unlock();