1/*
2 * Copyright 2011, 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 <stdio.h>
12
13#include <ByteOrder.h>
14#include <Looper.h>
15#include <Messenger.h>
16#include <ScrollView.h>
17#include <String.h>
18
19#include "Architecture.h"
20#include "Team.h"
21#include "TeamMemoryBlock.h"
22
23
24enum {
25	MSG_TARGET_ADDRESS_CHANGED = 'mtac'
26};
27
28
29MemoryView::MemoryView(::Team* team)
30	:
31	BView("memoryView", B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE
32		| B_SUBPIXEL_PRECISE),
33	fTeam(team),
34	fTargetBlock(NULL),
35	fTargetAddress(0LL),
36	fCharWidth(0.0),
37	fLineHeight(0.0),
38	fTextCharsPerLine(0),
39	fHexBlocksPerLine(0),
40	fHexMode(HexMode8BitInt),
41	fTextMode(TextModeASCII)
42{
43	Architecture* architecture = team->GetArchitecture();
44	fTargetAddressSize = architecture->AddressSize() * 2;
45	fCurrentEndianMode = architecture->IsBigEndian()
46		? EndianModeBigEndian : EndianModeLittleEndian;
47
48}
49
50
51MemoryView::~MemoryView()
52{
53	if (fTargetBlock != NULL)
54		fTargetBlock->ReleaseReference();
55}
56
57
58/*static */ MemoryView*
59MemoryView::Create(::Team* team)
60{
61	MemoryView* self = new MemoryView(team);
62
63	try {
64		self->_Init();
65	} catch(...) {
66		delete self;
67		throw;
68	}
69
70	return self;
71}
72
73
74void
75MemoryView::SetTargetAddress(TeamMemoryBlock* block, target_addr_t address)
76{
77	fTargetAddress = address;
78	if (fTargetBlock != NULL)
79		fTargetBlock->ReleaseReference();
80
81	fTargetBlock = block;
82	fTargetBlock->AcquireReference();
83	MakeFocus(true);
84	BMessenger(this).SendMessage(MSG_TARGET_ADDRESS_CHANGED);
85}
86
87
88void
89MemoryView::AttachedToWindow()
90{
91	BView::AttachedToWindow();
92	SetViewColor(ui_color(B_DOCUMENT_BACKGROUND_COLOR));
93	SetFont(be_fixed_font);
94	fCharWidth = be_fixed_font->StringWidth("a");
95	font_height fontHeight;
96	be_fixed_font->GetHeight(&fontHeight);
97	fLineHeight = ceilf(fontHeight.ascent + fontHeight.descent
98		+ fontHeight.leading);
99}
100
101
102void
103MemoryView::Draw(BRect rect)
104{
105	rect = Bounds();
106
107	float divider = (fTargetAddressSize + 1) * fCharWidth;
108	StrokeLine(BPoint(divider, rect.top),
109				BPoint(divider, rect.bottom));
110
111	if (fTargetBlock == NULL)
112		return;
113
114	uint32 hexBlockSize = (1 << fHexMode) + 1;
115	uint32 blockByteSize = hexBlockSize / 2;
116	if (fHexMode != HexModeNone && fTextMode != TextModeNone) {
117		divider += (fHexBlocksPerLine * hexBlockSize + 1) * fCharWidth;
118		StrokeLine(BPoint(divider, rect.top),
119					BPoint(divider, rect.bottom));
120	}
121
122	char buffer[32];
123	char textbuffer[512];
124
125	int32 startLine = int32(rect.top / fLineHeight);
126	const char* currentAddress = (const char*)(fTargetBlock->Data()
127		+ fHexBlocksPerLine * blockByteSize * startLine);
128	const char* maxAddress = (const char*)(fTargetBlock->Data()
129		+ fTargetBlock->Size());
130	const char* targetAddress = (const char *)fTargetBlock->Data()
131		+ fTargetAddress - fTargetBlock->BaseAddress();
132	BPoint drawPoint(1.0, (startLine + 1) * fLineHeight);
133	int32 currentBlocksPerLine = fHexBlocksPerLine;
134	int32 currentCharsPerLine = fTextCharsPerLine;
135	rgb_color addressColor = tint_color(HighColor(), B_LIGHTEN_1_TINT);
136	rgb_color dataColor = HighColor();
137	font_height fh;
138	GetFontHeight(&fh);
139	target_addr_t lineAddress = fTargetBlock->BaseAddress() + startLine
140		* currentCharsPerLine;
141	for (; currentAddress < maxAddress && drawPoint.y < rect.bottom
142		+ fLineHeight; drawPoint.y += fLineHeight) {
143		drawPoint.x = 1.0;
144		snprintf(buffer, sizeof(buffer), "%0*" B_PRIx64,
145			(int)fTargetAddressSize, lineAddress);
146		PushState();
147		SetHighColor(tint_color(HighColor(), B_LIGHTEN_1_TINT));
148		DrawString(buffer, drawPoint);
149		drawPoint.x += fCharWidth * (fTargetAddressSize + 2);
150		PopState();
151
152		if (fHexMode != HexModeNone) {
153			if (currentAddress + (currentBlocksPerLine * blockByteSize)
154				> maxAddress) {
155				currentCharsPerLine = maxAddress - currentAddress;
156				currentBlocksPerLine = currentCharsPerLine
157					/ blockByteSize;
158			}
159
160			for (int32 j = 0; j < currentBlocksPerLine; j++) {
161				const char* blockAddress = currentAddress + (j
162					* blockByteSize);
163				_GetNextHexBlock(buffer,
164					std::min((size_t)hexBlockSize, sizeof(buffer)),
165					blockAddress);
166				DrawString(buffer, drawPoint);
167				if (targetAddress >= blockAddress && targetAddress <
168					blockAddress + blockByteSize) {
169					PushState();
170					SetHighColor(B_TRANSPARENT_COLOR);
171					SetDrawingMode(B_OP_INVERT);
172					FillRect(BRect(drawPoint.x, drawPoint.y - fh.ascent,
173						drawPoint.x + (hexBlockSize - 1) * fCharWidth,
174						drawPoint.y + fh.descent));
175					PopState();
176				}
177
178				drawPoint.x += fCharWidth * hexBlockSize;
179			}
180
181			if (currentBlocksPerLine < fHexBlocksPerLine)
182				drawPoint.x += fCharWidth * hexBlockSize
183					* (fHexBlocksPerLine - currentBlocksPerLine);
184		}
185		if (fTextMode != TextModeNone) {
186			drawPoint.x += fCharWidth;
187			for (int32 j = 0; j < currentCharsPerLine; j++) {
188				// filter non-printable characters
189				textbuffer[j] = currentAddress[j] > 32 ? currentAddress[j]
190					: '.';
191			}
192			textbuffer[fTextCharsPerLine] = '\0';
193			DrawString(textbuffer, drawPoint);
194			if (targetAddress >= currentAddress && targetAddress
195				< currentAddress + currentCharsPerLine) {
196				PushState();
197				SetHighColor(B_TRANSPARENT_COLOR);
198				SetDrawingMode(B_OP_INVERT);
199				uint32 blockAddress = uint32(targetAddress - currentAddress);
200				if (fHexMode != HexModeNone)
201					blockAddress &= ~(blockByteSize - 1);
202				float startX = drawPoint.x + fCharWidth * blockAddress;
203				float endX = startX;
204				if (fHexMode != HexModeNone)
205					endX += fCharWidth * ((hexBlockSize - 1) / 2);
206				else
207					endX += fCharWidth;
208				FillRect(BRect(startX, drawPoint.y - fh.ascent, endX,
209					drawPoint.y + fh.descent));
210				PopState();
211			}
212		}
213		if (currentBlocksPerLine > 0) {
214			currentAddress += currentBlocksPerLine * blockByteSize;
215			lineAddress += currentBlocksPerLine * blockByteSize;
216		} else {
217			currentAddress += fTextCharsPerLine;
218			lineAddress += fTextCharsPerLine;
219		}
220	}
221}
222
223
224void
225MemoryView::FrameResized(float width, float height)
226{
227	BView::FrameResized(width, height);
228	_RecalcScrollBars();
229	Invalidate();
230}
231
232
233void
234MemoryView::KeyDown(const char* bytes, int32 numBytes)
235{
236	bool handled = true;
237	if (fTargetBlock != NULL) {
238		target_addr_t newAddress = fTargetAddress;
239		target_addr_t maxAddress = fTargetBlock->BaseAddress()
240			+ fTargetBlock->Size() - 1;
241		int32 blockSize = 1;
242		if (fHexMode != HexModeNone)
243			blockSize = 1 << (fHexMode - 1);
244		int32 lineCount = int32(Bounds().Height() / fLineHeight);
245
246		switch(bytes[0]) {
247			case B_UP_ARROW:
248			{
249				newAddress -= blockSize * fHexBlocksPerLine;
250				break;
251			}
252			case B_DOWN_ARROW:
253			{
254				newAddress += blockSize * fHexBlocksPerLine;
255				break;
256			}
257			case B_LEFT_ARROW:
258			{
259				newAddress -= blockSize;
260				break;
261			}
262			case B_RIGHT_ARROW:
263			{
264				newAddress += blockSize;
265				break;
266			}
267			case B_PAGE_UP:
268			{
269				newAddress -= (blockSize * fHexBlocksPerLine) * lineCount;
270				break;
271			}
272			case B_PAGE_DOWN:
273			{
274				newAddress += (blockSize * fHexBlocksPerLine) * lineCount;
275				break;
276			}
277			case B_HOME:
278			{
279				newAddress = fTargetBlock->BaseAddress();
280				break;
281			}
282			case B_END:
283			{
284				newAddress = maxAddress;
285				break;
286			}
287			default:
288			{
289				handled = false;
290				break;
291			}
292		}
293		if (handled) {
294			if (newAddress < fTargetBlock->BaseAddress())
295				newAddress = fTargetAddress;
296			else if (newAddress > maxAddress)
297				newAddress = maxAddress;
298
299			if (newAddress != fTargetAddress) {
300				fTargetAddress = newAddress;
301				BMessenger(this).SendMessage(MSG_TARGET_ADDRESS_CHANGED);
302			}
303		}
304	} else
305		handled = false;
306
307	if (!handled)
308		BView::KeyDown(bytes, numBytes);
309}
310
311
312void
313MemoryView::MakeFocus(bool isFocused)
314{
315	BScrollView* parent = dynamic_cast<BScrollView*>(Parent());
316	if (parent != NULL)
317		parent->SetBorderHighlighted(isFocused);
318
319	BView::MakeFocus(isFocused);
320}
321
322
323void
324MemoryView::MessageReceived(BMessage* message)
325{
326	switch(message->what) {
327		case MSG_TARGET_ADDRESS_CHANGED:
328		{
329			_RecalcScrollBars();
330			ScrollToSelection();
331			Invalidate();
332			break;
333		}
334		case MSG_SET_HEX_MODE:
335		{
336			int32 mode;
337			if (message->FindInt32("mode", &mode) == B_OK) {
338				fHexMode = mode;
339				_RecalcScrollBars();
340				Invalidate();
341			}
342			break;
343		}
344		case MSG_SET_ENDIAN_MODE:
345		{
346			int32 mode;
347			if (message->FindInt32("mode", &mode) == B_OK) {
348				fCurrentEndianMode = mode;
349				Invalidate();
350			}
351			break;
352		}
353		case MSG_SET_TEXT_MODE:
354		{
355			int32 mode;
356			if (message->FindInt32("mode", &mode) == B_OK) {
357				fTextMode = mode;
358				_RecalcScrollBars();
359				Invalidate();
360			}
361			break;
362		}
363		default:
364		{
365			BView::MessageReceived(message);
366			break;
367		}
368	}
369}
370
371
372void
373MemoryView::MouseDown(BPoint point)
374{
375	if (!IsFocus())
376		MakeFocus(true);
377
378	BView::MouseDown(point);
379}
380
381
382void
383MemoryView::ScrollToSelection()
384{
385	if (fTargetBlock != NULL) {
386		target_addr_t offset = fTargetAddress - fTargetBlock->BaseAddress();
387		int32 lineNumber = 0;
388		if (fHexBlocksPerLine > 0)
389			lineNumber = offset / (fHexBlocksPerLine * (1 << (fHexMode - 1)));
390		else if (fTextCharsPerLine > 0)
391			lineNumber = offset / fTextCharsPerLine;
392		float y = lineNumber * fLineHeight;
393		if (y < Bounds().top)
394			ScrollTo(0.0, y);
395		else if (y + fLineHeight > Bounds().bottom)
396			ScrollTo(0.0, y + fLineHeight - Bounds().Height());
397	}
398}
399
400
401void
402MemoryView::TargetedByScrollView(BScrollView* scrollView)
403{
404	BView::TargetedByScrollView(scrollView);
405	scrollView->ScrollBar(B_VERTICAL)->SetRange(0.0, 0.0);
406}
407
408
409void
410MemoryView::_Init()
411{
412	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
413}
414
415
416void
417MemoryView::_RecalcScrollBars()
418{
419	float max = 0.0;
420	BScrollBar *scrollBar = ScrollBar(B_VERTICAL);
421	if (fTargetBlock != NULL) {
422		BRect bounds = Bounds();
423		// the left portion of the view is off limits since it
424		// houses the address offset of the current line
425		float baseWidth = bounds.Width() - ((fTargetAddressSize + 2)
426			* fCharWidth);
427		float hexWidth = 0.0;
428		float textWidth = 0.0;
429		int32 hexDigits = 1 << fHexMode;
430		int32 sizeFactor = 1 + hexDigits;
431		if (fHexMode != HexModeNone) {
432			if (fTextMode != TextModeNone) {
433				float hexProportion = sizeFactor / (float)(sizeFactor
434					+ hexDigits / 2);
435				hexWidth = baseWidth * hexProportion;
436				// when sharing the display between hex and text,
437				// we allocate a 2 character space to separate the views
438				hexWidth -= 2 * fCharWidth;
439				textWidth = baseWidth - hexWidth;
440			} else
441				hexWidth = baseWidth;
442		} else if (fTextMode != TextModeNone)
443			textWidth = baseWidth;
444
445		int32 nybblesPerLine = int32(hexWidth / fCharWidth);
446		fHexBlocksPerLine = 0;
447		fTextCharsPerLine = 0;
448		if (fHexMode != HexModeNone) {
449			fHexBlocksPerLine = nybblesPerLine / sizeFactor;
450			fHexBlocksPerLine &= ~1;
451			if (fTextMode != TextModeNone)
452				fTextCharsPerLine = fHexBlocksPerLine * hexDigits / 2;
453		} else if (fTextMode != TextModeNone)
454			fTextCharsPerLine = int32(textWidth / fCharWidth);
455
456		int32 lineCount = 0;
457		float totalHeight = 0.0;
458		if (fHexBlocksPerLine > 0) {
459			lineCount = fTargetBlock->Size() / (fHexBlocksPerLine
460					* hexDigits / 2);
461		} else if (fTextCharsPerLine > 0)
462			lineCount = fTargetBlock->Size() / fTextCharsPerLine;
463
464		totalHeight = lineCount * fLineHeight;
465		if (totalHeight > 0.0) {
466			max = totalHeight - bounds.Height();
467			scrollBar->SetProportion(bounds.Height() / totalHeight);
468			scrollBar->SetSteps(fLineHeight, bounds.Height());
469		}
470	}
471	scrollBar->SetRange(0.0, max);
472}
473
474void
475MemoryView::_GetNextHexBlock(char* buffer, int32 bufferSize,
476	const char* address)
477{
478	switch(fHexMode) {
479		case HexMode8BitInt:
480		{
481			snprintf(buffer, bufferSize, "%02" B_PRIx8, *address);
482			break;
483		}
484		case HexMode16BitInt:
485		{
486			uint16 data = *((const uint16*)address);
487			switch(fCurrentEndianMode)
488			{
489				case EndianModeBigEndian:
490				{
491					data = B_HOST_TO_BENDIAN_INT16(data);
492				}
493				break;
494
495				case EndianModeLittleEndian:
496				{
497					data = B_HOST_TO_LENDIAN_INT16(data);
498				}
499				break;
500			}
501			snprintf(buffer, bufferSize, "%04" B_PRIx16,
502				data);
503			break;
504		}
505		case HexMode32BitInt:
506		{
507			uint32 data = *((const uint32*)address);
508			switch(fCurrentEndianMode)
509			{
510				case EndianModeBigEndian:
511				{
512					data = B_HOST_TO_BENDIAN_INT32(data);
513				}
514				break;
515
516				case EndianModeLittleEndian:
517				{
518					data = B_HOST_TO_LENDIAN_INT32(data);
519				}
520				break;
521			}
522			snprintf(buffer, bufferSize, "%08" B_PRIx32,
523				data);
524			break;
525		}
526		case HexMode64BitInt:
527		{
528			uint64 data = *((const uint16*)address);
529			switch(fCurrentEndianMode)
530			{
531				case EndianModeBigEndian:
532				{
533					data = B_HOST_TO_BENDIAN_INT64(data);
534				}
535				break;
536
537				case EndianModeLittleEndian:
538				{
539					data = B_HOST_TO_LENDIAN_INT64(data);
540				}
541				break;
542			}
543			snprintf(buffer, bufferSize, "%0*" B_PRIx64,
544				16, data);
545			break;
546		}
547	}
548}
549