all repos — mgba @ a2b3129bc0748e7373443a61259c52231216b704

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 <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* obj, 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 force) {
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		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		if (m_controller->platform() == PLATFORM_GB) {
203			uint8_t* io = static_cast<GB*>(m_controller->thread()->core->board)->memory.io;
204			int x = io[m_map == 0 ? 0x42 : 0x4A];
205			int y = io[m_map == 0 ? 0x43 : 0x4B];
206			offset = QString("%1, %2").arg(x).arg(y);
207		}
208		if (bitmap >= 0) {
209			mBitmapCache* bitmapCache = mBitmapCacheSetGetPointer(&m_cacheSet->bitmaps, bitmap);
210			int width = mBitmapCacheSystemInfoGetWidth(bitmapCache->sysConfig);
211			int height = mBitmapCacheSystemInfoGetHeight(bitmapCache->sysConfig);
212			m_ui.bgInfo->setCustomProperty("screenBase", QString("0x%1").arg(m_addressBase + bitmapCache->bitsStart[frame], 8, 16, QChar('0')));
213			m_ui.bgInfo->setCustomProperty("charBase", tr("N/A"));
214			m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(width).arg(height));
215			m_ui.bgInfo->setCustomProperty("priority", priority);
216			m_ui.bgInfo->setCustomProperty("offset", offset);
217			m_ui.bgInfo->setCustomProperty("transform", transform);
218			m_rawMap = QImage(QSize(width, height), QImage::Format_ARGB32);
219			uchar* bgBits = m_rawMap.bits();
220			for (int j = 0; j < height; ++j) {
221				mBitmapCacheCleanRow(bitmapCache, m_bitmapStatus, j);
222				memcpy(static_cast<void*>(&bgBits[width * j * 4]), mBitmapCacheGetRow(bitmapCache, j), width * 4);
223			}
224			m_rawMap = m_rawMap.convertToFormat(QImage::Format_RGB32).rgbSwapped();
225		} else {
226			mMapCache* mapCache = mMapCacheSetGetPointer(&m_cacheSet->maps, m_map);
227			int tilesW = 1 << mMapCacheSystemInfoGetTilesWide(mapCache->sysConfig);
228			int tilesH = 1 << mMapCacheSystemInfoGetTilesHigh(mapCache->sysConfig);
229			m_ui.bgInfo->setCustomProperty("screenBase", QString("%0%1")
230					.arg(m_addressWidth == 8 ? "0x" : "")
231					.arg(m_addressBase + mapCache->mapStart, m_addressWidth, 16, QChar('0')));
232			m_ui.bgInfo->setCustomProperty("charBase", QString("%0%1")
233					.arg(m_addressWidth == 8 ? "0x" : "")
234					.arg(m_addressBase + mapCache->tileCache->tileBase, m_addressWidth, 16, QChar('0')));
235			m_ui.bgInfo->setCustomProperty("size", QString("%1×%2").arg(tilesW * 8).arg(tilesH * 8));
236			m_ui.bgInfo->setCustomProperty("priority", priority);
237			m_ui.bgInfo->setCustomProperty("offset", offset);
238			m_ui.bgInfo->setCustomProperty("transform", transform);
239			m_rawMap = compositeMap(m_map, m_mapStatus);
240		}
241	}
242	QPixmap map = QPixmap::fromImage(m_rawMap.convertToFormat(QImage::Format_RGB32));
243	if (m_ui.magnification->value() > 1) {
244		map = map.scaled(map.size() * m_ui.magnification->value());
245	}
246	m_ui.map->setPixmap(map);
247}
248
249#ifdef M_CORE_GB
250void MapView::updateTilesGB(bool force) {
251	updateTilesGBA(force);
252}
253#endif
254
255void MapView::exportMap() {
256	QString filename = GBAApp::app()->getSaveFileName(this, tr("Export map"),
257	                                                  tr("Portable Network Graphics (*.png)"));
258	if (filename.isNull()) {
259		return;
260	}
261
262	CoreController::Interrupter interrupter(m_controller);
263	m_rawMap.save(filename, "PNG");
264}
265
266void MapView::copyMap() {
267	CoreController::Interrupter interrupter(m_controller);
268	GBAApp::app()->clipboard()->setImage(m_rawMap);
269}