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