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/memory.h>
16#endif
17#ifdef M_CORE_GB
18#include <mgba/internal/gb/memory.h>
19#endif
20
21#include <QButtonGroup>
22#include <QFontDatabase>
23#include <QMouseEvent>
24#include <QRadioButton>
25#include <QTimer>
26
27using namespace QGBA;
28
29MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
30 : AssetView(controller, parent)
31 , m_controller(controller)
32{
33 m_ui.setupUi(this);
34 m_ui.tile->setController(controller);
35
36 switch (m_controller->platform()) {
37#ifdef M_CORE_GBA
38 case PLATFORM_GBA:
39 m_boundary = 2048;
40 m_addressBase = BASE_VRAM;
41 m_addressWidth = 8;
42 break;
43#endif
44#ifdef M_CORE_GB
45 case PLATFORM_GB:
46 m_boundary = 1024;
47 m_addressBase = GB_BASE_VRAM;
48 m_addressWidth = 4;
49 break;
50#endif
51 default:
52 return;
53 }
54 m_ui.tile->setBoundary(m_boundary, 0, 0);
55
56 connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
57 updateTiles(true);
58 });
59
60 CoreController::Interrupter interrupter(m_controller);
61 const mCoreChannelInfo* videoLayers;
62 size_t nVideo = m_controller->thread()->core->listVideoLayers(m_controller->thread()->core, &videoLayers);
63 QButtonGroup* group = new QButtonGroup(this);
64 for (size_t i = 0; i < nVideo; ++i) {
65 if (strncmp(videoLayers[i].internalName, "bg", 2) != 0) {
66 continue;
67 }
68 QRadioButton* button = new QRadioButton(tr(videoLayers[i].visibleName));
69 if (!i) {
70 button->setChecked(true);
71 }
72 m_ui.bgLayout->addWidget(button);
73 connect(button, &QAbstractButton::pressed, button, [this, i]() {
74 selectMap(i);
75 });
76 group->addButton(button);
77 }
78#ifdef USE_PNG
79 connect(m_ui.exportButton, &QAbstractButton::clicked, this, &MapView::exportMap);
80#else
81 m_ui.exportButton->setVisible(false);
82#endif
83 m_ui.map->installEventFilter(this);
84 m_ui.tile->addCustomProperty("mapAddr", tr("Map Addr."));
85 m_ui.tile->addCustomProperty("flip", tr("Mirror"));
86 selectTile(0, 0);
87}
88
89void MapView::selectMap(int map) {
90 if (map >= mMapCacheSetSize(&m_cacheSet->maps)) {
91 return;
92 }
93 if (map == m_map) {
94 return;
95 }
96 m_map = map;
97 updateTiles(true);
98}
99
100void MapView::selectTile(int x, int y) {
101 CoreController::Interrupter interrupter(m_controller);
102 mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
103 size_t tileCache = mTileCacheSetIndex(&m_cacheSet->tiles, mapCache->tileCache);
104 m_ui.tile->setBoundary(m_boundary, tileCache, tileCache);
105 uint32_t location = mMapCacheTileId(mapCache, x, y);
106 mMapCacheEntry* entry = &m_mapStatus[location];
107 m_ui.tile->selectIndex(entry->tileId + mapCache->tileStart);
108 m_ui.tile->setPalette(mMapCacheEntryFlagsGetPaletteId(entry->flags));
109 m_ui.tile->setFlip(mMapCacheEntryFlagsGetHMirror(entry->flags), mMapCacheEntryFlagsGetVMirror(entry->flags));
110 location <<= (mMapCacheSystemInfoGetMapAlign(mapCache->sysConfig));
111 location += m_addressBase + mapCache->mapStart;
112
113 QString flip(tr("None"));
114 if (mMapCacheEntryFlagsGetHMirror(entry->flags) && mMapCacheEntryFlagsGetVMirror(entry->flags)) {
115 flip = tr("Both");
116 } else if (mMapCacheEntryFlagsGetHMirror(entry->flags)) {
117 flip = tr("Horizontal");
118 } else if (mMapCacheEntryFlagsGetVMirror(entry->flags)) {
119 flip = tr("Vertical");
120 }
121 m_ui.tile->setCustomProperty("flip", flip);
122 m_ui.tile->setCustomProperty("mapAddr", QString("%0%1")
123 .arg(m_addressWidth == 8 ? "0x" : "")
124 .arg(location, m_addressWidth, 16, QChar('0')));
125}
126
127bool MapView::eventFilter(QObject* obj, QEvent* event) {
128 if (event->type() != QEvent::MouseButtonPress) {
129 return false;
130 }
131 int x = static_cast<QMouseEvent*>(event)->x();
132 int y = static_cast<QMouseEvent*>(event)->y();
133 x /= 8 * m_ui.magnification->value();
134 y /= 8 * m_ui.magnification->value();
135 selectTile(x, y);
136 return true;
137}
138
139void MapView::updateTilesGBA(bool force) {
140 {
141 CoreController::Interrupter interrupter(m_controller);
142 mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
143 int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
144 int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
145 m_rawMap = QImage(QSize(tilesW * 8, tilesH * 8), QImage::Format_ARGB32);
146 uchar* bgBits = m_rawMap.bits();
147 for (int j = 0; j < tilesH; ++j) {
148 for (int i = 0; i < tilesW; ++i) {
149 mMapCacheCleanTile(mapCache, m_mapStatus, i, j);
150 }
151 for (int i = 0; i < 8; ++i) {
152 memcpy(static_cast<void*>(&bgBits[tilesW * 32 * (i + j * 8)]), mMapCacheGetRow(mapCache, i + j * 8), tilesW * 32);
153 }
154 }
155 }
156 m_rawMap = m_rawMap.rgbSwapped();
157 QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32));
158 if (m_ui.magnification->value() > 1) {
159 map = map.scaled(map.size() * m_ui.magnification->value());
160 }
161 m_ui.map->setPixmap(map);
162}
163
164#ifdef M_CORE_GB
165void MapView::updateTilesGB(bool force) {
166 updateTilesGBA(force);
167}
168#endif
169
170#ifdef USE_PNG
171void MapView::exportMap() {
172 QString filename = GBAApp::app()->getSaveFileName(this, tr("Export map"),
173 tr("Portable Network Graphics (*.png)"));
174 VFile* vf = VFileDevice::open(filename, O_WRONLY | O_CREAT | O_TRUNC);
175 if (!vf) {
176 LOG(QT, ERROR) << tr("Failed to open output PNG file: %1").arg(filename);
177 return;
178 }
179
180 CoreController::Interrupter interrupter(m_controller);
181 png_structp png = PNGWriteOpen(vf);
182 png_infop info = PNGWriteHeaderA(png, m_rawMap.width(), m_rawMap.height());
183
184 mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
185 QImage map = m_rawMap.rgbSwapped();
186 PNGWritePixelsA(png, map.width(), map.height(), map.bytesPerLine() / 4, static_cast<const void*>(map.constBits()));
187 PNGWriteClose(png, info);
188 vf->close(vf);
189}
190#endif