all repos — mgba @ 19a42a387a4a9316e5626c8079bed4d989d9c3ac

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/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