all repos — mgba @ c188e77b15daf35c2a4f0f85959119f8007c672f

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