Add action to open the project root in a text editor

This commit is contained in:
BigBahss 2020-12-01 07:12:32 -05:00
parent a4528fb0d9
commit dbafb99fd4
9 changed files with 204 additions and 97 deletions

View file

@ -2623,6 +2623,8 @@
<addaction name="actionNew_Tileset"/> <addaction name="actionNew_Tileset"/>
<addaction name="actionTileset_Editor"/> <addaction name="actionTileset_Editor"/>
<addaction name="actionRegion_Map_Editor"/> <addaction name="actionRegion_Map_Editor"/>
<addaction name="separator"/>
<addaction name="actionOpen_Project_in_Text_Editor"/>
</widget> </widget>
<widget class="QMenu" name="menuHelp"> <widget class="QMenu" name="menuHelp">
<property name="title"> <property name="title">
@ -2904,6 +2906,14 @@
<property name="text"> <property name="text">
<string>Edit Preferences...</string> <string>Edit Preferences...</string>
</property> </property>
<property name="shortcut">
<string>Ctrl+,</string>
</property>
</action>
<action name="actionOpen_Project_in_Text_Editor">
<property name="text">
<string>Open Project in Text Editor</string>
</property>
</action> </action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>480</width> <width>500</width>
<height>320</height> <height>500</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -15,51 +15,6 @@
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox_TextEditor">
<property name="title">
<string>Preferred Text Editor</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_TextEditor">
<property name="text">
<string>Command</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_TextEditor">
<property name="toolTip">
<string>The command (including the necessary parameters) to perform the action. See the command parameter legend below for a list of supported parameter variables, which will be substituted when launching the command. When upper-case letters (e.g. %F, $D, %N) are used, the action will be applicable even if more than one item is selected. Esle the action will only be applicable f exactly one item is selected.</string>
</property>
<property name="placeholderText">
<string>e.g. code --goto %F:%L</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_TextEditorHelp">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The command that will be executed when clicking the &amp;quot;Open Map Scripts&amp;quot; button. You may optionally include '%F' and '%L' in the command. '%F' will be substituted with the scripts file path and '%L' will be substituted with a line number. If '%L' is specified then the scripts file will be opened to map script cooresponding to the currently selected event (If the script can be found). If '%F' is &lt;span style=&quot; font-weight:600;&quot;&gt;not&lt;/span&gt; specified then the map scripts file path will be appended to the end of the command.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QGroupBox" name="groupBox_Themes"> <widget class="QGroupBox" name="groupBox_Themes">
<property name="sizePolicy"> <property name="sizePolicy">
@ -73,6 +28,118 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox_TextEditor">
<property name="title">
<string>Preferred Text Editor</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="2" column="0">
<widget class="QScrollArea" name="scrollArea_TextEditor">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents_TextEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>482</width>
<height>398</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item row="5" column="0" colspan="2">
<widget class="QLabel" name="label_TextEditorGotoLineHelp">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The command that will be executed when clicking the &lt;span style=&quot; font-weight:600;&quot;&gt;Open Map Scripts&lt;/span&gt; button&lt;span style=&quot; font-weight:600;&quot;&gt;. %F&lt;/span&gt; will be substituted with the scripts file path and &lt;span style=&quot; font-weight:600;&quot;&gt;%L&lt;/span&gt; will be substituted with the line number of the event that is currently selected. &lt;span style=&quot; font-weight:600;&quot;&gt;%F &lt;/span&gt;&lt;span style=&quot; font-style:italic;&quot;&gt;must&lt;/span&gt; be specified if &lt;span style=&quot; font-weight:600;&quot;&gt;%L&lt;/span&gt; is specified. If &lt;span style=&quot; font-weight:600;&quot;&gt;%F&lt;/span&gt; is &lt;span style=&quot; font-style:italic;&quot;&gt;not&lt;/span&gt; specified then the scripts file path will be appended to the end of the command.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="lineEdit_TextEditorGotoLine">
<property name="toolTip">
<string>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).</string>
</property>
<property name="placeholderText">
<string>e.g. code --goto %F:%L</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QLabel" name="label_TextEditorOpenFolderHelp">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The command that will be executed when clicking &lt;span style=&quot; font-weight:600;&quot;&gt;Open Project in Text Editor&lt;/span&gt; in the &lt;span style=&quot; font-weight:600;&quot;&gt;Tools&lt;/span&gt; menu. &lt;span style=&quot; font-weight:600;&quot;&gt;%D&lt;/span&gt; will be substituted with the current project's root directory. If &lt;span style=&quot; font-weight:600;&quot;&gt;%D&lt;/span&gt; is &lt;span style=&quot; font-style:italic;&quot;&gt;not&lt;/span&gt; specified then the project directory will be appended to the end of the command.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_TextEditorOpenFolder">
<property name="text">
<string>Open Directory Command</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_TextEditorOpenFolder">
<property name="toolTip">
<string>The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH).</string>
</property>
<property name="placeholderText">
<string>e.g. code %D</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_TextEditorGotoLine">
<property name="text">
<string>Goto Line Command</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">

