Add color search feature

This commit is contained in:
GriffinR 2025-07-25 13:21:46 -04:00
parent 2f2f71948a
commit 6eaeee5f57
26 changed files with 776 additions and 249 deletions

152
forms/palettecolorsearch.ui Normal file
View File

@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PaletteColorSearch</class>
<widget class="QDialog" name="PaletteColorSearch">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>547</width>
<height>329</height>
</rect>
</property>
<property name="windowTitle">
<string>Palette Color Search</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_Title">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="searchBar">
<property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="frame_Color">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_Color">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Color</string>
</property>
</widget>
</item>
<item>
<widget class="NoScrollSpinBox" name="spinBox_ColorId">
<property name="value">
<number>1</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_Palette">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Palette</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBox_PaletteId"/>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTableWidget" name="table_Results">
<property name="columnCount">
<number>2</number>
</property>
<column/>
<column/>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>NoScrollSpinBox</class>
<extends>QSpinBox</extends>
<header>noscrollspinbox.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -38,6 +38,20 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolButton_ColorSearch">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Opens a search dialog to find which tilesets/metatiles are using certain colors.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../resources/images.qrc">
<normaloff>:/icons/magnifier.ico</normaloff>:/icons/magnifier.ico</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_AllColorsUsed">
<property name="text">
@ -96,7 +110,7 @@
<x>0</x>
<y>0</y>
<width>883</width>
<height>784</height>
<height>779</height>
</rect>
</property>
<layout class="QGridLayout" name="layout_Colors">
@ -146,9 +160,16 @@
</property>
<addaction name="actionShow_Unused_Colors"/>
</widget>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>Tools</string>
</property>
<addaction name="actionFind_Color_Usage"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuView"/>
<addaction name="menuTools"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionUndo">
@ -186,7 +207,14 @@
<string>Show Unused Colors</string>
</property>
</action>
<action name="actionFind_Color_Usage">
<property name="text">
<string>Find Color Usage...</string>
</property>
</action>
</widget>
<resources/>
<resources>
<include location="../resources/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -61,7 +61,7 @@ public:
static int getDefaultAttributesSize(BaseGameVersion version);
static void setLayout(Project*);
static QString getMetatileIdString(uint16_t metatileId);
static QString getMetatileIdStrings(const QList<uint16_t> metatileIds);
static QString getMetatileIdStrings(const QList<uint16_t> &metatileIds);
static QString getLayerName(int layerNum);
static constexpr int tileWidth() { return 2; }

View File

@ -37,10 +37,14 @@ public:
QList<QList<QRgb>> palettePreviews;
static QString stripPrefix(const QString &fullName);
static Tileset* getPaletteTileset(int, Tileset*, Tileset*);
static const Tileset* getPaletteTileset(int, const Tileset*, const Tileset*);
static Tileset* getMetatileTileset(int, Tileset*, Tileset*);
static const Tileset* getMetatileTileset(int, const Tileset*, const Tileset*);
static Tileset* getTileTileset(int, Tileset*, Tileset*);
static const Tileset* getTileTileset(int, const Tileset*, const Tileset*);
static Metatile* getMetatile(int, Tileset*, Tileset*);
static const Metatile* getMetatile(int, const Tileset*, const Tileset*);
static Tileset* getMetatileLabelTileset(int, Tileset*, Tileset*);
static QString getMetatileLabel(int, Tileset *, Tileset *);
static QString getOwnedMetatileLabel(int, Tileset *, Tileset *);
@ -48,9 +52,9 @@ public:
static bool setMetatileLabel(int, QString, Tileset *, Tileset *);
QString getMetatileLabelPrefix();
static QString getMetatileLabelPrefix(const QString &name);
static QList<QList<QRgb>> getBlockPalettes(Tileset*, Tileset*, bool useTruePalettes = false);
static QList<QRgb> getPalette(int, Tileset*, Tileset*, bool useTruePalettes = false);
static bool metatileIsValid(uint16_t metatileId, Tileset *, Tileset *);
static QList<QList<QRgb>> getBlockPalettes(const Tileset*, const Tileset*, bool useTruePalettes = false);
static QList<QRgb> getPalette(int, const Tileset*, const Tileset*, bool useTruePalettes = false);
static bool metatileIsValid(uint16_t metatileId, const Tileset*, const Tileset*);
static QHash<int, QString> getHeaderMemberMap(bool usingAsm);
static QString getExpectedDir(QString tilesetName, bool isSecondary);
QString getExpectedDir();
@ -93,7 +97,8 @@ public:
QImage tileImage(uint16_t tileId) const { return m_tiles.value(Tile::getIndexInTileset(tileId)); }
QSet<int> getUnusedColorIds(int paletteId, Tileset *pairedTileset, const QSet<int> &searchColors = {}) const;
QSet<int> getUnusedColorIds(int paletteId, const Tileset *pairedTileset, const QSet<int> &searchColors = {}) const;
QList<uint16_t> findMetatilesUsingColor(int paletteId, int colorId, const Tileset *pairedTileset) const;
static constexpr int maxPalettes() { return 16; }
static constexpr int numColorsPerPalette() { return 16; }

View File

@ -16,6 +16,7 @@ namespace Util {
QString replaceExtension(const QString &path, const QString &newExtension);
void setErrorStylesheet(QLineEdit *lineEdit, bool isError);
QString toStylesheetString(const QFont &font);
void show(QWidget *w);
}
#endif // UTILITY_H

View File

@ -67,8 +67,6 @@ public:
bool setLayout(QString layoutName);
void unsetMap();
Tileset *getCurrentMapPrimaryTileset();
bool displayMap();
bool displayLayout();

View File

@ -385,7 +385,6 @@ private:
void openDuplicateMapOrLayoutDialog();
void openNewMapGroupDialog();
void openNewLocationDialog();
void openSubWindow(QWidget * window);
void scrollMapList(MapTree *list, const QString &itemName);
void scrollMapListToCurrentMap(MapTree *list);
void scrollMapListToCurrentLayout(MapTree *list);

View File

@ -104,12 +104,11 @@ public:
bool load();
QMap<QString, Tileset*> tilesetCache;
Tileset* loadTileset(QString, Tileset *tileset = nullptr);
Tileset* getTileset(QString, bool forceLoad = false);
Tileset* getTileset(const QString&, bool forceLoad = false);
QStringList primaryTilesetLabels;
QStringList secondaryTilesetLabels;
QStringList tilesetLabelsOrdered;
QSet<QString> getPairedTilesetLabels(Tileset *tileset) const;
QSet<QString> getPairedTilesetLabels(const Tileset *tileset) const;
bool readMapGroups();
void addNewMapGroup(const QString &groupName);
@ -343,6 +342,7 @@ private:
void resetFileCache();
void resetFileWatcher();
void logFileWatchStatus();
void cacheTileset(const QString &label, Tileset *tileset);
bool saveMapLayouts();
bool saveMapGroups();

View File

