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="actionTileset_Editor"/>
<addaction name="actionRegion_Map_Editor"/>
<addaction name="separator"/>
<addaction name="actionOpen_Project_in_Text_Editor"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
@ -2904,6 +2906,14 @@
<property name="text">
<string>Edit Preferences...</string>
</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>
</widget>
<layoutdefault spacing="6" margin="11"/>

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>320</height>
<width>500</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
@ -15,51 +15,6 @@
</property>
<widget class="QWidget" name="centralwidget">
<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>
<widget class="QGroupBox" name="groupBox_Themes">
<property name="sizePolicy">
@ -73,6 +28,118 @@
</property>
</widget>
</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>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">

View file

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

View file

@ -155,6 +155,7 @@ public:
public slots:
void openMapScripts() const;
void openProjectInTextEditor() const;
void maskNonVisibleConnectionTiles();
private:
@ -184,7 +185,9 @@ private:
QString getMovementPermissionText(uint16_t collision, uint16_t elevation);
QString getMetatileDisplayMessage(uint16_t metatileId);
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:
void onMapStartPaint(QGraphicsSceneMouseEvent *event, MapPixmapItem *item);

View file

@ -187,8 +187,10 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) {
}
} else if (key == "theme") {
this->theme = value;
} else if (key == "text_editor") {
this->textEditorCommandTemplate = 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));
}
@ -218,7 +220,8 @@ QMap<QString, QString> 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", this->textEditorCommandTemplate);
map.insert("text_editor_open_directory", this->textEditorOpenFolder);
map.insert("text_editor_goto_line", this->textEditorGotoLine);
return map;
}
@ -319,8 +322,13 @@ void PorymapConfig::setTheme(QString theme) {
this->theme = theme;
}
void PorymapConfig::setTextEditorCommandTemplate(const QString &commandTemplate) {
this->textEditorCommandTemplate = commandTemplate;
void PorymapConfig::setTextEditorOpenFolder(const QString &command) {
this->textEditorOpenFolder = command;
this->save();
}
void PorymapConfig::setTextEditorGotoLine(const QString &command) {
this->textEditorGotoLine = command;
this->save();
}
@ -406,8 +414,12 @@ QString PorymapConfig::getTheme() {
return this->theme;
}
QString PorymapConfig::getTextEditorCommandTemplate() {
return this->textEditorCommandTemplate;
QString PorymapConfig::getTextEditorOpenFolder() {
return this->textEditorOpenFolder;
}
QString PorymapConfig::getTextEditorGotoLine() {
return this->textEditorGotoLine;
}
const QMap<BaseGameVersion, QString> baseGameVersionMap = {

View file

@ -434,7 +434,7 @@ int ParseUtil::getRawScriptLineNumber(QString text, const QString &scriptLabel)
removeStringLiterals(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);
while (it.hasNext()) {
const QRegularExpressionMatch match = it.next();
@ -449,7 +449,7 @@ int ParseUtil::getPoryScriptLineNumber(QString text, const QString &scriptLabel)
removeStringLiterals(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);
while (it.hasNext()) {
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;
}
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);
while (raw_it.hasNext()) {
const QRegularExpressionMatch match = raw_it.next();
const int relative_lineNum = getRawScriptLineNumber(match.captured("script"), scriptLabel);
if (relative_lineNum)
return text.left(match.capturedStart("script")).count('\n') + relative_lineNum;
const int relativelineNum = getRawScriptLineNumber(match.captured("raw_script"), scriptLabel);
if (relativelineNum)
return text.left(match.capturedStart("raw_script")).count('\n') + relativelineNum;
}
return 0;

View file

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

View file

@ -29,7 +29,6 @@
#include <QDialogButtonBox>
#include <QScroller>
#include <math.h>
#include <QProcess>
#include <QSysInfo>
#include <QDesktopServices>
#include <QTransform>
@ -155,6 +154,7 @@ void MainWindow::initEditor() {
connect(this->editor, SIGNAL(currentMetatilesSelectionChanged()), this, SLOT(currentMetatilesSelectionChanged()));
connect(this->editor, SIGNAL(wildMonDataChanged()), this, SLOT(onWildMonDataChanged()));
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();

View file

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