View file

@ -45,6 +45,8 @@ public:
this->monitorFiles = true; this->monitorFiles = true;
this->regionMapDimensions = QSize(32, 20); this->regionMapDimensions = QSize(32, 20);
this->theme = "default"; this->theme = "default";
this->textEditorOpenFolder = "";
this->textEditorGotoLine = "";
} }
void setRecentProject(QString project); void setRecentProject(QString project);
void setRecentMap(QString map); void setRecentMap(QString map);
@ -61,7 +63,8 @@ public:
void setMonitorFiles(bool monitor); void setMonitorFiles(bool monitor);
void setRegionMapDimensions(int width, int height); void setRegionMapDimensions(int width, int height);
void setTheme(QString theme); void setTheme(QString theme);
void setTextEditorCommandTemplate(const QString &commandTemplate); void setTextEditorOpenFolder(const QString &command);
void setTextEditorGotoLine(const QString &command);
QString getRecentProject(); QString getRecentProject();
QString getRecentMap(); QString getRecentMap();
MapSortOrder getMapSortOrder(); MapSortOrder getMapSortOrder();
@ -77,7 +80,8 @@ public:
bool getMonitorFiles(); bool getMonitorFiles();
QSize getRegionMapDimensions(); QSize getRegionMapDimensions();
QString getTheme(); QString getTheme();
QString getTextEditorCommandTemplate(); QString getTextEditorOpenFolder();
QString getTextEditorGotoLine();
protected: protected:
virtual QString getConfigFilepath() override; virtual QString getConfigFilepath() override;
virtual void parseConfigKeyValue(QString key, QString value) override; virtual void parseConfigKeyValue(QString key, QString value) override;
@ -109,7 +113,8 @@ private:
bool monitorFiles; bool monitorFiles;
QSize regionMapDimensions; QSize regionMapDimensions;
QString theme; QString theme;
QString textEditorCommandTemplate; QString textEditorOpenFolder;
QString textEditorGotoLine;
}; };
extern PorymapConfig porymapConfig; extern PorymapConfig porymapConfig;

View file

@ -155,6 +155,7 @@ public:
public slots: public slots:
void openMapScripts() const; void openMapScripts() const;
void openProjectInTextEditor() const;
void maskNonVisibleConnectionTiles(); void maskNonVisibleConnectionTiles();
private: private:
@ -184,7 +185,9 @@ private:
QString getMovementPermissionText(uint16_t collision, uint16_t elevation); QString getMovementPermissionText(uint16_t collision, uint16_t elevation);
QString getMetatileDisplayMessage(uint16_t metatileId); QString getMetatileDisplayMessage(uint16_t metatileId);
bool eventLimitReached(Map *, QString); bool eventLimitReached(Map *, QString);
QString constructTextEditorCommand(QString commandTemplate, const QString &path) const; bool startDetachedProcess(const QString &command,
const QString &workingDirectory = QString(),
qint64 *pid = nullptr) const;
private slots: private slots:
void onMapStartPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); void onMapStartPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item);

