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}