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