all repos — mgba @ 27545462e4b14f77929d9469382299fe2a7510e1

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