@ -11,14 +11,14 @@ class Layout;
QImage getCollisionMetatileImage(Block);
QImage getCollisionMetatileImage(int, int);
QImage getMetatileImage(uint16_t, Layout*, bool useTruePalettes = false);
QImage getMetatileImage(Metatile*, Layout*, bool useTruePalettes = false);
QImage getMetatileImage(uint16_t, Tileset*, Tileset*, const QList<int>&, const QList<float>& = {}, bool useTruePalettes = false);
QImage getMetatileImage(Metatile*, Tileset*, Tileset*, const QList<int>&, const QList<float>& = {}, bool useTruePalettes = false);
QImage getMetatileImage(uint16_t, const Layout*, bool useTruePalettes = false);
QImage getMetatileImage(const Metatile*, const Layout*, bool useTruePalettes = false);
QImage getMetatileImage(uint16_t, const Tileset*, const Tileset*, const QList<int>& = {0,1,2}, const QList<float>& = {}, bool useTruePalettes = false);
QImage getMetatileImage(const Metatile*, const Tileset*, const Tileset*, const QList<int>& = {0,1,2}, const QList<float>& = {}, bool useTruePalettes = false);
QImage getMetatileSheetImage(Layout *layout, int numMetatilesWIde, bool useTruePalettes = false);
QImage getMetatileSheetImage(Tileset *primaryTileset,
Tileset *secondaryTileset,
QImage getMetatileSheetImage(const Layout *layout, int numMetatilesWIde, bool useTruePalettes = false);
QImage getMetatileSheetImage(const Tileset *primaryTileset,
const Tileset *secondaryTileset,
uint16_t metatileIdStart,
uint16_t metatileIdEnd,
int numMetatilesWIde,
@ -26,8 +26,8 @@ QImage getMetatileSheetImage(Tileset *primaryTileset,
const QList<float> &layerOpacity = {},
const QSize &metatileSize = Metatile::pixelSize(),
bool useTruePalettes = false);
QImage getMetatileSheetImage(Tileset *primaryTileset,
Tileset *secondaryTileset,
QImage getMetatileSheetImage(const Tileset *primaryTileset,
const Tileset *secondaryTileset,
int numMetatilesWide,
const QList<int> &layerOrder,
const QList<float> &layerOpacity = {},
@ -36,9 +36,9 @@ QImage getMetatileSheetImage(Tileset *primaryTileset,
QImage getTileImage(uint16_t, const Tileset*, const Tileset*);
QImage getPalettedTileImage(uint16_t, Tileset*, Tileset*, int, bool useTruePalettes = false);
QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList<QRgb> &palette);
QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset);
QImage getPalettedTileImage(uint16_t, const Tileset*, const Tileset*, int, bool useTruePalettes = false);
QImage getColoredTileImage(uint16_t tileId, const Tileset *, const Tileset *, const QList<QRgb> &palette);
QImage getGreyscaleTileImage(uint16_t tileId, const Tileset *, const Tileset *);
void flattenTo4bppImage(QImage * image);

View File

@ -0,0 +1,20 @@
#ifndef NUMERICSORTTABLEITEM_H
#define NUMERICSORTTABLEITEM_H
#include <QTableWidgetItem>
#include <QCollator>
class NumericSortTableItem : public QTableWidgetItem
{
public:
explicit NumericSortTableItem(const QString &text) : QTableWidgetItem(text) {};
protected:
virtual bool operator<(const QTableWidgetItem &other) const override {
QCollator collator;
collator.setNumericMode(true);
return collator.compare(text(), other.text()) < 0;
}
};
#endif // NUMERICSORTTABLEITEM_H

View File

@ -0,0 +1,67 @@
#ifndef PALETTECOLORSEARCH_H
#define PALETTECOLORSEARCH_H
#include <QDialog>
#include <QIcon>
#include <QMap>
class Tileset;
class Project;
namespace Ui {
class PaletteColorSearch;
}
class PaletteColorSearch : public QDialog
{
Q_OBJECT
public:
explicit PaletteColorSearch(Project *project,
const Tileset *primaryTileset,
const Tileset *secondaryTileset,
QWidget *parent = nullptr);
~PaletteColorSearch();
void setPaletteId(int paletteId);
int currentPaletteId() const;
void setColorId(int colorId);
int currentColorId() const;
void setTilesets(const Tileset *primaryTileset, const Tileset *secondaryTileset);
const Tileset* currentTileset() const;
signals:
void metatileSelected(uint16_t metatileId);
void paletteIdChanged(int paletteId);
private:
struct RowData {
QString tilesetName;
QString pairedTilesetName;
QString metatileId;
QIcon metatileIcon;
};
enum ResultsColumn {
TilesetName,
Metatile,
};
Ui::PaletteColorSearch *ui;
Project *m_project;
const Tileset *m_primaryTileset;
const Tileset *m_secondaryTileset;
QMap<QString,QList<RowData>> m_resultsCache;
void addTableEntry(const RowData &rowData);
QList<RowData> search(int colorId) const;
QList<RowData> search(int colorId, const Tileset *tileset, const Tileset *pairedTileset) const;
void refresh();
void updateResults();
void cellDoubleClicked(int row, int col);
};
#endif // PALETTECOLORSEARCH_H

View File

@ -2,10 +2,12 @@
#define PALETTEEDITOR_H
#include <QMainWindow>
#include <QPointer>
#include "colorinputwidget.h"
#include "project.h"
#include "history.h"
#include "palettecolorsearch.h"
namespace Ui {
class PaletteEditor;
@ -32,27 +34,31 @@ public:
bool showingUnusedColors() const;
signals:
void metatileSelected(uint16_t metatileId);
private:
Ui::PaletteEditor *ui;
Project *project = nullptr;
QList<ColorInputWidget*> colorInputs;
Project *project;
Tileset *primaryTileset;
Tileset *secondaryTileset;
QList<ColorInputWidget*> colorInputs;
QList<History<PaletteHistoryItem*>> palettesHistory;
QMap<int,QSet<int>> unusedColorCache;
QPointer<PaletteColorSearch> colorSearchWindow;
Tileset* getTileset(int paletteId) const;
void refreshColorInputs();
void refreshPaletteId();
void commitEditHistory();
void commitEditHistory(int paletteId);
void restoreWindowState();
void onWindowActivated();
void invalidateCache();
void closeEvent(QCloseEvent*);
void setColorInputTitles(bool show);
QSet<int> getUnusedColorIds() const;
QSet<int> getUnusedColorIds();
void openColorSearch();
void setRgb(int index, QRgb rgb);
void setPalette(int paletteId, const QList<QRgb> &palette);
@ -67,7 +73,6 @@ signals:
void changedPaletteColor();
void changedPalette(int);
private slots:
void on_spinBox_PaletteId_valueChanged(int arg1);
void on_actionUndo_triggered();
void on_actionRedo_triggered();
void on_actionImport_Palette_triggered();

View File

@ -2,6 +2,7 @@
#define TILESETEDITOR_H
#include <QMainWindow>
#include <QPointer>
#include "project.h"
#include "history.h"
#include "paletteeditor.h"
@ -54,6 +55,9 @@ public:
QObjectList shortcutableObjects() const;
void setPaletteId(int paletteId);
int paletteId() const;
public slots:
void applyUserShortcuts();
void onSelectedMetatileChanged(uint16_t);
@ -65,13 +69,9 @@ private slots:
void onHoveredTileChanged(const Tile&);
void onHoveredTileChanged(uint16_t);
void onHoveredTileCleared();
void onSelectedTilesChanged();
void onMetatileLayerTileChanged(int, int);
void onMetatileLayerSelectionChanged(QPoint, int, int);
void onPaletteEditorChangedPaletteColor();
void onPaletteEditorChangedPalette(int);
void on_spinBox_paletteSelector_valueChanged(int arg1);
void on_actionChange_Metatiles_Count_triggered();
@ -136,23 +136,20 @@ private:
void commitTerrainType();
void commitLayerType();
void setRawAttributesVisible(bool visible);
void setXFlip(bool enabled);
void setYFlip(bool enabled);
void refreshTileFlips();
void refreshPaletteId();
Ui::TilesetEditor *ui;
History<MetatileHistoryItem*> metatileHistory;
TilesetEditorMetatileSelector *metatileSelector = nullptr;
TilesetEditorTileSelector *tileSelector = nullptr;
MetatileLayersItem *metatileLayersItem = nullptr;
PaletteEditor *paletteEditor = nullptr;
QPointer<PaletteEditor> paletteEditor = nullptr;
Project *project = nullptr;
Layout *layout = nullptr;
Metatile *metatile = nullptr;
Metatile *copiedMetatile = nullptr;
QString copiedMetatileLabel;
int paletteId;
bool tileXFlip;
bool tileYFlip;
bool hasUnsavedChanges;
Tileset *primaryTileset = nullptr;
Tileset *secondaryTileset = nullptr;

View File

@ -2,24 +2,11 @@
#define WILDMONSEARCH_H
#include <QDialog>
#include <QTableWidgetItem>
#include <QCollator>
#include "numericsorttableitem.h"
class Project;
class NumericSortTableItem : public QTableWidgetItem
{
public:
explicit NumericSortTableItem(const QString &text) : QTableWidgetItem(text) {};
protected:
virtual bool operator<(const QTableWidgetItem &other) const override {
QCollator collator;
collator.setNumericMode(true);
return collator.compare(text(), other.text()) < 0;
}
};
namespace Ui {
class WildMonSearch;
}

View File

@ -116,6 +116,7 @@ SOURCES += src/core/advancemapparser.cpp \
src/ui/montabwidget.cpp \
src/ui/encountertablemodel.cpp \
src/ui/encountertabledelegates.cpp \
src/ui/palettecolorsearch.cpp \
src/ui/paletteeditor.cpp \
src/ui/selectablepixmapitem.cpp \
src/ui/tileseteditor.cpp \
@ -234,6 +235,7 @@ HEADERS += include/core/advancemapparser.h \
include/ui/encountertablemodel.h \
include/ui/encountertabledelegates.h \
include/ui/adjustingstackedwidget.h \
include/ui/palettecolorsearch.h \
include/ui/paletteeditor.h \
include/ui/selectablepixmapitem.h \
include/ui/tileseteditor.h \
@ -287,6 +289,7 @@ FORMS += forms/mainwindow.ui \
forms/prefabcreationdialog.ui \
forms/prefabframe.ui \
forms/tileseteditor.ui \
forms/palettecolorsearch.ui \
forms/paletteeditor.ui \
forms/regionmapeditor.ui \
forms/newmapdialog.ui \

View File

@ -48,7 +48,7 @@ QString Metatile::getMetatileIdString(uint16_t metatileId) {
return Util::toHexString(metatileId, numMetatileIdChars);
};
QString Metatile::getMetatileIdStrings(const QList<uint16_t> metatileIds) {
QString Metatile::getMetatileIdStrings(const QList<uint16_t> &metatileIds) {
QStringList metatiles;
for (auto metatileId : metatileIds)
metatiles << Metatile::getMetatileIdString(metatileId);

View File

@ -8,6 +8,7 @@
#include <QPainter>
#include <QImage>
#include <algorithm>
Tileset::Tileset(const Tileset &other)
@ -110,6 +111,20 @@ int Tileset::maxTiles() const {
return this->is_secondary ? Project::getNumTilesSecondary() : Project::getNumTilesPrimary();
}
Tileset* Tileset::getPaletteTileset(int paletteId, Tileset *primaryTileset, Tileset *secondaryTileset) {
return const_cast<Tileset*>(getPaletteTileset(paletteId, static_cast<const Tileset*>(primaryTileset), static_cast<const Tileset*>(secondaryTileset)));
}
const Tileset* Tileset::getPaletteTileset(int paletteId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
if (paletteId < Project::getNumPalettesPrimary()) {
return primaryTileset;
} else if (paletteId < Project::getNumPalettesTotal()) {
return secondaryTileset;
} else {
return nullptr;
}
}
Tileset* Tileset::getTileTileset(int tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
return const_cast<Tileset*>(getTileTileset(tileId, static_cast<const Tileset*>(primaryTileset), static_cast<const Tileset*>(secondaryTileset)));
}
@ -125,8 +140,12 @@ const Tileset* Tileset::getTileTileset(int tileId, const Tileset *primaryTileset
}
}
// Get the tileset *expected* to contain the given 'metatileId'. Note that this does not mean the metatile actually exists in that tileset.
Tileset* Tileset::getMetatileTileset(int metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
return const_cast<Tileset*>(getMetatileTileset(metatileId, static_cast<const Tileset*>(primaryTileset), static_cast<const Tileset*>(secondaryTileset)));
}
// Get the tileset *expected* to contain the given 'metatileId'. Note that this does not mean the metatile actually exists in that tileset.
const Tileset* Tileset::getMetatileTileset(int metatileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
if (metatileId < Project::getNumMetatilesPrimary()) {
return primaryTileset;
} else if (metatileId < Project::getNumMetatilesTotal()) {
@ -137,7 +156,11 @@ Tileset* Tileset::getMetatileTileset(int metatileId, Tileset *primaryTileset, Ti
}
Metatile* Tileset::getMetatile(int metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
Tileset *tileset = Tileset::getMetatileTileset(metatileId, primaryTileset, secondaryTileset);
return const_cast<Metatile*>(getMetatile(metatileId, static_cast<const Tileset*>(primaryTileset), static_cast<const Tileset*>(secondaryTileset)));
}
const Metatile* Tileset::getMetatile(int metatileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
const Tileset *tileset = Tileset::getMetatileTileset(metatileId, primaryTileset, secondaryTileset);
if (!tileset) {
return nullptr;
}
@ -226,12 +249,12 @@ QString Tileset::getMetatileLabelPrefix(const QString &name)
return QString("%1%2_").arg(labelPrefix).arg(Tileset::stripPrefix(name));
}
bool Tileset::metatileIsValid(uint16_t metatileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
bool Tileset::metatileIsValid(uint16_t metatileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
return (primaryTileset && primaryTileset->contains(metatileId))
|| (secondaryTileset && secondaryTileset->contains(metatileId));
}
QList<QList<QRgb>> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *secondaryTileset, bool useTruePalettes) {
QList<QList<QRgb>> Tileset::getBlockPalettes(const Tileset *primaryTileset, const Tileset *secondaryTileset, bool useTruePalettes) {
QList<QList<QRgb>> palettes;
QList<QList<QRgb>> primaryPalettes;
@ -253,9 +276,9 @@ QList<QList<QRgb>> Tileset::getBlockPalettes(Tileset *primaryTileset, Tileset *s
return palettes;
}
QList<QRgb> Tileset::getPalette(int paletteId, Tileset *primaryTileset, Tileset *secondaryTileset, bool useTruePalettes) {
QList<QRgb> Tileset::getPalette(int paletteId, const Tileset *primaryTileset, const Tileset *secondaryTileset, bool useTruePalettes) {
QList<QRgb> paletteTable;
Tileset *tileset = paletteId < Project::getNumPalettesPrimary()
const Tileset *tileset = paletteId < Project::getNumPalettesPrimary()
? primaryTileset
: secondaryTileset;
if (!tileset) {
@ -669,7 +692,7 @@ QString Tileset::stripPrefix(const QString &fullName) {
// Find which of the specified color IDs in 'searchColors' are not used by any of this tileset's metatiles.
// The 'pairedTileset' may be used to get the tile images for any tiles that don't belong to this tileset.
// If 'searchColors' is empty, it will for search for all unused colors.
QSet<int> Tileset::getUnusedColorIds(int paletteId, Tileset *pairedTileset, const QSet<int> &searchColors) const {
QSet<int> Tileset::getUnusedColorIds(int paletteId, const Tileset *pairedTileset, const QSet<int> &searchColors) const {
QSet<int> unusedColors = searchColors;
if (unusedColors.isEmpty()) {
// Search for all colors
@ -707,3 +730,43 @@ QSet<int> Tileset::getUnusedColorIds(int paletteId, Tileset *pairedTileset, cons
}
return unusedColors;
}
// Returns the list of metatile IDs representing all the metatiles in this tileset that use the specified color ID.
QList<uint16_t> Tileset::findMetatilesUsingColor(int paletteId, int colorId, const Tileset *pairedTileset) const {
const Tileset *primaryTileset = this->is_secondary ? pairedTileset : this;
const Tileset *secondaryTileset = this->is_secondary ? this : pairedTileset;
QSet<uint16_t> metatileIdSet;
QHash<uint16_t, bool> tileContainsColor;
uint16_t metatileIdBase = firstMetatileId();
for (int i = 0; i < m_metatiles.length(); i++) {
uint16_t metatileId = i + metatileIdBase;
for (const auto &tile : m_metatiles.at(i)->tiles) {
if (tile.palette != paletteId)
continue;
// Save time on tiles we've already inspected by getting the cached result.
auto tileIt = tileContainsColor.constFind(tile.tileId);
if (tileIt != tileContainsColor.constEnd()) {
if (tileIt.value()) metatileIdSet.insert(metatileId);
continue;
}
tileContainsColor[tile.tileId] = false;
QImage image = getTileImage(tile.tileId, primaryTileset, secondaryTileset);
if (image.isNull() || image.sizeInBytes() < Tile::numPixels())
continue;
const uchar * pixels = image.constBits();
for (int j = 0; j < Tile::numPixels(); j++) {
if (pixels[j] == colorId) {
metatileIdSet.insert(metatileId);
tileContainsColor[tile.tileId] = true;
break;
}
}
}
}
QList<uint16_t> metatileIds(metatileIdSet.constBegin(), metatileIdSet.constEnd());
std::sort(metatileIds.begin(), metatileIds.end());
return metatileIds;
}

View File

@ -112,3 +112,16 @@ void Util::setErrorStylesheet(QLineEdit *lineEdit, bool isError) {
static const QString stylesheet = QStringLiteral("QLineEdit { background-color: rgba(255, 0, 0, 25%) }");
lineEdit->setStyleSheet(isError ? stylesheet : "");
}
void Util::show(QWidget *w) {
if (!w) return;
if (!w->isVisible()) {
w->show();
} else if (w->isMinimized()) {
w->showNormal();
} else {
w->raise();
w->activateWindow();
}
}

View File

@ -2060,12 +2060,6 @@ void Editor::updateCustomMapAttributes()
map->modify();
}
Tileset* Editor::getCurrentMapPrimaryTileset()
{
QString tilesetLabel = this->layout->tileset_primary_label;
return project->getTileset(tilesetLabel);
}
void Editor::redrawAllEvents() {
if (this->map) redrawEvents(this->map->getEvents());
}

View File

@ -345,7 +345,7 @@ void MainWindow::checkForUpdates(bool requestedByUser) {
if (requestedByUser) {
openSubWindow(this->updatePromoter);
Util::show(this->updatePromoter);
} else {
// This is an automatic update check. Only run if we haven't done one in the last 5 minutes
QDateTime lastCheck = porymapConfig.lastUpdateCheckTime;
@ -419,7 +419,7 @@ void MainWindow::initEditor() {
}
void MainWindow::openEditHistory() {
openSubWindow(this->undoView);
Util::show(this->undoView);
}
void MainWindow::initMiscHeapObjects() {
@ -936,19 +936,6 @@ void MainWindow::refreshRecentProjectsMenu() {
clearAction->setEnabled(!recentProjects.isEmpty());
}
void MainWindow::openSubWindow(QWidget * window) {
if (!window) return;
if (!window->isVisible()) {
window->show();
} else if (window->isMinimized()) {
window->showNormal();
} else {
window->raise();
window->activateWindow();
}
}
void MainWindow::showFileWatcherWarning() {
if (!porymapConfig.monitorFiles || !isProjectOpen())
return;
@ -2266,7 +2253,7 @@ void MainWindow::on_actionGrid_Settings_triggered() {
connect(this->gridSettingsDialog, &GridSettingsDialog::changedGridSettings, this->editor, &Editor::updateMapGrid);
connect(this->gridSettingsDialog, &GridSettingsDialog::accepted, [this] { porymapConfig.gridSettings = this->editor->gridSettings; });
}
openSubWindow(this->gridSettingsDialog);
Util::show(this->gridSettingsDialog);
}
void MainWindow::on_actionShortcuts_triggered()
@ -2274,7 +2261,7 @@ void MainWindow::on_actionShortcuts_triggered()
if (!shortcutsEditor)
initShortcutsEditor();
openSubWindow(shortcutsEditor);
Util::show(shortcutsEditor);
}
void MainWindow::initShortcutsEditor() {
@ -2698,7 +2685,7 @@ void MainWindow::showExportMapImageWindow(ImageExporterMode mode) {
}
}
openSubWindow(this->mapImageExporter);
Util::show(this->mapImageExporter);
}
void MainWindow::on_pushButton_AddConnection_clicked() {
@ -2726,7 +2713,7 @@ void MainWindow::on_pushButton_SummaryChart_clicked() {
connect(this->editor, &Editor::wildMonTableClosed, this->wildMonChart, &WildMonChart::clearTable);
connect(this->editor, &Editor::wildMonTableEdited, this->wildMonChart, &WildMonChart::refresh);
}
openSubWindow(this->wildMonChart);
Util::show(this->wildMonChart);
}
void MainWindow::on_toolButton_WildMonSearch_clicked() {
@ -2735,7 +2722,7 @@ void MainWindow::on_toolButton_WildMonSearch_clicked() {
connect(this->wildMonSearch, &WildMonSearch::openWildMonTableRequested, this, &MainWindow::openWildMonTable);
connect(this->editor, &Editor::wildMonTableEdited, this->wildMonSearch, &WildMonSearch::refresh);
}
openSubWindow(this->wildMonSearch);
Util::show(this->wildMonSearch);
}
void MainWindow::openWildMonTable(const QString &mapName, const QString &groupName, const QString &fieldName) {
@ -2843,7 +2830,7 @@ void MainWindow::on_actionTileset_Editor_triggered()
initTilesetEditor();
}
openSubWindow(this->tilesetEditor);
Util::show(this->tilesetEditor);
MetatileSelection selection = this->editor->metatile_selector_item->getMetatileSelection();
if (!selection.metatileItems.isEmpty()) {
@ -2895,7 +2882,7 @@ void MainWindow::on_actionAbout_Porymap_triggered()
{
if (!this->aboutWindow)
this->aboutWindow = new AboutPorymap(this);
openSubWindow(this->aboutWindow);
Util::show(this->aboutWindow);
}
void MainWindow::on_actionOpen_Log_File_triggered() {
@ -2927,7 +2914,7 @@ void MainWindow::on_actionPreferences_triggered() {
connect(preferenceEditor, &PreferenceEditor::reloadProjectRequested, this, &MainWindow::on_action_Reload_Project_triggered);
}
openSubWindow(preferenceEditor);
Util::show(preferenceEditor);
}
void MainWindow::togglePreferenceSpecificUi() {
@ -2944,7 +2931,7 @@ void MainWindow::openProjectSettingsEditor(int tab) {
this, &MainWindow::on_action_Reload_Project_triggered);
}
this->projectSettingsEditor->setTab(tab);
openSubWindow(this->projectSettingsEditor);
Util::show(this->projectSettingsEditor);
}
void MainWindow::on_actionProject_Settings_triggered() {
@ -2982,7 +2969,7 @@ void MainWindow::on_actionCustom_Scripts_triggered() {
if (!this->customScriptsEditor)
initCustomScriptsEditor();
openSubWindow(this->customScriptsEditor);
Util::show(this->customScriptsEditor);
}
void MainWindow::initCustomScriptsEditor() {
@ -3062,7 +3049,7 @@ void MainWindow::on_actionRegion_Map_Editor_triggered() {
}
}
openSubWindow(this->regionMapEditor);
Util::show(this->regionMapEditor);
}
void MainWindow::on_pushButton_CreatePrefab_clicked() {
@ -3120,7 +3107,7 @@ bool MainWindow::closeSupplementaryWindows() {
if (widget != this && widget->isWindow()) {
// Make sure the window is raised and activated before closing in case it has a confirmation prompt.
if (widget->isVisible()) {
openSubWindow(widget);
Util::show(widget);
}
if (!widget->close()) {
QString message = QStringLiteral("Aborted project close");

View File

@ -186,6 +186,8 @@ bool Project::load() {
this->parser.setUpdatesSplashScreen(true);
resetFileWatcher();
resetFileCache();
QPixmapCache::clear();
this->disabledSettingsNames.clear();
bool success = readGlobalConstants()
&& readMapLayouts()
@ -273,6 +275,17 @@ void Project::clearTilesetCache() {
this->tilesetCache.clear();
}
void Project::cacheTileset(const QString &name, Tileset *tileset) {
auto it = this->tilesetCache.constFind(name);
if (it != this->tilesetCache.constEnd() && it.value() && tileset != it.value()) {
// Callers of this function should ensure this doesn't happen,
// but in case it does we should avoid leaking memory.
logWarn(QString("New tileset %1 overwrote existing tileset.").arg(name));
delete it.value();
}
this->tilesetCache.insert(name, tileset);
}
Map* Project::loadMap(const QString &mapName) {
if (mapName == getDynamicMapName()) {
// Silently ignored, caller is expected to handle this if they want this to be an error.
@ -1180,7 +1193,21 @@ bool Project::loadLayoutTilesets(Layout *layout) {
return layout->tileset_primary && layout->tileset_secondary;
}
Tileset* Project::loadTileset(QString label, Tileset *tileset) {
Tileset* Project::getTileset(const QString &label, bool forceLoad) {
Tileset *tileset = nullptr;
auto it = this->tilesetCache.constFind(label);
if (it != this->tilesetCache.constEnd()) {
tileset = it.value();
if (!forceLoad) {
return tileset;
}
} else {
// Create a cache entry even if we don't end up loading the tileset successfully.
// This will prevent repeated file reads if the tileset fails to load.
cacheTileset(label, nullptr);
}
auto memberMap = Tileset::getHeaderMemberMap(this->usingAsmTilesets);
if (this->usingAsmTilesets) {
// Read asm tileset header. Backwards compatibility
@ -1225,7 +1252,7 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) {
return nullptr;
}
tilesetCache.insert(label, tileset);
cacheTileset(tileset->name, tileset);
return tileset;
}
@ -1617,15 +1644,14 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa
metatilesFilepath.append(projectConfig.getFilePath(ProjectFilePath::tilesets_metatiles));
}
ignoreWatchedFilesTemporarily({headersFilepath, graphicsFilepath, metatilesFilepath});
name = Tileset::stripPrefix(name);
tileset->appendToHeaders(headersFilepath, name, this->usingAsmTilesets);
tileset->appendToGraphics(graphicsFilepath, name, this->usingAsmTilesets);
tileset->appendToMetatiles(metatilesFilepath, name, this->usingAsmTilesets);
QString baseName = Tileset::stripPrefix(name);
tileset->appendToHeaders(headersFilepath, baseName, this->usingAsmTilesets);
tileset->appendToGraphics(graphicsFilepath, baseName, this->usingAsmTilesets);
tileset->appendToMetatiles(metatilesFilepath, baseName, this->usingAsmTilesets);
tileset->save();
this->tilesetCache.insert(tileset->name, tileset);
cacheTileset(tileset->name, tileset);
emit tilesetCreated(tileset);
return tileset;
}
@ -1680,20 +1706,6 @@ void Project::loadTilesetMetatileLabels(Tileset* tileset) {
}
}
Tileset* Project::getTileset(QString label, bool forceLoad) {
Tileset *existingTileset = nullptr;
if (tilesetCache.contains(label)) {
existingTileset = tilesetCache.value(label);
}
if (existingTileset && !forceLoad) {
return existingTileset;
} else {
Tileset *tileset = loadTileset(label, existingTileset);
return tileset;
}
}
bool Project::saveTextFile(const QString &path, const QString &text) {
QFile file(path);
if (!file.open(QIODevice::WriteOnly)) {
@ -2266,6 +2278,7 @@ bool Project::readTilesetLabels() {
this->primaryTilesetLabels.clear();
this->secondaryTilesetLabels.clear();
this->tilesetLabelsOrdered.clear();
clearTilesetCache();
QString filename = projectConfig.getFilePath(ProjectFilePath::tilesets_headers);
QFileInfo fileInfo(this->root + "/" + filename);
@ -3520,7 +3533,7 @@ bool Project::hasUnsavedChanges() {
}
// Searches the project's map layouts to find the names of the tilesets that the provided tileset gets paired with.
QSet<QString> Project::getPairedTilesetLabels(Tileset *tileset) const {
QSet<QString> Project::getPairedTilesetLabels(const Tileset *tileset) const {
QSet<QString> pairedLabels;
for (const auto &layout : this->mapLayouts) {
if (tileset->is_secondary) {

View File

@ -12,14 +12,14 @@ QImage getCollisionMetatileImage(int collision, int elevation) {
return image ? *image : QImage();
}
QImage getMetatileImage(uint16_t metatileId, Layout *layout, bool useTruePalettes) {
QImage getMetatileImage(uint16_t metatileId, const Layout *layout, bool useTruePalettes) {
Metatile* metatile = Tileset::getMetatile(metatileId,
layout ? layout->tileset_primary : nullptr,
layout ? layout->tileset_secondary : nullptr);
return getMetatileImage(metatile, layout, useTruePalettes);
}
QImage getMetatileImage(Metatile *metatile, Layout *layout, bool useTruePalettes) {
QImage getMetatileImage(const Metatile *metatile, const Layout *layout, bool useTruePalettes) {
if (!layout) {
return getMetatileImage(metatile, nullptr, nullptr, {}, {}, useTruePalettes);
}
@ -33,8 +33,8 @@ QImage getMetatileImage(Metatile *metatile, Layout *layout, bool useTruePalettes
QImage getMetatileImage(
uint16_t metatileId,
Tileset *primaryTileset,
Tileset *secondaryTileset,
const Tileset *primaryTileset,
const Tileset *secondaryTileset,
const QList<int> &layerOrder,
const QList<float> &layerOpacity,
bool useTruePalettes)
@ -54,9 +54,9 @@ QColor getInvalidImageColor() {
}
QImage getMetatileImage(
Metatile *metatile,
Tileset *primaryTileset,
Tileset *secondaryTileset,
const Metatile *metatile,
const Tileset *primaryTileset,
const Tileset *secondaryTileset,
const QList<int> &layerOrder,
const QList<float> &layerOpacity,
bool useTruePalettes)
@ -146,7 +146,7 @@ QImage getTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tilese
return tileset ? tileset->tileImage(tileId) : QImage();
}
QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, const QList<QRgb> &palette) {
QImage getColoredTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset, const QList<QRgb> &palette) {
QImage tileImage = getTileImage(tileId, primaryTileset, secondaryTileset);
if (tileImage.isNull()) {
// Some tiles specify tile IDs or palette IDs that are outside the valid range.
@ -162,12 +162,12 @@ QImage getColoredTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *se
return tileImage;
}
QImage getPalettedTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset, int paletteId, bool useTruePalettes) {
QImage getPalettedTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset, int paletteId, bool useTruePalettes) {
QList<QRgb> palette = Tileset::getPalette(paletteId, primaryTileset, secondaryTileset, useTruePalettes);
return getColoredTileImage(tileId, primaryTileset, secondaryTileset, palette);
}
QImage getGreyscaleTileImage(uint16_t tileId, Tileset *primaryTileset, Tileset *secondaryTileset) {
QImage getGreyscaleTileImage(uint16_t tileId, const Tileset *primaryTileset, const Tileset *secondaryTileset) {
return getColoredTileImage(tileId, primaryTileset, secondaryTileset, greyscalePalette);
}
@ -181,8 +181,8 @@ void flattenTo4bppImage(QImage * image) {
}
// Constructs a grid image of the metatiles in the specified ID range.
QImage getMetatileSheetImage(Tileset *primaryTileset,
Tileset *secondaryTileset,
QImage getMetatileSheetImage(const Tileset *primaryTileset,
const Tileset *secondaryTileset,
uint16_t metatileIdStart,
uint16_t metatileIdEnd,
int numMetatilesWide,
@ -219,15 +219,15 @@ QImage getMetatileSheetImage(Tileset *primaryTileset,
// Constructs a grid image of the metatiles in the primary and secondary tileset,
// rounding as necessary to keep the two tilesets on separate rows.
// The unused metatiles (if any) between the primary and secondary tilesets are skipped.
QImage getMetatileSheetImage(Tileset *primaryTileset,
Tileset *secondaryTileset,
QImage getMetatileSheetImage(const Tileset *primaryTileset,
const Tileset *secondaryTileset,
int numMetatilesWide,
const QList<int> &layerOrder,
const QList<float> &layerOpacity,
const QSize &metatileSize,
bool useTruePalettes)
{
auto createSheetImage = [=](uint16_t start, Tileset *tileset) {
auto createSheetImage = [=](uint16_t start, const Tileset *tileset) {
uint16_t end = start;
if (tileset) {
if (tileset->numMetatiles() == 0)
@ -259,7 +259,7 @@ QImage getMetatileSheetImage(Tileset *primaryTileset,
return image;
}
QImage getMetatileSheetImage(Layout *layout, int numMetatilesWide, bool useTruePalettes) {
QImage getMetatileSheetImage(const Layout *layout, int numMetatilesWide, bool useTruePalettes) {
if (!layout)
return QImage();
return getMetatileSheetImage(layout->tileset_primary,

View File

@ -0,0 +1,207 @@
#include "palettecolorsearch.h"
#include "ui_palettecolorsearch.h"
#include "project.h"
#include "tileset.h"
#include "imageproviders.h"
#include "eventfilters.h"
#include "log.h"
#include "numericsorttableitem.h"
enum ResultsDataRole {
PairedTilesetName = Qt::UserRole,
};
PaletteColorSearch::PaletteColorSearch(Project *project, const Tileset *primaryTileset, const Tileset *secondaryTileset, QWidget *parent) :
QDialog(parent),
ui(new Ui::PaletteColorSearch),
m_project(project),
m_primaryTileset(primaryTileset),
m_secondaryTileset(secondaryTileset)
{
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
ui->buttonBox->setVisible(isModal());
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close);
// Rather than try to keep track of metatile/tile changes that affect which colors are used,
// we'll just refresh when the window is activated.
ActiveWindowFilter *filter = new ActiveWindowFilter(this);
connect(filter, &ActiveWindowFilter::activated, this, &PaletteColorSearch::refresh);
this->installEventFilter(filter);
ui->spinBox_ColorId->setRange(0, Tileset::numColorsPerPalette() - 1);
connect(ui->spinBox_ColorId, QOverload<int>::of(&QSpinBox::valueChanged), this, &PaletteColorSearch::updateResults);
ui->spinBox_PaletteId->setRange(0, Project::getNumPalettesTotal() - 1);
connect(ui->spinBox_PaletteId, QOverload<int>::of(&QSpinBox::valueChanged), this, &PaletteColorSearch::updateResults);
connect(ui->spinBox_PaletteId, QOverload<int>::of(&QSpinBox::valueChanged), this, &PaletteColorSearch::paletteIdChanged);
// Set up table header
static const QStringList labels = {"Tileset", "Metatile"};
ui->table_Results->setHorizontalHeaderLabels(labels);
ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::TilesetName, QHeaderView::ResizeToContents);
ui->table_Results->horizontalHeader()->setSectionResizeMode(ResultsColumn::Metatile, QHeaderView::Stretch);
// Table is read-only
ui->table_Results->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->table_Results->setSelectionMode(QAbstractItemView::NoSelection);
connect(ui->table_Results, &QTableWidget::cellDoubleClicked, this, &PaletteColorSearch::cellDoubleClicked);
}
PaletteColorSearch::~PaletteColorSearch() {
delete ui;
}
void PaletteColorSearch::setPaletteId(int paletteId) {
ui->spinBox_PaletteId->setValue(paletteId);
}
int PaletteColorSearch::currentPaletteId() const {
return ui->spinBox_PaletteId->value();
}
void PaletteColorSearch::setColorId(int colorId) {
ui->spinBox_ColorId->setValue(colorId);
}
int PaletteColorSearch::currentColorId() const {
return ui->spinBox_ColorId->value();
}
void PaletteColorSearch::setTilesets(const Tileset *primaryTileset, const Tileset *secondaryTileset) {
m_primaryTileset = primaryTileset;
m_secondaryTileset = secondaryTileset;
refresh();
}
const Tileset* PaletteColorSearch::currentTileset() const {
return Tileset::getPaletteTileset(currentPaletteId(), m_primaryTileset, m_secondaryTileset);
}
void PaletteColorSearch::addTableEntry(const RowData &rowData) {
int row = ui->table_Results->rowCount();
ui->table_Results->insertRow(row);
auto tilesetNameItem = new NumericSortTableItem(rowData.tilesetName);
tilesetNameItem->setData(ResultsDataRole::PairedTilesetName, rowData.pairedTilesetName);
ui->table_Results->setItem(row, ResultsColumn::TilesetName, tilesetNameItem);
ui->table_Results->setItem(row, ResultsColumn::Metatile, new QTableWidgetItem(rowData.metatileIcon, rowData.metatileId));
}
QList<PaletteColorSearch::RowData> PaletteColorSearch::search(int colorId) const {
QList<RowData> results;
// Check our current tilesets for color usage.
results.append(search(colorId, m_primaryTileset, m_secondaryTileset));
results.append(search(colorId, m_secondaryTileset, m_primaryTileset));
// The current palette comes from either the primary or secondary tileset.
// We need to check all the other tilesets that are paired with the tileset that owns this palette.
const Tileset *paletteTileset = currentTileset();
QSet<QString> tilesetsToSearch = m_project->getPairedTilesetLabels(paletteTileset);
// We exclude the currently-loaded pair (we already checked them, and because they're being
// edited in the Tileset Editor they may differ from their copies saved in the layout).
tilesetsToSearch.remove(m_primaryTileset->name);
tilesetsToSearch.remove(m_secondaryTileset->name);
for (const auto &label : tilesetsToSearch) {
Tileset *searchTileset = m_project->getTileset(label);
if (searchTileset) {
results.append(search(colorId, searchTileset, paletteTileset));
}
}
return results;
}
QList<PaletteColorSearch::RowData> PaletteColorSearch::search(int colorId, const Tileset *tileset, const Tileset *pairedTileset) const {
QList<RowData> results;
QList<uint16_t> metatileIds = tileset->findMetatilesUsingColor(currentPaletteId(), colorId, pairedTileset);
auto primaryTileset = tileset->is_secondary ? pairedTileset : tileset;
auto secondaryTileset = tileset->is_secondary ? tileset : pairedTileset;
for (const auto &metatileId : metatileIds) {
QImage metatileImage = getMetatileImage(metatileId, primaryTileset, secondaryTileset);
RowData rowData = {
.tilesetName = tileset->name,
.pairedTilesetName = pairedTileset->name,
.metatileId = Metatile::getMetatileIdString(metatileId),
.metatileIcon = QIcon(QPixmap::fromImage(metatileImage)),
};
results.append(rowData);
}
return results;
}
void PaletteColorSearch::refresh() {
m_resultsCache.clear();
updateResults();
}
void PaletteColorSearch::updateResults() {
const Tileset *tileset = currentTileset();
int paletteId = currentPaletteId();
int colorId = currentColorId();
// Update color icon
QRgb color = tileset->palettePreviews.value(paletteId).value(colorId);
ui->frame_Color->setStyleSheet(QString("background-color: rgb(%1, %2, %3);").arg(qRed(color)).arg(qGreen(color)).arg(qBlue(color)));
// Update title
ui->label_Title->setText(QString("Searching for usage of %1's palette %2.").arg(tileset->name).arg(paletteId));
// Update table
ui->table_Results->clearContents();
ui->table_Results->setRowCount(0);
QString cacheKey = QString("%1#%2").arg(paletteId).arg(colorId);
auto it = m_resultsCache.constFind(cacheKey);
bool inCache = (it != m_resultsCache.constEnd());
const QList<RowData> results = inCache ? it.value() : search(colorId);
if (results.isEmpty()) {
static const RowData noResults = {
.tilesetName = QStringLiteral("This color is unused."),
.pairedTilesetName = "",
.metatileId = QStringLiteral("--"),
.metatileIcon = QIcon(),
};
addTableEntry(noResults);
} else {
for (const auto &entry : results) {
addTableEntry(entry);
}
}
ui->table_Results->sortByColumn(ResultsColumn::TilesetName, Qt::AscendingOrder);
if (!inCache) m_resultsCache.insert(cacheKey, results);
}
// Double-clicking row data selects the corresponding metatile in the Tileset Editor.
void PaletteColorSearch::cellDoubleClicked(int row, int) {
auto tilesetNameItem = ui->table_Results->item(row, ResultsColumn::TilesetName);
auto metatileItem = ui->table_Results->item(row, ResultsColumn::Metatile);
if (!tilesetNameItem || !metatileItem)
return;
// The Tileset Editor (as of writing) has no way to change the selected tilesets independently of
// the main editor's layout, so if the metatile is not in the current tileset we do nothing.
// To compare the tileset names, rather than sort out which was the primary or secondary we
// just make sure it's the same set of names.
QSet<QString> currentTilesets;
currentTilesets.insert(m_primaryTileset->name);
currentTilesets.insert(m_secondaryTileset->name);
QSet<QString> metatileTilesets;
metatileTilesets.insert(tilesetNameItem->text());
metatileTilesets.insert(tilesetNameItem->data(ResultsDataRole::PairedTilesetName).toString());
if (currentTilesets != metatileTilesets)
return;
bool ok;
uint16_t metatileId = metatileItem->text().toUInt(&ok, 0);
if (ok) emit metatileSelected(metatileId);
}

View File

@ -6,20 +6,19 @@
#include "filedialog.h"
#include "message.h"
#include "eventfilters.h"
#include "utility.h"
PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset *secondaryTileset, int paletteId, QWidget *parent) :
QMainWindow(parent),
ui(new Ui::PaletteEditor)
ui(new Ui::PaletteEditor),
project(project),
primaryTileset(primaryTileset),
secondaryTileset(secondaryTileset)
{
this->project = project;
this->primaryTileset = primaryTileset;
this->secondaryTileset = secondaryTileset;
setAttribute(Qt::WA_DeleteOnClose);
this->ui->setupUi(this);
this->ui->spinBox_PaletteId->setMinimum(0);
this->ui->spinBox_PaletteId->setMaximum(Project::getNumPalettesTotal() - 1);
this->colorInputs.clear();
const int numColorsPerRow = 4;
for (int i = 0; i < this->numColors; i++) {
auto colorInput = new ColorInputWidget;
@ -43,43 +42,48 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset
setBitDepth(bitDepth);
// Connect bit depth buttons
connect(this->ui->bit_depth_15, &QRadioButton::toggled, [this](bool checked){ if (checked) this->setBitDepth(15); });
connect(this->ui->bit_depth_24, &QRadioButton::toggled, [this](bool checked){ if (checked) this->setBitDepth(24); });
connect(this->ui->bit_depth_15, &QRadioButton::toggled, [this](bool checked){ if (checked) setBitDepth(15); });
connect(this->ui->bit_depth_24, &QRadioButton::toggled, [this](bool checked){ if (checked) setBitDepth(24); });
this->ui->actionShow_Unused_Colors->setChecked(porymapConfig.showPaletteEditorUnusedColors);
connect(this->ui->actionShow_Unused_Colors, &QAction::toggled, this, &PaletteEditor::setColorInputTitles);
connect(this->ui->toolButton_ColorSearch, &QToolButton::clicked, this, &PaletteEditor::openColorSearch);
connect(this->ui->actionFind_Color_Usage, &QAction::triggered, this, &PaletteEditor::openColorSearch);
// Rather than try to keep track of metatile/tile changes that affect which colors are used,
// we'll just refresh when the window is activated.
ActiveWindowFilter *filter = new ActiveWindowFilter(this);
connect(filter, &ActiveWindowFilter::activated, this, &PaletteEditor::onWindowActivated);
connect(filter, &ActiveWindowFilter::activated, this, &PaletteEditor::invalidateCache);
this->installEventFilter(filter);
this->setPaletteId(paletteId);
this->commitEditHistory();
this->restoreWindowState();
this->ui->spinBox_PaletteId->setRange(0, Project::getNumPalettesTotal() - 1);
this->ui->spinBox_PaletteId->setValue(paletteId);
connect(this->ui->spinBox_PaletteId, QOverload<int>::of(&QSpinBox::valueChanged), this, &PaletteEditor::refreshPaletteId);
connect(this->ui->spinBox_PaletteId, QOverload<int>::of(&QSpinBox::valueChanged), this, &PaletteEditor::changedPalette);
refreshPaletteId();
restoreWindowState();
}
PaletteEditor::~PaletteEditor() {
delete ui;
}
void PaletteEditor::onWindowActivated() {
// Rather than try to keep track of metatile/tile changes that affect which colors are used,
// we'll just refresh when the window is activated.
invalidateCache();
}
int PaletteEditor::currentPaletteId() const {
return ui->spinBox_PaletteId->value();
}
void PaletteEditor::setPaletteId(int paletteId) {
ui->spinBox_PaletteId->setValue(paletteId);
}
bool PaletteEditor::showingUnusedColors() const {
return ui->actionShow_Unused_Colors->isChecked();
}
Tileset* PaletteEditor::getTileset(int paletteId) const {
return (paletteId < Project::getNumPalettesPrimary())
? this->primaryTileset
: this->secondaryTileset;
return Tileset::getPaletteTileset(paletteId, this->primaryTileset, this->secondaryTileset);
}
void PaletteEditor::setBitDepth(int bits) {
@ -95,7 +99,6 @@ void PaletteEditor::setRgb(int colorIndex, QRgb rgb) {
Tileset *tileset = getTileset(paletteId);
tileset->palettes[paletteId][colorIndex] = rgb;
tileset->palettePreviews[paletteId][colorIndex] = rgb;
emit changedPaletteColor();
}
@ -120,25 +123,26 @@ void PaletteEditor::refreshColorInputs() {
setColorInputTitles(showingUnusedColors());
}
void PaletteEditor::setPaletteId(int paletteId) {
const QSignalBlocker b(ui->spinBox_PaletteId);
this->ui->spinBox_PaletteId->setValue(paletteId);
this->refreshColorInputs();
void PaletteEditor::refreshPaletteId() {
refreshColorInputs();
int paletteId = currentPaletteId();
if (!this->palettesHistory[paletteId].current()) {
commitEditHistory(paletteId);
}
if (this->colorSearchWindow) {
this->colorSearchWindow->setPaletteId(paletteId);
}
}
void PaletteEditor::setTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) {
this->primaryTileset = primaryTileset;
this->secondaryTileset = secondaryTileset;
this->invalidateCache();
this->refreshColorInputs();
}
void PaletteEditor::on_spinBox_PaletteId_valueChanged(int paletteId) {
this->refreshColorInputs();
if (!this->palettesHistory[paletteId].current()) {
this->commitEditHistory(paletteId);
invalidateCache();
if (this->colorSearchWindow) {
this->colorSearchWindow->setTilesets(primaryTileset, secondaryTileset);
}
emit this->changedPalette(paletteId);
refreshColorInputs();
}
void PaletteEditor::commitEditHistory() {
@ -157,8 +161,8 @@ void PaletteEditor::commitEditHistory(int paletteId) {
void PaletteEditor::restoreWindowState() {
logInfo("Restoring palette editor geometry from previous session.");
QMap<QString, QByteArray> geometry = porymapConfig.getPaletteEditorGeometry();
this->restoreGeometry(geometry.value("palette_editor_geometry"));
this->restoreState(geometry.value("palette_editor_state"));
restoreGeometry(geometry.value("palette_editor_geometry"));
restoreState(geometry.value("palette_editor_state"));
}
void PaletteEditor::on_actionUndo_triggered() {
@ -195,6 +199,16 @@ void PaletteEditor::on_actionImport_Palette_triggered() {
commitEditHistory(paletteId);
}
void PaletteEditor::openColorSearch() {
if (!this->colorSearchWindow) {
this->colorSearchWindow = new PaletteColorSearch(this->project, this->primaryTileset, this->secondaryTileset, this);
this->colorSearchWindow->setPaletteId(currentPaletteId());
connect(this->colorSearchWindow, &PaletteColorSearch::metatileSelected, this, &PaletteEditor::metatileSelected);
connect(this->colorSearchWindow, &PaletteColorSearch::paletteIdChanged, this, &PaletteEditor::setPaletteId);
}
Util::show(this->colorSearchWindow);
}
void PaletteEditor::invalidateCache() {
this->unusedColorCache.clear();
if (showingUnusedColors()) {
@ -202,7 +216,7 @@ void PaletteEditor::invalidateCache() {
}
}
QSet<int> PaletteEditor::getUnusedColorIds() const {
QSet<int> PaletteEditor::getUnusedColorIds() {
const int paletteId = currentPaletteId();
if (this->unusedColorCache.contains(paletteId)) {
@ -256,7 +270,7 @@ void PaletteEditor::setColorInputTitles(bool showUnused) {
void PaletteEditor::closeEvent(QCloseEvent*) {
porymapConfig.setPaletteEditorGeometry(
this->saveGeometry(),
this->saveState()
saveGeometry(),
saveState()
);
}

View File

@ -26,14 +26,23 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
setAttribute(Qt::WA_DeleteOnClose);
ui->setupUi(this);
ui->spinBox_paletteSelector->setRange(0, Project::getNumPalettesTotal() - 1);
auto validator = new IdentifierValidator(this);
validator->setAllowEmpty(true);
ui->lineEdit_metatileLabel->setValidator(validator);
ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider);
ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes);
ActiveWindowFilter *filter = new ActiveWindowFilter(this);
connect(filter, &ActiveWindowFilter::activated, this, &TilesetEditor::onWindowActivated);
this->installEventFilter(filter);
setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label);
connect(ui->checkBox_xFlip, &QCheckBox::toggled, this, &TilesetEditor::setXFlip);
connect(ui->checkBox_yFlip, &QCheckBox::toggled, this, &TilesetEditor::setYFlip);
this->tileXFlip = ui->checkBox_xFlip->isChecked();
this->tileYFlip = ui->checkBox_yFlip->isChecked();
this->paletteId = ui->spinBox_paletteSelector->value();
connect(ui->checkBox_xFlip, &QCheckBox::toggled, this, &TilesetEditor::refreshTileFlips);
connect(ui->checkBox_yFlip, &QCheckBox::toggled, this, &TilesetEditor::refreshTileFlips);
connect(ui->actionSave_Tileset, &QAction::triggered, this, &TilesetEditor::save);
@ -51,19 +60,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
connect(ui->actionExport_Metatiles_Image, &QAction::triggered, [this] { exportMetatilesImage(); });
ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider);
ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes);
ui->spinBox_paletteSelector->setMinimum(0);
ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1);
auto validator = new IdentifierValidator(this);
validator->setAllowEmpty(true);
ui->lineEdit_metatileLabel->setValidator(validator);
ActiveWindowFilter *filter = new ActiveWindowFilter(this);
connect(filter, &ActiveWindowFilter::activated, this, &TilesetEditor::onWindowActivated);
this->installEventFilter(filter);
connect(ui->spinBox_paletteSelector, QOverload<int>::of(&QSpinBox::valueChanged), this, &TilesetEditor::refreshPaletteId);
initAttributesUi();
initMetatileSelector();
@ -131,11 +128,11 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi
this->metatileReloadQueue.clear();
Tileset *primaryTileset = project->getTileset(primaryTilesetLabel);
Tileset *secondaryTileset = project->getTileset(secondaryTilesetLabel);
if (this->primaryTileset) delete this->primaryTileset;
if (this->secondaryTileset) delete this->secondaryTileset;
delete this->primaryTileset;
delete this->secondaryTileset;
this->primaryTileset = new Tileset(*primaryTileset);
this->secondaryTileset = new Tileset(*secondaryTileset);
if (paletteEditor) paletteEditor->setTilesets(this->primaryTileset, this->secondaryTileset);
if (this->paletteEditor) this->paletteEditor->setTilesets(this->primaryTileset, this->secondaryTileset);
this->initMetatileHistory();
}
@ -265,10 +262,8 @@ void TilesetEditor::initTileSelector()
connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileChanged, [this](uint16_t tileId) {
onHoveredTileChanged(tileId);
});
connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileCleared,
this, &TilesetEditor::onHoveredTileCleared);
connect(this->tileSelector, &TilesetEditorTileSelector::selectedTilesChanged,
this, &TilesetEditor::onSelectedTilesChanged);
connect(this->tileSelector, &TilesetEditorTileSelector::hoveredTileCleared, this, &TilesetEditor::onHoveredTileCleared);
connect(this->tileSelector, &TilesetEditorTileSelector::selectedTilesChanged, this, &TilesetEditor::drawSelectedTiles);
this->tileSelector->showDivider = this->ui->actionShow_Tileset_Divider->isChecked();
@ -475,10 +470,6 @@ void TilesetEditor::onHoveredTileCleared() {
this->ui->statusbar->clearMessage();
}
void TilesetEditor::onSelectedTilesChanged() {
this->drawSelectedTiles();
}
void TilesetEditor::onMetatileLayerTileChanged(int x, int y) {
static const QList<QPoint> tileCoords = QList<QPoint>{
QPoint(0, 0),
@ -543,39 +534,32 @@ void TilesetEditor::onMetatileLayerSelectionChanged(QPoint selectionOrigin, int
this->tileSelector->setExternalSelection(width, height, tiles, tileIdxs);
if (width == 1 && height == 1) {
ui->spinBox_paletteSelector->setValue(tiles[0].palette);
setPaletteId(tiles[0].palette);
this->tileSelector->highlight(static_cast<uint16_t>(tiles[0].tileId));
this->redrawTileSelector();
}
this->metatileLayersItem->clearLastModifiedCoords();
}
void TilesetEditor::on_spinBox_paletteSelector_valueChanged(int paletteId)
{
this->ui->spinBox_paletteSelector->blockSignals(true);
this->ui->spinBox_paletteSelector->setValue(paletteId);
this->ui->spinBox_paletteSelector->blockSignals(false);
this->paletteId = paletteId;
this->tileSelector->setPaletteId(paletteId);
void TilesetEditor::setPaletteId(int paletteId) {
ui->spinBox_paletteSelector->setValue(paletteId);
}
int TilesetEditor::paletteId() const {
return ui->spinBox_paletteSelector->value();
}
void TilesetEditor::refreshPaletteId() {
this->tileSelector->setPaletteId(paletteId());
this->drawSelectedTiles();
if (this->paletteEditor) {
this->paletteEditor->setPaletteId(paletteId);
this->paletteEditor->setPaletteId(paletteId());
}
this->metatileLayersItem->clearLastModifiedCoords();
}
void TilesetEditor::setXFlip(bool enabled)
{
this->tileXFlip = enabled;
this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip);
this->drawSelectedTiles();
this->metatileLayersItem->clearLastModifiedCoords();
}
void TilesetEditor::setYFlip(bool enabled)
{
this->tileYFlip = enabled;
this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip);
void TilesetEditor::refreshTileFlips() {
this->tileSelector->setTileFlips(ui->checkBox_xFlip->isChecked(), ui->checkBox_yFlip->isChecked());
this->drawSelectedTiles();
this->metatileLayersItem->clearLastModifiedCoords();
}
@ -872,21 +856,12 @@ void TilesetEditor::on_actionChange_Palettes_triggered()
{
if (!this->paletteEditor) {
this->paletteEditor = new PaletteEditor(this->project, this->primaryTileset,
this->secondaryTileset, this->paletteId, this);
connect(this->paletteEditor, &PaletteEditor::changedPaletteColor,
this, &TilesetEditor::onPaletteEditorChangedPaletteColor);
connect(this->paletteEditor, &PaletteEditor::changedPalette,
this, &TilesetEditor::onPaletteEditorChangedPalette);
}
if (!this->paletteEditor->isVisible()) {
this->paletteEditor->show();
} else if (this->paletteEditor->isMinimized()) {
this->paletteEditor->showNormal();
} else {
this->paletteEditor->raise();
this->paletteEditor->activateWindow();
this->secondaryTileset, this->paletteId(), this);
connect(this->paletteEditor, &PaletteEditor::changedPaletteColor, this, &TilesetEditor::onPaletteEditorChangedPaletteColor);
connect(this->paletteEditor, &PaletteEditor::changedPalette, this, &TilesetEditor::setPaletteId);
connect(this->paletteEditor, &PaletteEditor::metatileSelected, this, &TilesetEditor::selectMetatile);
}
Util::show(this->paletteEditor);
}
void TilesetEditor::onPaletteEditorChangedPaletteColor() {
@ -894,10 +869,6 @@ void TilesetEditor::onPaletteEditorChangedPaletteColor() {
this->hasUnsavedChanges = true;
}
void TilesetEditor::onPaletteEditorChangedPalette(int paletteId) {
this->on_spinBox_paletteSelector_valueChanged(paletteId);
}
bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile * src, QString newLabel)
{
Metatile * dest = Tileset::getMetatile(metatileId, this->primaryTileset, this->secondaryTileset);
@ -996,7 +967,7 @@ void TilesetEditor::pasteMetatile(const Metatile * toPaste, QString newLabel)
void TilesetEditor::exportTilesImage(Tileset *tileset) {
bool primary = !tileset->is_secondary;
QString defaultFilepath = QString("%1/%2_Tiles_Pal%3.png").arg(FileDialog::getDirectory()).arg(tileset->name).arg(this->paletteId);
QString defaultFilepath = QString("%1/%2_Tiles_Pal%3.png").arg(FileDialog::getDirectory()).arg(tileset->name).arg(this->paletteId());
QString filepath = FileDialog::getSaveFileName(this, QString("Export %1 Tiles Image").arg(primary ? "Primary" : "Secondary"), defaultFilepath, "Image Files (*.png)");
if (!filepath.isEmpty()) {
QImage image = primary ? this->tileSelector->buildPrimaryTilesIndexedImage() : this->tileSelector->buildSecondaryTilesIndexedImage();

View File

@ -122,7 +122,10 @@ void WildMonSearch::updateResults(const QString &species) {
if (ui->comboBox_Search->findText(species) < 0)
return; // Not a species name, no need to search wild encounter data.
const QList<RowData> results = this->resultsCache.value(species, search(species));
auto it = this->resultsCache.constFind(species);
bool inCache = (it != this->resultsCache.constEnd());
const QList<RowData> results = inCache ? it.value() : search(species);
if (results.isEmpty()) {
static const RowData noResults = {
.mapName = "",
@ -140,7 +143,7 @@ void WildMonSearch::updateResults(const QString &species) {
ui->table_Results->setSortingEnabled(true);
this->resultsCache.insert(species, results);
if (!inCache) this->resultsCache.insert(species, results);
}
// Double-clicking row data opens the corresponding map/table on the Wild Pokémon tab.