all repos — mgba @ a3ee07a6cc20512e3db26d84a23adfa5b8d0f52b

mGBA Game Boy Advance Emulator

src/platform/qt/MemoryModel.cpp (view raw)

  1/* Copyright (c) 2013-2015 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include "MemoryModel.h"
  7
  8#include "GBAApp.h"
  9#include "GameController.h"
 10#include "LogController.h"
 11#include "VFileDevice.h"
 12
 13#include <QAction>
 14#include <QApplication>
 15#include <QClipboard>
 16#include <QFontMetrics>
 17#include <QPainter>
 18#include <QScrollBar>
 19#include <QSlider>
 20#include <QWheelEvent>
 21
 22extern "C" {
 23#include "core/core.h"
 24}
 25
 26using namespace QGBA;
 27
 28MemoryModel::MemoryModel(QWidget* parent)
 29	: QAbstractScrollArea(parent)
 30	, m_core(nullptr)
 31	, m_top(0)
 32	, m_align(1)
 33	, m_selection(0, 0)
 34	, m_selectionAnchor(0)
 35	, m_codec(nullptr)
 36{
 37	m_font.setFamily("Source Code Pro");
 38	m_font.setStyleHint(QFont::Monospace);
 39#ifdef Q_OS_MAC
 40	m_font.setPointSize(12);
 41#else
 42	m_font.setPointSize(10);
 43#endif
 44	QFontMetrics metrics(m_font);
 45	m_cellHeight = metrics.height();
 46	m_letterWidth = metrics.averageCharWidth();
 47
 48	setFocusPolicy(Qt::StrongFocus);
 49	setContextMenuPolicy(Qt::ActionsContextMenu);
 50
 51	QAction* copy = new QAction(tr("Copy selection"), this);
 52	copy->setShortcut(QKeySequence::Copy);
 53	connect(copy, SIGNAL(triggered()), this, SLOT(copy()));
 54	addAction(copy);
 55
 56	QAction* save = new QAction(tr("Save selection"), this);
 57	save->setShortcut(QKeySequence::Save);
 58	connect(save, SIGNAL(triggered()), this, SLOT(save()));
 59	addAction(save);
 60
 61	QAction* paste = new QAction(tr("Paste"), this);
 62	paste->setShortcut(QKeySequence::Paste);
 63	connect(paste, SIGNAL(triggered()), this, SLOT(paste()));
 64	addAction(paste);
 65
 66	QAction* load = new QAction(tr("Load"), this);
 67	load->setShortcut(QKeySequence::Open);
 68	connect(load, SIGNAL(triggered()), this, SLOT(load()));
 69	addAction(load);
 70
 71	static QString arg("%0");
 72	for (int i = 0; i < 256; ++i) {
 73		QStaticText str(arg.arg(i, 2, 16, QChar('0')).toUpper());
 74		str.prepare(QTransform(), m_font);
 75		m_staticNumbers.append(str);
 76	}
 77
 78	for (int i = 0; i < 128; ++i) {
 79		QChar c(i);
 80		if (!c.isPrint()) {
 81			c = '.';
 82		}
 83		QStaticText str = QStaticText(QString(c));
 84		str.prepare(QTransform(), m_font);
 85		m_staticAscii.append(str);
 86	}
 87
 88	setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
 89	setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
 90	m_margins = QMargins(metrics.width("0FFFFFF0 ") + 3, m_cellHeight + 1, metrics.width(" AAAAAAAAAAAAAAAA") + 3, 0);
 91	m_cellSize = QSizeF((viewport()->size().width() - (m_margins.left() + m_margins.right())) / 16.0, m_cellHeight);
 92
 93	connect(verticalScrollBar(), &QSlider::sliderMoved, [this](int position) {
 94		m_top = position;
 95		update();
 96	});
 97
 98	setRegion(0, 0x10000000, tr("All"));
 99}
100
101void MemoryModel::setController(GameController* controller) {
102	m_core = controller->thread()->core;
103}
104
105void MemoryModel::setRegion(uint32_t base, uint32_t size, const QString& name, int segment) {
106	m_top = 0;
107	m_base = base;
108	m_size = size;
109	m_regionName = name;
110	m_regionName.prepare(QTransform(), m_font);
111	m_currentBank = segment;
112	verticalScrollBar()->setRange(0, (size >> 4) + 1 - viewport()->size().height() / m_cellHeight);
113	verticalScrollBar()->setValue(0);
114	viewport()->update();
115}
116
117void MemoryModel::setSegment(int segment) {
118	m_currentBank = segment;
119	viewport()->update();
120}
121
122void MemoryModel::setAlignment(int width) {
123	if (width != 1 && width != 2 && width != 4) {
124		return;
125	}
126	m_align = width;
127	m_buffer = 0;
128	m_bufferedNybbles = 0;
129	viewport()->update();
130}
131
132void MemoryModel::loadTBL(const QString& path) {
133	VFile* vf = VFileDevice::open(path, O_RDONLY);
134	if (!vf) {
135		return;
136	}
137	m_codec = std::unique_ptr<TextCodec, TextCodecFree>(new TextCodec);
138	TextCodecLoadTBL(m_codec.get(), vf, true);
139	vf->close(vf);
140}
141
142void MemoryModel::loadTBL() {
143	QString filename = GBAApp::app()->getOpenFileName(this, tr("Load TBL"));
144	if (filename.isNull()) {
145		return;
146	}
147	loadTBL(filename);
148}
149
150void MemoryModel::jumpToAddress(const QString& hex) {
151	bool ok = false;
152	uint32_t i = hex.toInt(&ok, 16);
153	if (ok) {
154		jumpToAddress(i);
155	}
156}
157
158void MemoryModel::jumpToAddress(uint32_t address) {
159	if (address >= 0x10000000) {
160		return;
161	}
162	if (address < m_base || address >= m_base + m_size) {
163		setRegion(0, 0x10000000, tr("All"));
164	}
165	m_top = (address - m_base) / 16;
166	boundsCheck();
167	verticalScrollBar()->setValue(m_top);
168	m_buffer = 0;
169	m_bufferedNybbles = 0;
170}
171
172void MemoryModel::copy() {
173	QClipboard* clipboard = QApplication::clipboard();
174	if (!clipboard) {
175		return;
176	}
177	QByteArray bytestring(serialize());
178	QString string;
179	string.reserve(bytestring.size() * 2);
180	static QString arg("%0");
181	static QChar c0('0');
182	for (uchar c : bytestring) {
183		string.append(arg.arg(c, 2, 16, c0).toUpper());
184	}
185	clipboard->setText(string);
186}
187
188void MemoryModel::paste() {
189	QClipboard* clipboard = QApplication::clipboard();
190	if (!clipboard) {
191		return;
192	}
193	QString string = clipboard->text();
194	if (string.isEmpty()) {
195		return;
196	}
197	QByteArray bytestring(QByteArray::fromHex(string.toLocal8Bit()));
198	deserialize(bytestring);
199	viewport()->update();
200}
201
202void MemoryModel::save() {
203	QString filename = GBAApp::app()->getSaveFileName(this, tr("Save selected memory"));
204	if (filename.isNull()) {
205		return;
206	}
207	QFile outfile(filename);
208	if (!outfile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
209		LOG(QT, WARN) << tr("Failed to open output file: %1").arg(filename);
210		return;
211	}
212	QByteArray out(serialize());
213	outfile.write(out);
214}
215
216void MemoryModel::load() {
217	QString filename = GBAApp::app()->getOpenFileName(this, tr("Load memory"));
218	if (filename.isNull()) {
219		return;
220	}
221	QFile infile(filename);
222	if (!infile.open(QIODevice::ReadOnly)) {
223		LOG(QT, WARN) << tr("Failed to open input file: %1").arg(filename);
224		return;
225	}
226	QByteArray bytestring(infile.readAll());
227	deserialize(bytestring);
228	viewport()->update();
229}
230
231QByteArray MemoryModel::serialize() {
232	QByteArray bytes;
233	bytes.reserve(m_selection.second - m_selection.first);
234	switch (m_align) {
235	case 1:
236		for (uint32_t i = m_selection.first; i < m_selection.second; i += m_align) {
237			char datum = m_core->rawRead8(m_core, i, m_currentBank);
238			bytes.append(datum);
239		}
240		break;
241	case 2:
242		for (uint32_t i = m_selection.first; i < m_selection.second; i += m_align) {
243			quint16 datum = m_core->rawRead16(m_core, i, m_currentBank);
244			char leDatum[2];
245			STORE_16LE(datum, 0, (uint16_t*) leDatum);
246			bytes.append(leDatum, 2);
247		}
248		break;
249	case 4:
250		for (uint32_t i = m_selection.first; i < m_selection.second; i += m_align) {
251			quint32 datum = m_core->rawRead32(m_core, i, m_currentBank);
252			char leDatum[4];
253			STORE_32LE(datum, 0, (uint16_t*) leDatum);
254			bytes.append(leDatum, 4);
255		}
256		break;
257	}
258	return bytes;
259}
260
261void MemoryModel::deserialize(const QByteArray& bytes) {
262	uint32_t addr = m_selection.first;
263	switch (m_align) {
264	case 1:
265		for (int i = 0; i < bytes.size(); i += m_align, addr += m_align) {
266			uint8_t datum = bytes[i];
267			m_core->rawWrite8(m_core, addr, m_currentBank, datum);
268		}
269		break;
270	case 2:
271		for (int i = 0; i < bytes.size(); i += m_align, addr += m_align) {
272			char leDatum[2]{ bytes[i], bytes[i + 1] };
273			uint16_t datum;
274			LOAD_16LE(datum, 0, leDatum);
275			m_core->rawWrite16(m_core, addr, m_currentBank, datum);
276		}
277		break;
278	case 4:
279		for (int i = 0; i < bytes.size(); i += m_align, addr += m_align) {
280			char leDatum[4]{ bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3] };
281			uint32_t datum;
282			LOAD_32LE(datum, 0, leDatum);
283			m_core->rawWrite32(m_core, addr, m_currentBank, datum);
284		}
285		break;
286	}
287}
288
289QString MemoryModel::decodeText(const QByteArray& bytes) {
290	QString text;
291	if (m_codec) {
292		TextCodecIterator iter;
293		TextCodecStartDecode(m_codec.get(), &iter);
294		uint8_t lineBuffer[128];
295		for (quint8 byte : bytes) {
296			size_t size = TextCodecAdvance(&iter, byte, lineBuffer, sizeof(lineBuffer));
297			if (size > sizeof(lineBuffer)) {
298				size = sizeof(lineBuffer);
299			}
300			for (size_t i = 0; i < size; ++i) {
301				text.append(lineBuffer[i]);
302			}
303		}
304		size_t size = TextCodecFinish(&iter, lineBuffer, sizeof(lineBuffer));
305		if (size > sizeof(lineBuffer)) {
306			size = sizeof(lineBuffer);
307		}
308		for (size_t i = 0; i < size; ++i) {
309			text.append(lineBuffer[i]);
310		}
311	} else {
312		for (QChar c : bytes) {
313			if (!c.isPrint() || c >= 127) {
314				continue;
315			}
316			text.append(c);
317		}
318	}
319	return text;
320}
321
322void MemoryModel::resizeEvent(QResizeEvent*) {
323	m_cellSize = QSizeF((viewport()->size().width() - (m_margins.left() + m_margins.right())) / 16.0, m_cellHeight);
324	verticalScrollBar()->setRange(0, (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight);
325	boundsCheck();
326}
327
328void MemoryModel::paintEvent(QPaintEvent* event) {
329	QPainter painter(viewport());
330	QPalette palette;
331	painter.setFont(m_font);
332	painter.setPen(palette.color(QPalette::WindowText));
333	static QChar c0('0');
334	static QString arg("%0");
335	static QString arg2("%0:%1");
336	QSizeF letterSize = QSizeF(m_letterWidth, m_cellHeight);
337	painter.drawStaticText(QPointF((m_margins.left() - m_regionName.size().width() - 1) / 2.0, 0), m_regionName);
338	painter.drawText(
339	    QRect(QPoint(viewport()->size().width() - m_margins.right(), 0), QSize(m_margins.right(), m_margins.top())),
340	    Qt::AlignHCenter, m_codec ? tr("TBL") : tr("ASCII"));
341	for (int x = 0; x < 16; ++x) {
342		painter.drawText(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), 0), m_cellSize), Qt::AlignHCenter,
343		                 QString::number(x, 16).toUpper());
344	}
345	int height = (viewport()->size().height() - m_cellHeight) / m_cellHeight;
346	for (int y = 0; y < height; ++y) {
347		int yp = m_cellHeight * y + m_margins.top();
348		if ((y + m_top) * 16 >= m_size) {
349			break;
350		}
351		QString data;
352		if (m_currentBank >= 0) {
353			data = arg2.arg(m_currentBank, 2, 16, c0).arg((y + m_top) * 16 + m_base, 4, 16, c0).toUpper();
354		} else {
355			data = arg.arg((y + m_top) * 16 + m_base, 8, 16, c0).toUpper();
356		}
357		painter.drawText(QRectF(QPointF(0, yp), QSizeF(m_margins.left(), m_cellHeight)), Qt::AlignHCenter, data);
358		switch (m_align) {
359		case 2:
360			for (int x = 0; x < 16; x += 2) {
361				uint32_t address = (y + m_top) * 16 + x + m_base;
362				if (address >= m_base + m_size) {
363					break;
364				}
365				if (isInSelection(address)) {
366					painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp),
367					                        QSizeF(m_cellSize.width() * 2, m_cellSize.height())),
368					                 palette.highlight());
369					painter.setPen(palette.color(QPalette::HighlightedText));
370					if (isEditing(address)) {
371						drawEditingText(
372						    painter,
373						    QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp));
374						continue;
375					}
376				} else {
377					painter.setPen(palette.color(QPalette::WindowText));
378				}
379				uint16_t b = m_core->rawRead16(m_core, address, m_currentBank);
380				painter.drawStaticText(
381				    QPointF(m_cellSize.width() * (x + 1.0) - 2 * m_letterWidth + m_margins.left(), yp),
382				    m_staticNumbers[(b >> 8) & 0xFF]);
383				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 1.0) + m_margins.left(), yp),
384				                       m_staticNumbers[b & 0xFF]);
385			}
386			break;
387		case 4:
388			for (int x = 0; x < 16; x += 4) {
389				uint32_t address = (y + m_top) * 16 + x + m_base;
390				if (address >= m_base + m_size) {
391					break;
392				}
393				if (isInSelection(address)) {
394					painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp),
395					                        QSizeF(m_cellSize.width() * 4, m_cellSize.height())),
396					                 palette.highlight());
397					painter.setPen(palette.color(QPalette::HighlightedText));
398					if (isEditing(address)) {
399						drawEditingText(
400						    painter,
401						    QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp));
402						continue;
403					}
404				} else {
405					painter.setPen(palette.color(QPalette::WindowText));
406				}
407				uint32_t b = m_core->rawRead32(m_core, address, m_currentBank);
408				painter.drawStaticText(
409				    QPointF(m_cellSize.width() * (x + 2.0) - 4 * m_letterWidth + m_margins.left(), yp),
410				    m_staticNumbers[(b >> 24) & 0xFF]);
411				painter.drawStaticText(
412				    QPointF(m_cellSize.width() * (x + 2.0) - 2 * m_letterWidth + m_margins.left(), yp),
413				    m_staticNumbers[(b >> 16) & 0xFF]);
414				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 2.0) + m_margins.left(), yp),
415				                       m_staticNumbers[(b >> 8) & 0xFF]);
416				painter.drawStaticText(
417				    QPointF(m_cellSize.width() * (x + 2.0) + 2 * m_letterWidth + m_margins.left(), yp),
418				    m_staticNumbers[b & 0xFF]);
419			}
420			break;
421		case 1:
422		default:
423			for (int x = 0; x < 16; ++x) {
424				uint32_t address = (y + m_top) * 16 + x + m_base;
425				if (address >= m_base + m_size) {
426					break;
427				}
428				if (isInSelection(address)) {
429					painter.fillRect(QRectF(QPointF(m_cellSize.width() * x + m_margins.left(), yp), m_cellSize),
430					                 palette.highlight());
431					painter.setPen(palette.color(QPalette::HighlightedText));
432					if (isEditing(address)) {
433						drawEditingText(painter,
434						                QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp));
435						continue;
436					}
437				} else {
438					painter.setPen(palette.color(QPalette::WindowText));
439				}
440				uint8_t b = m_core->rawRead8(m_core, address, m_currentBank);
441				painter.drawStaticText(QPointF(m_cellSize.width() * (x + 0.5) - m_letterWidth + m_margins.left(), yp),
442				                       m_staticNumbers[b]);
443			}
444			break;
445		}
446		painter.setPen(palette.color(QPalette::WindowText));
447		for (int x = 0; x < 16; ++x) {
448			uint8_t b = m_core->rawRead8(m_core, (y + m_top) * 16 + x + m_base, m_currentBank);
449			QString text = decodeText(QByteArray(1, b));
450			if (text.isEmpty()) {
451				b = '.';
452			} else {
453				QChar c = text.at(0);
454				if (!c.isPrint() || c >= 127) {
455					b = '.';
456				} else {
457					b = c.toLatin1();
458				}
459			}
460			painter.drawStaticText(
461			    QPointF(viewport()->size().width() - (16 - x) * m_margins.right() / 17.0 - m_letterWidth * 0.5, yp), m_staticAscii[b]);
462		}
463	}
464	painter.drawLine(m_margins.left(), 0, m_margins.left(), viewport()->size().height());
465	painter.drawLine(viewport()->size().width() - m_margins.right(), 0, viewport()->size().width() - m_margins.right(),
466	                 viewport()->size().height());
467	painter.drawLine(0, m_margins.top(), viewport()->size().width(), m_margins.top());
468}
469
470void MemoryModel::wheelEvent(QWheelEvent* event) {
471	m_top -= event->angleDelta().y() / 8;
472	boundsCheck();
473	event->accept();
474	verticalScrollBar()->setValue(m_top);
475	update();
476}
477
478void MemoryModel::mousePressEvent(QMouseEvent* event) {
479	if (event->x() < m_margins.left() || event->y() < m_margins.top() ||
480	    event->x() > size().width() - m_margins.right()) {
481		m_selection = qMakePair(0, 0);
482		return;
483	}
484
485	QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top()));
486	uint32_t address = int(position.x() / m_cellSize.width()) +
487	                   (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
488	if (event->button() == Qt::RightButton && isInSelection(address)) {
489		return;
490	}
491	if (event->modifiers() & Qt::ShiftModifier) {
492		if ((address & ~(m_align - 1)) < m_selectionAnchor) {
493			m_selection = qMakePair(address & ~(m_align - 1), m_selectionAnchor + m_align);
494		} else {
495			m_selection = qMakePair(m_selectionAnchor, (address & ~(m_align - 1)) + m_align);
496		}
497	} else {
498		m_selectionAnchor = address & ~(m_align - 1);
499		m_selection = qMakePair(m_selectionAnchor, m_selectionAnchor + m_align);
500	}
501	m_buffer = 0;
502	m_bufferedNybbles = 0;
503	emit selectionChanged(m_selection.first, m_selection.second);
504	viewport()->update();
505}
506
507void MemoryModel::mouseMoveEvent(QMouseEvent* event) {
508	if (event->x() < m_margins.left() || event->y() < m_margins.top() ||
509	    event->x() > size().width() - m_margins.right()) {
510		return;
511	}
512
513	QPoint position(event->pos() - QPoint(m_margins.left(), m_margins.top()));
514	uint32_t address = int(position.x() / m_cellSize.width()) +
515	                   (int(position.y() / m_cellSize.height()) + m_top) * 16 + m_base;
516	if ((address & ~(m_align - 1)) < m_selectionAnchor) {
517		m_selection = qMakePair(address & ~(m_align - 1), m_selectionAnchor + m_align);
518	} else {
519		m_selection = qMakePair(m_selectionAnchor, (address & ~(m_align - 1)) + m_align);
520	}
521	m_buffer = 0;
522	m_bufferedNybbles = 0;
523	emit selectionChanged(m_selection.first, m_selection.second);
524	viewport()->update();
525}
526
527void MemoryModel::keyPressEvent(QKeyEvent* event) {
528	if (m_selection.first >= m_selection.second) {
529		return;
530	}
531	int key = event->key();
532	uint8_t nybble = 0;
533	switch (key) {
534	case Qt::Key_0:
535	case Qt::Key_1:
536	case Qt::Key_2:
537	case Qt::Key_3:
538	case Qt::Key_4:
539	case Qt::Key_5:
540	case Qt::Key_6:
541	case Qt::Key_7:
542	case Qt::Key_8:
543	case Qt::Key_9:
544		nybble = key - Qt::Key_0;
545		break;
546	case Qt::Key_A:
547	case Qt::Key_B:
548	case Qt::Key_C:
549	case Qt::Key_D:
550	case Qt::Key_E:
551	case Qt::Key_F:
552		nybble = key - Qt::Key_A + 10;
553		break;
554	case Qt::Key_Left:
555		adjustCursor(-m_align, event->modifiers() & Qt::ShiftModifier);
556		return;
557	case Qt::Key_Right:
558		adjustCursor(m_align, event->modifiers() & Qt::ShiftModifier);
559		return;
560	case Qt::Key_Up:
561		adjustCursor(-16, event->modifiers() & Qt::ShiftModifier);
562		return;
563	case Qt::Key_Down:
564		adjustCursor(16, event->modifiers() & Qt::ShiftModifier);
565		return;
566	default:
567		return;
568	}
569	m_buffer <<= 4;
570	m_buffer |= nybble;
571	++m_bufferedNybbles;
572	if (m_bufferedNybbles == m_align * 2) {
573		switch (m_align) {
574		case 1:
575			m_core->rawWrite8(m_core, m_selection.first, m_currentBank, m_buffer);
576			break;
577		case 2:
578			m_core->rawWrite16(m_core, m_selection.first, m_currentBank, m_buffer);
579			break;
580		case 4:
581			m_core->rawWrite32(m_core, m_selection.first, m_currentBank, m_buffer);
582			break;
583		}
584		m_bufferedNybbles = 0;
585		m_buffer = 0;
586		m_selection.first += m_align;
587		if (m_selection.second <= m_selection.first) {
588			m_selection.second = m_selection.first + m_align;
589		}
590		emit selectionChanged(m_selection.first, m_selection.second);
591	}
592	viewport()->update();
593}
594
595void MemoryModel::boundsCheck() {
596	if (m_top < 0) {
597		m_top = 0;
598	} else if (m_top > (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight) {
599		m_top = (m_size >> 4) + 1 - viewport()->size().height() / m_cellHeight;
600	}
601}
602
603bool MemoryModel::isInSelection(uint32_t address) {
604	if (m_selection.first == m_selection.second) {
605		return false;
606	}
607	if (m_selection.second <= (address | (m_align - 1))) {
608		return false;
609	}
610	if (m_selection.first <= (address & ~(m_align - 1))) {
611		return true;
612	}
613	return false;
614}
615
616bool MemoryModel::isEditing(uint32_t address) {
617	return m_bufferedNybbles && m_selection.first == (address & ~(m_align - 1));
618}
619
620void MemoryModel::drawEditingText(QPainter& painter, const QPointF& origin) {
621	QPointF o(origin);
622	for (int nybbles = m_bufferedNybbles; nybbles > 0; nybbles -= 2) {
623		if (nybbles > 1) {
624			uint8_t b = m_buffer >> ((nybbles - 2) * 4);
625			painter.drawStaticText(o, m_staticNumbers[b]);
626		} else {
627			int b = m_buffer & 0xF;
628			if (b < 10) {
629				painter.drawStaticText(o, m_staticAscii[b + '0']);
630			} else {
631				painter.drawStaticText(o, m_staticAscii[b - 10 + 'A']);
632			}
633		}
634		o += QPointF(m_letterWidth * 2, 0);
635	}
636}
637
638void MemoryModel::adjustCursor(int adjust, bool shift) {
639	if (m_selection.first >= m_selection.second) {
640		return;
641	}
642	int cursorPosition = m_top;
643	if (shift) {
644		if (m_selectionAnchor == m_selection.first) {
645			if (adjust < 0 && m_base - adjust > m_selection.second) {
646				adjust = m_base - m_selection.second + m_align;
647			} else if (adjust > 0 && m_selection.second + adjust >= m_base + m_size) {
648				adjust = m_base + m_size - m_selection.second;
649			}
650			adjust += m_selection.second;
651			if (adjust <= m_selection.first) {
652				m_selection.second = m_selection.first + m_align;
653				m_selection.first = adjust - m_align;
654				cursorPosition = m_selection.first;
655			} else {
656				m_selection.second = adjust;
657				cursorPosition = m_selection.second - m_align;
658			}
659		} else {
660			if (adjust < 0 && m_base - adjust > m_selection.first) {
661				adjust = m_base - m_selection.first;
662			} else if (adjust > 0 && m_selection.first + adjust >= m_base + m_size) {
663				adjust = m_base + m_size - m_selection.first - m_align;
664			}
665			adjust += m_selection.first;
666			if (adjust >= m_selection.second) {
667				m_selection.first = m_selection.second - m_align;
668				m_selection.second = adjust + m_align;
669				cursorPosition = adjust;
670			} else {
671				m_selection.first = adjust;
672				cursorPosition = m_selection.first;
673			}
674		}
675		cursorPosition = (cursorPosition - m_base) / 16;
676	} else {
677		if (m_selectionAnchor == m_selection.first) {
678			m_selectionAnchor = m_selection.second - m_align;
679		} else {
680			m_selectionAnchor = m_selection.first;
681		}
682		if (adjust < 0 && m_base - adjust > m_selectionAnchor) {
683			m_selectionAnchor = m_base;
684		} else if (adjust > 0 && m_selectionAnchor + adjust >= m_base + m_size) {
685			m_selectionAnchor = m_base + m_size - m_align;
686		} else {
687			m_selectionAnchor += adjust;
688		}
689		m_selection.first = m_selectionAnchor;
690		m_selection.second = m_selection.first + m_align;
691		cursorPosition = (m_selectionAnchor - m_base) / 16;
692	}
693	if (cursorPosition < m_top) {
694		m_top = cursorPosition;
695	} else if (cursorPosition >= m_top + viewport()->size().height() / m_cellHeight - 1) {
696		m_top = cursorPosition - viewport()->size().height() / m_cellHeight + 2;
697	}
698	emit selectionChanged(m_selection.first, m_selection.second);
699	viewport()->update();
700}
701
702void MemoryModel::TextCodecFree::operator()(TextCodec* codec) {
703	TextCodecDeinit(codec);
704	delete(codec);
705}