diff --git a/CHANGELOG.md b/CHANGELOG.md
index 34a9b266..2cfad804 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project somewhat adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). The MAJOR version number is bumped when there are **"Breaking Changes"** in the pret projects. For more on this, see [the manual page on breaking changes](https://huderlem.github.io/porymap/manual/breaking-changes.html).
## [Unreleased]
+### Added
+- Add an option under `Preferences` to include common scripts in the autocomplete for Script labels.
+- A link to Porymap's manual is now available under `Help`.
+
### Changed
- The scroll position of the map view now remains the same between the Connections tab and the Map/Events tabs.
- The Move tool now behaves more like a traditional pan tool (with no momentum).
diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui
index 996dff3b..30520979 100644
--- a/forms/mainwindow.ui
+++ b/forms/mainwindow.ui
@@ -2926,6 +2926,7 @@
Help
+
@@ -3267,6 +3268,11 @@
Alt+Right
+
+
+ Open Manual
+
+
diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui
index d3ec7cea..63017212 100644
--- a/forms/preferenceeditor.ui
+++ b/forms/preferenceeditor.ui
@@ -104,7 +104,7 @@
-
- Qt::Vertical
+ Qt::Orientation::Vertical
@@ -159,7 +159,7 @@
-
- Qt::Horizontal
+ Qt::Orientation::Horizontal
@@ -209,7 +209,7 @@
-
- Qt::Vertical
+ Qt::Orientation::Vertical
@@ -253,13 +253,42 @@
-
-
-
- <html><head/><body><p>If checked, the list of suggestions when typing in an Event's Script field will include all global script labels in the project. Enabling this setting will make Porymap's startup slower.</p></body></html>
-
-
- Autocomplete Script labels using all possible scripts
+
+
+ Script label autocomplete
+
+
-
+
+
+ <html><head/><body><p>If checked, the list of suggestions when typing in an Event's Script field will include all global script labels in the project. This is the slowest option for Porymap's project opening.</p></body></html>
+
+
+ All possible scripts
+
+
+
+ -
+
+
+ <html><head/><body><p>If checked, the list of suggestions when typing in an Event's Script field will include script labels from the current map's scripts file, scripts in-use by the map's other events, and all script files in the <span style=" font-family:'SFMono-Regular','Menlo','Monaco','Consolas','Liberation Mono','Courier New','Courier','monospace'; font-size:11px; color:#e74c3c; background-color:#ffffff;">data_scripts_folders </span>folder.</p></body></html>
+
+
+ Current map, and global script files
+
+
+
+ -
+
+
+ <html><head/><body><p>If checked, the list of suggestions when typing in an Event's Script field will only include script labels from the current map's scripts file and scripts in-use by the map's other events. This is the fastest option for Porymap's project opening.</p></body></html>
+
+
+ Current map only
+
+
+
+
-
@@ -294,7 +323,7 @@
-
- Qt::Vertical
+ Qt::Orientation::Vertical
@@ -323,7 +352,7 @@
-
- QFrame::NoFrame
+ QFrame::Shape::NoFrame
true
@@ -333,13 +362,13 @@
0
0
- 476
- 343
+ 495
+ 376
- QLayout::SetMinimumSize
+ QLayout::SizeConstraint::SetMinimumSize
-
@@ -347,7 +376,7 @@
<html><head/><body><p>When this command is set a button will appear next to the <span style=" font-weight:600; font-style:italic;">Script</span> combo-box in the <span style=" font-weight:600; font-style:italic;">Events</span> tab which executes this command.<span style=" font-weight:600;"> %F</span> will be substituted with the file path of the script and <span style=" font-weight:600;">%L</span> will be substituted with the line number of the script in that file. <span style=" font-weight:600;">%F </span><span style=" font-style:italic;">must</span> be given if <span style=" font-weight:600;">%L</span> is given. If <span style=" font-weight:600;">%F</span> is <span style=" font-style:italic;">not</span> given then the script's file path will be added to the end of the command. If the script can't be found then the current map's scripts file is opened.</p></body></html>
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+ Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop
true
@@ -380,7 +409,7 @@
<html><head/><body><p>This is the command that is executed when clicking <span style=" font-weight:600; font-style:italic;">Open Project in Text Editor</span> in the <span style=" font-weight:600; font-style:italic;">Tools</span> menu. <span style=" font-weight:600;">%D</span> will be substituted with the project's root directory. If <span style=" font-weight:600;">%D</span> is <span style=" font-style:italic;">not</span> specified then the project directory will be added to the end of the command.</p></body></html>
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+ Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop
true
@@ -410,10 +439,10 @@
-
- Qt::Vertical
+ Qt::Orientation::Vertical
- QSizePolicy::Fixed
+ QSizePolicy::Policy::Fixed
@@ -426,7 +455,7 @@
-
- Qt::Vertical
+ Qt::Orientation::Vertical
@@ -447,7 +476,7 @@
-
- QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+ QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok
diff --git a/include/config.h b/include/config.h
index 6e38ae4a..18c66c9e 100644
--- a/include/config.h
+++ b/include/config.h
@@ -27,6 +27,12 @@ extern const QVersionNumber porymapVersion;
#define CONFIG_BACKWARDS_COMPATABILITY
+enum ScriptAutocompleteMode {
+ MapOnly,
+ MapAndCommon,
+ All,
+};
+
class KeyValueConfigBase
{
public:
@@ -119,7 +125,7 @@ public:
QString textEditorGotoLine;
int paletteEditorBitDepth;
int projectSettingsTab;
- bool loadAllEventScripts;
+ ScriptAutocompleteMode scriptAutocompleteMode;
bool warpBehaviorWarningDisabled;
bool eventDeleteWarningDisabled;
bool eventOverlayEnabled;
diff --git a/include/core/map.h b/include/core/map.h
index 3b843f2a..734d5945 100644
--- a/include/core/map.h
+++ b/include/core/map.h
@@ -148,7 +148,7 @@ signals:
void modified();
void scriptsModified();
void mapDimensionsChanged(const QSize &size);
- void openScriptRequested(QString label);
+ void openScriptRequested(const QString &label);
void connectionAdded(MapConnection*);
void connectionRemoved(MapConnection*);
void layoutChanged();
diff --git a/include/editor.h b/include/editor.h
index a4620af6..fd9a809e 100644
--- a/include/editor.h
+++ b/include/editor.h
@@ -209,7 +209,8 @@ public:
public slots:
void openMapScripts() const;
- void openScript(const QString &scriptLabel) const;
+ bool openScript(const QString &scriptLabel) const;
+ bool openScriptInFile(const QString &scriptLabel, const QString &filepath) const;
void openProjectInTextEditor() const;
void maskNonVisibleConnectionTiles();
void onBorderMetatilesChanged();
diff --git a/include/mainwindow.h b/include/mainwindow.h
index 8441babb..813370b4 100644
--- a/include/mainwindow.h
+++ b/include/mainwindow.h
@@ -290,6 +290,7 @@ private slots:
void on_spinBox_SelectedCollision_valueChanged(int collision);
void on_actionRegion_Map_Editor_triggered();
void on_actionPreferences_triggered();
+ void on_actionOpen_Manual_triggered();
void on_actionCheck_for_Updates_triggered();
void togglePreferenceSpecificUi();
void on_actionProject_Settings_triggered();
diff --git a/include/project.h b/include/project.h
index 3dbae1c5..dd66c2e5 100644
--- a/include/project.h
+++ b/include/project.h
@@ -215,7 +215,11 @@ public:
static QString getScriptFileExtension(bool usePoryScript);
QString getScriptDefaultString(bool usePoryScript, QString mapName) const;
- QStringList getEventScriptsFilepaths() const;
+
+ QStringList getAllEventScriptsFilepaths() const;
+ QStringList getMapScriptsFilepaths() const;
+ QStringList getCommonEventScriptsFilepaths() const;
+ QStringList findScriptsFiles(const QString &searchDir, const QStringList &fileNames = {"*"}) const;
void insertGlobalScriptLabels(QStringList &scriptLabels) const;
QString getDefaultPrimaryTilesetLabel() const;
diff --git a/include/ui/preferenceeditor.h b/include/ui/preferenceeditor.h
index 74181c37..24b6253f 100644
--- a/include/ui/preferenceeditor.h
+++ b/include/ui/preferenceeditor.h
@@ -2,6 +2,7 @@
#define PREFERENCES_H
#include
+#include "config.h"
class NoScrollComboBox;
class QAbstractButton;
@@ -23,7 +24,7 @@ public:
signals:
void preferencesSaved();
void themeChanged(const QString &theme);
- void scriptSettingsChanged(bool on);
+ void scriptSettingsChanged(ScriptAutocompleteMode mode);
void reloadProjectRequested();
private:
diff --git a/src/config.cpp b/src/config.cpp
index 6347e7e4..5810828d 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -355,7 +355,7 @@ void PorymapConfig::reset() {
this->textEditorGotoLine = "";
this->paletteEditorBitDepth = 24;
this->projectSettingsTab = 0;
- this->loadAllEventScripts = false;
+ this->scriptAutocompleteMode = ScriptAutocompleteMode::MapOnly;
this->warpBehaviorWarningDisabled = false;
this->eventDeleteWarningDisabled = false;
this->eventOverlayEnabled = false;
@@ -490,8 +490,13 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
}
} else if (key == "project_settings_tab") {
this->projectSettingsTab = getConfigInteger(key, value, 0);
+#ifdef CONFIG_BACKWARDS_COMPATABILITY
+ // Old setting replaced by script_autocomplete_mode
} else if (key == "load_all_event_scripts") {
- this->loadAllEventScripts = getConfigBool(key, value);
+ this->scriptAutocompleteMode = getConfigBool(key, value) ? ScriptAutocompleteMode::All : ScriptAutocompleteMode::MapOnly;
+#endif
+ } else if (key == "script_autocomplete_mode") {
+ this->scriptAutocompleteMode = static_cast(getConfigInteger(key, value, ScriptAutocompleteMode::MapOnly, ScriptAutocompleteMode::All));
} else if (key == "warp_behavior_warning_disabled") {
this->warpBehaviorWarningDisabled = getConfigBool(key, value);
} else if (key == "event_delete_warning_disabled") {
@@ -613,7 +618,7 @@ QMap PorymapConfig::getKeyValueMap() {
map.insert("text_editor_goto_line", this->textEditorGotoLine);
map.insert("palette_editor_bit_depth", QString::number(this->paletteEditorBitDepth));
map.insert("project_settings_tab", QString::number(this->projectSettingsTab));
- map.insert("load_all_event_scripts", QString::number(this->loadAllEventScripts));
+ map.insert("script_autocomplete_mode", QString::number(this->scriptAutocompleteMode));
map.insert("warp_behavior_warning_disabled", QString::number(this->warpBehaviorWarningDisabled));
map.insert("event_delete_warning_disabled", QString::number(this->eventDeleteWarningDisabled));
map.insert("event_overlay_enabled", QString::number(this->eventOverlayEnabled));
diff --git a/src/editor.cpp b/src/editor.cpp
index daa490e4..5f4b3f62 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -2353,21 +2353,29 @@ void Editor::openMapScripts() const {
openInTextEditor(map->getScriptsFilepath());
}
-void Editor::openScript(const QString &scriptLabel) const {
+bool Editor::openScript(const QString &scriptLabel) const {
// Find the location of scriptLabel.
- QStringList scriptPaths(map->getScriptsFilepath());
- scriptPaths << project->getEventScriptsFilepaths();
- int lineNum = 0;
- QString scriptPath = scriptPaths.first();
- for (const auto &path : scriptPaths) {
- lineNum = ParseUtil::getScriptLineNumber(path, scriptLabel);
- if (lineNum != 0) {
- scriptPath = path;
- break;
- }
- }
+ // First, try the current map's scripts file.
+ if (openScriptInFile(scriptLabel, map->getScriptsFilepath()))
+ return true;
- openInTextEditor(scriptPath, lineNum);
+ // Script is not in the current map's scripts file.
+ // Search all possible script files.
+ const QStringList paths = project->getAllEventScriptsFilepaths();
+ for (const auto &path : paths) {
+ if (openScriptInFile(scriptLabel, path))
+ return true;
+ }
+ return false;
+}
+
+bool Editor::openScriptInFile(const QString &scriptLabel, const QString &filepath) const {
+ int lineNum = ParseUtil::getScriptLineNumber(filepath, scriptLabel);
+ if (lineNum == 0)
+ return false;
+
+ openInTextEditor(filepath, lineNum);
+ return true;
}
void Editor::openMapJson(const QString &mapName) const {
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 2e2f9440..c191ea07 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -2908,6 +2908,11 @@ void MainWindow::on_actionOpen_Config_Folder_triggered() {
QDesktopServices::openUrl(QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)));
}
+void MainWindow::on_actionOpen_Manual_triggered() {
+ static const QUrl url("https://huderlem.github.io/porymap/");
+ QDesktopServices::openUrl(url);
+}
+
void MainWindow::on_actionPreferences_triggered() {
if (!preferenceEditor) {
preferenceEditor = new PreferenceEditor(this);
diff --git a/src/project.cpp b/src/project.cpp
index e1f074b7..b76821f3 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -2969,13 +2969,19 @@ bool Project::readGlobalConstants() {
bool Project::readEventScriptLabels() {
this->globalScriptLabels.clear();
- if (porymapConfig.loadAllEventScripts) {
- for (const auto &filePath : getEventScriptsFilepaths())
- this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(filePath);
-
- this->globalScriptLabels.sort(Qt::CaseInsensitive);
- this->globalScriptLabels.removeDuplicates();
+ QStringList paths;
+ if (porymapConfig.scriptAutocompleteMode == ScriptAutocompleteMode::All) {
+ paths = getAllEventScriptsFilepaths();
+ } else if (porymapConfig.scriptAutocompleteMode == ScriptAutocompleteMode::MapAndCommon) {
+ paths = getCommonEventScriptsFilepaths();
}
+
+ for (const auto &path : paths) {
+ this->globalScriptLabels << ParseUtil::getGlobalScriptLabels(path);
+ }
+ this->globalScriptLabels.sort(Qt::CaseInsensitive);
+ this->globalScriptLabels.removeDuplicates();
+
emit eventScriptLabelsRead();
return true;
}
@@ -3011,30 +3017,46 @@ QString Project::getScriptDefaultString(bool usePoryScript, QString mapName) con
return QString("%1_MapScripts::\n\t.byte 0\n").arg(mapName);
}
-QStringList Project::getEventScriptsFilepaths() const {
- QStringList filePaths(QDir::cleanPath(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts)));
- const QString scriptsDir = QDir::cleanPath(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_scripts_folders));
- const QString mapsDir = QDir::cleanPath(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_map_folders));
+QStringList Project::getAllEventScriptsFilepaths() const {
+ return getMapScriptsFilepaths() + getCommonEventScriptsFilepaths();
+}
- if (projectConfig.usePoryScript) {
- QDirIterator it_pory_shared(scriptsDir, {"*.pory"}, QDir::Files);
- while (it_pory_shared.hasNext())
- filePaths << it_pory_shared.next();
+// Get the paths for all "scripts.inc" / "scripts.pory" files in the data/maps/*/ folders.
+QStringList Project::getMapScriptsFilepaths() const {
+ return findScriptsFiles(projectConfig.getFilePath(ProjectFilePath::data_map_folders), {"scripts"});
+}
- QDirIterator it_pory_maps(mapsDir, {"scripts.pory"}, QDir::Files, QDirIterator::Subdirectories);
- while (it_pory_maps.hasNext())
- filePaths << it_pory_maps.next();
+// Get the paths for all "*.inc" / "*.pory" files in the data/scripts/ folder, + data/event_scripts.s.
+QStringList Project::getCommonEventScriptsFilepaths() const {
+ QStringList paths = { QDir::cleanPath(this->root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts)) };
+ return paths + findScriptsFiles(projectConfig.getFilePath(ProjectFilePath::data_scripts_folders));
+}
+
+QStringList Project::findScriptsFiles(const QString &searchDir, const QStringList &fileNames) const {
+ QStringList paths;
+
+ QString dir = searchDir;
+ if (!dir.startsWith(this->root)) {
+ dir = QDir::cleanPath(this->root + "/" + dir);
}
- QDirIterator it_inc_shared(scriptsDir, {"*.inc"}, QDir::Files);
- while (it_inc_shared.hasNext())
- filePaths << it_inc_shared.next();
+ QStringList filters;
+ for (const auto &s : fileNames) {
+ filters.append(s + getScriptFileExtension(false));
+ if (projectConfig.usePoryScript) {
+ filters.append(s + getScriptFileExtension(true));
+ }
+ }
- QDirIterator it_inc_maps(mapsDir, {"scripts.inc"}, QDir::Files, QDirIterator::Subdirectories);
- while (it_inc_maps.hasNext())
- filePaths << it_inc_maps.next();
-
- return filePaths;
+ // TODO: Filter out .inc files that are generated by a .pory file.
+ // They won't cause problems for the user, but it'll create extra parsing work later.
+ if (!filters.isEmpty()) {
+ QDirIterator it(dir, filters, QDir::Files, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
+ while (it.hasNext()) {
+ paths.append(it.next());
+ }
+ }
+ return paths;
}
void Project::loadEventPixmap(Event *event, bool forceLoad) {
diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp
index 6457e564..a1af9e97 100644
--- a/src/ui/eventframes.cpp
+++ b/src/ui/eventframes.cpp
@@ -201,8 +201,8 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj
QStringList scripts = map->getScriptLabels(this->event->getEventGroup());
populateDropdown(combo, scripts);
- // Depending on the settings, the autocomplete may also contain all global scripts.
- if (project && porymapConfig.loadAllEventScripts) {
+ // Depending on the settings, the autocomplete may also contain scripts from outside the map.
+ if (project && porymapConfig.scriptAutocompleteMode != ScriptAutocompleteMode::MapOnly) {
project->insertGlobalScriptLabels(scripts);
}
diff --git a/src/ui/preferenceeditor.cpp b/src/ui/preferenceeditor.cpp
index 4f4b0929..5765ba01 100644
--- a/src/ui/preferenceeditor.cpp
+++ b/src/ui/preferenceeditor.cpp
@@ -1,6 +1,5 @@
#include "preferenceeditor.h"
#include "ui_preferenceeditor.h"
-#include "config.h"
#include "noscrollcombobox.h"
#include "message.h"
@@ -76,7 +75,14 @@ void PreferenceEditor::updateFields() {
ui->checkBox_OpenRecentProject->setChecked(porymapConfig.reopenOnLaunch);
ui->checkBox_CheckForUpdates->setChecked(porymapConfig.checkForUpdates);
ui->checkBox_DisableEventWarning->setChecked(porymapConfig.eventDeleteWarningDisabled);
- ui->checkBox_AutocompleteAllScripts->setChecked(porymapConfig.loadAllEventScripts);
+
+ if (porymapConfig.scriptAutocompleteMode == ScriptAutocompleteMode::MapOnly) {
+ ui->radioButton_AutocompleteMapScripts->setChecked(true);
+ } else if (porymapConfig.scriptAutocompleteMode == ScriptAutocompleteMode::MapAndCommon) {
+ ui->radioButton_AutocompleteCommonScripts->setChecked(true);
+ } else if (porymapConfig.scriptAutocompleteMode == ScriptAutocompleteMode::All) {
+ ui->radioButton_AutocompleteAllScripts->setChecked(true);
+ }
auto logTypeEnd = porymapConfig.statusBarLogTypes.end();
ui->checkBox_StatusErrors->setChecked(porymapConfig.statusBarLogTypes.find(LogType::LOG_ERROR) != logTypeEnd);
@@ -95,11 +101,18 @@ void PreferenceEditor::saveFields() {
porymapConfig.theme = themeSelector->currentText();
changedTheme = true;
}
- bool loadAllEventScripts = ui->checkBox_AutocompleteAllScripts->isChecked();
- if (loadAllEventScripts != porymapConfig.loadAllEventScripts) {
- porymapConfig.loadAllEventScripts = loadAllEventScripts;
- emit scriptSettingsChanged(loadAllEventScripts);
+
+ auto scriptAutocompleteMode = ScriptAutocompleteMode::MapOnly;
+ if (ui->radioButton_AutocompleteCommonScripts->isChecked()) {
+ scriptAutocompleteMode = ScriptAutocompleteMode::MapAndCommon;
+ } else if (ui->radioButton_AutocompleteAllScripts->isChecked()) {
+ scriptAutocompleteMode = ScriptAutocompleteMode::All;
}
+ if (scriptAutocompleteMode != porymapConfig.scriptAutocompleteMode) {
+ porymapConfig.scriptAutocompleteMode = scriptAutocompleteMode;
+ emit scriptSettingsChanged(scriptAutocompleteMode);
+ }
+
porymapConfig.eventSelectionShapeMode = ui->radioButton_OnSprite->isChecked() ? QGraphicsPixmapItem::MaskShape : QGraphicsPixmapItem::BoundingRectShape;
porymapConfig.textEditorOpenFolder = ui->lineEdit_TextEditorOpenFolder->text();
porymapConfig.textEditorGotoLine = ui->lineEdit_TextEditorGotoLine->text();
diff --git a/src/ui/uintspinbox.cpp b/src/ui/uintspinbox.cpp
index cfe19b72..7155aa67 100644
--- a/src/ui/uintspinbox.cpp
+++ b/src/ui/uintspinbox.cpp
@@ -119,11 +119,16 @@ void UIntSpinBox::onEditFinished() {
// Valid input
newValue = this->valueFromText(input);
} else if (state == QValidator::Intermediate) {
- // User has deleted all the number text.
- // If they did this by selecting all text and then hitting delete
- // make sure to put the cursor back in front of the prefix.
- newValue = m_minimum;
- this->lineEdit()->setCursorPosition(m_prefix.length());
+ if (input == m_prefix) {
+ // User has deleted all the number text.
+ // If they did this by selecting all text and then hitting delete
+ // make sure to put the cursor back in front of the prefix.
+ newValue = m_minimum;
+ this->lineEdit()->setCursorPosition(m_prefix.length());
+ } else {
+ // Other intermediate inputs (values outside of acceptable range) should be ignored.
+ return;
+ }
}
if (newValue != m_value) {
m_value = newValue;
@@ -167,10 +172,14 @@ QValidator::State UIntSpinBox::validate(QString &input, int &pos) const {
bool ok;
uint32_t num = copy.toUInt(&ok, m_displayIntegerBase);
- if (!ok || num < m_minimum || num > m_maximum)
+ if (!ok)
return QValidator::Invalid;
input += copy.toUpper();
+
+ if (num < m_minimum || num > m_maximum)
+ return QValidator::Intermediate;
+
return QValidator::Acceptable;
}