all repos — mgba @ 78e4083a562a1ce34a29dee8ecdb315401c9fb1a

mGBA Game Boy Advance Emulator

src/platform/qt/MemoryModel.cpp (view raw)

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