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 const float kTintDarkenTouch = (B_NO_TINT + B_DARKEN_1_TINT) / 2.0f; 402 403 for (uint32 lineNum = 0, i = 0; i < fSizeInView; lineNum++, i += kBlockSize) { 404 // Tint every 2nd line to create a striped background 405 406 float tint = (lineNum % 2 == 0) ? B_NO_TINT : kTintDarkenTouch; 407 SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR, tint); 408 409 FillRect(BRect(location.x - kHorizontalSpace, 410 kVerticalSpace + lineNum * fFontHeight, 411 location.x - kHorizontalSpace / 2 + Bounds().right, 412 kVerticalSpace + (lineNum + 1) * fFontHeight), B_SOLID_LOW); 413 414 ConvertLine(line, i, fData + i, fSizeInView - i); 415 DrawString(line, location); 416 417 location.y += fFontHeight; 418 } 419 420 // Draw first vertical line after 18 chars ("0000: xx xx xx xx") 421 // Next three vertical lines require an offset of 12 chars ("xx xx xx xx") 422 423 BPoint line_start(kHorizontalSpace + fCharWidth * 18 + fCharWidth / 2, 0); 424 BPoint line_end(line_start.x, Bounds().bottom); 425 426 rgb_color lineColor = tint_color(lineColor, B_LIGHTEN_2_TINT); 427 uint32 kDrawNumLines = 3; 428 429 BeginLineArray(kDrawNumLines); 430 for (uint32 i = 0; i < kDrawNumLines; i++) { 431 AddLine(line_start, line_end, lineColor); 432 line_start.x += fCharWidth * 12; 433 line_end.x = line_start.x; 434 } 435 EndLineArray(); 436 437 DrawSelection(); 438 } 439 440 441 BRect 442 DataView::DataBounds(bool inView) const 443 { 444 return BRect(0, 0, 445 fCharWidth * (kBlockSize * 4 + kPositionLength + 6) + 2 * kHorizontalSpace, 446 fFontHeight * (((inView ? fSizeInView : fDataSize) + kBlockSize - 1) / kBlockSize) 447 + 2 * kVerticalSpace); 448 } 449 450 451 int32 452 DataView::PositionAt(view_focus focus, BPoint point, view_focus *_newFocus) 453 { 454 // clip the point into our data bounds 455 456 BRect bounds = DataBounds(true); 457 if (point.x < bounds.left) 458 point.x = bounds.left; 459 else if (point.x > bounds.right) 460 point.x = bounds.right; 461 462 if (point.y < bounds.top) 463 point.y = bounds.top; 464 else if (point.y >= bounds.bottom - kVerticalSpace) 465 point.y = bounds.bottom - kVerticalSpace - 1; 466 467 float left = fCharWidth * (kPositionLength + kBlockSpace) + kHorizontalSpace; 468 float hexWidth = fCharWidth * kBlockSize * kHexByteWidth; 469 float width = fCharWidth; 470 471 if (focus == kNoFocus) { 472 // find in which part the point is in 473 if (point.x < left - width / 2) 474 return -1; 475 476 if (point.x > left + hexWidth) 477 focus = kAsciiFocus; 478 else 479 focus = kHexFocus; 480 481 if (_newFocus) 482 *_newFocus = focus; 483 } 484 if (focus == kHexFocus) { 485 left -= width / 2; 486 width *= kHexByteWidth; 487 } else 488 left += hexWidth + (kBlockSpace * width); 489 490 int32 row = int32((point.y - kVerticalSpace) / fFontHeight); 491 int32 column = int32((point.x - left) / width); 492 if (column >= (int32)kBlockSize) 493 column = (int32)kBlockSize - 1; 494 else if (column < 0) 495 column = 0; 496 497 return row * kBlockSize + column; 498 } 499 500 501 BRect 502 DataView::SelectionFrame(view_focus which, int32 start, int32 end) 503 { 504 float spacing = 0; 505 float width = fCharWidth; 506 float byteWidth = fCharWidth; 507 float left; 508 509 if (which == kHexFocus) { 510 spacing = fCharWidth / 2; 511 left = width * (kPositionLength + kBlockSpace); 512 width *= kHexByteWidth; 513 byteWidth *= 2; 514 } else 515 left = width * (kPositionLength + 2 * kBlockSpace + kHexByteWidth * kBlockSize); 516 517 left += kHorizontalSpace; 518 float startInLine = (start % kBlockSize) * width; 519 float endInLine = (end % kBlockSize) * width + byteWidth - 1; 520 521 return BRect(left + startInLine - spacing, 522 kVerticalSpace + (start / kBlockSize) * fFontHeight, 523 left + endInLine + spacing, 524 kVerticalSpace + (end / kBlockSize + 1) * fFontHeight - 1); 525 } 526 527 528 void 529 DataView::DrawSelectionFrame(view_focus which) 530 { 531 if (fFileSize == 0) 532 return; 533 534 bool drawBlock = false; 535 bool drawLastLine = false; 536 BRect block, lastLine; 537 int32 spacing = 0; 538 if (which == kAsciiFocus) 539 spacing++; 540 541 // draw first line 542 543 int32 start = fStart % kBlockSize; 544 int32 first = (fStart / kBlockSize) * kBlockSize; 545 546 int32 end = fEnd; 547 if (end > first + (int32)kBlockSize - 1) 548 end = first + kBlockSize - 1; 549 550 BRect firstLine = SelectionFrame(which, first + start, end); 551 firstLine.right += spacing; 552 first += kBlockSize; 553 554 // draw block (and last line) if necessary 555 556 end = fEnd % kBlockSize; 557 int32 last = (fEnd / kBlockSize) * kBlockSize; 558 559 if (last >= first) { 560 if (end == kBlockSize - 1) 561 last += kBlockSize; 562 if (last > first) { 563 block = SelectionFrame(which, first, last - 1); 564 block.right += spacing; 565 drawBlock = true; 566 } 567 if (end != kBlockSize - 1) { 568 lastLine = SelectionFrame(which, last, last + end); 569 lastLine.right += spacing; 570 drawLastLine = true; 571 } 572 } 573 574 SetDrawingMode(B_OP_INVERT); 575 BeginLineArray(8); 576 577 // +******* 578 // | * 579 // +------+ 580 581 const rgb_color color = {0, 0, 0}; 582 float bottom; 583 if (drawBlock) 584 bottom = block.bottom; 585 else 586 bottom = firstLine.bottom; 587 588 AddLine(BPoint(firstLine.left + 1, firstLine.top), firstLine.RightTop(), color); 589 AddLine(BPoint(firstLine.right, firstLine.top + 1), BPoint(firstLine.right, bottom), color); 590 591 // *-------+ 592 // * | 593 // ********* 594 595 BRect rect; 596 if (start == 0 || (!drawBlock && !drawLastLine)) 597 rect = firstLine; 598 else if (drawBlock) 599 rect = block; 600 else 601 rect = lastLine; 602 603 if (drawBlock) 604 rect.bottom = block.bottom; 605 if (drawLastLine) { 606 rect.bottom = lastLine.bottom; 607 rect.right = lastLine.right; 608 } 609 rect.bottom++; 610 611 AddLine(rect.LeftTop(), rect.LeftBottom(), color); 612 AddLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom(), color); 613 614 // *--------+ 615 // * | 616 // +**** | 617 // | | 618 619 if (start && (drawLastLine || drawBlock)) { 620 AddLine(firstLine.LeftTop(), firstLine.LeftBottom(), color); 621 622 float right = firstLine.left; 623 if (!drawBlock && right > lastLine.right) 624 right = lastLine.right; 625 AddLine(BPoint(rect.left + 1, rect.top), BPoint(right, rect.top), color); 626 } 627 628 // | | 629 // | ***** 630 // | * 631 // +--------+ 632 633 if (drawLastLine) { 634 AddLine(lastLine.RightBottom(), BPoint(lastLine.right, lastLine.top + 1), color); 635 if (!drawBlock && lastLine.right <= firstLine.left) 636 lastLine.right = firstLine.left + (lastLine.right < firstLine.left ? 0 : 1); 637 AddLine(BPoint(lastLine.right, lastLine.top), BPoint(firstLine.right, lastLine.top), color); 638 } 639 640 EndLineArray(); 641 SetDrawingMode(B_OP_COPY); 642 } 643 644 645 void 646 DataView::DrawSelectionBlock(view_focus which, int32 blockStart, int32 blockEnd) 647 { 648 if (fFileSize == 0) 649 return; 650 651 // draw first line 652 653 SetDrawingMode(B_OP_INVERT); 654 655 int32 start = blockStart % kBlockSize; 656 int32 first = (blockStart / kBlockSize) * kBlockSize; 657 658 int32 end = blockEnd; 659 if (end > first + (int32)kBlockSize - 1) 660 end = first + kBlockSize - 1; 661 662 FillRect(SelectionFrame(which, first + start, end)); 663 first += kBlockSize; 664 665 // draw block (and last line) if necessary 666 667 end = blockEnd % kBlockSize; 668 int32 last = (blockEnd / kBlockSize) * kBlockSize; 669 670 if (last >= first) { 671 if (end == kBlockSize - 1) 672 last += kBlockSize; 673 674 if (last > first) 675 FillRect(SelectionFrame(which, first, last - 1)); 676 if (end != kBlockSize - 1) 677 FillRect(SelectionFrame(which, last, last + end)); 678 } 679 680 SetDrawingMode(B_OP_COPY); 681 } 682 683 684 void 685 DataView::DrawSelectionBlock(view_focus which) 686 { 687 DrawSelectionBlock(which, fStart, fEnd); 688 } 689 690 691 void 692 DataView::DrawSelection(bool frameOnly) 693 { 694 if (IsFocus() && fIsActive) { 695 if (!frameOnly) 696 DrawSelectionBlock(fFocus); 697 DrawSelectionFrame(fFocus == kHexFocus ? kAsciiFocus : kHexFocus); 698 } else { 699 DrawSelectionFrame(kHexFocus); 700 DrawSelectionFrame(kAsciiFocus); 701 } 702 } 703 704 705 void 706 DataView::SetSelection(int32 start, int32 end, view_focus focus) 707 { 708 // correct the values if necessary 709 710 if (start > end) { 711 int32 temp = start; 712 start = end; 713 end = temp; 714 } 715 716 if (start > (int32)fSizeInView - 1) 717 start = (int32)fSizeInView - 1; 718 if (start < 0) 719 start = 0; 720 721 if (end > (int32)fSizeInView - 1) 722 end = (int32)fSizeInView - 1; 723 if (end < 0) 724 end = 0; 725 726 if (fStart == start && fEnd == end) { 727 // nothing has changed, no need to update 728 return; 729 } 730 731 // notify our listeners 732 if (fStart != start) { 733 BMessage update; 734 update.AddInt64("position", start); 735 SendNotices(kDataViewCursorPosition, &update); 736 } 737 738 BMessage update; 739 update.AddInt64("start", start); 740 update.AddInt64("end", end); 741 SendNotices(kDataViewSelection, &update); 742 743 // Update selection - first, we need to remove the old selection, then 744 // we redraw the selection with the current values. 745 746 DrawSelection(focus == kNoFocus); 747 // From the block selection, only the parts that need updating are 748 // actually updated, if there is no focus change. 749 750 if (IsFocus() && fIsActive && focus == kNoFocus) { 751 // Update the selection block incrementally 752 753 if (start > fStart) { 754 // remove from the top 755 DrawSelectionBlock(fFocus, fStart, start - 1); 756 } else if (start < fStart) { 757 // add to the top 758 DrawSelectionBlock(fFocus, start, fStart - 1); 759 } 760 761 if (end < fEnd) { 762 // remove from bottom 763 DrawSelectionBlock(fFocus, end + 1, fEnd); 764 } else if (end > fEnd) { 765 // add to the bottom 766 DrawSelectionBlock(fFocus, fEnd + 1, end); 767 } 768 } 769 770 if (focus != kNoFocus) 771 fFocus = focus; 772 fStart = start; 773 fEnd = end; 774 775 DrawSelection(focus == kNoFocus); 776 777 fBitPosition = 0; 778 } 779 780 781 void 782 DataView::GetSelection(int32 &start, int32 &end) 783 { 784 start = fStart; 785 end = fEnd; 786 } 787 788 789 void 790 DataView::InvalidateRange(int32 start, int32 end) 791 { 792 if (start <= 0 && end >= int32(fDataSize) - 1) { 793 Invalidate(); 794 return; 795 } 796 797 int32 startLine = start / kBlockSize; 798 int32 endLine = end / kBlockSize; 799 800 if (endLine > startLine) { 801 start = startLine * kBlockSize; 802 end = (endLine + 1) * kBlockSize - 1; 803 } 804 805 // the part with focus 806 BRect rect = SelectionFrame(fFocus, start, end); 807 rect.bottom++; 808 rect.right++; 809 Invalidate(rect); 810 811 // the part without focus 812 rect = SelectionFrame(fFocus == kHexFocus ? kAsciiFocus : kHexFocus, start, end); 813 rect.bottom++; 814 rect.right++; 815 Invalidate(rect); 816 } 817 818 819 void 820 DataView::MakeVisible(int32 position) 821 { 822 if (position < 0 || position > int32(fDataSize) - 1) 823 return; 824 825 BRect frame = SelectionFrame(fFocus, position, position); 826 BRect bounds = Bounds(); 827 if (bounds.Contains(frame)) 828 return; 829 830 // special case the first and the last line and column, so that 831 // we can take kHorizontalSpace & kVerticalSpace into account 832 833 if ((position % kBlockSize) == 0) 834 frame.left -= kHorizontalSpace; 835 else if ((position % kBlockSize) == kBlockSize - 1) 836 frame.right += kHorizontalSpace; 837 838 if (position < int32(kBlockSize)) 839 frame.top -= kVerticalSpace; 840 else if (position > int32(fDataSize - kBlockSize)) 841 frame.bottom += kVerticalSpace; 842 843 // compute the scroll point 844 845 BPoint point = bounds.LeftTop(); 846 if (bounds.left > frame.left) 847 point.x = frame.left; 848 else if (bounds.right < frame.right) 849 point.x = frame.right - bounds.Width(); 850 851 if (bounds.top > frame.top) 852 point.y = frame.top; 853 else if (bounds.bottom < frame.bottom) 854 point.y = frame.bottom - bounds.Height(); 855 856 ScrollTo(point); 857 } 858 859 860 const uint8 * 861 DataView::DataAt(int32 start) 862 { 863 if (start < 0 || start >= int32(fSizeInView) || fData == NULL) 864 return NULL; 865 866 return fData + start; 867 } 868 869 870 /*static*/ int32 871 DataView::WidthForFontSize(float size) 872 { 873 BFont font = be_fixed_font; 874 font.SetSize(size); 875 876 float charWidth = font.StringWidth("w"); 877 return (int32)ceilf(charWidth * (kBlockSize * 4 + kPositionLength + 6) 878 + 2 * kHorizontalSpace); 879 } 880 881 882 void 883 DataView::SetBase(base_type type) 884 { 885 if (fBase == type) 886 return; 887 888 fBase = type; 889 Invalidate(); 890 } 891 892 893 void 894 DataView::SetFocus(view_focus which) 895 { 896 if (which == fFocus) 897 return; 898 899 DrawSelection(); 900 fFocus = which; 901 DrawSelection(); 902 } 903 904 905 void 906 DataView::SetActive(bool active) 907 { 908 if (active == fIsActive) 909 return; 910 911 fIsActive = active; 912 913 // only redraw the focussed part 914 915 if (IsFocus() && active) { 916 DrawSelectionFrame(fFocus); 917 DrawSelectionBlock(fFocus); 918 } else { 919 DrawSelectionBlock(fFocus); 920 DrawSelectionFrame(fFocus); 921 } 922 } 923 924 925 void 926 DataView::WindowActivated(bool active) 927 { 928 BView::WindowActivated(active); 929 SetActive(active); 930 } 931 932 933 void 934 DataView::MakeFocus(bool focus) 935 { 936 bool previous = IsFocus(); 937 BView::MakeFocus(focus); 938 939 if (focus == previous) 940 return; 941 942 if (Window()->IsActive() && focus) 943 SetActive(true); 944 else if (!Window()->IsActive() || !focus) 945 SetActive(false); 946 } 947 948 949 void 950 DataView::UpdateScroller() 951 { 952 float width, height; 953 GetPreferredSize(&width, &height); 954 955 SetExplicitMinSize(BSize(250, 200)); 956 SetExplicitMaxSize(BSize(B_SIZE_UNSET, height)); 957 SetExplicitPreferredSize(BSize(width, height)); 958 959 BScrollBar *bar; 960 if ((bar = ScrollBar(B_HORIZONTAL)) != NULL) { 961 float delta = width - Bounds().Width(); 962 if (delta < 0) 963 delta = 0; 964 965 bar->SetRange(0, delta); 966 bar->SetSteps(fCharWidth, Bounds().Width()); 967 bar->SetProportion(Bounds().Width() / width); 968 } 969 if ((bar = ScrollBar(B_VERTICAL)) != NULL) { 970 float delta = height - Bounds().Height(); 971 if (delta < 0) 972 delta = 0; 973 974 bar->SetRange(0, delta); 975 bar->SetSteps(fFontHeight, Bounds().Height()); 976 bar->SetProportion(Bounds().Height() / height); 977 } 978 } 979 980 981 void 982 DataView::FrameResized(float width, float height) 983 { 984 if (fFitFontSize) { 985 // adapt the font size to fit in the view's bounds 986 float oldSize = FontSize(); 987 float steps = 0.5f; 988 989 float size; 990 for (size = 1.f; size < 100; size += steps) { 991 int32 preferredWidth = WidthForFontSize(size); 992 if (preferredWidth > width) 993 break; 994 995 if (size > 6) 996 steps = 1.0f; 997 } 998 size -= steps; 999 1000 if (oldSize != size) { 1001 BFont font = be_fixed_font; 1002 font.SetSize(size); 1003 SetFont(&font); 1004 1005 Invalidate(); 1006 } 1007 } 1008 1009 UpdateScroller(); 1010 } 1011 1012 1013 void 1014 DataView::InitiateDrag(view_focus focus) 1015 { 1016 BMessage *drag = new BMessage(B_MIME_DATA); 1017 1018 // Add originator and action 1019 drag->AddPointer("be:originator", this); 1020 //drag->AddString("be:clip_name", "Byte Clipping"); 1021 //drag->AddInt32("be_actions", B_TRASH_TARGET); 1022 1023 // Add data (just like in Copy()) 1024 uint8 *data = fData + fStart; 1025 size_t length = fEnd + 1 - fStart; 1026 1027 drag->AddData(B_FILE_MIME_TYPE, B_MIME_TYPE, data, length); 1028 if (is_valid_utf8(data, length)) 1029 drag->AddData("text/plain", B_MIME_TYPE, data, length); 1030 1031 // get a frame that contains the whole selection - SelectionFrame() 1032 // only spans a rectangle between the start and the end point, so 1033 // we have to pass it the correct input values 1034 1035 BRect frame; 1036 const int32 width = kBlockSize - 1; 1037 int32 first = fStart & ~width; 1038 int32 last = ((fEnd + width) & ~width) - 1; 1039 if (first == (last & ~width)) 1040 frame = SelectionFrame(focus, fStart, fEnd); 1041 else 1042 frame = SelectionFrame(focus, first, last); 1043 1044 BRect bounds = Bounds(); 1045 if (!bounds.Contains(frame)) 1046 frame = bounds & frame; 1047 1048 DragMessage(drag, frame, NULL); 1049 1050 fStoredStart = fStart; 1051 fStoredEnd = fEnd; 1052 fDragMessageSize = length; 1053 } 1054 1055 1056 void 1057 DataView::MouseDown(BPoint where) 1058 { 1059 MakeFocus(true); 1060 1061 BMessage *message = Looper()->CurrentMessage(); 1062 int32 buttons; 1063 if (message == NULL || message->FindInt32("buttons", &buttons) != B_OK) 1064 return; 1065 1066 view_focus newFocus; 1067 int32 position = PositionAt(kNoFocus, where, &newFocus); 1068 1069 if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0 1070 && position >= fStart && position <= fEnd) { 1071 InitiateDrag(newFocus); 1072 return; 1073 } 1074 1075 if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0) 1076 return; 1077 1078 int32 modifiers = message->FindInt32("modifiers"); 1079 1080 fMouseSelectionStart = position; 1081 if (fMouseSelectionStart == -1) { 1082 // "where" is outside the valid frame 1083 return; 1084 } 1085 1086 int32 selectionEnd = fMouseSelectionStart; 1087 if (modifiers & B_SHIFT_KEY) { 1088 // enlarge the current selection 1089 if (fStart < selectionEnd) 1090 fMouseSelectionStart = fStart; 1091 else if (fEnd > selectionEnd) 1092 fMouseSelectionStart = fEnd; 1093 } 1094 SetSelection(fMouseSelectionStart, selectionEnd, newFocus); 1095 1096 SetMouseEventMask(B_POINTER_EVENTS, 1097 B_NO_POINTER_HISTORY | B_SUSPEND_VIEW_FOCUS | B_LOCK_WINDOW_FOCUS); 1098 } 1099 1100 1101 void 1102 DataView::MouseMoved(BPoint where, uint32 transit, const BMessage *dragMessage) 1103 { 1104 if (transit == B_EXITED_VIEW && fDragMessageSize > 0) { 1105 SetSelection(fStoredStart, fStoredEnd); 1106 fDragMessageSize = -1; 1107 } 1108 1109 if (dragMessage && AcceptsDrop(dragMessage)) { 1110 // handle drag message and tracking 1111 1112 if (transit == B_ENTERED_VIEW) { 1113 fStoredStart = fStart; 1114 fStoredEnd = fEnd; 1115 1116 const void *data; 1117 ssize_t size; 1118 if (dragMessage->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK 1119 || dragMessage->FindData("text/plain", B_MIME_TYPE, &data, &size) == B_OK) 1120 fDragMessageSize = size; 1121 } else if (fDragMessageSize > 0) { 1122 view_focus newFocus; 1123 int32 start = PositionAt(kNoFocus, where, &newFocus); 1124 int32 end = start + fDragMessageSize - 1; 1125 1126 SetSelection(start, end); 1127 MakeVisible(start); 1128 } 1129 return; 1130 } 1131 1132 if (fMouseSelectionStart == -1) 1133 return; 1134 1135 int32 end = PositionAt(fFocus, where); 1136 if (end == -1) 1137 return; 1138 1139 SetSelection(fMouseSelectionStart, end); 1140 MakeVisible(end); 1141 } 1142 1143 1144 void 1145 DataView::MouseUp(BPoint where) 1146 { 1147 fMouseSelectionStart = fKeySelectionStart = -1; 1148 } 1149 1150 1151 void 1152 DataView::KeyDown(const char *bytes, int32 numBytes) 1153 { 1154 int32 modifiers; 1155 if (Looper()->CurrentMessage() == NULL 1156 || Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers) != B_OK) 1157 modifiers = ::modifiers(); 1158 1159 // check if the selection is going to be changed 1160 switch (bytes[0]) { 1161 case B_LEFT_ARROW: 1162 case B_RIGHT_ARROW: 1163 case B_UP_ARROW: 1164 case B_DOWN_ARROW: 1165 if (modifiers & B_SHIFT_KEY) { 1166 if (fKeySelectionStart == -1) 1167 fKeySelectionStart = fStart; 1168 } else 1169 fKeySelectionStart = -1; 1170 break; 1171 } 1172 1173 switch (bytes[0]) { 1174 case B_LEFT_ARROW: 1175 { 1176 int32 position = fStart - 1; 1177 1178 if (modifiers & B_SHIFT_KEY) { 1179 if (fKeySelectionStart == fEnd) 1180 SetSelection(fStart - 1, fEnd); 1181 else { 1182 SetSelection(fStart, fEnd - 1); 1183 position = fEnd; 1184 } 1185 } else 1186 SetSelection(fStart - 1, fStart - 1); 1187 1188 MakeVisible(position); 1189 break; 1190 } 1191 case B_RIGHT_ARROW: 1192 { 1193 int32 position = fEnd + 1; 1194 1195 if (modifiers & B_SHIFT_KEY) { 1196 if (fKeySelectionStart == fStart) 1197 SetSelection(fStart, fEnd + 1); 1198 else 1199 SetSelection(fStart + 1, fEnd); 1200 } else 1201 SetSelection(fEnd + 1, fEnd + 1); 1202 1203 MakeVisible(position); 1204 break; 1205 } 1206 case B_UP_ARROW: 1207 { 1208 int32 start, end; 1209 if (modifiers & B_SHIFT_KEY) { 1210 if (fKeySelectionStart == fStart) { 1211 start = fEnd - int32(kBlockSize); 1212 end = fStart; 1213 } else { 1214 start = fStart - int32(kBlockSize); 1215 end = fEnd; 1216 } 1217 if (start < 0) 1218 start = 0; 1219 } else { 1220 start = fStart - int32(kBlockSize); 1221 if (start < 0) 1222 start = fStart; 1223 1224 end = start; 1225 } 1226 1227 SetSelection(start, end); 1228 MakeVisible(start); 1229 break; 1230 } 1231 case B_DOWN_ARROW: 1232 { 1233 int32 start, end; 1234 if (modifiers & B_SHIFT_KEY) { 1235 if (fKeySelectionStart == fEnd) { 1236 start = fEnd; 1237 end = fStart + int32(kBlockSize); 1238 } else { 1239 start = fStart; 1240 end = fEnd + int32(kBlockSize); 1241 } 1242 if (end >= int32(fSizeInView)) 1243 end = int32(fSizeInView) - 1; 1244 } else { 1245 end = fEnd + int32(kBlockSize); 1246 if (end >= int32(fSizeInView)) 1247 start = fEnd; 1248 1249 start = end; 1250 } 1251 1252 SetSelection(start, end); 1253 MakeVisible(end); 1254 break; 1255 } 1256 1257 case B_PAGE_UP: 1258 { 1259 // scroll one page up, but keep the same cursor column 1260 1261 BRect frame = SelectionFrame(fFocus, fStart, fStart); 1262 frame.OffsetBy(0, -Bounds().Height()); 1263 if (frame.top <= kVerticalSpace) 1264 frame.top = kVerticalSpace + 1; 1265 ScrollBy(0, -Bounds().Height()); 1266 1267 int32 position = PositionAt(fFocus, frame.LeftTop()); 1268 SetSelection(position, position); 1269 break; 1270 } 1271 case B_PAGE_DOWN: 1272 { 1273 // scroll one page down, but keep the same cursor column 1274 1275 BRect frame = SelectionFrame(fFocus, fStart, fStart); 1276 frame.OffsetBy(0, Bounds().Height()); 1277 1278 float lastLine = DataBounds().Height() - 1 - kVerticalSpace; 1279 if (frame.top > lastLine) 1280 frame.top = lastLine; 1281 ScrollBy(0, Bounds().Height()); 1282 1283 int32 position = PositionAt(fFocus, frame.LeftTop()); 1284 SetSelection(position, position); 1285 break; 1286 } 1287 case B_HOME: 1288 SetSelection(0, 0); 1289 MakeVisible(fStart); 1290 break; 1291 case B_END: 1292 SetSelection(fDataSize - 1, fDataSize - 1); 1293 MakeVisible(fStart); 1294 break; 1295 case B_TAB: 1296 SetFocus(fFocus == kHexFocus ? kAsciiFocus : kHexFocus); 1297 MakeVisible(fStart); 1298 break; 1299 1300 case B_FUNCTION_KEY: 1301 // this is ignored 1302 break; 1303 1304 case B_BACKSPACE: 1305 if (fBitPosition == 0) 1306 SetSelection(fStart - 1, fStart - 1); 1307 1308 if (fFocus == kHexFocus) 1309 fBitPosition = (fBitPosition + 4) % 8; 1310 1311 // supposed to fall through 1312 case B_DELETE: 1313 SetSelection(fStart, fStart); 1314 // to make sure only the cursor is selected 1315 1316 if (fFocus == kHexFocus) { 1317 const uint8 *data = DataAt(fStart); 1318 if (data == NULL) 1319 break; 1320 1321 uint8 c = data[0] & (fBitPosition == 0 ? 0x0f : 0xf0); 1322 // mask out region to be cleared 1323 1324 fEditor.Replace(fOffset + fStart, &c, 1); 1325 } else 1326 fEditor.Replace(fOffset + fStart, (const uint8 *)"", 1); 1327 break; 1328 1329 default: 1330 if (fFocus == kHexFocus) { 1331 // only hexadecimal characters are allowed to be entered 1332 const uint8 *data = DataAt(fStart); 1333 uint8 c = bytes[0]; 1334 if (c >= 'A' && c <= 'F') 1335 c += 'A' - 'a'; 1336 const char *hexNumbers = "0123456789abcdef"; 1337 addr_t number; 1338 if (data == NULL 1339 || (number = (addr_t)strchr(hexNumbers, c)) == 0) 1340 break; 1341 1342 SetSelection(fStart, fStart); 1343 // to make sure only the cursor is selected 1344 1345 number -= (addr_t)hexNumbers; 1346 fBitPosition = (fBitPosition + 4) % 8; 1347 1348 c = (data[0] & (fBitPosition ? 0x0f : 0xf0)) 1349 | (number << fBitPosition); 1350 // mask out overwritten region and bit-wise or the number 1351 // to be inserted 1352 1353 if (fEditor.Replace(fOffset + fStart, &c, 1) == B_OK 1354 && fBitPosition == 0) 1355 SetSelection(fStart + 1, fStart + 1); 1356 } else { 1357 if (fEditor.Replace(fOffset + fStart, (const uint8 *)bytes, 1358 numBytes) == B_OK) 1359 SetSelection(fStart + 1, fStart + 1); 1360 } 1361 break; 1362 } 1363 } 1364 1365 1366 void 1367 DataView::SetFont(const BFont *font, uint32 properties) 1368 { 1369 // Even in a full and hal fixed font, the characters we use (all in the 1370 // Latin-1 range as everything else is filtered out) will all have the same 1371 // width. 1372 if (!font->IsFixed() && !font->IsFullAndHalfFixed()) 1373 return; 1374 1375 BView::SetFont(font, properties); 1376 1377 font_height fontHeight; 1378 font->GetHeight(&fontHeight); 1379 1380 fFontHeight = int32(fontHeight.ascent + fontHeight.descent 1381 + fontHeight.leading); 1382 fAscent = fontHeight.ascent; 1383 fCharWidth = font->StringWidth("w"); 1384 } 1385 1386 1387 float 1388 DataView::FontSize() const 1389 { 1390 BFont font; 1391 GetFont(&font); 1392 1393 return font.Size(); 1394 } 1395 1396 1397 void 1398 DataView::SetFontSize(float point) 1399 { 1400 bool fit = (point == 0.0f); 1401 if (fit) { 1402 if (!fFitFontSize) 1403 SendNotices(kDataViewPreferredSize); 1404 fFitFontSize = fit; 1405 1406 FrameResized(Bounds().Width(), Bounds().Height()); 1407 return; 1408 } 1409 1410 fFitFontSize = false; 1411 1412 BFont font = be_fixed_font; 1413 font.SetSize(point); 1414 1415 SetFont(&font); 1416 UpdateScroller(); 1417 Invalidate(); 1418 1419 SendNotices(kDataViewPreferredSize); 1420 } 1421 1422 1423 void 1424 DataView::GetPreferredSize(float *_width, float *_height) 1425 { 1426 BRect bounds = DataBounds(); 1427 1428 if (_width) 1429 *_width = bounds.Width(); 1430 1431 if (_height) 1432 *_height = bounds.Height(); 1433 } 1434 1435