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}