all repos — mgba @ ba00cdfc024e495dc377f07d9ed6e5c57958de9c

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