1 /*
2 *
3 * Stellarium
4 * Copyright (C) 2009 Matthew Gates
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
19 */
20
21 #include "ScriptConsole.hpp"
22 #include "ui_scriptConsole.h"
23 #include "StelMainView.hpp"
24 #include "StelScriptMgr.hpp"
25 #include "StelFileMgr.hpp"
26 #include "StelApp.hpp"
27 #include "StelTranslator.hpp"
28 #include "StelScriptSyntaxHighlighter.hpp"
29
30 #include <QDialog>
31 #include <QMessageBox>
32 #include <QDebug>
33 #include <QTextStream>
34 #include <QTemporaryFile>
35 #include <QDir>
36 #include <QFile>
37 #include <QFileDialog>
38 #include <QDateTime>
39 #include <QSyntaxHighlighter>
40 #include <QTextDocumentFragment>
41 #include <QRegularExpression>
42
ScriptConsole(QObject * parent)43 ScriptConsole::ScriptConsole(QObject *parent)
44 : StelDialog("ScriptConsole", parent)
45 , highlighter(Q_NULLPTR)
46 , useUserDir(false)
47 , hideWindowAtScriptRun(false)
48 , clearOutput(false)
49 , scriptFileName("")
50 , isNew(true)
51 , dirty(false)
52 {
53 ui = new Ui_scriptConsoleForm;
54 }
55
~ScriptConsole()56 ScriptConsole::~ScriptConsole()
57 {
58 delete ui;
59 delete highlighter; highlighter = Q_NULLPTR;
60 }
61
retranslate()62 void ScriptConsole::retranslate()
63 {
64 if (dialog)
65 {
66 ui->retranslateUi(dialog);
67 populateQuickRunList();
68 }
69 }
70
styleChanged()71 void ScriptConsole::styleChanged()
72 {
73 if (highlighter)
74 {
75 highlighter->setFormats();
76 highlighter->rehighlight();
77 }
78 }
79
populateQuickRunList()80 void ScriptConsole::populateQuickRunList()
81 {
82 ui->quickrunCombo->clear();
83 ui->quickrunCombo->addItem(""); // First line is empty!
84 ui->quickrunCombo->addItem(qc_("selected text as script","command"));
85 ui->quickrunCombo->addItem(qc_("remove screen text","command"));
86 ui->quickrunCombo->addItem(qc_("remove screen images","command"));
87 ui->quickrunCombo->addItem(qc_("remove screen markers","command"));
88 ui->quickrunCombo->addItem(qc_("clear map: natural","command"));
89 ui->quickrunCombo->addItem(qc_("clear map: starchart","command"));
90 ui->quickrunCombo->addItem(qc_("clear map: deepspace","command"));
91 ui->quickrunCombo->addItem(qc_("clear map: galactic","command"));
92 ui->quickrunCombo->addItem(qc_("clear map: supergalactic","command"));
93 }
94
createDialogContent()95 void ScriptConsole::createDialogContent()
96 {
97 ui->setupUi(dialog);
98 connect(&StelApp::getInstance(), SIGNAL(languageChanged()), this, SLOT(retranslate()));
99
100 highlighter = new StelScriptSyntaxHighlighter(ui->scriptEdit->document());
101 ui->includeEdit->setText(StelFileMgr::getInstallationDir() + "/scripts");
102
103 populateQuickRunList();
104
105 connect(ui->scriptEdit, SIGNAL(cursorPositionChanged()), this, SLOT(rowColumnChanged()));
106 connect(ui->scriptEdit, SIGNAL(textChanged()), this, SLOT(setDirty()));
107 connect(ui->closeStelWindow, SIGNAL(clicked()), this, SLOT(close()));
108 connect(ui->TitleBar, SIGNAL(movedTo(QPoint)), this, SLOT(handleMovedTo(QPoint)));
109 connect(ui->loadButton, SIGNAL(clicked()), this, SLOT(loadScript()));
110 connect(ui->saveButton, SIGNAL(clicked()), this, SLOT(saveScript()));
111 connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clearButtonPressed()));
112 connect(ui->preprocessSSCButton, SIGNAL(clicked()), this, SLOT(preprocessScript()));
113 connect(ui->runButton, SIGNAL(clicked()), this, SLOT(runScript()));
114 connect(ui->stopButton, SIGNAL(clicked()), &StelApp::getInstance().getScriptMgr(), SLOT(stopScript()));
115 connect(ui->includeBrowseButton, SIGNAL(clicked()), this, SLOT(includeBrowse()));
116 connect(ui->quickrunCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(quickRun(int)));
117 connect(&StelApp::getInstance().getScriptMgr(), SIGNAL(scriptRunning()), this, SLOT(scriptStarted()));
118 connect(&StelApp::getInstance().getScriptMgr(), SIGNAL(scriptStopped()), this, SLOT(scriptEnded()));
119 connect(&StelApp::getInstance().getScriptMgr(), SIGNAL(scriptDebug(const QString&)), this, SLOT(appendLogLine(const QString&)));
120 connect(&StelApp::getInstance().getScriptMgr(), SIGNAL(scriptOutput(const QString&)), this, SLOT(appendOutputLine(const QString&)));
121 ui->tabs->setCurrentIndex(0);
122
123 // get decent indentation
124 QFont font = ui->scriptEdit->font();
125 QFontMetrics fontMetrics = QFontMetrics(font);
126 int width = fontMetrics.boundingRect("0").width();
127 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
128 ui->scriptEdit->setTabStopDistance(4*width); // 4 characters
129 #else
130 ui->scriptEdit->setTabStopWidth(4*width); // 4 characters
131 #endif
132 ui->scriptEdit->setFocus();
133
134 QSettings* conf = StelApp::getInstance().getSettings();
135 useUserDir = conf->value("gui/flag_scripts_user_dir", false).toBool();
136 ui->useUserDirCheckBox->setChecked(useUserDir);
137 hideWindowAtScriptRun = conf->value("gui/flag_scripts_hide_window", false).toBool();
138 ui->closeWindowAtScriptRunCheckbox->setChecked(hideWindowAtScriptRun);
139 clearOutput = conf->value("gui/flag_scripts_clear_output", false).toBool();
140 ui->clearOutputCheckbox->setChecked(clearOutput);
141 connect(ui->useUserDirCheckBox, SIGNAL(toggled(bool)), this, SLOT(setFlagUserDir(bool)));
142 connect(ui->closeWindowAtScriptRunCheckbox, SIGNAL(toggled(bool)), this, SLOT(setFlagHideWindow(bool)));
143 connect(ui->clearOutputCheckbox, SIGNAL(toggled(bool)), this, SLOT(setFlagClearOutput(bool)));
144
145 // Let's improve visibility of the text
146 QString style = "QLabel { color: rgb(238, 238, 238); }";
147 ui->quickrunLabel->setStyleSheet(style);
148 dirty = false;
149 }
150
setFlagUserDir(bool b)151 void ScriptConsole::setFlagUserDir(bool b)
152 {
153 if (b!=useUserDir)
154 {
155 useUserDir = b;
156 StelApp::getInstance().getSettings()->setValue("gui/flag_scripts_user_dir", b);
157 }
158 }
159
setFlagHideWindow(bool b)160 void ScriptConsole::setFlagHideWindow(bool b)
161 {
162 if (b!=hideWindowAtScriptRun)
163 {
164 hideWindowAtScriptRun = b;
165 StelApp::getInstance().getSettings()->setValue("gui/flag_scripts_hide_window", b);
166 }
167 }
168
setFlagClearOutput(bool b)169 void ScriptConsole::setFlagClearOutput(bool b)
170 {
171 if (b!=clearOutput)
172 {
173 clearOutput = b;
174 StelApp::getInstance().getSettings()->setValue("gui/flag_scripts_clear_output", b);
175 }
176 }
177
loadScript()178 void ScriptConsole::loadScript()
179 {
180 if (dirty)
181 {
182 // We are loaded and dirty: don't just overwrite!
183 if (QMessageBox::question(&StelMainView::getInstance(), q_("Caution!"), q_("Are you sure you want to load script without saving changes?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)
184 return;
185 }
186
187 QString openDir;
188 if (getFlagUserDir())
189 {
190 openDir = StelFileMgr::findFile("scripts", StelFileMgr::Flags(StelFileMgr::Writable|StelFileMgr::Directory));
191 if (openDir.isEmpty() || openDir.contains(StelFileMgr::getInstallationDir()))
192 openDir = StelFileMgr::getUserDir();
193 }
194 else
195 openDir = StelFileMgr::getInstallationDir() + "/scripts";
196
197 QString filter = q_("Stellarium Script Files");
198 filter.append(" (*.ssc *.inc);;");
199 filter.append(getFileMask());
200 QString aFile = QFileDialog::getOpenFileName(Q_NULLPTR, q_("Load Script"), openDir, filter);
201 if (aFile.isNull())
202 return;
203 scriptFileName = aFile;
204 QFile file(scriptFileName);
205 if (file.open(QIODevice::ReadOnly))
206 {
207 ui->scriptEdit->setPlainText(file.readAll());
208 dirty = false;
209 ui->includeEdit->setText(StelFileMgr::dirName(scriptFileName));
210 file.close();
211 }
212 ui->tabs->setCurrentIndex(0);
213 }
214
saveScript()215 void ScriptConsole::saveScript()
216 {
217 QString saveDir = StelFileMgr::findFile("scripts", StelFileMgr::Flags(StelFileMgr::Writable|StelFileMgr::Directory));
218 if (saveDir.isEmpty())
219 saveDir = StelFileMgr::getUserDir();
220
221 QString defaultFilter("(*.ssc)");
222 // Let's ask file name, when file is new and overwrite him in other case
223 if (scriptFileName.isEmpty())
224 {
225 QString aFile = QFileDialog::getSaveFileName(Q_NULLPTR, q_("Save Script"), saveDir + "/myscript.ssc", getFileMask(), &defaultFilter);
226 if (aFile.isNull())
227 return;
228 scriptFileName = aFile;
229 }
230 else
231 {
232 // skip save
233 if (!dirty)
234 return;
235 }
236 QFile file(scriptFileName);
237 if (file.open(QIODevice::WriteOnly))
238 {
239 QTextStream out(&file);
240 out.setCodec("UTF-8");
241 out << ui->scriptEdit->toPlainText();
242 file.close();
243 dirty = false;
244 }
245 else
246 qWarning() << "[ScriptConsole] ERROR - cannot write script file";
247 }
248
clearButtonPressed()249 void ScriptConsole::clearButtonPressed()
250 {
251 if (ui->tabs->currentIndex() == 0)
252 {
253 if (QMessageBox::question(&StelMainView::getInstance(), q_("Caution!"), q_("Are you sure you want to clear script?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)
254 {
255 ui->scriptEdit->clear();
256 scriptFileName = ""; // OK, it's a new file!
257 dirty = false;
258 }
259 }
260 else if (ui->tabs->currentIndex() == 1)
261 ui->logBrowser->clear();
262 else if (ui->tabs->currentIndex() == 2)
263 ui->outputBrowser->clear();
264 }
265
preprocessScript()266 void ScriptConsole::preprocessScript()
267 {
268 //perform pre-processing without an intermediate temp file
269 QString dest;
270 QString src = ui->scriptEdit->toPlainText();
271
272 int errLoc = 0;
273 if (sender() == ui->preprocessSSCButton)
274 {
275 qDebug() << "[ScriptConsole] Preprocessing with SSC proprocessor";
276 StelApp::getInstance().getScriptMgr().preprocessScript( scriptFileName, src, dest, ui->includeEdit->text(), errLoc );
277 }
278 else
279 qDebug() << "[ScriptConsole] WARNING - unknown preprocessor type";
280
281 ui->scriptEdit->setPlainText(dest);
282 scriptFileName = ""; // OK, it's a new file!
283 dirty = true;
284 ui->tabs->setCurrentIndex( 0 );
285 if( errLoc != -1 ){
286 QTextCursor tc = ui->scriptEdit->textCursor();
287 tc.setPosition( errLoc );
288 ui->scriptEdit->setTextCursor( tc );
289 }
290 }
291
runScript()292 void ScriptConsole::runScript()
293 {
294 ui->tabs->setCurrentIndex(1);
295 ui->logBrowser->clear();
296 if( clearOutput )
297 ui->outputBrowser->clear();
298
299 appendLogLine(QString("Starting script at %1").arg(QDateTime::currentDateTime().toString()));
300 int errLoc = 0;
301 if (!StelApp::getInstance().getScriptMgr().runScriptDirect(scriptFileName, ui->scriptEdit->toPlainText(), errLoc, ui->includeEdit->text()))
302 {
303 QString msg = QString("ERROR - cannot run script");
304 qWarning() << "[ScriptConsole] " + msg;
305 appendLogLine(msg);
306 if( errLoc != -1 ){
307 QTextCursor tc = ui->scriptEdit->textCursor();
308 tc.setPosition( errLoc );
309 ui->scriptEdit->setTextCursor( tc );
310 }
311 return;
312 }
313 }
314
scriptStarted()315 void ScriptConsole::scriptStarted()
316 {
317 //prevent strating of scripts while any script is running
318 ui->quickrunCombo->setEnabled(false);
319 ui->runButton->setEnabled(false);
320 ui->stopButton->setEnabled(true);
321 if (hideWindowAtScriptRun)
322 dialog->setVisible(false);
323 }
324
scriptEnded()325 void ScriptConsole::scriptEnded()
326 {
327 qDebug() << "ScriptConsole::scriptEnded";
328 appendLogLine(QString("Script finished at %1").arg(QDateTime::currentDateTime().toString()));
329 ui->quickrunCombo->setEnabled(true);
330 ui->runButton->setEnabled(true);
331 ui->stopButton->setEnabled(false);
332 if (hideWindowAtScriptRun)
333 dialog->setVisible(true);
334 }
335
appendLogLine(const QString & s)336 void ScriptConsole::appendLogLine(const QString& s)
337 {
338 QString html = ui->logBrowser->toHtml();
339 html.replace(QRegularExpression("^\\s+"), "");
340 html += s;
341 ui->logBrowser->setHtml(html);
342 }
343
appendOutputLine(const QString & s)344 void ScriptConsole::appendOutputLine(const QString& s)
345 {
346 if (s.isEmpty())
347 {
348 ui->outputBrowser->clear();
349 }
350 else
351 {
352 QString html = ui->outputBrowser->toHtml();
353 html.replace(QRegularExpression("^\\s+"), "");
354 html += s;
355 ui->outputBrowser->setHtml(html);
356 }
357 }
358
includeBrowse()359 void ScriptConsole::includeBrowse()
360 {
361 QString aDir = QFileDialog::getExistingDirectory(Q_NULLPTR, q_("Select Script Include Directory"), StelFileMgr::getInstallationDir() + "/scripts");
362 if (!aDir.isNull())
363 ui->includeEdit->setText(aDir);
364 }
365
quickRun(int idx)366 void ScriptConsole::quickRun(int idx)
367 {
368 if (idx==0)
369 return;
370 static const QMap<int, QString>map = {
371 {2, "LabelMgr.deleteAllLabels();\n"},
372 {3, "ScreenImageMgr.deleteAllImages();\n"},
373 {4, "MarkerMgr.deleteAllMarkers();\n"},
374 {5, "core.clear(\"natural\");\n"},
375 {6, "core.clear(\"starchart\");\n"},
376 {7, "core.clear(\"deepspace\");\n"},
377 {8, "core.clear(\"galactic\");\n"},
378 {9, "core.clear(\"supergalactic\");\n"}};
379 QString scriptText = map.value(idx, QTextDocumentFragment::fromHtml(ui->scriptEdit->textCursor().selectedText(), ui->scriptEdit->document()).toPlainText());
380
381 if (!scriptText.isEmpty())
382 {
383 appendLogLine(QString("Running: %1").arg(scriptText));
384 int errLoc;
385 StelApp::getInstance().getScriptMgr().runScriptDirect( "<>", scriptText, errLoc );
386 ui->quickrunCombo->setCurrentIndex(0);
387 }
388 }
389
rowColumnChanged()390 void ScriptConsole::rowColumnChanged()
391 {
392 // TRANSLATORS: The first letter of word "Row"
393 QString row = qc_("R", "text cursor");
394 // TRANSLATORS: The first letter of word "Column"
395 QString column = qc_("C", "text cursor");
396 ui->rowColumnLabel->setText(QString("%1:%2 %3:%4")
397 .arg(row).arg(ui->scriptEdit->textCursor().blockNumber())
398 .arg(column).arg(ui->scriptEdit->textCursor().columnNumber()));
399 }
400
setDirty()401 void ScriptConsole::setDirty()
402 {
403 if (isNew)
404 isNew = false;
405 else
406 dirty = true;
407 }
408
getFileMask()409 const QString ScriptConsole::getFileMask()
410 {
411 QString filter = q_("Stellarium Script");
412 filter.append(" (*.ssc);;");
413 filter.append(q_("Include File"));
414 filter.append(" (*.inc)");
415 return filter;
416 }
417
418