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