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