all repos — mgba @ c300da9be6e85928dc6068aaadc3f9275e476418

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