1 /* Copyright (c) 2013-2016 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 "ObjView.h"
7 
8 #include "CoreController.h"
9 #include "GBAApp.h"
10 
11 #include <QAction>
12 #include <QClipboard>
13 #include <QListWidgetItem>
14 #include <QTimer>
15 
16 #include "LogController.h"
17 #include "VFileDevice.h"
18 
19 #ifdef M_CORE_GBA
20 #include <mgba/internal/gba/gba.h>
21 #endif
22 #ifdef M_CORE_GB
23 #include <mgba/internal/gb/gb.h>
24 #endif
25 #include <mgba-util/vfs.h>
26 
27 using namespace QGBA;
28 
ObjView(std::shared_ptr<CoreController> controller,QWidget * parent)29 ObjView::ObjView(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 	const QFont font = GBAApp::app()->monospaceFont();
37 
38 	m_ui.x->setFont(font);
39 	m_ui.y->setFont(font);
40 	m_ui.w->setFont(font);
41 	m_ui.h->setFont(font);
42 	m_ui.address->setFont(font);
43 	m_ui.priority->setFont(font);
44 	m_ui.palette->setFont(font);
45 	m_ui.transform->setFont(font);
46 	m_ui.xformPA->setFont(font);
47 	m_ui.xformPB->setFont(font);
48 	m_ui.xformPC->setFont(font);
49 	m_ui.xformPD->setFont(font);
50 	m_ui.mode->setFont(font);
51 
52 	connect(m_ui.tiles, &TilePainter::indexPressed, this, &ObjView::translateIndex);
53 	connect(m_ui.tiles, &TilePainter::needsRedraw, this, [this]() {
54 		updateTiles(true);
55 	});
56 	connect(m_ui.objId, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ObjView::selectObj);
57 	connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
58 		updateTiles(true);
59 	});
60 	connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj);
61 	connect(m_ui.copyButton, &QAbstractButton::clicked, this, &ObjView::copyObj);
62 
63 	connect(m_ui.objList, &QListWidget::currentItemChanged, [this]() {
64 		QListWidgetItem* item = m_ui.objList->currentItem();
65 		if (item) {
66 			selectObj(item->data(Qt::UserRole).toInt());
67 		}
68 	});
69 
70 	QAction* exportAction = new QAction(this);
71 	exportAction->setShortcut(QKeySequence::Save);
72 	connect(exportAction, &QAction::triggered, this, &ObjView::exportObj);
73 	addAction(exportAction);
74 
75 	QAction* copyAction = new QAction(this);
76 	copyAction->setShortcut(QKeySequence::Copy);
77 	connect(copyAction, &QAction::triggered, this, &ObjView::copyObj);
78 	addAction(copyAction);
79 }
80 
selectObj(int obj)81 void ObjView::selectObj(int obj) {
82 	m_objId = obj;
83 	bool blocked = m_ui.objId->blockSignals(true);
84 	m_ui.objId->setValue(m_objId);
85 	m_ui.objId->blockSignals(blocked);
86 	if (m_objs.size() > obj) {
87 		blocked = m_ui.objList->blockSignals(true);
88 		m_ui.objList->setCurrentItem(m_objs[obj]);
89 		m_ui.objList->blockSignals(blocked);
90 	}
91 	updateTiles(true);
92 }
93 
translateIndex(int index)94 void ObjView::translateIndex(int index) {
95 	unsigned x = index % m_objInfo.width;
96 	unsigned y = index / m_objInfo.width;
97 	m_ui.tile->selectIndex(x + y * m_objInfo.stride + m_tileOffset + m_boundary);
98 }
99 
100 #ifdef M_CORE_GBA
updateTilesGBA(bool force)101 void ObjView::updateTilesGBA(bool force) {
102 	m_ui.objId->setMaximum(127);
103 	const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
104 	const GBAObj* obj = &gba->video.oam.obj[m_objId];
105 
106 	updateObjList(128);
107 
108 	ObjInfo newInfo;
109 	lookupObj(m_objId, &newInfo);
110 
111 	m_ui.tiles->setTileCount(newInfo.width * newInfo.height);
112 	m_ui.tiles->setMinimumSize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
113 	m_ui.tiles->resize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
114 	unsigned tileBase = newInfo.tile;
115 	unsigned tile = newInfo.tile;
116 	if (GBAObjAttributesAIs256Color(obj->a)) {
117 		m_ui.palette->setText("256-color");
118 		m_ui.tile->setBoundary(1024, 1, 3);
119 		m_ui.tile->setPalette(0);
120 		m_boundary = 1024;
121 		tileBase *= 2;
122 	} else {
123 		m_ui.palette->setText(QString::number(newInfo.paletteId));
124 		m_ui.tile->setBoundary(2048, 0, 2);
125 		m_ui.tile->setPalette(newInfo.paletteId);
126 		m_boundary = 2048;
127 	}
128 	if (newInfo != m_objInfo) {
129 		force = true;
130 	}
131 	m_objInfo = newInfo;
132 	m_tileOffset = newInfo.tile;
133 	mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet);
134 	unsigned maxTiles = mTileCacheSystemInfoGetMaxTiles(tileCache->sysConfig);
135 	int i = 0;
136 	for (unsigned y = 0; y < newInfo.height; ++y) {
137 		for (unsigned x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) {
138 			if (tile < maxTiles) {
139 				const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId);
140 				if (data) {
141 					m_ui.tiles->setTile(i, data);
142 				} else if (force) {
143 					m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, newInfo.paletteId));
144 				}
145 			} else {
146 				m_ui.tiles->clearTile(i);
147 			}
148 		}
149 		tile += newInfo.stride - newInfo.width;
150 		tileBase += newInfo.stride - newInfo.width;
151 	}
152 
153 	m_ui.x->setText(QString::number(newInfo.x));
154 	m_ui.y->setText(QString::number(newInfo.y));
155 	m_ui.w->setText(QString::number(newInfo.width * 8));
156 	m_ui.h->setText(QString::number(newInfo.height * 8));
157 
158 	m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
159 	m_ui.priority->setText(QString::number(newInfo.priority));
160 	m_ui.flippedH->setChecked(newInfo.hflip);
161 	m_ui.flippedV->setChecked(newInfo.vflip);
162 	m_ui.enabled->setChecked(newInfo.enabled);
163 	m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
164 	m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));
165 
166 	if (GBAObjAttributesAIsTransformed(obj->a)) {
167 		int mtxId = GBAObjAttributesBGetMatIndex(obj->b);
168 		struct GBAOAMMatrix mat;
169 		LOAD_16LE(mat.a, 0, &gba->video.oam.mat[mtxId].a);
170 		LOAD_16LE(mat.b, 0, &gba->video.oam.mat[mtxId].b);
171 		LOAD_16LE(mat.c, 0, &gba->video.oam.mat[mtxId].c);
172 		LOAD_16LE(mat.d, 0, &gba->video.oam.mat[mtxId].d);
173 		m_ui.transform->setText(QString::number(mtxId));
174 		m_ui.xformPA->setText(QString("%0").arg(mat.a / 256., 5, 'f', 2));
175 		m_ui.xformPB->setText(QString("%0").arg(mat.b / 256., 5, 'f', 2));
176 		m_ui.xformPC->setText(QString("%0").arg(mat.c / 256., 5, 'f', 2));
177 		m_ui.xformPD->setText(QString("%0").arg(mat.d / 256., 5, 'f', 2));
178 	} else {
179 		m_ui.transform->setText(tr("Off"));
180 		m_ui.xformPA->setText(tr("---"));
181 		m_ui.xformPB->setText(tr("---"));
182 		m_ui.xformPC->setText(tr("---"));
183 		m_ui.xformPD->setText(tr("---"));
184 	}
185 
186 	switch (GBAObjAttributesAGetMode(obj->a)) {
187 	case OBJ_MODE_NORMAL:
188 		m_ui.mode->setText(tr("Normal"));
189 		break;
190 	case OBJ_MODE_SEMITRANSPARENT:
191 		m_ui.mode->setText(tr("Trans"));
192 		break;
193 	case OBJ_MODE_OBJWIN:
194 		m_ui.mode->setText(tr("OBJWIN"));
195 		break;
196 	default:
197 		m_ui.mode->setText(tr("Invalid"));
198 		break;
199 	}
200 }
201 #endif
202 
203 #ifdef M_CORE_GB
updateTilesGB(bool force)204 void ObjView::updateTilesGB(bool force) {
205 	m_ui.objId->setMaximum(39);
206 	const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
207 	const GBObj* obj = &gb->video.oam.obj[m_objId];
208 
209 	updateObjList(40);
210 
211 	ObjInfo newInfo;
212 	lookupObj(m_objId, &newInfo);
213 
214 	mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
215 	unsigned tile = newInfo.tile;
216 	m_ui.tiles->setTileCount(newInfo.height);
217 	m_ui.tile->setBoundary(1024, 0, 0);
218 	m_ui.tiles->setMinimumSize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
219 	m_ui.tiles->resize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
220 	m_ui.palette->setText(QString::number(newInfo.paletteId - 8));
221 
222 	if (newInfo != m_objInfo) {
223 		force = true;
224 	}
225 	m_objInfo = newInfo;
226 	m_tileOffset = tile;
227 	m_boundary = 1024;
228 
229 	int i = 0;
230 	m_ui.tile->setPalette(newInfo.paletteId);
231 	for (unsigned y = 0; y < newInfo.height; ++y, ++i) {
232 		unsigned t = tile + i;
233 		const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, newInfo.paletteId);
234 		if (data) {
235 			m_ui.tiles->setTile(i, data);
236 		} else if (force) {
237 			m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, newInfo.paletteId));
238 		}
239 	}
240 
241 	m_ui.x->setText(QString::number(newInfo.x));
242 	m_ui.y->setText(QString::number(newInfo.y));
243 	m_ui.w->setText(QString::number(8));
244 	m_ui.h->setText(QString::number(newInfo.height * 8));
245 
246 	m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
247 	m_ui.priority->setText(QString::number(newInfo.priority));
248 	m_ui.flippedH->setChecked(newInfo.hflip);
249 	m_ui.flippedV->setChecked(newInfo.vflip);
250 	m_ui.enabled->setChecked(newInfo.enabled);
251 	m_ui.doubleSize->setChecked(false);
252 	m_ui.mosaic->setChecked(false);
253 	m_ui.transform->setText(tr("N/A"));
254 	m_ui.xformPA->setText(tr("---"));
255 	m_ui.xformPB->setText(tr("---"));
256 	m_ui.xformPC->setText(tr("---"));
257 	m_ui.xformPD->setText(tr("---"));
258 	m_ui.mode->setText(tr("N/A"));
259 }
260 #endif
261 
updateObjList(int maxObj)262 void ObjView::updateObjList(int maxObj) {
263 	for (int i = 0; i < maxObj; ++i) {
264 		if (m_objs.size() <= i) {
265 			QListWidgetItem* item = new QListWidgetItem;
266 			item->setText(QString::number(i));
267 			item->setData(Qt::UserRole, i);
268 			item->setSizeHint(QSize(64, 96));
269 			if (m_objId == i) {
270 				item->setSelected(true);
271 			}
272 			m_objs.append(item);
273 			m_ui.objList->addItem(item);
274 		}
275 		QListWidgetItem* item = m_objs[i];
276 		ObjInfo info;
277 		lookupObj(i, &info);
278 		item->setIcon(QPixmap::fromImage(compositeObj(info)));
279 	}
280 }
281 
exportObj()282 void ObjView::exportObj() {
283 	QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
284 	                                                  tr("Portable Network Graphics (*.png)"));
285 	if (filename.isNull()) {
286 		return;
287 	}
288 	CoreController::Interrupter interrupter(m_controller);
289 	QImage obj = compositeObj(m_objInfo);
290 	obj.save(filename, "PNG");
291 }
292 
copyObj()293 void ObjView::copyObj() {
294 	CoreController::Interrupter interrupter(m_controller);
295 	GBAApp::app()->clipboard()->setImage(compositeObj(m_objInfo));
296 }
297