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
22static const uint32 kBlockSize = 16;
23// TODO: use variable spacing
24static const uint32 kHorizontalSpace = 8;
25static const uint32 kVerticalSpace = 4;
26static const uint32 kPositionLength = 4;
27
28static const uint32 kBlockSpace = 3;
29static 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*/
37bool
38is_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
96DataView::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
125DataView::~DataView()
126{
127}
128
129
130void
131DataView::DetachedFromWindow()
132{
133	fEditor.StopWatching(this);
134}
135
136
137void
138DataView::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
151void
152DataView::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
203bool
204DataView::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
212void
213DataView::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
302void
303DataView::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
327void
328DataView::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
353void
354DataView::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
391void
392DataView::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
441BRect
442DataView::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
451int32
452DataView::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
501BRect
502DataView::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
528void
529DataView::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
645void
646DataView::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
684void
685DataView::DrawSelectionBlock(view_focus which)
686{
687	DrawSelectionBlock(which, fStart, fEnd);
688}
689
690
691void
692DataView::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
705void
706DataView::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
781void
782DataView::GetSelection(int32 &start, int32 &end)
783{
784	start = fStart;
785	end = fEnd;
786}
787
788
789void
790DataView::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
819void
820DataView::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
860const uint8 *
861DataView::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
871DataView::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
882void
883DataView::SetBase(base_type type)
884{
885	if (fBase == type)
886		return;
887
888	fBase = type;
889	Invalidate();
890}
891
892
893void
894DataView::SetFocus(view_focus which)
895{
896	if (which == fFocus)
897		return;
898
899	DrawSelection();
900	fFocus = which;
901	DrawSelection();
902}
903
904
905void
906DataView::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
925void
926DataView::WindowActivated(bool active)
927{
928	BView::WindowActivated(active);
929	SetActive(active);
930}
931
932
933void
934DataView::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
949void
950DataView::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
981void
982DataView::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
1013void
1014DataView::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
1056void
1057DataView::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
1101void
1102DataView::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
1144void
1145DataView::MouseUp(BPoint where)
1146{
1147	fMouseSelectionStart = fKeySelectionStart = -1;
1148}
1149
1150
1151void
1152DataView::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
1366void
1367DataView::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
1387float
1388DataView::FontSize() const
1389{
1390	BFont font;
1391	GetFont(&font);
1392
1393	return font.Size();
1394}
1395
1396
1397void
1398DataView::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
1423void
1424DataView::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