Merge pull request from garakmon/closeproject

Fix Open Project & Monitor Changed Project Files
This commit is contained in:
huderlem 2020-04-25 16:28:02 -05:00 committed by GitHub
commit c0674d9676
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 374 additions and 65 deletions

View file

@ -561,7 +561,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>545</width>
<width>542</width>
<height>628</height>
</rect>
</property>
@ -767,7 +767,7 @@
<rect>
<x>8</x>
<y>0</y>
<width>221</width>
<width>224</width>
<height>324</height>
</rect>
</property>
@ -1118,7 +1118,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>231</width>
<width>234</width>
<height>83</height>
</rect>
</property>
@ -2872,6 +2872,7 @@
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
@ -2886,6 +2887,7 @@
<string>File</string>
</property>
<addaction name="action_Open_Project"/>
<addaction name="action_Reload_Project"/>
<addaction name="action_Save"/>
<addaction name="action_Save_Project"/>
<addaction name="separator"/>
@ -2935,13 +2937,21 @@
</property>
<addaction name="actionAbout_Porymap"/>
</widget>
<widget class="QMenu" name="menuOptions">
<property name="title">
<string>Options</string>
</property>
<addaction name="actionUse_Encounter_Json"/>
<addaction name="actionMonitor_Project_Files"/>
<addaction name="actionUse_Poryscript"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
<addaction name="menuView"/>
<addaction name="menuTools"/>
<addaction name="menuOptions"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="action_Save_Project">
<property name="text">
<string>Save All</string>
@ -3173,6 +3183,35 @@
<string>Themes...</string>
</property>
</action>
<action name="action_Reload_Project">
<property name="text">
<string>Reload Project</string>
</property>
</action>
<action name="actionUse_Encounter_Json">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Show Wild Encounter Tables</string>
</property>
</action>
<action name="actionMonitor_Project_Files">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Monitor Project Files</string>
</property>
</action>
<action name="actionUse_Poryscript">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Use PoryScript</string>
</property>
</action>
<action name="actionExport_Stitched_Map_Image">
<property name="text">
<string>Export Map Stitch Image...</string>

View file

