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