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