Add action to open the project root in a text editor
This commit is contained in:
parent
a4528fb0d9
commit
dbafb99fd4
9 changed files with 204 additions and 97 deletions
|
@ -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"/>
|
||||
|
|
|
@ -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><html><head/><body><p>The command that will be executed when clicking the &quot;Open Map Scripts&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 <span style=" font-weight:600;">not</span> specified then the map scripts file path will be appended to the end of the command.</p></body></html></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><html><head/><body><p>The command that will be executed when clicking the <span style=" font-weight:600;">Open Map Scripts</span> button<span style=" font-weight:600;">. %F</span> will be substituted with the scripts file path and <span style=" font-weight:600;">%L</span> will be substituted with the line number of the event that is currently selected. <span style=" font-weight:600;">%F </span><span style=" font-style:italic;">must</span> be specified if <span style=" font-weight:600;">%L</span> is specified. If <span style=" font-weight:600;">%F</span> is <span style=" font-style:italic;">not</span> specified then the scripts file path will be appended to the end of the command.</p></body></html></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><html><head/><body><p>The command that will be executed when clicking <span style=" font-weight:600;">Open Project in Text Editor</span> in the <span style=" font-weight:600;">Tools</span> menu. <span style=" font-weight:600;">%D</span> will be substituted with the current 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 appended to the end of the command.</p></body></html></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">
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue