vfs: check userland buffers before reading them.
[haiku.git] / src / apps / charactermap / CharacterView.cpp
blobe4f41db420156ebbf10f47d39b44caf2662d8bdf
1 /*
2 * Copyright 2009-2010, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
7 #include "CharacterView.h"
9 #include <stdio.h>
10 #include <string.h>
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>
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),
31 fTargetCommand(0),
32 fClickPoint(-1, 0),
33 fHasCharacter(false),
34 fShowPrivateBlocks(false),
35 fShowContainedBlocksOnly(false)
37 fTitleTops = new int32[kNumUnicodeBlocks];
38 fCharacterFont.SetSize(fCharacterFont.Size() * 1.5f);
40 _UpdateFontSize();
41 DoLayout();
45 CharacterView::~CharacterView()
47 delete[] fTitleTops;
51 void
52 CharacterView::SetTarget(BMessenger target, uint32 command)
54 fTarget = target;
55 fTargetCommand = command;
59 void
60 CharacterView::SetCharacterFont(const BFont& font)
62 fCharacterFont = font;
64 InvalidateLayout();
68 void
69 CharacterView::ShowPrivateBlocks(bool show)
71 if (fShowPrivateBlocks == show)
72 return;
74 fShowPrivateBlocks = show;
75 InvalidateLayout();
79 void
80 CharacterView::ShowContainedBlocksOnly(bool show)
82 if (fShowContainedBlocksOnly == show)
83 return;
85 fShowContainedBlocksOnly = show;
86 InvalidateLayout();
90 bool
91 CharacterView::IsShowingBlock(int32 blockIndex) const
93 if (blockIndex < 0 || blockIndex >= (int32)kNumUnicodeBlocks)
94 return false;
96 if (!fShowPrivateBlocks && kUnicodeBlocks[blockIndex].private_block)
97 return false;
99 if (fShowContainedBlocksOnly
100 && !fCharacterFont.Blocks().Includes(
101 kUnicodeBlocks[blockIndex].block)) {
102 return false;
105 return true;
109 void
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))
116 return;
118 if (blockIndex < 0)
119 blockIndex = 0;
120 else if (blockIndex >= (int32)kNumUnicodeBlocks)
121 blockIndex = kNumUnicodeBlocks - 1;
123 BView::ScrollTo(0.0f, fTitleTops[blockIndex]);
127 void
128 CharacterView::ScrollToCharacter(uint32 c)
130 if (IsCharacterVisible(c))
131 return;
133 BRect frame = _FrameFor(c);
134 BView::ScrollTo(0.0f, frame.top);
138 bool
139 CharacterView::IsCharacterVisible(uint32 c) const
141 return Bounds().Contains(_FrameFor(c));
145 bool
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)
152 return true;
154 return false;
158 /*static*/ void
159 CharacterView::UnicodeToUTF8(uint32 c, char* text, size_t textSize)
161 if (textSize < 5) {
162 if (textSize > 0)
163 text[0] = '\0';
164 return;
167 char* s = text;
169 if (c < 0x80)
170 *(s++) = c;
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);
185 s[0] = '\0';
189 /*static*/ void
190 CharacterView::UnicodeToUTF8Hex(uint32 c, char* text, size_t textSize)
192 char character[16];
193 CharacterView::UnicodeToUTF8(c, character, sizeof(character));
195 int size = 0;
196 for (int32 i = 0; character[i] && size < (int)textSize; i++) {
197 size += snprintf(text + size, textSize - size, "\\x%02x",
198 (uint8)character[i]);
203 void
204 CharacterView::MessageReceived(BMessage* message)
206 switch (message->what) {
207 case kMsgCopyAsEscapedString:
208 case B_COPY:
210 uint32 character;
211 if (message->FindInt32("character", (int32*)&character) != B_OK) {
212 if (!fHasCharacter)
213 break;
215 character = fCurrentCharacter;
218 char text[16];
219 if (message->what == kMsgCopyAsEscapedString)
220 UnicodeToUTF8Hex(character, text, sizeof(text));
221 else
222 UnicodeToUTF8(character, text, sizeof(text));
224 _CopyToClipboard(text);
225 break;
228 default:
229 BView::MessageReceived(message);
230 break;
235 void
236 CharacterView::AttachedToWindow()
238 Window()->AddShortcut('C', B_SHIFT_KEY,
239 new BMessage(kMsgCopyAsEscapedString), this);
240 SetViewColor(255, 255, 255, 255);
241 SetLowColor(ViewColor());
245 void
246 CharacterView::DetachedFromWindow()
251 BSize
252 CharacterView::MinSize()
254 return BLayoutUtils::ComposeSize(ExplicitMinSize(),
255 BSize(fCharacterHeight, fCharacterHeight + fTitleHeight));
259 void
260 CharacterView::FrameResized(float width, float height)
262 // Scroll to character
264 if (!fHasTopCharacter)
265 return;
267 BRect frame = _FrameFor(fTopCharacter);
268 if (!frame.IsValid())
269 return;
271 BView::ScrollTo(0, frame.top - fTopOffset);
272 fHasTopCharacter = false;
276 void
277 CharacterView::MouseDown(BPoint where)
279 int32 buttons;
280 if (!fHasCharacter
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
285 fClickPoint = where;
286 return;
289 // Open pop-up menu
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);
310 void
311 CharacterView::MouseUp(BPoint where)
313 fClickPoint.x = -1;
317 void
318 CharacterView::MouseMoved(BPoint where, uint32 transit,
319 const BMessage* dragMessage)
321 if (dragMessage != NULL)
322 return;
324 BRect frame;
325 uint32 character;
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);
336 Invalidate(frame);
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)) {
345 // Start dragging
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))
350 return;
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) {
357 delete bitmap;
358 return;
360 bitmap->Lock();
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);
368 // Draw character
369 char text[16];
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,
377 fCharacterBase));
379 view->Sync();
380 bitmap->RemoveChild(view);
381 bitmap->Unlock();
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);
391 fClickPoint.x = -1;
393 fHasCharacter = false;
394 Invalidate(fCurrentCharacterFrame);
399 void
400 CharacterView::Draw(BRect updateRect)
402 const int32 kXGap = fGap / 2;
404 BFont font;
405 GetFont(&font);
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;
413 i++) {
414 if (!IsShowingBlock(i))
415 continue;
417 int32 y = fTitleTops[i];
418 if (y > updateRect.bottom)
419 break;
421 SetHighColor(color);
422 DrawString(kUnicodeBlocks[i].name, BPoint(3, y + fTitleBase));
424 y += fTitleHeight;
425 int32 x = kXGap;
426 SetFont(&fCharacterFont);
428 for (uint32 c = kUnicodeBlocks[i].start; c <= kUnicodeBlocks[i].end;
429 c++) {
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));
441 SetHighColor(color);
442 SetLowColor(highlight);
445 // Draw character
446 char character[16];
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;
457 x = kXGap;
461 if (x != kXGap)
462 y += fCharacterHeight;
463 y += fTitleGap;
465 SetFont(&font);
470 void
471 CharacterView::DoLayout()
473 fHasTopCharacter = _GetTopmostCharacter(fTopCharacter, fTopOffset);
474 _UpdateSize();
478 int32
479 CharacterView::_BlockAt(BPoint point) const
481 uint32 min = 0;
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))
488 return -1;
489 else
490 return guess;
493 if (fTitleTops[guess + 1] < point.y) {
494 min = guess + 1;
495 } else {
496 max = guess - 1;
499 guess = (max + min) / 2;
502 return -1;
506 bool
507 CharacterView::_GetCharacterAt(BPoint point, uint32& character,
508 BRect* _frame) const
510 int32 i = _BlockAt(point);
511 if (i == -1)
512 return false;
514 int32 y = fTitleTops[i] + fTitleHeight;
515 if (y > point.y)
516 return false;
518 const int32 startX = fGap / 2;
519 if (startX > point.x)
520 return false;
522 int32 endX = startX + fCharactersPerLine * (fCharacterWidth + fGap);
523 if (endX < point.x)
524 return false;
526 for (uint32 c = kUnicodeBlocks[i].start; c <= kUnicodeBlocks[i].end;
527 c += fCharactersPerLine, y += fCharacterHeight) {
528 if (y + fCharacterHeight <= point.y)
529 continue;
531 int32 pos = (int32)((point.x - startX) / (fCharacterWidth + fGap));
532 if (c + pos > kUnicodeBlocks[i].end)
533 return false;
535 // Found character at position
537 character = c + pos;
539 if (_frame != NULL) {
540 _frame->Set(startX + pos * (fCharacterWidth + fGap),
541 y, startX + (pos + 1) * (fCharacterWidth + fGap) - 1,
542 y + fCharacterHeight);
545 return true;
548 return false;
552 void
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);
575 if (fGap < 3)
576 fGap = 3;
578 fCharacterHeight += fGap;
579 fTitleGap = fGap * 3;
583 void
584 CharacterView::_UpdateSize()
586 // Compute data rect
588 BRect bounds = Bounds();
590 _UpdateFontSize();
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))
603 continue;
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)
614 return;
616 if (bounds.Height() > fDataRect.Height()) {
617 // no scrolling
618 scroller->SetRange(0.0f, 0.0f);
619 scroller->SetValue(0.0f);
620 } else {
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());
631 Invalidate();
635 bool
636 CharacterView::_GetTopmostCharacter(uint32& character, int32& offset) const
638 int32 top = (int32)Bounds().top;
640 int32 i = _BlockAt(BPoint(0, top));
641 if (i == -1)
642 return false;
644 int32 characterTop = fTitleTops[i] + fTitleHeight;
645 if (characterTop > top) {
646 character = kUnicodeBlocks[i].start;
647 offset = characterTop - top;
648 return true;
651 int32 lines = (top - characterTop + fCharacterHeight - 1)
652 / fCharacterHeight;
654 character = kUnicodeBlocks[i].start + lines * fCharactersPerLine;
655 offset = top - characterTop - lines * fCharacterHeight;
656 return true;
660 BRect
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);
675 return BRect();
679 void
680 CharacterView::_CopyToClipboard(const char* text)
682 if (!be_clipboard->Lock())
683 return;
685 be_clipboard->Clear();
687 BMessage* clip = be_clipboard->Data();
688 if (clip != NULL) {
689 clip->AddData("text/plain", B_MIME_TYPE, text, strlen(text));
690 be_clipboard->Commit();
693 be_clipboard->Unlock();