1/*
2 * Copyright 2011-2015, Rene Gollent, rene@gollent.com. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include "MemoryView.h"
8
9#include <algorithm>
10
11#include <ctype.h>
12#include <stdio.h>
13
14#include <ByteOrder.h>
15#include <Clipboard.h>
16#include <Looper.h>
17#include <MenuItem.h>
18#include <MessageRunner.h>
19#include <Messenger.h>
20#include <PopUpMenu.h>
21#include <Region.h>
22#include <ScrollView.h>
23#include <String.h>
24
25#include "Architecture.h"
26#include "AutoDeleter.h"
27#include "MessageCodes.h"
28#include "Team.h"
29#include "TeamMemoryBlock.h"
30
31
32enum {
33	MSG_TARGET_ADDRESS_CHANGED	= 'mtac',
34	MSG_VIEW_AUTOSCROLL			= 'mvas'
35};
36
37static const bigtime_t kScrollTimer = 10000LL;
38
39
40MemoryView::MemoryView(::Team* team, Listener* listener)
41	:
42	BView("memoryView", B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE
43		| B_SUBPIXEL_PRECISE),
44	fTeam(team),
45	fTargetBlock(NULL),
46	fEditableData(NULL),
47	fEditedOffsets(),
48	fTargetAddress(0LL),
49	fEditMode(false),
50	fEditLowNybble(false),
51	fCharWidth(0.0),
52	fLineHeight(0.0),
53	fTextCharsPerLine(0),
54	fHexBlocksPerLine(0),
55	fHexMode(HexMode8BitInt),
56	fTextMode(TextModeASCII),
57	fSelectionBase(0),
58	fSelectionStart(0),
59	fSelectionEnd(0),
60	fScrollRunner(NULL),
61	fTrackingMouse(false),
62	fListener(listener)
63{
64	Architecture* architecture = team->GetArchitecture();
65	fTargetAddressSize = architecture->AddressSize() * 2;
66	fCurrentEndianMode = architecture->IsBigEndian()
67		? EndianModeBigEndian : EndianModeLittleEndian;
68
69}
70
71
72MemoryView::~MemoryView()
73{
74	if (fTargetBlock != NULL)
75		fTargetBlock->ReleaseReference();
76
77	delete[] fEditableData;
78}
79
80
81/*static */ MemoryView*
82MemoryView::Create(::Team* team, Listener* listener)
83{
84	MemoryView* self = new MemoryView(team, listener);
85
86	try {
87		self->_Init();
88	} catch(...) {
89		delete self;
90		throw;
91	}
92
93	return self;
94}
95
96
97void
98MemoryView::SetTargetAddress(TeamMemoryBlock* block, target_addr_t address)
99{
100	fTargetAddress = address;
101	if (block != fTargetBlock) {
102		if (fTargetBlock != NULL)
103			fTargetBlock->ReleaseReference();
104
105		fTargetBlock = block;
106		if (block != NULL)
107			fTargetBlock->AcquireReference();
108	}
109
110	MakeFocus(true);
111	BMessenger(this).SendMessage(MSG_TARGET_ADDRESS_CHANGED);
112}
113
114
115void
116MemoryView::UnsetListener()
117{
118	fListener = NULL;
119}
120
121
122status_t
123MemoryView::SetEditMode(bool enabled)
124{
125	if (fTargetBlock == NULL)
126		return B_BAD_VALUE;
127	else if (fEditMode == enabled)
128		return B_OK;
129
130	if (enabled) {
131		status_t error = _SetupEditableData();
132		if (error != B_OK)
133			return error;
134	} else {
135		delete[] fEditableData;
136		fEditableData = NULL;
137		fEditedOffsets.clear();
138		fEditLowNybble = false;
139	}
140
141	fEditMode = enabled;
142	Invalidate();
143
144	return B_OK;
145}
146
147
148void
149MemoryView::AttachedToWindow()
150{
151	BView::AttachedToWindow();
152	SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
153	SetFont(be_fixed_font);
154	fCharWidth = be_fixed_font->StringWidth("a");
155	font_height fontHeight;
156	be_fixed_font->GetHeight(&fontHeight);
157	fLineHeight = ceilf(fontHeight.ascent + fontHeight.descent
158		+ fontHeight.leading);
159}
160
161
162void
163MemoryView::Draw(BRect rect)
164{
165	rect = Bounds();
166
167	float divider = (fTargetAddressSize + 1) * fCharWidth;
168	StrokeLine(BPoint(divider, rect.top),
169				BPoint(divider, rect.bottom));
170
171	if (fTargetBlock == NULL)
172		return;
173
174	uint32 hexBlockSize = _GetHexDigitsPerBlock() + 1;
175	uint32 blockByteSize = hexBlockSize / 2;
176	if (fHexMode != HexModeNone && fTextMode != TextModeNone) {
177		divider += (fHexBlocksPerLine * hexBlockSize + 1) * fCharWidth;
178		StrokeLine(BPoint(divider, rect.top),
179					BPoint(divider, rect.bottom));
180	}
181
182	char buffer[32];
183	char textbuffer[512];
184
185	const char* dataSource = (const char*)(fEditMode ? fEditableData
186			: fTargetBlock->Data());
187
188	int32 startLine = int32(rect.top / fLineHeight);
189	const char* currentAddress = dataSource + fHexBlocksPerLine
190		* blockByteSize * startLine;
191	const char* maxAddress = dataSource + fTargetBlock->Size();
192	const char* targetAddress = dataSource + fTargetAddress
193		- fTargetBlock->BaseAddress();
194	BPoint drawPoint(1.0, (startLine + 1) * fLineHeight);
195	int32 currentBlocksPerLine = fHexBlocksPerLine;
196	int32 currentCharsPerLine = fTextCharsPerLine;
197	font_height fh;
198	GetFontHeight(&fh);
199	target_addr_t lineAddress = fTargetBlock->BaseAddress() + startLine
200		* currentCharsPerLine;
201	bool highlightBlock = false;
202	rgb_color highlightColor;
203	for (; currentAddress < maxAddress && drawPoint.y < rect.bottom
204		+ fLineHeight; drawPoint.y += fLineHeight) {
205		drawPoint.x = 1.0;
206		snprintf(buffer, sizeof(buffer), "%0*" B_PRIx64,
207			(int)fTargetAddressSize, lineAddress);
208		PushState();
209		SetHighColor(tint_color(HighColor(), B_LIGHTEN_1_TINT));
210		DrawString(buffer, drawPoint);
211		drawPoint.x += fCharWidth * (fTargetAddressSize + 2);
212		PopState();
213		if (fHexMode != HexModeNone) {
214			if (currentAddress + (currentBlocksPerLine * blockByteSize)
215				> maxAddress) {
216				currentCharsPerLine = maxAddress - currentAddress;
217				currentBlocksPerLine = currentCharsPerLine
218					/ blockByteSize;
219			}
220
221			for (int32 j = 0; j < currentBlocksPerLine; j++) {
222				const char* blockAddress = currentAddress + (j
223					* blockByteSize);
224				_GetNextHexBlock(buffer, sizeof(buffer), blockAddress);
225
226				highlightBlock = false;
227				if (fEditMode)
228				{
229					int32 offset = blockAddress - dataSource;
230					for (uint32 i = 0; i < blockByteSize; i++) {
231						if (fEditedOffsets.count(offset + i) != 0) {
232							highlightBlock = true;
233							highlightColor.set_to(0, 216, 0);
234							break;
235						}
236					}
237
238				} else if (targetAddress >= blockAddress && targetAddress <
239						blockAddress + blockByteSize) {
240						highlightBlock = true;
241						highlightColor.set_to(216, 0, 0);
242				}
243
244				if (highlightBlock) {
245					PushState();
246					SetHighColor(highlightColor);
247				}
248
249				DrawString(buffer, drawPoint);
250
251				if (highlightBlock)
252					PopState();
253
254				drawPoint.x += fCharWidth * hexBlockSize;
255			}
256
257			if (currentBlocksPerLine < fHexBlocksPerLine)
258				drawPoint.x += fCharWidth * hexBlockSize
259					* (fHexBlocksPerLine - currentBlocksPerLine);
260		}
261
262		if (fTextMode != TextModeNone) {
263			drawPoint.x += fCharWidth;
264			for (int32 j = 0; j < currentCharsPerLine; j++) {
265				// filter non-printable characters
266				textbuffer[j] = currentAddress[j] > 32 ? currentAddress[j]
267					: '.';
268			}
269			textbuffer[fTextCharsPerLine] = '\0';
270			DrawString(textbuffer, drawPoint);
271			if (targetAddress >= currentAddress && targetAddress
272				< currentAddress + currentCharsPerLine) {
273				PushState();
274				SetHighColor(B_TRANSPARENT_COLOR);
275				SetDrawingMode(B_OP_INVERT);
276				uint32 blockAddress = uint32(targetAddress - currentAddress);
277				if (fHexMode != HexModeNone)
278					blockAddress &= ~(blockByteSize - 1);
279				float startX = drawPoint.x + fCharWidth * blockAddress;
280				float endX = startX;
281				if (fHexMode != HexModeNone)
282					endX += fCharWidth * ((hexBlockSize - 1) / 2);
283				else
284					endX += fCharWidth;
285				FillRect(BRect(startX, drawPoint.y - fh.ascent, endX,
286					drawPoint.y + fh.descent));
287				PopState();
288			}
289		}
290		if (currentBlocksPerLine > 0) {
291			currentAddress += currentBlocksPerLine * blockByteSize;
292			lineAddress += currentBlocksPerLine * blockByteSize;
293		} else {
294			currentAddress += fTextCharsPerLine;
295			lineAddress += fTextCharsPerLine;
296		}
297	}
298
299	if (fSelectionStart != fSelectionEnd) {
300		PushState();
301		BRegion selectionRegion;
302		_GetSelectionRegion(selectionRegion);
303		SetDrawingMode(B_OP_INVERT);
304		FillRegion(&selectionRegion, B_SOLID_HIGH);
305		PopState();
306	}
307
308	if (fEditMode) {
309		PushState();
310		BRect caretRect;
311		_GetEditCaretRect(caretRect);
312		SetDrawingMode(B_OP_INVERT);
313		FillRect(caretRect, B_SOLID_HIGH);
314		PopState();
315
316	}
317}
318
319
320void
321MemoryView::FrameResized(float width, float height)
322{
323	BView::FrameResized(width, height);
324	_RecalcScrollBars();
325	Invalidate();
326}
327
328
329void
330MemoryView::KeyDown(const char* bytes, int32 numBytes)
331{
332	bool handled = true;
333	if (fTargetBlock != NULL) {
334		target_addr_t newAddress = fTargetAddress;
335		target_addr_t maxAddress = fTargetBlock->BaseAddress()
336			+ fTargetBlock->Size() - 1;
337		int32 blockSize = 1;
338		if (fHexMode != HexModeNone)
339			blockSize = 1 << (fHexMode - 1);
340		int32 lineCount = int32(Bounds().Height() / fLineHeight);
341
342		switch(bytes[0]) {
343			case B_UP_ARROW:
344			{
345				newAddress -= blockSize * fHexBlocksPerLine;
346				break;
347			}
348			case B_DOWN_ARROW:
349			{
350				newAddress += blockSize * fHexBlocksPerLine;
351				break;
352			}
353			case B_LEFT_ARROW:
354			{
355				if (fEditMode) {
356					if (!fEditLowNybble)
357						newAddress--;
358					fEditLowNybble = !fEditLowNybble;
359					if (newAddress == fTargetAddress)
360						Invalidate();
361				} else
362					newAddress -= blockSize;
363				break;
364			}
365			case B_RIGHT_ARROW:
366			{
367				if (fEditMode) {
368					if (fEditLowNybble)
369						newAddress++;
370					fEditLowNybble = !fEditLowNybble;
371					if (newAddress == fTargetAddress)
372						Invalidate();
373				} else
374					newAddress += blockSize;
375				break;
376			}
377			case B_PAGE_UP:
378			{
379				newAddress -= (blockSize * fHexBlocksPerLine) * lineCount;
380				break;
381			}
382			case B_PAGE_DOWN:
383			{
384				newAddress += (blockSize * fHexBlocksPerLine) * lineCount;
385				break;
386			}
387			case B_HOME:
388			{
389				newAddress = fTargetBlock->BaseAddress();
390				fEditLowNybble = false;
391				break;
392			}
393			case B_END:
394			{
395				newAddress = maxAddress;
396				fEditLowNybble = true;
397				break;
398			}
399			default:
400			{
401				if (fEditMode && isxdigit(bytes[0]))
402				{
403					int value = 0;
404					if (isdigit(bytes[0]))
405						value = bytes[0] - '0';
406					else
407						value = (int)strtol(bytes, NULL, 16);
408
409					int32 byteOffset = fTargetAddress
410						- fTargetBlock->BaseAddress();
411
412					if (fEditLowNybble)
413						value = (fEditableData[byteOffset] & 0xf0) | value;
414					else {
415						value = (fEditableData[byteOffset] & 0x0f)
416							| (value << 4);
417					}
418
419					fEditableData[byteOffset] = value;
420
421					if (fEditableData[byteOffset]
422						!= fTargetBlock->Data()[byteOffset]) {
423						fEditedOffsets.insert(byteOffset);
424					} else
425						fEditedOffsets.erase(byteOffset);
426
427					if (fEditLowNybble) {
428						if (newAddress < maxAddress) {
429							newAddress++;
430							fEditLowNybble = false;
431						}
432					} else
433						fEditLowNybble = true;
434
435					Invalidate();
436				} else
437					handled = false;
438
439				break;
440			}
441		}
442
443		if (handled) {
444			if (newAddress < fTargetBlock->BaseAddress())
445				newAddress = fTargetAddress;
446			else if (newAddress > maxAddress)
447				newAddress = maxAddress;
448
449			if (newAddress != fTargetAddress) {
450				fTargetAddress = newAddress;
451				BMessenger(this).SendMessage(MSG_TARGET_ADDRESS_CHANGED);
452			}
453		}
454	} else
455		handled = false;
456
457	if (!handled)
458		BView::KeyDown(bytes, numBytes);
459}
460
461
462void
463MemoryView::MakeFocus(bool isFocused)
464{
465	BScrollView* parent = dynamic_cast<BScrollView*>(Parent());
466	if (parent != NULL)
467		parent->SetBorderHighlighted(isFocused);
468
469	BView::MakeFocus(isFocused);
470}
471
472
473void
474MemoryView::MessageReceived(BMessage* message)
475{
476	switch(message->what) {
477		case B_COPY:
478		{
479			_CopySelectionToClipboard();
480			break;
481		}
482		case MSG_TARGET_ADDRESS_CHANGED:
483		{
484			_RecalcScrollBars();
485			ScrollToSelection();
486			Invalidate();
487			if (fListener != NULL)
488				fListener->TargetAddressChanged(fTargetAddress);
489			break;
490		}
491		case MSG_SET_HEX_MODE:
492		{
493			// while editing, hex view changes are disallowed.
494			if (fEditMode)
495				break;
496
497			int32 mode;
498			if (message->FindInt32("mode", &mode) == B_OK) {
499				if (fHexMode == mode)
500					break;
501
502				fHexMode = mode;
503				_RecalcScrollBars();
504				Invalidate();
505
506				if (fListener != NULL)
507					fListener->HexModeChanged(mode);
508			}
509			break;
510		}
511		case MSG_SET_ENDIAN_MODE:
512		{
513			int32 mode;
514			if (message->FindInt32("mode", &mode) == B_OK) {
515				if (fCurrentEndianMode == mode)
516					break;
517
518				fCurrentEndianMode = mode;
519				Invalidate();
520
521				if (fListener != NULL)
522					fListener->EndianModeChanged(mode);
523			}
524			break;
525		}
526		case MSG_SET_TEXT_MODE:
527		{
528			int32 mode;
529			if (message->FindInt32("mode", &mode) == B_OK) {
530				if (fTextMode == mode)
531					break;
532
533				fTextMode = mode;
534				_RecalcScrollBars();
535				Invalidate();
536
537				if (fListener != NULL)
538					fListener->TextModeChanged(mode);
539			}
540			break;
541		}
542		case MSG_VIEW_AUTOSCROLL:
543		{
544			_HandleAutoScroll();
545			break;
546		}
547		default:
548		{
549			BView::MessageReceived(message);
550			break;
551		}
552	}
553}
554
555
556void
557MemoryView::MouseDown(BPoint point)
558{
559	if (!IsFocus())
560		MakeFocus(true);
561
562	if (fTargetBlock == NULL)
563		return;
564
565	int32 buttons;
566	if (Looper()->CurrentMessage()->FindInt32("buttons", &buttons) != B_OK)
567		buttons = B_PRIMARY_MOUSE_BUTTON;
568
569	if (buttons == B_SECONDARY_MOUSE_BUTTON) {
570		_HandleContextMenu(point);
571		return;
572	}
573
574	int32 offset = _GetOffsetAt(point);
575	if (offset < fSelectionStart || offset > fSelectionEnd) {
576		BRegion oldSelectionRegion;
577		_GetSelectionRegion(oldSelectionRegion);
578		fSelectionBase = offset;
579		fSelectionStart = fSelectionBase;
580		fSelectionEnd = fSelectionBase;
581		Invalidate(oldSelectionRegion.Frame());
582	}
583
584	SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
585	fTrackingMouse = true;
586}
587
588
589void
590MemoryView::MouseMoved(BPoint point, uint32 transit, const BMessage* message)
591{
592	if (!fTrackingMouse)
593		return;
594
595	BRegion oldSelectionRegion;
596	_GetSelectionRegion(oldSelectionRegion);
597	int32 offset = _GetOffsetAt(point);
598	if (offset < fSelectionBase) {
599		fSelectionStart = offset;
600		fSelectionEnd = fSelectionBase;
601	} else {
602		fSelectionStart = fSelectionBase;
603		fSelectionEnd = offset;
604	}
605
606	BRegion region;
607	_GetSelectionRegion(region);
608	region.Include(&oldSelectionRegion);
609	Invalidate(region.Frame());
610
611	switch (transit) {
612		case B_EXITED_VIEW:
613			fScrollRunner = new BMessageRunner(BMessenger(this),
614				new BMessage(MSG_VIEW_AUTOSCROLL), kScrollTimer);
615			break;
616
617		case B_ENTERED_VIEW:
618			delete fScrollRunner;
619			fScrollRunner = NULL;
620			break;
621
622		default:
623			break;
624	}
625}
626
627
628void
629MemoryView::MouseUp(BPoint point)
630{
631	fTrackingMouse = false;
632	delete fScrollRunner;
633	fScrollRunner = NULL;
634}
635
636
637void
638MemoryView::ScrollToSelection()
639{
640	if (fTargetBlock != NULL) {
641		target_addr_t offset = fTargetAddress - fTargetBlock->BaseAddress();
642		int32 lineNumber = 0;
643		if (fHexBlocksPerLine > 0) {
644			lineNumber = offset / (fHexBlocksPerLine
645				* (_GetHexDigitsPerBlock() / 2));
646		} else if (fTextCharsPerLine > 0)
647			lineNumber = offset / fTextCharsPerLine;
648
649		float y = lineNumber * fLineHeight;
650		if (y < Bounds().top)
651			ScrollTo(0.0, y);
652		else if (y + fLineHeight > Bounds().bottom)
653			ScrollTo(0.0, y + fLineHeight - Bounds().Height());
654	}
655}
656
657
658void
659MemoryView::TargetedByScrollView(BScrollView* scrollView)
660{
661	BView::TargetedByScrollView(scrollView);
662	scrollView->ScrollBar(B_VERTICAL)->SetRange(0.0, 0.0);
663}
664
665
666BSize
667MemoryView::MinSize()
668{
669	return BSize(0.0, 0.0);
670}
671
672
673BSize
674MemoryView::PreferredSize()
675{
676	return MinSize();
677}
678
679
680BSize
681MemoryView::MaxSize()
682{
683	return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
684}
685
686
687void
688MemoryView::_Init()
689{
690	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
691}
692
693
694void
695MemoryView::_RecalcScrollBars()
696{
697	float max = 0.0;
698	BScrollBar *scrollBar = ScrollBar(B_VERTICAL);
699	if (fTargetBlock != NULL) {
700		int32 hexDigits = _GetHexDigitsPerBlock();
701		int32 sizeFactor = 1 + hexDigits;
702		_RecalcBounds();
703
704		float hexWidth = fHexRight - fHexLeft;
705		int32 nybblesPerLine = int32(hexWidth / fCharWidth);
706		fHexBlocksPerLine = 0;
707		fTextCharsPerLine = 0;
708		if (fHexMode != HexModeNone) {
709			fHexBlocksPerLine = nybblesPerLine / sizeFactor;
710			fHexBlocksPerLine &= ~1;
711			fHexRight = fHexLeft + (fHexBlocksPerLine * sizeFactor
712				* fCharWidth);
713			if (fTextMode != TextModeNone)
714				fTextCharsPerLine = fHexBlocksPerLine * hexDigits / 2;
715		} else if (fTextMode != TextModeNone)
716			fTextCharsPerLine = int32((fTextRight - fTextLeft) / fCharWidth);
717		int32 lineCount = 0;
718		float totalHeight = 0.0;
719		if (fHexBlocksPerLine > 0) {
720			lineCount = fTargetBlock->Size() / (fHexBlocksPerLine
721					* hexDigits / 2);
722		} else if (fTextCharsPerLine > 0)
723			lineCount = fTargetBlock->Size() / fTextCharsPerLine;
724
725		totalHeight = lineCount * fLineHeight;
726		if (totalHeight > 0.0) {
727			BRect bounds = Bounds();
728			max = totalHeight - bounds.Height();
729			scrollBar->SetProportion(bounds.Height() / totalHeight);
730			scrollBar->SetSteps(fLineHeight, bounds.Height());
731		}
732	}
733	scrollBar->SetRange(0.0, max);
734}
735
736void
737MemoryView::_GetNextHexBlock(char* buffer, int32 bufferSize,
738	const char* address) const
739{
740	switch(fHexMode) {
741		case HexMode8BitInt:
742		{
743			snprintf(buffer, bufferSize, "%02" B_PRIx8,
744				*((const uint8*)address));
745			break;
746		}
747		case HexMode16BitInt:
748		{
749			uint16 data = *((const uint16*)address);
750			switch(fCurrentEndianMode)
751			{
752				case EndianModeBigEndian:
753				{
754					data = B_HOST_TO_BENDIAN_INT16(data);
755				}
756				break;
757
758				case EndianModeLittleEndian:
759				{
760					data = B_HOST_TO_LENDIAN_INT16(data);
761				}
762				break;
763			}
764			snprintf(buffer, bufferSize, "%04" B_PRIx16,
765				data);
766			break;
767		}
768		case HexMode32BitInt:
769		{
770			uint32 data = *((const uint32*)address);
771			switch(fCurrentEndianMode)
772			{
773				case EndianModeBigEndian:
774				{
775					data = B_HOST_TO_BENDIAN_INT32(data);
776				}
777				break;
778
779				case EndianModeLittleEndian:
780				{
781					data = B_HOST_TO_LENDIAN_INT32(data);
782				}
783				break;
784			}
785			snprintf(buffer, bufferSize, "%08" B_PRIx32,
786				data);
787			break;
788		}
789		case HexMode64BitInt:
790		{
791			uint64 data = *((const uint64*)address);
792			switch(fCurrentEndianMode)
793			{
794				case EndianModeBigEndian:
795				{
796					data = B_HOST_TO_BENDIAN_INT64(data);
797				}
798				break;
799
800				case EndianModeLittleEndian:
801				{
802					data = B_HOST_TO_LENDIAN_INT64(data);
803				}
804				break;
805			}
806			snprintf(buffer, bufferSize, "%0*" B_PRIx64,
807				16, data);
808			break;
809		}
810	}
811}
812
813
814int32
815MemoryView::_GetOffsetAt(BPoint point) const
816{
817	if (fTargetBlock == NULL)
818		return -1;
819
820	// TODO: support selection in the text region as well
821	if (fHexMode == HexModeNone)
822		return -1;
823
824	int32 lineNumber = int32(point.y / fLineHeight);
825	int32 charsPerBlock = _GetHexDigitsPerBlock() / 2;
826	int32 totalHexBlocks = fTargetBlock->Size() / charsPerBlock;
827	int32 lineCount = totalHexBlocks / fHexBlocksPerLine;
828
829	if (lineNumber >= lineCount)
830		return -1;
831
832	point.x -= fHexLeft;
833	if (point.x < 0)
834		point.x = 0;
835	else if (point.x > fHexRight)
836		point.x = fHexRight;
837
838	float blockWidth = (charsPerBlock * 2 + 1) * fCharWidth;
839	int32 containingBlock = int32(floor(point.x / blockWidth));
840
841	return fHexBlocksPerLine * charsPerBlock * lineNumber
842		+ containingBlock * charsPerBlock;
843}
844
845
846BPoint
847MemoryView::_GetPointForOffset(int32 offset) const
848{
849	BPoint point;
850	if (fHexMode == HexModeNone)
851		return point;
852
853	int32 bytesPerLine = fHexBlocksPerLine * _GetHexDigitsPerBlock() / 2;
854	int32 line = offset / bytesPerLine;
855	int32 lineOffset = offset % bytesPerLine;
856
857	point.x = fHexLeft + (lineOffset * 2 * fCharWidth)
858			+ (lineOffset * 2 * fCharWidth / _GetHexDigitsPerBlock());
859	point.y = line * fLineHeight;
860
861	return point;
862}
863
864
865void
866MemoryView::_RecalcBounds()
867{
868	fHexLeft = 0;
869	fHexRight = 0;
870	fTextLeft = 0;
871	fTextRight = 0;
872
873	// the left bound is determined by the space taken up by the actual
874	// displayed addresses.
875	float left = _GetAddressDisplayWidth();
876	float width = Bounds().Width() - left;
877
878	if (fHexMode != HexModeNone) {
879		int32 hexDigits = _GetHexDigitsPerBlock();
880		int32 sizeFactor = 1 + hexDigits;
881		if (fTextMode != TextModeNone) {
882			float hexProportion = sizeFactor / (float)(sizeFactor
883				+ hexDigits / 2);
884			float hexWidth = width * hexProportion;
885			fTextLeft = left + hexWidth;
886			fHexLeft = left;
887			// when sharing the display between hex and text,
888			// we allocate a 2 character space to separate the views
889			hexWidth -= 2 * fCharWidth;
890			fHexRight = left + hexWidth;
891		} else {
892			fHexLeft = left;
893			fHexRight = left + width;
894		}
895	} else if (fTextMode != TextModeNone) {
896		fTextLeft = left;
897		fTextRight = left + width;
898	}
899}
900
901
902float
903MemoryView::_GetAddressDisplayWidth() const
904{
905	return (fTargetAddressSize + 2) * fCharWidth;
906}
907
908
909void
910MemoryView::_GetEditCaretRect(BRect& rect) const
911{
912	if (!fEditMode)
913		return;
914
915	int32 byteOffset = fTargetAddress - fTargetBlock->BaseAddress();
916	BPoint point = _GetPointForOffset(byteOffset);
917	if (fEditLowNybble)
918		point.x += fCharWidth;
919
920	rect.left = point.x;
921	rect.right = point.x + fCharWidth;
922	rect.top = point.y;
923	rect.bottom = point.y + fLineHeight;
924}
925
926
927void
928MemoryView::_GetSelectionRegion(BRegion& region) const
929{
930	if (fHexMode == HexModeNone || fTargetBlock == NULL)
931		return;
932
933	region.MakeEmpty();
934	BPoint startPoint = _GetPointForOffset(fSelectionStart);
935	BPoint endPoint = _GetPointForOffset(fSelectionEnd);
936
937	BRect rect;
938	if (startPoint.y == endPoint.y) {
939		// single line case
940		rect.left = startPoint.x;
941		rect.top = startPoint.y;
942		rect.right = endPoint.x;
943		rect.bottom = endPoint.y + fLineHeight;
944		region.Include(rect);
945	} else {
946		float currentLine = startPoint.y;
947
948		// first line
949		rect.left = startPoint.x;
950		rect.top = startPoint.y;
951		rect.right = fHexRight;
952		rect.bottom = startPoint.y + fLineHeight;
953		region.Include(rect);
954		currentLine += fLineHeight;
955
956		// middle region
957		if (currentLine < endPoint.y) {
958			rect.left = fHexLeft;
959			rect.top = currentLine;
960			rect.right = fHexRight;
961			rect.bottom = endPoint.y;
962			region.Include(rect);
963		}
964
965		rect.left = fHexLeft;
966		rect.top = endPoint.y;
967		rect.right = endPoint.x;
968		rect.bottom = endPoint.y + fLineHeight;
969		region.Include(rect);
970	}
971}
972
973
974void
975MemoryView::_GetSelectedText(BString& text) const
976{
977	if (fSelectionStart == fSelectionEnd)
978		return;
979
980	text.Truncate(0);
981	const uint8* dataSource = fEditMode ? fEditableData : fTargetBlock->Data();
982
983	const char* data = (const char *)dataSource + fSelectionStart;
984	int16 blockSize = _GetHexDigitsPerBlock() / 2;
985	int32 count = (fSelectionEnd - fSelectionStart)
986		/ blockSize;
987
988	char buffer[32];
989	for (int32 i = 0; i < count; i++) {
990		_GetNextHexBlock(buffer, sizeof(buffer), data);
991		data += blockSize;
992		text << buffer;
993		if (i < count - 1)
994			text << " ";
995	}
996}
997
998
999void
1000MemoryView::_CopySelectionToClipboard()
1001{
1002	BString text;
1003	_GetSelectedText(text);
1004
1005	if (text.Length() > 0) {
1006		be_clipboard->Lock();
1007		be_clipboard->Data()->RemoveData("text/plain");
1008		be_clipboard->Data()->AddData ("text/plain",
1009			B_MIME_TYPE, text.String(), text.Length());
1010		be_clipboard->Commit();
1011		be_clipboard->Unlock();
1012	}
1013}
1014
1015
1016void
1017MemoryView::_HandleAutoScroll()
1018{
1019	BPoint point;
1020	uint32 buttons;
1021	GetMouse(&point, &buttons);
1022	float difference = 0.0;
1023	int factor = 0;
1024	BRect visibleRect = Bounds();
1025	if (point.y < visibleRect.top)
1026		difference = point.y - visibleRect.top;
1027	else if (point.y > visibleRect.bottom)
1028		difference = point.y - visibleRect.bottom;
1029	if (difference != 0.0) {
1030		factor = (int)(ceilf(difference / fLineHeight));
1031		_ScrollByLines(factor);
1032	}
1033
1034	MouseMoved(point, B_OUTSIDE_VIEW, NULL);
1035}
1036
1037
1038void
1039MemoryView::_ScrollByLines(int32 lineCount)
1040{
1041	BScrollBar* vertical = ScrollBar(B_VERTICAL);
1042	if (vertical == NULL)
1043		return;
1044
1045	float value = vertical->Value();
1046	vertical->SetValue(value + fLineHeight * lineCount);
1047}
1048
1049
1050void
1051MemoryView::_HandleContextMenu(BPoint point)
1052{
1053	int32 offset = _GetOffsetAt(point);
1054	if (offset < fSelectionStart || offset > fSelectionEnd)
1055		return;
1056
1057	BPopUpMenu* menu = new(std::nothrow) BPopUpMenu("Options");
1058	if (menu == NULL)
1059		return;
1060
1061	ObjectDeleter<BPopUpMenu> menuDeleter(menu);
1062	ObjectDeleter<BMenuItem> itemDeleter;
1063	ObjectDeleter<BMessage> messageDeleter;
1064	BMessage* message = NULL;
1065	BMenuItem* item = NULL;
1066	if (fSelectionEnd - fSelectionStart == fTargetAddressSize / 2) {
1067		BMessage* message = new(std::nothrow) BMessage(MSG_INSPECT_ADDRESS);
1068		if (message == NULL)
1069			return;
1070
1071		target_addr_t address;
1072		if (fTargetAddressSize == 8)
1073			address = *((uint32*)(fTargetBlock->Data() + fSelectionStart));
1074		else
1075			address = *((uint64*)(fTargetBlock->Data() + fSelectionStart));
1076
1077		if (fCurrentEndianMode == EndianModeBigEndian)
1078			address = B_HOST_TO_BENDIAN_INT64(address);
1079		else
1080			address = B_HOST_TO_LENDIAN_INT64(address);
1081
1082		messageDeleter.SetTo(message);
1083		message->AddUInt64("address", address);
1084		BMenuItem* item = new(std::nothrow) BMenuItem("Inspect", message);
1085		if (item == NULL)
1086			return;
1087
1088		messageDeleter.Detach();
1089		itemDeleter.SetTo(item);
1090		if (!menu->AddItem(item))
1091			return;
1092
1093		item->SetTarget(Looper());
1094		itemDeleter.Detach();
1095	}
1096
1097	message = new(std::nothrow) BMessage(B_COPY);
1098	if (message == NULL)
1099		return;
1100
1101	messageDeleter.SetTo(message);
1102	item = new(std::nothrow) BMenuItem("Copy", message);
1103	if (item == NULL)
1104		return;
1105
1106	messageDeleter.Detach();
1107	itemDeleter.SetTo(item);
1108	if (!menu->AddItem(item))
1109		return;
1110
1111	item->SetTarget(this);
1112	itemDeleter.Detach();
1113	menuDeleter.Detach();
1114
1115	BPoint screenWhere(point);
1116	ConvertToScreen(&screenWhere);
1117	BRect mouseRect(screenWhere, screenWhere);
1118	mouseRect.InsetBy(-4.0, -4.0);
1119	menu->Go(screenWhere, true, false, mouseRect, true);
1120}
1121
1122
1123status_t
1124MemoryView::_SetupEditableData()
1125{
1126	fEditableData = new(std::nothrow) uint8[fTargetBlock->Size()];
1127	if (fEditableData == NULL)
1128		return B_NO_MEMORY;
1129
1130	memcpy(fEditableData, fTargetBlock->Data(), fTargetBlock->Size());
1131
1132	if (fHexMode != HexMode8BitInt) {
1133		fHexMode = HexMode8BitInt;
1134		if (fListener != NULL)
1135			fListener->HexModeChanged(fHexMode);
1136
1137		_RecalcScrollBars();
1138	}
1139
1140	return B_OK;
1141}
1142
1143
1144//#pragma mark - Listener
1145
1146
1147MemoryView::Listener::~Listener()
1148{
1149}
1150