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
is_valid_utf8(uint8 * data,size_t size)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
DataView(DataEditor & editor)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
~DataView()125 DataView::~DataView()
126 {
127 }
128
129
130 void
DetachedFromWindow()131 DataView::DetachedFromWindow()
132 {
133 fEditor.StopWatching(this);
134 }
135
136
137 void
AttachedToWindow()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
UpdateFromEditor(BMessage * message)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
AcceptsDrop(const BMessage * message)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
MessageReceived(BMessage * message)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
Copy()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
Paste()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
ConvertLine(char * line,off_t offset,const uint8 * buffer,size_t size)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
Draw(BRect updateRect)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
DataBounds(bool inView) const442 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
PositionAt(view_focus focus,BPoint point,view_focus * _newFocus)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
SelectionFrame(view_focus which,int32 start,int32 end)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
DrawSelectionFrame(view_focus which)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
DrawSelectionBlock(view_focus which,int32 blockStart,int32 blockEnd)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
DrawSelectionBlock(view_focus which)685 DataView::DrawSelectionBlock(view_focus which)
686 {
687 DrawSelectionBlock(which, fStart, fEnd);
688 }
689
690
691 void
DrawSelection(bool frameOnly)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
SetSelection(int32 start,int32 end,view_focus focus)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
GetSelection(int32 & start,int32 & end)782 DataView::GetSelection(int32 &start, int32 &end)
783 {
784 start = fStart;
785 end = fEnd;
786 }
787
788
789 void
InvalidateRange(int32 start,int32 end)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
MakeVisible(int32 position)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 *
DataAt(int32 start)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
WidthForFontSize(float size)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
SetBase(base_type type)883 DataView::SetBase(base_type type)
884 {
885 if (fBase == type)
886 return;
887
888 fBase = type;
889 Invalidate();
890 }
891
892
893 void
SetFocus(view_focus which)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
SetActive(bool active)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
WindowActivated(bool active)926 DataView::WindowActivated(bool active)
927 {
928 BView::WindowActivated(active);
929 SetActive(active);
930 }
931
932
933 void
MakeFocus(bool focus)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
UpdateScroller()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
FrameResized(float width,float height)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
InitiateDrag(view_focus focus)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
MouseDown(BPoint where)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
MouseMoved(BPoint where,uint32 transit,const BMessage * dragMessage)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
MouseUp(BPoint where)1145 DataView::MouseUp(BPoint where)
1146 {
1147 fMouseSelectionStart = fKeySelectionStart = -1;
1148 }
1149
1150
1151 void
KeyDown(const char * bytes,int32 numBytes)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
SetFont(const BFont * font,uint32 properties)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
FontSize() const1388 DataView::FontSize() const
1389 {
1390 BFont font;
1391 GetFont(&font);
1392
1393 return font.Size();
1394 }
1395
1396
1397 void
SetFontSize(float point)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
GetPreferredSize(float * _width,float * _height)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