BTRFS: Implement BTree::Path and change _Find.
[haiku.git] / src / apps / diskprobe / DataView.cpp
blobc2df0556ffa460ff90d1f0a6961528797c4102d2
1 /*
2 * Copyright 2004-2015, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
7 #include "DataView.h"
9 #include <stdio.h>
10 #include <stdlib.h>
12 #include <Autolock.h>
13 #include <Beep.h>
14 #include <Clipboard.h>
15 #include <Mime.h>
16 #include <ScrollBar.h>
17 #include <Window.h>
19 #include "DataEditor.h"
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;
28 static const uint32 kBlockSpace = 3;
29 static const uint32 kHexByteWidth = 3;
30 // these are determined by the implementation of DataView::ConvertLine()
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.
37 bool
38 is_valid_utf8(uint8 *data, size_t size)
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;
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;
52 continue;
55 if ((data[i] & 0xc0) == 0x80) {
56 // not a proper multibyte start
57 return false;
60 // start of a multibyte character
61 uint8 mask = 0x80;
62 uint32 result = (uint32)(data[i++] & 0xff);
64 while (result & mask) {
65 if (mask == 0x02) {
66 // seven byte char - invalid
67 return false;
70 result &= ~mask;
71 mask >>= 1;
74 while (i < size && (data[i] & 0xc0) == 0x80) {
75 result <<= 6;
76 result += data[i++] & 0x3f;
78 mask <<= 1;
79 if (mask == 0x40)
80 break;
83 if (mask != 0x40) {
84 // not enough bytes in multibyte char
85 return false;
89 return true;
93 // #pragma mark -
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)
108 fStart = fEnd = 0;
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);
118 SetFontSize(12.0);
119 // also sets the fixed font
121 UpdateFromEditor();
125 DataView::~DataView()
130 void
131 DataView::DetachedFromWindow()
133 fEditor.StopWatching(this);
137 void
138 DataView::AttachedToWindow()
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...
145 SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
146 SetLowUIColor(ViewUIColor());
147 SetHighUIColor(B_DOCUMENT_TEXT_COLOR);
151 void
152 DataView::UpdateFromEditor(BMessage *message)
154 if (fData == NULL)
155 return;
157 BAutolock locker(fEditor);
159 fFileSize = fEditor.FileSize();
161 // get the range of the changes
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;
174 if (offset > fOffset)
175 start = offset - fOffset;
176 if (offset + (off_t)size < fOffset + (off_t)fDataSize)
177 end = offset + size - fOffset;
180 if (fOffset + (off_t)fDataSize > fFileSize)
181 fSizeInView = fFileSize - fOffset;
182 else
183 fSizeInView = fDataSize;
185 const uint8 *data;
186 if (fEditor.GetViewBuffer(&data) == B_OK)
187 // ToDo: copy only the relevant part
188 memcpy(fData, data, fDataSize);
190 InvalidateRange(start, end);
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);
203 bool
204 DataView::AcceptsDrop(const BMessage *message)
206 return !fEditor.IsReadOnly()
207 && (message->HasData("text/plain", B_MIME_TYPE)
208 || message->HasData(B_FILE_MIME_TYPE, B_MIME_TYPE));
212 void
213 DataView::MessageReceived(BMessage *message)
215 switch (message->what) {
216 case kMsgUpdateData:
217 case kMsgDataEditorUpdate:
218 UpdateFromEditor(message);
219 break;
221 case kMsgDataEditorParameterChange:
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);
230 if (message->FindInt32("view_size", &viewSize) == B_OK) {
231 fDataSize = viewSize;
232 fData = (uint8 *)realloc(fData, fDataSize);
233 UpdateScroller();
234 SendNotices(kDataViewPreferredSize);
236 if (message->FindInt64("file_size", &offset) == B_OK)
237 UpdateFromEditor();
238 break;
241 case kMsgBaseType:
243 int32 type;
244 if (message->FindInt32("base", &type) != B_OK)
245 break;
247 SetBase((base_type)type);
248 break;
251 case kMsgSetSelection:
253 int64 start, end;
254 if (message->FindInt64("start", &start) != B_OK
255 || message->FindInt64("end", &end) != B_OK)
256 break;
258 SetSelection(start, end);
259 break;
262 case B_SELECT_ALL:
263 SetSelection(0, fDataSize - 1);
264 break;
266 case B_COPY:
267 Copy();
268 break;
270 case B_PASTE:
271 Paste();
272 break;
274 case B_UNDO:
275 fEditor.Undo();
276 break;
278 case B_REDO:
279 fEditor.Redo();
280 break;
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);
291 fDragMessageSize = -1;
294 break;
296 default:
297 BView::MessageReceived(message);
302 void
303 DataView::Copy()
305 if (!be_clipboard->Lock())
306 return;
308 be_clipboard->Clear();
310 BMessage *clip;
311 if ((clip = be_clipboard->Data()) != NULL) {
312 uint8 *data = fData + fStart;
313 size_t length = fEnd + 1 - fStart;
315 clip->AddData(B_FILE_MIME_TYPE, B_MIME_TYPE, data, length);
317 if (is_valid_utf8(data, length))
318 clip->AddData("text/plain", B_MIME_TYPE, data, length);
320 be_clipboard->Commit();
323 be_clipboard->Unlock();
327 void
328 DataView::Paste()
330 if (!be_clipboard->Lock())
331 return;
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;
344 if (fEditor.Replace(fOffset + fStart, (const uint8 *)data, length) == B_OK)
345 SetSelection(fStart + length, fStart + length);
346 } else
347 beep();
349 be_clipboard->Unlock();
353 void
354 DataView::ConvertLine(char *line, off_t offset, const uint8 *buffer, size_t size)
356 if (size == 0) {
357 line[0] = '\0';
358 return;
361 line += sprintf(line, fBase == kHexBase ? "%0*" B_PRIxOFF": " : "%0*"
362 B_PRIdOFF": ", (int)kPositionLength, offset);
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));
372 strcpy(line, " ");
373 line += 3;
375 for (uint32 i = 0; i < kBlockSize; i++) {
376 if (i < size) {
377 char c = buffer[i];
379 if (c < ' ' || c == 0x7f)
380 *line++ = '.';
381 else
382 *line++ = c;
383 } else
384 break;
387 *line = '\0';
391 void
392 DataView::Draw(BRect updateRect)
394 if (fData == NULL || fFileSize == 0)
395 return;
397 // ToDo: take "updateRect" into account!
399 char line[255];
400 BPoint location(kHorizontalSpace, kVerticalSpace + fAscent);
402 for (uint32 i = 0; i < fSizeInView; i += kBlockSize) {
403 ConvertLine(line, i, fData + i, fSizeInView - i);
404 DrawString(line, location);
406 location.y += fFontHeight;
409 DrawSelection();
413 BRect
414 DataView::DataBounds(bool inView) const
416 return BRect(0, 0,
417 fCharWidth * (kBlockSize * 4 + kPositionLength + 6) + 2 * kHorizontalSpace,
418 fFontHeight * (((inView ? fSizeInView : fDataSize) + kBlockSize - 1) / kBlockSize)
419 + 2 * kVerticalSpace);
423 int32
424 DataView::PositionAt(view_focus focus, BPoint point, view_focus *_newFocus)
426 // clip the point into our data bounds
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;
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;
439 float left = fCharWidth * (kPositionLength + kBlockSpace) + kHorizontalSpace;
440 float hexWidth = fCharWidth * kBlockSize * kHexByteWidth;
441 float width = fCharWidth;
443 if (focus == kNoFocus) {
444 // find in which part the point is in
445 if (point.x < left - width / 2)
446 return -1;
448 if (point.x > left + hexWidth)
449 focus = kAsciiFocus;
450 else
451 focus = kHexFocus;
453 if (_newFocus)
454 *_newFocus = focus;
456 if (focus == kHexFocus) {
457 left -= width / 2;
458 width *= kHexByteWidth;
459 } else
460 left += hexWidth + (kBlockSpace * width);
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;
469 return row * kBlockSize + column;
473 BRect
474 DataView::SelectionFrame(view_focus which, int32 start, int32 end)
476 float spacing = 0;
477 float width = fCharWidth;
478 float byteWidth = fCharWidth;
479 float left;
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);
489 left += kHorizontalSpace;
490 float startInLine = (start % kBlockSize) * width;
491 float endInLine = (end % kBlockSize) * width + byteWidth - 1;
493 return BRect(left + startInLine - spacing,
494 kVerticalSpace + (start / kBlockSize) * fFontHeight,
495 left + endInLine + spacing,
496 kVerticalSpace + (end / kBlockSize + 1) * fFontHeight - 1);
500 void
501 DataView::DrawSelectionFrame(view_focus which)
503 if (fFileSize == 0)
504 return;
506 bool drawBlock = false;
507 bool drawLastLine = false;
508 BRect block, lastLine;
509 int32 spacing = 0;
510 if (which == kAsciiFocus)
511 spacing++;
513 // draw first line
515 int32 start = fStart % kBlockSize;
516 int32 first = (fStart / kBlockSize) * kBlockSize;
518 int32 end = fEnd;
519 if (end > first + (int32)kBlockSize - 1)
520 end = first + kBlockSize - 1;
522 BRect firstLine = SelectionFrame(which, first + start, end);
523 firstLine.right += spacing;
524 first += kBlockSize;
526 // draw block (and last line) if necessary
528 end = fEnd % kBlockSize;
529 int32 last = (fEnd / kBlockSize) * kBlockSize;
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;
539 if (end != kBlockSize - 1) {
540 lastLine = SelectionFrame(which, last, last + end);
541 lastLine.right += spacing;
542 drawLastLine = true;
546 SetDrawingMode(B_OP_INVERT);
547 BeginLineArray(8);
549 // +*******
550 // | *
551 // +------+
553 const rgb_color color = {0, 0, 0};
554 float bottom;
555 if (drawBlock)
556 bottom = block.bottom;
557 else
558 bottom = firstLine.bottom;
560 AddLine(BPoint(firstLine.left + 1, firstLine.top), firstLine.RightTop(), color);
561 AddLine(BPoint(firstLine.right, firstLine.top + 1), BPoint(firstLine.right, bottom), color);
563 // *-------+
564 // * |
565 // *********
567 BRect rect;
568 if (start == 0 || (!drawBlock && !drawLastLine))
569 rect = firstLine;
570 else if (drawBlock)
571 rect = block;
572 else
573 rect = lastLine;
575 if (drawBlock)
576 rect.bottom = block.bottom;
577 if (drawLastLine) {
578 rect.bottom = lastLine.bottom;
579 rect.right = lastLine.right;
581 rect.bottom++;
583 AddLine(rect.LeftTop(), rect.LeftBottom(), color);
584 AddLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom(), color);
586 // *--------+
587 // * |
588 // +**** |
589 // | |
591 if (start && (drawLastLine || drawBlock)) {
592 AddLine(firstLine.LeftTop(), firstLine.LeftBottom(), color);
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);
600 // | |
601 // | *****
602 // | *
603 // +--------+
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);
612 EndLineArray();
613 SetDrawingMode(B_OP_COPY);
617 void
618 DataView::DrawSelectionBlock(view_focus which, int32 blockStart, int32 blockEnd)
620 if (fFileSize == 0)
621 return;
623 // draw first line
625 SetDrawingMode(B_OP_INVERT);
627 int32 start = blockStart % kBlockSize;
628 int32 first = (blockStart / kBlockSize) * kBlockSize;
630 int32 end = blockEnd;
631 if (end > first + (int32)kBlockSize - 1)
632 end = first + kBlockSize - 1;
634 FillRect(SelectionFrame(which, first + start, end));
635 first += kBlockSize;
637 // draw block (and last line) if necessary
639 end = blockEnd % kBlockSize;
640 int32 last = (blockEnd / kBlockSize) * kBlockSize;
642 if (last >= first) {
643 if (end == kBlockSize - 1)
644 last += kBlockSize;
646 if (last > first)
647 FillRect(SelectionFrame(which, first, last - 1));
648 if (end != kBlockSize - 1)
649 FillRect(SelectionFrame(which, last, last + end));
652 SetDrawingMode(B_OP_COPY);
656 void
657 DataView::DrawSelectionBlock(view_focus which)
659 DrawSelectionBlock(which, fStart, fEnd);
663 void
664 DataView::DrawSelection(bool frameOnly)
666 if (IsFocus() && fIsActive) {
667 if (!frameOnly)
668 DrawSelectionBlock(fFocus);
669 DrawSelectionFrame(fFocus == kHexFocus ? kAsciiFocus : kHexFocus);
670 } else {
671 DrawSelectionFrame(kHexFocus);
672 DrawSelectionFrame(kAsciiFocus);
677 void
678 DataView::SetSelection(int32 start, int32 end, view_focus focus)
680 // correct the values if necessary
682 if (start > end) {
683 int32 temp = start;
684 start = end;
685 end = temp;
688 if (start > (int32)fSizeInView - 1)
689 start = (int32)fSizeInView - 1;
690 if (start < 0)
691 start = 0;
693 if (end > (int32)fSizeInView - 1)
694 end = (int32)fSizeInView - 1;
695 if (end < 0)
696 end = 0;
698 if (fStart == start && fEnd == end) {
699 // nothing has changed, no need to update
700 return;
703 // notify our listeners
704 if (fStart != start) {
705 BMessage update;
706 update.AddInt64("position", start);
707 SendNotices(kDataViewCursorPosition, &update);
710 BMessage update;
711 update.AddInt64("start", start);
712 update.AddInt64("end", end);
713 SendNotices(kDataViewSelection, &update);
715 // Update selection - first, we need to remove the old selection, then
716 // we redraw the selection with the current values.
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.
722 if (IsFocus() && fIsActive && focus == kNoFocus) {
723 // Update the selection block incrementally
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);
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);
742 if (focus != kNoFocus)
743 fFocus = focus;
744 fStart = start;
745 fEnd = end;
747 DrawSelection(focus == kNoFocus);
749 fBitPosition = 0;
753 void
754 DataView::GetSelection(int32 &start, int32 &end)
756 start = fStart;
757 end = fEnd;
761 void
762 DataView::InvalidateRange(int32 start, int32 end)
764 if (start <= 0 && end >= int32(fDataSize) - 1) {
765 Invalidate();
766 return;
769 int32 startLine = start / kBlockSize;
770 int32 endLine = end / kBlockSize;
772 if (endLine > startLine) {
773 start = startLine * kBlockSize;
774 end = (endLine + 1) * kBlockSize - 1;
777 // the part with focus
778 BRect rect = SelectionFrame(fFocus, start, end);
779 rect.bottom++;
780 rect.right++;
781 Invalidate(rect);
783 // the part without focus
784 rect = SelectionFrame(fFocus == kHexFocus ? kAsciiFocus : kHexFocus, start, end);
785 rect.bottom++;
786 rect.right++;
787 Invalidate(rect);
791 void
792 DataView::MakeVisible(int32 position)
794 if (position < 0 || position > int32(fDataSize) - 1)
795 return;
797 BRect frame = SelectionFrame(fFocus, position, position);
798 BRect bounds = Bounds();
799 if (bounds.Contains(frame))
800 return;
802 // special case the first and the last line and column, so that
803 // we can take kHorizontalSpace & kVerticalSpace into account
805 if ((position % kBlockSize) == 0)
806 frame.left -= kHorizontalSpace;
807 else if ((position % kBlockSize) == kBlockSize - 1)
808 frame.right += kHorizontalSpace;
810 if (position < int32(kBlockSize))
811 frame.top -= kVerticalSpace;
812 else if (position > int32(fDataSize - kBlockSize))
813 frame.bottom += kVerticalSpace;
815 // compute the scroll point
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();
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();
828 ScrollTo(point);
832 const uint8 *
833 DataView::DataAt(int32 start)
835 if (start < 0 || start >= int32(fSizeInView) || fData == NULL)
836 return NULL;
838 return fData + start;
842 /*static*/ int32
843 DataView::WidthForFontSize(float size)
845 BFont font = be_fixed_font;
846 font.SetSize(size);
848 float charWidth = font.StringWidth("w");
849 return (int32)ceilf(charWidth * (kBlockSize * 4 + kPositionLength + 6)
850 + 2 * kHorizontalSpace);
854 void
855 DataView::SetBase(base_type type)
857 if (fBase == type)
858 return;
860 fBase = type;
861 Invalidate();
865 void
866 DataView::SetFocus(view_focus which)
868 if (which == fFocus)
869 return;
871 DrawSelection();
872 fFocus = which;
873 DrawSelection();
877 void
878 DataView::SetActive(bool active)
880 if (active == fIsActive)
881 return;
883 fIsActive = active;
885 // only redraw the focussed part
887 if (IsFocus() && active) {
888 DrawSelectionFrame(fFocus);
889 DrawSelectionBlock(fFocus);
890 } else {
891 DrawSelectionBlock(fFocus);
892 DrawSelectionFrame(fFocus);
897 void
898 DataView::WindowActivated(bool active)
900 BView::WindowActivated(active);
901 SetActive(active);
905 void
906 DataView::MakeFocus(bool focus)
908 bool previous = IsFocus();
909 BView::MakeFocus(focus);
911 if (focus == previous)
912 return;
914 if (Window()->IsActive() && focus)
915 SetActive(true);
916 else if (!Window()->IsActive() || !focus)
917 SetActive(false);
921 void
922 DataView::UpdateScroller()
924 float width, height;
925 GetPreferredSize(&width, &height);
927 SetExplicitMinSize(BSize(250, 200));
928 SetExplicitMaxSize(BSize(B_SIZE_UNSET, height));
929 SetExplicitPreferredSize(BSize(width, height));
931 BScrollBar *bar;
932 if ((bar = ScrollBar(B_HORIZONTAL)) != NULL) {
933 float delta = width - Bounds().Width();
934 if (delta < 0)
935 delta = 0;
937 bar->SetRange(0, delta);
938 bar->SetSteps(fCharWidth, Bounds().Width());
939 bar->SetProportion(Bounds().Width() / width);
941 if ((bar = ScrollBar(B_VERTICAL)) != NULL) {
942 float delta = height - Bounds().Height();
943 if (delta < 0)
944 delta = 0;
946 bar->SetRange(0, delta);
947 bar->SetSteps(fFontHeight, Bounds().Height());
948 bar->SetProportion(Bounds().Height() / height);
953 void
954 DataView::FrameResized(float width, float height)
956 if (fFitFontSize) {
957 // adapt the font size to fit in the view's bounds
958 float oldSize = FontSize();
959 float steps = 0.5f;
961 float size;
962 for (size = 1.f; size < 100; size += steps) {
963 int32 preferredWidth = WidthForFontSize(size);
964 if (preferredWidth > width)
965 break;
967 if (size > 6)
968 steps = 1.0f;
970 size -= steps;
972 if (oldSize != size) {
973 BFont font = be_fixed_font;
974 font.SetSize(size);
975 SetFont(&font);
977 Invalidate();
981 UpdateScroller();
985 void
986 DataView::InitiateDrag(view_focus focus)
988 BMessage *drag = new BMessage(B_MIME_DATA);
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);
995 // Add data (just like in Copy())
996 uint8 *data = fData + fStart;
997 size_t length = fEnd + 1 - fStart;
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);
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
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);
1016 BRect bounds = Bounds();
1017 if (!bounds.Contains(frame))
1018 frame = bounds & frame;
1020 DragMessage(drag, frame, NULL);
1022 fStoredStart = fStart;
1023 fStoredEnd = fEnd;
1024 fDragMessageSize = length;
1028 void
1029 DataView::MouseDown(BPoint where)
1031 MakeFocus(true);
1033 BMessage *message = Looper()->CurrentMessage();
1034 int32 buttons;
1035 if (message == NULL || message->FindInt32("buttons", &buttons) != B_OK)
1036 return;
1038 view_focus newFocus;
1039 int32 position = PositionAt(kNoFocus, where, &newFocus);
1041 if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0
1042 && position >= fStart && position <= fEnd) {
1043 InitiateDrag(newFocus);
1044 return;
1047 if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0)
1048 return;
1050 int32 modifiers = message->FindInt32("modifiers");
1052 fMouseSelectionStart = position;
1053 if (fMouseSelectionStart == -1) {
1054 // "where" is outside the valid frame
1055 return;
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;
1066 SetSelection(fMouseSelectionStart, selectionEnd, newFocus);
1068 SetMouseEventMask(B_POINTER_EVENTS,
1069 B_NO_POINTER_HISTORY | B_SUSPEND_VIEW_FOCUS | B_LOCK_WINDOW_FOCUS);
1073 void
1074 DataView::MouseMoved(BPoint where, uint32 transit, const BMessage *dragMessage)
1076 if (transit == B_EXITED_VIEW && fDragMessageSize > 0) {
1077 SetSelection(fStoredStart, fStoredEnd);
1078 fDragMessageSize = -1;
1081 if (dragMessage && AcceptsDrop(dragMessage)) {
1082 // handle drag message and tracking
1084 if (transit == B_ENTERED_VIEW) {
1085 fStoredStart = fStart;
1086 fStoredEnd = fEnd;
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;
1098 SetSelection(start, end);
1099 MakeVisible(start);
1101 return;
1104 if (fMouseSelectionStart == -1)
1105 return;
1107 int32 end = PositionAt(fFocus, where);
1108 if (end == -1)
1109 return;
1111 SetSelection(fMouseSelectionStart, end);
1112 MakeVisible(end);
1116 void
1117 DataView::MouseUp(BPoint where)
1119 fMouseSelectionStart = fKeySelectionStart = -1;
1123 void
1124 DataView::KeyDown(const char *bytes, int32 numBytes)
1126 int32 modifiers;
1127 if (Looper()->CurrentMessage() == NULL
1128 || Looper()->CurrentMessage()->FindInt32("modifiers", &modifiers) != B_OK)
1129 modifiers = ::modifiers();
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;
1145 switch (bytes[0]) {
1146 case B_LEFT_ARROW:
1148 int32 position = fStart - 1;
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;
1157 } else
1158 SetSelection(fStart - 1, fStart - 1);
1160 MakeVisible(position);
1161 break;
1163 case B_RIGHT_ARROW:
1165 int32 position = fEnd + 1;
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);
1175 MakeVisible(position);
1176 break;
1178 case B_UP_ARROW:
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;
1189 if (start < 0)
1190 start = 0;
1191 } else {
1192 start = fStart - int32(kBlockSize);
1193 if (start < 0)
1194 start = fStart;
1196 end = start;
1199 SetSelection(start, end);
1200 MakeVisible(start);
1201 break;
1203 case B_DOWN_ARROW:
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);
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;
1221 start = end;
1224 SetSelection(start, end);
1225 MakeVisible(end);
1226 break;
1229 case B_PAGE_UP:
1231 // scroll one page up, but keep the same cursor column
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());
1239 int32 position = PositionAt(fFocus, frame.LeftTop());
1240 SetSelection(position, position);
1241 break;
1243 case B_PAGE_DOWN:
1245 // scroll one page down, but keep the same cursor column
1247 BRect frame = SelectionFrame(fFocus, fStart, fStart);
1248 frame.OffsetBy(0, Bounds().Height());
1250 float lastLine = DataBounds().Height() - 1 - kVerticalSpace;
1251 if (frame.top > lastLine)
1252 frame.top = lastLine;
1253 ScrollBy(0, Bounds().Height());
1255 int32 position = PositionAt(fFocus, frame.LeftTop());
1256 SetSelection(position, position);
1257 break;
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;
1272 case B_FUNCTION_KEY:
1273 // this is ignored
1274 break;
1276 case B_BACKSPACE:
1277 if (fBitPosition == 0)
1278 SetSelection(fStart - 1, fStart - 1);
1280 if (fFocus == kHexFocus)
1281 fBitPosition = (fBitPosition + 4) % 8;
1283 // supposed to fall through
1284 case B_DELETE:
1285 SetSelection(fStart, fStart);
1286 // to make sure only the cursor is selected
1288 if (fFocus == kHexFocus) {
1289 const uint8 *data = DataAt(fStart);
1290 if (data == NULL)
1291 break;
1293 uint8 c = data[0] & (fBitPosition == 0 ? 0x0f : 0xf0);
1294 // mask out region to be cleared
1296 fEditor.Replace(fOffset + fStart, &c, 1);
1297 } else
1298 fEditor.Replace(fOffset + fStart, (const uint8 *)"", 1);
1299 break;
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 || (number = (addr_t)strchr(hexNumbers, c)) == 0)
1311 break;
1313 SetSelection(fStart, fStart);
1314 // to make sure only the cursor is selected
1316 number -= (addr_t)hexNumbers;
1317 fBitPosition = (fBitPosition + 4) % 8;
1319 c = (data[0] & (fBitPosition ? 0x0f : 0xf0)) | (number << fBitPosition);
1320 // mask out overwritten region and bit-wise or the number to be inserted
1322 if (fEditor.Replace(fOffset + fStart, &c, 1) == B_OK && fBitPosition == 0)
1323 SetSelection(fStart + 1, fStart + 1);
1324 } else {
1325 if (fEditor.Replace(fOffset + fStart, (const uint8 *)bytes, numBytes) == B_OK)
1326 SetSelection(fStart + 1, fStart + 1);
1328 break;
1333 void
1334 DataView::SetFont(const BFont *font, uint32 properties)
1336 if (!font->IsFixed())
1337 return;
1339 BView::SetFont(font, properties);
1341 font_height fontHeight;
1342 font->GetHeight(&fontHeight);
1344 fFontHeight = int32(fontHeight.ascent + fontHeight.descent + fontHeight.leading);
1345 fAscent = fontHeight.ascent;
1346 fCharWidth = font->StringWidth("w");
1350 float
1351 DataView::FontSize() const
1353 BFont font;
1354 GetFont(&font);
1356 return font.Size();
1360 void
1361 DataView::SetFontSize(float point)
1363 bool fit = (point == 0.0f);
1364 if (fit) {
1365 if (!fFitFontSize)
1366 SendNotices(kDataViewPreferredSize);
1367 fFitFontSize = fit;
1369 FrameResized(Bounds().Width(), Bounds().Height());
1370 return;
1373 fFitFontSize = false;
1375 BFont font = be_fixed_font;
1376 font.SetSize(point);
1378 SetFont(&font);
1379 UpdateScroller();
1380 Invalidate();
1382 SendNotices(kDataViewPreferredSize);
1386 void
1387 DataView::GetPreferredSize(float *_width, float *_height)
1389 BRect bounds = DataBounds();
1391 if (_width)
1392 *_width = bounds.Width();
1394 if (_height)
1395 *_height = bounds.Height();