diff --git a/CHANGELOG.md b/CHANGELOG.md index ddd94760..b651c7c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add ruler tool for measuring metatile distance in events tab (Right-click to turn on/off, left-click to lock in place). - Add delete button to wild pokemon encounters tab. - Add shortcut customization via `Options -> Edit Shortcuts`. +- Add custom text editor commands in `Options -> Edit Preferences`, a tool-button next to the `Script` combo-box, and `Tools -> Open Project in Text Editor`. The tool-button will open the containing file to the cooresponding script. ### Changed - Holding `shift` now toggles "Smart Path" drawing; when the "Smart Paths" checkbox is checked, holding `shift` will temporarily disable it. diff --git a/docsrc/manual/editing-map-events.rst b/docsrc/manual/editing-map-events.rst index 7837d48d..ca0b4e41 100644 --- a/docsrc/manual/editing-map-events.rst +++ b/docsrc/manual/editing-map-events.rst @@ -225,11 +225,17 @@ Respawn NPC Open Map Scripts ---------------- -Clicking the ``Open Map Scripts`` button |open-map-scripts-button| will open the map's scripts file in your default text editor. If nothing happens when this button is clicked, you may need to associate a text editor with the `.inc` file extension. +Clicking the ``Open Map Scripts`` button |open-map-scripts-button| will open the map's scripts file in your default text editor. If nothing happens when this button is clicked, you may need to associate a text editor with the `.inc` file extension (or `.pory` if you're using Porycript). + +Additionally, if you specify a ``Goto Line Command`` in *Options -> Edit Preferences* then a tool-button will appear next to the `Script` combo-box in the *Events* tab. Clicking this button will open the file that contains the script directly to the line number of that script. If the script cannot be found in the project then the current map's scripts file is opened. +|go-to-script-button| .. |open-map-scripts-button| image:: images/editing-map-events/open-map-scripts-button.png +.. |go-to-script-button| + image:: images/editing-map-events/go-to-script-button.png + Tool Buttons ------------ diff --git a/docsrc/manual/images/editing-map-events/go-to-script-button.png b/docsrc/manual/images/editing-map-events/go-to-script-button.png new file mode 100644 index 00000000..859ed2f3 Binary files /dev/null and b/docsrc/manual/images/editing-map-events/go-to-script-button.png differ diff --git a/docsrc/manual/settings-and-options.rst b/docsrc/manual/settings-and-options.rst index 9b055226..5a4968e3 100644 --- a/docsrc/manual/settings-and-options.rst +++ b/docsrc/manual/settings-and-options.rst @@ -31,6 +31,8 @@ determined by this file. ``monitor_files``, 1, global, yes, Whether porymap will monitor changes to project files ``region_map_dimensions``, 32x20, global, yes, The dimensions of the region map tilemap ``theme``, default, global, yes, The color theme for porymap windows and widgets + ``text_editor_goto_line``, , global, yes, The command that will be executed when clicking the button next the ``Script`` combo-box. + ``text_editor_open_directory``, , global, yes, The command that will be executed when clicking ``Open Project in Text Editor``. ``base_game_version``, , project, no, The base pret repo for this project ``use_encounter_json``, 1, project, yes, Enables wild encounter table editing ``use_poryscript``, 0, project, yes, Whether to open .pory files for scripts diff --git a/docsrc/manual/shortcuts.rst b/docsrc/manual/shortcuts.rst index e45fa22f..05a5cfd1 100644 --- a/docsrc/manual/shortcuts.rst +++ b/docsrc/manual/shortcuts.rst @@ -41,6 +41,7 @@ Main Window Open New Tileset Dialog, ``Ctrl+Shift+N`` Open Tileset Editor, ``Ctrl+T`` Open Region Map Editor, ``Ctrl+M`` + Edit Preferences, ``Ctrl+,`` .. csv-table:: :header: Map Editing diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index ed09794f..941a3179 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2624,8 +2624,6 @@ - - @@ -2642,6 +2640,8 @@ + + @@ -2657,6 +2657,7 @@ + @@ -2914,16 +2915,24 @@ Ctrl+Shift+N - - - Themes... - - Export Map Stitch Image... + + + Edit Preferences... + + + Ctrl+, + + + + + Open Project in Text Editor + + Edit Shortcuts... diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui new file mode 100644 index 00000000..c44fa98c --- /dev/null +++ b/forms/preferenceeditor.ui @@ -0,0 +1,190 @@ + + + PreferenceEditor + + + + 0 + 0 + 600 + 480 + + + + Preferences + + + + + 9 + + + + + + 0 + 0 + + + + Application Theme + + + + + + + Preferred Text Editor + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + true + + + + + 0 + 0 + 582 + 372 + + + + + QLayout::SetMinimumSize + + + + + <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 + + + true + + + + + + + Goto Line Command + + + + + + + The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH). + + + e.g. code %D + + + true + + + + + + + <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 + + + true + + + + + + + Open Directory Command + + + + + + + The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH). + + + e.g. code --goto %F:%L + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 15 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + diff --git a/include/config.h b/include/config.h index 4b0db13c..bd0a1296 100644 --- a/include/config.h +++ b/include/config.h @@ -47,6 +47,8 @@ public: this->monitorFiles = true; this->regionMapDimensions = QSize(32, 20); this->theme = "default"; + this->textEditorOpenFolder = ""; + this->textEditorGotoLine = ""; } void setRecentProject(QString project); void setRecentMap(QString map); @@ -63,6 +65,8 @@ public: void setMonitorFiles(bool monitor); void setRegionMapDimensions(int width, int height); void setTheme(QString theme); + void setTextEditorOpenFolder(const QString &command); + void setTextEditorGotoLine(const QString &command); QString getRecentProject(); QString getRecentMap(); MapSortOrder getMapSortOrder(); @@ -78,6 +82,8 @@ public: bool getMonitorFiles(); QSize getRegionMapDimensions(); QString getTheme(); + QString getTextEditorOpenFolder(); + QString getTextEditorGotoLine(); protected: virtual QString getConfigFilepath() override; virtual void parseConfigKeyValue(QString key, QString value) override; @@ -109,6 +115,8 @@ private: bool monitorFiles; QSize regionMapDimensions; QString theme; + QString textEditorOpenFolder; + QString textEditorGotoLine; }; extern PorymapConfig porymapConfig; diff --git a/include/core/event.h b/include/core/event.h index 48d13253..b6e0d175 100644 --- a/include/core/event.h +++ b/include/core/event.h @@ -31,10 +31,10 @@ public: Event(const Event&); Event(QJsonObject, QString); public: - int x() { + int x() const { return getInt("x"); } - int y() { + int y() const { return getInt("y"); } int elevation() { @@ -46,16 +46,16 @@ public: void setY(int y) { put("y", y); } - QString get(QString key) { + QString get(const QString &key) const { return values.value(key); } - int getInt(QString key) { + int getInt(const QString &key) const { return values.value(key).toInt(nullptr, 0); } - uint16_t getU16(QString key) { + uint16_t getU16(const QString &key) const { return values.value(key).toUShort(nullptr, 0); } - int16_t getS16(QString key) { + int16_t getS16(const QString &key) const { return values.value(key).toShort(nullptr, 0); } void put(QString key, int value) { diff --git a/include/core/map.h b/include/core/map.h index 4d34a071..a2a51551 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -83,7 +83,8 @@ public: void floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); void _floodFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); void magicFillCollisionElevation(int x, int y, uint16_t collision, uint16_t elevation); - QList getAllEvents(); + QList getAllEvents() const; + QStringList eventScriptLabels(const QString &event_group_type = QString()) const; void removeEvent(Event*); void addEvent(Event*); QPixmap renderConnection(MapConnection, MapLayout *); diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 22abe367..bd614686 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -40,7 +40,7 @@ class ParseUtil public: ParseUtil(); void set_root(QString); - QString readTextFile(QString); + static QString readTextFile(QString); void strip_comment(QString*); QList* parseAsm(QString); int evaluateDefine(QString, QMap*); @@ -54,6 +54,17 @@ public: bool tryParseJsonFile(QJsonDocument *out, QString filepath); bool ensureFieldsExist(QJsonObject obj, QList fields); + // Returns the 1-indexed line number for the definition of scriptLabel in the scripts file at filePath. + // Returns 0 if a definition for scriptLabel cannot be found. + static int getScriptLineNumber(const QString &filePath, const QString &scriptLabel); + static int getRawScriptLineNumber(QString text, const QString &scriptLabel); + static int getPoryScriptLineNumber(QString text, const QString &scriptLabel); + static QString &removeStringLiterals(QString &text); + static QString &removeLineComments(QString &text, const QString &commentSymbol); + static QString &removeLineComments(QString &text, const QStringList &commentSymbols); + + static QStringList splitShellCommand(QStringView command); + private: QString root; QString text; diff --git a/include/editor.h b/include/editor.h index bfdbd1bc..90361ca3 100644 --- a/include/editor.h +++ b/include/editor.h @@ -65,7 +65,6 @@ public: void displayMapBorder(); void displayMapGrid(); void displayWildMonTables(); - void maskNonVisibleConnectionTiles(); void updateMapBorder(); void updateMapConnections(); @@ -155,6 +154,12 @@ public: void shouldReselectEvents(); void scaleMapView(int); +public slots: + void openMapScripts() const; + void openScript(const QString &scriptLabel) const; + void openProjectInTextEditor() const; + void maskNonVisibleConnectionTiles(); + private: void setConnectionItemsVisible(bool); void setBorderItemsVisible(bool, qreal = 1); @@ -182,6 +187,10 @@ private: QString getMovementPermissionText(uint16_t collision, uint16_t elevation); QString getMetatileDisplayMessage(uint16_t metatileId); bool eventLimitReached(Map *, QString); + void openInTextEditor(const QString &path, int lineNum = 0) const; + bool startDetachedProcess(const QString &command, + const QString &workingDirectory = QString(), + qint64 *pid = nullptr) const; private slots: void onMapStartPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); diff --git a/include/mainwindow.h b/include/mainwindow.h index e65db84f..f5cab1e8 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -22,6 +22,7 @@ #include "newmappopup.h" #include "newtilesetdialog.h" #include "shortcutseditor.h" +#include "preferenceeditor.h" namespace Ui { class MainWindow; @@ -119,8 +120,6 @@ private slots: void duplicate(); - void openInTextEditor(); - void onLoadMapRequested(QString, QString); void onMapChanged(Map *map); void onMapNeedsRedrawing(); @@ -167,7 +166,6 @@ private slots: void on_actionMap_Shift_triggered(); void on_toolButton_deleteObject_clicked(); - void on_toolButton_Open_Scripts_clicked(); void addNewEvent(QString); void updateSelectedObjects(); @@ -222,7 +220,6 @@ private slots: void on_toolButton_ExpandAll_clicked(); void on_toolButton_CollapseAll_clicked(); void on_actionAbout_Porymap_triggered(); - void on_actionThemes_triggered(); void on_pushButton_AddCustomHeaderField_clicked(); void on_pushButton_DeleteCustomHeaderField_clicked(); void on_tableWidget_CustomHeaderFields_cellChanged(int row, int column); @@ -232,6 +229,8 @@ private slots: void on_pushButton_ConfigureEncountersJSON_clicked(); void on_actionRegion_Map_Editor_triggered(); + void on_actionEdit_Preferences_triggered(); + void togglePreferenceSpecificUi(); private: Ui::MainWindow *ui; @@ -242,6 +241,7 @@ private: MapImageExporter *mapImageExporter = nullptr; FilterChildrenProxyModel *mapListProxyModel; NewMapPopup *newmapprompt = nullptr; + PreferenceEditor *preferenceEditor = nullptr; QStandardItemModel *mapListModel; QList *mapGroupItemsList; QMap mapListIndexes; @@ -267,6 +267,7 @@ private: DraggablePixmapItem *selectedHealspot; QList registeredActions; + QVector openScriptButtons; bool isProgrammaticEventTabChange; bool projectHasUnsavedChanges; diff --git a/include/project.h b/include/project.h index 179a5a93..afc82030 100644 --- a/include/project.h +++ b/include/project.h @@ -172,8 +172,10 @@ public: QString fixPalettePath(QString path); QString fixGraphicPath(QString path); - QString getScriptFileExtension(bool usePoryScript); - QString getScriptDefaultString(bool usePoryScript, QString mapName); + QString getScriptFileExtension(bool usePoryScript) const; + QString getScriptDefaultString(bool usePoryScript, QString mapName) const; + QString getMapScriptsFilePath(const QString &mapName) const; + QStringList getEventScriptsFilePaths() const; bool loadMapBorder(Map *map); diff --git a/include/ui/preferenceeditor.h b/include/ui/preferenceeditor.h new file mode 100644 index 00000000..c16716d9 --- /dev/null +++ b/include/ui/preferenceeditor.h @@ -0,0 +1,37 @@ +#ifndef PREFERENCES_H +#define PREFERENCES_H + +#include + +class NoScrollComboBox; +class QAbstractButton; + + +namespace Ui { +class PreferenceEditor; +} + +class PreferenceEditor : public QMainWindow +{ + Q_OBJECT + +public: + explicit PreferenceEditor(QWidget *parent = nullptr); + ~PreferenceEditor(); + +signals: + void preferencesSaved(); + void themeChanged(const QString &theme); + +private: + Ui::PreferenceEditor *ui; + NoScrollComboBox *themeSelector; + + void populateFields(); + void saveFields(); + +private slots: + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // PREFERENCES_H diff --git a/porymap.pro b/porymap.pro index af5a6205..a741f69a 100644 --- a/porymap.pro +++ b/porymap.pro @@ -74,6 +74,7 @@ SOURCES += src/core/block.cpp \ src/ui/shortcut.cpp \ src/ui/shortcutseditor.cpp \ src/ui/multikeyedit.cpp \ + src/ui/preferenceeditor.cpp \ src/config.cpp \ src/editor.cpp \ src/main.cpp \ @@ -146,6 +147,7 @@ HEADERS += include/core/block.h \ include/ui/shortcut.h \ include/ui/shortcutseditor.h \ include/ui/multikeyedit.h \ + include/ui/preferenceeditor.h \ include/config.h \ include/editor.h \ include/mainwindow.h \ @@ -163,7 +165,8 @@ FORMS += forms/mainwindow.ui \ forms/aboutporymap.ui \ forms/newtilesetdialog.ui \ forms/mapimageexporter.ui \ - forms/shortcutseditor.ui + forms/shortcutseditor.ui \ + forms/preferenceeditor.ui RESOURCES += \ resources/images.qrc \ diff --git a/src/config.cpp b/src/config.cpp index ae562334..5ef3723f 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -190,6 +190,10 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { } } else if (key == "theme") { this->theme = value; + } else if (key == "text_editor_open_directory") { + this->textEditorOpenFolder = value; + } else if (key == "text_editor_goto_line") { + this->textEditorGotoLine = value; } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -219,6 +223,8 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("region_map_dimensions", QString("%1x%2").arg(this->regionMapDimensions.width()) .arg(this->regionMapDimensions.height())); map.insert("theme", this->theme); + map.insert("text_editor_open_directory", this->textEditorOpenFolder); + map.insert("text_editor_goto_line", this->textEditorGotoLine); return map; } @@ -319,6 +325,16 @@ void PorymapConfig::setTheme(QString theme) { this->theme = theme; } +void PorymapConfig::setTextEditorOpenFolder(const QString &command) { + this->textEditorOpenFolder = command; + this->save(); +} + +void PorymapConfig::setTextEditorGotoLine(const QString &command) { + this->textEditorGotoLine = command; + this->save(); +} + QString PorymapConfig::getRecentProject() { return this->recentProject; } @@ -401,6 +417,14 @@ QString PorymapConfig::getTheme() { return this->theme; } +QString PorymapConfig::getTextEditorOpenFolder() { + return this->textEditorOpenFolder; +} + +QString PorymapConfig::getTextEditorGotoLine() { + return this->textEditorGotoLine; +} + const QMap baseGameVersionMap = { {BaseGameVersion::pokeruby, "pokeruby"}, {BaseGameVersion::pokefirered, "pokefirered"}, diff --git a/src/core/map.cpp b/src/core/map.cpp index 02cd15f8..14da4650 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -450,12 +450,32 @@ void Map::magicFillCollisionElevation(int initialX, int initialY, uint16_t colli } } -QList Map::getAllEvents() { - QList all; - for (QList list : events.values()) { - all += list; +QList Map::getAllEvents() const { + QList all_events; + for (const auto &event_list : events) { + all_events << event_list; } - return all; + return all_events; +} + +QStringList Map::eventScriptLabels(const QString &event_group_type) const { + QStringList scriptLabels; + if (event_group_type.isEmpty()) { + for (const auto *event : getAllEvents()) + scriptLabels << event->get("script_label"); + } else { + for (const auto *event : events.value(event_group_type)) + scriptLabels << event->get("script_label"); + } + + scriptLabels.removeDuplicates(); + scriptLabels.removeAll(QString()); + if (scriptLabels.contains("0x0")) + scriptLabels.move(scriptLabels.indexOf("0x0"), scriptLabels.count() - 1); + if (scriptLabels.contains("NULL")) + scriptLabels.move(scriptLabels.indexOf("NULL"), scriptLabels.count() - 1); + + return scriptLabels; } void Map::removeEvent(Event *event) { diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 9c4f1f2d..74330738 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -420,3 +420,118 @@ bool ParseUtil::ensureFieldsExist(QJsonObject obj, QList fields) { } return true; } + +int ParseUtil::getScriptLineNumber(const QString &filePath, const QString &scriptLabel) { + if (scriptLabel.isEmpty()) + return 0; + + if (filePath.endsWith(".inc") || filePath.endsWith(".s")) + return getRawScriptLineNumber(readTextFile(filePath), scriptLabel); + else if (filePath.endsWith(".pory")) + return getPoryScriptLineNumber(readTextFile(filePath), scriptLabel); + + return 0; +} + +int ParseUtil::getRawScriptLineNumber(QString text, const QString &scriptLabel) { + removeStringLiterals(text); + removeLineComments(text, "@"); + + static const QRegularExpression re_incScriptLabel("\\b(?