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
22extern "C" {
23#include "core/core.h"
24}
25
26using namespace QGBA;
27
28MemoryModel::MemoryModel(QWidget* parent)
29 : QAbstractScrollArea(parent)
30 , m_core(nullptr)
31 , m_top(0)
32 , m_align(1)
33 , m_selection(0, 0)
34 , m_selectionAnchor(0)
35 , m_codec(nullptr)
36{
37 m_font.setFamily("Source Code Pro");
38 m_font.setStyleHint(QFont::Monospace);
39#ifdef Q_OS_MAC
40 m_font.setPointSize(12);
41#else
42 m_font.setPointSize(10);
43#endif
44 QFontMetrics metrics(m_font);
45 m_cellHeight = metrics.height();
46 m_letterWidth = metrics.averageCharWidth();
47
48 setFocusPolicy(Qt::StrongFocus);
49 setContextMenuPolicy(Qt::ActionsContextMenu);
50
51 QAction* copy = new QAction(tr("Copy selection"), this);
52 copy->setShortcut(QKeySequence::Copy);
53 connect(copy, SIGNAL(triggered()), this, SLOT(copy()));
54 addAction(copy);
55
56 QAction* save = new QAction(tr("Save selection"), this);
57 save->setShortcut(QKeySequence::Save);
58 connect(save, SIGNAL(triggered()), this, SLOT(save()));
59 addAction(save);
60
61 QAction* paste = new QAction(tr("Paste"), this);
62 paste->setShortcut(QKeySequence::Paste);
63 connect(paste, SIGNAL(triggered()), this, SLOT(paste()));
64 addAction(paste);
65
66 QAction* load = new QAction(tr("Load"), this);
67 load->setShortcut(QKeySequence::Open);
68 connect(load, SIGNAL(triggered()), this, SLOT(load()));
69 addAction(load);
70
71 static QString arg("%0");
72 for (int i = 0; i < 256; ++i) {
73 QStaticText str(arg.arg(i, 2, 16, QChar('0')).toUpper());
74 str.prepare(QTransform(), m_font);
75 m_staticNumbers.append(str);
76 }
77
78 for (int i = 0; i < 256; ++i) {
79 QChar c(i);
80 if (!c.isPrint()) {
81 c = '.';
82 }
83 QStaticText str = QStaticText(QString(c));
84 str.prepare(QTransform(), m_font);
85 m_staticLatin1.append(str);
86 }
87
88 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
89 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
90 m_margins = QMargins(metrics.width("0FFFFFF0 ") + 3, m_cellHeight + 1, metrics.width(" AAAAAAAAAAAAAAAA") + 3, 0);
91 m_cellSize = QSizeF((viewport()->size().width() - (m_margins.left() + m_margins.right())) / 16.0, m_cellHeight);
92
93 connect(verticalScrollBar(), &QSlider::sliderMoved, [this](int position) {
94 m_top = position;
95 update();
96 });
97
98 setRegion(0, 0x10000000, tr("All"));
99}
100
101void MemoryModel::setController(GameController* controller) {
102 m_core = controller->thread()->core;
103}
104
105void MemoryModel::setRegion(uint32_t base, uint32_t size, const QString& name, int segment) {
106 m_top = 0;
107 m_base = base;
108 m_size = size;
109 m_regionName = name;
110 m_regionName.prepare(QTransform(), m_font);
111 m_currentBank = segment;
112 verticalScrollBar()->setRange(0, (size >> 4) + 1 - viewport()->size().height() / m_cellHeight);
113 verticalScrollBar()->setValue(0);
114 viewport()->update();
115}
116
117void MemoryModel::setSegment(int segment) {
118 m_currentBank = segment;
119 viewport()->update();
120}
121
122void MemoryModel::setAlignment(int width) {
123 if (width != 1 && width != 2 && width != 4) {
124 return;
125 }
126 m_align = width;
127 m_buffer = 0;
128 m_bufferedNybbles = 0;
129 viewport()->update();
130}
131
132void MemoryModel::loadTBL(const QString& path) {
133 VFile* vf = VFileDevice::open(path, O_RDONLY);
134 if (!vf) {
135 return;
136 }
137 m_codec = std::unique_ptr<TextCodec, TextCodecFree>(new TextCodec);
138 TextCodecLoadTBL(m_codec.get(), vf, true);
139 vf->close(vf);
140}
141
142void MemoryModel::loadTBL() {
143 QString filename = GBAApp::app()->getOpenFileName(this, tr("Load TBL"));
144 if (filename.isNull()) {
145 return;
146 }
147 loadTBL(filename);
148}
149
150void MemoryModel::jumpToAddress(const QString& hex) {
151 bool ok = false;
152 uint32_t i = hex.toInt(&ok, 16);
153 if (ok) {
154 jumpToAddress(i);
155 }
156}
157
158void MemoryModel::jumpToAddress(uint32_t address) {
159 if (address >= 0x10000000) {
160 return;
161 }
162 if (address < m_base || address >= m_base + m_size) {
163 setRegion(0, 0x10000000, tr("All"));
164 }
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}