all repos — mgba @ e51d3e105ffedc5c983165fc7ae24ebda8786148

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