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