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