@ -18,6 +18,7 @@ public:
void save();
void load();
virtual ~KeyValueConfigBase();
virtual void reset() = 0;
protected:
virtual QString getConfigFilepath() = 0;
virtual void parseConfigKeyValue(QString key, QString value) = 0;
@ -29,6 +30,9 @@ class PorymapConfig: public KeyValueConfigBase
{
public:
PorymapConfig() {
reset();
}
virtual void reset() override {
this->recentProject = "";
this->recentMap = "";
this->mapSortOrder = MapSortOrder::Group;
@ -37,6 +41,7 @@ public:
this->metatilesZoom = 30;
this->showPlayerView = false;
this->showCursorTile = true;
this->monitorFiles = true;
this->regionMapDimensions = QSize(32, 20);
this->theme = "default";
}
@ -49,6 +54,7 @@ public:
void setMetatilesZoom(int zoom);
void setShowPlayerView(bool enabled);
void setShowCursorTile(bool enabled);
void setMonitorFiles(bool monitor);
void setRegionMapDimensions(int width, int height);
void setTheme(QString theme);
QString getRecentProject();
@ -60,13 +66,14 @@ public:
int getMetatilesZoom();
bool getShowPlayerView();
bool getShowCursorTile();
bool getMonitorFiles();
QSize getRegionMapDimensions();
QString getTheme();
protected:
QString getConfigFilepath();
void parseConfigKeyValue(QString key, QString value);
QMap<QString, QString> getKeyValueMap();
void onNewConfigFileCreated() {}
virtual QString getConfigFilepath() override;
virtual void parseConfigKeyValue(QString key, QString value) override;
virtual QMap<QString, QString> getKeyValueMap() override;
virtual void onNewConfigFileCreated() override {}
private:
QString recentProject;
QString recentMap;
@ -83,6 +90,7 @@ private:
int metatilesZoom;
bool showPlayerView;
bool showCursorTile;
bool monitorFiles;
QSize regionMapDimensions;
QString theme;
};
@ -99,8 +107,12 @@ class ProjectConfig: public KeyValueConfigBase
{
public:
ProjectConfig() {
reset();
}
virtual void reset() override {
this->baseGameVersion = BaseGameVersion::pokeemerald;
this->useEncounterJson = true;
this->useCustomBorderSize = false;
}
void setBaseGameVersion(BaseGameVersion baseGameVersion);
BaseGameVersion getBaseGameVersion();
@ -112,10 +124,10 @@ public:
void setUseCustomBorderSize(bool enable);
bool getUseCustomBorderSize();
protected:
QString getConfigFilepath();
void parseConfigKeyValue(QString key, QString value);
QMap<QString, QString> getKeyValueMap();
void onNewConfigFileCreated();
virtual QString getConfigFilepath() override;
virtual void parseConfigKeyValue(QString key, QString value) override;
virtual QMap<QString, QString> getKeyValueMap() override;
virtual void onNewConfigFileCreated() override;
private:
BaseGameVersion baseGameVersion;
QString projectDir;

View file

@ -31,6 +31,12 @@ class Editor : public QObject
Q_OBJECT
public:
Editor(Ui::MainWindow* ui);
~Editor();
Editor() = delete;
Editor(const Editor &) = delete;
Editor & operator = (const Editor &) = delete;
public:
Ui::MainWindow* ui;
QObject *parent = nullptr;

View file

@ -30,14 +30,19 @@ class MainWindow : public QMainWindow
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
explicit MainWindow(QWidget *parent);
~MainWindow();
MainWindow() = delete;
MainWindow(const MainWindow &) = delete;
MainWindow & operator = (const MainWindow &) = delete;
public slots:
void scaleMapView(int);
private slots:
void on_action_Open_Project_triggered();
void on_action_Reload_Project_triggered();
void on_mapList_activated(const QModelIndex &index);
void on_action_Save_Project_triggered();
void openWarpMap(QString map_name, QString warp_num);
@ -69,6 +74,9 @@ private slots:
void on_checkBox_AllowBiking_clicked(bool checked);
void on_checkBox_AllowEscapeRope_clicked(bool checked);
void on_spinBox_FloorNumber_valueChanged(int offset);
void on_actionUse_Encounter_Json_triggered(bool checked);
void on_actionMonitor_Project_Files_triggered(bool checked);
void on_actionUse_Poryscript_triggered(bool checked);
void on_tabWidget_currentChanged(int index);
@ -189,6 +197,7 @@ private:
bool setMap(QString, bool scrollTreeView = false);
void redrawMapScene();
bool loadDataStructures();
bool loadProjectCombos();
bool populateMapList();
void sortMapList();
QString getExistingDirectory(QString);

View file

@ -15,17 +15,25 @@
#include <QPair>
#include <QStandardItem>
#include <QVariant>
#include <QFileSystemWatcher>
static QString NONE_MAP_CONSTANT = "MAP_NONE";
static QString NONE_MAP_NAME = "None";
class Project
class Project : public QObject
{
Q_OBJECT
public:
Project(QWidget *parent = nullptr);
~Project();
Project(const Project &) = delete;
Project & operator = (const Project &) = delete;
public:
Project();
QString root;
QStringList *groupNames = nullptr;
QMap<QString, int> *map_groups;
QMap<QString, int> *mapGroups;
QList<QStringList> groupedMapNames;
QStringList *mapNames = nullptr;
QMap<QString, QVariant> miscConstants;
@ -55,9 +63,16 @@ public:
QMap<int, QString> metatileBehaviorMapInverse;
QMap<QString, QString> facingDirections;
ParseUtil parser;
QFileSystemWatcher fileWatcher;
QMap<QString, qint64> modifiedFileTimestamps;
void set_root(QString);
void initSignals();
void clearMapCache();
void clearTilesetCache();
struct DataQualifiers
{
bool isStatic;
@ -66,11 +81,11 @@ public:
DataQualifiers getDataQualifiers(QString, QString);
QMap<QString, DataQualifiers> dataQualifiers;
QMap<QString, Map*> *map_cache;
QMap<QString, Map*> *mapCache;
Map* loadMap(QString);
Map* getMap(QString);
QMap<QString, Tileset*> *tileset_cache = nullptr;
QMap<QString, Tileset*> *tilesetCache = nullptr;
Tileset* loadTileset(QString, Tileset *tileset = nullptr);
Tileset* getTileset(QString, bool forceLoad = false);
QMap<QString, QStringList> tilesetLabels;
@ -180,12 +195,20 @@ private:
void setNewMapEvents(Map *map);
void setNewMapConnections(Map *map);
void ignoreWatchedFileTemporarily(QString filepath);
static int num_tiles_primary;
static int num_tiles_total;
static int num_metatiles_primary;
static int num_metatiles_total;
static int num_pals_primary;
static int num_pals_total;
QWidget *parent;
signals:
void reloadProject();
void uncheckMonitorFilesAction();
};
#endif // PROJECT_H

View file

@ -17,6 +17,7 @@ KeyValueConfigBase::~KeyValueConfigBase() {
}
void KeyValueConfigBase::load() {
reset();
QFile file(this->getConfigFilepath());
if (!file.exists()) {
if (!file.open(QIODevice::WriteOnly)) {
@ -156,6 +157,12 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
if (!ok) {
logWarn(QString("Invalid config value for show_cursor_tile: '%1'. Must be 0 or 1.").arg(value));
}
} else if (key == "monitor_files") {
bool ok;
this->monitorFiles = value.toInt(&ok);
if (!ok) {
logWarn(QString("Invalid config value for monitor_files: '%1'. Must be 0 or 1.").arg(value));
}
} else if (key == "region_map_dimensions") {
bool ok1, ok2;
QStringList dims = value.split("x");
@ -189,6 +196,7 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
map.insert("metatiles_zoom", QString("%1").arg(this->metatilesZoom));
map.insert("show_player_view", this->showPlayerView ? "1" : "0");
map.insert("show_cursor_tile", this->showCursorTile ? "1" : "0");
map.insert("monitor_files", this->monitorFiles ? "1" : "0");
map.insert("region_map_dimensions", QString("%1x%2").arg(this->regionMapDimensions.width())
.arg(this->regionMapDimensions.height()));
map.insert("theme", this->theme);
@ -232,6 +240,11 @@ void PorymapConfig::setPrettyCursors(bool enabled) {
this->save();
}
void PorymapConfig::setMonitorFiles(bool monitor) {
this->monitorFiles = monitor;
this->save();
}
void PorymapConfig::setGeometry(QByteArray windowGeometry_, QByteArray windowState_, QByteArray mapSplitterState_,
QByteArray eventsSlpitterState_, QByteArray mainSplitterState_) {
this->windowGeometry = windowGeometry_;
@ -314,6 +327,10 @@ bool PorymapConfig::getShowCursorTile() {
return this->showCursorTile;
}
bool PorymapConfig::getMonitorFiles() {
return this->monitorFiles;
}
QSize PorymapConfig::getRegionMapDimensions() {
return this->regionMapDimensions;
}

View file

@ -23,6 +23,16 @@ Editor::Editor(Ui::MainWindow* ui)
this->cursorMapTileRect = new CursorTileRect(&this->settings->cursorTileRectEnabled, qRgb(255, 255, 255));
}
Editor::~Editor()
{
delete this->selected_events;
delete this->settings;
delete this->playerViewRect;
delete this->cursorMapTileRect;
closeProject();
}
void Editor::saveProject() {
if (project) {
saveUiFields();

View file

@ -5,7 +5,7 @@ int main(int argc, char *argv[])
{
QApplication a(argc, argv);
a.setStyle("fusion");
MainWindow w;
MainWindow w(nullptr);
w.show();
return a.exec();

View file

@ -28,6 +28,7 @@
#include <QSysInfo>
#include <QDesktopServices>
#include <QMatrix>
#include <QSignalBlocker>
#include <QSet>
MainWindow::MainWindow(QWidget *parent) :
@ -43,7 +44,7 @@ MainWindow::MainWindow(QWidget *parent) :
QCoreApplication::setOrganizationName("pret");
QCoreApplication::setApplicationName("porymap");
QApplication::setApplicationDisplayName("porymap");
QApplication::setWindowIcon(QIcon(":/icons/porymap-icon-1.ico"));
QApplication::setWindowIcon(QIcon(":/icons/porymap-icon-2.ico"));
ui->setupUi(this);
this->initWindow();
@ -151,8 +152,10 @@ void MainWindow::initMapSortOrder() {
void MainWindow::setProjectSpecificUIVisibility()
{
if (!projectConfig.getEncounterJsonActive())
ui->tabWidget->removeTab(4);
ui->tabWidget->setTabEnabled(4, projectConfig.getEncounterJsonActive());
ui->actionUse_Encounter_Json->setChecked(projectConfig.getEncounterJsonActive());
ui->actionUse_Poryscript->setChecked(projectConfig.getUsePoryScript());
switch (projectConfig.getBaseGameVersion())
{
@ -251,6 +254,7 @@ void MainWindow::loadUserSettings() {
ui->horizontalSlider_MetatileZoom->blockSignals(true);
ui->horizontalSlider_MetatileZoom->setValue(porymapConfig.getMetatilesZoom());
ui->horizontalSlider_MetatileZoom->blockSignals(false);
ui->actionMonitor_Project_Files->setChecked(porymapConfig.getMonitorFiles());
setTheme(porymapConfig.getTheme());
}
@ -303,13 +307,21 @@ bool MainWindow::openProject(QString dir) {
bool already_open = isProjectOpen() && (editor->project->root == dir);
if (!already_open) {
editor->project = new Project;
editor->closeProject();
editor->project = new Project(this);
QObject::connect(editor->project, SIGNAL(reloadProject()), this, SLOT(on_action_Reload_Project_triggered()));
QObject::connect(editor->project, &Project::uncheckMonitorFilesAction, [this] () { ui->actionMonitor_Project_Files->setChecked(false); });
on_actionMonitor_Project_Files_triggered(porymapConfig.getMonitorFiles());
editor->project->set_root(dir);
success = loadDataStructures()
&& populateMapList()
&& setMap(getDefaultMap(), true);
} else {
success = loadDataStructures() && populateMapList();
QString open_map = editor->map->name;
editor->project->fileWatcher.removePaths(editor->project->fileWatcher.files());
editor->project->clearMapCache();
editor->project->clearTilesetCache();
success = loadDataStructures() && populateMapList() && setMap(open_map, true);
}
if (success) {
@ -376,6 +388,19 @@ void MainWindow::on_action_Open_Project_triggered()
}
}
void MainWindow::on_action_Reload_Project_triggered() {
// TODO: when undo history is complete show only if has unsaved changes
QMessageBox warning(this);
warning.setText("WARNING");
warning.setInformativeText("Reloading this project will discard any unsaved changes.");
warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
warning.setIcon(QMessageBox::Warning);
if (warning.exec() == QMessageBox::Ok) {
openProject(editor->project->root);
}
}
bool MainWindow::setMap(QString map_name, bool scrollTreeView) {
logInfo(QString("Setting map to '%1'").arg(map_name));
if (map_name.isEmpty()) {
@ -663,14 +688,25 @@ bool MainWindow::loadDataStructures() {
success = success
&& project->readSecretBaseIds()
&& project->readCoordEventWeatherNames();
if (!success) {
return false;
}
return success && loadProjectCombos();
}
bool MainWindow::loadProjectCombos() {
// set up project ui comboboxes
QStringList songs = project->getSongNames();
Project *project = editor->project;
// Block signals to the comboboxes while they are being modified
const QSignalBlocker blocker1(ui->comboBox_Song);
const QSignalBlocker blocker2(ui->comboBox_Location);
const QSignalBlocker blocker3(ui->comboBox_PrimaryTileset);
const QSignalBlocker blocker4(ui->comboBox_SecondaryTileset);
const QSignalBlocker blocker5(ui->comboBox_Weather);
const QSignalBlocker blocker6(ui->comboBox_BattleScene);
const QSignalBlocker blocker7(ui->comboBox_Type);
ui->comboBox_Song->clear();
ui->comboBox_Song->addItems(songs);
ui->comboBox_Song->addItems(project->getSongNames());
ui->comboBox_Location->clear();
ui->comboBox_Location->addItems(project->mapSectionValueToName.values());
@ -678,6 +714,7 @@ bool MainWindow::loadDataStructures() {
if (tilesets.isEmpty()) {
return false;
}
ui->comboBox_PrimaryTileset->clear();
ui->comboBox_PrimaryTileset->addItems(tilesets.value("primary"));
ui->comboBox_SecondaryTileset->clear();
@ -688,6 +725,7 @@ bool MainWindow::loadDataStructures() {
ui->comboBox_BattleScene->addItems(*project->mapBattleScenes);
ui->comboBox_Type->clear();
ui->comboBox_Type->addItems(*project->mapTypes);
return true;
}
@ -1104,10 +1142,10 @@ void MainWindow::drawMapListIcons(QAbstractItemModel *model) {
QVariant data = index.data(Qt::UserRole);
if (!data.isNull()) {
QString map_name = data.toString();
if (editor->project && editor->project->map_cache->contains(map_name)) {
if (editor->project && editor->project->mapCache->contains(map_name)) {
QStandardItem *map = mapListModel->itemFromIndex(mapListIndexes.value(map_name));
map->setIcon(*mapIcon);
if (editor->project->map_cache->value(map_name)->hasUnsavedChanges()) {
if (editor->project->mapCache->value(map_name)->hasUnsavedChanges()) {
map->setIcon(*mapEditedIcon);
projectHasUnsavedChanges = true;
}
@ -1216,6 +1254,25 @@ void MainWindow::on_actionCursor_Tile_Outline_triggered()
this->editor->settings->cursorTileRectEnabled = enabled;
}
void MainWindow::on_actionUse_Encounter_Json_triggered(bool checked)
{
QMessageBox warning(this);
warning.setText("You must reload the project for this setting to take effect.");
warning.setIcon(QMessageBox::Information);
warning.exec();
projectConfig.setEncounterJsonActive(checked);
}
void MainWindow::on_actionMonitor_Project_Files_triggered(bool checked)
{
porymapConfig.setMonitorFiles(checked);
}
void MainWindow::on_actionUse_Poryscript_triggered(bool checked)
{
projectConfig.setUsePoryScript(checked);
}
void MainWindow::on_actionPencil_triggered()
{
on_toolButton_Paint_clicked();

View file

@ -34,10 +34,10 @@ int Project::num_metatiles_total = 1024;
int Project::num_pals_primary = 6;
int Project::num_pals_total = 13;
Project::Project()
Project::Project(QWidget *parent) : parent(parent)
{
groupNames = new QStringList;
map_groups = new QMap<QString, int>;
mapGroups = new QMap<QString, int>;
mapNames = new QStringList;
itemNames = new QStringList;
flagNames = new QStringList;
@ -50,10 +50,77 @@ Project::Project()
secretBaseIds = new QStringList;
bgEventFacingDirections = new QStringList;
trainerTypes = new QStringList;
map_cache = new QMap<QString, Map*>;
mapCache = new QMap<QString, Map*>;
mapConstantsToMapNames = new QMap<QString, QString>;
mapNamesToMapConstants = new QMap<QString, QString>;
tileset_cache = new QMap<QString, Tileset*>;
tilesetCache = new QMap<QString, Tileset*>;
initSignals();
}
Project::~Project()
{
delete this->groupNames;
delete this->mapGroups;
delete this->mapNames;
delete this->itemNames;
delete this->flagNames;
delete this->varNames;
delete this->weatherNames;
delete this->coordEventWeatherNames;
delete this->secretBaseIds;
delete this->movementTypes;
delete this->bgEventFacingDirections;
delete this->mapBattleScenes;
delete this->trainerTypes;
delete this->mapTypes;
delete this->mapConstantsToMapNames;
delete this->mapNamesToMapConstants;
clearMapCache();
delete this->mapCache;
clearTilesetCache();
delete this->tilesetCache;
}
void Project::initSignals() {
// detect changes to specific filepaths being monitored
QObject::connect(&fileWatcher, &QFileSystemWatcher::fileChanged, [this](QString changed){
if (!porymapConfig.getMonitorFiles()) return;
if (modifiedFileTimestamps.contains(changed)) {
if (QDateTime::currentMSecsSinceEpoch() < modifiedFileTimestamps[changed]) {
return;
}
modifiedFileTimestamps.remove(changed);
}
static bool showing = false;
if (showing) return;
QMessageBox notice(this->parent);
notice.setText("File Changed");
notice.setInformativeText(QString("The file %1 has changed on disk. Would you like to reload the project?")
.arg(changed.remove(this->root + "/")));
notice.setStandardButtons(QMessageBox::No | QMessageBox::Yes);
notice.setIcon(QMessageBox::Question);
QCheckBox showAgainCheck("Do not ask again.");
notice.setCheckBox(&showAgainCheck);
showing = true;
int choice = notice.exec();
if (choice == QMessageBox::Yes) {
emit reloadProject();
} else if (choice == QMessageBox::No) {
if (showAgainCheck.isChecked()) {
porymapConfig.setMonitorFiles(false);
emit uncheckMonitorFilesAction();
}
}
showing = false;
});
}
void Project::set_root(QString dir) {
@ -69,10 +136,24 @@ QString Project::getProjectTitle() {
}
}
void Project::clearMapCache() {
for (QString mapName : mapCache->keys()) {
Map *map = mapCache->take(mapName);
if (map) delete map;
}
}
void Project::clearTilesetCache() {
for (QString tilesetName : tilesetCache->keys()) {
Tileset *tileset = tilesetCache->take(tilesetName);
if (tileset) delete tileset;
}
}
Map* Project::loadMap(QString map_name) {
Map *map;
if (map_cache->contains(map_name)) {
map = map_cache->value(map_name);
if (mapCache->contains(map_name)) {
map = mapCache->value(map_name);
// TODO: uncomment when undo/redo history is fully implemented for all actions.
if (true/*map->hasUnsavedChanges()*/) {
return map;
@ -87,7 +168,7 @@ Map* Project::loadMap(QString map_name) {
map->commit();
map->metatileHistory.save();
map_cache->insert(map_name, map);
mapCache->insert(map_name, map);
return map;
}
@ -390,8 +471,8 @@ bool Project::loadMapData(Map* map) {
}
QString Project::readMapLayoutId(QString map_name) {
if (map_cache->contains(map_name)) {
return map_cache->value(map_name)->layoutId;
if (mapCache->contains(map_name)) {
return mapCache->value(map_name)->layoutId;
}
QString mapFilepath = QString("%1/data/maps/%2/map.json").arg(root).arg(map_name);
@ -406,8 +487,8 @@ QString Project::readMapLayoutId(QString map_name) {
}
QString Project::readMapLocation(QString map_name) {
if (map_cache->contains(map_name)) {
return map_cache->value(map_name)->location;
if (mapCache->contains(map_name)) {
return mapCache->value(map_name)->location;
}
QString mapFilepath = QString("%1/data/maps/%2/map.json").arg(root).arg(map_name);
@ -473,6 +554,7 @@ bool Project::readMapLayouts() {
mapLayoutsTable.clear();
QString layoutsFilepath = QString("%1/data/layouts/layouts.json").arg(root);
fileWatcher.addPath(layoutsFilepath);
QJsonDocument layoutsDoc;
if (!parser.tryParseJsonFile(&layoutsDoc, layoutsFilepath)) {
logError(QString("Failed to read map layouts from %1").arg(layoutsFilepath));
@ -620,6 +702,8 @@ void Project::saveMapLayouts() {
layoutsArr.push_back(layoutObj);
}
ignoreWatchedFileTemporarily(layoutsFilepath);
layoutsObj["layouts"] = layoutsArr;
OrderedJson layoutJson(layoutsObj);
OrderedJsonDoc jsonDoc(&layoutJson);
@ -627,6 +711,11 @@ void Project::saveMapLayouts() {
layoutsFile.close();
}
void Project::ignoreWatchedFileTemporarily(QString filepath) {
// Ignore any file-change events for this filepath for the next 5 seconds.
modifiedFileTimestamps.insert(filepath, QDateTime::currentMSecsSinceEpoch() + 5000);
}
void Project::setNewMapLayout(Map* map) {
MapLayout *layout = new MapLayout();
layout->id = MapLayout::layoutConstantFromName(map->name);
@ -674,6 +763,8 @@ void Project::saveMapGroups() {
groupNum++;
}
ignoreWatchedFileTemporarily(mapGroupsFilepath);
OrderedJson mapGroupJson(mapGroupsObj);
OrderedJsonDoc jsonDoc(&mapGroupJson);
jsonDoc.dump(&mapGroupsFile);
@ -762,6 +853,8 @@ void Project::saveWildMonData() {
}
wildEncountersObject["wild_encounter_groups"] = wildEncounterGroups;
ignoreWatchedFileTemporarily(wildEncountersJsonFilepath);
OrderedJson encounterJson(wildEncountersObject);
OrderedJsonDoc jsonDoc(&encounterJson);
jsonDoc.dump(&wildEncountersFile);
@ -798,7 +891,10 @@ void Project::saveMapConstantsHeader() {
text += QString("#define MAP_GROUPS_COUNT %1\n\n").arg(groupNum);
text += QString("#endif // GUARD_CONSTANTS_MAP_GROUPS_H\n");
saveTextFile(root + "/include/constants/map_groups.h", text);
QString mapGroupFilepath = root + "/include/constants/map_groups.h";
ignoreWatchedFileTemporarily(mapGroupFilepath);
saveTextFile(mapGroupFilepath, text);
}
// saves heal location coords in root + /src/data/heal_locations.h
@ -899,8 +995,13 @@ void Project::saveHealLocationStruct(Map *map) {
data_text += QString("};\n");
constants_text += QString("\n#endif // GUARD_CONSTANTS_HEAL_LOCATIONS_H\n");
saveTextFile(root + "/src/data/heal_locations.h", data_text);
saveTextFile(root + "/include/constants/heal_locations.h", constants_text);
QString healLocationFilepath = root + "/src/data/heal_locations.h";
ignoreWatchedFileTemporarily(healLocationFilepath);
saveTextFile(healLocationFilepath, data_text);
QString healLocationConstantsFilepath = root + "/include/constants/heal_locations.h";
ignoreWatchedFileTemporarily(healLocationConstantsFilepath);
saveTextFile(healLocationConstantsFilepath, constants_text);
}
void Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) {
@ -992,6 +1093,8 @@ void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *second
}
outputText += "\n#endif // GUARD_METATILE_LABELS_H\n";
saveTextFile(root + "/include/constants/metatile_labels.h", outputText);
}
@ -1111,7 +1214,7 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) {
loadTilesetAssets(tileset);
tileset_cache->insert(label, tileset);
tilesetCache->insert(label, tileset);
return tileset;
}
@ -1201,10 +1304,10 @@ void Project::writeBlockdata(QString path, Blockdata *blockdata) {
}
void Project::saveAllMaps() {
QList<QString> keys = map_cache->keys();
QList<QString> keys = mapCache->keys();
for (int i = 0; i < keys.length(); i++) {
QString key = keys.value(i);
Map* map = map_cache->value(key);
Map* map = mapCache->value(key);
saveMap(map);
}
}
@ -1642,8 +1745,8 @@ Blockdata* Project::readBlockdata(QString path) {
}
Map* Project::getMap(QString map_name) {
if (map_cache->contains(map_name)) {
return map_cache->value(map_name);
if (mapCache->contains(map_name)) {
return mapCache->value(map_name);
} else {
Map *map = loadMap(map_name);
return map;
@ -1652,12 +1755,12 @@ Map* Project::getMap(QString map_name) {
Tileset* Project::getTileset(QString label, bool forceLoad) {
Tileset *existingTileset = nullptr;
if (tileset_cache->contains(label)) {
existingTileset = tileset_cache->value(label);
if (tilesetCache->contains(label)) {
existingTileset = tilesetCache->value(label);
}
if (existingTileset && !forceLoad) {
return tileset_cache->value(label);
return tilesetCache->value(label);
} else {
Tileset *tileset = loadTileset(label, existingTileset);
return tileset;
@ -1699,6 +1802,7 @@ bool Project::readWildMonData() {
}
QString wildMonJsonFilepath = QString("%1/src/data/wild_encounters.json").arg(root);
fileWatcher.addPath(wildMonJsonFilepath);
QJsonDocument wildMonsJsonDoc;
if (!parser.tryParseJsonFile(&wildMonsJsonDoc, wildMonJsonFilepath)) {
logError(QString("Failed to read wild encounters from %1").arg(wildMonJsonFilepath));
@ -1764,9 +1868,10 @@ bool Project::readWildMonData() {
bool Project::readMapGroups() {
mapConstantsToMapNames->clear();
mapNamesToMapConstants->clear();
map_groups->clear();
mapGroups->clear();
QString mapGroupsFilepath = QString("%1/data/maps/map_groups.json").arg(root);
fileWatcher.addPath(mapGroupsFilepath);
QJsonDocument mapGroupsDoc;
if (!parser.tryParseJsonFile(&mapGroupsDoc, mapGroupsFilepath)) {
logError(QString("Failed to read map groups from %1").arg(mapGroupsFilepath));
@ -1786,7 +1891,7 @@ bool Project::readMapGroups() {
groups->append(groupName);
for (int j = 0; j < mapNames.size(); j++) {
QString mapName = mapNames.at(j).toString();
map_groups->insert(mapName, groupIndex);
mapGroups->insert(mapName, groupIndex);
groupedMaps[groupIndex].append(mapName);
maps->append(mapName);
@ -1810,7 +1915,7 @@ bool Project::readMapGroups() {
Map* Project::addNewMapToGroup(QString mapName, int groupNum) {
// Setup new map in memory, but don't write to file until map is actually saved later.
mapNames->append(mapName);
map_groups->insert(mapName, groupNum);
mapGroups->insert(mapName, groupNum);
groupedMapNames[groupNum].append(mapName);
Map *map = new Map;
@ -1827,14 +1932,14 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum) {
setNewMapConnections(map);
map->commit();
map->metatileHistory.save();
map_cache->insert(mapName, map);
mapCache->insert(mapName, map);
return map;
}
Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool existingLayout) {
mapNames->append(mapName);
map_groups->insert(mapName, groupNum);
mapGroups->insert(mapName, groupNum);
groupedMapNames[groupNum].append(mapName);
Map *map = new Map;
@ -1936,7 +2041,9 @@ QMap<QString, QStringList> Project::getTilesetLabels() {
bool Project::readTilesetProperties() {
QStringList definePrefixes;
definePrefixes << "NUM_";
QMap<QString, int> defines = parser.readCDefines("include/fieldmap.h", definePrefixes);
QString filename = "include/fieldmap.h";
fileWatcher.addPath(root + "/" + filename);
QMap<QString, int> defines = parser.readCDefines(filename, definePrefixes);
auto it = defines.find("NUM_TILES_IN_PRIMARY");
if (it != defines.end()) {
@ -1995,6 +2102,7 @@ bool Project::readRegionMapSections() {
QStringList prefixes = (QStringList() << "MAPSEC_");
QString filename = "include/constants/region_map_sections.h";
fileWatcher.addPath(root + "/" + filename);
this->mapSectionNameToValue = parser.readCDefines(filename, prefixes);
if (this->mapSectionNameToValue.isEmpty()) {
logError(QString("Failed to read region map sections from %1.").arg(filename));
@ -2011,6 +2119,7 @@ bool Project::readHealLocations() {
dataQualifiers.clear();
flyableMaps.clear();
QString filename = "src/data/heal_locations.h";
fileWatcher.addPath(root + "/" + filename);
QString text = parser.readTextFile(root + "/" + filename);
text.replace(QRegularExpression("//.*?(\r\n?|\n)|/\\*.*?\\*/", QRegularExpression::DotMatchesEverythingOption), "");
@ -2058,6 +2167,7 @@ bool Project::readItemNames() {
itemNames->clear();
QStringList prefixes = (QStringList() << "ITEM_");
QString filename = "include/constants/items.h";
fileWatcher.addPath(root + "/" + filename);
parser.readCDefinesSorted(filename, prefixes, itemNames);
if (itemNames->isEmpty()) {
logError(QString("Failed to read item constants from %1").arg(filename));
@ -2070,6 +2180,7 @@ bool Project::readFlagNames() {
flagNames->clear();
QStringList prefixes = (QStringList() << "FLAG_");
QString filename = "include/constants/flags.h";
fileWatcher.addPath(root + "/" + filename);
parser.readCDefinesSorted(filename, prefixes, flagNames);
if (flagNames->isEmpty()) {
logError(QString("Failed to read flag constants from %1").arg(filename));
@ -2082,6 +2193,7 @@ bool Project::readVarNames() {
varNames->clear();
QStringList prefixes = (QStringList() << "VAR_");
QString filename = "include/constants/vars.h";
fileWatcher.addPath(root + "/" + filename);
parser.readCDefinesSorted(filename, prefixes, varNames);
if (varNames->isEmpty()) {
logError(QString("Failed to read var constants from %1").arg(filename));
@ -2094,6 +2206,7 @@ bool Project::readMovementTypes() {
movementTypes->clear();
QStringList prefixes = (QStringList() << "MOVEMENT_TYPE_");
QString filename = "include/constants/event_object_movement.h";
fileWatcher.addPath(root + "/" + filename);
parser.readCDefinesSorted(filename, prefixes, movementTypes);
if (movementTypes->isEmpty()) {
logError(QString("Failed to read movement type constants from %1").arg(filename));
@ -2104,6 +2217,7 @@ bool Project::readMovementTypes() {
bool Project::readInitialFacingDirections() {
QString filename = "src/event_object_movement.c";
fileWatcher.addPath(root + "/" + filename);
facingDirections = parser.readNamedIndexCArray(filename, "gInitialMovementTypeFacingDirections");
if (facingDirections.isEmpty()) {
logError(QString("Failed to read initial movement type facing directions from %1").arg(filename));
@ -2116,6 +2230,7 @@ bool Project::readMapTypes() {
mapTypes->clear();
QStringList prefixes = (QStringList() << "MAP_TYPE_");
QString filename = "include/constants/map_types.h";
fileWatcher.addPath(root + "/" + filename);
parser.readCDefinesSorted(filename, prefixes, mapTypes);
if (mapTypes->isEmpty()) {
logError(QString("Failed to read map type constants from %1").arg(filename));
@ -2128,6 +2243,7 @@ bool Project::readMapBattleScenes() {
mapBattleScenes->clear();
QStringList prefixes = (QStringList() << "MAP_BATTLE_SCENE_");
QString filename = "include/constants/map_types.h";
fileWatcher.addPath(root + "/" + filename);
parser.readCDefinesSorted("include/constants/map_types.h", prefixes, mapBattleScenes);
if (mapBattleScenes->isEmpty()) {
logError(QString("Failed to read map battle scene constants from %1").arg(filename));
@ -2140,6 +2256,7 @@ bool Project::readWeatherNames() {
weatherNames->clear();
QStringList prefixes = (QStringList() << "\\bWEATHER_");
QString filename = "include/constants/weather.h";
fileWatcher.addPath(root + "/" + filename);
parser.readCDefinesSorted(filename, prefixes, weatherNames);
if (weatherNames->isEmpty()) {
logError(QString("Failed to read weather constants from %1").arg(filename));
@ -2152,6 +2269,7 @@ bool Project::readCoordEventWeatherNames() {
coordEventWeatherNames->clear();
QStringList prefixes = (QStringList() << "COORD_EVENT_WEATHER_");
QString filename = "include/constants/weather.h";
fileWatcher.addPath(root + "/" + filename);
parser.readCDefinesSorted(filename, prefixes, coordEventWeatherNames);
if (coordEventWeatherNames->isEmpty()) {
logError(QString("Failed to read coord event weather constants from %1").arg(filename));
@ -2164,6 +2282,7 @@ bool Project::readSecretBaseIds() {
secretBaseIds->clear();
QStringList prefixes = (QStringList() << "SECRET_BASE_[A-Za-z0-9_]*_[0-9]+");
QString filename = "include/constants/secret_bases.h";
fileWatcher.addPath(root + "/" + filename);
parser.readCDefinesSorted(filename, prefixes, secretBaseIds);
if (secretBaseIds->isEmpty()) {
logError(QString("Failed to read secret base id constants from %1").arg(filename));
@ -2176,6 +2295,7 @@ bool Project::readBgEventFacingDirections() {
bgEventFacingDirections->clear();
QStringList prefixes = (QStringList() << "BG_EVENT_PLAYER_FACING_");
QString filename = "include/constants/event_bg.h";
fileWatcher.addPath(root + "/" + filename);
parser.readCDefinesSorted(filename, prefixes, bgEventFacingDirections);
if (bgEventFacingDirections->isEmpty()) {
logError(QString("Failed to read bg event facing direction constants from %1").arg(filename));
@ -2188,6 +2308,7 @@ bool Project::readTrainerTypes() {
trainerTypes->clear();
QStringList prefixes = (QStringList() << "TRAINER_TYPE_");
QString filename = "include/constants/trainer_types.h";
fileWatcher.addPath(root + "/" + filename);
parser.readCDefinesSorted(filename, prefixes, trainerTypes);
if (trainerTypes->isEmpty()) {
logError(QString("Failed to read trainer type constants from %1").arg(filename));
@ -2202,6 +2323,7 @@ bool Project::readMetatileBehaviors() {
QStringList prefixes = (QStringList() << "MB_");
QString filename = "include/constants/metatile_behaviors.h";
fileWatcher.addPath(root + "/" + filename);
this->metatileBehaviorMap = parser.readCDefines(filename, prefixes);
if (this->metatileBehaviorMap.isEmpty()) {
logError(QString("Failed to read metatile behaviors from %1.").arg(filename));
@ -2217,7 +2339,9 @@ bool Project::readMetatileBehaviors() {
QStringList Project::getSongNames() {
QStringList songDefinePrefixes;
songDefinePrefixes << "SE_" << "MUS_";
QMap<QString, int> songDefines = parser.readCDefines("include/constants/songs.h", songDefinePrefixes);
QString filename = "include/constants/songs.h";
fileWatcher.addPath(root + "/" + filename);
QMap<QString, int> songDefines = parser.readCDefines(filename, songDefinePrefixes);
QStringList names = songDefines.keys();
this->defaultSong = names.value(0, "MUS_DUMMY");
@ -2228,7 +2352,9 @@ QMap<QString, int> Project::getEventObjGfxConstants() {
QStringList eventObjGfxPrefixes;
eventObjGfxPrefixes << "OBJ_EVENT_GFX_";
QMap<QString, int> constants = parser.readCDefines("include/constants/event_objects.h", eventObjGfxPrefixes);
QString filename = "include/constants/event_objects.h";
fileWatcher.addPath(root + "/" + filename);
QMap<QString, int> constants = parser.readCDefines(filename, eventObjGfxPrefixes);
return constants;
}
@ -2236,7 +2362,9 @@ QMap<QString, int> Project::getEventObjGfxConstants() {
bool Project::readMiscellaneousConstants() {
miscConstants.clear();
if (projectConfig.getEncounterJsonActive()) {
QMap<QString, int> pokemonDefines = parser.readCDefines("include/constants/pokemon.h", QStringList() << "MIN_" << "MAX_");
QString filename = "include/constants/pokemon.h";
fileWatcher.addPath(root + "/" + filename);
QMap<QString, int> pokemonDefines = parser.readCDefines(filename, QStringList() << "MIN_" << "MAX_");
miscConstants.insert("max_level_define", pokemonDefines.value("MAX_LEVEL") > pokemonDefines.value("MIN_LEVEL") ? pokemonDefines.value("MAX_LEVEL") : 100);
miscConstants.insert("min_level_define", pokemonDefines.value("MIN_LEVEL") < pokemonDefines.value("MAX_LEVEL") ? pokemonDefines.value("MIN_LEVEL") : 1);
}
@ -2283,6 +2411,11 @@ void Project::loadEventPixmaps(QList<Event*> objects) {
QMap<QString, int> constants = getEventObjGfxConstants();
fileWatcher.addPaths(QStringList() << root + "/" + "src/data/object_events/object_event_graphics_info_pointers.h"
<< root + "/" + "src/data/object_events/object_event_graphics_info.h"
<< root + "/" + "src/data/object_events/object_event_pic_tables.h"
<< root + "/" + "src/data/object_events/object_event_graphics.h");
QMap<QString, QString> pointerHash = parser.readNamedIndexCArray("src/data/object_events/object_event_graphics_info_pointers.h", "gObjectEventGraphicsInfoPointers");
for (Event *object : objects) {
@ -2344,10 +2477,13 @@ void Project::loadEventPixmaps(QList<Event*> objects) {
bool Project::readSpeciesIconPaths() {
speciesToIconPath.clear();
QString filename = "src/pokemon_icon.c";
QMap<QString, QString> monIconNames = parser.readNamedIndexCArray(filename, "gMonIconTable");
QString srcfilename = "src/pokemon_icon.c";
QString incfilename = "src/data/graphics/pokemon.h";
fileWatcher.addPath(root + "/" + srcfilename);
fileWatcher.addPath(root + "/" + incfilename);
QMap<QString, QString> monIconNames = parser.readNamedIndexCArray(srcfilename, "gMonIconTable");
for (QString species : monIconNames.keys()) {
QString path = parser.readCIncbin("src/data/graphics/pokemon.h", monIconNames.value(species));
QString path = parser.readCIncbin(incfilename, monIconNames.value(species));
speciesToIconPath.insert(species, root + "/" + path.replace("4bpp", "png"));
}
return true;