mirror of
https://github.com/huderlem/porymap.git
synced 2026-03-21 17:45:44 -05:00
Merge pull request #787 from GriffinRichards/config-json
Replace old config format
This commit is contained in:
commit
8b4ccc12ef
|
|
@ -2289,7 +2289,7 @@ All constants are accessible via the global ``constants`` object.
|
|||
|
||||
.. js:attribute:: constants.base_game_version
|
||||
|
||||
The string value of the config setting ``base_game_version``. This will either be ``pokeruby``, ``pokefirered``, or ``pokeemerald``.
|
||||
The string value of the config setting ``base_game_version``. This will either be ``pokeruby``, ``pokefirered``, ``pokeemerald``, or an empty string.
|
||||
|
||||
.. js:attribute:: constants.version.major
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>264</width>
|
||||
<height>173</height>
|
||||
<width>387</width>
|
||||
<height>357</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
|
@ -36,8 +36,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>240</width>
|
||||
<height>109</height>
|
||||
<width>363</width>
|
||||
<height>293</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
|
|
|
|||
|
|
@ -2,6 +2,14 @@
|
|||
<ui version="4.0">
|
||||
<class>NewLayoutForm</class>
|
||||
<widget class="QWidget" name="NewLayoutForm">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>196</width>
|
||||
<height>331</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
|
|
@ -168,6 +176,9 @@
|
|||
<property name="insertPolicy">
|
||||
<enum>QComboBox::InsertPolicy::NoInsert</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
|
|
@ -204,6 +215,9 @@
|
|||
<property name="insertPolicy">
|
||||
<enum>QComboBox::InsertPolicy::NoInsert</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>559</width>
|
||||
<height>614</height>
|
||||
<width>467</width>
|
||||
<height>428</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
|
@ -39,8 +39,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>535</width>
|
||||
<height>550</height>
|
||||
<width>443</width>
|
||||
<height>364</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
|
|
@ -129,6 +129,9 @@
|
|||
<property name="insertPolicy">
|
||||
<enum>QComboBox::InsertPolicy::NoInsert</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
|
|
@ -171,6 +174,9 @@
|
|||
<property name="insertPolicy">
|
||||
<enum>QComboBox::InsertPolicy::NoInsert</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
|
|
|
|||
521
include/config.h
521
include/config.h
|
|
@ -1,521 +0,0 @@
|
|||
#pragma once
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
#include <QByteArrayList>
|
||||
#include <QSize>
|
||||
#include <QKeySequence>
|
||||
#include <QMultiMap>
|
||||
#include <QDateTime>
|
||||
#include <QUrl>
|
||||
#include <QVersionNumber>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QFontDatabase>
|
||||
#include <QStandardPaths>
|
||||
#include <QColorSpace>
|
||||
#include <set>
|
||||
|
||||
#include "events.h"
|
||||
#include "gridsettings.h"
|
||||
|
||||
extern const QVersionNumber porymapVersion;
|
||||
|
||||
// Distance in pixels from the edge of a GBA screen (240x160) to the center 16x16 pixels.
|
||||
#define GBA_H_DIST_TO_CENTER ((240-16)/2)
|
||||
#define GBA_V_DIST_TO_CENTER ((160-16)/2)
|
||||
|
||||
#define CONFIG_BACKWARDS_COMPATABILITY
|
||||
|
||||
enum ScriptAutocompleteMode {
|
||||
MapOnly,
|
||||
MapAndCommon,
|
||||
All,
|
||||
};
|
||||
|
||||
class KeyValueConfigBase
|
||||
{
|
||||
public:
|
||||
bool save();
|
||||
bool load(const QString &dir = QString());
|
||||
|
||||
void setRoot(const QString &dir);
|
||||
QString root() const { return m_root; }
|
||||
QString filepath() const { return m_filepath; }
|
||||
QString filename() const { return m_filename; }
|
||||
|
||||
explicit KeyValueConfigBase(const QString &filename)
|
||||
: m_root(QString()),
|
||||
m_filename(filename),
|
||||
m_filepath(filename)
|
||||
{ };
|
||||
virtual ~KeyValueConfigBase() {};
|
||||
virtual void reset() = 0;
|
||||
protected:
|
||||
virtual void parseConfigKeyValue(QString key, QString value) = 0;
|
||||
virtual QMap<QString, QString> getKeyValueMap() = 0;
|
||||
virtual void init() = 0;
|
||||
virtual void setUnreadKeys() = 0;
|
||||
|
||||
static bool getConfigBool(const QString &key, const QString &value);
|
||||
static int getConfigInteger(const QString &key, const QString &value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0);
|
||||
static uint32_t getConfigUint32(const QString &key, const QString &value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0);
|
||||
static QColor getConfigColor(const QString &key, const QString &value, const QColor &defaultValue = QColor(Qt::black));
|
||||
static QString toConfigColor(const QColor &color);
|
||||
|
||||
QString m_root;
|
||||
QString m_filename;
|
||||
QString m_filepath;
|
||||
};
|
||||
|
||||
class PorymapConfig: public KeyValueConfigBase
|
||||
{
|
||||
public:
|
||||
PorymapConfig();
|
||||
virtual void reset() override;
|
||||
void addRecentProject(QString project);
|
||||
void setRecentProjects(QStringList projects);
|
||||
QString getRecentProject();
|
||||
QStringList getRecentProjects();
|
||||
void setMainGeometry(QByteArray, QByteArray, QByteArray, QByteArray, QByteArray);
|
||||
void setTilesetEditorGeometry(QByteArray, QByteArray, QByteArray);
|
||||
void setPaletteEditorGeometry(QByteArray, QByteArray);
|
||||
void setRegionMapEditorGeometry(QByteArray, QByteArray);
|
||||
void setProjectSettingsEditorGeometry(QByteArray, QByteArray);
|
||||
void setCustomScriptsEditorGeometry(QByteArray, QByteArray);
|
||||
QMap<QString, QByteArray> getMainGeometry();
|
||||
QMap<QString, QByteArray> getTilesetEditorGeometry();
|
||||
QMap<QString, QByteArray> getPaletteEditorGeometry();
|
||||
QMap<QString, QByteArray> getRegionMapEditorGeometry();
|
||||
QMap<QString, QByteArray> getProjectSettingsEditorGeometry();
|
||||
QMap<QString, QByteArray> getCustomScriptsEditorGeometry();
|
||||
|
||||
static QFont defaultMapListFont() { return QFontDatabase::systemFont(QFontDatabase::FixedFont); }
|
||||
|
||||
bool reopenOnLaunch;
|
||||
bool projectManuallyClosed;
|
||||
int mapListTab;
|
||||
bool mapListEditGroupsEnabled;
|
||||
QMap<int, bool> mapListHideEmptyEnabled;
|
||||
bool mapListLayoutsSorted;
|
||||
bool mapListLocationsSorted;
|
||||
bool prettyCursors;
|
||||
bool mirrorConnectingMaps;
|
||||
bool showDiveEmergeMaps;
|
||||
int diveEmergeMapOpacity;
|
||||
int diveMapOpacity;
|
||||
int emergeMapOpacity;
|
||||
int collisionOpacity;
|
||||
int collisionZoom;
|
||||
int metatilesZoom;
|
||||
int tilesetEditorMetatilesZoom;
|
||||
int tilesetEditorTilesZoom;
|
||||
Qt::Orientation tilesetEditorLayerOrientation;
|
||||
bool showPlayerView;
|
||||
bool showCursorTile;
|
||||
bool showBorder;
|
||||
bool showGrid;
|
||||
bool showTilesetEditorMetatileGrid;
|
||||
bool showTilesetEditorLayerGrid;
|
||||
bool showTilesetEditorDivider;
|
||||
bool showTilesetEditorRawAttributes;
|
||||
bool showPaletteEditorUnusedColors;
|
||||
bool monitorFiles;
|
||||
bool tilesetCheckerboardFill;
|
||||
bool newMapHeaderSectionExpanded;
|
||||
QString theme;
|
||||
QString wildMonChartTheme;
|
||||
QString textEditorOpenFolder;
|
||||
QString textEditorGotoLine;
|
||||
int paletteEditorBitDepth;
|
||||
int projectSettingsTab;
|
||||
ScriptAutocompleteMode scriptAutocompleteMode;
|
||||
bool warpBehaviorWarningDisabled;
|
||||
bool eventDeleteWarningDisabled;
|
||||
bool eventOverlayEnabled;
|
||||
bool checkForUpdates;
|
||||
bool showProjectLoadingScreen;
|
||||
QDateTime lastUpdateCheckTime;
|
||||
QVersionNumber lastUpdateCheckVersion;
|
||||
QMap<QUrl, QDateTime> rateLimitTimes;
|
||||
QGraphicsPixmapItem::ShapeMode eventSelectionShapeMode;
|
||||
QByteArray wildMonChartGeometry;
|
||||
QByteArray newMapDialogGeometry;
|
||||
QByteArray newLayoutDialogGeometry;
|
||||
bool shownInGameReloadMessage;
|
||||
GridSettings gridSettings;
|
||||
// Prefer over QSet to prevent shuffling elements when writing the config file.
|
||||
std::set<LogType> statusBarLogTypes;
|
||||
QFont applicationFont;
|
||||
QFont mapListFont;
|
||||
int imageExportColorSpaceId;
|
||||
QMap<QString,QString> trustedScriptHashes;
|
||||
|
||||
protected:
|
||||
virtual void parseConfigKeyValue(QString key, QString value) override;
|
||||
virtual QMap<QString, QString> getKeyValueMap() override;
|
||||
virtual void init() override {};
|
||||
virtual void setUnreadKeys() override {};
|
||||
|
||||
private:
|
||||
QString stringFromByteArray(const QByteArray&);
|
||||
QByteArray bytesFromString(const QString&);
|
||||
|
||||
QStringList recentProjects;
|
||||
QByteArray mainWindowGeometry;
|
||||
QByteArray mainWindowState;
|
||||
QByteArray mapSplitterState;
|
||||
QByteArray mainSplitterState;
|
||||
QByteArray metatilesSplitterState;
|
||||
QByteArray tilesetEditorGeometry;
|
||||
QByteArray tilesetEditorState;
|
||||
QByteArray tilesetEditorSplitterState;
|
||||
QByteArray paletteEditorGeometry;
|
||||
QByteArray paletteEditorState;
|
||||
QByteArray regionMapEditorGeometry;
|
||||
QByteArray regionMapEditorState;
|
||||
QByteArray projectSettingsEditorGeometry;
|
||||
QByteArray projectSettingsEditorState;
|
||||
QByteArray customScriptsEditorGeometry;
|
||||
QByteArray customScriptsEditorState;
|
||||
};
|
||||
|
||||
extern PorymapConfig porymapConfig;
|
||||
|
||||
enum BaseGameVersion {
|
||||
none,
|
||||
pokeruby,
|
||||
pokefirered,
|
||||
pokeemerald,
|
||||
};
|
||||
|
||||
enum ProjectIdentifier {
|
||||
symbol_facing_directions,
|
||||
symbol_obj_event_gfx_pointers,
|
||||
symbol_pokemon_icon_table,
|
||||
symbol_attribute_table,
|
||||
symbol_tilesets_prefix,
|
||||
symbol_dynamic_map_name,
|
||||
define_obj_event_count,
|
||||
define_min_level,
|
||||
define_max_level,
|
||||
define_max_encounter_rate,
|
||||
define_tiles_primary,
|
||||
define_tiles_total,
|
||||
define_metatiles_primary,
|
||||
define_pals_primary,
|
||||
define_pals_total,
|
||||
define_tiles_per_metatile,
|
||||
define_map_size,
|
||||
define_map_offset_width,
|
||||
define_map_offset_height,
|
||||
define_mask_metatile,
|
||||
define_mask_collision,
|
||||
define_mask_elevation,
|
||||
define_mask_behavior,
|
||||
define_mask_layer,
|
||||
define_attribute_behavior,
|
||||
define_attribute_layer,
|
||||
define_attribute_terrain,
|
||||
define_attribute_encounter,
|
||||
define_metatile_label_prefix,
|
||||
define_heal_locations_prefix,
|
||||
define_layout_prefix,
|
||||
define_map_prefix,
|
||||
define_map_dynamic,
|
||||
define_map_empty,
|
||||
define_map_section_prefix,
|
||||
define_map_section_empty,
|
||||
define_species_prefix,
|
||||
define_species_empty,
|
||||
regex_behaviors,
|
||||
regex_obj_event_gfx,
|
||||
regex_items,
|
||||
regex_flags,
|
||||
regex_vars,
|
||||
regex_movement_types,
|
||||
regex_map_types,
|
||||
regex_battle_scenes,
|
||||
regex_weather,
|
||||
regex_coord_event_weather,
|
||||
regex_secret_bases,
|
||||
regex_sign_facing_directions,
|
||||
regex_trainer_types,
|
||||
regex_music,
|
||||
regex_encounter_types,
|
||||
regex_terrain_types,
|
||||
pals_output_extension,
|
||||
tiles_output_extension,
|
||||
};
|
||||
|
||||
enum ProjectFilePath {
|
||||
data_map_folders,
|
||||
data_scripts_folders,
|
||||
data_layouts_folders,
|
||||
data_primary_tilesets_folders,
|
||||
data_secondary_tilesets_folders,
|
||||
data_event_scripts,
|
||||
json_map_groups,
|
||||
json_layouts,
|
||||
json_wild_encounters,
|
||||
json_heal_locations,
|
||||
json_region_map_entries,
|
||||
json_region_porymap_cfg,
|
||||
tilesets_headers,
|
||||
tilesets_graphics,
|
||||
tilesets_metatiles,
|
||||
tilesets_headers_asm,
|
||||
tilesets_graphics_asm,
|
||||
tilesets_metatiles_asm,
|
||||
data_obj_event_gfx_pointers,
|
||||
data_obj_event_gfx_info,
|
||||
data_obj_event_pic_tables,
|
||||
data_obj_event_gfx,
|
||||
data_pokemon_gfx,
|
||||
constants_global,
|
||||
constants_items,
|
||||
constants_flags,
|
||||
constants_vars,
|
||||
constants_weather,
|
||||
constants_songs,
|
||||
constants_pokemon,
|
||||
constants_map_types,
|
||||
constants_trainer_types,
|
||||
constants_secret_bases,
|
||||
constants_obj_event_movement,
|
||||
constants_obj_events,
|
||||
constants_event_bg,
|
||||
constants_metatile_labels,
|
||||
constants_metatile_behaviors,
|
||||
constants_species,
|
||||
constants_fieldmap,
|
||||
global_fieldmap,
|
||||
fieldmap,
|
||||
initial_facing_table,
|
||||
wild_encounter,
|
||||
pokemon_icon_table,
|
||||
pokemon_gfx,
|
||||
};
|
||||
|
||||
class ProjectConfig: public KeyValueConfigBase
|
||||
{
|
||||
public:
|
||||
ProjectConfig();
|
||||
virtual void reset() override {
|
||||
this->baseGameVersion = BaseGameVersion::pokeemerald;
|
||||
// Reset non-version-specific settings
|
||||
this->usePoryScript = false;
|
||||
this->tripleLayerMetatilesEnabled = false;
|
||||
this->defaultMetatileId = 1;
|
||||
this->defaultElevation = 3;
|
||||
this->defaultCollision = 0;
|
||||
this->defaultMapSize = QSize(20,20);
|
||||
this->defaultPrimaryTileset = "gTileset_General";
|
||||
this->prefabFilepath = QString();
|
||||
this->prefabImportPrompted = false;
|
||||
this->tilesetsHaveCallback = true;
|
||||
this->tilesetsHaveIsCompressed = true;
|
||||
this->transparencyColor = QColor(Qt::black);
|
||||
this->preserveMatchingOnlyData = false;
|
||||
this->filePaths.clear();
|
||||
this->eventIconPaths.clear();
|
||||
this->pokemonIconPaths.clear();
|
||||
this->eventsTabIconPath = QString();
|
||||
this->collisionSheetPath = QString();
|
||||
this->collisionSheetSize = QSize(2, 16);
|
||||
this->playerViewDistance = QMargins(GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER, GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER);
|
||||
this->blockMetatileIdMask = 0x03FF;
|
||||
this->blockCollisionMask = 0x0C00;
|
||||
this->blockElevationMask = 0xF000;
|
||||
this->unusedTileNormal = 0x3014;
|
||||
this->unusedTileCovered = 0x0000;
|
||||
this->unusedTileSplit = 0x0000;
|
||||
this->maxEventsPerGroup = 255;
|
||||
this->forcedMajorVersion = 0;
|
||||
this->metatileSelectorWidth = 8;
|
||||
this->globalConstantsFilepaths.clear();
|
||||
this->globalConstants.clear();
|
||||
this->identifiers.clear();
|
||||
this->readKeys.clear();
|
||||
}
|
||||
static const QMap<ProjectIdentifier, QPair<QString, QString>> defaultIdentifiers;
|
||||
static const QMap<ProjectFilePath, QPair<QString, QString>> defaultPaths;
|
||||
static const QStringList versionStrings;
|
||||
static BaseGameVersion stringToBaseGameVersion(const QString &string);
|
||||
|
||||
static QString getPlayerIconPath(BaseGameVersion baseGameVersion, int character);
|
||||
static QIcon getPlayerIcon(BaseGameVersion baseGameVersion, int character);
|
||||
|
||||
QString projectDir() const { return m_root; } // Alias for root()
|
||||
void reset(BaseGameVersion baseGameVersion);
|
||||
void setFilePath(ProjectFilePath pathId, const QString &path);
|
||||
void setFilePath(const QString &pathId, const QString &path);
|
||||
QString getCustomFilePath(ProjectFilePath pathId);
|
||||
QString getCustomFilePath(const QString &pathId);
|
||||
QString getFilePath(ProjectFilePath pathId);
|
||||
void setIdentifier(ProjectIdentifier id, QString text);
|
||||
void setIdentifier(const QString &id, const QString &text);
|
||||
QString getCustomIdentifier(ProjectIdentifier id);
|
||||
QString getCustomIdentifier(const QString &id);
|
||||
QString getIdentifier(ProjectIdentifier id);
|
||||
QString getBaseGameVersionString(BaseGameVersion version);
|
||||
QString getBaseGameVersionString();
|
||||
int getNumLayersInMetatile();
|
||||
int getNumTilesInMetatile();
|
||||
void setEventIconPath(Event::Group group, const QString &path);
|
||||
QString getEventIconPath(Event::Group group);
|
||||
void setPokemonIconPath(const QString &species, const QString &path);
|
||||
QString getPokemonIconPath(const QString &species);
|
||||
QMap<QString, QString> getPokemonIconPaths();
|
||||
|
||||
BaseGameVersion baseGameVersion;
|
||||
bool usePoryScript;
|
||||
bool useCustomBorderSize;
|
||||
bool eventWeatherTriggerEnabled;
|
||||
bool eventSecretBaseEnabled;
|
||||
bool hiddenItemQuantityEnabled;
|
||||
bool hiddenItemRequiresItemfinderEnabled;
|
||||
bool healLocationRespawnDataEnabled;
|
||||
bool eventCloneObjectEnabled;
|
||||
bool floorNumberEnabled;
|
||||
bool createMapTextFileEnabled;
|
||||
bool tripleLayerMetatilesEnabled;
|
||||
uint16_t defaultMetatileId;
|
||||
uint16_t defaultElevation;
|
||||
uint16_t defaultCollision;
|
||||
QSize defaultMapSize;
|
||||
QList<uint16_t> newMapBorderMetatileIds;
|
||||
QString defaultPrimaryTileset;
|
||||
QString defaultSecondaryTileset;
|
||||
QString prefabFilepath;
|
||||
bool prefabImportPrompted;
|
||||
bool tilesetsHaveCallback;
|
||||
bool tilesetsHaveIsCompressed;
|
||||
QColor transparencyColor;
|
||||
bool preserveMatchingOnlyData;
|
||||
int metatileAttributesSize;
|
||||
uint32_t metatileBehaviorMask;
|
||||
uint32_t metatileTerrainTypeMask;
|
||||
uint32_t metatileEncounterTypeMask;
|
||||
uint32_t metatileLayerTypeMask;
|
||||
uint16_t blockMetatileIdMask;
|
||||
uint16_t blockCollisionMask;
|
||||
uint16_t blockElevationMask;
|
||||
uint16_t unusedTileNormal;
|
||||
uint16_t unusedTileCovered;
|
||||
uint16_t unusedTileSplit;
|
||||
bool mapAllowFlagsEnabled;
|
||||
QString eventsTabIconPath;
|
||||
QString collisionSheetPath;
|
||||
QSize collisionSheetSize;
|
||||
QMargins playerViewDistance;
|
||||
QList<uint32_t> warpBehaviors;
|
||||
int maxEventsPerGroup;
|
||||
int forcedMajorVersion;
|
||||
int metatileSelectorWidth;
|
||||
QStringList globalConstantsFilepaths;
|
||||
QMap<QString,QString> globalConstants;
|
||||
|
||||
protected:
|
||||
virtual void parseConfigKeyValue(QString key, QString value) override;
|
||||
virtual QMap<QString, QString> getKeyValueMap() override;
|
||||
virtual void init() override;
|
||||
virtual void setUnreadKeys() override;
|
||||
|
||||
private:
|
||||
QStringList readKeys;
|
||||
QMap<ProjectIdentifier, QString> identifiers;
|
||||
QMap<ProjectFilePath, QString> filePaths;
|
||||
QMap<Event::Group, QString> eventIconPaths;
|
||||
QMap<QString, QString> pokemonIconPaths;
|
||||
};
|
||||
|
||||
extern ProjectConfig projectConfig;
|
||||
|
||||
class UserConfig: public KeyValueConfigBase
|
||||
{
|
||||
public:
|
||||
UserConfig();
|
||||
virtual void reset() override {
|
||||
this->recentMapOrLayout = QString();
|
||||
this->useEncounterJson = true;
|
||||
this->customScripts.clear();
|
||||
this->readKeys.clear();
|
||||
}
|
||||
|
||||
QString projectDir() const { return m_root; } // Alias for root()
|
||||
void parseCustomScripts(QString input);
|
||||
QString outputCustomScripts();
|
||||
void setCustomScripts(QStringList scripts, QList<bool> enabled);
|
||||
QStringList getCustomScriptPaths();
|
||||
QList<bool> getCustomScriptsEnabled();
|
||||
|
||||
QString recentMapOrLayout;
|
||||
bool useEncounterJson;
|
||||
|
||||
protected:
|
||||
virtual void parseConfigKeyValue(QString key, QString value) override;
|
||||
virtual QMap<QString, QString> getKeyValueMap() override;
|
||||
virtual void init() override;
|
||||
virtual void setUnreadKeys() override;
|
||||
#ifdef CONFIG_BACKWARDS_COMPATABILITY
|
||||
friend class ProjectConfig;
|
||||
#endif
|
||||
|
||||
private:
|
||||
QStringList readKeys;
|
||||
QMap<QString, bool> customScripts;
|
||||
};
|
||||
|
||||
extern UserConfig userConfig;
|
||||
|
||||
class QAction;
|
||||
class Shortcut;
|
||||
|
||||
class ShortcutsConfig : public KeyValueConfigBase
|
||||
{
|
||||
public:
|
||||
ShortcutsConfig();
|
||||
|
||||
virtual void reset() override {
|
||||
setRoot(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
|
||||
user_shortcuts.clear();
|
||||
}
|
||||
|
||||
// Call this before applying user shortcuts so that the user can restore defaults.
|
||||
void setDefaultShortcuts(const QObjectList &objects);
|
||||
QList<QKeySequence> defaultShortcuts(const QObject *object) const;
|
||||
|
||||
void setUserShortcuts(const QObjectList &objects);
|
||||
void setUserShortcuts(const QMultiMap<const QObject *, QKeySequence> &objects_keySequences);
|
||||
QList<QKeySequence> userShortcuts(const QObject *object) const;
|
||||
|
||||
protected:
|
||||
virtual void parseConfigKeyValue(QString key, QString value) override;
|
||||
virtual QMap<QString, QString> getKeyValueMap() override;
|
||||
virtual void init() override { };
|
||||
virtual void setUnreadKeys() override { };
|
||||
|
||||
private:
|
||||
QMultiMap<QString, QKeySequence> user_shortcuts;
|
||||
QMultiMap<QString, QKeySequence> default_shortcuts;
|
||||
|
||||
enum StoreType {
|
||||
User,
|
||||
Default
|
||||
};
|
||||
|
||||
QString cfgKey(const QObject *object) const;
|
||||
QList<QKeySequence> currentShortcuts(const QObject *object) const;
|
||||
|
||||
void storeShortcutsFromList(StoreType storeType, const QObjectList &objects);
|
||||
void storeShortcuts(
|
||||
StoreType storeType,
|
||||
const QString &cfgKey,
|
||||
const QList<QKeySequence> &keySequences);
|
||||
};
|
||||
|
||||
extern ShortcutsConfig shortcutsConfig;
|
||||
|
||||
#endif // CONFIG_H
|
||||
5
include/config/config.h
Normal file
5
include/config/config.h
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
#include "porymapconfig.h"
|
||||
#include "projectconfig.h"
|
||||
#include "userconfig.h"
|
||||
#include "shortcutsconfig.h"
|
||||
48
include/config/keyvalueconfigbase.h
Normal file
48
include/config/keyvalueconfigbase.h
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
#ifndef KEYVALUECONFIGBASE_H
|
||||
#define KEYVALUECONFIGBASE_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
#include "fieldmanager.h"
|
||||
|
||||
class KeyValueConfigBase
|
||||
{
|
||||
public:
|
||||
explicit KeyValueConfigBase(const QString& filename)
|
||||
: m_root(QString()),
|
||||
m_filename(filename),
|
||||
m_filepath(filename)
|
||||
{ };
|
||||
virtual ~KeyValueConfigBase() {};
|
||||
|
||||
virtual bool save();
|
||||
virtual bool load();
|
||||
|
||||
virtual QJsonObject toJson();
|
||||
virtual void loadFromJson(const QJsonObject& obj);
|
||||
|
||||
void setRoot(const QString& dir);
|
||||
QString root() const { return m_root; }
|
||||
QString filepath() const { return m_filepath; }
|
||||
QString filename() const { return m_filename; }
|
||||
protected:
|
||||
virtual void initializeFromEmpty() {};
|
||||
virtual QJsonObject getDefaultJson() const { return QJsonObject(); }
|
||||
|
||||
virtual FieldManager* getFieldManager() { return nullptr; }
|
||||
|
||||
virtual bool parseJsonKeyValue(const QString& key, const QJsonValue& value);
|
||||
virtual bool parseLegacyKeyValue(const QString& , const QString& ) {return false;}
|
||||
|
||||
QString m_root;
|
||||
QString m_filename;
|
||||
QString m_filepath;
|
||||
private:
|
||||
bool loadLegacy();
|
||||
|
||||
bool m_saveAllFields = false;
|
||||
};
|
||||
|
||||
#endif // KEYVALUECONFIGBASE_H
|
||||
189
include/config/porymapconfig.h
Normal file
189
include/config/porymapconfig.h
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
#pragma once
|
||||
#ifndef PORYMAPCONFIG_H
|
||||
#define PORYMAPCONFIG_H
|
||||
|
||||
#include "keyvalueconfigbase.h"
|
||||
#include "block.h"
|
||||
#include "events.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <QColorSpace>
|
||||
#include <QFontDatabase>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QStandardPaths>
|
||||
|
||||
enum ScriptAutocompleteMode {
|
||||
MapOnly,
|
||||
MapAndCommon,
|
||||
All,
|
||||
};
|
||||
|
||||
class PorymapConfig: public KeyValueConfigBase
|
||||
{
|
||||
public:
|
||||
PorymapConfig() : KeyValueConfigBase(QStringLiteral("settings.json")) {
|
||||
setRoot(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
|
||||
|
||||
// Initialize defaults not available at compile time.
|
||||
this->mapListFont = defaultMapListFont();
|
||||
}
|
||||
|
||||
virtual bool save() override;
|
||||
|
||||
virtual void loadFromJson(const QJsonObject& obj) override;
|
||||
|
||||
void addRecentProject(const QString& project);
|
||||
void setRecentProjects(const QStringList& projects);
|
||||
QString getRecentProject() const;
|
||||
const QStringList& getRecentProjects() const;
|
||||
|
||||
void saveGeometry(const QWidget* widget, const QString& keyPrefix = QString(), bool recursive = true);
|
||||
bool restoreGeometry(QWidget* widget, const QString& keyPrefix = QString(), bool recursive = true) const;
|
||||
|
||||
static QFont defaultMapListFont() { return QFontDatabase::systemFont(QFontDatabase::FixedFont); }
|
||||
|
||||
bool reopenOnLaunch = true;
|
||||
bool projectManuallyClosed = false;
|
||||
int mapListTab = 0;
|
||||
bool mapListEditGroupsEnabled = false;
|
||||
OrderedSet<int> mapListTabsHidingEmptyFolders;
|
||||
bool mapListLayoutsSorted = true;
|
||||
bool mapListLocationsSorted = true;
|
||||
bool prettyCursors = true;
|
||||
bool mirrorConnectingMaps = true;
|
||||
bool showDiveEmergeMaps = false;
|
||||
int diveEmergeMapOpacity = 30;
|
||||
int diveMapOpacity = 15;
|
||||
int emergeMapOpacity = 15;
|
||||
int collisionOpacity = 50;
|
||||
int collisionZoom = 30;
|
||||
int metatilesZoom = 30;
|
||||
int tilesetEditorMetatilesZoom = 30;
|
||||
int tilesetEditorTilesZoom = 30;
|
||||
Qt::Orientation tilesetEditorLayerOrientation = Qt::Vertical;
|
||||
bool showPlayerView = false;
|
||||
bool showCursorTile = true;
|
||||
bool showBorder = true;
|
||||
bool showGrid = false;
|
||||
bool showTilesetEditorMetatileGrid = false;
|
||||
bool showTilesetEditorLayerGrid = true;
|
||||
bool showTilesetEditorDivider = true;
|
||||
bool showTilesetEditorRawAttributes = false;
|
||||
bool showPaletteEditorUnusedColors = false;
|
||||
bool monitorFiles = true;
|
||||
bool tilesetCheckerboardFill = true;
|
||||
bool newMapHeaderSectionExpanded = false;
|
||||
bool displayIdsHexadecimal = true;
|
||||
QString theme = QStringLiteral("default");
|
||||
QString wildMonChartTheme;
|
||||
QString textEditorOpenFolder;
|
||||
QString textEditorGotoLine;
|
||||
int paletteEditorBitDepth = 24;
|
||||
int projectSettingsTab = 0;
|
||||
ScriptAutocompleteMode scriptAutocompleteMode = ScriptAutocompleteMode::MapOnly;
|
||||
bool warpBehaviorWarningDisabled = false;
|
||||
bool eventDeleteWarningDisabled = false;
|
||||
bool eventOverlayEnabled = false;
|
||||
bool checkForUpdates = true;
|
||||
bool showProjectLoadingScreen = true;
|
||||
QDateTime lastUpdateCheckTime;
|
||||
QVersionNumber lastUpdateCheckVersion;
|
||||
QMap<QUrl, QDateTime> rateLimitTimes;
|
||||
QGraphicsPixmapItem::ShapeMode eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape;
|
||||
bool shownInGameReloadMessage = false;
|
||||
GridSettings gridSettings;
|
||||
OrderedSet<LogType> statusBarLogTypes = { LogType::LOG_ERROR, LogType::LOG_WARN };
|
||||
QFont applicationFont;
|
||||
QFont mapListFont;
|
||||
#ifdef Q_OS_MACOS
|
||||
// Since the release of the Retina display, Apple products use the Display P3 color space by default.
|
||||
// If we don't use this for exported images (which by default will either have no color space or the sRGB
|
||||
// color space) then they may appear to have different colors than the same image displayed in Porymap.
|
||||
std::optional<QColorSpace::NamedColorSpace> imageExportColorSpace = QColorSpace::DisplayP3;
|
||||
#else
|
||||
// As of writing Qt has no way to get a reasonable color space from the user's environment,
|
||||
// so we export images without one and let them handle it.
|
||||
std::optional<QColorSpace::NamedColorSpace> imageExportColorSpace = {};
|
||||
#endif
|
||||
QMap<QString,QString> trustedScriptHashes;
|
||||
|
||||
FieldManager* getFieldManager() override {
|
||||
if (!m_fm) {
|
||||
m_fm = std::make_shared<FieldManager>();
|
||||
m_fm->addField(&this->reopenOnLaunch, "reopen_on_launch");
|
||||
m_fm->addField(&this->projectManuallyClosed, "project_manually_closed");
|
||||
m_fm->addField(&this->mapListTab, "map_list_tab", 0, 2);
|
||||
m_fm->addField(&this->mapListEditGroupsEnabled, "map_list_edit_groups_enabled");
|
||||
m_fm->addField(&this->mapListTabsHidingEmptyFolders, "map_list_tabs_hiding_empty_folders");
|
||||
m_fm->addField(&this->mapListLayoutsSorted, "map_list_layouts_sorted");
|
||||
m_fm->addField(&this->mapListLocationsSorted, "map_list_locations_sorted");
|
||||
m_fm->addField(&this->prettyCursors, "pretty_cursors");
|
||||
m_fm->addField(&this->mirrorConnectingMaps, "mirror_connecting_maps");
|
||||
m_fm->addField(&this->showDiveEmergeMaps, "show_dive_emerge_maps");
|
||||
m_fm->addField(&this->diveEmergeMapOpacity, "dive_emerge_map_opacity", 10, 90);
|
||||
m_fm->addField(&this->diveMapOpacity, "dive_map_opacity", 10, 90);
|
||||
m_fm->addField(&this->emergeMapOpacity, "emerge_map_opacity", 10, 90);
|
||||
m_fm->addField(&this->collisionOpacity, "collision_opacity", 0, 100);
|
||||
m_fm->addField(&this->collisionZoom, "collision_zoom", 10, 100);
|
||||
m_fm->addField(&this->metatilesZoom, "metatiles_zoom", 10, 100);
|
||||
m_fm->addField(&this->tilesetEditorMetatilesZoom, "tileset_editor_metatiles_zoom", 10, 100);
|
||||
m_fm->addField(&this->tilesetEditorTilesZoom, "tileset_editor_tiles_zoom", 10, 100);
|
||||
m_fm->addField(&this->tilesetEditorLayerOrientation, "tileset_editor_layer_orientation");
|
||||
m_fm->addField(&this->showPlayerView, "show_player_view");
|
||||
m_fm->addField(&this->showCursorTile, "show_cursor_tile");
|
||||
m_fm->addField(&this->showBorder, "show_border");
|
||||
m_fm->addField(&this->showGrid, "show_grid");
|
||||
m_fm->addField(&this->showTilesetEditorMetatileGrid, "show_tileset_editor_metatile_grid");
|
||||
m_fm->addField(&this->showTilesetEditorLayerGrid, "show_tileset_editor_layer_grid");
|
||||
m_fm->addField(&this->showTilesetEditorDivider, "show_tileset_editor_divider");
|
||||
m_fm->addField(&this->showTilesetEditorRawAttributes, "show_tileset_editor_raw_attributes");
|
||||
m_fm->addField(&this->showPaletteEditorUnusedColors, "show_palette_editor_unused_colors");
|
||||
m_fm->addField(&this->monitorFiles, "monitor_files");
|
||||
m_fm->addField(&this->tilesetCheckerboardFill, "tileset_checkerboard_fill");
|
||||
m_fm->addField(&this->newMapHeaderSectionExpanded, "new_map_header_section_expanded");
|
||||
m_fm->addField(&this->displayIdsHexadecimal, "display_ids_hexadecimal");
|
||||
m_fm->addField(&this->theme, "theme");
|
||||
m_fm->addField(&this->wildMonChartTheme, "wild_mon_chart_theme");
|
||||
m_fm->addField(&this->textEditorOpenFolder, "text_editor_open_folder");
|
||||
m_fm->addField(&this->textEditorGotoLine, "text_editor_goto_line");
|
||||
m_fm->addField(&this->paletteEditorBitDepth, "palette_editor_bit_depth", {24,15});
|
||||
m_fm->addField(&this->projectSettingsTab, "project_settings_tab");
|
||||
m_fm->addField(&this->scriptAutocompleteMode, "script_autocomplete_mode");
|
||||
m_fm->addField(&this->warpBehaviorWarningDisabled, "warp_behavior_warning_disabled");
|
||||
m_fm->addField(&this->eventDeleteWarningDisabled, "event_delete_warning_disabled");
|
||||
m_fm->addField(&this->eventOverlayEnabled, "event_overlay_enabled");
|
||||
m_fm->addField(&this->checkForUpdates, "check_for_updates");
|
||||
m_fm->addField(&this->showProjectLoadingScreen, "show_project_loading_screen");
|
||||
m_fm->addField(&this->lastUpdateCheckTime, "last_update_check_time");
|
||||
m_fm->addField(&this->lastUpdateCheckVersion, "last_update_check_version");
|
||||
m_fm->addField(&this->rateLimitTimes, "rate_limit_times");
|
||||
m_fm->addField(&this->eventSelectionShapeMode, "event_selection_shape_mode");
|
||||
m_fm->addField(&this->shownInGameReloadMessage, "shown_in_game_reload_message");
|
||||
m_fm->addField(&this->gridSettings, "map_grid");
|
||||
m_fm->addField(&this->statusBarLogTypes, "status_bar_log_types");
|
||||
m_fm->addField(&this->applicationFont, "application_font");
|
||||
m_fm->addField(&this->mapListFont, "map_list_font");
|
||||
m_fm->addField(&this->imageExportColorSpace, "image_export_color_space");
|
||||
m_fm->addField(&this->trustedScriptHashes, "trusted_script_hashes");
|
||||
|
||||
m_fm->addField(&this->recentProjects, "recent_projects");
|
||||
m_fm->addField(&this->savedGeometryMap, "geometry");
|
||||
m_fm->addField(&this->geometryVersion, "geometry_version");
|
||||
}
|
||||
return m_fm.get();
|
||||
};
|
||||
|
||||
protected:
|
||||
virtual bool parseLegacyKeyValue(const QString& key, const QString& value) override;
|
||||
virtual QJsonObject getDefaultJson() const override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<FieldManager> m_fm = nullptr;
|
||||
QStringList recentProjects;
|
||||
QMap<QString, QByteArray> savedGeometryMap;
|
||||
int geometryVersion = 0;
|
||||
};
|
||||
|
||||
extern PorymapConfig porymapConfig;
|
||||
|
||||
#endif // PORYMAPCONFIG_H
|
||||
276
include/config/projectconfig.h
Normal file
276
include/config/projectconfig.h
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
#pragma once
|
||||
#ifndef PROJECTCONFIG_H
|
||||
#define PROJECTCONFIG_H
|
||||
|
||||
#include "keyvalueconfigbase.h"
|
||||
#include "block.h"
|
||||
#include "events.h"
|
||||
|
||||
enum ProjectIdentifier {
|
||||
symbol_facing_directions,
|
||||
symbol_obj_event_gfx_pointers,
|
||||
symbol_pokemon_icon_table,
|
||||
symbol_attribute_table,
|
||||
symbol_tilesets_prefix,
|
||||
symbol_dynamic_map_name,
|
||||
define_obj_event_count,
|
||||
define_min_level,
|
||||
define_max_level,
|
||||
define_max_encounter_rate,
|
||||
define_tiles_primary,
|
||||
define_tiles_total,
|
||||
define_metatiles_primary,
|
||||
define_pals_primary,
|
||||
define_pals_total,
|
||||
define_tiles_per_metatile,
|
||||
define_map_size,
|
||||
define_map_offset_width,
|
||||
define_map_offset_height,
|
||||
define_mask_metatile,
|
||||
define_mask_collision,
|
||||
define_mask_elevation,
|
||||
define_mask_behavior,
|
||||
define_mask_layer,
|
||||
define_attribute_behavior,
|
||||
define_attribute_layer,
|
||||
define_attribute_terrain,
|
||||
define_attribute_encounter,
|
||||
define_metatile_label_prefix,
|
||||
define_heal_locations_prefix,
|
||||
define_layout_prefix,
|
||||
define_map_prefix,
|
||||
define_map_dynamic,
|
||||
define_map_empty,
|
||||
define_map_section_prefix,
|
||||
define_map_section_empty,
|
||||
define_species_prefix,
|
||||
define_species_empty,
|
||||
regex_behaviors,
|
||||
regex_obj_event_gfx,
|
||||
regex_items,
|
||||
regex_flags,
|
||||
regex_vars,
|
||||
regex_movement_types,
|
||||
regex_map_types,
|
||||
regex_battle_scenes,
|
||||
regex_weather,
|
||||
regex_coord_event_weather,
|
||||
regex_secret_bases,
|
||||
regex_sign_facing_directions,
|
||||
regex_trainer_types,
|
||||
regex_music,
|
||||
regex_encounter_types,
|
||||
regex_terrain_types,
|
||||
pals_output_extension,
|
||||
tiles_output_extension,
|
||||
};
|
||||
|
||||
enum ProjectFilePath {
|
||||
data_map_folders,
|
||||
data_scripts_folders,
|
||||
data_layouts_folders,
|
||||
data_primary_tilesets_folders,
|
||||
data_secondary_tilesets_folders,
|
||||
data_event_scripts,
|
||||
json_map_groups,
|
||||
json_layouts,
|
||||
json_wild_encounters,
|
||||
json_heal_locations,
|
||||
json_region_map_entries,
|
||||
json_region_porymap_cfg,
|
||||
tilesets_headers,
|
||||
tilesets_graphics,
|
||||
tilesets_metatiles,
|
||||
tilesets_headers_asm,
|
||||
tilesets_graphics_asm,
|
||||
tilesets_metatiles_asm,
|
||||
data_obj_event_gfx_pointers,
|
||||
data_obj_event_gfx_info,
|
||||
data_obj_event_pic_tables,
|
||||
data_obj_event_gfx,
|
||||
data_pokemon_gfx,
|
||||
constants_global,
|
||||
constants_items,
|
||||
constants_flags,
|
||||
constants_vars,
|
||||
constants_weather,
|
||||
constants_songs,
|
||||
constants_pokemon,
|
||||
constants_map_types,
|
||||
constants_trainer_types,
|
||||
constants_secret_bases,
|
||||
constants_obj_event_movement,
|
||||
constants_obj_events,
|
||||
constants_event_bg,
|
||||
constants_metatile_labels,
|
||||
constants_metatile_behaviors,
|
||||
constants_species,
|
||||
constants_fieldmap,
|
||||
global_fieldmap,
|
||||
fieldmap,
|
||||
initial_facing_table,
|
||||
wild_encounter,
|
||||
pokemon_icon_table,
|
||||
pokemon_gfx,
|
||||
};
|
||||
|
||||
// Distance in pixels from the edge of a GBA screen (240x160) to the center 16x16 pixels.
|
||||
#define GBA_H_DIST_TO_CENTER ((240-16)/2)
|
||||
#define GBA_V_DIST_TO_CENTER ((160-16)/2)
|
||||
|
||||
class ProjectConfig: public KeyValueConfigBase
|
||||
{
|
||||
public:
|
||||
ProjectConfig(const QString& root = QString()) : KeyValueConfigBase(QStringLiteral("porymap.project.json")) {
|
||||
setRoot(root);
|
||||
}
|
||||
ProjectConfig(BaseGame::Version version, const QString& root = QString()) : ProjectConfig(root) {
|
||||
setVersionSpecificDefaults(version);
|
||||
}
|
||||
|
||||
virtual bool save() override;
|
||||
|
||||
virtual void loadFromJson(const QJsonObject& obj) override;
|
||||
|
||||
QString projectDir() const { return m_root; } // Alias for root()
|
||||
void setVersionSpecificDefaults(BaseGame::Version baseGameVersion);
|
||||
|
||||
void setFilePath(ProjectFilePath pathId, const QString& path);
|
||||
void setFilePath(const QString& pathId, const QString& path);
|
||||
QString getCustomFilePath(ProjectFilePath pathId);
|
||||
QString getCustomFilePath(const QString& pathId);
|
||||
QString getFilePath(ProjectFilePath pathId);
|
||||
|
||||
void setIdentifier(ProjectIdentifier id, const QString& text);
|
||||
void setIdentifier(const QString& id, const QString& text);
|
||||
QString getCustomIdentifier(ProjectIdentifier id);
|
||||
QString getCustomIdentifier(const QString& id);
|
||||
QString getIdentifier(ProjectIdentifier id);
|
||||
|
||||
static const QMap<ProjectIdentifier, QPair<QString, QString>> defaultIdentifiers;
|
||||
static const QMap<ProjectFilePath, QPair<QString, QString>> defaultPaths;
|
||||
|
||||
BaseGame::Version baseGameVersion = BaseGame::Version::none;
|
||||
bool usePoryScript = false;
|
||||
bool useCustomBorderSize = false;
|
||||
bool eventWeatherTriggerEnabled = false;
|
||||
bool eventSecretBaseEnabled = false;
|
||||
bool hiddenItemQuantityEnabled = false;
|
||||
bool hiddenItemRequiresItemfinderEnabled = false;
|
||||
bool healLocationRespawnDataEnabled = false;
|
||||
bool eventCloneObjectEnabled = false;
|
||||
bool floorNumberEnabled = false;
|
||||
bool createMapTextFileEnabled = false;
|
||||
bool tripleLayerMetatilesEnabled = false;
|
||||
uint16_t defaultMetatileId = 1;
|
||||
uint16_t defaultElevation = 3;
|
||||
uint16_t defaultCollision = 0;
|
||||
QSize defaultMapSize = QSize(20,20);
|
||||
QList<uint16_t> newMapBorderMetatileIds;
|
||||
QString defaultPrimaryTileset = QStringLiteral("gTileset_General");
|
||||
QString defaultSecondaryTileset;
|
||||
bool tilesetsHaveCallback = true;
|
||||
bool tilesetsHaveIsCompressed = true;
|
||||
QColor transparencyColor = QColorConstants::Black;
|
||||
bool preserveMatchingOnlyData = false;
|
||||
int metatileAttributesSize = 2;
|
||||
uint32_t metatileBehaviorMask = 0;
|
||||
uint32_t metatileTerrainTypeMask = 0;
|
||||
uint32_t metatileEncounterTypeMask = 0;
|
||||
uint32_t metatileLayerTypeMask = 0;
|
||||
uint16_t blockMetatileIdMask = Block::DefaultMetatileIdMask;
|
||||
uint16_t blockCollisionMask = Block::DefaultCollisionMask;
|
||||
uint16_t blockElevationMask = Block::DefaultElevationMask;
|
||||
uint16_t unusedTileNormal = 0x3014;
|
||||
uint16_t unusedTileCovered = 0x0000;
|
||||
uint16_t unusedTileSplit = 0x0000;
|
||||
bool mapAllowFlagsEnabled = true;
|
||||
QString eventsTabIconPath;
|
||||
QString collisionSheetPath;
|
||||
QSize collisionSheetSize = QSize(2, 16);
|
||||
QMargins playerViewDistance = QMargins(GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER, GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER);
|
||||
OrderedSet<uint32_t> warpBehaviors;
|
||||
int maxEventsPerGroup = 255;
|
||||
int metatileSelectorWidth = 8;
|
||||
QStringList globalConstantsFilepaths;
|
||||
QMap<QString,QString> globalConstants;
|
||||
QList<ScriptSettings> customScripts;
|
||||
QMap<Event::Group, QString> eventIconPaths;
|
||||
QMap<QString, QString> pokemonIconPaths;
|
||||
QVersionNumber minimumVersion;
|
||||
|
||||
FieldManager* getFieldManager() override {
|
||||
if (!m_fm) {
|
||||
m_fm = std::make_shared<FieldManager>();
|
||||
m_fm->addField(&this->baseGameVersion, "base_game_version");
|
||||
m_fm->addField(&this->usePoryScript, "use_poryscript");
|
||||
m_fm->addField(&this->useCustomBorderSize, "use_custom_border_size");
|
||||
m_fm->addField(&this->eventWeatherTriggerEnabled, "enable_event_weather_trigger");
|
||||
m_fm->addField(&this->eventSecretBaseEnabled, "enable_event_secret_base");
|
||||
m_fm->addField(&this->hiddenItemQuantityEnabled, "enable_hidden_item_quantity");
|
||||
m_fm->addField(&this->hiddenItemRequiresItemfinderEnabled, "enable_hidden_item_requires_itemfinder");
|
||||
m_fm->addField(&this->healLocationRespawnDataEnabled, "enable_heal_location_respawn_data");
|
||||
m_fm->addField(&this->eventCloneObjectEnabled, "enable_event_clone_object");
|
||||
m_fm->addField(&this->floorNumberEnabled, "enable_floor_number");
|
||||
m_fm->addField(&this->createMapTextFileEnabled, "create_map_text_file");
|
||||
m_fm->addField(&this->tripleLayerMetatilesEnabled, "enable_triple_layer_metatiles");
|
||||
m_fm->addField<uint16_t>(&this->defaultMetatileId, "default_metatile_id", 0, Block::MaxValue);
|
||||
m_fm->addField<uint16_t>(&this->defaultElevation, "default_elevation", 0, Block::MaxValue);
|
||||
m_fm->addField<uint16_t>(&this->defaultCollision, "default_collision", 0, Block::MaxValue);
|
||||
m_fm->addField(&this->defaultMapSize, "default_map_size");
|
||||
m_fm->addField(&this->newMapBorderMetatileIds, "new_map_border_metatiles");
|
||||
m_fm->addField(&this->defaultPrimaryTileset, "default_primary_tileset");
|
||||
m_fm->addField(&this->defaultSecondaryTileset, "default_secondary_tileset");
|
||||
m_fm->addField(&this->tilesetsHaveCallback, "tilesets_have_callback");
|
||||
m_fm->addField(&this->tilesetsHaveIsCompressed, "tilesets_have_is_compressed");
|
||||
m_fm->addField(&this->transparencyColor, "transparency_color");
|
||||
m_fm->addField(&this->preserveMatchingOnlyData, "preserve_matching_only_data");
|
||||
m_fm->addField(&this->metatileAttributesSize, "metatile_attributes_size");
|
||||
m_fm->addField(&this->metatileBehaviorMask, "metatile_behavior_mask");
|
||||
m_fm->addField(&this->metatileTerrainTypeMask, "metatile_terrain_type_mask");
|
||||
m_fm->addField(&this->metatileEncounterTypeMask, "metatile_encounter_type_mask");
|
||||
m_fm->addField(&this->metatileLayerTypeMask, "metatile_layer_type_mask");
|
||||
m_fm->addField<uint16_t>(&this->blockMetatileIdMask, "block_metatile_id_mask", 0, Block::MaxValue);
|
||||
m_fm->addField<uint16_t>(&this->blockCollisionMask, "block_collision_mask", 0, Block::MaxValue);
|
||||
m_fm->addField<uint16_t>(&this->blockElevationMask, "block_elevation_mask", 0, Block::MaxValue);
|
||||
m_fm->addField<uint16_t>(&this->unusedTileNormal, "unused_tile_normal", 0, Tile::MaxValue);
|
||||
m_fm->addField<uint16_t>(&this->unusedTileCovered, "unused_tile_covered", 0, Tile::MaxValue);
|
||||
m_fm->addField<uint16_t>(&this->unusedTileSplit, "unused_tile_split", 0, Tile::MaxValue);
|
||||
m_fm->addField(&this->mapAllowFlagsEnabled, "enable_map_allow_flags");
|
||||
m_fm->addField(&this->eventsTabIconPath, "events_tab_icon_path");
|
||||
m_fm->addField(&this->collisionSheetPath, "collision_sheet_path");
|
||||
m_fm->addField(&this->collisionSheetSize, "collision_sheet_size", QSize(1,1), QSize(Block::MaxValue, Block::MaxValue));
|
||||
m_fm->addField(&this->playerViewDistance, "player_view_distance", QMargins(0,0,0,0), QMargins(INT_MAX, INT_MAX, INT_MAX, INT_MAX));
|
||||
m_fm->addField(&this->warpBehaviors, "warp_behaviors");
|
||||
m_fm->addField(&this->maxEventsPerGroup, "max_events_per_group", 1, INT_MAX);
|
||||
m_fm->addField(&this->metatileSelectorWidth, "metatile_selector_width", 1, INT_MAX);
|
||||
m_fm->addField(&this->globalConstantsFilepaths, "global_constants_filepaths");
|
||||
m_fm->addField(&this->globalConstants, "global_constants");
|
||||
m_fm->addField(&this->customScripts, "custom_scripts");
|
||||
m_fm->addField(&this->eventIconPaths, "event_icon_paths");
|
||||
m_fm->addField(&this->pokemonIconPaths, "pokemon_icon_paths");
|
||||
m_fm->addField(&this->minimumVersion, "minimum_version");
|
||||
|
||||
m_fm->addField(&this->identifiers, "custom_identifiers");
|
||||
m_fm->addField(&this->filePaths, "custom_file_paths");
|
||||
}
|
||||
return m_fm.get();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual bool parseLegacyKeyValue(const QString& key, const QString& value) override;
|
||||
virtual QJsonObject getDefaultJson() const override;
|
||||
virtual void initializeFromEmpty() override;
|
||||
|
||||
private:
|
||||
ProjectFilePath reverseDefaultPaths(const QString& str);
|
||||
ProjectIdentifier reverseDefaultIdentifier(const QString& str);
|
||||
|
||||
std::shared_ptr<FieldManager> m_fm = nullptr;
|
||||
QMap<ProjectIdentifier, QString> identifiers;
|
||||
QMap<ProjectFilePath, QString> filePaths;
|
||||
};
|
||||
|
||||
extern ProjectConfig projectConfig;
|
||||
|
||||
#endif // PROJECTCONFIG_H
|
||||
55
include/config/shortcutsconfig.h
Normal file
55
include/config/shortcutsconfig.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
#ifndef SHORTCUTSCONFIG_H
|
||||
#define SHORTCUTSCONFIG_H
|
||||
|
||||
#include "keyvalueconfigbase.h"
|
||||
|
||||
#include <QStandardPaths>
|
||||
|
||||
class QAction;
|
||||
class Shortcut;
|
||||
|
||||
class ShortcutsConfig : public KeyValueConfigBase
|
||||
{
|
||||
public:
|
||||
ShortcutsConfig() : KeyValueConfigBase(QStringLiteral("shortcuts.json")) {
|
||||
setRoot(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
|
||||
}
|
||||
|
||||
virtual QJsonObject toJson() override;
|
||||
virtual void loadFromJson(const QJsonObject& obj) override;
|
||||
|
||||
// Call this before applying user shortcuts so that the user can restore defaults.
|
||||
void setDefaultShortcuts(const QObjectList& objects);
|
||||
QList<QKeySequence> defaultShortcuts(const QObject *object) const;
|
||||
|
||||
void setUserShortcuts(const QObjectList& objects);
|
||||
void setUserShortcuts(const QMultiMap<const QObject *, QKeySequence>& objects_keySequences);
|
||||
QList<QKeySequence> userShortcuts(const QObject *object) const;
|
||||
|
||||
protected:
|
||||
virtual bool parseLegacyKeyValue(const QString& key, const QString& value) override;
|
||||
virtual QJsonObject getDefaultJson() const override;
|
||||
|
||||
private:
|
||||
QMultiMap<QString, QKeySequence> user_shortcuts;
|
||||
QMultiMap<QString, QKeySequence> default_shortcuts;
|
||||
|
||||
enum StoreType {
|
||||
User,
|
||||
Default
|
||||
};
|
||||
|
||||
QString cfgKey(const QObject *object) const;
|
||||
QList<QKeySequence> currentShortcuts(const QObject *object) const;
|
||||
|
||||
void storeShortcutsFromList(StoreType storeType, const QObjectList& objects);
|
||||
void storeShortcuts(
|
||||
StoreType storeType,
|
||||
const QString& cfgKey,
|
||||
const QList<QKeySequence>& keySequences);
|
||||
};
|
||||
|
||||
extern ShortcutsConfig shortcutsConfig;
|
||||
|
||||
#endif // SHORTCUTSCONFIG_H
|
||||
46
include/config/userconfig.h
Normal file
46
include/config/userconfig.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
#ifndef USERCONFIG_H
|
||||
#define USERCONFIG_H
|
||||
|
||||
#include "keyvalueconfigbase.h"
|
||||
|
||||
class UserConfig: public KeyValueConfigBase
|
||||
{
|
||||
public:
|
||||
UserConfig(const QString& root = QString()) : KeyValueConfigBase(QStringLiteral("porymap.user.json")) {
|
||||
setRoot(root);
|
||||
}
|
||||
|
||||
virtual void loadFromJson(const QJsonObject& obj) override;
|
||||
|
||||
QString projectDir() const { return m_root; } // Alias for root()
|
||||
|
||||
QString recentMapOrLayout;
|
||||
QString prefabsFilepath;
|
||||
bool prefabsImportPrompted = false;
|
||||
bool useEncounterJson = true;
|
||||
QList<ScriptSettings> customScripts;
|
||||
|
||||
protected:
|
||||
virtual bool parseLegacyKeyValue(const QString& key, const QString& value) override;
|
||||
virtual QJsonObject getDefaultJson() const override;
|
||||
|
||||
FieldManager* getFieldManager() override {
|
||||
if (!m_fm) {
|
||||
m_fm = std::make_shared<FieldManager>();
|
||||
m_fm->addField(&this->recentMapOrLayout, "recent_map_or_layout");
|
||||
m_fm->addField(&this->prefabsFilepath, "prefabs_filepath");
|
||||
m_fm->addField(&this->prefabsImportPrompted, "prefabs_import_prompted");
|
||||
m_fm->addField(&this->useEncounterJson, "use_encounter_json");
|
||||
m_fm->addField(&this->customScripts, "custom_scripts");
|
||||
}
|
||||
return m_fm.get();
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<FieldManager> m_fm = nullptr;
|
||||
};
|
||||
|
||||
extern UserConfig userConfig;
|
||||
|
||||
#endif // USERCONFIG_H
|
||||
22
include/core/basegame.h
Normal file
22
include/core/basegame.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
#ifndef BASEGAMEVERSION_H
|
||||
#define BASEGAMEVERSION_H
|
||||
|
||||
#include <QString>
|
||||
#include <QIcon>
|
||||
|
||||
namespace BaseGame {
|
||||
enum Version {
|
||||
none,
|
||||
pokeruby,
|
||||
pokefirered,
|
||||
pokeemerald,
|
||||
};
|
||||
Version stringToVersion(const QString &string);
|
||||
QString versionToString(Version version);
|
||||
|
||||
QString getPlayerIconPath(Version version, int character);
|
||||
QIcon getPlayerIcon(Version version, int character);
|
||||
};
|
||||
|
||||
#endif // BASEGAMEVERSION_H
|
||||
|
|
@ -26,7 +26,12 @@ public:
|
|||
static uint16_t getMaxCollision();
|
||||
static uint16_t getMaxElevation();
|
||||
|
||||
static const uint16_t maxValue;
|
||||
// Upper limit for metatile ID, collision, and elevation masks. Used externally.
|
||||
static constexpr uint16_t MaxValue = 0xFFFF;
|
||||
|
||||
static constexpr uint16_t DefaultMetatileIdMask = 0x03FF;
|
||||
static constexpr uint16_t DefaultCollisionMask = 0x0C00;
|
||||
static constexpr uint16_t DefaultElevationMask = 0xF000;
|
||||
|
||||
private:
|
||||
uint16_t m_metatileId;
|
||||
|
|
|
|||
401
include/core/converter.h
Normal file
401
include/core/converter.h
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
#pragma once
|
||||
#ifndef CONVERTER_H
|
||||
#define CONVERTER_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonValue>
|
||||
#include <QUrl>
|
||||
#include <QVariant>
|
||||
#include <QVersionNumber>
|
||||
|
||||
#include "magic_enum.hpp"
|
||||
#include "orderedset.h"
|
||||
#include "scriptsettings.h"
|
||||
#include "gridsettings.h"
|
||||
#include "basegame.h"
|
||||
|
||||
/*
|
||||
These are templates for type conversion to/from JSON,
|
||||
though other type conversions can be implemented here too.
|
||||
|
||||
This is mostly useful when converting the type is complicated,
|
||||
or when the type is generalized away.
|
||||
|
||||
|
||||
## Example Usage ##
|
||||
QSize size;
|
||||
QJsonValue json = Converter<QSize>::toJson(size);
|
||||
QSize sameSize = Converter<QSize>::fromJson(json);
|
||||
|
||||
|
||||
## Adding a new conversion ##
|
||||
To add a new type conversion, add a new 'Converter' template:
|
||||
|
||||
template <>
|
||||
struct Converter<NewType> : DefaultConverter<NewType> {
|
||||
// And re-implement any of the desired conversion functions.
|
||||
// Any functions not implemented will be inherited from DefaultConverter.
|
||||
static QJsonValue toJson(const NewType& value) {
|
||||
// your conversion to JSON
|
||||
return QJsonValue();
|
||||
}
|
||||
static NewType fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
// your conversion from JSON
|
||||
return NewType();
|
||||
}
|
||||
};
|
||||
|
||||
Note: When serializing to/from JSON, anything that can be serialized to/from
|
||||
a string is trivially serializable for JSON. In this case, rather than
|
||||
inheriting from 'DefaultConverter' and reimplementing 'toJson' and 'fromJson',
|
||||
you can inherit from 'DefaultStringConverter' and/or reimplement 'toString'/'fromString'.
|
||||
Appropriately implementing 'toString'/'fromString' has the added benefit that your type
|
||||
can automatically be used as a JSON key if it for example appears as the key in a QMap.
|
||||
|
||||
If a type doesn't support the '<'/'>' operators, 'clamp' can be reimplemented as well to allow
|
||||
the type to be used in range validation.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
template <typename T>
|
||||
struct DefaultConverter {
|
||||
// Defaults to straightforward QJsonValue construction.
|
||||
// This handles most of the primitive types.
|
||||
static QJsonValue toJson(const T& value) {
|
||||
return QJsonValue(value);
|
||||
}
|
||||
static T fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
const QVariant v = json.toVariant();
|
||||
if (!v.canConvert<T>()) {
|
||||
if (errors) errors->append(QString("Can't convert from type '%1'").arg(v.typeName()));
|
||||
// Failed conversion will return a default-constructed object below
|
||||
}
|
||||
return v.value<T>();
|
||||
}
|
||||
|
||||
// Default to identity
|
||||
static QString toString(const T& value) {return value;}
|
||||
static T fromString(const QString& string, QStringList* = nullptr) {return string;}
|
||||
|
||||
static T clamp(const T& value, const T& min, const T& max, QStringList* errors = nullptr) {
|
||||
Q_ASSERT(min <= max);
|
||||
if (value < min) {
|
||||
if (errors) errors->append("Value too low");
|
||||
return min;
|
||||
}
|
||||
if (value > max) {
|
||||
if (errors) errors->append("Value too high");
|
||||
return max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct Converter : DefaultConverter<T> {};
|
||||
|
||||
// This template implements JSON conversion by first converting the data to/from a string.
|
||||
// This allows any type that can describe how to stringify itself to automatically also
|
||||
// support JSON conversion with no additional work.
|
||||
template <typename T>
|
||||
struct DefaultStringConverter : DefaultConverter<T> {
|
||||
static QJsonValue toJson(const T& value) {
|
||||
return Converter<QString>::toJson(Converter<T>::toString(value));
|
||||
}
|
||||
static T fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
const auto string = Converter<QString>::fromJson(json, errors);
|
||||
return Converter<T>::fromString(string, errors);
|
||||
}
|
||||
// Many types have a 'toString' function, so we default to trying that.
|
||||
static QString toString(const T& value) {
|
||||
return value.toString();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<QUrl> : DefaultStringConverter<QUrl> {};
|
||||
|
||||
template <>
|
||||
struct Converter<QKeySequence> : DefaultStringConverter<QKeySequence> {};
|
||||
|
||||
template <>
|
||||
struct Converter<uint32_t> : DefaultConverter<uint32_t> {
|
||||
// Constructing a QJsonValue from uint32_t is ambiguous, so we need an explicit cast.
|
||||
static QJsonValue toJson(uint32_t value) {
|
||||
return QJsonValue{static_cast<qint64>(value)};
|
||||
}
|
||||
};
|
||||
|
||||
// Template for generic enum values.
|
||||
// Converts JSON -> string/int -> enum, handling the unsafe conversion if the int is out of range of the enum.
|
||||
// Qt has a system for this (Q_ENUM) but they don't use it for all their internal enums, so we use magic_enum instead.
|
||||
template <typename T>
|
||||
struct Converter<T, std::enable_if_t<std::is_enum_v<T>>> : DefaultStringConverter<T> {
|
||||
static QString toString(const T& value) {
|
||||
const std::string s = std::string(magic_enum::enum_name(value));
|
||||
return QString::fromStdString(s);
|
||||
}
|
||||
static T fromString(const QString& string, QStringList* errors = nullptr) {
|
||||
auto e = magic_enum::enum_cast<T>(string.toStdString(), magic_enum::case_insensitive);
|
||||
if (!e.has_value()) {
|
||||
if (errors) errors->append(QString("'%1' is not a named enum value.").arg(string));
|
||||
return magic_enum::enum_value<T>(0);
|
||||
}
|
||||
return e.value();
|
||||
}
|
||||
// When reading from JSON, handle either the named enum or an enum's number value.
|
||||
static T fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
if (json.isString()) return Converter<T>::fromString(json.toString());
|
||||
auto value = Converter<int>::fromJson(json, errors);
|
||||
auto e = magic_enum::enum_cast<T>(value);
|
||||
if (!e.has_value()) {
|
||||
if (errors) errors->append(QString("'%1' is out of range of enum.").arg(QString::number(value)));
|
||||
return magic_enum::enum_value<T>(0);
|
||||
}
|
||||
return e.value();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<QVersionNumber> : DefaultStringConverter<QVersionNumber> {
|
||||
static QVersionNumber fromString(const QString& string, QStringList* = nullptr) {
|
||||
return QVersionNumber::fromString(string);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<QDateTime> : DefaultStringConverter<QDateTime> {
|
||||
static QString toString(const QDateTime& value) {
|
||||
return value.toUTC().toString();
|
||||
}
|
||||
static QDateTime fromString(const QString& string, QStringList* = nullptr) {
|
||||
return QDateTime::fromString(string).toLocalTime();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<QColor> : DefaultStringConverter<QColor> {
|
||||
static QString toString(const QColor& value) {
|
||||
return value.name();
|
||||
}
|
||||
static QColor fromString(const QString& string, QStringList* errors = nullptr) {
|
||||
const QColor color(string);
|
||||
if (!color.isValid()) {
|
||||
if (errors) errors->append(QString("'%1' is not a valid color.").arg(string));
|
||||
return QColorConstants::Black;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<QFont> : DefaultStringConverter<QFont> {
|
||||
static QFont fromString(const QString& string, QStringList* errors = nullptr) {
|
||||
QFont font;
|
||||
if (!font.fromString(string) && errors) {
|
||||
errors->append(QString("'%1' is not a valid font description.").arg(string));
|
||||
}
|
||||
return font;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<BaseGame::Version> : DefaultStringConverter<BaseGame::Version> {
|
||||
static QString toString(const BaseGame::Version& value) {
|
||||
return BaseGame::versionToString(value);
|
||||
}
|
||||
static BaseGame::Version fromString(const QString& string, QStringList* = nullptr) {
|
||||
return BaseGame::stringToVersion(string);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Converter<std::optional<T>> : DefaultConverter<std::optional<T>> {
|
||||
static QJsonValue toJson(const std::optional<T>& optional) {
|
||||
return optional.has_value() ? Converter<T>::toJson(optional.value()) : QJsonValue();
|
||||
}
|
||||
static std::optional<T> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
if (json.isNull()) return {};
|
||||
return Converter<T>::fromJson(json, errors);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct ListConverter : DefaultConverter<QList<T>> {
|
||||
static QJsonValue toJson(const QList<T>& list) {
|
||||
QJsonArray arr;
|
||||
for (auto& elem : list) arr.append(Converter<T>::toJson(elem));
|
||||
return arr;
|
||||
}
|
||||
static QList<T> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
const auto arr = Converter<QJsonArray>::fromJson(json, errors);
|
||||
QList<T> list;
|
||||
for (const auto& elem : arr) list.append(Converter<T>::fromJson(elem, errors));
|
||||
return list;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Converter<QList<T>> : ListConverter<T> {};
|
||||
|
||||
// Only needed for Qt5
|
||||
template <>
|
||||
struct Converter<QStringList> : ListConverter<QString> {};
|
||||
|
||||
template <typename K, typename V>
|
||||
struct Converter<QMap<K,V>> : DefaultConverter<QMap<K,V>> {
|
||||
static QJsonObject toJson(const QMap<K,V>& map) {
|
||||
QJsonObject obj;
|
||||
for (auto it = map.begin(); it != map.end(); it++) {
|
||||
const QString key = Converter<K>::toString(it.key());
|
||||
obj[key] = Converter<V>::toJson(it.value());
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
static QMap<K,V> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
|
||||
QMap<K,V> map;
|
||||
for (auto it = obj.begin(); it != obj.end(); it++) {
|
||||
const auto key = Converter<K>::fromString(it.key(), errors);
|
||||
map.insert(key, Converter<V>::fromJson(it.value(), errors));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename K, typename V>
|
||||
struct Converter<QMultiMap<K,V>> : DefaultConverter<QMultiMap<K,V>> {
|
||||
static QJsonObject toJson(const QMultiMap<K,V>& map) {
|
||||
QJsonObject obj;
|
||||
for (const auto& uniqueKey : map.uniqueKeys()) {
|
||||
const QString key = Converter<K>::toString(uniqueKey);
|
||||
obj[key] = Converter<QList<V>>::toJson(map.values(uniqueKey));
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
static QMultiMap<K,V> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
|
||||
QMultiMap<K,V> map;
|
||||
for (auto it = obj.begin(); it != obj.end(); it++) {
|
||||
const auto key = Converter<K>::fromString(it.key(), errors);
|
||||
const auto values = Converter<QList<V>>::fromJson(it.value(), errors);
|
||||
for (const auto& value : values) map.insert(key, value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Converter<OrderedSet<T>> : DefaultConverter<OrderedSet<T>> {
|
||||
static QJsonValue toJson(const OrderedSet<T>& set) {
|
||||
QJsonArray arr;
|
||||
for (auto& elem : set) arr.append(Converter<T>::toJson(elem));
|
||||
return arr;
|
||||
}
|
||||
static OrderedSet<T> fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
const auto arr = Converter<QJsonArray>::fromJson(json, errors);
|
||||
OrderedSet<T> set;
|
||||
for (const auto& elem : arr) set.insert(Converter<T>::fromJson(elem, errors));
|
||||
return set;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<QSize> : DefaultConverter<QSize> {
|
||||
static QJsonValue toJson(const QSize& value) {
|
||||
QJsonObject obj;
|
||||
obj["width"] = value.width();
|
||||
obj["height"] = value.height();
|
||||
return obj;
|
||||
}
|
||||
static QSize fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
|
||||
QSize size;
|
||||
size.setWidth(obj.value("width").toInt());
|
||||
size.setHeight(obj.value("height").toInt());
|
||||
return size;
|
||||
}
|
||||
static QSize clamp(const QSize& value, const QSize& min, const QSize& max, const QStringList* = nullptr) {
|
||||
Q_ASSERT(min.width() <= max.width());
|
||||
Q_ASSERT(min.height() <= max.height());
|
||||
QSize size = value;
|
||||
if (value.width() < min.width() || value.height() < min.height()) size = value.expandedTo(min);
|
||||
if (value.width() > max.width() || value.height() > max.height()) size = value.boundedTo(max);
|
||||
return size;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<QMargins> : DefaultConverter<QMargins> {
|
||||
static QJsonValue toJson(const QMargins& value) {
|
||||
QJsonObject obj;
|
||||
obj["top"] = value.top();
|
||||
obj["bottom"] = value.bottom();
|
||||
obj["left"] = value.left();
|
||||
obj["right"] = value.right();
|
||||
return obj;
|
||||
}
|
||||
static QMargins fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
|
||||
QMargins margins;
|
||||
margins.setTop(obj.value("top").toInt());
|
||||
margins.setBottom(obj.value("bottom").toInt());
|
||||
margins.setLeft(obj.value("left").toInt());
|
||||
margins.setRight(obj.value("right").toInt());
|
||||
return margins;
|
||||
}
|
||||
static QMargins clamp(const QMargins& value, const QMargins& min, const QMargins& max, const QStringList* = nullptr) {
|
||||
Q_ASSERT(min.left() <= max.left());
|
||||
Q_ASSERT(min.right() <= max.right());
|
||||
Q_ASSERT(min.top() <= max.top());
|
||||
Q_ASSERT(min.bottom() <= max.bottom());
|
||||
QMargins margins = value;
|
||||
margins.setLeft( std::clamp(value.left(), min.left(), max.left()));
|
||||
margins.setRight( std::clamp(value.right(), min.right(), max.right()));
|
||||
margins.setTop( std::clamp(value.top(), min.top(), max.top()));
|
||||
margins.setBottom(std::clamp(value.bottom(), min.bottom(), max.bottom()));
|
||||
return margins;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<ScriptSettings> : DefaultConverter<ScriptSettings> {
|
||||
static QJsonValue toJson(const ScriptSettings& value) {
|
||||
QJsonObject obj;
|
||||
obj["path"] = value.path;
|
||||
obj["enabled"] = value.enabled;
|
||||
return obj;
|
||||
}
|
||||
static ScriptSettings fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
|
||||
ScriptSettings settings;
|
||||
settings.path = obj.value("path").toString();
|
||||
settings.enabled = obj.value("enabled").toBool();
|
||||
return settings;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<GridSettings> : DefaultConverter<GridSettings> {
|
||||
static QJsonValue toJson(const GridSettings& value) {
|
||||
return value.toJson();
|
||||
}
|
||||
static GridSettings fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
const auto obj = Converter<QJsonObject>::fromJson(json, errors);
|
||||
return GridSettings::fromJson(obj);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<QByteArray> : DefaultConverter<QByteArray> {
|
||||
static QJsonValue toJson(const QByteArray& value) {
|
||||
return QString::fromLocal8Bit(value.toBase64());
|
||||
}
|
||||
static QByteArray fromJson(const QJsonValue& json, QStringList* errors = nullptr) {
|
||||
const auto s = Converter<QString>::fromJson(json, errors);
|
||||
return QByteArray::fromBase64(s.toLocal8Bit());
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CONVERTER_H
|
||||
167
include/core/fieldmanager.h
Normal file
167
include/core/fieldmanager.h
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
#include "converter.h"
|
||||
|
||||
// A FieldInterface provides a simple interface for converting a field to/from JSON.
|
||||
// It's constructed with a pointer to some data, and has two functions:
|
||||
// - 'get' returns the pointed-to data, converted to JSON
|
||||
// - 'set' assigns the pointed-to data to a given QJsonValue, with appropriate conversion.
|
||||
// Returns any errors that occur during conversion/assignment.
|
||||
//
|
||||
// A FieldInterface is normally constructed using the 'makeFieldInterface' function:
|
||||
// Example:
|
||||
// int someField = 0;
|
||||
// FieldInterface* fi = makeFieldInterface(&someField);
|
||||
// fi->set(QJsonValue("5")); // someField is now 5
|
||||
//
|
||||
// There are additional implementations of 'makeFieldInterface' that let you specify what valid values are.
|
||||
// Example:
|
||||
// int someField = 0;
|
||||
// int min = 1, max = 4;
|
||||
// FieldInterface* fi = makeFieldInterface(&someField, min, max);
|
||||
// fi->set(QJsonValue("5")); // someField is now 4 (defaults to closest bound), error messages returned
|
||||
// or
|
||||
// QString someField = "";
|
||||
// QList<QString> options = {"hello","hi there"};
|
||||
// FieldInterface* fi = makeFieldInterface(&someField, options);
|
||||
// fi->set(QJsonValue("5")); // someField is now "hello" (defaults to first element), error messages returned
|
||||
|
||||
// Base class lets us use the interface without any type information.
|
||||
class FieldInterface {
|
||||
public:
|
||||
FieldInterface(){};
|
||||
virtual ~FieldInterface() {};
|
||||
virtual QJsonValue get() const = 0;
|
||||
virtual QStringList set(const QJsonValue& json) const = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class BasicFieldInterface : public FieldInterface {
|
||||
public:
|
||||
BasicFieldInterface(T* field) : m_field(field) {
|
||||
Q_ASSERT(m_field);
|
||||
};
|
||||
virtual ~BasicFieldInterface() {};
|
||||
virtual QJsonValue get() const override {return Converter<T>::toJson(*m_field);}
|
||||
virtual QStringList set(const QJsonValue& json) const override {
|
||||
QStringList errors;
|
||||
auto value = Converter<T>::fromJson(json, &errors);
|
||||
if (errors.isEmpty()) *m_field = value; // Don't bother changing the value if conversion failed
|
||||
return errors;
|
||||
}
|
||||
protected:
|
||||
T* m_field;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
static FieldInterface* makeFieldInterface(T* field) {
|
||||
return new BasicFieldInterface<T>(field);
|
||||
}
|
||||
|
||||
// Create a regular FieldInterface, but override 'set' to use the given min/max.
|
||||
template <typename T>
|
||||
static FieldInterface* makeFieldInterface(T* field, const T& min, const T& max) {
|
||||
class BoundedFieldInterface : public BasicFieldInterface<T> {
|
||||
public:
|
||||
BoundedFieldInterface(T* field, const T& min, const T& max)
|
||||
: BasicFieldInterface<T>(field), m_min(min), m_max(max) {};
|
||||
virtual ~BoundedFieldInterface() {};
|
||||
virtual QStringList set(const QJsonValue& json) const override {
|
||||
QStringList errors;
|
||||
auto value = Converter<T>::fromJson(json, &errors);
|
||||
if (errors.isEmpty()) { // Don't bother changing the value if conversion failed
|
||||
value = Converter<T>::clamp(value, m_min, m_max, &errors);
|
||||
*this->m_field = value;
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
private:
|
||||
const T m_min;
|
||||
const T m_max;
|
||||
};
|
||||
return new BoundedFieldInterface(field, min, max);
|
||||
}
|
||||
|
||||
// Create a regular FieldInterface, but override 'set' to use the given 'acceptableValues'.
|
||||
template <typename T>
|
||||
static FieldInterface* makeFieldInterface(T* field, const QList<T>& acceptableValues) {
|
||||
Q_ASSERT(!acceptableValues.isEmpty());
|
||||
class BoundedFieldInterface : public BasicFieldInterface<T> {
|
||||
public:
|
||||
BoundedFieldInterface(T* field, const QList<T>& acceptableValues)
|
||||
: BasicFieldInterface<T>(field),
|
||||
m_acceptableValues(acceptableValues.begin(), acceptableValues.end()),
|
||||
m_defaultValue(acceptableValues.first()) {};
|
||||
virtual ~BoundedFieldInterface() {};
|
||||
virtual QStringList set(const QJsonValue& json) const override {
|
||||
QStringList errors;
|
||||
auto value = Converter<T>::fromJson(json, &errors);
|
||||
if (errors.isEmpty()) {
|
||||
if (!m_acceptableValues.contains(value)) {
|
||||
value = m_defaultValue;
|
||||
errors.append("Invalid value.");
|
||||
}
|
||||
*this->m_field = value;
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
private:
|
||||
// The order of the list only matters for determining the default value,
|
||||
// so save that separately and convert the list to a set for better lookup speed.
|
||||
const QSet<T> m_acceptableValues;
|
||||
const T m_defaultValue;
|
||||
};
|
||||
return new BoundedFieldInterface(field, acceptableValues);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// FieldManager manages a QHash mapping string keys to FieldInterfaces.
|
||||
// This makes it easy to map many fields to/from JSON without explicitly serializing anything.
|
||||
|
||||
class FieldManager {
|
||||
public:
|
||||
~FieldManager(){ clear(); }
|
||||
|
||||
QStringList setField(const QString& key, const QJsonValue& value) const {
|
||||
auto it = m_fields.find(key);
|
||||
return (it != m_fields.end()) ? it.value()->set(value) : QStringList();
|
||||
}
|
||||
|
||||
QJsonValue getField(const QString& key) const {
|
||||
auto it = m_fields.find(key);
|
||||
return (it != m_fields.end()) ? it.value()->get() : QJsonValue();
|
||||
}
|
||||
|
||||
QJsonObject getFields() const {
|
||||
QJsonObject obj;
|
||||
for (auto it = m_fields.constBegin(); it != m_fields.constEnd(); it++) {
|
||||
obj[it.key()] = it.value()->get();
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
qDeleteAll(m_fields);
|
||||
m_fields.clear();
|
||||
}
|
||||
|
||||
bool hasField(const QString& key) const {return m_fields.contains(key);}
|
||||
|
||||
template <typename T>
|
||||
void addField(T* field, const QString& key) {
|
||||
Q_ASSERT(!m_fields.contains(key));
|
||||
m_fields.insert(key, makeFieldInterface<T>(field));
|
||||
}
|
||||
template <typename T>
|
||||
void addField(T* field, const QString& key, const T& min, const T& max) {
|
||||
Q_ASSERT(!m_fields.contains(key));
|
||||
m_fields.insert(key, makeFieldInterface<T>(field, min, max));
|
||||
}
|
||||
template <typename T>
|
||||
void addField(T* field, const QString& key, const QList<T>& acceptableValues) {
|
||||
Q_ASSERT(!m_fields.contains(key));
|
||||
m_fields.insert(key, makeFieldInterface<T>(field, acceptableValues));
|
||||
}
|
||||
|
||||
private:
|
||||
QHash<QString, FieldInterface*> m_fields;
|
||||
};
|
||||
|
|
@ -8,6 +8,7 @@
|
|||
#include <QPixmap>
|
||||
#include <QString>
|
||||
#include <QUndoStack>
|
||||
#include <QJsonObject>
|
||||
|
||||
class Map;
|
||||
class LayoutPixmapItem;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@
|
|||
#define METATILE_H
|
||||
|
||||
#include "tile.h"
|
||||
#include "config.h"
|
||||
#include "basegame.h"
|
||||
#include "bitpacker.h"
|
||||
#include <QImage>
|
||||
#include <QPoint>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
|
||||
class Project;
|
||||
|
||||
|
|
@ -41,7 +43,7 @@ public:
|
|||
uint32_t getAttributes() const;
|
||||
uint32_t getAttribute(Metatile::Attr attr) const { return this->attributes.value(attr, 0); }
|
||||
void setAttributes(uint32_t data);
|
||||
void setAttributes(uint32_t data, BaseGameVersion version);
|
||||
void setAttributes(uint32_t data, BaseGame::Version version);
|
||||
void setAttribute(Metatile::Attr attr, uint32_t value);
|
||||
|
||||
// For convenience
|
||||
|
|
@ -56,17 +58,18 @@ public:
|
|||
|
||||
static int getIndexInTileset(int);
|
||||
static QPoint coordFromPixmapCoord(const QPointF &pixelCoord);
|
||||
static uint32_t getDefaultAttributesMask(BaseGameVersion version, Metatile::Attr attr);
|
||||
static uint32_t getDefaultAttributesMask(BaseGame::Version version, Metatile::Attr attr);
|
||||
static uint32_t getMaxAttributesMask();
|
||||
static int getDefaultAttributesSize(BaseGameVersion version);
|
||||
static int getDefaultAttributesSize(BaseGame::Version version);
|
||||
static void setLayout(Project*);
|
||||
static QString getMetatileIdString(uint16_t metatileId);
|
||||
static QString getMetatileIdStrings(const QList<uint16_t> &metatileIds);
|
||||
static QString getLayerName(int layerNum);
|
||||
|
||||
static int numLayers();
|
||||
static constexpr int tileWidth() { return 2; }
|
||||
static constexpr int tileHeight() { return 2; }
|
||||
static constexpr int tilesPerLayer() { return Metatile::tileWidth() * Metatile::tileHeight(); }
|
||||
static int maxTiles() { return Metatile::numLayers() * Metatile::tilesPerLayer(); }
|
||||
static constexpr int pixelWidth() { return Metatile::tileWidth() * Tile::pixelWidth(); }
|
||||
static constexpr int pixelHeight() { return Metatile::tileHeight() * Tile::pixelHeight(); }
|
||||
static constexpr QSize pixelSize() { return QSize(pixelWidth(), pixelHeight()); }
|
||||
|
|
|
|||
31
include/core/orderedset.h
Normal file
31
include/core/orderedset.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
#ifndef ORDERED_SET_H
|
||||
#define ORDERED_SET_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <set>
|
||||
|
||||
template <typename T>
|
||||
class OrderedSet : public std::set<T>
|
||||
{
|
||||
using std::set<T>::set;
|
||||
|
||||
public:
|
||||
// Not introduced to std::set until C++20
|
||||
#if __cplusplus < 202002L
|
||||
bool contains(const T& value) const {
|
||||
return this->find(value) != this->end();
|
||||
}
|
||||
#endif
|
||||
QSet<T> toQSet() const {
|
||||
return QSet<T>(this->begin(), this->end());
|
||||
}
|
||||
static QSet<T> fromQSet(const QSet<T>& set) {
|
||||
return OrderedSet<T>(set.begin(), set.end());
|
||||
}
|
||||
bool isEmpty() const {
|
||||
return this->empty();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ORDERED_SET_H
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include "map.h"
|
||||
#include "tilemaptileselector.h"
|
||||
#include "history.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QString>
|
||||
|
|
|
|||
27
include/core/scriptsettings.h
Normal file
27
include/core/scriptsettings.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#ifndef SCRIPTSETTINGS_H
|
||||
#define SCRIPTSETTINGS_H
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
|
||||
// Holds the basic user-provided information about a plug-in script.
|
||||
struct ScriptSettings {
|
||||
QString path;
|
||||
bool enabled = true;
|
||||
|
||||
// Scripts can either by specific to the project, or specific to the user.
|
||||
// This allows projects to send scripts downstream to their users,
|
||||
// while still allowing them to use their own personal scripts.
|
||||
bool userOnly = true;
|
||||
|
||||
static QStringList filter(const QList<ScriptSettings>& scripts) {
|
||||
QStringList paths;
|
||||
for (auto& script : scripts) {
|
||||
if (script.enabled) paths.append(script.path);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // SCRIPTSETTINGS_H
|
||||
|
|
@ -5,9 +5,6 @@
|
|||
#include <QObject>
|
||||
#include <QSize>
|
||||
|
||||
// TODO: Replace once config refactoring is complete.
|
||||
extern bool ConfigDisplayIdsHexadecimal;
|
||||
|
||||
class Tile
|
||||
{
|
||||
public:
|
||||
|
|
@ -30,7 +27,8 @@ public:
|
|||
QString toString() const;
|
||||
static QString getTileIdString(uint16_t tileId);
|
||||
|
||||
static const uint16_t maxValue;
|
||||
// Upper limit for raw value (i.e., uint16_t max).
|
||||
static constexpr uint16_t MaxValue = 0xFFFF;
|
||||
|
||||
static constexpr int pixelWidth() { return 8; }
|
||||
static constexpr int pixelHeight() { return 8; }
|
||||
|
|
|
|||
|
|
@ -18,9 +18,21 @@ namespace Util {
|
|||
void setErrorStylesheet(QLineEdit *lineEdit, bool isError);
|
||||
QString toStylesheetString(const QFont &font);
|
||||
void show(QWidget *w);
|
||||
QColorSpace toColorSpace(int colorSpaceInt);
|
||||
QString mkpath(const QString& dirPath);
|
||||
QString getFileHash(const QString &filepath);
|
||||
|
||||
// Given a QMap<T,QString>, erases all entries with empty strings.
|
||||
// Returns the number of entries erased.
|
||||
template <typename T>
|
||||
int removeEmptyStrings(QMap<T,QString> *map) {
|
||||
if (!map) return 0;
|
||||
int numRemoved = 0;
|
||||
for (auto it = map->begin(); it != map->end();) {
|
||||
if (it.value().isEmpty()) it = map->erase(it);
|
||||
else {it++; numRemoved++;}
|
||||
}
|
||||
return numRemoved;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // UTILITY_H
|
||||
|
|
|
|||
9
include/core/version.h
Normal file
9
include/core/version.h
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
#ifndef VERSION_H
|
||||
#define VERSION_H
|
||||
|
||||
#include <QVersionNumber>
|
||||
|
||||
extern const QVersionNumber porymapVersion;
|
||||
|
||||
#endif // VERSION_H
|
||||
1549
include/lib/magic_enum.hpp
Normal file
1549
include/lib/magic_enum.hpp
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -440,7 +440,7 @@ private:
|
|||
void initShortcuts();
|
||||
void initExtraShortcuts();
|
||||
void loadUserSettings();
|
||||
void restoreWindowState();
|
||||
void resizeWithinScreen();
|
||||
void setTheme(QString);
|
||||
void updateTilesetEditor();
|
||||
Event::Group getEventGroupFromTabWidget(QWidget *tab);
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public:
|
|||
void clearHealLocations();
|
||||
|
||||
bool sanityCheck();
|
||||
int getSupportedMajorVersion(QString *errorOut = nullptr);
|
||||
QVersionNumber getMinimumVersion(QString *errorOut = nullptr) const;
|
||||
bool load();
|
||||
|
||||
QMap<QString, Tileset*> tilesetCache;
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public:
|
|||
Q_INVOKABLE bool getBorderVisibility();
|
||||
Q_INVOKABLE void setSmartPathsEnabled(bool visible);
|
||||
Q_INVOKABLE bool getSmartPathsEnabled();
|
||||
Q_INVOKABLE QList<QString> getCustomScripts();
|
||||
static Q_INVOKABLE QList<QString> getCustomScripts();
|
||||
Q_INVOKABLE QList<int> getMetatileLayerOrder();
|
||||
Q_INVOKABLE void setMetatileLayerOrder(const QList<int> &order);
|
||||
Q_INVOKABLE QList<float> getMetatileLayerOpacity();
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
#ifndef CITYMAPPIXMAPITEM_H
|
||||
#define CITYMAPPIXMAPITEM_H
|
||||
|
||||
#include "tilemaptileselector.h"
|
||||
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QByteArray>
|
||||
#include <QVector>
|
||||
|
||||
class CityMapPixmapItem : public QObject, public QGraphicsPixmapItem {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
using QGraphicsPixmapItem::paint;
|
||||
|
||||
public:
|
||||
CityMapPixmapItem(QString fname, TilemapTileSelector *tile_selector) {
|
||||
this->file = fname;
|
||||
this->tile_selector = tile_selector;
|
||||
setAcceptHoverEvents(true);
|
||||
init();
|
||||
}
|
||||
TilemapTileSelector *tile_selector;
|
||||
|
||||
QString file;
|
||||
|
||||
QByteArray data;
|
||||
|
||||
void init();
|
||||
void save();
|
||||
void create(QString);
|
||||
virtual void paint(QGraphicsSceneMouseEvent *);
|
||||
virtual void draw();
|
||||
int getIndexAt(int, int);
|
||||
int width();
|
||||
int height();
|
||||
|
||||
QVector<uint8_t> getTiles();
|
||||
void setTiles(QVector<uint8_t>);
|
||||
|
||||
private:
|
||||
int width_;
|
||||
int height_;
|
||||
|
||||
signals:
|
||||
void mouseEvent(QGraphicsSceneMouseEvent *, CityMapPixmapItem *);
|
||||
void hoveredRegionMapTileChanged(int x, int y);
|
||||
void hoveredRegionMapTileCleared();
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent*);
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent*);
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent*);
|
||||
};
|
||||
|
||||
#endif // CITYMAPPIXMAPITEM_H
|
||||
|
|
@ -33,7 +33,7 @@ private:
|
|||
bool hasUnsavedChanges = false;
|
||||
const QString baseDir;
|
||||
|
||||
void displayScript(const QString &filepath, bool enabled);
|
||||
void displayScript(const ScriptSettings &settings);
|
||||
void displayNewScript(QString filepath);
|
||||
QString chooseScript(QString dir);
|
||||
void removeScript(QListWidgetItem * item);
|
||||
|
|
@ -46,7 +46,6 @@ private:
|
|||
int prompt(const QString &text, QMessageBox::StandardButton defaultButton);
|
||||
void save();
|
||||
void closeEvent(QCloseEvent*);
|
||||
void restoreWindowState();
|
||||
void initShortcuts();
|
||||
QObjectList shortcutableObjects() const;
|
||||
void openManual();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#define CUSTOMSCRIPTSLISTITEM_H
|
||||
|
||||
#include <QFrame>
|
||||
#include "scriptsettings.h"
|
||||
|
||||
namespace Ui {
|
||||
class CustomScriptsListItem;
|
||||
|
|
@ -13,9 +14,26 @@ class CustomScriptsListItem : public QFrame
|
|||
|
||||
public:
|
||||
explicit CustomScriptsListItem(QWidget *parent = nullptr);
|
||||
explicit CustomScriptsListItem(const ScriptSettings& settings, QWidget *parent = nullptr);
|
||||
~CustomScriptsListItem();
|
||||
|
||||
public:
|
||||
void setSettings(const ScriptSettings& settings);
|
||||
ScriptSettings getSettings() const;
|
||||
|
||||
void setPath(const QString& text);
|
||||
QString path() const;
|
||||
|
||||
void setScriptEnabled(bool enabled);
|
||||
bool scriptEnabled() const;
|
||||
|
||||
signals:
|
||||
void clickedChooseScript();
|
||||
void clickedEditScript();
|
||||
void clickedDeleteScript();
|
||||
void toggledEnable(bool checked);
|
||||
void pathEdited(const QString& text);
|
||||
|
||||
private:
|
||||
Ui::CustomScriptsListItem *ui;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include <QObject>
|
||||
#include <QEvent>
|
||||
#include <QSet>
|
||||
|
||||
|
||||
/// Ctrl+Wheel = zoom
|
||||
|
|
@ -16,7 +17,6 @@ public slots:
|
|||
};
|
||||
|
||||
|
||||
|
||||
/// Emits a signal when a window gets activated / regains focus
|
||||
class ActiveWindowFilter : public QObject {
|
||||
Q_OBJECT
|
||||
|
|
@ -27,3 +27,15 @@ public:
|
|||
signals:
|
||||
void activated();
|
||||
};
|
||||
|
||||
|
||||
class GeometrySaver : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GeometrySaver(QObject *parent, bool enableLogging = true)
|
||||
: QObject(parent), m_loggingEnabled(enableLogging) {}
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
private:
|
||||
bool m_loggingEnabled = true;
|
||||
QSet<QObject*> m_shown;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,10 +3,12 @@
|
|||
|
||||
#include <QDialog>
|
||||
#include <QAbstractButton>
|
||||
#include <QJsonObject>
|
||||
#include "metatile.h"
|
||||
|
||||
class GridSettings {
|
||||
public:
|
||||
explicit GridSettings() {};
|
||||
constexpr GridSettings() {};
|
||||
~GridSettings() {};
|
||||
|
||||
enum Style {
|
||||
|
|
@ -17,15 +19,18 @@ public:
|
|||
Dots,
|
||||
};
|
||||
|
||||
uint width = 16;
|
||||
uint height = 16;
|
||||
uint width = Metatile::pixelWidth();
|
||||
uint height = Metatile::pixelHeight();
|
||||
int offsetX = 0;
|
||||
int offsetY = 0;
|
||||
Style style = Style::Solid;
|
||||
QColor color = Qt::black;
|
||||
QColor color = QColorConstants::Black;
|
||||
QVector<qreal> getHorizontalDashPattern() const { return this->getDashPattern(this->width); }
|
||||
QVector<qreal> getVerticalDashPattern() const { return this->getDashPattern(this->height); }
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static GridSettings fromJson(const QJsonObject &obj);
|
||||
|
||||
static QString getStyleName(Style style);
|
||||
static GridSettings::Style getStyleFromName(const QString &name);
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -84,7 +84,12 @@ private:
|
|||
QImage m_previewImage;
|
||||
bool m_previewUpdateQueued = false;
|
||||
QList<int> m_layerOrder;
|
||||
ProjectConfig m_savedConfig;
|
||||
struct SavedConfigSettings {
|
||||
QColor transparencyColor;
|
||||
uint16_t unusedTileNormal = 0;
|
||||
uint16_t unusedTileCovered = 0;
|
||||
uint16_t unusedTileSplit = 0;
|
||||
} m_savedConfig;
|
||||
QList<QRadioButton*> m_transparencyButtons;
|
||||
|
||||
void populate(const Settings &settings);
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ private:
|
|||
void commitEditHistory();
|
||||
void commitEditHistory(int paletteId);
|
||||
void updateEditHistoryActions();
|
||||
void restoreWindowState();
|
||||
void invalidateCache();
|
||||
void closeEvent(QCloseEvent*);
|
||||
void setColorInputTitles(bool show);
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public:
|
|||
void addPrefab(MetatileSelection selection, Layout *layout, QString name);
|
||||
void updatePrefabUi(QPointer<Layout> layout);
|
||||
void clearPrefabUi();
|
||||
bool tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version, QString filepath = "");
|
||||
bool tryImportDefaultPrefabs(QWidget * parent, BaseGame::Version version, QString filepath = "");
|
||||
|
||||
private:
|
||||
QPointer<MetatileSelector> selector;
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ private:
|
|||
|
||||
void initUi();
|
||||
void connectSignals();
|
||||
void restoreWindowState();
|
||||
void save();
|
||||
void refresh();
|
||||
void closeEvent(QCloseEvent*);
|
||||
|
|
@ -72,6 +71,7 @@ private:
|
|||
void addNewGlobalConstant();
|
||||
void addGlobalConstant(const QString &name, const QString &expression);
|
||||
QMap<QString,QString> getGlobalConstants();
|
||||
BaseGame::Version getBaseGameVersion() const;
|
||||
|
||||
private slots:
|
||||
void dialogButtonClicked(QAbstractButton *button);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
#define REGIONMAPEDITOR_H
|
||||
|
||||
#include "regionmappixmapitem.h"
|
||||
#include "citymappixmapitem.h"
|
||||
#include "regionmaplayoutpixmapitem.h"
|
||||
#include "regionmapentriespixmapitem.h"
|
||||
#include "regionmap.h"
|
||||
|
|
@ -118,7 +117,6 @@ private:
|
|||
void setRegionMap(RegionMap *map);
|
||||
void setLocations(const QStringList &locations);
|
||||
|
||||
void restoreWindowState();
|
||||
void closeEvent(QCloseEvent* event);
|
||||
|
||||
void setTileHFlip(bool enabled);
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ private:
|
|||
void initMetatileLayersItem();
|
||||
void initShortcuts();
|
||||
void initExtraShortcuts();
|
||||
void restoreWindowState();
|
||||
void initMetatileHistory();
|
||||
void setTilesets(QString primaryTilesetLabel, QString secondaryTilesetLabel);
|
||||
void reset();
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ public:
|
|||
explicit WildMonChart(QWidget *parent, const EncounterTableModel *table);
|
||||
~WildMonChart();
|
||||
|
||||
virtual void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
public slots:
|
||||
void setTable(const EncounterTableModel *table);
|
||||
void clearTable();
|
||||
|
|
|
|||
26
porymap.pro
26
porymap.pro
|
|
@ -46,9 +46,15 @@ DEFINES += PORYMAP_LATEST_COMMIT=\\\"$$LATEST_COMMIT\\\"
|
|||
VERSION = 6.3.0
|
||||
DEFINES += PORYMAP_VERSION=\\\"$$VERSION\\\"
|
||||
|
||||
SOURCES += src/core/advancemapparser.cpp \
|
||||
SOURCES += src/config/keyvalueconfigbase.cpp \
|
||||
src/config/legacy.cpp \
|
||||
src/config/porymapconfig.cpp \
|
||||
src/config/projectconfig.cpp \
|
||||
src/config/shortcutsconfig.cpp \
|
||||
src/config/userconfig.cpp \
|
||||
src/core/advancemapparser.cpp \
|
||||
src/core/basegame.cpp \
|
||||
src/core/block.cpp \
|
||||
src/ui/resizelayoutpopup.cpp \
|
||||
src/core/bitpacker.cpp \
|
||||
src/core/blockdata.cpp \
|
||||
src/core/events.cpp \
|
||||
|
|
@ -66,6 +72,7 @@ SOURCES += src/core/advancemapparser.cpp \
|
|||
src/core/tileset.cpp \
|
||||
src/core/utility.cpp \
|
||||
src/core/validator.cpp \
|
||||
src/core/version.cpp \
|
||||
src/core/regionmap.cpp \
|
||||
src/core/wildmoninfo.cpp \
|
||||
src/core/editcommands.cpp \
|
||||
|
|
@ -113,7 +120,7 @@ SOURCES += src/core/advancemapparser.cpp \
|
|||
src/ui/layoutpixmapitem.cpp \
|
||||
src/ui/prefabcreationdialog.cpp \
|
||||
src/ui/regionmappixmapitem.cpp \
|
||||
src/ui/citymappixmapitem.cpp \
|
||||
src/ui/resizelayoutpopup.cpp \
|
||||
src/ui/mapheaderform.cpp \
|
||||
src/ui/metatilelayersitem.cpp \
|
||||
src/ui/metatileselector.cpp \
|
||||
|
|
@ -152,7 +159,6 @@ SOURCES += src/core/advancemapparser.cpp \
|
|||
src/ui/colorpicker.cpp \
|
||||
src/ui/loadingscreen.cpp \
|
||||
src/ui/unlockableicon.cpp \
|
||||
src/config.cpp \
|
||||
src/editor.cpp \
|
||||
src/main.cpp \
|
||||
src/mainwindow.cpp \
|
||||
|
|
@ -164,10 +170,16 @@ SOURCES += src/core/advancemapparser.cpp \
|
|||
src/ui/wildmonchart.cpp \
|
||||
src/ui/wildmonsearch.cpp
|
||||
|
||||
HEADERS += include/core/advancemapparser.h \
|
||||
HEADERS += include/config/keyvalueconfigbase.h \
|
||||
include/config/porymapconfig.h \
|
||||
include/config/projectconfig.h \
|
||||
include/config/shortcutsconfig.h \
|
||||
include/config/userconfig.h \
|
||||
include/core/advancemapparser.h \
|
||||
include/core/block.h \
|
||||
include/core/bitpacker.h \
|
||||
include/core/blockdata.h \
|
||||
include/core/converter.h \
|
||||
include/core/events.h \
|
||||
include/core/filedialog.h \
|
||||
include/core/history.h \
|
||||
|
|
@ -184,6 +196,7 @@ HEADERS += include/core/advancemapparser.h \
|
|||
include/core/tileset.h \
|
||||
include/core/utility.h \
|
||||
include/core/validator.h \
|
||||
include/core/version.h \
|
||||
include/core/regionmap.h \
|
||||
include/core/wildmoninfo.h \
|
||||
include/core/editcommands.h \
|
||||
|
|
@ -233,7 +246,6 @@ HEADERS += include/core/advancemapparser.h \
|
|||
include/ui/mapview.h \
|
||||
include/ui/prefabcreationdialog.h \
|
||||
include/ui/regionmappixmapitem.h \
|
||||
include/ui/citymappixmapitem.h \
|
||||
include/ui/colorinputwidget.h \
|
||||
include/ui/metatilelayersitem.h \
|
||||
include/ui/metatileselector.h \
|
||||
|
|
@ -274,7 +286,6 @@ HEADERS += include/core/advancemapparser.h \
|
|||
include/ui/colorpicker.h \
|
||||
include/ui/loadingscreen.h \
|
||||
include/ui/unlockableicon.h \
|
||||
include/config.h \
|
||||
include/editor.h \
|
||||
include/mainwindow.h \
|
||||
include/project.h \
|
||||
|
|
@ -332,6 +343,7 @@ RESOURCES += \
|
|||
resources/text.qrc
|
||||
|
||||
INCLUDEPATH += include
|
||||
INCLUDEPATH += include/config
|
||||
INCLUDEPATH += include/core
|
||||
INCLUDEPATH += include/ui
|
||||
INCLUDEPATH += include/lib
|
||||
|
|
|
|||
1518
src/config.cpp
1518
src/config.cpp
File diff suppressed because it is too large
Load Diff
105
src/config/keyvalueconfigbase.cpp
Normal file
105
src/config/keyvalueconfigbase.cpp
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#include "keyvalueconfigbase.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
|
||||
void KeyValueConfigBase::setRoot(const QString& root) {
|
||||
m_root = root;
|
||||
QDir dir(m_root);
|
||||
if (!m_root.isEmpty() && !dir.exists()) {
|
||||
dir.mkpath(m_root);
|
||||
}
|
||||
// Caching the filepath constructed from m_root + m_filename
|
||||
m_filepath = dir.absoluteFilePath(m_filename);
|
||||
}
|
||||
|
||||
bool KeyValueConfigBase::load() {
|
||||
QFile file(filepath());
|
||||
if (file.exists()) {
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
logError(QString("Failed to read config file '%1': %2").arg(filepath()).arg(file.errorString()));
|
||||
return false;
|
||||
}
|
||||
if (file.size() == 0) {
|
||||
logWarn(QString("Config file '%1' was empty.").arg(filepath()));
|
||||
// An empty file isn't a valid JSON file, but other than the warning
|
||||
// we'll treat it the same as if it were a JSON file with an empty object.
|
||||
initializeFromEmpty();
|
||||
return true;
|
||||
}
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(file.readAll(), &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
logError(QString("Failed to read config file '%1': %2").arg(filepath()).arg(parseError.errorString()));
|
||||
return false;
|
||||
}
|
||||
if (!jsonDoc.isObject()) {
|
||||
logError(QString("Failed to read config file '%1': Expected top level JSON object.").arg(filepath()));
|
||||
return false;
|
||||
}
|
||||
loadFromJson(jsonDoc.object());
|
||||
logInfo(QString("Loaded config file '%1'").arg(filename()));
|
||||
} else if (!loadLegacy()) {
|
||||
// No config file is present (either in the new or old format)
|
||||
initializeFromEmpty();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void KeyValueConfigBase::loadFromJson(const QJsonObject& obj) {
|
||||
for (auto it = obj.begin(); it != obj.end(); it++) {
|
||||
if (!parseJsonKeyValue(it.key(), it.value())) {
|
||||
logWarn(QString("Discarding unrecognized config key '%1'").arg(it.key()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool KeyValueConfigBase::parseJsonKeyValue(const QString& key, const QJsonValue& value) {
|
||||
auto fieldManager = getFieldManager();
|
||||
if (!fieldManager || !fieldManager->hasField(key)) return false;
|
||||
|
||||
// Recognized 'key' as a registered field. Let the FieldManager try to assign the value.
|
||||
const QStringList errors = fieldManager->setField(key, value);
|
||||
if (errors.length() == 1) logWarn(QString("Failed to read config key '%1': %2").arg(key).arg(errors.at(0)));
|
||||
else if (errors.length() > 1) logWarn(QString("Failed to read config key '%1':\n%2").arg(key).arg(errors.join("\n")));
|
||||
return true;
|
||||
}
|
||||
|
||||
QJsonObject KeyValueConfigBase::toJson() {
|
||||
auto fieldManager = getFieldManager();
|
||||
return fieldManager ? fieldManager->getFields() : QJsonObject();
|
||||
}
|
||||
|
||||
bool KeyValueConfigBase::save() {
|
||||
QFile file(filepath());
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
logError(QString("Could not open config file '%1' for writing: ").arg(filepath()) + file.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject savedObject;
|
||||
if (m_saveAllFields) {
|
||||
savedObject = toJson();
|
||||
} else {
|
||||
// We limit the output to fields that have changed from the default value.
|
||||
// This has a few notable benefits:
|
||||
// - It allows changes to the default values to be downstreamed from Porymap.
|
||||
// - It reduces diff noise for configs as Porymap's settings change over time.
|
||||
// - It discourages manual editing of the file; all settings should be edited in the GUI.
|
||||
// If the child class does not reimplement getDefaultJson it returns an empty QJsonObject,
|
||||
// and so the default behavior is to output all fields.
|
||||
const QJsonObject curObject = toJson();
|
||||
const QJsonObject defaultObject = getDefaultJson();
|
||||
for (auto it = curObject.begin(); it != curObject.end(); it++) {
|
||||
if (it.value() != defaultObject.value(it.key())) {
|
||||
savedObject[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonDocument doc(savedObject);
|
||||
file.write(doc.toJson());
|
||||
return true;
|
||||
}
|
||||
419
src/config/legacy.cpp
Normal file
419
src/config/legacy.cpp
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "tile.h"
|
||||
#include "block.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QColor>
|
||||
|
||||
|
||||
QString getLegacyFilename(const QString &newFilename) {
|
||||
static const QMap<QString, QString> map = {
|
||||
{QStringLiteral("settings.json"), QStringLiteral("porymap.cfg")},
|
||||
{QStringLiteral("shortcuts.json"), QStringLiteral("porymap.shortcuts.cfg")},
|
||||
{QStringLiteral("porymap.project.json"), QStringLiteral("porymap.project.cfg")},
|
||||
{QStringLiteral("porymap.user.json"), QStringLiteral("porymap.user.cfg")},
|
||||
};
|
||||
return map.value(newFilename);
|
||||
}
|
||||
|
||||
bool KeyValueConfigBase::loadLegacy() {
|
||||
const QString oldFilename = getLegacyFilename(filename());
|
||||
if (oldFilename.isEmpty()) return false;
|
||||
|
||||
QDir dir(root());
|
||||
QFile file(dir.absoluteFilePath(oldFilename));
|
||||
if (!file.exists() || !file.open(QIODevice::ReadOnly)) return false;
|
||||
|
||||
QTextStream in(&file);
|
||||
static const QRegularExpression re("^(?<key>[^=]+)=(?<value>.*)$");
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine().trimmed();
|
||||
int commentIndex = line.indexOf("#");
|
||||
if (commentIndex >= 0) {
|
||||
line = line.left(commentIndex).trimmed();
|
||||
}
|
||||
|
||||
if (line.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QRegularExpressionMatch match = re.match(line);
|
||||
if (match.hasMatch()) {
|
||||
parseLegacyKeyValue(match.captured("key").trimmed(), match.captured("value").trimmed());
|
||||
}
|
||||
}
|
||||
logInfo(QString("Loaded legacy config file '%1'").arg(oldFilename));
|
||||
|
||||
// Save before deleting the old config file to ensure no data is lost.
|
||||
if (save()) {
|
||||
if (!file.remove()) logWarn(QString("Failed to delete legacy config file '%1'.").arg(oldFilename));
|
||||
else logInfo(QString("Deleted legacy config file '%1'.").arg(oldFilename));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool toBool(const QString &value) {
|
||||
return (value.toInt(nullptr, 0) != 0);
|
||||
}
|
||||
|
||||
int toInt(const QString &value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0) {
|
||||
bool ok;
|
||||
int result = value.toInt(&ok, 0);
|
||||
if (!ok) result = defaultValue;
|
||||
return qBound(min, result, max);
|
||||
}
|
||||
|
||||
uint32_t toUInt(const QString &value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0) {
|
||||
bool ok;
|
||||
uint32_t result = value.toUInt(&ok, 0);
|
||||
if (!ok) result = defaultValue;
|
||||
return qBound(min, result, max);
|
||||
}
|
||||
|
||||
QColor toColor(const QString &value, const QColor &defaultValue = QColor(Qt::black)) {
|
||||
if (value.isEmpty()) return QColor();
|
||||
QColor color = QColor("#" + value);
|
||||
if (!color.isValid()) color = defaultValue;
|
||||
return color;
|
||||
}
|
||||
|
||||
bool PorymapConfig::parseLegacyKeyValue(const QString &key, const QString &value) {
|
||||
if (key == "recent_project") {
|
||||
this->recentProjects = value.split(",", Qt::SkipEmptyParts);
|
||||
this->recentProjects.removeDuplicates();
|
||||
} else if (key == "project_manually_closed") {
|
||||
this->projectManuallyClosed = toBool(value);
|
||||
} else if (key == "reopen_on_launch") {
|
||||
this->reopenOnLaunch = toBool(value);
|
||||
} else if (key == "pretty_cursors") {
|
||||
this->prettyCursors = toBool(value);
|
||||
} else if (key == "map_list_tab") {
|
||||
this->mapListTab = toInt(value, 0, 2, 0);
|
||||
} else if (key == "map_list_edit_groups_enabled") {
|
||||
this->mapListEditGroupsEnabled = toBool(value);
|
||||
} else if (key.startsWith("map_list_hide_empty_enabled/")) {
|
||||
bool ok;
|
||||
int tab = key.mid(QStringLiteral("map_list_hide_empty_enabled/").length()).toInt(&ok, 0);
|
||||
if (ok && toBool(value)) this->mapListTabsHidingEmptyFolders.insert(tab);
|
||||
} else if (key == "map_list_layouts_sorted") {
|
||||
this->mapListLayoutsSorted = toBool(value);
|
||||
} else if (key == "map_list_locations_sorted") {
|
||||
this->mapListLocationsSorted = toBool(value);
|
||||
} else if (key == "mirror_connecting_maps") {
|
||||
this->mirrorConnectingMaps = toBool(value);
|
||||
} else if (key == "show_dive_emerge_maps") {
|
||||
this->showDiveEmergeMaps = toBool(value);
|
||||
} else if (key == "dive_emerge_map_opacity") {
|
||||
this->diveEmergeMapOpacity = toInt(value, 10, 90, 30);
|
||||
} else if (key == "dive_map_opacity") {
|
||||
this->diveMapOpacity = toInt(value, 10, 90, 15);
|
||||
} else if (key == "emerge_map_opacity") {
|
||||
this->emergeMapOpacity = toInt(value, 10, 90, 15);
|
||||
} else if (key == "collision_opacity") {
|
||||
this->collisionOpacity = toInt(value, 0, 100, 50);
|
||||
} else if (key == "metatiles_zoom") {
|
||||
this->metatilesZoom = toInt(value, 10, 100, 30);
|
||||
} else if (key == "collision_zoom") {
|
||||
this->collisionZoom = toInt(value, 10, 100, 30);
|
||||
} else if (key == "tileset_editor_metatiles_zoom") {
|
||||
this->tilesetEditorMetatilesZoom = toInt(value, 10, 100, 30);
|
||||
} else if (key == "tileset_editor_tiles_zoom") {
|
||||
this->tilesetEditorTilesZoom = toInt(value, 10, 100, 30);
|
||||
} else if (key == "tileset_editor_layer_orientation") {
|
||||
// Being explicit here to avoid casting out-of-range values.
|
||||
this->tilesetEditorLayerOrientation = (toInt(value) == static_cast<int>(Qt::Horizontal)) ? Qt::Horizontal : Qt::Vertical;
|
||||
} else if (key == "show_player_view") {
|
||||
this->showPlayerView = toBool(value);
|
||||
} else if (key == "show_cursor_tile") {
|
||||
this->showCursorTile = toBool(value);
|
||||
} else if (key == "show_border") {
|
||||
this->showBorder = toBool(value);
|
||||
} else if (key == "show_grid") {
|
||||
this->showGrid = toBool(value);
|
||||
} else if (key == "show_tileset_editor_metatile_grid") {
|
||||
this->showTilesetEditorMetatileGrid = toBool(value);
|
||||
} else if (key == "show_tileset_editor_layer_grid") {
|
||||
this->showTilesetEditorLayerGrid = toBool(value);
|
||||
} else if (key == "show_tileset_editor_divider") {
|
||||
this->showTilesetEditorDivider = toBool(value);
|
||||
} else if (key == "show_tileset_editor_raw_attributes") {
|
||||
this->showTilesetEditorRawAttributes = toBool(value);
|
||||
} else if (key == "show_palette_editor_unused_colors") {
|
||||
this->showPaletteEditorUnusedColors = toBool(value);
|
||||
} else if (key == "monitor_files") {
|
||||
this->monitorFiles = toBool(value);
|
||||
} else if (key == "tileset_checkerboard_fill") {
|
||||
this->tilesetCheckerboardFill = toBool(value);
|
||||
} else if (key == "new_map_header_section_expanded") {
|
||||
this->newMapHeaderSectionExpanded = toBool(value);
|
||||
} else if (key == "theme") {
|
||||
this->theme = value;
|
||||
} else if (key == "wild_mon_chart_theme") {
|
||||
this->wildMonChartTheme = value;
|
||||
} else if (key == "text_editor_open_directory") {
|
||||
this->textEditorOpenFolder = value;
|
||||
} else if (key == "text_editor_goto_line") {
|
||||
this->textEditorGotoLine = value;
|
||||
} else if (key == "palette_editor_bit_depth") {
|
||||
int bitDepth = toInt(value, 15, 24, 24);
|
||||
if (bitDepth == 15 || bitDepth == 24){
|
||||
this->paletteEditorBitDepth = bitDepth;
|
||||
}
|
||||
} else if (key == "project_settings_tab") {
|
||||
this->projectSettingsTab = toInt(value, 0);
|
||||
} else if (key == "load_all_event_scripts") { // Old setting replaced by script_autocomplete_mode
|
||||
this->scriptAutocompleteMode = toBool(value) ? ScriptAutocompleteMode::All : ScriptAutocompleteMode::MapOnly;
|
||||
} else if (key == "script_autocomplete_mode") {
|
||||
this->scriptAutocompleteMode = static_cast<ScriptAutocompleteMode>(toInt(value, ScriptAutocompleteMode::MapOnly, ScriptAutocompleteMode::All));
|
||||
} else if (key == "warp_behavior_warning_disabled") {
|
||||
this->warpBehaviorWarningDisabled = toBool(value);
|
||||
} else if (key == "event_delete_warning_disabled") {
|
||||
this->eventDeleteWarningDisabled = toBool(value);
|
||||
} else if (key == "event_overlay_enabled") {
|
||||
this->eventOverlayEnabled = toBool(value);
|
||||
} else if (key == "check_for_updates") {
|
||||
this->checkForUpdates = toBool(value);
|
||||
} else if (key == "show_project_loading_screen") {
|
||||
this->showProjectLoadingScreen = toBool(value);
|
||||
} else if (key == "last_update_check_time") {
|
||||
this->lastUpdateCheckTime = QDateTime::fromString(value).toLocalTime();
|
||||
} else if (key == "last_update_check_version") {
|
||||
auto version = QVersionNumber::fromString(value);
|
||||
if (version.segmentCount() == 3) {
|
||||
this->lastUpdateCheckVersion = version;
|
||||
}
|
||||
} else if (key.startsWith("rate_limit_time/")) {
|
||||
static const QRegularExpression regex("\\brate_limit_time/(?<url>.+)");
|
||||
QRegularExpressionMatch match = regex.match(key);
|
||||
if (match.hasMatch()) {
|
||||
this->rateLimitTimes.insert(match.captured("url"), QDateTime::fromString(value).toLocalTime());
|
||||
}
|
||||
} else if (key == "event_selection_shape_mode") {
|
||||
if (value == "mask") {
|
||||
this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape;
|
||||
} else if (value == "bounding_rect") {
|
||||
this->eventSelectionShapeMode = QGraphicsPixmapItem::BoundingRectShape;
|
||||
}
|
||||
} else if (key == "shown_in_game_reload_message") {
|
||||
this->shownInGameReloadMessage = toBool(value);
|
||||
} else if (key == "grid_width") {
|
||||
this->gridSettings.width = toUInt(value);
|
||||
} else if (key == "grid_height") {
|
||||
this->gridSettings.height = toUInt(value);
|
||||
} else if (key == "grid_x") {
|
||||
this->gridSettings.offsetX = toInt(value, 0, 999);
|
||||
} else if (key == "grid_y") {
|
||||
this->gridSettings.offsetY = toInt(value, 0, 999);
|
||||
} else if (key == "grid_style") {
|
||||
this->gridSettings.style = GridSettings::getStyleFromName(value);
|
||||
} else if (key == "grid_color") {
|
||||
this->gridSettings.color = toColor(value);
|
||||
} else if (key == "status_bar_log_types") {
|
||||
this->statusBarLogTypes.clear();
|
||||
auto typeStrings = value.split(",", Qt::SkipEmptyParts);
|
||||
for (const auto &typeString : typeStrings) {
|
||||
LogType type = static_cast<LogType>(toInt(typeString, 0, 2));
|
||||
this->statusBarLogTypes.insert(type);
|
||||
}
|
||||
} else if (key == "application_font") {
|
||||
this->applicationFont = QFont();
|
||||
this->applicationFont.fromString(value);
|
||||
} else if (key == "map_list_font") {
|
||||
this->mapListFont = QFont();
|
||||
this->mapListFont.fromString(value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProjectConfig::parseLegacyKeyValue(const QString &key, const QString &value_) {
|
||||
QString value(value_);
|
||||
if (key == "base_game_version") {
|
||||
this->baseGameVersion = BaseGame::stringToVersion(value);
|
||||
} else if (key == "use_poryscript") {
|
||||
this->usePoryScript = toBool(value);
|
||||
} else if (key == "use_custom_border_size") {
|
||||
this->useCustomBorderSize = toBool(value);
|
||||
} else if (key == "enable_event_weather_trigger") {
|
||||
this->eventWeatherTriggerEnabled = toBool(value);
|
||||
} else if (key == "enable_event_secret_base") {
|
||||
this->eventSecretBaseEnabled = toBool(value);
|
||||
} else if (key == "enable_hidden_item_quantity") {
|
||||
this->hiddenItemQuantityEnabled = toBool(value);
|
||||
} else if (key == "enable_hidden_item_requires_itemfinder") {
|
||||
this->hiddenItemRequiresItemfinderEnabled = toBool(value);
|
||||
} else if (key == "enable_heal_location_respawn_data") {
|
||||
this->healLocationRespawnDataEnabled = toBool(value);
|
||||
} else if (key == "enable_event_clone_object" || key == "enable_object_event_in_connection") {
|
||||
this->eventCloneObjectEnabled = toBool(value);
|
||||
} else if (key == "enable_floor_number") {
|
||||
this->floorNumberEnabled = toBool(value);
|
||||
} else if (key == "create_map_text_file") {
|
||||
this->createMapTextFileEnabled = toBool(value);
|
||||
} else if (key == "enable_triple_layer_metatiles") {
|
||||
this->tripleLayerMetatilesEnabled = toBool(value);
|
||||
} else if (key == "default_metatile") {
|
||||
this->defaultMetatileId = toUInt(value, 0, Block::MaxValue);
|
||||
} else if (key == "default_elevation") {
|
||||
this->defaultElevation = toUInt(value, 0, Block::MaxValue);
|
||||
} else if (key == "default_collision") {
|
||||
this->defaultCollision = toUInt(value, 0, Block::MaxValue);
|
||||
} else if (key == "default_map_width") {
|
||||
this->defaultMapSize.setWidth(toInt(value, 1));
|
||||
} else if (key == "default_map_height") {
|
||||
this->defaultMapSize.setHeight(toInt(value, 1));
|
||||
} else if (key == "new_map_border_metatiles") {
|
||||
this->newMapBorderMetatileIds.clear();
|
||||
QList<QString> metatileIds = value.split(",");
|
||||
for (int i = 0; i < metatileIds.size(); i++) {
|
||||
int metatileId = toUInt(metatileIds.at(i), 0, Block::MaxValue);
|
||||
this->newMapBorderMetatileIds.append(metatileId);
|
||||
}
|
||||
} else if (key == "default_primary_tileset") {
|
||||
this->defaultPrimaryTileset = value;
|
||||
} else if (key == "default_secondary_tileset") {
|
||||
this->defaultSecondaryTileset = value;
|
||||
} else if (key == "metatile_attributes_size") {
|
||||
int size = toInt(value, 1, 4, 2);
|
||||
if (!(size & (size - 1))) this->metatileAttributesSize = size;
|
||||
} else if (key == "metatile_behavior_mask") {
|
||||
this->metatileBehaviorMask = toUInt(value);
|
||||
} else if (key == "metatile_terrain_type_mask") {
|
||||
this->metatileTerrainTypeMask = toUInt(value);
|
||||
} else if (key == "metatile_encounter_type_mask") {
|
||||
this->metatileEncounterTypeMask = toUInt(value);
|
||||
} else if (key == "metatile_layer_type_mask") {
|
||||
this->metatileLayerTypeMask = toUInt(value);
|
||||
} else if (key == "block_metatile_id_mask") {
|
||||
this->blockMetatileIdMask = toUInt(value, 0, Block::MaxValue);
|
||||
} else if (key == "block_collision_mask") {
|
||||
this->blockCollisionMask = toUInt(value, 0, Block::MaxValue);
|
||||
} else if (key == "block_elevation_mask") {
|
||||
this->blockElevationMask = toUInt(value, 0, Block::MaxValue);
|
||||
} else if (key == "unused_tile_normal") {
|
||||
this->unusedTileNormal = toUInt(value, 0, Tile::MaxValue);
|
||||
} else if (key == "unused_tile_covered") {
|
||||
this->unusedTileCovered = toUInt(value, 0, Tile::MaxValue);
|
||||
} else if (key == "unused_tile_split") {
|
||||
this->unusedTileSplit = toUInt(value, 0, Tile::MaxValue);
|
||||
} else if (key == "enable_map_allow_flags") {
|
||||
this->mapAllowFlagsEnabled = toBool(value);
|
||||
} else if (key.startsWith("path/")) {
|
||||
auto k = reverseDefaultPaths(key.mid(QStringLiteral("path/").length()));
|
||||
if (k != static_cast<ProjectFilePath>(-1)) {
|
||||
this->setFilePath(k, value);
|
||||
}
|
||||
} else if (key.startsWith("ident/")) {
|
||||
auto identifierId = reverseDefaultIdentifier(key.mid(QStringLiteral("ident/").length()));
|
||||
if (identifierId != static_cast<ProjectIdentifier>(-1)) {
|
||||
this->setIdentifier(identifierId, value);
|
||||
}
|
||||
} else if (key.startsWith("global_constant/")) {
|
||||
this->globalConstants.insert(key.mid(QStringLiteral("global_constant/").length()), value);
|
||||
} else if (key == "global_constants_filepaths") {
|
||||
this->globalConstantsFilepaths = value.split(",", Qt::SkipEmptyParts);
|
||||
} else if (key == "tilesets_have_callback") {
|
||||
this->tilesetsHaveCallback = toBool(value);
|
||||
} else if (key == "tilesets_have_is_compressed") {
|
||||
this->tilesetsHaveIsCompressed = toBool(value);
|
||||
} else if (key == "set_transparent_pixels_black") { // Old setting replaced by transparency_color
|
||||
this->transparencyColor = toBool(value) ? QColor(Qt::black) : QColor();
|
||||
} else if (key == "transparency_color") {
|
||||
this->transparencyColor = toColor(value);
|
||||
} else if (key == "preserve_matching_only_data") {
|
||||
this->preserveMatchingOnlyData = toBool(value);
|
||||
} else if (key == "event_icon_path_object") {
|
||||
this->eventIconPaths[Event::Group::Object] = value;
|
||||
} else if (key == "event_icon_path_warp") {
|
||||
this->eventIconPaths[Event::Group::Warp] = value;
|
||||
} else if (key == "event_icon_path_coord") {
|
||||
this->eventIconPaths[Event::Group::Coord] = value;
|
||||
} else if (key == "event_icon_path_bg") {
|
||||
this->eventIconPaths[Event::Group::Bg] = value;
|
||||
} else if (key == "event_icon_path_heal") {
|
||||
this->eventIconPaths[Event::Group::Heal] = value;
|
||||
} else if (key.startsWith("pokemon_icon_path/")) {
|
||||
this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()), value);
|
||||
} else if (key == "events_tab_icon_path") {
|
||||
this->eventsTabIconPath = value;
|
||||
} else if (key == "collision_sheet_path") {
|
||||
this->collisionSheetPath = value;
|
||||
} else if (key == "collision_sheet_width") {
|
||||
this->collisionSheetSize.setWidth(toInt(value, 1, Block::MaxValue));
|
||||
} else if (key == "collision_sheet_height") {
|
||||
this->collisionSheetSize.setHeight(toInt(value, 1, Block::MaxValue));
|
||||
} else if (key == "player_view_north") {
|
||||
this->playerViewDistance.setTop(toInt(value, 0, INT_MAX, GBA_V_DIST_TO_CENTER));
|
||||
} else if (key == "player_view_south") {
|
||||
this->playerViewDistance.setBottom(toInt(value, 0, INT_MAX, GBA_V_DIST_TO_CENTER));
|
||||
} else if (key == "player_view_west") {
|
||||
this->playerViewDistance.setLeft(toInt(value, 0, INT_MAX, GBA_H_DIST_TO_CENTER));
|
||||
} else if (key == "player_view_east") {
|
||||
this->playerViewDistance.setRight(toInt(value, 0, INT_MAX, GBA_H_DIST_TO_CENTER));
|
||||
} else if (key == "warp_behaviors") {
|
||||
this->warpBehaviors.clear();
|
||||
value.remove(" ");
|
||||
const QStringList behaviorList = value.split(",", Qt::SkipEmptyParts);
|
||||
for (auto s : behaviorList)
|
||||
this->warpBehaviors.insert(toUInt(s));
|
||||
} else if (key == "max_events_per_group") {
|
||||
this->maxEventsPerGroup = toInt(value, 1, INT_MAX, 255);
|
||||
} else if (key == "metatile_selector_width") {
|
||||
this->metatileSelectorWidth = toInt(value, 1, INT_MAX, 8);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Read input from the config to get the script paths and whether each is enabled or disbled.
|
||||
// The format is a comma-separated list of paths. Each path can be followed (before the comma)
|
||||
// by a :0 or :1 to indicate whether it should be disabled or enabled, respectively. If neither
|
||||
// follow, it's assumed the script should be enabled.
|
||||
QList<ScriptSettings> parseCustomScripts(const QString &input) {
|
||||
QMap<QString,bool> customScripts;
|
||||
const QList<QString> paths = input.split(",", Qt::SkipEmptyParts);
|
||||
for (QString path : paths) {
|
||||
// Read and remove suffix
|
||||
bool enabled = !path.endsWith(":0");
|
||||
if (!enabled || path.endsWith(":1"))
|
||||
path.chop(2);
|
||||
|
||||
if (!path.isEmpty()) {
|
||||
// If a path is repeated only its last instance will be considered.
|
||||
customScripts.insert(path, enabled);
|
||||
}
|
||||
}
|
||||
QList<ScriptSettings> settingsList;
|
||||
for (auto it = customScripts.begin(); it != customScripts.end(); it++) {
|
||||
settingsList.append({
|
||||
.path = it.key(),
|
||||
.enabled = it.value(),
|
||||
.userOnly = true,
|
||||
});
|
||||
}
|
||||
return settingsList;
|
||||
}
|
||||
|
||||
bool UserConfig::parseLegacyKeyValue(const QString &key, const QString &value) {
|
||||
if (key == "recent_map_or_layout") {
|
||||
this->recentMapOrLayout = value;
|
||||
} else if (key == "use_encounter_json") {
|
||||
this->useEncounterJson = toBool(value);
|
||||
} else if (key == "custom_scripts") {
|
||||
this->customScripts = parseCustomScripts(value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShortcutsConfig::parseLegacyKeyValue(const QString &key, const QString &value) {
|
||||
QStringList keySequences = value.split(' ');
|
||||
for (auto keySequence : keySequences)
|
||||
user_shortcuts.insert(key, keySequence);
|
||||
return true;
|
||||
}
|
||||
106
src/config/porymapconfig.cpp
Normal file
106
src/config/porymapconfig.cpp
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#include "porymapconfig.h"
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QSplitter>
|
||||
|
||||
PorymapConfig porymapConfig;
|
||||
|
||||
bool PorymapConfig::save() {
|
||||
// Clean out old rate limit times, leaving only times still in the future.
|
||||
for (auto it = this->rateLimitTimes.begin(); it != this->rateLimitTimes.end();) {
|
||||
const QDateTime time = it.value();
|
||||
if (!time.isNull() && time > QDateTime::currentDateTime()) {
|
||||
it = this->rateLimitTimes.erase(it);
|
||||
} else it++;
|
||||
}
|
||||
|
||||
return KeyValueConfigBase::save();
|
||||
}
|
||||
|
||||
void PorymapConfig::loadFromJson(const QJsonObject& obj) {
|
||||
KeyValueConfigBase::loadFromJson(obj);
|
||||
|
||||
// Reset geometry between major/minor versions.
|
||||
// We could try to keep separate versions for each geometry,
|
||||
// but that requires a lot of careful maintenance.
|
||||
// This ensures that as widgets change they won't
|
||||
// receive data for old layouts/states, and that as widgets
|
||||
// get renamed their old keys wont accumulate in the config.
|
||||
constexpr int CurrentGeometryVersion = 1;
|
||||
if (this->geometryVersion != CurrentGeometryVersion) {
|
||||
this->geometryVersion = CurrentGeometryVersion;
|
||||
this->savedGeometryMap.clear();
|
||||
}
|
||||
|
||||
this->gridSettings.offsetX = std::clamp(this->gridSettings.offsetX, 0, 999);
|
||||
this->gridSettings.offsetY = std::clamp(this->gridSettings.offsetY, 0, 999);
|
||||
}
|
||||
|
||||
QJsonObject PorymapConfig::getDefaultJson() const {
|
||||
PorymapConfig defaultConfig;
|
||||
return defaultConfig.toJson();
|
||||
}
|
||||
|
||||
void PorymapConfig::addRecentProject(const QString& project) {
|
||||
this->recentProjects.removeOne(project);
|
||||
this->recentProjects.prepend(project);
|
||||
}
|
||||
|
||||
void PorymapConfig::setRecentProjects(const QStringList& projects) {
|
||||
this->recentProjects = projects;
|
||||
}
|
||||
|
||||
QString PorymapConfig::getRecentProject() const {
|
||||
return this->recentProjects.value(0);
|
||||
}
|
||||
|
||||
const QStringList& PorymapConfig::getRecentProjects() const {
|
||||
return this->recentProjects;
|
||||
}
|
||||
|
||||
void PorymapConfig::saveGeometry(const QWidget* widget, const QString& keyPrefix, bool recursive) {
|
||||
if (!widget || widget->objectName().isEmpty()) return;
|
||||
|
||||
const QString key = keyPrefix + widget->objectName();
|
||||
this->savedGeometryMap.insert(key, widget->saveGeometry());
|
||||
|
||||
// In addition to geometry, some widgets have other states that can be saved/restored.
|
||||
const QString stateKey = key + QStringLiteral("/State");
|
||||
auto mainWindow = qobject_cast<const QMainWindow*>(widget);
|
||||
if (mainWindow) this->savedGeometryMap.insert(stateKey, mainWindow->saveState());
|
||||
else {
|
||||
auto splitter = qobject_cast<const QSplitter*>(widget);
|
||||
if (splitter) this->savedGeometryMap.insert(stateKey, splitter->saveState());
|
||||
}
|
||||
if (recursive) {
|
||||
for (const auto splitter : widget->findChildren<QSplitter*>()) {
|
||||
saveGeometry(splitter, key + "_", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PorymapConfig::restoreGeometry(QWidget* widget, const QString& keyPrefix, bool recursive) const {
|
||||
if (!widget || widget->objectName().isEmpty()) return false;
|
||||
|
||||
const QString key = keyPrefix + widget->objectName();
|
||||
auto it = this->savedGeometryMap.constFind(key);
|
||||
if (it == this->savedGeometryMap.constEnd()) return false;
|
||||
widget->restoreGeometry(it.value());
|
||||
|
||||
// In addition to geometry, some widgets have other states that can be saved/restored.
|
||||
it = this->savedGeometryMap.constFind(key + QStringLiteral("/State"));
|
||||
if (it != this->savedGeometryMap.constEnd()) {
|
||||
auto mainWindow = qobject_cast<QMainWindow*>(widget);
|
||||
if (mainWindow) mainWindow->restoreState(it.value());
|
||||
else {
|
||||
auto splitter = qobject_cast<QSplitter*>(widget);
|
||||
if (splitter) splitter->restoreState(it.value());
|
||||
}
|
||||
}
|
||||
if (recursive) {
|
||||
for (const auto splitter : widget->findChildren<QSplitter*>()) {
|
||||
restoreGeometry(splitter, key + "_", false);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
368
src/config/projectconfig.cpp
Normal file
368
src/config/projectconfig.cpp
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
#include "projectconfig.h"
|
||||
#include "utility.h"
|
||||
#include "validator.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDir>
|
||||
#include <QFormLayout>
|
||||
#include <QLabel>
|
||||
|
||||
// TODO: This should eventually be contained by each individual Project instance.
|
||||
ProjectConfig projectConfig;
|
||||
|
||||
const QMap<ProjectIdentifier, QPair<QString, QString>> ProjectConfig::defaultIdentifiers = {
|
||||
// Symbols
|
||||
{ProjectIdentifier::symbol_facing_directions, {"symbol_facing_directions", "gInitialMovementTypeFacingDirections"}},
|
||||
{ProjectIdentifier::symbol_obj_event_gfx_pointers, {"symbol_obj_event_gfx_pointers", "gObjectEventGraphicsInfoPointers"}},
|
||||
{ProjectIdentifier::symbol_pokemon_icon_table, {"symbol_pokemon_icon_table", "gMonIconTable"}},
|
||||
{ProjectIdentifier::symbol_attribute_table, {"symbol_attribute_table", "sMetatileAttrMasks"}},
|
||||
{ProjectIdentifier::symbol_tilesets_prefix, {"symbol_tilesets_prefix", "gTileset_"}},
|
||||
{ProjectIdentifier::symbol_dynamic_map_name, {"symbol_dynamic_map_name", "Dynamic"}},
|
||||
// Defines
|
||||
{ProjectIdentifier::define_obj_event_count, {"define_obj_event_count", "OBJECT_EVENT_TEMPLATES_COUNT"}},
|
||||
{ProjectIdentifier::define_min_level, {"define_min_level", "MIN_LEVEL"}},
|
||||
{ProjectIdentifier::define_max_level, {"define_max_level", "MAX_LEVEL"}},
|
||||
{ProjectIdentifier::define_max_encounter_rate, {"define_max_encounter_rate", "MAX_ENCOUNTER_RATE"}},
|
||||
{ProjectIdentifier::define_tiles_primary, {"define_tiles_primary", "NUM_TILES_IN_PRIMARY"}},
|
||||
{ProjectIdentifier::define_tiles_total, {"define_tiles_total", "NUM_TILES_TOTAL"}},
|
||||
{ProjectIdentifier::define_metatiles_primary, {"define_metatiles_primary", "NUM_METATILES_IN_PRIMARY"}},
|
||||
{ProjectIdentifier::define_pals_primary, {"define_pals_primary", "NUM_PALS_IN_PRIMARY"}},
|
||||
{ProjectIdentifier::define_pals_total, {"define_pals_total", "NUM_PALS_TOTAL"}},
|
||||
{ProjectIdentifier::define_tiles_per_metatile, {"define_tiles_per_metatile", "NUM_TILES_PER_METATILE"}},
|
||||
{ProjectIdentifier::define_map_size, {"define_map_size", "MAX_MAP_DATA_SIZE"}},
|
||||
{ProjectIdentifier::define_map_offset_width, {"define_map_offset_width", "MAP_OFFSET_W"}},
|
||||
{ProjectIdentifier::define_map_offset_height, {"define_map_offset_height", "MAP_OFFSET_H"}},
|
||||
{ProjectIdentifier::define_mask_metatile, {"define_mask_metatile", "MAPGRID_METATILE_ID_MASK"}},
|
||||
{ProjectIdentifier::define_mask_collision, {"define_mask_collision", "MAPGRID_COLLISION_MASK"}},
|
||||
{ProjectIdentifier::define_mask_elevation, {"define_mask_elevation", "MAPGRID_ELEVATION_MASK"}},
|
||||
{ProjectIdentifier::define_mask_behavior, {"define_mask_behavior", "METATILE_ATTR_BEHAVIOR_MASK"}},
|
||||
{ProjectIdentifier::define_mask_layer, {"define_mask_layer", "METATILE_ATTR_LAYER_MASK"}},
|
||||
{ProjectIdentifier::define_attribute_behavior, {"define_attribute_behavior", "METATILE_ATTRIBUTE_BEHAVIOR"}},
|
||||
{ProjectIdentifier::define_attribute_layer, {"define_attribute_layer", "METATILE_ATTRIBUTE_LAYER_TYPE"}},
|
||||
{ProjectIdentifier::define_attribute_terrain, {"define_attribute_terrain", "METATILE_ATTRIBUTE_TERRAIN"}},
|
||||
{ProjectIdentifier::define_attribute_encounter, {"define_attribute_encounter", "METATILE_ATTRIBUTE_ENCOUNTER_TYPE"}},
|
||||
{ProjectIdentifier::define_metatile_label_prefix, {"define_metatile_label_prefix", "METATILE_"}},
|
||||
{ProjectIdentifier::define_heal_locations_prefix, {"define_heal_locations_prefix", "HEAL_LOCATION_"}},
|
||||
{ProjectIdentifier::define_layout_prefix, {"define_layout_prefix", "LAYOUT_"}},
|
||||
{ProjectIdentifier::define_map_prefix, {"define_map_prefix", "MAP_"}},
|
||||
{ProjectIdentifier::define_map_dynamic, {"define_map_dynamic", "MAP_DYNAMIC"}},
|
||||
{ProjectIdentifier::define_map_empty, {"define_map_empty", "MAP_UNDEFINED"}},
|
||||
{ProjectIdentifier::define_map_section_prefix, {"define_map_section_prefix", "MAPSEC_"}},
|
||||
{ProjectIdentifier::define_map_section_empty, {"define_map_section_empty", "NONE"}},
|
||||
{ProjectIdentifier::define_species_prefix, {"define_species_prefix", "SPECIES_"}},
|
||||
{ProjectIdentifier::define_species_empty, {"define_species_empty", "NONE"}},
|
||||
// Regex
|
||||
{ProjectIdentifier::regex_behaviors, {"regex_behaviors", "\\bMB_"}},
|
||||
{ProjectIdentifier::regex_obj_event_gfx, {"regex_obj_event_gfx", "\\bOBJ_EVENT_GFX_"}},
|
||||
{ProjectIdentifier::regex_items, {"regex_items", "\\bITEM_(?!(B_)?USE_)"}}, // Exclude ITEM_USE_ and ITEM_B_USE_ constants
|
||||
{ProjectIdentifier::regex_flags, {"regex_flags", "\\bFLAG_"}},
|
||||
{ProjectIdentifier::regex_vars, {"regex_vars", "\\bVAR_"}},
|
||||
{ProjectIdentifier::regex_movement_types, {"regex_movement_types", "\\bMOVEMENT_TYPE_"}},
|
||||
{ProjectIdentifier::regex_map_types, {"regex_map_types", "\\bMAP_TYPE_"}},
|
||||
{ProjectIdentifier::regex_battle_scenes, {"regex_battle_scenes", "\\bMAP_BATTLE_SCENE_"}},
|
||||
{ProjectIdentifier::regex_weather, {"regex_weather", "\\bWEATHER_"}},
|
||||
{ProjectIdentifier::regex_coord_event_weather, {"regex_coord_event_weather", "\\bCOORD_EVENT_WEATHER_"}},
|
||||
{ProjectIdentifier::regex_secret_bases, {"regex_secret_bases", "\\bSECRET_BASE_[\\w]+_[\\d]+"}},
|
||||
{ProjectIdentifier::regex_sign_facing_directions, {"regex_sign_facing_directions", "\\bBG_EVENT_PLAYER_FACING_"}},
|
||||
{ProjectIdentifier::regex_trainer_types, {"regex_trainer_types", "\\bTRAINER_TYPE_"}},
|
||||
{ProjectIdentifier::regex_music, {"regex_music", "\\b(SE|MUS)_"}},
|
||||
{ProjectIdentifier::regex_encounter_types, {"regex_encounter_types", "\\bTILE_ENCOUNTER_"}},
|
||||
{ProjectIdentifier::regex_terrain_types, {"regex_terrain_types", "\\bTILE_TERRAIN_"}},
|
||||
// Other
|
||||
{ProjectIdentifier::pals_output_extension, {"pals_output_extension", ".gbapal"}},
|
||||
{ProjectIdentifier::tiles_output_extension, {"tiles_output_extension", ".4bpp.lz"}},
|
||||
};
|
||||
|
||||
const QMap<ProjectFilePath, QPair<QString, QString>> ProjectConfig::defaultPaths = {
|
||||
{ProjectFilePath::data_map_folders, { "data_map_folders", "data/maps/"}},
|
||||
{ProjectFilePath::data_scripts_folders, { "data_scripts_folders", "data/scripts/"}},
|
||||
{ProjectFilePath::data_layouts_folders, { "data_layouts_folders", "data/layouts/"}},
|
||||
{ProjectFilePath::data_primary_tilesets_folders, { "data_primary_tilesets_folders", "data/tilesets/primary/"}},
|
||||
{ProjectFilePath::data_secondary_tilesets_folders, { "data_secondary_tilesets_folders", "data/tilesets/secondary/"}},
|
||||
{ProjectFilePath::data_event_scripts, { "data_event_scripts", "data/event_scripts.s"}},
|
||||
{ProjectFilePath::json_map_groups, { "json_map_groups", "data/maps/map_groups.json"}},
|
||||
{ProjectFilePath::json_layouts, { "json_layouts", "data/layouts/layouts.json"}},
|
||||
{ProjectFilePath::json_wild_encounters, { "json_wild_encounters", "src/data/wild_encounters.json"}},
|
||||
{ProjectFilePath::json_heal_locations, { "json_heal_locations", "src/data/heal_locations.json"}},
|
||||
{ProjectFilePath::json_region_map_entries, { "json_region_map_entries", "src/data/region_map/region_map_sections.json"}},
|
||||
{ProjectFilePath::json_region_porymap_cfg, { "json_region_porymap_cfg", "src/data/region_map/porymap_config.json"}},
|
||||
{ProjectFilePath::tilesets_headers, { "tilesets_headers", "src/data/tilesets/headers.h"}},
|
||||
{ProjectFilePath::tilesets_graphics, { "tilesets_graphics", "src/data/tilesets/graphics.h"}},
|
||||
{ProjectFilePath::tilesets_metatiles, { "tilesets_metatiles", "src/data/tilesets/metatiles.h"}},
|
||||
{ProjectFilePath::tilesets_headers_asm, { "tilesets_headers_asm", "data/tilesets/headers.inc"}},
|
||||
{ProjectFilePath::tilesets_graphics_asm, { "tilesets_graphics_asm", "data/tilesets/graphics.inc"}},
|
||||
{ProjectFilePath::tilesets_metatiles_asm, { "tilesets_metatiles_asm", "data/tilesets/metatiles.inc"}},
|
||||
{ProjectFilePath::data_obj_event_gfx_pointers, { "data_obj_event_gfx_pointers", "src/data/object_events/object_event_graphics_info_pointers.h"}},
|
||||
{ProjectFilePath::data_obj_event_gfx_info, { "data_obj_event_gfx_info", "src/data/object_events/object_event_graphics_info.h"}},
|
||||
{ProjectFilePath::data_obj_event_pic_tables, { "data_obj_event_pic_tables", "src/data/object_events/object_event_pic_tables.h"}},
|
||||
{ProjectFilePath::data_obj_event_gfx, { "data_obj_event_gfx", "src/data/object_events/object_event_graphics.h"}},
|
||||
{ProjectFilePath::data_pokemon_gfx, { "data_pokemon_gfx", "src/data/graphics/pokemon.h"}},
|
||||
{ProjectFilePath::constants_global, { "constants_global", "include/constants/global.h"}},
|
||||
{ProjectFilePath::constants_items, { "constants_items", "include/constants/items.h"}},
|
||||
{ProjectFilePath::constants_flags, { "constants_flags", "include/constants/flags.h"}},
|
||||
{ProjectFilePath::constants_vars, { "constants_vars", "include/constants/vars.h"}},
|
||||
{ProjectFilePath::constants_weather, { "constants_weather", "include/constants/weather.h"}},
|
||||
{ProjectFilePath::constants_songs, { "constants_songs", "include/constants/songs.h"}},
|
||||
{ProjectFilePath::constants_pokemon, { "constants_pokemon", "include/constants/pokemon.h"}},
|
||||
{ProjectFilePath::constants_map_types, { "constants_map_types", "include/constants/map_types.h"}},
|
||||
{ProjectFilePath::constants_trainer_types, { "constants_trainer_types", "include/constants/trainer_types.h"}},
|
||||
{ProjectFilePath::constants_secret_bases, { "constants_secret_bases", "include/constants/secret_bases.h"}},
|
||||
{ProjectFilePath::constants_obj_event_movement, { "constants_obj_event_movement", "include/constants/event_object_movement.h"}},
|
||||
{ProjectFilePath::constants_obj_events, { "constants_obj_events", "include/constants/event_objects.h"}},
|
||||
{ProjectFilePath::constants_event_bg, { "constants_event_bg", "include/constants/event_bg.h"}},
|
||||
{ProjectFilePath::constants_metatile_labels, { "constants_metatile_labels", "include/constants/metatile_labels.h"}},
|
||||
{ProjectFilePath::constants_metatile_behaviors, { "constants_metatile_behaviors", "include/constants/metatile_behaviors.h"}},
|
||||
{ProjectFilePath::constants_species, { "constants_species", "include/constants/species.h"}},
|
||||
{ProjectFilePath::constants_fieldmap, { "constants_fieldmap", "include/fieldmap.h"}},
|
||||
{ProjectFilePath::global_fieldmap, { "global_fieldmap", "include/global.fieldmap.h"}},
|
||||
{ProjectFilePath::fieldmap, { "fieldmap", "src/fieldmap.c"}},
|
||||
{ProjectFilePath::pokemon_icon_table, { "pokemon_icon_table", "src/pokemon_icon.c"}},
|
||||
{ProjectFilePath::initial_facing_table, { "initial_facing_table", "src/event_object_movement.c"}},
|
||||
{ProjectFilePath::wild_encounter, { "wild_encounter", "src/wild_encounter.c"}},
|
||||
{ProjectFilePath::pokemon_gfx, { "pokemon_gfx", "graphics/pokemon/"}},
|
||||
};
|
||||
|
||||
ProjectIdentifier ProjectConfig::reverseDefaultIdentifier(const QString& str) {
|
||||
for (auto i = defaultIdentifiers.cbegin(), end = defaultIdentifiers.cend(); i != end; i++) {
|
||||
if (i.value().first == str) return i.key();
|
||||
}
|
||||
return static_cast<ProjectIdentifier>(-1);
|
||||
}
|
||||
|
||||
ProjectFilePath ProjectConfig::reverseDefaultPaths(const QString& str) {
|
||||
for (auto it = defaultPaths.constKeyValueBegin(); it != defaultPaths.constKeyValueEnd(); ++it) {
|
||||
if ((*it).second.first == str) return (*it).first;
|
||||
}
|
||||
return static_cast<ProjectFilePath>(-1);
|
||||
}
|
||||
|
||||
|
||||
void ProjectConfig::setVersionSpecificDefaults(BaseGame::Version version) {
|
||||
this->baseGameVersion = version;
|
||||
if (this->baseGameVersion == BaseGame::Version::none) return;
|
||||
|
||||
this->metatileAttributesSize = Metatile::getDefaultAttributesSize(this->baseGameVersion);
|
||||
this->metatileBehaviorMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::Behavior);
|
||||
this->metatileTerrainTypeMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::TerrainType);
|
||||
this->metatileEncounterTypeMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::EncounterType);
|
||||
this->metatileLayerTypeMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::LayerType);
|
||||
|
||||
if (this->baseGameVersion == BaseGame::Version::pokefirered) {
|
||||
this->useCustomBorderSize = true;
|
||||
this->eventWeatherTriggerEnabled = false;
|
||||
this->eventSecretBaseEnabled = false;
|
||||
this->hiddenItemQuantityEnabled = true;
|
||||
this->hiddenItemRequiresItemfinderEnabled = true;
|
||||
this->healLocationRespawnDataEnabled = true;
|
||||
this->eventCloneObjectEnabled = true;
|
||||
this->floorNumberEnabled = true;
|
||||
this->createMapTextFileEnabled = true;
|
||||
this->newMapBorderMetatileIds = {0x14, 0x15, 0x1C, 0x1D};
|
||||
this->defaultSecondaryTileset = QStringLiteral("gTileset_PalletTown");
|
||||
this->warpBehaviors = {
|
||||
0x60, // MB_CAVE_DOOR
|
||||
0x61, // MB_LADDER
|
||||
0x62, // MB_EAST_ARROW_WARP
|
||||
0x63, // MB_WEST_ARROW_WARP
|
||||
0x64, // MB_NORTH_ARROW_WARP
|
||||
0x65, // MB_SOUTH_ARROW_WARP
|
||||
0x66, // MB_FALL_WARP
|
||||
0x67, // MB_REGULAR_WARP
|
||||
0x68, // MB_LAVARIDGE_1F_WARP
|
||||
0x69, // MB_WARP_DOOR
|
||||
0x6A, // MB_UP_ESCALATOR
|
||||
0x6B, // MB_DOWN_ESCALATOR
|
||||
0x6C, // MB_UP_RIGHT_STAIR_WARP
|
||||
0x6D, // MB_UP_LEFT_STAIR_WARP
|
||||
0x6E, // MB_DOWN_RIGHT_STAIR_WARP
|
||||
0x6F, // MB_DOWN_LEFT_STAIR_WARP
|
||||
0x71, // MB_UNION_ROOM_WARP
|
||||
};
|
||||
} else { // pokeemerald / pokeruby
|
||||
this->useCustomBorderSize = false;
|
||||
this->eventWeatherTriggerEnabled = true;
|
||||
this->eventSecretBaseEnabled = true;
|
||||
this->hiddenItemQuantityEnabled = false;
|
||||
this->hiddenItemRequiresItemfinderEnabled = false;
|
||||
this->healLocationRespawnDataEnabled = false;
|
||||
this->eventCloneObjectEnabled = false;
|
||||
this->floorNumberEnabled = false;
|
||||
this->createMapTextFileEnabled = false;
|
||||
this->newMapBorderMetatileIds = {0x1D4, 0x1D5, 0x1DC, 0x1DD};
|
||||
this->defaultSecondaryTileset = QStringLiteral("gTileset_Petalburg");
|
||||
this->warpBehaviors = {
|
||||
0x0E, // MB_MOSSDEEP_GYM_WARP
|
||||
0x0F, // MB_MT_PYRE_HOLE
|
||||
0x1B, // MB_STAIRS_OUTSIDE_ABANDONED_SHIP
|
||||
0x1C, // MB_SHOAL_CAVE_ENTRANCE
|
||||
0x29, // MB_LAVARIDGE_GYM_B1F_WARP
|
||||
0x60, // MB_NON_ANIMATED_DOOR
|
||||
0x61, // MB_LADDER
|
||||
0x62, // MB_EAST_ARROW_WARP
|
||||
0x63, // MB_WEST_ARROW_WARP
|
||||
0x64, // MB_NORTH_ARROW_WARP
|
||||
0x65, // MB_SOUTH_ARROW_WARP
|
||||
0x67, // MB_AQUA_HIDEOUT_WARP
|
||||
0x68, // MB_LAVARIDGE_GYM_1F_WARP
|
||||
0x69, // MB_ANIMATED_DOOR
|
||||
0x6A, // MB_UP_ESCALATOR
|
||||
0x6B, // MB_DOWN_ESCALATOR
|
||||
0x6C, // MB_WATER_DOOR
|
||||
0x6D, // MB_WATER_SOUTH_ARROW_WARP
|
||||
0x6E, // MB_DEEP_SOUTH_WARP
|
||||
0x70, // MB_UNION_ROOM_WARP
|
||||
0x8D, // MB_PETALBURG_GYM_DOOR
|
||||
0x91, // MB_SECRET_BASE_SPOT_RED_CAVE_OPEN
|
||||
0x93, // MB_SECRET_BASE_SPOT_BROWN_CAVE_OPEN
|
||||
0x95, // MB_SECRET_BASE_SPOT_YELLOW_CAVE_OPEN
|
||||
0x97, // MB_SECRET_BASE_SPOT_TREE_LEFT_OPEN
|
||||
0x99, // MB_SECRET_BASE_SPOT_SHRUB_OPEN
|
||||
0x9B, // MB_SECRET_BASE_SPOT_BLUE_CAVE_OPEN
|
||||
0x9D, // MB_SECRET_BASE_SPOT_TREE_RIGHT_OPEN
|
||||
};
|
||||
}
|
||||
this->mapAllowFlagsEnabled = (this->baseGameVersion != BaseGame::Version::pokeruby);
|
||||
}
|
||||
|
||||
bool ProjectConfig::save() {
|
||||
// Clean out empty paths
|
||||
Util::removeEmptyStrings(&this->globalConstants);
|
||||
Util::removeEmptyStrings(&this->eventIconPaths);
|
||||
Util::removeEmptyStrings(&this->pokemonIconPaths);
|
||||
|
||||
return KeyValueConfigBase::save();
|
||||
}
|
||||
|
||||
void ProjectConfig::loadFromJson(const QJsonObject& obj_) {
|
||||
QJsonObject obj(obj_);
|
||||
|
||||
// Parse the base game version before anything else so we can initialize the defaults.
|
||||
auto versionKey = QStringLiteral("base_game_version");
|
||||
const QJsonValue version = obj.take(versionKey);
|
||||
if (!version.isUndefined() && parseJsonKeyValue(versionKey, version)) {
|
||||
setVersionSpecificDefaults(this->baseGameVersion);
|
||||
}
|
||||
|
||||
KeyValueConfigBase::loadFromJson(obj);
|
||||
|
||||
// Enforce this setting for projectConfig's custom scripts
|
||||
for (auto& settings : this->customScripts) settings.userOnly = false;
|
||||
}
|
||||
|
||||
QJsonObject ProjectConfig::getDefaultJson() const {
|
||||
ProjectConfig defaultConfig(this->baseGameVersion);
|
||||
// The defaults are version-specific, make sure we always output non-empty versions.
|
||||
defaultConfig.baseGameVersion = BaseGame::Version::none;
|
||||
return defaultConfig.toJson();
|
||||
}
|
||||
|
||||
// TODO: Replace with a new prompt that allows choosing either the defaults for each version, or customizing settings.
|
||||
void ProjectConfig::initializeFromEmpty() {
|
||||
const QString dirName = QDir(projectDir()).dirName();
|
||||
BaseGame::Version version = BaseGame::stringToVersion(dirName);
|
||||
if (version != BaseGame::Version::none) {
|
||||
this->baseGameVersion = version;
|
||||
logInfo(QString("Auto-detected base_game_version as '%1'").arg(BaseGame::versionToString(version)));
|
||||
} else {
|
||||
QDialog dialog(nullptr, Qt::WindowTitleHint);
|
||||
dialog.setWindowTitle("Project Configuration");
|
||||
dialog.setWindowModality(Qt::NonModal);
|
||||
|
||||
QFormLayout form(&dialog);
|
||||
|
||||
auto comboBox = new QComboBox();
|
||||
comboBox->addItem(BaseGame::versionToString(BaseGame::Version::pokeruby), BaseGame::Version::pokeruby);
|
||||
comboBox->addItem(BaseGame::versionToString(BaseGame::Version::pokefirered), BaseGame::Version::pokefirered);
|
||||
comboBox->addItem(BaseGame::versionToString(BaseGame::Version::pokeemerald), BaseGame::Version::pokeemerald);
|
||||
form.addRow(new QLabel("Game Version"), comboBox);
|
||||
|
||||
QDialogButtonBox buttonBox(QDialogButtonBox::Ok, Qt::Horizontal, &dialog);
|
||||
QObject::connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
|
||||
form.addRow(&buttonBox);
|
||||
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
this->baseGameVersion = static_cast<BaseGame::Version>(comboBox->currentData().toInt());
|
||||
} else {
|
||||
logWarn(QString("No base_game_version selected, using default '%1'").arg(BaseGame::versionToString(this->baseGameVersion)));
|
||||
}
|
||||
}
|
||||
setVersionSpecificDefaults(this->baseGameVersion);
|
||||
}
|
||||
|
||||
void ProjectConfig::setFilePath(ProjectFilePath pathId, const QString& path) {
|
||||
if (!defaultPaths.contains(pathId)) return;
|
||||
if (path.isEmpty()) {
|
||||
this->filePaths.remove(pathId);
|
||||
} else {
|
||||
this->filePaths[pathId] = path;
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectConfig::setFilePath(const QString& pathId, const QString& path) {
|
||||
this->setFilePath(reverseDefaultPaths(pathId), path);
|
||||
}
|
||||
|
||||
QString ProjectConfig::getCustomFilePath(ProjectFilePath pathId) {
|
||||
return QDir::cleanPath(this->filePaths.value(pathId));
|
||||
}
|
||||
|
||||
QString ProjectConfig::getCustomFilePath(const QString& pathId) {
|
||||
return this->getCustomFilePath(reverseDefaultPaths(pathId));
|
||||
}
|
||||
|
||||
QString ProjectConfig::getFilePath(ProjectFilePath pathId) {
|
||||
QString customPath = this->getCustomFilePath(pathId);
|
||||
if (!customPath.isEmpty()) {
|
||||
// A custom filepath has been specified. If the file/folder exists, use that.
|
||||
const QString baseDir = this->projectDir() + "/";
|
||||
if (customPath.startsWith(baseDir)) {
|
||||
customPath.remove(0, baseDir.length());
|
||||
}
|
||||
if (QFileInfo::exists(QDir::cleanPath(baseDir + customPath))) {
|
||||
return customPath;
|
||||
} else {
|
||||
logError(QString("Custom project filepath '%1' not found. Using default.").arg(customPath));
|
||||
}
|
||||
}
|
||||
return defaultPaths.contains(pathId) ? defaultPaths[pathId].second : QString();
|
||||
}
|
||||
|
||||
void ProjectConfig::setIdentifier(ProjectIdentifier id, const QString& text) {
|
||||
if (!defaultIdentifiers.contains(id))
|
||||
return;
|
||||
|
||||
if (text.isEmpty()) {
|
||||
this->identifiers.remove(id);
|
||||
} else {
|
||||
const QString idName = defaultIdentifiers.value(id).first;
|
||||
if (idName.startsWith("define_") || idName.startsWith("symbol_")) {
|
||||
// Validate the input for the identifier, depending on the type.
|
||||
IdentifierValidator validator;
|
||||
if (!validator.isValid(text)) {
|
||||
logError(QString("The name '%1' for project identifier '%2' is invalid. It must only contain word characters, and cannot start with a digit.").arg(text).arg(idName));
|
||||
return;
|
||||
}
|
||||
}
|
||||
this->identifiers[id] = text;
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectConfig::setIdentifier(const QString& id, const QString& text) {
|
||||
this->setIdentifier(reverseDefaultIdentifier(id), text);
|
||||
}
|
||||
|
||||
QString ProjectConfig::getCustomIdentifier(ProjectIdentifier id) {
|
||||
return this->identifiers.value(id);
|
||||
}
|
||||
|
||||
QString ProjectConfig::getCustomIdentifier(const QString& id) {
|
||||
return this->getCustomIdentifier(reverseDefaultIdentifier(id));
|
||||
}
|
||||
|
||||
QString ProjectConfig::getIdentifier(ProjectIdentifier id) {
|
||||
const QString customText = this->getCustomIdentifier(id);
|
||||
if (!customText.isEmpty())
|
||||
return customText;
|
||||
return defaultIdentifiers.contains(id) ? defaultIdentifiers[id].second : QString();
|
||||
}
|
||||
110
src/config/shortcutsconfig.cpp
Normal file
110
src/config/shortcutsconfig.cpp
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#include "shortcutsconfig.h"
|
||||
#include "shortcut.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QRegularExpression>
|
||||
|
||||
ShortcutsConfig shortcutsConfig;
|
||||
|
||||
void ShortcutsConfig::loadFromJson(const QJsonObject& obj) {
|
||||
this->user_shortcuts = Converter<QMultiMap<QString, QKeySequence>>::fromJson(obj);
|
||||
}
|
||||
|
||||
QJsonObject ShortcutsConfig::toJson() {
|
||||
return Converter<QMultiMap<QString, QKeySequence>>::toJson(this->user_shortcuts);
|
||||
}
|
||||
|
||||
QJsonObject ShortcutsConfig::getDefaultJson() const {
|
||||
return Converter<QMultiMap<QString, QKeySequence>>::toJson(this->default_shortcuts);
|
||||
}
|
||||
|
||||
void ShortcutsConfig::setDefaultShortcuts(const QObjectList& objects) {
|
||||
storeShortcutsFromList(StoreType::Default, objects);
|
||||
}
|
||||
|
||||
QList<QKeySequence> ShortcutsConfig::defaultShortcuts(const QObject *object) const {
|
||||
return default_shortcuts.values(cfgKey(object));
|
||||
}
|
||||
|
||||
void ShortcutsConfig::setUserShortcuts(const QObjectList& objects) {
|
||||
storeShortcutsFromList(StoreType::User, objects);
|
||||
}
|
||||
|
||||
void ShortcutsConfig::setUserShortcuts(const QMultiMap<const QObject *, QKeySequence>& objects_keySequences) {
|
||||
for (auto *object : objects_keySequences.uniqueKeys())
|
||||
if (!object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
|
||||
storeShortcuts(StoreType::User, cfgKey(object), objects_keySequences.values(object));
|
||||
}
|
||||
|
||||
QList<QKeySequence> ShortcutsConfig::userShortcuts(const QObject *object) const {
|
||||
return user_shortcuts.values(cfgKey(object));
|
||||
}
|
||||
|
||||
void ShortcutsConfig::storeShortcutsFromList(StoreType storeType, const QObjectList& objects) {
|
||||
for (const auto *object : objects)
|
||||
if (!object->objectName().isEmpty() && !object->objectName().startsWith("_q_"))
|
||||
storeShortcuts(storeType, cfgKey(object), currentShortcuts(object));
|
||||
}
|
||||
|
||||
void ShortcutsConfig::storeShortcuts(
|
||||
StoreType storeType,
|
||||
const QString& cfgKey,
|
||||
const QList<QKeySequence>& keySequences)
|
||||
{
|
||||
bool storeUser = (storeType == User) || !user_shortcuts.contains(cfgKey);
|
||||
|
||||
if (storeType == Default)
|
||||
default_shortcuts.remove(cfgKey);
|
||||
if (storeUser)
|
||||
user_shortcuts.remove(cfgKey);
|
||||
|
||||
if (keySequences.isEmpty()) {
|
||||
if (storeType == Default)
|
||||
default_shortcuts.insert(cfgKey, QKeySequence());
|
||||
if (storeUser)
|
||||
user_shortcuts.insert(cfgKey, QKeySequence());
|
||||
} else {
|
||||
for (auto keySequence : keySequences) {
|
||||
if (storeType == Default)
|
||||
default_shortcuts.insert(cfgKey, keySequence);
|
||||
if (storeUser)
|
||||
user_shortcuts.insert(cfgKey, keySequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Creates a config key from the object's name prepended with the parent
|
||||
* window's object name, and converts camelCase to snake_case. */
|
||||
QString ShortcutsConfig::cfgKey(const QObject *object) const {
|
||||
auto cfg_key = QString();
|
||||
auto *parentWidget = static_cast<QWidget *>(object->parent());
|
||||
if (parentWidget)
|
||||
cfg_key = parentWidget->window()->objectName() + '_';
|
||||
cfg_key += object->objectName();
|
||||
|
||||
static const QRegularExpression re("[A-Z]");
|
||||
int i = cfg_key.indexOf(re, 1);
|
||||
while (i != -1) {
|
||||
if (cfg_key.at(i - 1) != '_')
|
||||
cfg_key.insert(i++, '_');
|
||||
i = cfg_key.indexOf(re, i + 1);
|
||||
}
|
||||
return cfg_key.toLower();
|
||||
}
|
||||
|
||||
QList<QKeySequence> ShortcutsConfig::currentShortcuts(const QObject *object) const {
|
||||
if (object->inherits("QAction")) {
|
||||
const auto *action = qobject_cast<const QAction *>(object);
|
||||
return action->shortcuts();
|
||||
} else if (object->inherits("Shortcut")) {
|
||||
const auto *shortcut = qobject_cast<const Shortcut *>(object);
|
||||
return shortcut->keys();
|
||||
} else if (object->inherits("QShortcut")) {
|
||||
const auto *qshortcut = qobject_cast<const QShortcut *>(object);
|
||||
return { qshortcut->key() };
|
||||
} else if (object->property("shortcut").isValid()) {
|
||||
return { object->property("shortcut").value<QKeySequence>() };
|
||||
} else {
|
||||
return { };
|
||||
}
|
||||
}
|
||||
16
src/config/userconfig.cpp
Normal file
16
src/config/userconfig.cpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#include "userconfig.h"
|
||||
|
||||
// TODO: This should eventually be contained by each individual Project instance.
|
||||
UserConfig userConfig;
|
||||
|
||||
void UserConfig::loadFromJson(const QJsonObject& obj) {
|
||||
KeyValueConfigBase::loadFromJson(obj);
|
||||
|
||||
// Enforce this setting for userConfig's custom scripts
|
||||
for (auto& settings : this->customScripts) settings.userOnly = true;
|
||||
}
|
||||
|
||||
QJsonObject UserConfig::getDefaultJson() const {
|
||||
UserConfig defaultConfig;
|
||||
return defaultConfig.toJson();
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#include "log.h"
|
||||
#include "project.h"
|
||||
#include "maplayout.h"
|
||||
#include "config.h"
|
||||
|
||||
Layout *AdvanceMapParser::parseLayout(const QString &filepath, bool *error, const Project *project)
|
||||
{
|
||||
|
|
@ -118,18 +119,18 @@ QList<Metatile*> AdvanceMapParser::parseMetatiles(const QString &filepath, bool
|
|||
|
||||
int projIdOffset = in.length() - 4;
|
||||
int metatileSize = 16;
|
||||
BaseGameVersion version;
|
||||
BaseGame::Version version;
|
||||
if (in.at(projIdOffset + 0) == 'R'
|
||||
&& in.at(projIdOffset + 1) == 'S'
|
||||
&& in.at(projIdOffset + 2) == 'E'
|
||||
&& in.at(projIdOffset + 3) == ' ') {
|
||||
// ruby and emerald are handled equally here.
|
||||
version = BaseGameVersion::pokeemerald;
|
||||
version = BaseGame::Version::pokeemerald;
|
||||
} else if (in.at(projIdOffset + 0) == 'F'
|
||||
&& in.at(projIdOffset + 1) == 'R'
|
||||
&& in.at(projIdOffset + 2) == 'L'
|
||||
&& in.at(projIdOffset + 3) == 'G') {
|
||||
version = BaseGameVersion::pokefirered;
|
||||
version = BaseGame::Version::pokefirered;
|
||||
} else {
|
||||
*error = true;
|
||||
logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'."));
|
||||
|
|
|
|||
62
src/core/basegame.cpp
Normal file
62
src/core/basegame.cpp
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
#include "basegame.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
|
||||
// If a string exclusively contains one version name we assume its identity,
|
||||
// otherwise we leave it unknown and we'll need the user to tell us the version.
|
||||
BaseGame::Version BaseGame::stringToVersion(const QString &input_) {
|
||||
static const QMap<Version, QStringList> versionDetectNames = {
|
||||
{Version::pokeruby, {"ruby", "sapphire"}},
|
||||
{Version::pokefirered, {"firered", "leafgreen"}},
|
||||
{Version::pokeemerald, {"emerald"}},
|
||||
};
|
||||
|
||||
const QString input(input_.toLower());
|
||||
Version version = Version::none;
|
||||
for (auto it = versionDetectNames.begin(); it != versionDetectNames.end(); it++) {
|
||||
// Compare the given string to all the possible names for this game version
|
||||
for (const auto &name : it.value()) {
|
||||
if (input.contains(name)) {
|
||||
if (version != Version::none) {
|
||||
// The given string matches multiple versions, so we can't be sure which it is.
|
||||
return Version::none;
|
||||
}
|
||||
version = it.key();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// We finished checking the names for each version; the name either matched 1 version or none.
|
||||
return version;
|
||||
}
|
||||
|
||||
QString BaseGame::versionToString(BaseGame::Version version) {
|
||||
static const QMap<Version, QString> map = {
|
||||
{Version::pokeruby, "pokeruby"},
|
||||
{Version::pokefirered, "pokefirered"},
|
||||
{Version::pokeemerald, "pokeemerald"},
|
||||
};
|
||||
return map.value(version);
|
||||
}
|
||||
|
||||
QString BaseGame::getPlayerIconPath(BaseGame::Version version, int character) {
|
||||
if (version == Version::pokeemerald) {
|
||||
static const QStringList paths = { QStringLiteral(":/icons/player/brendan_em.ico"),
|
||||
QStringLiteral(":/icons/player/may_em.ico"), };
|
||||
return paths.value(character);
|
||||
} else if (version == Version::pokefirered) {
|
||||
static const QStringList paths = { QStringLiteral(":/icons/player/red.ico"),
|
||||
QStringLiteral(":/icons/player/green.ico"), };
|
||||
return paths.value(character);
|
||||
} else if (version == Version::pokeruby) {
|
||||
static const QStringList paths = { QStringLiteral(":/icons/player/brendan_rs.ico"),
|
||||
QStringLiteral(":/icons/player/may_rs.ico"), };
|
||||
return paths.value(character);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
QIcon BaseGame::getPlayerIcon(BaseGame::Version baseGameVersion, int character) {
|
||||
return QIcon(getPlayerIconPath(baseGameVersion, character));
|
||||
}
|
||||
|
|
@ -2,12 +2,10 @@
|
|||
#include "bitpacker.h"
|
||||
#include "config.h"
|
||||
|
||||
// Upper limit for metatile ID, collision, and elevation masks. Used externally.
|
||||
const uint16_t Block::maxValue = 0xFFFF;
|
||||
|
||||
static BitPacker bitsMetatileId = BitPacker(0x3FF);
|
||||
static BitPacker bitsCollision = BitPacker(0xC00);
|
||||
static BitPacker bitsElevation = BitPacker(0xF000);
|
||||
static BitPacker bitsMetatileId = BitPacker(Block::DefaultMetatileIdMask);
|
||||
static BitPacker bitsCollision = BitPacker(Block::DefaultCollisionMask);
|
||||
static BitPacker bitsElevation = BitPacker(Block::DefaultElevationMask);
|
||||
|
||||
Block::Block() :
|
||||
m_metatileId(0),
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) {
|
|||
|
||||
static int numMetatileIdChars = 4;
|
||||
QString Metatile::getMetatileIdString(uint16_t metatileId) {
|
||||
return /*porymapConfig.displayIdsHexadecimal*/ConfigDisplayIdsHexadecimal
|
||||
return porymapConfig.displayIdsHexadecimal
|
||||
? Util::toHexString(metatileId, numMetatileIdChars)
|
||||
: QString::number(metatileId);
|
||||
};
|
||||
|
|
@ -62,6 +62,10 @@ QString Metatile::getLayerName(int layerNum) {
|
|||
return layerTitles.value(layerNum);
|
||||
}
|
||||
|
||||
int Metatile::numLayers() {
|
||||
return projectConfig.tripleLayerMetatilesEnabled ? 3 : 2;
|
||||
}
|
||||
|
||||
// Read and pack together this metatile's attributes.
|
||||
uint32_t Metatile::getAttributes() const {
|
||||
uint32_t data = 0;
|
||||
|
|
@ -81,8 +85,8 @@ void Metatile::setAttributes(uint32_t data) {
|
|||
}
|
||||
|
||||
// Unpack and insert metatile attributes from the given data using a vanilla layout. For AdvanceMap import
|
||||
void Metatile::setAttributes(uint32_t data, BaseGameVersion version) {
|
||||
const auto vanillaPackers = (version == BaseGameVersion::pokefirered) ? attributePackersFRLG : attributePackersRSE;
|
||||
void Metatile::setAttributes(uint32_t data, BaseGame::Version version) {
|
||||
const auto vanillaPackers = (version == BaseGame::Version::pokefirered) ? attributePackersFRLG : attributePackersRSE;
|
||||
for (auto i = vanillaPackers.cbegin(), end = vanillaPackers.cend(); i != end; i++){
|
||||
const auto packer = i.value();
|
||||
this->setAttribute(i.key(), packer.unpack(data));
|
||||
|
|
@ -95,12 +99,12 @@ void Metatile::setAttribute(Metatile::Attr attr, uint32_t value) {
|
|||
this->attributes.insert(attr, packer.clamp(value));
|
||||
}
|
||||
|
||||
int Metatile::getDefaultAttributesSize(BaseGameVersion version) {
|
||||
return (version == BaseGameVersion::pokefirered) ? 4 : 2;
|
||||
int Metatile::getDefaultAttributesSize(BaseGame::Version version) {
|
||||
return (version == BaseGame::Version::pokefirered) ? 4 : 2;
|
||||
}
|
||||
|
||||
uint32_t Metatile::getDefaultAttributesMask(BaseGameVersion version, Metatile::Attr attr) {
|
||||
const auto vanillaPackers = (version == BaseGameVersion::pokefirered) ? attributePackersFRLG : attributePackersRSE;
|
||||
uint32_t Metatile::getDefaultAttributesMask(BaseGame::Version version, Metatile::Attr attr) {
|
||||
const auto vanillaPackers = (version == BaseGame::Version::pokefirered) ? attributePackersFRLG : attributePackersRSE;
|
||||
return vanillaPackers.value(attr).mask();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,6 @@
|
|||
#include "project.h"
|
||||
#include "bitpacker.h"
|
||||
|
||||
bool ConfigDisplayIdsHexadecimal = true;
|
||||
|
||||
// Upper limit for raw value (i.e., uint16_t max).
|
||||
const uint16_t Tile::maxValue = 0xFFFF;
|
||||
|
||||
// At the moment these are fixed, and not exposed to the user.
|
||||
// We're only using them for convenience when converting between raw values.
|
||||
// The actual job of clamping Tile's members to correct values is handled by the widths in the bit field.
|
||||
|
|
@ -76,7 +71,7 @@ QString Tile::toString() const {
|
|||
}
|
||||
|
||||
QString Tile::getTileIdString(uint16_t tileId) {
|
||||
return /*porymapConfig.displayIdsHexadecimal*/ConfigDisplayIdsHexadecimal
|
||||
return porymapConfig.displayIdsHexadecimal
|
||||
? Util::toHexString(tileId, 3)
|
||||
: QString::number(tileId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ void Tileset::resizeMetatiles(int newNumMetatiles) {
|
|||
while (m_metatiles.length() > newNumMetatiles) {
|
||||
delete m_metatiles.takeLast();
|
||||
}
|
||||
const int numTiles = projectConfig.getNumTilesInMetatile();
|
||||
const int numTiles = Metatile::maxTiles();
|
||||
while (m_metatiles.length() < newNumMetatiles) {
|
||||
m_metatiles.append(new Metatile(numTiles));
|
||||
}
|
||||
|
|
@ -322,7 +322,7 @@ bool Tileset::appendToHeaders(const QString &filepath, const QString &friendlyNa
|
|||
dataString.append(QString("\t.4byte gTilesetTiles_%1\n").arg(friendlyName));
|
||||
dataString.append(QString("\t.4byte gTilesetPalettes_%1\n").arg(friendlyName));
|
||||
dataString.append(QString("\t.4byte gMetatiles_%1\n").arg(friendlyName));
|
||||
if (projectConfig.baseGameVersion == BaseGameVersion::pokefirered) {
|
||||
if (projectConfig.baseGameVersion == BaseGame::Version::pokefirered) {
|
||||
dataString.append("\t.4byte NULL @ animation callback\n");
|
||||
dataString.append(QString("\t.4byte gMetatileAttributes_%1\n").arg(friendlyName));
|
||||
} else {
|
||||
|
|
@ -439,7 +439,7 @@ QHash<int, QString> Tileset::getHeaderMemberMap(bool usingAsm)
|
|||
int paddingOffset = usingAsm ? 1 : 0;
|
||||
|
||||
// The position of metatileAttributes changes between games
|
||||
bool isPokefirered = (projectConfig.baseGameVersion == BaseGameVersion::pokefirered);
|
||||
bool isPokefirered = (projectConfig.baseGameVersion == BaseGame::Version::pokefirered);
|
||||
int metatileAttrPosition = (isPokefirered ? 6 : 5) + paddingOffset;
|
||||
|
||||
auto map = QHash<int, QString>();
|
||||
|
|
@ -461,7 +461,7 @@ bool Tileset::loadMetatiles() {
|
|||
}
|
||||
|
||||
QByteArray data = file.readAll();
|
||||
int tilesPerMetatile = projectConfig.getNumTilesInMetatile();
|
||||
int tilesPerMetatile = Metatile::maxTiles();
|
||||
int bytesPerMetatile = Tile::sizeInBytes() * tilesPerMetatile;
|
||||
int numMetatiles = data.length() / bytesPerMetatile;
|
||||
if (numMetatiles > maxMetatiles()) {
|
||||
|
|
@ -493,7 +493,7 @@ bool Tileset::saveMetatiles() {
|
|||
}
|
||||
|
||||
QByteArray data;
|
||||
int numTiles = projectConfig.getNumTilesInMetatile();
|
||||
int numTiles = Metatile::maxTiles();
|
||||
for (const auto &metatile : m_metatiles) {
|
||||
for (int i = 0; i < numTiles; i++) {
|
||||
uint16_t tile = metatile->tiles.value(i).rawValue();
|
||||
|
|
|
|||
|
|
@ -129,26 +129,6 @@ void Util::show(QWidget *w) {
|
|||
}
|
||||
}
|
||||
|
||||
// Safe conversion from an int representing a QColorSpace::NamedColorSpace to a QColorSpace.
|
||||
// This lets us use 0 to mean "no color space".
|
||||
QColorSpace Util::toColorSpace(int colorSpaceInt) {
|
||||
QColorSpace colorSpace;
|
||||
|
||||
int min = static_cast<int>(QColorSpace::SRgb);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 8, 0))
|
||||
// Qt 6.8.0 introduced additional color spaces
|
||||
int max = static_cast<int>(QColorSpace::Bt2100Hlg);
|
||||
#else
|
||||
int max = static_cast<int>(QColorSpace::ProPhotoRgb);
|
||||
#endif
|
||||
|
||||
if (colorSpaceInt >= min && colorSpaceInt <= max) {
|
||||
return QColorSpace(static_cast<QColorSpace::NamedColorSpace>(colorSpaceInt));
|
||||
} else {
|
||||
return QColorSpace();
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a directory named 'dirPath', including any non-existent parent directories. Returns an error message, if any.
|
||||
// If 'dirPath' already exists it's considered an error unless the directory has no files.
|
||||
QString Util::mkpath(const QString& dirPath) {
|
||||
|
|
|
|||
3
src/core/version.cpp
Normal file
3
src/core/version.cpp
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#include "version.h"
|
||||
|
||||
const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_VERSION);
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
#include "newmapgroupdialog.h"
|
||||
#include "newlocationdialog.h"
|
||||
#include "loadingscreen.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QDirIterator>
|
||||
|
|
@ -81,6 +82,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
void MainWindow::initialize() {
|
||||
this->initWindow();
|
||||
this->installEventFilter(new GeometrySaver(this));
|
||||
if (porymapConfig.reopenOnLaunch && !porymapConfig.projectManuallyClosed && this->openProject(porymapConfig.getRecentProject(), true)) {
|
||||
on_toolButton_Paint_clicked();
|
||||
}
|
||||
|
|
@ -92,7 +94,7 @@ void MainWindow::initialize() {
|
|||
if (porymapConfig.checkForUpdates)
|
||||
this->checkForUpdates(false);
|
||||
|
||||
this->restoreWindowState();
|
||||
this->resizeWithinScreen();
|
||||
this->show();
|
||||
}
|
||||
|
||||
|
|
@ -109,13 +111,6 @@ MainWindow::~MainWindow()
|
|||
}
|
||||
|
||||
void MainWindow::saveGlobalConfigs() {
|
||||
porymapConfig.setMainGeometry(
|
||||
this->saveGeometry(),
|
||||
this->saveState(),
|
||||
this->ui->splitter_map->saveState(),
|
||||
this->ui->splitter_main->saveState(),
|
||||
this->ui->splitter_Metatiles->saveState()
|
||||
);
|
||||
porymapConfig.save();
|
||||
shortcutsConfig.save();
|
||||
}
|
||||
|
|
@ -185,6 +180,7 @@ void MainWindow::setWindowDisabled(bool disabled) {
|
|||
}
|
||||
|
||||
void MainWindow::initWindow() {
|
||||
porymapConfig = PorymapConfig();
|
||||
porymapConfig.load();
|
||||
this->initLogStatusBar();
|
||||
this->initCustomUI();
|
||||
|
|
@ -227,6 +223,7 @@ void MainWindow::initWindow() {
|
|||
void MainWindow::initShortcuts() {
|
||||
initExtraShortcuts();
|
||||
|
||||
shortcutsConfig = ShortcutsConfig();
|
||||
shortcutsConfig.load();
|
||||
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
|
||||
applyUserShortcuts();
|
||||
|
|
@ -303,9 +300,8 @@ void MainWindow::applyUserShortcuts() {
|
|||
|
||||
void MainWindow::initLogStatusBar() {
|
||||
removeLogStatusBar(this->statusBar());
|
||||
auto logTypes = QSet<LogType>(porymapConfig.statusBarLogTypes.begin(), porymapConfig.statusBarLogTypes.end());
|
||||
if (!logTypes.isEmpty()) {
|
||||
addLogStatusBar(this->statusBar(), logTypes);
|
||||
if (!porymapConfig.statusBarLogTypes.isEmpty()) {
|
||||
addLogStatusBar(this->statusBar(), porymapConfig.statusBarLogTypes.toQSet());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -320,7 +316,7 @@ void MainWindow::initCustomUI() {
|
|||
|
||||
static const QMap<int, QIcon> mainTabIcons = {
|
||||
{MainTab::Map, QIcon(QStringLiteral(":/icons/minimap.ico"))},
|
||||
{MainTab::Events, ProjectConfig::getPlayerIcon(BaseGameVersion::pokefirered, 0)}, // Arbitrary default
|
||||
{MainTab::Events, BaseGame::getPlayerIcon(BaseGame::Version::pokefirered, 0)}, // Arbitrary default
|
||||
{MainTab::Header, QIcon(QStringLiteral(":/icons/application_form_edit.ico"))},
|
||||
{MainTab::Connections, QIcon(QStringLiteral(":/icons/connections.ico"))},
|
||||
{MainTab::WildPokemon, QIcon(QStringLiteral(":/icons/tall_grass.ico"))},
|
||||
|
|
@ -591,9 +587,9 @@ void MainWindow::initMapList() {
|
|||
|
||||
// Initialize settings from config
|
||||
ui->mapListToolBar_Groups->setEditsAllowed(porymapConfig.mapListEditGroupsEnabled);
|
||||
for (auto i = porymapConfig.mapListHideEmptyEnabled.constBegin(); i != porymapConfig.mapListHideEmptyEnabled.constEnd(); i++) {
|
||||
auto toolbar = getMapListToolBar(i.key());
|
||||
if (toolbar) toolbar->setEmptyFoldersVisible(!i.value());
|
||||
for (const auto &tab : porymapConfig.mapListTabsHidingEmptyFolders) {
|
||||
auto toolbar = getMapListToolBar(tab);
|
||||
if (toolbar) toolbar->setEmptyFoldersVisible(false);
|
||||
}
|
||||
|
||||
// Update config if map list settings change
|
||||
|
|
@ -601,13 +597,16 @@ void MainWindow::initMapList() {
|
|||
porymapConfig.mapListEditGroupsEnabled = allowed;
|
||||
});
|
||||
connect(ui->mapListToolBar_Groups, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) {
|
||||
porymapConfig.mapListHideEmptyEnabled[MapListTab::Groups] = !visible;
|
||||
if (visible) porymapConfig.mapListTabsHidingEmptyFolders.erase(MapListTab::Groups);
|
||||
else porymapConfig.mapListTabsHidingEmptyFolders.insert(MapListTab::Groups);
|
||||
});
|
||||
connect(ui->mapListToolBar_Locations, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) {
|
||||
porymapConfig.mapListHideEmptyEnabled[MapListTab::Locations] = !visible;
|
||||
if (visible) porymapConfig.mapListTabsHidingEmptyFolders.erase(MapListTab::Locations);
|
||||
else porymapConfig.mapListTabsHidingEmptyFolders.insert(MapListTab::Locations);
|
||||
});
|
||||
connect(ui->mapListToolBar_Layouts, &MapListToolBar::emptyFoldersVisibleChanged, [](bool visible) {
|
||||
porymapConfig.mapListHideEmptyEnabled[MapListTab::Layouts] = !visible;
|
||||
if (visible) porymapConfig.mapListTabsHidingEmptyFolders.erase(MapListTab::Layouts);
|
||||
else porymapConfig.mapListTabsHidingEmptyFolders.insert(MapListTab::Layouts);
|
||||
});
|
||||
|
||||
// When map list search filter is cleared we want the current map/layout in the editor to be visible in the list.
|
||||
|
|
@ -729,19 +728,8 @@ void MainWindow::loadUserSettings() {
|
|||
refreshRecentProjectsMenu();
|
||||
}
|
||||
|
||||
void MainWindow::restoreWindowState() {
|
||||
QMap<QString, QByteArray> geometry = porymapConfig.getMainGeometry();
|
||||
const QByteArray mainWindowGeometry = geometry.value("main_window_geometry");
|
||||
if (!mainWindowGeometry.isEmpty()) {
|
||||
logInfo("Restoring main window geometry from previous session.");
|
||||
restoreGeometry(mainWindowGeometry);
|
||||
restoreState(geometry.value("main_window_state"));
|
||||
ui->splitter_map->restoreState(geometry.value("map_splitter_state"));
|
||||
ui->splitter_main->restoreState(geometry.value("main_splitter_state"));
|
||||
ui->splitter_Metatiles->restoreState(geometry.value("metatiles_splitter_state"));
|
||||
}
|
||||
|
||||
// Resize the window if it exceeds the available screen size.
|
||||
// Resize the window if it exceeds the available screen size.
|
||||
void MainWindow::resizeWithinScreen() {
|
||||
auto screen = windowHandle() ? windowHandle()->screen() : QGuiApplication::primaryScreen();
|
||||
if (!screen) return;
|
||||
const QRect screenGeometry = screen->availableGeometry();
|
||||
|
|
@ -800,14 +788,14 @@ bool MainWindow::openProject(QString dir, bool initial) {
|
|||
logInfo("Aborted project open.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString openMessage = QString("Opening %1").arg(projectString);
|
||||
logInfo(openMessage);
|
||||
logInfo(QString("Opening %1").arg(projectString));
|
||||
|
||||
if (porymapConfig.showProjectLoadingScreen) porysplash->start();
|
||||
|
||||
porysplash->showLoadingMessage("config");
|
||||
if (!projectConfig.load(dir) || !userConfig.load(dir)) {
|
||||
projectConfig = ProjectConfig(dir);
|
||||
userConfig = UserConfig(dir);
|
||||
if (!projectConfig.load() || !userConfig.load()) {
|
||||
showProjectOpenFailure();
|
||||
porysplash->stop();
|
||||
return false;
|
||||
|
|
@ -845,9 +833,6 @@ bool MainWindow::openProject(QString dir, bool initial) {
|
|||
porysplash->stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only create the config files once the project has opened successfully in case the user selected an invalid directory
|
||||
this->editor->project->saveConfig();
|
||||
|
||||
updateWindowTitle();
|
||||
|
||||
|
|
@ -899,24 +884,25 @@ bool MainWindow::checkProjectSanity(Project *project) {
|
|||
|
||||
bool MainWindow::checkProjectVersion(Project *project) {
|
||||
QString error;
|
||||
int projectVersion = project->getSupportedMajorVersion(&error);
|
||||
if (projectVersion < 0) {
|
||||
// Failed to identify a supported major version.
|
||||
QVersionNumber minimumVersion = project->getMinimumVersion(&error);
|
||||
if (!error.isEmpty()) {
|
||||
// Failed to identify a supported version.
|
||||
// We can't draw any conclusions from this, so we don't consider the project to be invalid.
|
||||
QString msg = QStringLiteral("Failed to check project version");
|
||||
logWarn(error.isEmpty() ? msg : QString("%1: '%2'").arg(msg).arg(error));
|
||||
logWarn(QString("Failed to check project version: '%1'").arg(error));
|
||||
} else {
|
||||
QString msg = QStringLiteral("Successfully checked project version. ");
|
||||
logInfo(msg + ((projectVersion != 0) ? QString("Supports at least Porymap v%1").arg(projectVersion)
|
||||
: QStringLiteral("Too old for any Porymap version")));
|
||||
|
||||
if (projectVersion < porymapVersion.majorVersion() && projectConfig.forcedMajorVersion < porymapVersion.majorVersion()) {
|
||||
// We were unable to find the necessary changes for Porymap's current major version.
|
||||
if (minimumVersion.isNull()) {
|
||||
logInfo(QStringLiteral("Successfully checked project version. Too old for any Porymap version"));
|
||||
} else {
|
||||
logInfo(QString("Successfully checked project version. Supports at least Porymap v%1").arg(minimumVersion.toString()));
|
||||
}
|
||||
if (minimumVersion > porymapVersion || minimumVersion.majorVersion() != porymapVersion.majorVersion()) {
|
||||
// Porymap is incompatible with the project if its version is below the specified minimum version,
|
||||
// or if Porymap is so new that it exceeds the major version of the specified minimum version.
|
||||
// Unless they have explicitly suppressed this message, warn the user that this might mean their project is missing breaking changes.
|
||||
// Note: Do not report 'projectVersion' to the user in this message. We've already logged it for troubleshooting.
|
||||
// Note: Do not report 'minimumVersion' to the user in this message. We've already logged it for troubleshooting.
|
||||
// It is very plausible that the user may have reproduced the required changes in an
|
||||
// unknown commit, rather than merging the required changes directly from the base repo.
|
||||
// In this case the 'projectVersion' may actually be too old to use for their repo.
|
||||
// In this case the 'minimumVersion' may actually be too old to use for their repo.
|
||||
ErrorMessage msgBox(QStringLiteral("Your project may be incompatible!"), porysplash);
|
||||
msgBox.setTextFormat(Qt::RichText);
|
||||
msgBox.setInformativeText(QString("Make sure '%1' has all the required changes for Porymap version %2.<br>"
|
||||
|
|
@ -929,7 +915,7 @@ bool MainWindow::checkProjectVersion(Project *project) {
|
|||
return false;
|
||||
}
|
||||
// User opted to try with this version anyway. Don't warn them about this version again.
|
||||
projectConfig.forcedMajorVersion = porymapVersion.majorVersion();
|
||||
projectConfig.minimumVersion = QVersionNumber(porymapVersion.majorVersion());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
@ -939,7 +925,7 @@ void MainWindow::showProjectOpenFailure() {
|
|||
if (!this->isVisible()){
|
||||
// The main window is not visible during the initial project open; the splash screen is busy providing visual feedback.
|
||||
// If project opening fails we can immediately display the empty main window (which we need anyway to parent messages to).
|
||||
restoreWindowState();
|
||||
resizeWithinScreen();
|
||||
show();
|
||||
}
|
||||
RecentErrorMessage::show(QStringLiteral("There was an error opening the project."), this);
|
||||
|
|
@ -1491,7 +1477,7 @@ bool MainWindow::setProjectUI() {
|
|||
this->mapGroupModel = new MapGroupModel(editor->project);
|
||||
this->groupListProxyModel = new FilterChildrenProxyModel();
|
||||
this->groupListProxyModel->setSourceModel(this->mapGroupModel);
|
||||
this->groupListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Groups]);
|
||||
this->groupListProxyModel->setHideEmpty(porymapConfig.mapListTabsHidingEmptyFolders.contains(MapListTab::Groups));
|
||||
ui->mapList->setModel(groupListProxyModel);
|
||||
|
||||
this->ui->mapList->setItemDelegateForColumn(0, new GroupNameDelegate(this->editor->project, this));
|
||||
|
|
@ -1500,14 +1486,14 @@ bool MainWindow::setProjectUI() {
|
|||
this->mapLocationModel = new MapLocationModel(editor->project);
|
||||
this->locationListProxyModel = new FilterChildrenProxyModel();
|
||||
this->locationListProxyModel->setSourceModel(this->mapLocationModel);
|
||||
this->locationListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Locations]);
|
||||
this->locationListProxyModel->setHideEmpty(porymapConfig.mapListTabsHidingEmptyFolders.contains(MapListTab::Locations));
|
||||
ui->locationList->setModel(locationListProxyModel);
|
||||
setMapListSorted(ui->locationList, porymapConfig.mapListLocationsSorted);
|
||||
|
||||
this->layoutTreeModel = new LayoutTreeModel(editor->project);
|
||||
this->layoutListProxyModel = new FilterChildrenProxyModel();
|
||||
this->layoutListProxyModel->setSourceModel(this->layoutTreeModel);
|
||||
this->layoutListProxyModel->setHideEmpty(porymapConfig.mapListHideEmptyEnabled[MapListTab::Layouts]);
|
||||
this->layoutListProxyModel->setHideEmpty(porymapConfig.mapListTabsHidingEmptyFolders.contains(MapListTab::Layouts));
|
||||
ui->layoutList->setModel(layoutListProxyModel);
|
||||
setMapListSorted(ui->layoutList, porymapConfig.mapListLayoutsSorted);
|
||||
|
||||
|
|
@ -1524,7 +1510,7 @@ bool MainWindow::setProjectUI() {
|
|||
if (eventTabIcon.isNull()) {
|
||||
// We randomly choose between the available characters for ~flavor~.
|
||||
// For now, this correctly assumes all versions have 2 icons.
|
||||
eventTabIcon = ProjectConfig::getPlayerIcon(projectConfig.baseGameVersion, QRandomGenerator::global()->bounded(0, 2));
|
||||
eventTabIcon = BaseGame::getPlayerIcon(projectConfig.baseGameVersion, QRandomGenerator::global()->bounded(0, 2));
|
||||
}
|
||||
ui->mainTabBar->setTabIcon(MainTab::Events, eventTabIcon);
|
||||
|
||||
|
|
@ -2259,7 +2245,7 @@ void MainWindow::on_mapViewTab_tabBarClicked(int index)
|
|||
} else if (index == MapViewTab::Collision) {
|
||||
refreshCollisionSelector();
|
||||
} else if (index == MapViewTab::Prefabs) {
|
||||
if (projectConfig.prefabFilepath.isEmpty() && !projectConfig.prefabImportPrompted) {
|
||||
if (userConfig.prefabsFilepath.isEmpty() && !userConfig.prefabsImportPrompted) {
|
||||
// User hasn't set up prefabs and hasn't been prompted before.
|
||||
// Ask if they'd like to import the default prefabs file.
|
||||
if (prefab.tryImportDefaultPrefabs(this, projectConfig.baseGameVersion))
|
||||
|
|
@ -3299,7 +3285,8 @@ bool MainWindow::closeProject() {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
editor->closeProject();
|
||||
logInfo(QString("Closing project '%1'").arg(this->editor->project->root));
|
||||
this->editor->closeProject();
|
||||
clearProjectUI();
|
||||
refreshRecentProjectsMenu();
|
||||
setWindowDisabled(true);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,11 @@ Project::~Project()
|
|||
}
|
||||
|
||||
void Project::setRoot(const QString &dir) {
|
||||
// This is not currently designed to actually change the root folder.
|
||||
// It will not appropriately update instances of the root stored elsewhere,
|
||||
// like in projectConfig or userConfig.
|
||||
Q_ASSERT(this->root.isEmpty());
|
||||
|
||||
this->root = dir;
|
||||
FileDialog::setDirectory(dir);
|
||||
this->parser.setRoot(dir);
|
||||
|
|
@ -72,20 +77,21 @@ bool Project::sanityCheck() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Porymap projects have no standardized way for Porymap to determine whether they're compatible as of the latest breaking changes.
|
||||
// We can use the project's git history (if it has one, and we're able to get it) to make a reasonable guess.
|
||||
// We know the hashes of the commits in the base repos that contain breaking changes, so if we find one of these then the project
|
||||
// should support at least up to that Porymap major version. If this fails for any reason it returns a version of -1.
|
||||
int Project::getSupportedMajorVersion(QString *errorOut) {
|
||||
QVersionNumber Project::getMinimumVersion(QString *errorOut) const {
|
||||
if (!projectConfig.minimumVersion.isNull()) return projectConfig.minimumVersion;
|
||||
|
||||
// No explicitly supported version, we can use the project's git history (if it has one, and we're able to get it) to make a reasonable guess.
|
||||
// We know the hashes of the commits in the base repos that contain breaking changes, so if we find one of these then the project
|
||||
// should support at least up to that Porymap major version. If this fails for any reason it returns an empty QVersionNumber.
|
||||
|
||||
// This has relatively tight timeout windows (500ms for each process, compared to the default 30,000ms). This version check
|
||||
// is not important enough to significantly slow down project launch, we'd rather just timeout.
|
||||
const int timeoutLimit = 500;
|
||||
const int failureVersion = -1;
|
||||
QString gitName = "git";
|
||||
constexpr int TimeoutLimit = 500;
|
||||
const QString gitName = QStringLiteral("git");
|
||||
QString gitPath = QStandardPaths::findExecutable(gitName);
|
||||
if (gitPath.isEmpty()) {
|
||||
if (errorOut) *errorOut = QString("Unable to locate %1.").arg(gitName);
|
||||
return failureVersion;
|
||||
return QVersionNumber();
|
||||
}
|
||||
|
||||
QProcess process;
|
||||
|
|
@ -98,7 +104,7 @@ int Project::getSupportedMajorVersion(QString *errorOut) {
|
|||
// We'll get the root commit, then compare it to the known root commits for the base project repos.
|
||||
process.setArguments({ "-c", QString("safe.directory=%1").arg(this->root), "rev-list", "--max-parents=0", "HEAD" });
|
||||
process.start();
|
||||
if (!process.waitForFinished(timeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit || process.exitCode() != 0) {
|
||||
if (!process.waitForFinished(TimeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit || process.exitCode() != 0) {
|
||||
if (errorOut) {
|
||||
*errorOut = QStringLiteral("Failed to identify commit history");
|
||||
if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) {
|
||||
|
|
@ -109,7 +115,7 @@ int Project::getSupportedMajorVersion(QString *errorOut) {
|
|||
if (!error.isEmpty()) errorOut->append(QString(": %1").arg(error));
|
||||
}
|
||||
}
|
||||
return failureVersion;
|
||||
return QVersionNumber();
|
||||
}
|
||||
const QString rootCommit = QString(process.readLine()).remove('\n');
|
||||
|
||||
|
|
@ -145,22 +151,22 @@ int Project::getSupportedMajorVersion(QString *errorOut) {
|
|||
if (!historyMap.contains(rootCommit)) {
|
||||
// Either this repo does not share history with one of the base repos, or we got some unexpected result.
|
||||
if (errorOut) *errorOut = QStringLiteral("Unrecognized commit history");
|
||||
return failureVersion;
|
||||
return QVersionNumber();
|
||||
}
|
||||
|
||||
// We now know which base repo that the user's repo shares history with.
|
||||
// Next we check to see if it contains the changes required to support particular major versions of Porymap.
|
||||
// We'll start with the most recent major version and work backwards.
|
||||
for (const auto &pair : historyMap.value(rootCommit)) {
|
||||
int versionNum = pair.first;
|
||||
QString commitHash = pair.second;
|
||||
const QVersionNumber version = QVersionNumber(pair.first);
|
||||
const QString commitHash = pair.second;
|
||||
if (commitHash.isEmpty()) {
|
||||
// An empty commit hash means 'consider any point in the history a supported version'
|
||||
return versionNum;
|
||||
return version;
|
||||
}
|
||||
process.setArguments({ "-c", QString("safe.directory=%1").arg(this->root), "merge-base", "--is-ancestor", commitHash, "HEAD" });
|
||||
process.start();
|
||||
if (!process.waitForFinished(timeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit) {
|
||||
if (!process.waitForFinished(TimeoutLimit) || process.exitStatus() != QProcess::ExitStatus::NormalExit) {
|
||||
if (errorOut) {
|
||||
*errorOut = QStringLiteral("Failed to search commit history");
|
||||
if (process.error() != QProcess::UnknownError && !process.errorString().isEmpty()) {
|
||||
|
|
@ -171,15 +177,15 @@ int Project::getSupportedMajorVersion(QString *errorOut) {
|
|||
if (!error.isEmpty()) errorOut->append(QString(": %1").arg(error));
|
||||
}
|
||||
}
|
||||
return failureVersion;
|
||||
return QVersionNumber();
|
||||
}
|
||||
if (process.exitCode() == 0) {
|
||||
// Identified a supported major version
|
||||
return versionNum;
|
||||
return version;
|
||||
}
|
||||
}
|
||||
// We recognized the commit history, but it's too old for any version of Porymap to support.
|
||||
return 0;
|
||||
return QVersionNumber();
|
||||
}
|
||||
|
||||
bool Project::load() {
|
||||
|
|
@ -1553,7 +1559,7 @@ Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboa
|
|||
tileset->loadTilesImage(&tilesImage);
|
||||
|
||||
// Create default metatiles
|
||||
const int tilesPerMetatile = projectConfig.getNumTilesInMetatile();
|
||||
const int tilesPerMetatile = Metatile::maxTiles();
|
||||
for (int i = 0; i < tileset->maxMetatiles(); ++i) {
|
||||
auto metatile = new Metatile();
|
||||
for(int j = 0; j < tilesPerMetatile; ++j){
|
||||
|
|
@ -1645,8 +1651,8 @@ bool Project::readTilesetMetatileLabels() {
|
|||
for (auto i = defines.constBegin(); i != defines.constEnd(); i++) {
|
||||
QString label = i.key();
|
||||
uint32_t metatileId = i.value();
|
||||
if (metatileId > Block::maxValue) {
|
||||
metatileId &= Block::maxValue;
|
||||
if (metatileId > Block::MaxValue) {
|
||||
metatileId &= Block::MaxValue;
|
||||
logWarn(QString("Value of metatile label '%1' truncated to %2").arg(label).arg(Metatile::getMetatileIdString(metatileId)));
|
||||
}
|
||||
QString tilesetName = findMetatileLabelsTileset(label);
|
||||
|
|
@ -3239,7 +3245,7 @@ QPixmap Project::getEventPixmap(Event::Group group) {
|
|||
QPixmap defaultIcon = QPixmap(defaultIcons.copy(static_cast<int>(group) * defaultWidth, 0, defaultWidth, defaultHeight));
|
||||
|
||||
// Custom event icons may be provided by the user.
|
||||
QString customIconPath = projectConfig.getEventIconPath(group);
|
||||
QString customIconPath = projectConfig.eventIconPaths.value(group);
|
||||
if (customIconPath.isEmpty()) {
|
||||
// No custom icon specified, use the default icon.
|
||||
pixmap = defaultIcon;
|
||||
|
|
@ -3331,7 +3337,7 @@ QString Project::getDefaultSpeciesIconPath(const QString &species) {
|
|||
|
||||
// We failed to find a default icon path, this species will use a placeholder icon.
|
||||
// If the user has no custom icon path for this species, tell them they can provide one.
|
||||
if (path.isEmpty() && projectConfig.getPokemonIconPath(species).isEmpty()) {
|
||||
if (path.isEmpty() && projectConfig.pokemonIconPaths.value(species).isEmpty()) {
|
||||
logWarn(QString("Failed to find Pokémon icon for '%1'. The filepath can be specified under 'Options->Project Settings'").arg(species));
|
||||
}
|
||||
return path;
|
||||
|
|
@ -3372,7 +3378,7 @@ QPixmap Project::getSpeciesIcon(const QString &species) {
|
|||
QPixmap pixmap;
|
||||
if (!QPixmapCache::find(species, &pixmap)) {
|
||||
// Prefer path from config. If not present, use the path parsed from project files
|
||||
QString path = Project::getExistingFilepath(projectConfig.getPokemonIconPath(species));
|
||||
QString path = Project::getExistingFilepath(projectConfig.pokemonIconPaths.value(species));
|
||||
if (path.isEmpty()) {
|
||||
path = getDefaultSpeciesIconPath(species);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -715,7 +715,7 @@ void MainWindow::setMetatileAttributes(int metatileId, int attributes) {
|
|||
}
|
||||
|
||||
int MainWindow::calculateTileBounds(int * tileStart, int * tileEnd) {
|
||||
int maxNumTiles = projectConfig.getNumTilesInMetatile();
|
||||
int maxNumTiles = Metatile::maxTiles();
|
||||
if (*tileEnd >= maxNumTiles || *tileEnd < 0)
|
||||
*tileEnd = maxNumTiles - 1;
|
||||
if (*tileStart >= maxNumTiles || *tileStart < 0)
|
||||
|
|
|
|||
|
|
@ -198,7 +198,12 @@ bool ScriptUtility::getSmartPathsEnabled() {
|
|||
}
|
||||
|
||||
QList<QString> ScriptUtility::getCustomScripts() {
|
||||
return userConfig.getCustomScriptPaths();
|
||||
QList<QString> paths;
|
||||
for (const auto& settings : userConfig.customScripts)
|
||||
paths.append(settings.path);
|
||||
for (const auto& settings : projectConfig.customScripts)
|
||||
paths.append(settings.path);
|
||||
return paths;
|
||||
}
|
||||
|
||||
QList<int> ScriptUtility::getMetatileLayerOrder() {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "log.h"
|
||||
#include "config.h"
|
||||
#include "mainwindow.h"
|
||||
#include "version.h"
|
||||
|
||||
Scripting *instance = nullptr;
|
||||
|
||||
|
|
@ -22,11 +23,11 @@ Scripting::Scripting(MainWindow *mainWindow)
|
|||
: QObject(mainWindow), mainWindow(mainWindow), engine(new QJSEngine(this))
|
||||
{
|
||||
this->engine->installExtensions(QJSEngine::ConsoleExtension);
|
||||
const QStringList paths = userConfig.getCustomScriptPaths();
|
||||
const QList<bool> enabled = userConfig.getCustomScriptsEnabled();
|
||||
for (int i = 0; i < paths.length(); i++) {
|
||||
if (enabled.value(i, true))
|
||||
loadScript(paths.at(i));
|
||||
|
||||
const QStringList paths = ScriptSettings::filter(userConfig.customScripts)
|
||||
+ ScriptSettings::filter(projectConfig.customScripts);
|
||||
for (const auto& path : paths) {
|
||||
loadScript(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,10 +104,10 @@ void Scripting::populateGlobalObject() {
|
|||
constants.setProperty("max_secondary_metatiles", Project::getNumMetatilesSecondary());
|
||||
constants.setProperty("num_primary_palettes", Project::getNumPalettesPrimary());
|
||||
constants.setProperty("num_secondary_palettes", Project::getNumPalettesSecondary());
|
||||
constants.setProperty("layers_per_metatile", projectConfig.getNumLayersInMetatile());
|
||||
constants.setProperty("tiles_per_metatile", projectConfig.getNumTilesInMetatile());
|
||||
constants.setProperty("layers_per_metatile", Metatile::numLayers());
|
||||
constants.setProperty("tiles_per_metatile", Metatile::maxTiles());
|
||||
|
||||
constants.setProperty("base_game_version", projectConfig.getBaseGameVersionString());
|
||||
constants.setProperty("base_game_version", BaseGame::versionToString(projectConfig.baseGameVersion));
|
||||
|
||||
// Read out behavior values into constants object
|
||||
QJSValue behaviorsArray = instance->engine->newObject();
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
#include "citymappixmapitem.h"
|
||||
#include "imageproviders.h"
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QPainter>
|
||||
#include <QDebug>
|
||||
|
||||
void CityMapPixmapItem::init() {
|
||||
width_ = 10;
|
||||
height_ = 10;
|
||||
|
||||
QFile binFile(file);
|
||||
if (!binFile.open(QIODevice::ReadOnly)) return;
|
||||
|
||||
data = binFile.readAll();
|
||||
if (projectConfig.baseGameVersion == BaseGameVersion::pokeruby) {
|
||||
for (int i = 0; i < data.size(); i++)
|
||||
data[i] = data[i] ^ 0x80;
|
||||
}
|
||||
|
||||
binFile.close();
|
||||
}
|
||||
|
||||
void CityMapPixmapItem::draw() {
|
||||
QImage image(width_ * 8, height_ * 8, QImage::Format_RGBA8888);
|
||||
|
||||
// TODO: construct temporary tile from this based on the id?
|
||||
// QPainter painter(&image);
|
||||
// for (int i = 0; i < data.size() / 2; i++) {
|
||||
// QImage img = this->tile_selector->tileImg(data[i * 2]);// need to skip every other tile
|
||||
// int x = i % width_;
|
||||
// int y = i / width_;
|
||||
// QPoint pos = QPoint(x * 8, y * 8);
|
||||
// painter.drawImage(pos, img);
|
||||
// }
|
||||
// painter.end();
|
||||
|
||||
this->setPixmap(QPixmap::fromImage(image));
|
||||
}
|
||||
|
||||
void CityMapPixmapItem::save() {
|
||||
QFile binFile(file);
|
||||
if (!binFile.open(QIODevice::WriteOnly)) {
|
||||
logError(QString("Cannot save city map tilemap to %1.").arg(file));
|
||||
return;
|
||||
}
|
||||
if (projectConfig.baseGameVersion == BaseGameVersion::pokeruby) {
|
||||
for (int i = 0; i < data.size(); i++)
|
||||
data[i] = data[i] ^ 0x80;
|
||||
}
|
||||
binFile.write(data);
|
||||
binFile.close();
|
||||
}
|
||||
|
||||
void CityMapPixmapItem::paint(QGraphicsSceneMouseEvent *event) {
|
||||
QPointF pos = event->pos();
|
||||
int x = static_cast<int>(pos.x()) / 8;
|
||||
int y = static_cast<int>(pos.y()) / 8;
|
||||
int index = getIndexAt(x, y);
|
||||
data[index] = static_cast<uint8_t>(this->tile_selector->selectedTile);
|
||||
|
||||
draw();
|
||||
}
|
||||
|
||||
void CityMapPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
|
||||
emit mouseEvent(event, this);
|
||||
}
|
||||
|
||||
void CityMapPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
|
||||
QPointF pos = event->pos();
|
||||
int x = static_cast<int>(pos.x()) / 8;
|
||||
int y = static_cast<int>(pos.y()) / 8;
|
||||
if (x < width_ && x >= 0
|
||||
&& y < height_ && y >= 0) {
|
||||
emit this->hoveredRegionMapTileChanged(x, y);
|
||||
emit mouseEvent(event, this);
|
||||
}
|
||||
}
|
||||
|
||||
void CityMapPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
|
||||
emit mouseEvent(event, this);
|
||||
}
|
||||
|
||||
QVector<uint8_t> CityMapPixmapItem::getTiles() {
|
||||
QVector<uint8_t> tiles;
|
||||
for (auto tile : data) {
|
||||
tiles.append(tile);
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
void CityMapPixmapItem::setTiles(QVector<uint8_t> tiles) {
|
||||
QByteArray newData;
|
||||
for (auto tile : tiles) {
|
||||
newData.append(tile);
|
||||
}
|
||||
this->data = newData;
|
||||
}
|
||||
|
||||
int CityMapPixmapItem::getIndexAt(int x, int y) {
|
||||
return 2 * (x + y * this->width_);
|
||||
}
|
||||
|
||||
int CityMapPixmapItem::width() {
|
||||
return this->width_;
|
||||
}
|
||||
|
||||
int CityMapPixmapItem::height() {
|
||||
return this->height_;
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include "editor.h"
|
||||
#include "shortcut.h"
|
||||
#include "filedialog.h"
|
||||
#include "eventfilters.h"
|
||||
|
||||
#include <QDir>
|
||||
|
||||
|
|
@ -18,10 +19,10 @@ CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) :
|
|||
// This property seems to be reset if we don't set it programmatically
|
||||
ui->list->setDragDropMode(QAbstractItemView::NoDragDrop);
|
||||
|
||||
const QStringList paths = userConfig.getCustomScriptPaths();
|
||||
const QList<bool> enabled = userConfig.getCustomScriptsEnabled();
|
||||
for (int i = 0; i < paths.length(); i++)
|
||||
this->displayScript(paths.at(i), enabled.value(i, true));
|
||||
for (const auto& settings : projectConfig.customScripts)
|
||||
displayScript(settings);
|
||||
for (const auto& settings : userConfig.customScripts)
|
||||
displayScript(settings);
|
||||
|
||||
connect(ui->button_Help, &QAbstractButton::clicked, this, &CustomScriptsEditor::openManual);
|
||||
connect(ui->button_CreateNewScript, &QAbstractButton::clicked, this, &CustomScriptsEditor::createNewScript);
|
||||
|
|
@ -29,8 +30,8 @@ CustomScriptsEditor::CustomScriptsEditor(QWidget *parent) :
|
|||
connect(ui->button_RefreshScripts, &QAbstractButton::clicked, this, &CustomScriptsEditor::userRefreshScripts);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &CustomScriptsEditor::dialogButtonClicked);
|
||||
|
||||
this->initShortcuts();
|
||||
this->restoreWindowState();
|
||||
initShortcuts();
|
||||
installEventFilter(new GeometrySaver(this));
|
||||
}
|
||||
|
||||
CustomScriptsEditor::~CustomScriptsEditor()
|
||||
|
|
@ -60,7 +61,6 @@ void CustomScriptsEditor::initShortcuts() {
|
|||
shortcut_refresh->setObjectName("shortcut_refresh");
|
||||
shortcut_refresh->setWhatsThis("Refresh Scripts");
|
||||
|
||||
shortcutsConfig.load();
|
||||
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
|
||||
applyUserShortcuts();
|
||||
}
|
||||
|
|
@ -87,26 +87,16 @@ void CustomScriptsEditor::applyUserShortcuts() {
|
|||
shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut));
|
||||
}
|
||||
|
||||
void CustomScriptsEditor::restoreWindowState() {
|
||||
logInfo("Restoring custom scripts editor geometry from previous session.");
|
||||
const QMap<QString, QByteArray> geometry = porymapConfig.getCustomScriptsEditorGeometry();
|
||||
this->restoreGeometry(geometry.value("custom_scripts_editor_geometry"));
|
||||
this->restoreState(geometry.value("custom_scripts_editor_state"));
|
||||
}
|
||||
|
||||
void CustomScriptsEditor::displayScript(const QString &filepath, bool enabled) {
|
||||
auto item = new QListWidgetItem();
|
||||
auto widget = new CustomScriptsListItem();
|
||||
|
||||
widget->ui->checkBox_Enable->setChecked(enabled);
|
||||
widget->ui->lineEdit_filepath->setText(filepath);
|
||||
void CustomScriptsEditor::displayScript(const ScriptSettings& settings) {
|
||||
auto item = new QListWidgetItem(ui->list);
|
||||
auto widget = new CustomScriptsListItem(settings, ui->list);
|
||||
item->setSizeHint(widget->sizeHint());
|
||||
|
||||
connect(widget->ui->b_Choose, &QAbstractButton::clicked, [this, item](bool) { this->replaceScript(item); });
|
||||
connect(widget->ui->b_Edit, &QAbstractButton::clicked, [this, item](bool) { this->openScript(item); });
|
||||
connect(widget->ui->b_Delete, &QAbstractButton::clicked, [this, item](bool) { this->removeScript(item); });
|
||||
connect(widget->ui->checkBox_Enable, &QCheckBox::toggled, this, &CustomScriptsEditor::markEdited);
|
||||
connect(widget->ui->lineEdit_filepath, &QLineEdit::textEdited, this, &CustomScriptsEditor::markEdited);
|
||||
connect(widget, &CustomScriptsListItem::clickedChooseScript, [this, item] { this->replaceScript(item); });
|
||||
connect(widget, &CustomScriptsListItem::clickedEditScript, [this, item] { this->openScript(item); });
|
||||
connect(widget, &CustomScriptsListItem::clickedDeleteScript, [this, item] { this->removeScript(item); });
|
||||
connect(widget, &CustomScriptsListItem::toggledEnable, this, &CustomScriptsEditor::markEdited);
|
||||
connect(widget, &CustomScriptsListItem::pathEdited, this, &CustomScriptsEditor::markEdited);
|
||||
|
||||
// Per the Qt manual, for performance reasons QListWidget::setItemWidget shouldn't be used with non-static items.
|
||||
// There's an assumption here that users won't have enough scripts for that to be a problem.
|
||||
|
|
@ -122,7 +112,7 @@ QString CustomScriptsEditor::getScriptFilepath(QListWidgetItem * item, bool abso
|
|||
auto widget = dynamic_cast<CustomScriptsListItem *>(ui->list->itemWidget(item));
|
||||
if (!widget) return QString();
|
||||
|
||||
QString path = widget->ui->lineEdit_filepath->text();
|
||||
QString path = widget->path();
|
||||
if (absolutePath) {
|
||||
QFileInfo fileInfo(path);
|
||||
if (fileInfo.isRelative())
|
||||
|
|
@ -134,13 +124,13 @@ QString CustomScriptsEditor::getScriptFilepath(QListWidgetItem * item, bool abso
|
|||
void CustomScriptsEditor::setScriptFilepath(QListWidgetItem * item, QString filepath) const {
|
||||
auto widget = dynamic_cast<CustomScriptsListItem *>(ui->list->itemWidget(item));
|
||||
if (widget) {
|
||||
widget->ui->lineEdit_filepath->setText(Util::stripPrefix(filepath, this->baseDir));
|
||||
widget->setPath(Util::stripPrefix(filepath, this->baseDir));
|
||||
}
|
||||
}
|
||||
|
||||
bool CustomScriptsEditor::getScriptEnabled(QListWidgetItem * item) const {
|
||||
auto widget = dynamic_cast<CustomScriptsListItem *>(ui->list->itemWidget(item));
|
||||
return widget && widget->ui->checkBox_Enable->isChecked();
|
||||
return widget && widget->scriptEnabled();
|
||||
}
|
||||
|
||||
QString CustomScriptsEditor::chooseScript(QString dir) {
|
||||
|
|
@ -187,7 +177,9 @@ void CustomScriptsEditor::displayNewScript(QString filepath) {
|
|||
}
|
||||
}
|
||||
|
||||
this->displayScript(filepath, true);
|
||||
ScriptSettings settings;
|
||||
settings.path = filepath;
|
||||
this->displayScript(settings);
|
||||
this->markEdited();
|
||||
}
|
||||
|
||||
|
|
@ -253,19 +245,23 @@ void CustomScriptsEditor::save() {
|
|||
if (!this->hasUnsavedChanges)
|
||||
return;
|
||||
|
||||
QStringList paths;
|
||||
QList<bool> enabledStates;
|
||||
QList<ScriptSettings> userScripts;
|
||||
QList<ScriptSettings> projectScripts;
|
||||
for (int i = 0; i < ui->list->count(); i++) {
|
||||
auto item = ui->list->item(i);
|
||||
const QString path = this->getScriptFilepath(item, false);
|
||||
if (!path.isEmpty()) {
|
||||
paths.append(path);
|
||||
enabledStates.append(this->getScriptEnabled(item));
|
||||
}
|
||||
auto widget = dynamic_cast<CustomScriptsListItem *>(ui->list->itemWidget(item));
|
||||
if (!widget) continue;
|
||||
const ScriptSettings settings = widget->getSettings();
|
||||
if (settings.userOnly) userScripts.append(settings);
|
||||
else projectScripts.append(settings);
|
||||
}
|
||||
|
||||
userConfig.setCustomScripts(paths, enabledStates);
|
||||
userConfig.customScripts = userScripts;
|
||||
userConfig.save();
|
||||
|
||||
projectConfig.customScripts = projectScripts;
|
||||
projectConfig.save();
|
||||
|
||||
this->hasUnsavedChanges = false;
|
||||
this->refreshScripts();
|
||||
}
|
||||
|
|
@ -295,9 +291,4 @@ void CustomScriptsEditor::closeEvent(QCloseEvent* event) {
|
|||
if (result == QMessageBox::Yes)
|
||||
this->save();
|
||||
}
|
||||
|
||||
porymapConfig.setCustomScriptsEditorGeometry(
|
||||
this->saveGeometry(),
|
||||
this->saveState()
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,50 @@ CustomScriptsListItem::CustomScriptsListItem(QWidget *parent) :
|
|||
ui(new Ui::CustomScriptsListItem)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->b_Choose, &QAbstractButton::clicked, this, &CustomScriptsListItem::clickedChooseScript);
|
||||
connect(ui->b_Edit, &QAbstractButton::clicked, this, &CustomScriptsListItem::clickedEditScript);
|
||||
connect(ui->b_Delete, &QAbstractButton::clicked, this, &CustomScriptsListItem::clickedDeleteScript);
|
||||
connect(ui->checkBox_Enable, &QCheckBox::toggled, this, &CustomScriptsListItem::toggledEnable);
|
||||
connect(ui->lineEdit_filepath, &QLineEdit::textEdited, this, &CustomScriptsListItem::pathEdited);
|
||||
}
|
||||
|
||||
CustomScriptsListItem::~CustomScriptsListItem()
|
||||
CustomScriptsListItem::CustomScriptsListItem(const ScriptSettings& settings, QWidget *parent) :
|
||||
CustomScriptsListItem(parent)
|
||||
{
|
||||
setSettings(settings);
|
||||
}
|
||||
|
||||
CustomScriptsListItem::~CustomScriptsListItem() {
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void CustomScriptsListItem::setPath(const QString& text) {
|
||||
ui->lineEdit_filepath->setText(text);
|
||||
}
|
||||
|
||||
QString CustomScriptsListItem::path() const {
|
||||
return ui->lineEdit_filepath->text();
|
||||
}
|
||||
|
||||
void CustomScriptsListItem::setScriptEnabled(bool enabled) {
|
||||
ui->checkBox_Enable->setChecked(enabled);
|
||||
}
|
||||
|
||||
bool CustomScriptsListItem::scriptEnabled() const {
|
||||
return ui->checkBox_Enable->isChecked();
|
||||
}
|
||||
|
||||
// TODO: The two functions below should read/write from/to the UI whether the script belongs to the project or user.
|
||||
void CustomScriptsListItem::setSettings(const ScriptSettings& settings) {
|
||||
setPath(settings.path);
|
||||
setScriptEnabled(settings.enabled);
|
||||
}
|
||||
|
||||
ScriptSettings CustomScriptsListItem::getSettings() const {
|
||||
return {
|
||||
.path = path(),
|
||||
.enabled = scriptEnabled(),
|
||||
.userOnly = true,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
#include "eventfilters.h"
|
||||
#include "config.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <QGraphicsSceneWheelEvent>
|
||||
|
||||
|
|
@ -16,10 +18,41 @@ bool MapSceneEventFilter::eventFilter(QObject*, QEvent *event) {
|
|||
}
|
||||
|
||||
|
||||
|
||||
bool ActiveWindowFilter::eventFilter(QObject*, QEvent *event) {
|
||||
if (event->type() == QEvent::WindowActivate) {
|
||||
emit activated();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool GeometrySaver::eventFilter(QObject *object, QEvent *event) {
|
||||
if (event->spontaneous()) return false;
|
||||
|
||||
auto w = qobject_cast<QWidget*>(object);
|
||||
if (!w) return false;
|
||||
|
||||
if (event->type() == QEvent::Polish) {
|
||||
// Note: Restoring geometry in QEvent::Show would be too late,
|
||||
// and the widget would briefly appear with the old geometry.
|
||||
porymapConfig.restoreGeometry(w);
|
||||
} else if (event->type() == QEvent::Show) {
|
||||
if (m_loggingEnabled && !w->windowTitle().isEmpty()) {
|
||||
logInfo(QString("Opening window: %1").arg(w->windowTitle()));
|
||||
}
|
||||
m_shown.insert(object);
|
||||
} else if (event->type() == QEvent::Close || event->type() == QEvent::DeferredDelete) {
|
||||
// There are situations where a window might be 'closed' without
|
||||
// ever actually having been opened (for example, the Shortcuts Editor
|
||||
// will quietly construct windows to get their shortcuts, and those windows
|
||||
// can later be closed without having been displayed).
|
||||
// We don't want to save the geometry of these windows, or log that they closed,
|
||||
// so we checked to make sure the widget was displayed before proceeding.
|
||||
if (!m_shown.remove(object)) return false;
|
||||
porymapConfig.saveGeometry(w);
|
||||
if (m_loggingEnabled && !w->windowTitle().isEmpty()) {
|
||||
logInfo(QString("Closing window: %1").arg(w->windowTitle()));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,28 @@ QVector<qreal> GridSettings::getDashPattern(uint length) const {
|
|||
}
|
||||
}
|
||||
|
||||
QJsonObject GridSettings::toJson() const {
|
||||
QJsonObject obj;
|
||||
obj["width"] = static_cast<qint64>(this->width);
|
||||
obj["height"] = static_cast<qint64>(this->height);
|
||||
obj["offsetX"] = this->offsetX;
|
||||
obj["offsetY"] = this->offsetY;
|
||||
obj["style"] = getStyleName(this->style);
|
||||
obj["color"] = this->color.name();
|
||||
return obj;
|
||||
}
|
||||
|
||||
GridSettings GridSettings::fromJson(const QJsonObject &obj) {
|
||||
GridSettings settings;
|
||||
settings.width = obj["width"].toInt();
|
||||
settings.height = obj["height"].toInt();
|
||||
settings.offsetX = obj["offsetX"].toInt();
|
||||
settings.offsetY = obj["offsetY"].toInt();
|
||||
settings.style = getStyleFromName(obj["style"].toString());
|
||||
settings.color = QColor(obj["color"].toString());
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
GridSettingsDialog::GridSettingsDialog(QWidget *parent) :
|
||||
|
|
|
|||
|
|
@ -565,7 +565,9 @@ void MapImageExporter::updatePreview(bool forceUpdate) {
|
|||
}
|
||||
progress.close();
|
||||
|
||||
m_previewImage.setColorSpace(Util::toColorSpace(porymapConfig.imageExportColorSpaceId));
|
||||
if (porymapConfig.imageExportColorSpace) {
|
||||
m_previewImage.setColorSpace(QColorSpace(porymapConfig.imageExportColorSpace.value()));
|
||||
}
|
||||
m_preview->setPixmap(QPixmap::fromImage(m_previewImage));
|
||||
m_scene->setSceneRect(m_scene->itemsBoundingRect());
|
||||
scalePreview();
|
||||
|
|
|
|||
|
|
@ -276,7 +276,9 @@ void MetatileImageExporter::updatePreview() {
|
|||
m_layerOrder);
|
||||
}
|
||||
|
||||
m_previewImage.setColorSpace(Util::toColorSpace(porymapConfig.imageExportColorSpaceId));
|
||||
if (porymapConfig.imageExportColorSpace) {
|
||||
m_previewImage.setColorSpace(QColorSpace(porymapConfig.imageExportColorSpace.value()));
|
||||
}
|
||||
m_preview->setPixmap(QPixmap::fromImage(m_previewImage));
|
||||
m_scene->setSceneRect(m_scene->itemsBoundingRect());
|
||||
m_previewUpdateQueued = false;
|
||||
|
|
|
|||
|
|
@ -23,16 +23,17 @@ void MetatileLayersItem::setOrientation(Qt::Orientation orientation) {
|
|||
// Generate a table of tile positions that allows us to map between
|
||||
// the index of a tile in the metatile and its position in this layer view.
|
||||
this->tilePositions.clear();
|
||||
const int numLayers = Metatile::numLayers();
|
||||
if (this->orientation == Qt::Horizontal) {
|
||||
// Tiles are laid out horizontally, with the bottom layer on the left:
|
||||
// 0 1 4 5 8 9
|
||||
// 2 3 6 7 10 11
|
||||
for (int layer = 0; layer < projectConfig.getNumLayersInMetatile(); layer++)
|
||||
for (int layer = 0; layer < numLayers; layer++)
|
||||
for (int y = 0; y < Metatile::tileHeight(); y++)
|
||||
for (int x = 0; x < Metatile::tileWidth(); x++) {
|
||||
this->tilePositions.append(QPoint(x + layer * Metatile::tileWidth(), y));
|
||||
}
|
||||
maxWidth *= projectConfig.getNumLayersInMetatile();
|
||||
maxWidth *= numLayers;
|
||||
} else if (this->orientation == Qt::Vertical) {
|
||||
// Tiles are laid out vertically, with the bottom layer on the bottom:
|
||||
// 8 9
|
||||
|
|
@ -41,12 +42,12 @@ void MetatileLayersItem::setOrientation(Qt::Orientation orientation) {
|
|||
// 6 7
|
||||
// 0 1
|
||||
// 2 3
|
||||
for (int layer = projectConfig.getNumLayersInMetatile() - 1; layer >= 0; layer--)
|
||||
for (int layer = numLayers - 1; layer >= 0; layer--)
|
||||
for (int y = 0; y < Metatile::tileHeight(); y++)
|
||||
for (int x = 0; x < Metatile::tileWidth(); x++) {
|
||||
this->tilePositions.append(QPoint(x, y + layer * Metatile::tileHeight()));
|
||||
}
|
||||
maxHeight *= projectConfig.getNumLayersInMetatile();
|
||||
maxHeight *= numLayers;
|
||||
}
|
||||
setMaxSelectionSize(maxWidth, maxHeight);
|
||||
update();
|
||||
|
|
@ -61,7 +62,7 @@ void MetatileLayersItem::draw() {
|
|||
|
||||
// Draw tile images
|
||||
const Metatile* metatile = getMetatile();
|
||||
int numTiles = qMin(projectConfig.getNumTilesInMetatile(), metatile ? metatile->tiles.length() : 0);
|
||||
int numTiles = qMin(Metatile::maxTiles(), metatile ? metatile->tiles.length() : 0);
|
||||
for (int i = 0; i < numTiles; i++) {
|
||||
Tile tile = metatile->tiles.at(i);
|
||||
QImage tileImage = getPalettedTileImage(tile.tileId,
|
||||
|
|
@ -79,7 +80,7 @@ void MetatileLayersItem::draw() {
|
|||
painter.setPen(Qt::white);
|
||||
const int layerWidth = this->cellWidth * Metatile::tileWidth();
|
||||
const int layerHeight = this->cellHeight * Metatile::tileHeight();
|
||||
for (int i = 1; i < projectConfig.getNumLayersInMetatile(); i++) {
|
||||
for (int i = 1; i < Metatile::numLayers(); i++) {
|
||||
if (this->orientation == Qt::Vertical) {
|
||||
int y = i * layerHeight;
|
||||
painter.drawLine(0, y, layerWidth, y);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "ui_newlayoutdialog.h"
|
||||
#include "config.h"
|
||||
#include "validator.h"
|
||||
#include "eventfilters.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
|
|
@ -40,19 +41,12 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q
|
|||
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLayoutDialog::dialogButtonClicked);
|
||||
|
||||
refresh();
|
||||
|
||||
if (porymapConfig.newLayoutDialogGeometry.isEmpty()){
|
||||
// On first display resize to fit contents a little better
|
||||
adjustSize();
|
||||
} else {
|
||||
restoreGeometry(porymapConfig.newLayoutDialogGeometry);
|
||||
}
|
||||
installEventFilter(new GeometrySaver(this));
|
||||
ui->lineEdit_Name->setFocus();
|
||||
}
|
||||
|
||||
NewLayoutDialog::~NewLayoutDialog()
|
||||
{
|
||||
porymapConfig.newLayoutDialogGeometry = saveGeometry();
|
||||
saveSettings();
|
||||
delete ui;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "ui_newmapdialog.h"
|
||||
#include "config.h"
|
||||
#include "validator.h"
|
||||
#include "eventfilters.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QSet>
|
||||
|
|
@ -61,7 +62,7 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare
|
|||
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked);
|
||||
|
||||
refresh();
|
||||
restoreGeometry(porymapConfig.newMapDialogGeometry);
|
||||
installEventFilter(new GeometrySaver(this));
|
||||
ui->lineEdit_Name->setFocus();
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +92,6 @@ NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapL
|
|||
|
||||
NewMapDialog::~NewMapDialog()
|
||||
{
|
||||
porymapConfig.newMapDialogGeometry = saveGeometry();
|
||||
saveSettings();
|
||||
delete ui;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ PaletteEditor::PaletteEditor(Project *project, Tileset *primaryTileset, Tileset
|
|||
ui->actionRedo->setShortcuts({ui->actionRedo->shortcut(), QKeySequence("Ctrl+Shift+Z")});
|
||||
|
||||
refreshPaletteId();
|
||||
restoreWindowState();
|
||||
installEventFilter(new GeometrySaver(this));
|
||||
}
|
||||
|
||||
PaletteEditor::~PaletteEditor() {
|
||||
|
|
@ -159,13 +159,6 @@ void PaletteEditor::commitEditHistory(int paletteId) {
|
|||
updateEditHistoryActions();
|
||||
}
|
||||
|
||||
void PaletteEditor::restoreWindowState() {
|
||||
logInfo("Restoring palette editor geometry from previous session.");
|
||||
QMap<QString, QByteArray> geometry = porymapConfig.getPaletteEditorGeometry();
|
||||
restoreGeometry(geometry.value("palette_editor_geometry"));
|
||||
restoreState(geometry.value("palette_editor_state"));
|
||||
}
|
||||
|
||||
void PaletteEditor::updateEditHistoryActions() {
|
||||
int paletteId = currentPaletteId();
|
||||
// We have an initial commit that shouldn't be available to Undo, so we ignore that.
|
||||
|
|
@ -279,11 +272,6 @@ void PaletteEditor::setColorInputTitles(bool showUnused) {
|
|||
}
|
||||
|
||||
void PaletteEditor::closeEvent(QCloseEvent*) {
|
||||
porymapConfig.setPaletteEditorGeometry(
|
||||
saveGeometry(),
|
||||
saveState()
|
||||
);
|
||||
|
||||
// Opening the color search window then closing the Palette Editor sets
|
||||
// focus to the main editor window instead of the parent (Tileset Editor).
|
||||
// Make sure the parent is active when we close.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const QString defaultFilepath = "prefabs.json";
|
|||
|
||||
void Prefab::loadPrefabs() {
|
||||
this->items.clear();
|
||||
QString filepath = projectConfig.prefabFilepath;
|
||||
QString filepath = userConfig.prefabsFilepath;
|
||||
if (filepath.isEmpty()) return;
|
||||
|
||||
ParseUtil parser;
|
||||
|
|
@ -87,10 +87,10 @@ void Prefab::loadPrefabs() {
|
|||
}
|
||||
|
||||
void Prefab::savePrefabs() {
|
||||
if (projectConfig.prefabFilepath.isEmpty())
|
||||
projectConfig.prefabFilepath = defaultFilepath;
|
||||
if (userConfig.prefabsFilepath.isEmpty())
|
||||
userConfig.prefabsFilepath = defaultFilepath;
|
||||
|
||||
QString filepath = projectConfig.prefabFilepath;
|
||||
QString filepath = userConfig.prefabsFilepath;
|
||||
|
||||
QFileInfo info(filepath);
|
||||
if (info.isRelative()) {
|
||||
|
|
@ -283,9 +283,9 @@ void Prefab::addPrefab(MetatileSelection selection, Layout *layout, QString name
|
|||
this->updatePrefabUi(layout);
|
||||
}
|
||||
|
||||
bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version, QString filepath) {
|
||||
bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGame::Version version, QString filepath) {
|
||||
// Ensure we have default prefabs for the project's game version.
|
||||
if (version != BaseGameVersion::pokeruby && version != BaseGameVersion::pokeemerald && version != BaseGameVersion::pokefirered)
|
||||
if (version != BaseGame::Version::pokeruby && version != BaseGame::Version::pokeemerald && version != BaseGame::Version::pokefirered)
|
||||
return false;
|
||||
|
||||
if (filepath.isEmpty())
|
||||
|
|
@ -316,17 +316,17 @@ bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version,
|
|||
QMessageBox::question(parent,
|
||||
QApplication::applicationName(),
|
||||
QString("Would you like to import the default prefabs for %1? %2.")
|
||||
.arg(projectConfig.getBaseGameVersionString(version))
|
||||
.arg(BaseGame::versionToString(version))
|
||||
.arg(fileWarning),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
bool acceptedImport = (prompt == QMessageBox::Yes);
|
||||
if (acceptedImport) {
|
||||
// Sets up the default prefabs.json filepath.
|
||||
projectConfig.prefabFilepath = filepath;
|
||||
userConfig.prefabsFilepath = filepath;
|
||||
QFile prefabsFile(absFilepath);
|
||||
if (!prefabsFile.open(QIODevice::WriteOnly)) {
|
||||
projectConfig.prefabFilepath = QString();
|
||||
userConfig.prefabsFilepath = QString();
|
||||
|
||||
logError(QString("Error: Could not open %1 for writing").arg(absFilepath));
|
||||
QMessageBox messageBox(parent);
|
||||
|
|
@ -340,13 +340,13 @@ bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version,
|
|||
ParseUtil parser;
|
||||
QString content;
|
||||
switch (version) {
|
||||
case BaseGameVersion::pokeruby:
|
||||
case BaseGame::Version::pokeruby:
|
||||
content = parser.readTextFile(":/text/prefabs_default_ruby.json");
|
||||
break;
|
||||
case BaseGameVersion::pokefirered:
|
||||
case BaseGame::Version::pokefirered:
|
||||
content = parser.readTextFile(":/text/prefabs_default_firered.json");
|
||||
break;
|
||||
case BaseGameVersion::pokeemerald:
|
||||
case BaseGame::Version::pokeemerald:
|
||||
content = parser.readTextFile(":/text/prefabs_default_emerald.json");
|
||||
break;
|
||||
default:
|
||||
|
|
@ -359,7 +359,7 @@ bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version,
|
|||
this->loadPrefabs();
|
||||
}
|
||||
|
||||
projectConfig.prefabImportPrompted = true;
|
||||
userConfig.prefabsImportPrompted = true;
|
||||
return acceptedImport;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,11 @@ void PreferenceEditor::updateFields() {
|
|||
} else if (porymapConfig.eventSelectionShapeMode == QGraphicsPixmapItem::BoundingRectShape) {
|
||||
ui->radioButton_WithinRect->setChecked(true);
|
||||
}
|
||||
ui->comboBox_ColorSpace->setNumberItem(porymapConfig.imageExportColorSpaceId);
|
||||
if (porymapConfig.imageExportColorSpace) {
|
||||
ui->comboBox_ColorSpace->setNumberItem(porymapConfig.imageExportColorSpace.value());
|
||||
} else {
|
||||
ui->comboBox_ColorSpace->setNumberItem(0);
|
||||
}
|
||||
ui->lineEdit_TextEditorOpenFolder->setText(porymapConfig.textEditorOpenFolder);
|
||||
ui->lineEdit_TextEditorGotoLine->setText(porymapConfig.textEditorGotoLine);
|
||||
ui->checkBox_MonitorProjectFiles->setChecked(porymapConfig.monitorFiles);
|
||||
|
|
@ -103,7 +107,7 @@ void PreferenceEditor::updateFields() {
|
|||
ui->checkBox_StatusWarnings->setChecked(porymapConfig.statusBarLogTypes.find(LogType::LOG_WARN) != logTypeEnd);
|
||||
ui->checkBox_StatusInformation->setChecked(porymapConfig.statusBarLogTypes.find(LogType::LOG_INFO) != logTypeEnd);
|
||||
|
||||
if (/*porymapConfig.displayIdsHexadecimal*/ConfigDisplayIdsHexadecimal) {
|
||||
if (porymapConfig.displayIdsHexadecimal) {
|
||||
ui->radioButton_Hexadecimal->setChecked(true);
|
||||
} else {
|
||||
ui->radioButton_Decimal->setChecked(true);
|
||||
|
|
@ -133,7 +137,12 @@ void PreferenceEditor::saveFields() {
|
|||
emit scriptSettingsChanged(scriptAutocompleteMode);
|
||||
}
|
||||
|
||||
porymapConfig.imageExportColorSpaceId = ui->comboBox_ColorSpace->currentData().toInt();
|
||||
auto colorSpace = magic_enum::enum_cast<QColorSpace::NamedColorSpace>(ui->comboBox_ColorSpace->currentData().toInt());
|
||||
if (colorSpace.has_value()) {
|
||||
porymapConfig.imageExportColorSpace = colorSpace.value();
|
||||
} else {
|
||||
porymapConfig.imageExportColorSpace.reset();
|
||||
}
|
||||
porymapConfig.eventSelectionShapeMode = ui->radioButton_OnSprite->isChecked() ? QGraphicsPixmapItem::MaskShape : QGraphicsPixmapItem::BoundingRectShape;
|
||||
porymapConfig.textEditorOpenFolder = ui->lineEdit_TextEditorOpenFolder->text();
|
||||
porymapConfig.textEditorGotoLine = ui->lineEdit_TextEditorGotoLine->text();
|
||||
|
|
@ -141,7 +150,7 @@ void PreferenceEditor::saveFields() {
|
|||
porymapConfig.checkForUpdates = ui->checkBox_CheckForUpdates->isChecked();
|
||||
porymapConfig.eventDeleteWarningDisabled = ui->checkBox_DisableEventWarning->isChecked();
|
||||
porymapConfig.showProjectLoadingScreen = ui->checkBox_ShowProjectLoadingScreen->isChecked();
|
||||
/*porymapConfig.displayIdsHexadecimal*/ConfigDisplayIdsHexadecimal = ui->radioButton_Hexadecimal->isChecked();
|
||||
porymapConfig.displayIdsHexadecimal = ui->radioButton_Hexadecimal->isChecked();
|
||||
|
||||
porymapConfig.statusBarLogTypes.clear();
|
||||
if (ui->checkBox_StatusErrors->isChecked()) porymapConfig.statusBarLogTypes.insert(LogType::LOG_ERROR);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "filedialog.h"
|
||||
#include "newdefinedialog.h"
|
||||
#include "utility.h"
|
||||
#include "eventfilters.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QFormLayout>
|
||||
|
|
@ -27,9 +28,9 @@ ProjectSettingsEditor::ProjectSettingsEditor(QWidget *parent, Project *project)
|
|||
this->initUi();
|
||||
this->createProjectPathsTable();
|
||||
this->createProjectIdentifiersTable();
|
||||
this->installEventFilter(new GeometrySaver(this));
|
||||
this->connectSignals();
|
||||
this->refresh();
|
||||
this->restoreWindowState();
|
||||
}
|
||||
|
||||
ProjectSettingsEditor::~ProjectSettingsEditor()
|
||||
|
|
@ -110,16 +111,19 @@ void ProjectSettingsEditor::initUi() {
|
|||
ui->comboBox_IconSpecies->addItems(project->speciesNames);
|
||||
ui->comboBox_WarpBehaviors->addItems(project->metatileBehaviorMap.keys());
|
||||
}
|
||||
ui->comboBox_BaseGameVersion->addItems(ProjectConfig::versionStrings);
|
||||
ui->comboBox_BaseGameVersion->addItem("Custom", BaseGame::Version::none);
|
||||
ui->comboBox_BaseGameVersion->addItem(BaseGame::versionToString(BaseGame::Version::pokeruby), BaseGame::Version::pokeruby);
|
||||
ui->comboBox_BaseGameVersion->addItem(BaseGame::versionToString(BaseGame::Version::pokefirered), BaseGame::Version::pokefirered);
|
||||
ui->comboBox_BaseGameVersion->addItem(BaseGame::versionToString(BaseGame::Version::pokeemerald), BaseGame::Version::pokeemerald);
|
||||
ui->comboBox_AttributesSize->addItems({"1", "2", "4"});
|
||||
|
||||
ui->comboBox_EventsTabIcon->addItem("Automatic", "");
|
||||
ui->comboBox_EventsTabIcon->addItem("Brendan (Emerald)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeemerald, 0));
|
||||
ui->comboBox_EventsTabIcon->addItem("Brendan (R/S)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeruby, 0));
|
||||
ui->comboBox_EventsTabIcon->addItem("May (Emerald)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeemerald, 1));
|
||||
ui->comboBox_EventsTabIcon->addItem("May (R/S)", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokeruby, 1));
|
||||
ui->comboBox_EventsTabIcon->addItem("Red", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokefirered, 0));
|
||||
ui->comboBox_EventsTabIcon->addItem("Green", ProjectConfig::getPlayerIconPath(BaseGameVersion::pokefirered, 1));
|
||||
ui->comboBox_EventsTabIcon->addItem("Brendan (Emerald)", BaseGame::getPlayerIconPath(BaseGame::Version::pokeemerald, 0));
|
||||
ui->comboBox_EventsTabIcon->addItem("Brendan (R/S)", BaseGame::getPlayerIconPath(BaseGame::Version::pokeruby, 0));
|
||||
ui->comboBox_EventsTabIcon->addItem("May (Emerald)", BaseGame::getPlayerIconPath(BaseGame::Version::pokeemerald, 1));
|
||||
ui->comboBox_EventsTabIcon->addItem("May (R/S)", BaseGame::getPlayerIconPath(BaseGame::Version::pokeruby, 1));
|
||||
ui->comboBox_EventsTabIcon->addItem("Red", BaseGame::getPlayerIconPath(BaseGame::Version::pokefirered, 0));
|
||||
ui->comboBox_EventsTabIcon->addItem("Green", BaseGame::getPlayerIconPath(BaseGame::Version::pokefirered, 1));
|
||||
ui->comboBox_EventsTabIcon->addItem("Custom", "Custom");
|
||||
connect(ui->comboBox_EventsTabIcon, QOverload<int>::of(&NoScrollComboBox::currentIndexChanged), [this](int index) {
|
||||
bool usingCustom = (index == ui->comboBox_EventsTabIcon->findText("Custom"));
|
||||
|
|
@ -147,12 +151,12 @@ void ProjectSettingsEditor::initUi() {
|
|||
ui->spinBox_Collision->setMaximum(Block::getMaxCollision());
|
||||
ui->spinBox_MaxElevation->setMaximum(Block::getMaxElevation());
|
||||
ui->spinBox_MaxCollision->setMaximum(Block::getMaxCollision());
|
||||
ui->spinBox_MetatileIdMask->setMaximum(Block::maxValue);
|
||||
ui->spinBox_CollisionMask->setMaximum(Block::maxValue);
|
||||
ui->spinBox_ElevationMask->setMaximum(Block::maxValue);
|
||||
ui->spinBox_UnusedTileNormal->setMaximum(Tile::maxValue);
|
||||
ui->spinBox_UnusedTileCovered->setMaximum(Tile::maxValue);
|
||||
ui->spinBox_UnusedTileSplit->setMaximum(Tile::maxValue);
|
||||
ui->spinBox_MetatileIdMask->setMaximum(Block::MaxValue);
|
||||
ui->spinBox_CollisionMask->setMaximum(Block::MaxValue);
|
||||
ui->spinBox_ElevationMask->setMaximum(Block::MaxValue);
|
||||
ui->spinBox_UnusedTileNormal->setMaximum(Tile::MaxValue);
|
||||
ui->spinBox_UnusedTileCovered->setMaximum(Tile::MaxValue);
|
||||
ui->spinBox_UnusedTileSplit->setMaximum(Tile::MaxValue);
|
||||
ui->spinBox_MaxEvents->setMaximum(INT_MAX);
|
||||
ui->spinBox_MapWidth->setMaximum(INT_MAX);
|
||||
ui->spinBox_MapHeight->setMaximum(INT_MAX);
|
||||
|
|
@ -201,6 +205,10 @@ bool ProjectSettingsEditor::disableParsedSetting(QWidget * widget, const QString
|
|||
return false;
|
||||
}
|
||||
|
||||
BaseGame::Version ProjectSettingsEditor::getBaseGameVersion() const {
|
||||
return static_cast<BaseGame::Version>(ui->comboBox_BaseGameVersion->currentData().toInt());
|
||||
}
|
||||
|
||||
// Remember the current settings tab for future sessions
|
||||
void ProjectSettingsEditor::on_mainTabs_tabBarClicked(int index) {
|
||||
porymapConfig.projectSettingsTab = index;
|
||||
|
|
@ -434,13 +442,6 @@ QString ProjectSettingsEditor::chooseProjectFile(const QString &defaultFilepath)
|
|||
return path.remove(0, this->baseDir.length());
|
||||
}
|
||||
|
||||
void ProjectSettingsEditor::restoreWindowState() {
|
||||
logInfo("Restoring project settings editor geometry from previous session.");
|
||||
const QMap<QString, QByteArray> geometry = porymapConfig.getProjectSettingsEditorGeometry();
|
||||
this->restoreGeometry(geometry.value("project_settings_editor_geometry"));
|
||||
this->restoreState(geometry.value("project_settings_editor_state"));
|
||||
}
|
||||
|
||||
// Set UI states using config data
|
||||
void ProjectSettingsEditor::refresh() {
|
||||
this->refreshing = true; // Block signals
|
||||
|
|
@ -448,12 +449,12 @@ void ProjectSettingsEditor::refresh() {
|
|||
// Set combo box texts
|
||||
ui->comboBox_DefaultPrimaryTileset->setTextItem(projectConfig.defaultPrimaryTileset);
|
||||
ui->comboBox_DefaultSecondaryTileset->setTextItem(projectConfig.defaultSecondaryTileset);
|
||||
ui->comboBox_BaseGameVersion->setTextItem(projectConfig.getBaseGameVersionString());
|
||||
ui->comboBox_BaseGameVersion->setNumberItem(projectConfig.baseGameVersion);
|
||||
ui->comboBox_AttributesSize->setTextItem(QString::number(projectConfig.metatileAttributesSize));
|
||||
this->updateAttributeLimits(ui->comboBox_AttributesSize->currentText());
|
||||
|
||||
this->prevIconSpecies = QString();
|
||||
this->editedPokemonIconPaths = projectConfig.getPokemonIconPaths();
|
||||
this->editedPokemonIconPaths = projectConfig.pokemonIconPaths;
|
||||
this->updatePokemonIconPath(ui->comboBox_IconSpecies->currentText());
|
||||
|
||||
// Set check box states
|
||||
|
|
@ -511,13 +512,13 @@ void ProjectSettingsEditor::refresh() {
|
|||
this->setBorderMetatileIds(true, projectConfig.newMapBorderMetatileIds);
|
||||
|
||||
// Set line edit texts
|
||||
ui->lineEdit_PrefabsPath->setText(projectConfig.prefabFilepath);
|
||||
ui->lineEdit_PrefabsPath->setText(userConfig.prefabsFilepath);
|
||||
ui->lineEdit_CollisionGraphics->setText(projectConfig.collisionSheetPath);
|
||||
ui->lineEdit_ObjectsIcon->setText(projectConfig.getEventIconPath(Event::Group::Object));
|
||||
ui->lineEdit_WarpsIcon->setText(projectConfig.getEventIconPath(Event::Group::Warp));
|
||||
ui->lineEdit_TriggersIcon->setText(projectConfig.getEventIconPath(Event::Group::Coord));
|
||||
ui->lineEdit_BGsIcon->setText(projectConfig.getEventIconPath(Event::Group::Bg));
|
||||
ui->lineEdit_HealLocationsIcon->setText(projectConfig.getEventIconPath(Event::Group::Heal));
|
||||
ui->lineEdit_ObjectsIcon->setText(projectConfig.eventIconPaths.value(Event::Group::Object));
|
||||
ui->lineEdit_WarpsIcon->setText(projectConfig.eventIconPaths.value(Event::Group::Warp));
|
||||
ui->lineEdit_TriggersIcon->setText(projectConfig.eventIconPaths.value(Event::Group::Coord));
|
||||
ui->lineEdit_BGsIcon->setText(projectConfig.eventIconPaths.value(Event::Group::Bg));
|
||||
ui->lineEdit_HealLocationsIcon->setText(projectConfig.eventIconPaths.value(Event::Group::Heal));
|
||||
for (auto lineEdit : ui->scrollAreaContents_ProjectPaths->findChildren<QLineEdit*>())
|
||||
lineEdit->setText(projectConfig.getCustomFilePath(lineEdit->objectName()));
|
||||
for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren<QLineEdit*>())
|
||||
|
|
@ -556,7 +557,7 @@ void ProjectSettingsEditor::save() {
|
|||
// Save combo box settings
|
||||
projectConfig.defaultPrimaryTileset = ui->comboBox_DefaultPrimaryTileset->currentText();
|
||||
projectConfig.defaultSecondaryTileset = ui->comboBox_DefaultSecondaryTileset->currentText();
|
||||
projectConfig.baseGameVersion = projectConfig.stringToBaseGameVersion(ui->comboBox_BaseGameVersion->currentText());
|
||||
projectConfig.baseGameVersion = getBaseGameVersion();
|
||||
projectConfig.metatileAttributesSize = ui->comboBox_AttributesSize->currentText().toInt();
|
||||
|
||||
// Save check box settings
|
||||
|
|
@ -603,13 +604,13 @@ void ProjectSettingsEditor::save() {
|
|||
projectConfig.metatileSelectorWidth = ui->spinBox_MetatileSelectorWidth->value();
|
||||
|
||||
// Save line edit settings
|
||||
projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text();
|
||||
userConfig.prefabsFilepath = ui->lineEdit_PrefabsPath->text();
|
||||
projectConfig.collisionSheetPath = ui->lineEdit_CollisionGraphics->text();
|
||||
projectConfig.setEventIconPath(Event::Group::Object, ui->lineEdit_ObjectsIcon->text());
|
||||
projectConfig.setEventIconPath(Event::Group::Warp, ui->lineEdit_WarpsIcon->text());
|
||||
projectConfig.setEventIconPath(Event::Group::Coord, ui->lineEdit_TriggersIcon->text());
|
||||
projectConfig.setEventIconPath(Event::Group::Bg, ui->lineEdit_BGsIcon->text());
|
||||
projectConfig.setEventIconPath(Event::Group::Heal, ui->lineEdit_HealLocationsIcon->text());
|
||||
projectConfig.eventIconPaths[Event::Group::Object] = ui->lineEdit_ObjectsIcon->text();
|
||||
projectConfig.eventIconPaths[Event::Group::Warp] = ui->lineEdit_WarpsIcon->text();
|
||||
projectConfig.eventIconPaths[Event::Group::Coord] = ui->lineEdit_TriggersIcon->text();
|
||||
projectConfig.eventIconPaths[Event::Group::Bg] = ui->lineEdit_BGsIcon->text();
|
||||
projectConfig.eventIconPaths[Event::Group::Heal] = ui->lineEdit_HealLocationsIcon->text();
|
||||
for (auto lineEdit : ui->scrollAreaContents_ProjectPaths->findChildren<QLineEdit*>())
|
||||
projectConfig.setFilePath(lineEdit->objectName(), lineEdit->text());
|
||||
for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren<QLineEdit*>())
|
||||
|
|
@ -623,7 +624,7 @@ void ProjectSettingsEditor::save() {
|
|||
projectConfig.warpBehaviors.clear();
|
||||
const QStringList behaviorNames = this->getWarpBehaviorsList();
|
||||
for (auto name : behaviorNames)
|
||||
projectConfig.warpBehaviors.append(project->metatileBehaviorMap.value(name));
|
||||
projectConfig.warpBehaviors.insert(project->metatileBehaviorMap.value(name));
|
||||
|
||||
// Save border metatile IDs
|
||||
projectConfig.newMapBorderMetatileIds = this->getBorderMetatileIds(ui->checkBox_EnableCustomBorderSize->isChecked());
|
||||
|
|
@ -633,7 +634,7 @@ void ProjectSettingsEditor::save() {
|
|||
if (this->project->speciesNames.contains(species))
|
||||
this->editedPokemonIconPaths.insert(species, ui->lineEdit_PokemonIcon->text());
|
||||
for (auto i = this->editedPokemonIconPaths.cbegin(), end = this->editedPokemonIconPaths.cend(); i != end; i++)
|
||||
projectConfig.setPokemonIconPath(i.key(), i.value());
|
||||
projectConfig.pokemonIconPaths[i.key()] = i.value();
|
||||
|
||||
QString eventsTabIconPath;
|
||||
QVariant data = ui->comboBox_EventsTabIcon->currentData();
|
||||
|
|
@ -773,9 +774,9 @@ QString ProjectSettingsEditor::stripProjectDir(QString s) {
|
|||
|
||||
void ProjectSettingsEditor::importDefaultPrefabsClicked(bool) {
|
||||
// If the prompt is accepted the prefabs file will be created and its filepath will be saved in the config.
|
||||
BaseGameVersion version = projectConfig.stringToBaseGameVersion(ui->comboBox_BaseGameVersion->currentText());
|
||||
BaseGame::Version version = getBaseGameVersion();
|
||||
if (prefab.tryImportDefaultPrefabs(this, version, ui->lineEdit_PrefabsPath->text())) {
|
||||
ui->lineEdit_PrefabsPath->setText(projectConfig.prefabFilepath); // Refresh with new filepath
|
||||
ui->lineEdit_PrefabsPath->setText(userConfig.prefabsFilepath); // Refresh with new filepath
|
||||
this->hasUnsavedChanges = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -805,18 +806,20 @@ bool ProjectSettingsEditor::promptSaveChanges() {
|
|||
return true;
|
||||
}
|
||||
|
||||
// TODO: Changing the base game version implicitly changes defaults.
|
||||
// If the user declines this prompt, it should revert to the previous setting.
|
||||
bool ProjectSettingsEditor::promptRestoreDefaults() {
|
||||
if (this->refreshing)
|
||||
return false;
|
||||
|
||||
const QString versionText = ui->comboBox_BaseGameVersion->currentText();
|
||||
if (this->prompt(QString("Restore default config settings for %1?").arg(versionText)) == QMessageBox::No)
|
||||
if (this->prompt(QString("Restore default config settings for %1?").arg(ui->comboBox_BaseGameVersion->currentText())) == QMessageBox::No)
|
||||
return false;
|
||||
|
||||
// Restore defaults by resetting config in memory, refreshing the UI, then restoring the config.
|
||||
// Don't want to save changes until user accepts them.
|
||||
// TODO: Maybe give the project settings editor it's own copy of the config then.
|
||||
ProjectConfig tempProject = projectConfig;
|
||||
projectConfig.reset(projectConfig.stringToBaseGameVersion(versionText));
|
||||
projectConfig.setVersionSpecificDefaults(getBaseGameVersion());
|
||||
this->refresh();
|
||||
projectConfig = tempProject;
|
||||
|
||||
|
|
@ -863,11 +866,6 @@ void ProjectSettingsEditor::closeEvent(QCloseEvent* event) {
|
|||
return;
|
||||
}
|
||||
|
||||
porymapConfig.setProjectSettingsEditorGeometry(
|
||||
this->saveGeometry(),
|
||||
this->saveState()
|
||||
);
|
||||
|
||||
if (this->projectNeedsReload) {
|
||||
// Note: Declining this prompt with changes that need a reload may cause problems
|
||||
if (this->prompt("Settings saved, reload project to apply changes?") == QMessageBox::Yes)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "config.h"
|
||||
#include "log.h"
|
||||
#include "utility.h"
|
||||
#include "eventfilters.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDialog>
|
||||
|
|
@ -33,7 +34,7 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) :
|
|||
|
||||
this->configFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_porymap_cfg));
|
||||
this->initShortcuts();
|
||||
this->restoreWindowState();
|
||||
this->installEventFilter(new GeometrySaver(this));
|
||||
}
|
||||
|
||||
RegionMapEditor::~RegionMapEditor()
|
||||
|
|
@ -56,13 +57,6 @@ RegionMapEditor::~RegionMapEditor()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
void RegionMapEditor::restoreWindowState() {
|
||||
logInfo("Restoring region map editor geometry from previous session.");
|
||||
QMap<QString, QByteArray> geometry = porymapConfig.getRegionMapEditorGeometry();
|
||||
this->restoreGeometry(geometry.value("region_map_editor_geometry"));
|
||||
this->restoreState(geometry.value("region_map_editor_state"));
|
||||
}
|
||||
|
||||
void RegionMapEditor::initShortcuts() {
|
||||
auto *shortcut_RM_Options_delete = new Shortcut(
|
||||
{QKeySequence("Del"), QKeySequence("Backspace")}, this, SLOT(on_pushButton_RM_Options_delete_clicked()));
|
||||
|
|
@ -80,7 +74,6 @@ void RegionMapEditor::initShortcuts() {
|
|||
ui->menuEdit->addAction(undoAction);
|
||||
ui->menuEdit->addAction(redoAction);
|
||||
|
||||
shortcutsConfig.load();
|
||||
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
|
||||
applyUserShortcuts();
|
||||
|
||||
|
|
@ -122,42 +115,15 @@ bool RegionMapEditor::saveRegionMapEntries() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void buildEmeraldDefaults(poryjson::Json &json) {
|
||||
ParseUtil parser;
|
||||
QString emeraldDefault = parser.readTextFile(":/text/region_map_default_emerald.json");
|
||||
json = poryjson::Json::parse(emeraldDefault);
|
||||
}
|
||||
|
||||
void buildRubyDefaults(poryjson::Json &json) {
|
||||
ParseUtil parser;
|
||||
QString emeraldDefault = parser.readTextFile(":/text/region_map_default_ruby.json");
|
||||
json = poryjson::Json::parse(emeraldDefault);
|
||||
}
|
||||
|
||||
void buildFireredDefaults(poryjson::Json &json) {
|
||||
|
||||
ParseUtil parser;
|
||||
QString fireredDefault = parser.readTextFile(":/text/region_map_default_firered.json");
|
||||
json = poryjson::Json::parse(fireredDefault);
|
||||
}
|
||||
|
||||
poryjson::Json RegionMapEditor::buildDefaultJson() {
|
||||
poryjson::Json defaultJson;
|
||||
switch (projectConfig.baseGameVersion) {
|
||||
case BaseGameVersion::pokeemerald:
|
||||
buildEmeraldDefaults(defaultJson);
|
||||
break;
|
||||
case BaseGameVersion::pokeruby:
|
||||
buildRubyDefaults(defaultJson);
|
||||
break;
|
||||
case BaseGameVersion::pokefirered:
|
||||
buildFireredDefaults(defaultJson);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return defaultJson;
|
||||
static const QMap<BaseGame::Version, QString> versionToJsonPath = {
|
||||
{BaseGame::Version::pokeemerald, QStringLiteral(":/text/region_map_default_emerald.json")},
|
||||
{BaseGame::Version::pokeruby, QStringLiteral(":/text/region_map_default_ruby.json")},
|
||||
{BaseGame::Version::pokefirered, QStringLiteral(":/text/region_map_default_firered.json")},
|
||||
};
|
||||
const QString path = versionToJsonPath.value(projectConfig.baseGameVersion);
|
||||
if (path.isEmpty()) { Q_ASSERT(false); return OrderedJson::object(); }
|
||||
return OrderedJson::parse(ParseUtil::readTextFile(path));
|
||||
}
|
||||
|
||||
bool RegionMapEditor::buildConfigDialog() {
|
||||
|
|
@ -283,18 +249,22 @@ bool RegionMapEditor::buildConfigDialog() {
|
|||
|
||||
|
||||
// for sake of convenience, option to just use defaults for each basegame version
|
||||
// TODO: Each version's default settings should be available regardless of the base game version,
|
||||
// it can just suggest which default settings to use depending on the base game version.
|
||||
QPushButton *config_useProjectDefault = nullptr;
|
||||
switch (projectConfig.baseGameVersion) {
|
||||
case BaseGameVersion::pokefirered:
|
||||
case BaseGame::Version::pokefirered:
|
||||
config_useProjectDefault = new QPushButton("\nUse pokefirered defaults\n");
|
||||
break;
|
||||
case BaseGameVersion::pokeemerald:
|
||||
case BaseGame::Version::pokeemerald:
|
||||
config_useProjectDefault = new QPushButton("\nUse pokeemerald defaults\n");
|
||||
break;
|
||||
case BaseGameVersion::pokeruby:
|
||||
case BaseGame::Version::pokeruby:
|
||||
config_useProjectDefault = new QPushButton("\nUse pokeruby defaults\n");
|
||||
break;
|
||||
default:
|
||||
config_useProjectDefault = new QPushButton("\nNo default settings available\n");
|
||||
config_useProjectDefault->setEnabled(false);
|
||||
break;
|
||||
}
|
||||
form.addRow(config_useProjectDefault);
|
||||
|
|
@ -1251,11 +1221,6 @@ void RegionMapEditor::closeEvent(QCloseEvent *event)
|
|||
} else {
|
||||
event->accept();
|
||||
}
|
||||
|
||||
porymapConfig.setRegionMapEditorGeometry(
|
||||
this->saveGeometry(),
|
||||
this->saveState()
|
||||
);
|
||||
}
|
||||
|
||||
void RegionMapEditor::on_verticalSlider_Zoom_Map_Image_valueChanged(int val) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "multikeyedit.h"
|
||||
#include "message.h"
|
||||
#include "log.h"
|
||||
#include "eventfilters.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QFormLayout>
|
||||
|
|
@ -22,6 +23,7 @@ ShortcutsEditor::ShortcutsEditor(QWidget *parent) :
|
|||
{
|
||||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
installEventFilter(new GeometrySaver(this));
|
||||
main_container = ui->scrollAreaWidgetContents_Shortcuts;
|
||||
auto *main_layout = new QVBoxLayout(main_container);
|
||||
main_layout->setSpacing(12);
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
|
|||
|
||||
ActiveWindowFilter *filter = new ActiveWindowFilter(this);
|
||||
connect(filter, &ActiveWindowFilter::activated, this, &TilesetEditor::onWindowActivated);
|
||||
this->installEventFilter(filter);
|
||||
installEventFilter(filter);
|
||||
|
||||
setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label);
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent)
|
|||
initShortcuts();
|
||||
setMetatileLayerOrientation(porymapConfig.tilesetEditorLayerOrientation);
|
||||
this->metatileSelector->select(0);
|
||||
restoreWindowState();
|
||||
installEventFilter(new GeometrySaver(this));
|
||||
}
|
||||
|
||||
TilesetEditor::~TilesetEditor()
|
||||
|
|
@ -233,7 +233,7 @@ void TilesetEditor::setMetatileLayerOrientation(Qt::Orientation orientation) {
|
|||
|
||||
int numTilesWide = Metatile::tileWidth();
|
||||
int numTilesTall = Metatile::tileHeight();
|
||||
int numLayers = projectConfig.getNumLayersInMetatile();
|
||||
int numLayers = Metatile::numLayers();
|
||||
if (horizontal) {
|
||||
numTilesWide *= numLayers;
|
||||
} else {
|
||||
|
|
@ -339,8 +339,6 @@ void TilesetEditor::initSelectedTileItem() {
|
|||
|
||||
void TilesetEditor::initShortcuts() {
|
||||
initExtraShortcuts();
|
||||
|
||||
shortcutsConfig.load();
|
||||
shortcutsConfig.setDefaultShortcuts(shortcutableObjects());
|
||||
applyUserShortcuts();
|
||||
}
|
||||
|
|
@ -379,14 +377,6 @@ void TilesetEditor::applyUserShortcuts() {
|
|||
shortcut->setKeys(shortcutsConfig.userShortcuts(shortcut));
|
||||
}
|
||||
|
||||
void TilesetEditor::restoreWindowState() {
|
||||
logInfo("Restoring tileset editor geometry from previous session.");
|
||||
QMap<QString, QByteArray> geometry = porymapConfig.getTilesetEditorGeometry();
|
||||
this->restoreGeometry(geometry.value("tileset_editor_geometry"));
|
||||
this->restoreState(geometry.value("tileset_editor_state"));
|
||||
this->ui->splitter->restoreState(geometry.value("tileset_editor_splitter_state"));
|
||||
}
|
||||
|
||||
void TilesetEditor::onWindowActivated() {
|
||||
// User may have made layout edits since window was last focused, so update counts
|
||||
if (this->metatileSelector) {
|
||||
|
|
@ -853,11 +843,6 @@ void TilesetEditor::closeEvent(QCloseEvent *event)
|
|||
|
||||
if (event->isAccepted()) {
|
||||
if (this->paletteEditor) this->paletteEditor->close();
|
||||
porymapConfig.setTilesetEditorGeometry(
|
||||
this->saveGeometry(),
|
||||
this->saveState(),
|
||||
this->ui->splitter->saveState()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -930,7 +915,7 @@ bool TilesetEditor::replaceMetatile(uint16_t metatileId, const Metatile &src, QS
|
|||
|
||||
// Update tile usage if any tiles changed
|
||||
if (this->tileSelector && this->tileSelector->showUnused) {
|
||||
int numTiles = projectConfig.getNumTilesInMetatile();
|
||||
int numTiles = qMin(src.tiles.length(), dest->tiles.length());
|
||||
for (int i = 0; i < numTiles; i++) {
|
||||
if (src.tiles[i].tileId != dest->tiles[i].tileId) {
|
||||
this->tileSelector->usedTiles[src.tiles[i].tileId] += 1;
|
||||
|
|
@ -995,7 +980,7 @@ void TilesetEditor::on_actionRedo_triggered() {
|
|||
void TilesetEditor::on_actionCut_triggered()
|
||||
{
|
||||
this->copyMetatile(true);
|
||||
this->pasteMetatile(Metatile(projectConfig.getNumTilesInMetatile()), "");
|
||||
this->pasteMetatile(Metatile(Metatile::maxTiles()), "");
|
||||
}
|
||||
|
||||
void TilesetEditor::on_actionCopy_triggered()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "ui_updatepromoter.h"
|
||||
#include "log.h"
|
||||
#include "config.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "config.h"
|
||||
#include "utility.h"
|
||||
#include "message.h"
|
||||
#include "eventfilters.h"
|
||||
|
||||
static const QString baseWindowTitle = QString("Wild Pokémon Summary Charts");
|
||||
|
||||
|
|
@ -25,6 +26,7 @@ WildMonChart::WildMonChart(QWidget *parent, const EncounterTableModel *table) :
|
|||
ui->setupUi(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowFlags(Qt::Window);
|
||||
installEventFilter(new GeometrySaver(this));
|
||||
|
||||
connect(ui->button_Help, &QAbstractButton::clicked, this, &WildMonChart::showHelpDialog);
|
||||
|
||||
|
|
@ -49,8 +51,6 @@ WildMonChart::WildMonChart(QWidget *parent, const EncounterTableModel *table) :
|
|||
porymapConfig.wildMonChartTheme = ui->comboBox_Theme->currentText();
|
||||
}
|
||||
|
||||
restoreGeometry(porymapConfig.wildMonChartGeometry);
|
||||
|
||||
setTable(table);
|
||||
};
|
||||
|
||||
|
|
@ -461,9 +461,4 @@ void WildMonChart::showHelpDialog() {
|
|||
InfoMessage::show(text, informativeText, this);
|
||||
}
|
||||
|
||||
void WildMonChart::closeEvent(QCloseEvent *event) {
|
||||
porymapConfig.wildMonChartGeometry = saveGeometry();
|
||||
QWidget::closeEvent(event);
|
||||
}
|
||||
|
||||
#endif // QT_CHARTS_LIB
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user