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