all repos — mgba @ 8947b80a1cefc78458f447b6583c17d084998f8c

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