mirror of
https://github.com/devinacker/bsnes-plus.git
synced 2025-04-02 10:52:46 -04:00
331 lines
11 KiB
C++
331 lines
11 KiB
C++
#include "oam-viewer.moc"
|
|
OamViewer *oamViewer;
|
|
|
|
OamViewer::OamViewer() {
|
|
setObjectName("oam-viewer");
|
|
setWindowTitle("Sprite Viewer");
|
|
setGeometryString(&config().geometry.oamViewer);
|
|
application.windowList.append(this);
|
|
|
|
inRefreshCall = false;
|
|
|
|
dataModel = new OamDataModel(this);
|
|
|
|
proxyModel = new QSortFilterProxyModel(this);
|
|
proxyModel->setSourceModel(dataModel);
|
|
proxyModel->setSortRole(OamDataModel::SortRole);
|
|
proxyModel->setDynamicSortFilter(true);
|
|
|
|
graphicsScene = new OamGraphicsScene(dataModel, this);
|
|
|
|
outerLayout = new QVBoxLayout;
|
|
setLayout(outerLayout);
|
|
|
|
splitter = new QSplitter;
|
|
splitter->setChildrenCollapsible(false);
|
|
splitter->setOrientation(Qt::Vertical);
|
|
outerLayout->addWidget(splitter);
|
|
|
|
graphicsView = new QGraphicsView;
|
|
graphicsView->setMinimumSize(256, 256);
|
|
graphicsView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
graphicsView->setDragMode(QGraphicsView::RubberBandDrag);
|
|
graphicsView->setScene(graphicsScene);
|
|
graphicsView->setContextMenuPolicy(Qt::ActionsContextMenu);
|
|
splitter->addWidget(graphicsView);
|
|
|
|
bottomWidget = new QWidget;
|
|
splitter->addWidget(bottomWidget);
|
|
|
|
layout = new QHBoxLayout;
|
|
layout->setAlignment(Qt::AlignLeft);
|
|
layout->setMargin(Style::WindowMargin);
|
|
layout->setSpacing(Style::WidgetSpacing);
|
|
bottomWidget->setLayout(layout);
|
|
|
|
treeView = new QTreeView;
|
|
treeView->setModel(proxyModel);
|
|
treeView->setAllColumnsShowFocus(true);
|
|
treeView->setAlternatingRowColors(true);
|
|
treeView->setRootIsDecorated(false);
|
|
treeView->setUniformRowHeights(true);
|
|
treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
|
|
treeView->setContextMenuPolicy(Qt::ActionsContextMenu);
|
|
layout->addWidget(treeView);
|
|
|
|
unsigned dw = treeView->fontMetrics().width('0');
|
|
treeView->setColumnWidth(0, dw * 6);
|
|
treeView->setColumnWidth(1, dw * 8);
|
|
treeView->setColumnWidth(2, dw * 6);
|
|
treeView->setColumnWidth(3, dw * 6);
|
|
treeView->setColumnWidth(4, dw * 6);
|
|
treeView->setColumnWidth(5, dw * 6);
|
|
treeView->setColumnWidth(6, dw * 6);
|
|
treeView->setColumnWidth(7, dw * 6);
|
|
|
|
treeView->setSortingEnabled(true);
|
|
treeView->sortByColumn(OamDataModel::Columns::ID, Qt::AscendingOrder);
|
|
|
|
|
|
sidebarLayout = new QFormLayout;
|
|
sidebarLayout->setSizeConstraint(QLayout::SetMinimumSize);
|
|
sidebarLayout->setRowWrapPolicy(QFormLayout::DontWrapRows);
|
|
sidebarLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
|
sidebarLayout->setFormAlignment(Qt::AlignHCenter | Qt::AlignTop);
|
|
sidebarLayout->setLabelAlignment(Qt::AlignLeft);
|
|
layout->addLayout(sidebarLayout);
|
|
|
|
canvas = new OamCanvas(dataModel, graphicsScene, this);
|
|
sidebarLayout->addRow(canvas);
|
|
|
|
zoomCombo = new QComboBox;
|
|
zoomCombo->addItem("1x", QVariant(1));
|
|
zoomCombo->addItem("2x", QVariant(2));
|
|
zoomCombo->addItem("3x", QVariant(3));
|
|
zoomCombo->addItem("4x", QVariant(4));
|
|
zoomCombo->addItem("5x", QVariant(5));
|
|
zoomCombo->addItem("6x", QVariant(6));
|
|
zoomCombo->addItem("7x", QVariant(7));
|
|
zoomCombo->addItem("8x", QVariant(8));
|
|
zoomCombo->addItem("9x", QVariant(9));
|
|
sidebarLayout->addRow("Zoom:", zoomCombo);
|
|
|
|
autoUpdateBox = new QCheckBox("Auto update");
|
|
sidebarLayout->addRow(autoUpdateBox);
|
|
|
|
|
|
buttonLayout = new QHBoxLayout;
|
|
|
|
exportButton = new QPushButton("Export");
|
|
buttonLayout->addWidget(exportButton);
|
|
|
|
refreshButton = new QPushButton("Refresh");
|
|
buttonLayout->addWidget(refreshButton);
|
|
|
|
sidebarLayout->addRow(buttonLayout);
|
|
|
|
|
|
showScreenOutlineBox = new QCheckBox("Show Screen Outline");
|
|
showScreenOutlineBox->setChecked(true);
|
|
sidebarLayout->addRow(showScreenOutlineBox);
|
|
|
|
backgroundCombo = new QComboBox;
|
|
backgroundCombo->addItem("Transparent", OamGraphicsScene::BackgroundType::TRANSPARENT_BG);
|
|
backgroundCombo->addItem("Screen BG", OamGraphicsScene::BackgroundType::SCREEN_BG);
|
|
backgroundCombo->addItem("Sprite Palette 0", OamGraphicsScene::BackgroundType::PALETTE_0_BG);
|
|
backgroundCombo->addItem("Sprite Palette 1", OamGraphicsScene::BackgroundType::PALETTE_1_BG);
|
|
backgroundCombo->addItem("Sprite Palette 2", OamGraphicsScene::BackgroundType::PALETTE_2_BG);
|
|
backgroundCombo->addItem("Sprite Palette 3", OamGraphicsScene::BackgroundType::PALETTE_3_BG);
|
|
backgroundCombo->addItem("Sprite Palette 4", OamGraphicsScene::BackgroundType::PALETTE_4_BG);
|
|
backgroundCombo->addItem("Sprite Palette 5", OamGraphicsScene::BackgroundType::PALETTE_5_BG);
|
|
backgroundCombo->addItem("Sprite Palette 6", OamGraphicsScene::BackgroundType::PALETTE_6_BG);
|
|
backgroundCombo->addItem("Sprite Palette 7", OamGraphicsScene::BackgroundType::PALETTE_7_BG);
|
|
backgroundCombo->addItem("Magenta", OamGraphicsScene::BackgroundType::MAGENTA);
|
|
backgroundCombo->addItem("Cyan", OamGraphicsScene::BackgroundType::CYAN);
|
|
backgroundCombo->addItem("White", OamGraphicsScene::BackgroundType::WHITE);
|
|
backgroundCombo->addItem("Black", OamGraphicsScene::BackgroundType::BLACK);
|
|
sidebarLayout->addRow("Background:", backgroundCombo);
|
|
|
|
firstSprite = new QLineEdit;
|
|
firstSprite->setReadOnly(true);
|
|
firstSprite->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
|
firstSprite->setMinimumWidth(5 * firstSprite->fontMetrics().width('0'));
|
|
sidebarLayout->addRow("First Sprite:", firstSprite);
|
|
|
|
|
|
splitter->setSizes({ INT_MAX / 100 * 2, INT_MAX / 100 * 1 });
|
|
splitter->setStretchFactor(0, 2);
|
|
splitter->setStretchFactor(1, 1);
|
|
|
|
|
|
toggleVisibility = new QAction("Toggle Visibility", this);
|
|
showOnlySelectedObjects = new QAction("Show Only Selected Objects", this);
|
|
showAllObjects = new QAction("Show All Objects", this);
|
|
|
|
graphicsView->addAction(toggleVisibility);
|
|
graphicsView->addAction(showOnlySelectedObjects);
|
|
graphicsView->addAction(showAllObjects);
|
|
|
|
treeView->addAction(toggleVisibility);
|
|
treeView->addAction(showOnlySelectedObjects);
|
|
treeView->addAction(showAllObjects);
|
|
|
|
|
|
zoomCombo->setCurrentIndex(0);
|
|
onZoomChanged(0);
|
|
|
|
|
|
connect(exportButton, SIGNAL(clicked()), this, SLOT(onExportClicked()));
|
|
connect(refreshButton, SIGNAL(released()), this, SLOT(refresh()));
|
|
connect(zoomCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onZoomChanged(int)));
|
|
connect(showScreenOutlineBox, SIGNAL(clicked(bool)), graphicsScene, SLOT(setShowScreenOutline(bool)));
|
|
connect(backgroundCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(onBackgroundChanged(int)));
|
|
connect(treeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)), this, SLOT(onTreeViewSelectionChanged()));
|
|
connect(graphicsScene, SIGNAL(selectedIdsEdited()), this, SLOT(onGraphicsSceneSelectedIdsEdited()));
|
|
|
|
connect(showAllObjects, SIGNAL(triggered(bool)), dataModel, SLOT(showAllObjects()));
|
|
connect(toggleVisibility, SIGNAL(triggered(bool)), this, SLOT(onToggleVisibility()));
|
|
connect(showOnlySelectedObjects, SIGNAL(triggered(bool)), this, SLOT(onShowOnlySelectedObjects()));
|
|
}
|
|
|
|
void OamViewer::show() {
|
|
Window::show();
|
|
refresh();
|
|
}
|
|
|
|
void OamViewer::autoUpdate() {
|
|
if(isVisible() && autoUpdateBox->isChecked()) refresh();
|
|
}
|
|
|
|
void OamViewer::refresh() {
|
|
// Required to prevent the occasional infinite signal call loop.
|
|
if(inRefreshCall) return;
|
|
inRefreshCall = true;
|
|
|
|
dataModel->refresh();
|
|
graphicsScene->refresh();
|
|
canvas->refresh();
|
|
|
|
firstSprite->setText(QString::number(dataModel->firstSprite()));
|
|
|
|
inRefreshCall = false;
|
|
}
|
|
|
|
void OamViewer::onExportClicked() {
|
|
QImage image = graphicsScene->renderToImage();
|
|
|
|
if(image.isNull()) return;
|
|
|
|
QString selectedFile = QFileDialog::getSaveFileName(
|
|
this, "Export Sprites", config().path.current.exportVRAM, "PNG Image (*.png)");
|
|
|
|
if(!selectedFile.isEmpty()) {
|
|
QImageWriter writer(selectedFile, "PNG");
|
|
if(!writer.write(image)) {
|
|
QMessageBox::critical(this, "Export Sprites", "Unable to export sprites:\n\n" + writer.errorString());
|
|
}
|
|
config().path.current.exportVRAM = selectedFile;
|
|
}
|
|
}
|
|
|
|
void OamViewer::onZoomChanged(int index)
|
|
{
|
|
unsigned z = zoomCombo->itemData(index).toUInt();
|
|
graphicsView->setTransform(QTransform::fromScale(z, z));
|
|
}
|
|
|
|
void OamViewer::onBackgroundChanged(int index)
|
|
{
|
|
int t = backgroundCombo->itemData(index).toInt();
|
|
graphicsScene->setBackrgoundType(static_cast<OamGraphicsScene::BackgroundType>(t));
|
|
}
|
|
|
|
void OamViewer::onTreeViewSelectionChanged() {
|
|
const QModelIndexList proxySelectedRows = treeView->selectionModel()->selectedRows();
|
|
|
|
QSet<int> selectedIds;
|
|
selectedIds.reserve(proxySelectedRows.size() + 1);
|
|
for(const QModelIndex& proxyIndex : proxySelectedRows) {
|
|
const QModelIndex index = proxyModel->mapToSource(proxyIndex);
|
|
selectedIds.insert(dataModel->objectId(index));
|
|
}
|
|
|
|
graphicsScene->setSelectedIds(selectedIds);
|
|
|
|
if(selectedIds.size() == 1) {
|
|
canvas->setSelected(*selectedIds.begin());
|
|
}
|
|
else {
|
|
canvas->setSelected(-1);
|
|
}
|
|
|
|
updateActions();
|
|
}
|
|
|
|
void OamViewer::onGraphicsSceneSelectedIdsEdited() {
|
|
const QSet<int>& selectedIds = graphicsScene->selectedIds();
|
|
|
|
QItemSelection sel;
|
|
for(const int id : selectedIds) {
|
|
QModelIndex pIndex = proxyModel->mapFromSource(dataModel->objectIdToIndex(id));
|
|
sel.select(pIndex, pIndex);
|
|
}
|
|
treeView->selectionModel()->select(sel, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
|
|
|
if(selectedIds.size() == 1) {
|
|
int id = *selectedIds.begin();
|
|
QModelIndex pIndex = proxyModel->mapFromSource(dataModel->objectIdToIndex(id));
|
|
treeView->scrollTo(pIndex);
|
|
}
|
|
|
|
updateActions();
|
|
}
|
|
|
|
void OamViewer::updateActions() {
|
|
bool objSelected = graphicsScene->selectedIds().isEmpty() == false;
|
|
|
|
toggleVisibility->setEnabled(objSelected);
|
|
showOnlySelectedObjects->setEnabled(objSelected);
|
|
}
|
|
|
|
void OamViewer::onToggleVisibility() {
|
|
dataModel->toggleVisibility(graphicsScene->selectedIds());
|
|
}
|
|
|
|
void OamViewer::onShowOnlySelectedObjects() {
|
|
dataModel->showOnlySelectedObjects(graphicsScene->selectedIds());
|
|
}
|
|
|
|
OamCanvas::OamCanvas(OamDataModel* dataModel, OamGraphicsScene* graphicsScene, QWidget *parent)
|
|
: QFrame(parent)
|
|
, dataModel(dataModel)
|
|
, graphicsScene(graphicsScene)
|
|
, selected(-1)
|
|
, backgroundColor()
|
|
, pixmap()
|
|
, pixmapScale(1)
|
|
{
|
|
setFrameStyle(QFrame::Shape::Panel | QFrame::Sunken);
|
|
setLineWidth(2);
|
|
|
|
selected = -1;
|
|
setScale(2);
|
|
}
|
|
|
|
void OamCanvas::paintEvent(QPaintEvent*e) {
|
|
QFrame::paintEvent(e);
|
|
|
|
if(!pixmap.isNull()) {
|
|
QPainter painter(this);
|
|
painter.setRenderHints(0);
|
|
|
|
unsigned x = (width() - pixmapScale * pixmap.width()) / 2;
|
|
unsigned y = (height() - pixmapScale * pixmap.height()) / 2;
|
|
|
|
painter.translate(x, y);
|
|
painter.scale(pixmapScale, pixmapScale);
|
|
|
|
painter.fillRect(0, 0, pixmap.width(), pixmap.height(), backgroundColor);
|
|
painter.drawPixmap(0, 0, pixmap);
|
|
}
|
|
}
|
|
|
|
void OamCanvas::setScale(unsigned z) {
|
|
imageSize = 64 * z;
|
|
setFixedSize(imageSize + frameWidth() * 2, imageSize + frameWidth() * 2);
|
|
refresh();
|
|
}
|
|
|
|
void OamCanvas::setSelected(int s) {
|
|
selected = s;
|
|
refresh();
|
|
}
|
|
|
|
void OamCanvas::refresh() {
|
|
backgroundColor = graphicsScene->backgroundColorForObject(selected);
|
|
pixmap = graphicsScene->pixmapForObject(selected);
|
|
pixmapScale = imageSize / dataModel->objectSizes().maximumSize;
|
|
|
|
update();
|
|
}
|
|
|