all repos — mgba @ f6f3cb5d3d8b91dd603772ea0eebb2513562a0cf

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