mirror of
https://github.com/huderlem/porymap.git
synced 2026-03-21 17:45:44 -05:00
Split config files
This commit is contained in:
parent
463803292f
commit
fc15f2a27c
595
include/config.h
595
include/config.h
|
|
@ -1,595 +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 "converter.h"
|
||||
#include "fieldmanager.h"
|
||||
#include "events.h"
|
||||
#include "gridsettings.h"
|
||||
#include "scriptsettings.h"
|
||||
#include "log.h"
|
||||
#include "basegame.h"
|
||||
#include "orderedset.h"
|
||||
#include "block.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)
|
||||
|
||||
enum ScriptAutocompleteMode {
|
||||
MapOnly,
|
||||
MapAndCommon,
|
||||
All,
|
||||
};
|
||||
|
||||
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 = true;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
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(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;
|
||||
|
||||
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;
|
||||
|
||||
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 // 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"
|
||||
51
include/config/keyvalueconfigbase.h
Normal file
51
include/config/keyvalueconfigbase.h
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
#ifndef KEYVALUECONFIGBASE_H
|
||||
#define KEYVALUECONFIGBASE_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QVersionNumber>
|
||||
|
||||
#include "fieldmanager.h"
|
||||
|
||||
extern const QVersionNumber porymapVersion;
|
||||
|
||||
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
|
||||
17
porymap.pro
17
porymap.pro
|
|
@ -46,8 +46,13 @@ 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/core/bitpacker.cpp \
|
||||
|
|
@ -153,7 +158,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 \
|
||||
|
|
@ -165,7 +169,12 @@ 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 \
|
||||
|
|
@ -275,7 +284,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 \
|
||||
|
|
@ -333,6 +341,7 @@ RESOURCES += \
|
|||
resources/text.qrc
|
||||
|
||||
INCLUDEPATH += include
|
||||
INCLUDEPATH += include/config
|
||||
INCLUDEPATH += include/core
|
||||
INCLUDEPATH += include/ui
|
||||
INCLUDEPATH += include/lib
|
||||
|
|
|
|||
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>
|
||||
|
||||
const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_VERSION);
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
|
@ -1,25 +1,15 @@
|
|||
#include "config.h"
|
||||
#include "shortcut.h"
|
||||
#include "map.h"
|
||||
#include "validator.h"
|
||||
#include "projectconfig.h"
|
||||
#include "utility.h"
|
||||
#include "metatile.h"
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFormLayout>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QList>
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
#include <QTextStream>
|
||||
#include <QRegularExpression>
|
||||
#include <QAction>
|
||||
#include <QAbstractButton>
|
||||
#include <QMainWindow>
|
||||
#include <QSplitter>
|
||||
#include "validator.h"
|
||||
|
||||
const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_VERSION);
|
||||
#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
|
||||
|
|
@ -147,209 +137,6 @@ ProjectFilePath ProjectConfig::reverseDefaultPaths(const QString& str) {
|
|||
return static_cast<ProjectFilePath>(-1);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// TODO: This should eventually be contained by each individual Project instance.
|
||||
ProjectConfig projectConfig;
|
||||
|
||||
void ProjectConfig::setVersionSpecificDefaults(BaseGame::Version version) {
|
||||
this->baseGameVersion = version;
|
||||
|
|
@ -578,123 +365,3 @@ QString ProjectConfig::getIdentifier(ProjectIdentifier id) {
|
|||
return customText;
|
||||
return defaultIdentifiers.contains(id) ? defaultIdentifiers[id].second : QString();
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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 { };
|
||||
}
|
||||
}
|
||||
107
src/config/shortcutsconfig.cpp
Normal file
107
src/config/shortcutsconfig.cpp
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#include "shortcutsconfig.h"
|
||||
#include "shortcut.h"
|
||||
|
||||
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();
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user