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