all repos — mgba @ 18403682f7824b7fa76fac4ada63386c5e0e34dc

mGBA Game Boy Advance Emulator

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