View file

@ -187,8 +187,10 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
} }
} else if (key == "theme") { } else if (key == "theme") {
this->theme = value; this->theme = value;
} else if (key == "text_editor") { } else if (key == "text_editor_open_directory") {
this->textEditorCommandTemplate = value; this->textEditorOpenFolder = value;
} else if (key == "text_editor_goto_line") {
this->textEditorGotoLine = value;
} else { } else {
logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key));
} }
@ -218,7 +220,8 @@ QMap<QString, QString> PorymapConfig::getKeyValueMap() {
map.insert("region_map_dimensions", QString("%1x%2").arg(this->regionMapDimensions.width()) map.insert("region_map_dimensions", QString("%1x%2").arg(this->regionMapDimensions.width())
.arg(this->regionMapDimensions.height())); .arg(this->regionMapDimensions.height()));
map.insert("theme", this->theme); map.insert("theme", this->theme);
map.insert("text_editor", this->textEditorCommandTemplate); map.insert("text_editor_open_directory", this->textEditorOpenFolder);
map.insert("text_editor_goto_line", this->textEditorGotoLine);
return map; return map;
} }
@ -319,8 +322,13 @@ void PorymapConfig::setTheme(QString theme) {
this->theme = theme; this->theme = theme;
} }
void PorymapConfig::setTextEditorCommandTemplate(const QString &commandTemplate) { void PorymapConfig::setTextEditorOpenFolder(const QString &command) {
this->textEditorCommandTemplate = commandTemplate; this->textEditorOpenFolder = command;
this->save();
}
void PorymapConfig::setTextEditorGotoLine(const QString &command) {
this->textEditorGotoLine = command;
this->save(); this->save();
} }
@ -406,8 +414,12 @@ QString PorymapConfig::getTheme() {
return this->theme; return this->theme;
} }
QString PorymapConfig::getTextEditorCommandTemplate() { QString PorymapConfig::getTextEditorOpenFolder() {
return this->textEditorCommandTemplate; return this->textEditorOpenFolder;
}
QString PorymapConfig::getTextEditorGotoLine() {
return this->textEditorGotoLine;
} }
const QMap<BaseGameVersion, QString> baseGameVersionMap = { const QMap<BaseGameVersion, QString> baseGameVersionMap = {

View file

@ -434,7 +434,7 @@ int ParseUtil::getRawScriptLineNumber(QString text, const QString &scriptLabel)
removeStringLiterals(text); removeStringLiterals(text);
removeLineComments(text, "@"); removeLineComments(text, "@");
static const QRegularExpression re_incScriptLabel("(?<label>[a-zA-Z_]+[a-zA-Z0-9_]*):"); static const QRegularExpression re_incScriptLabel("(?<label>[\\w_][\\w\\d_]*):");
QRegularExpressionMatchIterator it = re_incScriptLabel.globalMatch(text); QRegularExpressionMatchIterator it = re_incScriptLabel.globalMatch(text);
while (it.hasNext()) { while (it.hasNext()) {
const QRegularExpressionMatch match = it.next(); const QRegularExpressionMatch match = it.next();
@ -449,7 +449,7 @@ int ParseUtil::getPoryScriptLineNumber(QString text, const QString &scriptLabel)
removeStringLiterals(text); removeStringLiterals(text);
removeLineComments(text, {"//", "#"}); removeLineComments(text, {"//", "#"});
static const QRegularExpression re_poryScriptLabel("\\b(script)\\b[\\s]+(?<label>[a-zA-Z_]+[a-zA-Z0-9_]*)"); static const QRegularExpression re_poryScriptLabel("\\b(script)\\b[\\s]+(?<label>[\\w_][\\w\\d_]*)");
QRegularExpressionMatchIterator it = re_poryScriptLabel.globalMatch(text); QRegularExpressionMatchIterator it = re_poryScriptLabel.globalMatch(text);
while (it.hasNext()) { while (it.hasNext()) {
const QRegularExpressionMatch match = it.next(); const QRegularExpressionMatch match = it.next();
@ -457,13 +457,13 @@ int ParseUtil::getPoryScriptLineNumber(QString text, const QString &scriptLabel)
return text.left(match.capturedStart("label")).count('\n') + 1; return text.left(match.capturedStart("label")).count('\n') + 1;
} }
static const QRegularExpression re_poryRawSection("\\b(raw)\\b[\\s]*`(?<script>[^`]*)"); static const QRegularExpression re_poryRawSection("\\b(raw)\\b[\\s]*`(?<raw_script>[^`]*)");
QRegularExpressionMatchIterator raw_it = re_poryRawSection.globalMatch(text); QRegularExpressionMatchIterator raw_it = re_poryRawSection.globalMatch(text);
while (raw_it.hasNext()) { while (raw_it.hasNext()) {
const QRegularExpressionMatch match = raw_it.next(); const QRegularExpressionMatch match = raw_it.next();
const int relative_lineNum = getRawScriptLineNumber(match.captured("script"), scriptLabel); const int relativelineNum = getRawScriptLineNumber(match.captured("raw_script"), scriptLabel);
if (relative_lineNum) if (relativelineNum)
return text.left(match.capturedStart("script")).count('\n') + relative_lineNum; return text.left(match.capturedStart("raw_script")).count('\n') + relativelineNum;
} }
return 0; return 0;

View file

@ -14,6 +14,7 @@
#include <QPainter> #include <QPainter>
#include <QMouseEvent> #include <QMouseEvent>
#include <QDir> #include <QDir>
#include <QProcess>
#include <math.h> #include <math.h>
static bool selectNewEvents = false; static bool selectNewEvents = false;
@ -2002,40 +2003,45 @@ void Editor::deleteEvent(Event *event) {
void Editor::openMapScripts() const { void Editor::openMapScripts() const {
const QString scriptsPath = project->getMapScriptsFilePath(map->name); const QString scriptsPath = project->getMapScriptsFilePath(map->name);
const QString commandTemplate = porymapConfig.getTextEditorCommandTemplate(); QString command = porymapConfig.getTextEditorGotoLine();
if (commandTemplate.isEmpty()) { if (command.isEmpty()) {
// Open map scripts in the system's default editor. // Open map scripts in the system's default editor.
QDesktopServices::openUrl(QUrl::fromLocalFile(scriptsPath)); QDesktopServices::openUrl(QUrl::fromLocalFile(scriptsPath));
} else { } else {
const QString command = constructTextEditorCommand(commandTemplate, scriptsPath); if (command.contains("%F")) {
#ifdef Q_OS_WIN if (command.contains("%L")) {
// On Windows, a QProcess command must be wrapped in a cmd.exe command. const QString scriptLabel = selected_events->isEmpty() ?
const QString program = QProcessEnvironment::systemEnvironment().value("COMSPEC"); QString() : selected_events->first()->event->get("script_label");
QStringList arguments({ QString("/c"), command }); const int lineNum = ParseUtil::getScriptLineNumber(scriptsPath, scriptLabel);
#else command.replace("%L", QString::number(lineNum));
QStringList arguments = QProcess::splitCommand(command); }
const QString program = arguments.takeFirst(); command.replace("%F", scriptsPath);
#endif } else {
static QProcess proc; command += ' ' + scriptsPath;
proc.setProgram(program); }
proc.setArguments(arguments); startDetachedProcess(command);
proc.start();
} }
} }
QString Editor::constructTextEditorCommand(QString commandTemplate, const QString &filePath) const { void Editor::openProjectInTextEditor() const {
if (commandTemplate.contains("%F")) { QString command = porymapConfig.getTextEditorOpenFolder();
if (commandTemplate.contains("%L")) { if (command.contains("%D"))
const QString scriptLabel = selected_events->isEmpty() ? command.replace("%D", project->root);
QString() : selected_events->first()->event->get("script_label"); else
const int lineNum = ParseUtil::getScriptLineNumber(filePath, scriptLabel); command += ' ' + project->root;
commandTemplate.replace("%L", QString::number(lineNum)); startDetachedProcess(command);
} }
commandTemplate.replace("%F", filePath);
} else { bool Editor::startDetachedProcess(const QString &command, const QString &workingDirectory, qint64 *pid) const {
commandTemplate += filePath; #ifdef Q_OS_WIN
} // On Windows, a QProcess command must be wrapped in a cmd.exe command.
return commandTemplate; const QString program = QProcessEnvironment::systemEnvironment().value("COMSPEC");
QStringList arguments({ QString("/c"), command });
#else
QStringList arguments = QProcess::splitCommand(command);
const QString program = arguments.takeFirst();
#endif
return QProcess::startDetached(program, arguments, workingDirectory, pid);
} }
// It doesn't seem to be possible to prevent the mousePress event // It doesn't seem to be possible to prevent the mousePress event

View file

@ -29,7 +29,6 @@
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QScroller> #include <QScroller>
#include <math.h> #include <math.h>
#include <QProcess>
#include <QSysInfo> #include <QSysInfo>
#include <QDesktopServices> #include <QDesktopServices>
#include <QTransform> #include <QTransform>
@ -155,6 +154,7 @@ void MainWindow::initEditor() {
connect(this->editor, SIGNAL(currentMetatilesSelectionChanged()), this, SLOT(currentMetatilesSelectionChanged())); connect(this->editor, SIGNAL(currentMetatilesSelectionChanged()), this, SLOT(currentMetatilesSelectionChanged()));
connect(this->editor, SIGNAL(wildMonDataChanged()), this, SLOT(onWildMonDataChanged())); connect(this->editor, SIGNAL(wildMonDataChanged()), this, SLOT(onWildMonDataChanged()));
connect(ui->toolButton_Open_Scripts, &QToolButton::clicked, this->editor, &Editor::openMapScripts); connect(ui->toolButton_Open_Scripts, &QToolButton::clicked, this->editor, &Editor::openMapScripts);
connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this->editor, &Editor::openProjectInTextEditor);
this->loadUserSettings(); this->loadUserSettings();

View file

@ -30,8 +30,6 @@ PreferenceEditor::~PreferenceEditor()
} }
void PreferenceEditor::populateFields() { void PreferenceEditor::populateFields() {
ui->lineEdit_TextEditor->setText(porymapConfig.getTextEditorCommandTemplate());
QStringList themes = { "default" }; QStringList themes = { "default" };
QRegularExpression re(":/themes/([A-z0-9_-]+).qss"); QRegularExpression re(":/themes/([A-z0-9_-]+).qss");
QDirIterator it(":/themes", QDirIterator::Subdirectories); QDirIterator it(":/themes", QDirIterator::Subdirectories);
@ -41,17 +39,23 @@ void PreferenceEditor::populateFields() {
} }
themeSelector->addItems(themes); themeSelector->addItems(themes);
themeSelector->setCurrentText(porymapConfig.getTheme()); themeSelector->setCurrentText(porymapConfig.getTheme());
ui->lineEdit_TextEditorOpenFolder->setText(porymapConfig.getTextEditorOpenFolder());
ui->lineEdit_TextEditorGotoLine->setText(porymapConfig.getTextEditorGotoLine());
} }
void PreferenceEditor::saveFields() { void PreferenceEditor::saveFields() {
porymapConfig.setTextEditorCommandTemplate(ui->lineEdit_TextEditor->text());
if (themeSelector->currentText() != porymapConfig.getTheme()) { if (themeSelector->currentText() != porymapConfig.getTheme()) {
const auto theme = themeSelector->currentText(); const auto theme = themeSelector->currentText();
porymapConfig.setTheme(theme); porymapConfig.setTheme(theme);
emit themeChanged(theme); emit themeChanged(theme);
} }
porymapConfig.setTextEditorOpenFolder(ui->lineEdit_TextEditorOpenFolder->text());
porymapConfig.setTextEditorGotoLine(ui->lineEdit_TextEditorGotoLine->text());
emit preferencesSaved(); emit preferencesSaved();
} }