all repos — mgba @ 2f1cb61d0197893cf0c3861e9130ff1aeaefb0b0

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