1 /* 2 * Copyright 2004-2015, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "DataView.h" 8 9 #include <stdio.h> 10 #include <stdlib.h> 11 12 #include <Autolock.h> 13 #include <Beep.h> 14 #include <Clipboard.h> 15 #include <Mime.h> 16 #include <ScrollBar.h> 17 #include <Window.h> 18 19 #include "DataEditor.h" 20 21 22 static const uint32 kBlockSize = 16; 23 // TODO: use variable spacing 24 static const uint32 kHorizontalSpace = 8; 25 static const uint32 kVerticalSpace = 4; 26 static const uint32 kPositionLength = 4; 27 28 static const uint32 kBlockSpace = 3; 29 static const uint32 kHexByteWidth = 3; 30 // these are determined by the implementation of DataView::ConvertLine() 31 32 33 /*! This function checks if the buffer contains a valid UTF-8 34 string, following the convention from the DataView::ConvertLine() 35 method: everything that's not replaced by a '.' will be accepted. 36 */ 37 bool 38 is_valid_utf8(uint8 *data, size_t size) 39 { 40 for (size_t i = 0; i < size; i++) { 41 // accept a terminating null byte 42 if (i == size - 1 && data[i] == '\0') 43 return true; 44 45 if ((data[i] & 0x80) == 0) { 46 // a single byte character 47 if ((data[i] < ' ' 48 && data[i] != 0x0d && data[i] != 0x09 && data[i] != 0x0a) 49 || data[i] == 0x7f) 50 return false; 51 52 continue; 53 } 54 55 if ((data[i] & 0xc0) == 0x80) { 56 // not a proper multibyte start 57 return false; 58 } 59 60 // start of a multibyte character 61 uint8 mask = 0x80; 62 uint32 result = (uint32)(data[i++] & 0xff); 63 64 while (result & mask) { 65 if (mask == 0x02) { 66 // seven byte char - invalid 67 return false; 68 } 69 70 result &= ~mask; 71 mask >>= 1; 72 } 73 74 while (i < size && (data[i] & 0xc0) == 0x80) { 75 result <<= 6; 76 result += data[i++] & 0x3f; 77 78 mask <<= 1; 79 if (mask == 0x40) 80 break; 81 } 82 83 if (mask != 0x40) { 84 // not enough bytes in multibyte char 85 return false; 86 } 87 } 88 89 return true; 90 } 91 92 93 // #pragma mark - 94 95 96 DataView::DataView(DataEditor &editor) 97 : BView("dataView", B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS), 98 fEditor(editor), 99 fFocus(kHexFocus), 100 fBase(kHexBase), 101 fIsActive(true), 102 fMouseSelectionStart(-1), 103 fKeySelectionStart(-1), 104 fBitPosition(0), 105 fFitFontSize(false), 106 fDragMessageSize(-1) 107 { 108 fStart = fEnd = 0; 109 110 if (fEditor.Lock()) { 111 fDataSize = fEditor.ViewSize(); 112 fOffset = fEditor.ViewOffset(); 113 fEditor.Unlock(); 114 } else 115 fDataSize = 512; 116 fData = (uint8 *)malloc(fDataSize); 117 118 SetFontSize(12.0); 119 // also sets the fixed font 120 121 UpdateFromEditor(); 122 } 123 124 125 DataView::~DataView() 126 { 127 } 128 129 130 void 131 DataView::DetachedFromWindow() 132 { 133 fEditor.StopWatching(this); 134 } 135 136 137 void 138 DataView::AttachedToWindow() 139 { 140 fEditor.StartWatching(this); 141 MakeFocus(true); 142 // this seems to be necessary - if we don't do this here, 143 // the view is sometimes focus, but IsFocus() returns false... 144 145 SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR); 146 SetLowUIColor(ViewUIColor()); 147 SetHighUIColor(B_DOCUMENT_TEXT_COLOR); 148 } 149 150 151 void 152 DataView::UpdateFromEditor(BMessage *message) 153 { 154 if (fData == NULL) 155 return; 156 157 BAutolock locker(fEditor); 158 159 fFileSize = fEditor.FileSize(); 160 161 // get the range of the changes 162 163 int32 start = 0, end = fDataSize - 1; 164 off_t offset, size; 165 if (message != NULL 166 && message->FindInt64("offset", &offset) == B_OK 167 && message->FindInt64("size", &size) == B_OK) { 168 if (offset > fOffset + (off_t)fDataSize 169 || offset + (off_t)size < fOffset) { 170 // the changes are not within our scope, so we can ignore them 171 return; 172 } 173 174 if (offset > fOffset) 175 start = offset - fOffset; 176 if (offset + (off_t)size < fOffset + (off_t)fDataSize) 177 end = offset + size - fOffset; 178 } 179 180 if (fOffset + (off_t)fDataSize > fFileSize) 181 fSizeInView = fFileSize - fOffset; 182 else 183 fSizeInView = fDataSize; 184 185 const uint8 *data; 186 if (fEditor.GetViewBuffer(&data) == B_OK) 187 // ToDo: copy only the relevant part 188 memcpy(fData, data, fDataSize); 189 190 InvalidateRange(start, end); 191 192 // we notify our selection listeners also if the 193 // data in the selection has changed 194 if (start <= fEnd && end >= fStart) { 195 BMessage update; 196 update.AddInt64("start", fStart); 197 update.AddInt64("end", fEnd); 198 SendNotices(kDataViewSelection, &update); 199 } 200 } 201 202 203 bool 204 DataView::AcceptsDrop(const BMessage *message) 205 { 206 return !fEditor.IsReadOnly() 207 && (message->HasData("text/plain", B_MIME_TYPE) 208 || message->HasData(B_FILE_MIME_TYPE, B_MIME_TYPE)); 209 } 210 211 212 void 213 DataView::MessageReceived(BMessage *message) 214 { 215 switch (message->what) { 216 case kMsgUpdateData: 217 case kMsgDataEditorUpdate: 218 UpdateFromEditor(message); 219 break; 220 221 case kMsgDataEditorParameterChange: 222 { 223 int32 viewSize; 224 off_t offset; 225 if (message->FindInt64("offset", &offset) == B_OK) { 226 fOffset = offset; 227 SetSelection(0, 0); 228 MakeVisible(0); 229 } 230 if (message->FindInt32("view_size", &viewSize) == B_OK) { 231 fDataSize = viewSize; 232 fData = (uint8 *)realloc(fData, fDataSize); 233 UpdateScroller(); 234 SendNotices(kDataViewPreferredSize); 235 } 236 if (message->FindInt64("file_size", &offset) == B_OK) 237 UpdateFromEditor(); 238 break; 239 } 240 241 case kMsgBaseType: 242 { 243 int32 type; 244 if (message->FindInt32("base", &type) != B_OK) 245 break; 246 247 SetBase((base_type)type); 248 break; 249 } 250 251 case kMsgSetSelection: 252 { 253 int64 start, end; 254 if (message->FindInt64("start", &start) != B_OK 255 || message->FindInt64("end", &end) != B_OK) 256 break; 257 258 SetSelection(start, end); 259 break; 260 } 261 262 case B_SELECT_ALL: 263 SetSelection(0, fDataSize - 1); 264 break; 265 266 case B_COPY: 267 Copy(); 268 break; 269 270 case B_PASTE: 271 Paste(); 272 break; 273 274 case B_UNDO: 275 fEditor.Undo(); 276 break; 277 278 case B_REDO: 279 fEditor.Redo(); 280 break; 281 282 case B_MIME_DATA: 283 if (AcceptsDrop(message)) { 284 const void *data; 285 ssize_t size; 286 if (message->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK 287 || message->FindData(B_FILE_MIME_TYPE, B_MIME_TYPE, &data, &size) == B_OK) { 288 if (fEditor.Replace(fOffset + fStart, (const uint8 *)data, size) != B_OK) 289 SetSelection(fStoredStart, fStoredEnd); 290 291 fDragMessageSize = -1; 292 } 293 } 294 break; 295 296 default: 297 BView::MessageReceived(message); 298 } 299 } 300 301 302 void 303 DataView::Copy() 304 { 305 if (!be_clipboard->Lock()) 306 return; 307 308 be_clipboard->Clear(); 309 310 BMessage *clip; 311 if ((clip = be_clipboard->Data()) != NULL) { 312 uint8 *data = fData + fStart; 313 size_t length = fEnd + 1 - fStart; 314 315 clip->AddData(B_FILE_MIME_TYPE, B_MIME_TYPE, data, length); 316 317 if (is_valid_utf8(data, length)) 318 clip->AddData("text/plain", B_MIME_TYPE, data, length); 319 320 be_clipboard->Commit(); 321 } 322 323 be_clipboard->Unlock(); 324 } 325 326 327 void 328 DataView::Paste() 329 { 330 if (!be_clipboard->Lock()) 331 return; 332 333 const void *data; 334 ssize_t length; 335 BMessage *clip; 336 if ((clip = be_clipboard->Data()) != NULL 337 && (clip->FindData(B_FILE_MIME_TYPE, B_MIME_TYPE, &data, &length) == B_OK 338 || clip->FindData("text/plain", B_MIME_TYPE, &data, &length) == B_OK)) { 339 // we have valid data, but it could still be too 340 // large to to fit in the file 341 if (fOffset + fStart + length > fFileSize) 342 length = fFileSize - fOffset; 343 344 if (fEditor.Replace(fOffset + fStart, (const uint8 *)data, length) == B_OK) 345 SetSelection(fStart + length, fStart + length); 346 } else 347 beep(); 348 349 be_clipboard->Unlock(); 350 } 351 352 353 void 354 DataView::ConvertLine(char *line, off_t offset, const uint8 *buffer, size_t size) 355 { 356 if (size == 0) { 357 line[0] = '\0'; 358 return; 359 } 360 361 line += sprintf(line, fBase == kHexBase ? "%0*" B_PRIxOFF": " : "%0*" 362 B_PRIdOFF": ", (int)kPositionLength, offset); 363 364 for (uint32 i = 0; i < kBlockSize; i++) { 365 if (i >= size) { 366 strcpy(line, " "); 367 line += kHexByteWidth; 368 } else 369 line += sprintf(line, "%02x ", *(unsigned char *)(buffer + i)); 370 } 371 372 strcpy(line, " "); 373 line += 3; 374 375 for (uint32 i = 0; i < kBlockSize; i++) { 376 if (i < size) { 377 char c = buffer[i]; 378 379 if (c < ' ' || c == 0x7f) 380 *line++ = '.'; 381 else 382 *line++ = c; 383 } else 384 break; 385 } 386 387 *line = '\0'; 388 } 389 390 391 void 392 DataView::Draw(BRect updateRect) 393 { 394 if (fData == NULL || fFileSize == 0) 395 return; 396 397 // ToDo: take "updateRect" into account! 398 399 char line[255]; 400 BPoint location(kHorizontalSpace, kVerticalSpace + fAscent); 401 402 for (uint32 i = 0; i < fSizeInView; i += kBlockSize) { 403 ConvertLine(line, i, fData + i, fSizeInView - i); 404 DrawString(line, location); 405 406 location.y += fFontHeight; 407 } 408 409 DrawSelection(); 410 } 411 412 413 BRect 414 DataView::DataBounds(bool inView) const 415 { 416 return BRect(0, 0, 417 fCharWidth * (kBlockSize * 4 + kPositionLength + 6) + 2 * kHorizontalSpace, 418 fFontHeight * (((inView ? fSizeInView : fDataSize) + kBlockSize - 1) / kBlockSize) 419 + 2 * kVerticalSpace); 420 } 421 422 423 int32 424 DataView::PositionAt(view_focus focus, BPoint point, view_focus *_newFocus) 425 { 426 // clip the point into our data bounds 427 428 BRect bounds = DataBounds(true); 429 if (point.x < bounds.left) 430 point.x = bounds.left; 431 else if (point.x > bounds.right) 432 point.x = bounds.right; 433 434 if (point.y < bounds.top) 435 point.y = bounds.top; 436 else if (point.y >= bounds.bottom - kVerticalSpace) 437 point.y = bounds.bottom - kVerticalSpace - 1; 438 439 float left = fCharWidth * (kPositionLength + kBlockSpace) + kHorizontalSpace; 440 float hexWidth = fCharWidth * kBlockSize * kHexByteWidth; 441 float width = fCharWidth; 442 443 if (focus == kNoFocus) { 444 // find in which part the point is in 445 if (point.x < left - width / 2) 446 return -1; 447 448 if (point.x > left + hexWidth) 449 focus = kAsciiFocus; 450 else 451 focus = kHexFocus; 452 453 if (_newFocus) 454 *_newFocus = focus; 455 } 456 if (focus == kHexFocus) { 457 left -= width / 2; 458 width *= kHexByteWidth; 459 } else 460 left += hexWidth + (kBlockSpace * width); 461 462 int32 row = int32((point.y - kVerticalSpace) / fFontHeight); 463 int32 column = int32((point.x - left) / width); 464 if (column >= (int32)kBlockSize) 465 column = (int32)kBlockSize - 1; 466 else if (column < 0) 467 column = 0; 468 469 return row * kBlockSize + column; 470 } 471 472 473 BRect 474 DataView::SelectionFrame(view_focus which, int32 start, int32 end) 475 { 476 float spacing = 0; 477 float width = fCharWidth; 478 float byteWidth = fCharWidth; 479 float left; 480 481 if (which == kHexFocus) { 482 spacing = fCharWidth / 2; 483 left = width * (kPositionLength + kBlockSpace); 484 width *= kHexByteWidth; 485 byteWidth *= 2; 486 } else 487 left = width * (kPositionLength + 2 * kBlockSpace + kHexByteWidth * kBlockSize); 488 489 left += kHorizontalSpace; 490 float startInLine = (start % kBlockSize) * width; 491 float endInLine = (end % kBlockSize) * width + byteWidth - 1; 492 493 return BRect(left + startInLine - spacing, 494 kVerticalSpace + (start / kBlockSize) * fFontHeight, 495 left + endInLine + spacing, 496 kVerticalSpace + (end / kBlockSize + 1) * fFontHeight - 1); 497 } 498 499 500 void 501 DataView::DrawSelectionFrame(view_focus which) 502 { 503 if (fFileSize == 0) 504 return; 505 506 bool drawBlock = false; 507 bool drawLastLine = false; 508 BRect block, lastLine; 509 int32 spacing = 0; 510 if (which == kAsciiFocus) 511 spacing++; 512 513 // draw first line 514 515 int32 start = fStart % kBlockSize; 516 int32 first = (fStart / kBlockSize) * kBlockSize; 517 518 int32 end = fEnd; 519 if (end > first + (int32)kBlockSize - 1) 520 end = first + kBlockSize - 1; 521 522 BRect firstLine = SelectionFrame(which, first + start, end); 523 firstLine.right += spacing; 524 first += kBlockSize; 525 526 // draw block (and last line) if necessary 527 528 end = fEnd % kBlockSize; 529 int32 last = (fEnd / kBlockSize) * kBlockSize; 530 531 if (last >= first) { 532 if (end == kBlockSize - 1) 533 last += kBlockSize; 534 if (last > first) { 535 block = SelectionFrame(which, first, last - 1); 536 block.right += spacing; 537 drawBlock = true; 538 } 539 if (end != kBlockSize - 1) { 540 lastLine = SelectionFrame(which, last, last + end); 541 lastLine.right += spacing; 542 drawLastLine = true; 543 } 544 } 545 546 SetDrawingMode(B_OP_INVERT); 547 BeginLineArray(8); 548 549 // +******* 550 // | * 551 // +------+ 552 553 const rgb_color color = {0, 0, 0}; 554 float bottom; 555 if (drawBlock) 556 bottom = block.bottom; 557 else 558 bottom = firstLine.bottom; 559 560 AddLine(BPoint(firstLine.left + 1, firstLine.top), firstLine.RightTop(), color); 561 AddLine(BPoint(firstLine.right, firstLine.top + 1), BPoint(firstLine.right, bottom), color); 562 563 // *-------+ 564 // * | 565 // ********* 566 567 BRect rect; 568 if (start == 0 || (!drawBlock && !drawLastLine)) 569 rect = firstLine; 570 else if (drawBlock) 571 rect = block; 572 else 573 rect = lastLine; 574 575 if (drawBlock) 576 rect.bottom = block.bottom; 577 if (drawLastLine) { 578 rect.bottom = lastLine.bottom; 579 rect.right = lastLine.right; 580 } 581 rect.bottom++; 582 583 AddLine(rect.LeftTop(), rect.LeftBottom(), color); 584 AddLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom(), color); 585 586 // *--------+ 587 // * | 588 // +**** | 589 // | | 590 591 if (start && (drawLastLine || drawBlock)) { 592 AddLine(firstLine.LeftTop(), firstLine.LeftBottom(), color); 593 594 float right = firstLine.left; 595 if (!drawBlock && right > lastLine.right) 596 right = lastLine.right; 597 AddLine(BPoint(rect.left + 1, rect.top), BPoint(right, rect.top), color); 598 } 599 600 // | | 601 // | ***** 602 // | * 603 // +--------+ 604 605 if (drawLastLine) { 606 AddLine(lastLine.RightBottom(), BPoint(lastLine.right, lastLine.top + 1), color); 607 if (!drawBlock && lastLine.right <= firstLine.left) 608 lastLine.right = firstLine.left + (lastLine.right < firstLine.left ? 0 : 1); 609 AddLine(BPoint(lastLine.right, lastLine.top), BPoint(firstLine.right, lastLine.top), color); 610 } 611 612 EndLineArray(); 613 SetDrawingMode(B_OP_COPY); 614 } 615 616 617 void 618 DataView::DrawSelectionBlock(view_focus which, int32 blockStart, int32 blockEnd) 619 { 620 if (fFileSize == 0) 621 return; 622 623 // draw first line 624 625 SetDrawingMode(B_OP_INVERT); 626 627 int32 start = blockStart % kBlockSize; 628 int32 first = (blockStart / kBlockSize) * kBlockSize; 629 630 int32 end = blockEnd; 631 if (end > first + (int32)kBlockSize - 1) 632 end = first + kBlockSize - 1; 633 634 FillRect(SelectionFrame(which, first + start, end)); 635 first += kBlockSize; 636 637 // draw block (and last line) if necessary 638 639 end = blockEnd % kBlockSize; 640 int32 last = (blockEnd / kBlockSize) * kBlockSize; 641 642 if (last >= first) { 643 if (end == kBlockSize - 1) 644 last += kBlockSize; 645 646 if (last > first) 647 FillRect(SelectionFrame(which, first, last - 1)); 648 if (end != kBlockSize - 1) 649 FillRect(SelectionFrame(which, last, last + end)); 650 } 651 652 SetDrawingMode(B_OP_COPY); 653 } 654 655 656 void 657 DataView::DrawSelectionBlock(view_focus which) 658 { 659 DrawSelectionBlock(which, fStart, fEnd); 660 } 661 662 663 void 664 DataView::DrawSelection(bool frameOnly) 665 { 666 if (IsFocus() && fIsActive) { 667 if (!frameOnly) 668 DrawSelectionBlock(fFocus); 669 DrawSelectionFrame(fFocus == kHexFocus ? kAsciiFocus : kHexFocus); 670 } else { 671 DrawSelectionFrame(kHexFocus); 672 DrawSelectionFrame(kAsciiFocus); 673 } 674 } 675 676 677 void 678 DataView::SetSelection(int32 start, int32 end, view_focus focus) 679 { 680 // correct the values if necessary 681 682 if (start > end) { 683 int32 temp = start; 684 start = end; 685 end = temp; 686 } 687 688 if (start > (int32)fSizeInView - 1) 689 start = (int32)fSizeInView - 1; 690 if (start < 0) 691 start = 0; 692 693 if (end > (int32)fSizeInView - 1) 694 end = (int32)fSizeInView - 1; 695 if (end < 0) 696 end = 0; 697 698 if (fStart == start && fEnd == end) { 699 // nothing has changed, no need to update 700 return; 701 } 702 703 // notify our listeners 704 if (fStart != start) { 705 BMessage update; 706 update.AddInt64("position", start); 707 SendNotices(kDataViewCursorPosition, &update); 708 } 709 710 BMessage update; 711 update.AddInt64("start", start); 712 update.AddInt64("end", end); 713 SendNotices(kDataViewSelection, &update); 714 715 // Update selection - first, we need to remove the old selection, then 716 // we redraw the selection with the current values. 717 718 DrawSelection(focus == kNoFocus); 719 // From the block selection, only the parts that need updating are 720 // actually updated, if there is no focus change. 721 722 if (IsFocus() && fIsActive && focus == kNoFocus) { 723 // Update the selection block incrementally 724 725 if (start > fStart) { 726 // remove from the top 727 DrawSelectionBlock(fFocus, fStart, start - 1); 728 } else if (start < fStart) { 729 // add to the top 730 DrawSelectionBlock(fFocus, start, fStart - 1); 731 } 732 733 if (end < fEnd) { 734 // remove from bottom 735 DrawSelectionBlock(fFocus, end + 1, fEnd); 736 } else if (end > fEnd) { 737 // add to the bottom 738 DrawSelectionBlock(fFocus, fEnd + 1, end); 739 } 740 } 741 742 if (focus != kNoFocus) 743 fFocus = focus; 744 fStart = start; 745 fEnd = end; 746 747 DrawSelection(focus == kNoFocus); 748 749 fBitPosition = 0; 750 } 751 752 753 void 754 DataView::GetSelection(int32 &start, int32 &end) 755 { 756 start = fStart; 757 end = fEnd; 758 } 759 760 761 void 762 DataView::InvalidateRange(int32 start, int32 end) 763 { 764 if (start <= 0 && end >= int32(fDataSize) - 1) { 765 Invalidate(); 766 return; 767 } 768 769 int32 startLine = start / kBlockSize; 770 int32 endLine = end / kBlockSize; 771 772 if (endLine > startLine) { 773 start = startLine * kBlockSize; 774 end = (endLine + 1) * kBlockSize - 1; 775 } 776 777 // the part with focus 778 BRect rect = SelectionFrame(fFocus, start, end); 779 rect.bottom++; 780 rect.right++; 781 Invalidate(rect); 782 783 // the part without focus 784 rect = SelectionFrame(fFocus == kHexFocus ? kAsciiFocus : kHexFocus, start, end); 785 rect.bottom++; 786 rect.right++; 787 Invalidate(rect); 788 } 789 790 791 void 792 DataView::MakeVisible(int32 position) 793 { 794 if (position < 0 || position > int32(fDataSize) - 1) 795 return; 796 797 BRect frame = SelectionFrame(fFocus, position, position); 798 BRect bounds = Bounds(); 799 if (bounds.Contains(frame)) 800 return; 801 802 // special case the first and the last line and column, so that 803 // we can take kHorizontalSpace & kVerticalSpace into account 804 805 if ((position % kBlockSize) == 0) 806 frame.left -= kHorizontalSpace; 807 else if ((position % kBlockSize) == kBlockSize - 1) 808 frame.right += kHorizontalSpace; 809 810 if (position < int32(kBlockSize)) 811 frame.top -= kVerticalSpace; 812 else if (position > int32(fDataSize - kBlockSize)) 813 frame.bottom += kVerticalSpace; 814 815 // compute the scroll point 816 817 BPoint point = bounds.LeftTop(); 818 if (bounds.left > frame.left) 819 point.x = frame.left; 820 else if (bounds.right < frame.right) 821 point.x = frame.right - bounds.Width(); 822 823 if (bounds.top > frame.top) 824 point.y = frame.top; 825 else if (bounds.bottom < frame.bottom) 826 point.y = frame.bottom - bounds.Height(); 827 828 ScrollTo(point); 829 } 830 831 832 const uint8 * 833 DataView::DataAt(int32 start) 834 { 835 if (start < 0 || start >= int32(fSizeInView) || fData == NULL) 836 return NULL; 837 838 return fData + start; 839 } 840 841 842 /*static*/ int32 843 DataView::WidthForFontSize(float size) 844 { 845 BFont font = be_fixed_font; 846 font.SetSize(size); 847 848 float charWidth = font.StringWidth("w"); 849 return (int32)ceilf(charWidth * (kBlockSize * 4 + kPositionLength + 6) 850 + 2 * kHorizontalSpace); 851 } 852 853 854 void 855 DataView::SetBase(base_type type) 856 { 857 if (fBase == type) 858 return; 859 860 fBase = type; 861 Invalidate(); 862 } 863 864 865 void 866 DataView::SetFocus(view_focus which) 867 { 868 if (which == fFocus) 869 return; 870 871 DrawSelection(); 872 fFocus = which; 873 DrawSelection(); 874 } 875 876 877 void 878 DataView::SetActive(bool active) 879 { 880 if (active == fIsActive) 881 return; 882 883 fIsActive = active; 884 885 // only redraw the focussed part 886 887 if (IsFocus() && active) { 888 DrawSelectionFrame(fFocus); 889 DrawSelectionBlock(fFocus); 890 } else { 891 DrawSelectionBlock(fFocus); 892 DrawSelectionFrame(fFocus); 893 } 894 } 895 896 897 void 898 DataView::WindowActivated(bool active) 899 { 900 BView::WindowActivated(active); 901 SetActive(active); 902 } 903 904 905 void 906 DataView::MakeFocus(bool focus) 907 { 908 bool previous = IsFocus(); 909 BView::MakeFocus(focus); 910 911 if (focus == previous) 912 return; 913 914 if (Window()->IsActive() && focus) 915 SetActive(true); 916 else if (!Window()->IsActive() || !focus) 917 SetActive(false); 918 } 919 920 921 void 922 DataView::UpdateScroller() 923 { 924 float width, height; 925 GetPreferredSize(&width, &height); 926 927 SetExplicitMinSize(BSize(250, 200)); 928 SetExplicitMaxSize(BSize(B_SIZE_UNSET, height)); 929 SetExplicitPreferredSize(BSize(width, height)); 930 931 BScrollBar *bar; 932 if ((bar = ScrollBar(B_HORIZONTAL)) != NULL) { 933 float delta = width - Bounds().Width(); 934 if (delta < 0) 935 delta = 0; 936 937 bar->SetRange(0, delta); 938 bar->SetSteps(fCharWidth, Bounds().Width()); 939 bar->SetProportion(Bounds().Width() / width); 940 } 941 if ((bar = ScrollBar(B_VERTICAL)) != NULL) { 942 float delta = height - Bounds().Height(); 943 if (delta < 0) 944 delta = 0; 945 946 bar->SetRange(0, delta); 947 bar->SetSteps(fFontHeight, Bounds().Height()); 948 bar->SetProportion(Bounds().Height() / height); 949 } 950 } 951 952 953 void 954 DataView::FrameResized(float width, float height) 955 { 956 if (fFitFontSize) { 957 // adapt the font size to fit in the view's bounds 958 float oldSize = FontSize(); 959 float steps = 0.5f; 960 961 float size; 962 for (size = 1.f; size < 100; size += steps) { 963 int32 preferredWidth = WidthForFontSize(size); 964 if (preferredWidth > width) 965 break; 966 967 if (size > 6) 968 steps = 1.0f; 969 } 970 size -= steps; 971 972 if (oldSize != size) { 973 BFont font = be_fixed_font; 974 font.SetSize(size); 975 SetFont(&font); 976 977 Invalidate(); 978 } 979 } 980 981 UpdateScroller(); 982 } 983 984 985 void 986 DataView::InitiateDrag(view_focus focus) 987 { 988 BMessage *drag = new BMessage(B_MIME_DATA); 989 990 // Add originator and action 991 drag->AddPointer("be:originator", this); 992 //drag->AddString("be:clip_name", "Byte Clipping"); 993 //drag->AddInt32("be_actions", B_TRASH_TARGET); 994 995 // Add data (just like in Copy()) 996 uint8 *data = fData + fStart; 997 size_t length = fEnd + 1 - fStart; 998 999 drag->AddData(B_FILE_MIME_TYPE, B_MIME_TYPE, data, length); 1000 if (is_valid_utf8(data, length)) 1001 drag->AddData("text/plain", B_MIME_TYPE, data, length); 1002 1003 // get a frame that contains the whole selection - SelectionFrame() 1004 // only spans a rectangle between the start and the end point, so 1005 // we have to pass it the correct input values 1006 1007 BRect frame; 1008 const int32 width = kBlockSize - 1; 1009 int32 first = fStart & ~width; 1010 int32 last = ((fEnd + width) & ~width) - 1; 1011 if (first == (last & ~width)) 1012 frame = SelectionFrame(focus, fStart, fEnd); 1013 else 1014 frame = SelectionFrame(focus, first, last); 1015 1016 BRect bounds = Bounds(); 1017 if (!bounds.Contains(frame)) 1018 frame = bounds & frame; 1019 1020 DragMessage(drag, frame, NULL); 1021 1022 fStoredStart = fStart; 1023 fStoredEnd = fEnd; 1024 fDragMessageSize = length; 1025 } 1026 1027 1028 void 1029 DataView::MouseDown(BPoint where) 1030 { 1031 MakeFocus(true); 1032 1033 BMessage *message = Looper()->CurrentMessage(); 1034 int32 buttons; 1035 if (message == NULL || message->FindInt32("buttons", &buttons) != B_OK) 1036 return; 1037 1038 view_focus newFocus; 1039 int32 position = PositionAt(kNoFocus, where, &newFocus); 1040 1041 if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0 1042 && position >= fStart && position <= fEnd) { 1043 InitiateDrag(newFocus); 1044 return; 1045 } 1046 1047 if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0) 1048 return; 1049 1050 int32 modifiers = message->FindInt32("modifiers"); 1051 1052 fMouseSelectionStart = position; 1053 if (fMouseSelectionStart == -1) { 1054 // "where" is outside the valid frame 1055 return; 1056 } 1057 1058 int32 selectionEnd = fMouseSelectionStart; 1059 if (modifiers & B_SHIFT_KEY) { 1060 // enlarge the current selection 1061 if (fStart < selectionEnd) 1062 fMouseSelectionStart = fStart; 1063 else if (fEnd > selectionEnd) 1064 fMouseSelectionStart = fEnd; 1065 } 1066 SetSelection(fMouseSelectionStart, selectionEnd, newFocus); 1067 1068 SetMouseEventMask(B_POINTER_EVENTS, 1069 B_NO_POINTER_HISTORY | B_SUSPEND_VIEW_FOCUS | B_LOCK_WINDOW_FOCUS); 1070 } 1071 1072 1073 void 1074 DataView::MouseMoved(BPoint where, uint32 transit, const BMessage *dragMessage) 1075 { 1076 if (transit == B_EXITED_VIEW && fDragMessageSize > 0) { 1077 SetSelection(fStoredStart, fStoredEnd); 1078 fDragMessageSize = -1; 1079 } 1080 1081 if (dragMessage && AcceptsDrop(dragMessage)) { 1082 // handle drag message and tracking 1083 1084 if (transit == B_ENTERED_VIEW) { 1085 fStoredStart = fStart; 1086 fStoredEnd = fEnd; 1087 1088 const void *data; 1089 ssize_t size; 1090 if (dragMessage->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK 1091 || dragMessage->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK) 1092 fDragMessageSize = size; 1093 } else if (fDragMessageSize > 0) { 1094 view_focus newFocus; 1095 int32 start = PositionAt(kNoFocus, where, &newFocus); 1096 int32 end = start + fDragMessageSize - 1; 1097 1098 SetSelection(start, end); 1099 MakeVisible(start); 1100 } 1101 return; 1102 } 1103 1104 if (fMouseSelectionStart == -1) 1105 return; 1106 1107 int32 end = PositionAt(fFocus, where); 1108 if (end == -1) 1109 return; 1110 1111 SetSelection(fMouseSelectionStart, end); 1112 MakeVisible(end); 1113 } 1114 1115 1116 void 1117 DataView::MouseUp(BPoint where) 1118 { 1119 fMouseSelectionStart = fKeySelectionStart = -1; 1120 } 1121 1122 1123 void 1124 DataView::KeyDown(const char *bytes, int32 numBytes) 1125 { 1126 int32 modifiers; 1127 if (Looper()->CurrentMessage() == NULL 1128 || Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers) != B_OK) 1129 modifiers = ::modifiers(); 1130 1131 // check if the selection is going to be changed 1132 switch (bytes[0]) { 1133 case B_LEFT_ARROW: 1134 case B_RIGHT_ARROW: 1135 case B_UP_ARROW: 1136 case B_DOWN_ARROW: 1137 if (modifiers & B_SHIFT_KEY) { 1138 if (fKeySelectionStart == -1) 1139 fKeySelectionStart = fStart; 1140 } else 1141 fKeySelectionStart = -1; 1142 break; 1143 } 1144 1145 switch (bytes[0]) { 1146 case B_LEFT_ARROW: 1147 { 1148 int32 position = fStart - 1; 1149 1150 if (modifiers & B_SHIFT_KEY) { 1151 if (fKeySelectionStart == fEnd) 1152 SetSelection(fStart - 1, fEnd); 1153 else { 1154 SetSelection(fStart, fEnd - 1); 1155 position = fEnd; 1156 } 1157 } else 1158 SetSelection(fStart - 1, fStart - 1); 1159 1160 MakeVisible(position); 1161 break; 1162 } 1163 case B_RIGHT_ARROW: 1164 { 1165 int32 position = fEnd + 1; 1166 1167 if (modifiers & B_SHIFT_KEY) { 1168 if (fKeySelectionStart == fStart) 1169 SetSelection(fStart, fEnd + 1); 1170 else 1171 SetSelection(fStart + 1, fEnd); 1172 } else 1173 SetSelection(fEnd + 1, fEnd + 1); 1174 1175 MakeVisible(position); 1176 break; 1177 } 1178 case B_UP_ARROW: 1179 { 1180 int32 start, end; 1181 if (modifiers & B_SHIFT_KEY) { 1182 if (fKeySelectionStart == fStart) { 1183 start = fEnd - int32(kBlockSize); 1184 end = fStart; 1185 } else { 1186 start = fStart - int32(kBlockSize); 1187 end = fEnd; 1188 } 1189 if (start < 0) 1190 start = 0; 1191 } else { 1192 start = fStart - int32(kBlockSize); 1193 if (start < 0) 1194 start = fStart; 1195 1196 end = start; 1197 } 1198 1199 SetSelection(start, end); 1200 MakeVisible(start); 1201 break; 1202 } 1203 case B_DOWN_ARROW: 1204 { 1205 int32 start, end; 1206 if (modifiers & B_SHIFT_KEY) { 1207 if (fKeySelectionStart == fEnd) { 1208 start = fEnd; 1209 end = fStart + int32(kBlockSize); 1210 } else { 1211 start = fStart; 1212 end = fEnd + int32(kBlockSize); 1213 } 1214 if (end >= int32(fSizeInView)) 1215 end = int32(fSizeInView) - 1; 1216 } else { 1217 end = fEnd + int32(kBlockSize); 1218 if (end >= int32(fSizeInView)) 1219 start = fEnd; 1220 1221 start = end; 1222 } 1223 1224 SetSelection(start, end); 1225 MakeVisible(end); 1226 break; 1227 } 1228 1229 case B_PAGE_UP: 1230 { 1231 // scroll one page up, but keep the same cursor column 1232 1233 BRect frame = SelectionFrame(fFocus, fStart, fStart); 1234 frame.OffsetBy(0, -Bounds().Height()); 1235 if (frame.top <= kVerticalSpace) 1236 frame.top = kVerticalSpace + 1; 1237 ScrollBy(0, -Bounds().Height()); 1238 1239 int32 position = PositionAt(fFocus, frame.LeftTop()); 1240 SetSelection(position, position); 1241 break; 1242 } 1243 case B_PAGE_DOWN: 1244 { 1245 // scroll one page down, but keep the same cursor column 1246 1247 BRect frame = SelectionFrame(fFocus, fStart, fStart); 1248 frame.OffsetBy(0, Bounds().Height()); 1249 1250 float lastLine = DataBounds().Height() - 1 - kVerticalSpace; 1251 if (frame.top > lastLine) 1252 frame.top = lastLine; 1253 ScrollBy(0, Bounds().Height()); 1254 1255 int32 position = PositionAt(fFocus, frame.LeftTop()); 1256 SetSelection(position, position); 1257 break; 1258 } 1259 case B_HOME: 1260 SetSelection(0, 0); 1261 MakeVisible(fStart); 1262 break; 1263 case B_END: 1264 SetSelection(fDataSize - 1, fDataSize - 1); 1265 MakeVisible(fStart); 1266 break; 1267 case B_TAB: 1268 SetFocus(fFocus == kHexFocus ? kAsciiFocus : kHexFocus); 1269 MakeVisible(fStart); 1270 break; 1271 1272 case B_FUNCTION_KEY: 1273 // this is ignored 1274 break; 1275 1276 case B_BACKSPACE: 1277 if (fBitPosition == 0) 1278 SetSelection(fStart - 1, fStart - 1); 1279 1280 if (fFocus == kHexFocus) 1281 fBitPosition = (fBitPosition + 4) % 8; 1282 1283 // supposed to fall through 1284 case B_DELETE: 1285 SetSelection(fStart, fStart); 1286 // to make sure only the cursor is selected 1287 1288 if (fFocus == kHexFocus) { 1289 const uint8 *data = DataAt(fStart); 1290 if (data == NULL) 1291 break; 1292 1293 uint8 c = data[0] & (fBitPosition == 0 ? 0x0f : 0xf0); 1294 // mask out region to be cleared 1295 1296 fEditor.Replace(fOffset + fStart, &c, 1); 1297 } else 1298 fEditor.Replace(fOffset + fStart, (const uint8 *)"", 1); 1299 break; 1300 1301 default: 1302 if (fFocus == kHexFocus) { 1303 // only hexadecimal characters are allowed to be entered 1304 const uint8 *data = DataAt(fStart); 1305 uint8 c = bytes[0]; 1306 if (c >= 'A' && c <= 'F') 1307 c += 'A' - 'a'; 1308 const char *hexNumbers = "0123456789abcdef"; 1309 addr_t number; 1310 if (data == NULL 1311 || (number = (addr_t)strchr(hexNumbers, c)) == 0) 1312 break; 1313 1314 SetSelection(fStart, fStart); 1315 // to make sure only the cursor is selected 1316 1317 number -= (addr_t)hexNumbers; 1318 fBitPosition = (fBitPosition + 4) % 8; 1319 1320 c = (data[0] & (fBitPosition ? 0x0f : 0xf0)) 1321 | (number << fBitPosition); 1322 // mask out overwritten region and bit-wise or the number 1323 // to be inserted 1324 1325 if (fEditor.Replace(fOffset + fStart, &c, 1) == B_OK 1326 && fBitPosition == 0) 1327 SetSelection(fStart + 1, fStart + 1); 1328 } else { 1329 if (fEditor.Replace(fOffset + fStart, (const uint8 *)bytes, 1330 numBytes) == B_OK) 1331 SetSelection(fStart + 1, fStart + 1); 1332 } 1333 break; 1334 } 1335 } 1336 1337 1338 void 1339 DataView::SetFont(const BFont *font, uint32 properties) 1340 { 1341 // Even in a full and hal fixed font, the characters we use (all in the 1342 // Latin-1 range as everything else is filtered out) will all have the same 1343 // width. 1344 if (!font->IsFixed() && !font->IsFullAndHalfFixed()) 1345 return; 1346 1347 BView::SetFont(font, properties); 1348 1349 font_height fontHeight; 1350 font->GetHeight(&fontHeight); 1351 1352 fFontHeight = int32(fontHeight.ascent + fontHeight.descent 1353 + fontHeight.leading); 1354 fAscent = fontHeight.ascent; 1355 fCharWidth = font->StringWidth("w"); 1356 } 1357 1358 1359 float 1360 DataView::FontSize() const 1361 { 1362 BFont font; 1363 GetFont(&font); 1364 1365 return font.Size(); 1366 } 1367 1368 1369 void 1370 DataView::SetFontSize(float point) 1371 { 1372 bool fit = (point == 0.0f); 1373 if (fit) { 1374 if (!fFitFontSize) 1375 SendNotices(kDataViewPreferredSize); 1376 fFitFontSize = fit; 1377 1378 FrameResized(Bounds().Width(), Bounds().Height()); 1379 return; 1380 } 1381 1382 fFitFontSize = false; 1383 1384 BFont font = be_fixed_font; 1385 font.SetSize(point); 1386 1387 SetFont(&font); 1388 UpdateScroller(); 1389 Invalidate(); 1390 1391 SendNotices(kDataViewPreferredSize); 1392 } 1393 1394 1395 void 1396 DataView::GetPreferredSize(float *_width, float *_height) 1397 { 1398 BRect bounds = DataBounds(); 1399 1400 if (_width) 1401 *_width = bounds.Width(); 1402 1403 if (_height) 1404 *_height = bounds.Height(); 1405 } 1406 1407