src/platform/qt/MapView.cpp (view raw)
1/* Copyright (c) 2013-2017 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 "MapView.h"
7
8#include "CoreController.h"
9#include "GBAApp.h"
10#include "LogController.h"
11
12#include <mgba-util/png-io.h>
13#include <mgba-util/vfs.h>
14#ifdef M_CORE_GBA
15#include <mgba/internal/gba/gba.h>
16#include <mgba/internal/gba/io.h>
17#include <mgba/internal/gba/memory.h>
18#include <mgba/internal/gba/video.h>
19#endif
20#ifdef M_CORE_GB
21#include <mgba/internal/gb/gb.h>
22#include <mgba/internal/gb/memory.h>
23#endif
24
25#include <QAction>
26#include <QButtonGroup>
27#include <QClipboard>
28#include <QFontDatabase>
29#include <QMouseEvent>
30#include <QRadioButton>
31#include <QTimer>
32
33using namespace QGBA;
34
35MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
36 : AssetView(controller, parent)
37 , m_controller(controller)
38{
39 m_ui.setupUi(this);
40 m_ui.tile->setController(controller);
41
42 switch (m_controller->platform()) {
43#ifdef M_CORE_GBA
44 case PLATFORM_GBA:
45 m_boundary = 2048;
46 m_addressBase = BASE_VRAM;
47 m_addressWidth = 8;
48 m_ui.bgInfo->addCustomProperty("priority", tr("Priority"));
49 m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base"));
50 m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base"));
51 m_ui.bgInfo->addCustomProperty("size", tr("Size"));
52 m_ui.bgInfo->addCustomProperty("offset", tr("Offset"));
53 m_ui.bgInfo->addCustomProperty("transform", tr("Xform"));
54 break;
55#endif
56#ifdef M_CORE_GB
57 case PLATFORM_GB:
58 m_boundary = 1024;
59 m_addressBase = GB_BASE_VRAM;
60 m_addressWidth = 4;
61 m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base"));
62 m_ui.bgInfo->addCustomProperty("charBase", tr("Tile base"));
63 m_ui.bgInfo->addCustomProperty("offset", tr("Offset"));
64 break;
65#endif
66 default:
67 return;
68 }
69 m_ui.tile->setBoundary(m_boundary, 0, 0);
70
71 connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
72 updateTiles(true);
73 });
74
75 CoreController::Interrupter interrupter(m_controller);
76 const mCoreChannelInfo* videoLayers;
77 size_t nVideo = m_controller->thread()->core->listVideoLayers(m_controller->thread()->core, &videoLayers);
78 QButtonGroup* group = new QButtonGroup(this);
79 for (size_t i = 0; i < nVideo; ++i) {
80 if (strncmp(videoLayers[i].internalName, "bg", 2) != 0) {
81 continue;
82 }
83 QRadioButton* button = new QRadioButton(tr(videoLayers[i].visibleName));
84 if (!i) {
85 button->setChecked(true);
86 }
87 m_ui.bgLayout->addWidget(button);
88 connect(button, &QAbstractButton::pressed, button, [this, i]() {
89 selectMap(i);
90 });
91 group->addButton(button);
92 }
93 connect(m_ui.exportButton, &QAbstractButton::clicked, this, &MapView::exportMap);
94 connect(m_ui.copyButton, &QAbstractButton::clicked, this, &MapView::copyMap);
95
96 QAction* exportAction = new QAction(this);
97 exportAction->setShortcut(QKeySequence::Save);
98 connect(exportAction, &QAction::triggered, this, &MapView::exportMap);
99 addAction(exportAction);
100
101 QAction* copyAction = new QAction(this);
102 copyAction->setShortcut(QKeySequence::Copy);
103 connect(copyAction, &QAction::triggered, this, &MapView::copyMap);
104 addAction(copyAction);
105
106 m_ui.map->installEventFilter(this);
107 m_ui.tile->addCustomProperty("mapAddr", tr("Map Addr."));
108 m_ui.tile->addCustomProperty("flip", tr("Mirror"));
109 selectTile(0, 0);
110}
111
112void MapView::selectMap(int map) {
113 if (map >= mMapCacheSetSize(&m_cacheSet->maps)) {
114 return;
115 }
116 if (map == m_map) {
117 return;
118 }
119 m_map = map;
120 updateTiles(true);
121}
122
123void MapView::selectTile(int x, int y) {
124 CoreController::Interrupter interrupter(m_controller);
125 mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
126 size_t tileCache = mTileCacheSetIndex(&m_cacheSet->tiles, mapCache->tileCache);
127 m_ui.tile->setBoundary(m_boundary, tileCache, tileCache);
128 uint32_t location = mMapCacheTileId(mapCache, x, y);
129 mMapCacheEntry* entry = &m_mapStatus[location];
130 m_ui.tile->selectIndex(entry->tileId + mapCache->tileStart);
131 m_ui.tile->setPalette(mMapCacheEntryFlagsGetPaletteId(entry->flags));
132 m_ui.tile->setFlip(mMapCacheEntryFlagsGetHMirror(entry->flags), mMapCacheEntryFlagsGetVMirror(entry->flags));
133 location <<= (mMapCacheSystemInfoGetMapAlign(mapCache->sysConfig));
134 location += m_addressBase + mapCache->mapStart;
135
136 QString flip(tr("None"));
137 if (mMapCacheEntryFlagsGetHMirror(entry->flags) && mMapCacheEntryFlagsGetVMirror(entry->flags)) {
138 flip = tr("Both");
139 } else if (mMapCacheEntryFlagsGetHMirror(entry->flags)) {
140 flip = tr("Horizontal");
141 } else if (mMapCacheEntryFlagsGetVMirror(entry->flags)) {
142 flip = tr("Vertical");
143 }
144 m_ui.tile->setCustomProperty("flip", flip);
145 m_ui.tile->setCustomProperty("mapAddr", QString("%0%1")
146 .arg(m_addressWidth == 8 ? "0x" : "")
147 .arg(location, m_addressWidth, 16, QChar('0')));
148}
149
150bool MapView::eventFilter(QObject*, QEvent* event) {
151 if (event->type() != QEvent::MouseButtonPress) {
152 return false;
153 }
154 int x = static_cast<QMouseEvent*>(event)->x();
155 int y = static_cast<QMouseEvent*>(event)->y();
156 x /= 8 * m_ui.magnification->value();
157 y /= 8 * m_ui.magnification->value();
158 selectTile(x, y);
159 return true;
160}
161
162void MapView::updateTilesGBA(bool) {
163 {
164 CoreController::Interrupter interrupter(m_controller);
165 int bitmap = -1;
166 int priority = -1;
167 int frame = 0;
168 QString offset(tr("N/A"));
169 QString transform(tr("N/A"));
170#ifdef M_CORE_GBA
171 if (m_controller->platform() == PLATFORM_GBA) {
172 uint16_t* io = static_cast<GBA*>(m_controller->thread()->core->board)->memory.io;
173 int mode = GBARegisterDISPCNTGetMode(io[REG_DISPCNT >> 1]);
174 if (m_map == 2 && mode > 2) {
175 bitmap = mode == 4 ? 1 : 0;
176 if (mode != 3) {
177 frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]);
178 }
179 }
180 priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]);
181 if (mode == 0 || (mode == 1 && m_map != 2)) {
182 offset = QString("%1, %2")
183 .arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)])
184 .arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]);
185 } else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) {
186 int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)];
187 refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16;
188 int32_t refY = io[(REG_BG2Y_LO >> 1) + ((m_map - 2) << 2)];
189 refY |= io[(REG_BG2Y_HI >> 1) + ((m_map - 2) << 2)] << 16;
190 refX <<= 4;
191 refY <<= 4;
192 refX >>= 4;
193 refY >>= 4;
194 offset = QString("%1\n%2").arg(refX / 65536., 0, 'f', 3).arg(refY / 65536., 0, 'f', 3);
195 transform = QString("%1 %2\n%3 %4")
196 .arg(io[(REG_BG2PA >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
197 .arg(io[(REG_BG2PB >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
198 .arg(io[(REG_BG2PC >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2)
199 .arg(io[(REG_BG2PD >> 1) + ((m_map - 2) << 2)] / 256., 3, 'f', 2);
200
201 }
202 }
203#endif
204#ifdef M_CORE_GB
205 if (m_controller->platform() == PLATFORM_GB) {
206 uint8_t* io = static_cast<GB*>(m_controller->thread()->core->board)->memory.io;
207 int x = io[m_map == 0 ? 0x42 : 0x4A];
208 int y = io[m_map == 0 ? 0x43 : 0x4B];
209 offset = QString("%1, %2").arg(x).arg(y);
210 }
211#endif
212 if (bitmap >= 0) {
213 mBitmapCache* bitmapCache = mBitmapCacheSetGetPointer(&m_cacheSet->bitmaps, bitmap);
214 int width = mBitmapCacheSystemInfoGetWidth(bitmapCache->sysConfig);
215 int height = mBitmapCacheSystemInfoGetHeight(bitmapCache->sysConfig);
216 m_ui.bgInfo->setCustomProperty("screenBase", QString("0x%1").arg(m_addressBase + bitmapCache->bitsStart[frame], 8, 16, QChar('0')));
217 m_ui.bgInfo->setCustomProperty("charBase", tr("N/A"));
218 m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(width).arg(height));
219 m_ui.bgInfo->setCustomProperty("priority", priority);
220 m_ui.bgInfo->setCustomProperty("offset", offset);
221 m_ui.bgInfo->setCustomProperty("transform", transform);
222 m_rawMap = QImage(QSize(width, height), QImage::Format_ARGB32);
223 uchar* bgBits = m_rawMap.bits();
224 for (int j = 0; j < height; ++j) {
225 mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j);
226 memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4);
227 }
228 m_rawMap = m_rawMap.convertToFormat(QImage::Format_RGB32).rgbSwapped();
229 } else {
230 mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
231 int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
232 int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
233 m_ui.bgInfo->setCustomProperty("screenBase", QString("%0%1")
234 .arg(m_addressWidth == 8 ? "0x" : "")
235 .arg(m_addressBase + mapCache->mapStart, m_addressWidth, 16, QChar('0')));
236 m_ui.bgInfo->setCustomProperty("charBase", QString("%0%1")
237 .arg(m_addressWidth == 8 ? "0x" : "")
238 .arg(m_addressBase + mapCache->tileCache->tileBase, m_addressWidth, 16, QChar('0')));
239 m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(tilesW * 8).arg(tilesH * 8));
240 m_ui.bgInfo->setCustomProperty("priority", priority);
241 m_ui.bgInfo->setCustomProperty("offset", offset);
242 m_ui.bgInfo->setCustomProperty("transform", transform);
243 m_rawMap = compositeMap(m_map, m_mapStatus);
244 }
245 }
246 QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32));
247 if (m_ui.magnification->value() > 1) {
248 map = map.scaled(map.size() * m_ui.magnification->value());
249 }
250 m_ui.map->setPixmap(map);
251}
252
253#ifdef M_CORE_GB
254void MapView::updateTilesGB(bool force) {
255 updateTilesGBA(force);
256}
257#endif
258
259void MapView::exportMap() {
260 QString filename = GBAApp::app()->getSaveFileName(this, tr("Export map"),
261 tr("Portable Network Graphics (*.png)"));
262 if (filename.isNull()) {
263 return;
264 }
265
266 CoreController::Interrupter interrupter(m_controller);
267 m_rawMap.save(filename, "PNG");
268}
269
270void MapView::copyMap() {
271 CoreController::Interrupter interrupter(m_controller);
272 GBAApp::app()->clipboard()->setImage(m_rawMap);
273}