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