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
482 uint32 max
= kNumUnicodeBlocks
;
483 uint32 guess
= (max
+ min
) / 2;
485 while ((max
>= min
) && (guess
< kNumUnicodeBlocks
- 1 )) {
486 if (fTitleTops
[guess
] <= point
.y
&& fTitleTops
[guess
+ 1] >= point
.y
) {
487 if (!IsShowingBlock(guess
))
493 if (fTitleTops
[guess
+ 1] < point
.y
) {
499 guess
= (max
+ min
) / 2;
507 CharacterView::_GetCharacterAt(BPoint point
, uint32
& character
,
510 int32 i
= _BlockAt(point
);
514 int32 y
= fTitleTops
[i
] + fTitleHeight
;
518 const int32 startX
= fGap
/ 2;
519 if (startX
> point
.x
)
522 int32 endX
= startX
+ fCharactersPerLine
* (fCharacterWidth
+ fGap
);
526 for (uint32 c
= kUnicodeBlocks
[i
].start
; c
<= kUnicodeBlocks
[i
].end
;
527 c
+= fCharactersPerLine
, y
+= fCharacterHeight
) {
528 if (y
+ fCharacterHeight
<= point
.y
)
531 int32 pos
= (int32
)((point
.x
- startX
) / (fCharacterWidth
+ fGap
));
532 if (c
+ pos
> kUnicodeBlocks
[i
].end
)
535 // Found character at position
539 if (_frame
!= NULL
) {
540 _frame
->Set(startX
+ pos
* (fCharacterWidth
+ fGap
),
541 y
, startX
+ (pos
+ 1) * (fCharacterWidth
+ fGap
) - 1,
542 y
+ fCharacterHeight
);
553 CharacterView::_UpdateFontSize()
555 font_height fontHeight
;
556 GetFontHeight(&fontHeight
);
557 fTitleHeight
= (int32
)ceilf(fontHeight
.ascent
+ fontHeight
.descent
558 + fontHeight
.leading
) + 2;
559 fTitleBase
= (int32
)ceilf(fontHeight
.ascent
);
561 // Find widest character
562 fCharacterWidth
= (int32
)ceilf(fCharacterFont
.StringWidth("W") * 1.5f
);
564 if (fCharacterFont
.IsFullAndHalfFixed()) {
565 // TODO: improve this!
566 fCharacterWidth
= (int32
)ceilf(fCharacterWidth
* 1.4);
569 fCharacterFont
.GetHeight(&fontHeight
);
570 fCharacterHeight
= (int32
)ceilf(fontHeight
.ascent
+ fontHeight
.descent
571 + fontHeight
.leading
);
572 fCharacterBase
= (int32
)ceilf(fontHeight
.ascent
);
574 fGap
= (int32
)roundf(fCharacterHeight
/ 8.0);
578 fCharacterHeight
+= fGap
;
579 fTitleGap
= fGap
* 3;
584 CharacterView::_UpdateSize()
588 BRect bounds
= Bounds();
592 fDataRect
.right
= bounds
.Width();
593 fDataRect
.bottom
= 0;
595 fCharactersPerLine
= int32(bounds
.Width() / (fGap
+ fCharacterWidth
));
596 if (fCharactersPerLine
== 0)
597 fCharactersPerLine
= 1;
599 for (uint32 i
= 0; i
< kNumUnicodeBlocks
; i
++) {
600 fTitleTops
[i
] = (int32
)ceilf(fDataRect
.bottom
);
602 if (!IsShowingBlock(i
))
605 int32 lines
= (kUnicodeBlocks
[i
].Count() + fCharactersPerLine
- 1)
606 / fCharactersPerLine
;
607 fDataRect
.bottom
+= lines
* fCharacterHeight
+ fTitleHeight
+ fTitleGap
;
610 // Update scroll bars
612 BScrollBar
* scroller
= ScrollBar(B_VERTICAL
);
613 if (scroller
== NULL
)
616 if (bounds
.Height() > fDataRect
.Height()) {
618 scroller
->SetRange(0.0f
, 0.0f
);
619 scroller
->SetValue(0.0f
);
621 scroller
->SetRange(0.0f
, fDataRect
.Height() - bounds
.Height() - 1.0f
);
622 scroller
->SetProportion(bounds
.Height () / fDataRect
.Height());
623 scroller
->SetSteps(fCharacterHeight
,
624 Bounds().Height() - fCharacterHeight
);
626 // scroll up if there is empty room on bottom
627 if (fDataRect
.Height() < bounds
.bottom
)
628 ScrollBy(0.0f
, bounds
.bottom
- fDataRect
.Height());
636 CharacterView::_GetTopmostCharacter(uint32
& character
, int32
& offset
) const
638 int32 top
= (int32
)Bounds().top
;
640 int32 i
= _BlockAt(BPoint(0, top
));
644 int32 characterTop
= fTitleTops
[i
] + fTitleHeight
;
645 if (characterTop
> top
) {
646 character
= kUnicodeBlocks
[i
].start
;
647 offset
= characterTop
- top
;
651 int32 lines
= (top
- characterTop
+ fCharacterHeight
- 1)
654 character
= kUnicodeBlocks
[i
].start
+ lines
* fCharactersPerLine
;
655 offset
= top
- characterTop
- lines
* fCharacterHeight
;
661 CharacterView::_FrameFor(uint32 character
) const
663 // find block containing the character
664 int32 blockNumber
= BlockForCharacter(character
);
666 if (blockNumber
> 0) {
667 int32 diff
= character
- kUnicodeBlocks
[blockNumber
].start
;
668 int32 y
= fTitleTops
[blockNumber
] + fTitleHeight
669 + (diff
/ fCharactersPerLine
) * fCharacterHeight
;
670 int32 x
= fGap
/ 2 + diff
% fCharactersPerLine
;
672 return BRect(x
, y
, x
+ fCharacterWidth
+ fGap
, y
+ fCharacterHeight
);
680 CharacterView::_CopyToClipboard(const char* text
)
682 if (!be_clipboard
->Lock())
685 be_clipboard
->Clear();
687 BMessage
* clip
= be_clipboard
->Data();
689 clip
->AddData("text/plain", B_MIME_TYPE
, text
, strlen(text
));
690 be_clipboard
->Commit();
693 be_clipboard
->Unlock();