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