Merge pull request #6087 from thoys/20708

CR for Job #20708 - Script Editor Improvements
This commit is contained in:
James B. Pollack 2015-10-15 12:28:32 -07:00
commit e63ed5cf84
9 changed files with 116 additions and 109 deletions

View file

@ -14,7 +14,7 @@
height="45"
id="svg3827"
version="1.1"
inkscape:version="0.48.1 r9760"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="start-script.svg">
<defs
id="defs3829">
@ -439,16 +439,16 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="76.804753"
inkscape:cy="13.198134"
inkscape:zoom="22.4"
inkscape:cx="44.04179"
inkscape:cy="22.346221"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1028"
inkscape:window-width="1680"
inkscape:window-height="997"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-y="21"
inkscape:window-maximized="1" />
<metadata
id="metadata3832">
@ -536,5 +536,15 @@
height="44.57473"
x="84.498352"
y="1050.0748" />
<g
id="run-arrow"
transform="translate(-46.607143,-3.5714285)"
inkscape:label="#run-arrow">
<path
d="m 75.506508,1023.3478 1.372845,5.4631 -14.094975,-1.152 2.35e-4,7.2772 14.094975,-1.152 -1.372845,5.1249 13.761293,-7.6113 -13.761293,-7.9499 z"
id="arrow-rect"
inkscape:connector-curvature="0"
style="fill:#d3d3d3;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:0.77974033;stroke-linecap:round;stroke-linejoin:round" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -16,7 +16,7 @@ ScriptHighlighting::ScriptHighlighting(QTextDocument* parent) :
QSyntaxHighlighter(parent)
{
_keywordRegex = QRegExp("\\b(break|case|catch|continue|debugger|default|delete|do|else|finally|for|function|if|in|instanceof|new|return|switch|this|throw|try|typeof|var|void|while|with)\\b");
_qoutedTextRegex = QRegExp("\"[^\"]*(\"){0,1}");
_quotedTextRegex = QRegExp("(\"[^\"]*(\"){0,1}|\'[^\']*(\'){0,1})");
_multiLineCommentBegin = QRegExp("/\\*");
_multiLineCommentEnd = QRegExp("\\*/");
_numberRegex = QRegExp("[0-9]+(\\.[0-9]+){0,1}");
@ -29,7 +29,7 @@ void ScriptHighlighting::highlightBlock(const QString& text) {
this->highlightKeywords(text);
this->formatNumbers(text);
this->formatTrueFalse(text);
this->formatQoutedText(text);
this->formatQuotedText(text);
this->formatComments(text);
}
@ -61,14 +61,14 @@ void ScriptHighlighting::formatComments(const QString& text) {
int index = _singleLineComment.indexIn(text);
while (index >= 0) {
int length = _singleLineComment.matchedLength();
int quoted_index = _qoutedTextRegex.indexIn(text);
int quoted_index = _quotedTextRegex.indexIn(text);
bool valid = true;
while (quoted_index >= 0 && valid) {
int quoted_length = _qoutedTextRegex.matchedLength();
int quoted_length = _quotedTextRegex.matchedLength();
if (quoted_index <= index && index <= (quoted_index + quoted_length)) {
valid = false;
}
quoted_index = _qoutedTextRegex.indexIn(text, quoted_index + quoted_length);
quoted_index = _quotedTextRegex.indexIn(text, quoted_index + quoted_length);
}
if (valid) {
@ -78,12 +78,12 @@ void ScriptHighlighting::formatComments(const QString& text) {
}
}
void ScriptHighlighting::formatQoutedText(const QString& text){
int index = _qoutedTextRegex.indexIn(text);
void ScriptHighlighting::formatQuotedText(const QString& text){
int index = _quotedTextRegex.indexIn(text);
while (index >= 0) {
int length = _qoutedTextRegex.matchedLength();
int length = _quotedTextRegex.matchedLength();
setFormat(index, length, Qt::red);
index = _qoutedTextRegex.indexIn(text, index + length);
index = _quotedTextRegex.indexIn(text, index + length);
}
}

View file

@ -29,14 +29,14 @@ protected:
void highlightBlock(const QString& text);
void highlightKeywords(const QString& text);
void formatComments(const QString& text);
void formatQoutedText(const QString& text);
void formatQuotedText(const QString& text);
void formatNumbers(const QString& text);
void formatTrueFalse(const QString& text);
private:
QRegExp _alphacharRegex;
QRegExp _keywordRegex;
QRegExp _qoutedTextRegex;
QRegExp _quotedTextRegex;
QRegExp _multiLineCommentBegin;
QRegExp _multiLineCommentEnd;
QRegExp _numberRegex;

View file

@ -9,9 +9,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QFuture>
#include <QKeyEvent>
#include <QLabel>
#include <QScrollBar>
#include <QtConcurrent/QtConcurrentRun>
#include <PathUtils.h>
@ -35,7 +37,8 @@ JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) :
_ui(new Ui::Console),
_currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND),
_commandHistory(),
_scriptEngine(scriptEngine) {
_ownScriptEngine(scriptEngine == NULL),
_scriptEngine(NULL) {
_ui->setupUi(this);
_ui->promptTextEdit->setLineWrapMode(QTextEdit::NoWrap);
@ -51,23 +54,42 @@ JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) :
connect(_ui->scrollArea->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(scrollToBottom()));
connect(_ui->promptTextEdit, SIGNAL(textChanged()), this, SLOT(resizeTextInput()));
if (_scriptEngine == NULL) {
_scriptEngine = qApp->loadScript(QString(), false);
}
connect(_scriptEngine, SIGNAL(evaluationFinished(QScriptValue, bool)),
this, SLOT(handleEvalutationFinished(QScriptValue, bool)));
connect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&)));
connect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(handleError(const QString&)));
setScriptEngine(scriptEngine);
resizeTextInput();
connect(&_executeWatcher, SIGNAL(finished()), this, SLOT(commandFinished()));
}
JSConsole::~JSConsole() {
disconnect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&)));
disconnect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(handleError(const QString&)));
if (_ownScriptEngine) {
_scriptEngine->deleteLater();
}
delete _ui;
}
void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) {
if (_scriptEngine == scriptEngine && scriptEngine != NULL) {
return;
}
if (_scriptEngine != NULL) {
disconnect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&)));
disconnect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(handleError(const QString&)));
if (_ownScriptEngine) {
_scriptEngine->deleteLater();
}
}
// if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine
_ownScriptEngine = scriptEngine == NULL;
_scriptEngine = _ownScriptEngine ? qApp->loadScript(QString(), false) : scriptEngine;
connect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&)));
connect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(handleError(const QString&)));
}
void JSConsole::executeCommand(const QString& command) {
_commandHistory.prepend(command);
if (_commandHistory.length() > MAX_HISTORY_SIZE) {
@ -78,23 +100,34 @@ void JSConsole::executeCommand(const QString& command) {
appendMessage(">", "<span style='" + COMMAND_STYLE + "'>" + command.toHtmlEscaped() + "</span>");
QMetaObject::invokeMethod(_scriptEngine, "evaluate", Q_ARG(const QString&, command));
resetCurrentCommandHistory();
QFuture<QScriptValue> future = QtConcurrent::run(this, &JSConsole::executeCommandInWatcher, command);
_executeWatcher.setFuture(future);
}
void JSConsole::handleEvalutationFinished(QScriptValue result, bool isException) {
QScriptValue JSConsole::executeCommandInWatcher(const QString& command) {
QScriptValue result;
QMetaObject::invokeMethod(_scriptEngine, "evaluate", Qt::ConnectionType::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, result), Q_ARG(const QString&, command));
return result;
}
void JSConsole::commandFinished() {
QScriptValue result = _executeWatcher.result();
_ui->promptTextEdit->setDisabled(false);
// Make sure focus is still on this window - some commands are blocking and can take awhile to execute.
if (window()->isActiveWindow()) {
_ui->promptTextEdit->setFocus();
}
QString gutter = (isException || result.isError()) ? GUTTER_ERROR : GUTTER_PREVIOUS_COMMAND;
QString resultColor = (isException || result.isError()) ? RESULT_ERROR_STYLE : RESULT_SUCCESS_STYLE;
QString resultStr = "<span style='" + resultColor + "'>" + result.toString().toHtmlEscaped() + "</span>";
bool error = (_scriptEngine->hasUncaughtException() || result.isError());
QString gutter = error ? GUTTER_ERROR : GUTTER_PREVIOUS_COMMAND;
QString resultColor = error ? RESULT_ERROR_STYLE : RESULT_SUCCESS_STYLE;
QString resultStr = "<span style='" + resultColor + "'>" + result.toString().toHtmlEscaped() + "</span>";
appendMessage(gutter, resultStr);
resetCurrentCommandHistory();
}
void JSConsole::handleError(const QString& message) {
@ -233,3 +266,13 @@ void JSConsole::appendMessage(const QString& gutter, const QString& message) {
_ui->logArea->updateGeometry();
scrollToBottom();
}
void JSConsole::clear() {
QLayoutItem* item;
while ((item = _ui->logArea->layout()->takeAt(0)) != NULL) {
delete item->widget();
delete item;
}
_ui->logArea->updateGeometry();
scrollToBottom();
}

View file

@ -14,6 +14,7 @@
#include <QDialog>
#include <QEvent>
#include <QFutureWatcher>
#include <QObject>
#include <QWidget>
@ -31,13 +32,12 @@ public:
JSConsole(QWidget* parent, ScriptEngine* scriptEngine = NULL);
~JSConsole();
void setScriptEngine(ScriptEngine* scriptEngine = NULL);
void clear();
public slots:
void executeCommand(const QString& command);
signals:
void commandExecuting(const QString& command);
void commandFinished(const QString& result);
protected:
void setAndSelectCommand(const QString& command);
virtual bool eventFilter(QObject* sender, QEvent* event);
@ -47,19 +47,23 @@ protected:
protected slots:
void scrollToBottom();
void resizeTextInput();
void handleEvalutationFinished(QScriptValue result, bool isException);
void handlePrint(const QString& message);
void handleError(const QString& message);
void commandFinished();
private:
void appendMessage(const QString& gutter, const QString& message);
void setToNextCommandInHistory();
void setToPreviousCommandInHistory();
void resetCurrentCommandHistory();
QScriptValue executeCommandInWatcher(const QString& command);
QFutureWatcher<QScriptValue> _executeWatcher;
Ui::Console* _ui;
int _currentCommandInHistory;
QList<QString> _commandHistory;
// Keeps track if the script engine is created inside the JSConsole
bool _ownScriptEngine;
QString _rootCommand;
ScriptEngine* _scriptEngine;
};

View file

@ -52,10 +52,16 @@ ScriptEditorWidget::ScriptEditorWidget() :
// We create a new ScriptHighligting QObject and provide it with a parent so this is NOT a memory leak.
new ScriptHighlighting(_scriptEditorWidgetUI->scriptEdit->document());
QTimer::singleShot(0, _scriptEditorWidgetUI->scriptEdit, SLOT(setFocus()));
_console = new JSConsole(this);
_console->setFixedHeight(CONSOLE_HEIGHT);
_scriptEditorWidgetUI->verticalLayout->addWidget(_console);
connect(_scriptEditorWidgetUI->clearButton, &QPushButton::clicked, _console, &JSConsole::clear);
}
ScriptEditorWidget::~ScriptEditorWidget() {
delete _scriptEditorWidgetUI;
delete _console;
}
void ScriptEditorWidget::onScriptModified() {
@ -68,6 +74,7 @@ void ScriptEditorWidget::onScriptModified() {
void ScriptEditorWidget::onScriptFinished(const QString& scriptPath) {
_scriptEngine = NULL;
_console->setScriptEngine(NULL);
if (_isRestarting) {
_isRestarting = false;
setRunning(true);
@ -89,8 +96,6 @@ bool ScriptEditorWidget::setRunning(bool run) {
if (_scriptEngine != NULL) {
disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
disconnect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
disconnect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
}
@ -100,15 +105,15 @@ bool ScriptEditorWidget::setRunning(bool run) {
// Reload script so that an out of date copy is not retrieved from the cache
_scriptEngine = qApp->loadScript(scriptURLString, true, true, false, true);
connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
connect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
connect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
} else {
connect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
qApp->stopScript(_currentScript);
const QString& scriptURLString = QUrl(_currentScript).toString();
qApp->stopScript(scriptURLString);
_scriptEngine = NULL;
}
_console->setScriptEngine(_scriptEngine);
return true;
}
@ -147,8 +152,6 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) {
if (_scriptEngine != NULL) {
disconnect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
disconnect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
disconnect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
}
@ -168,16 +171,14 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) {
static_cast<ScriptEditorWindow*>(this->parent()->parent()->parent())->terminateCurrentTab();
}
}
const QString& scriptURLString = QUrl(_currentScript).toString();
_scriptEngine = qApp->getScriptEngine(scriptURLString);
if (_scriptEngine != NULL) {
connect(_scriptEngine, &ScriptEngine::runningStateChanged, this, &ScriptEditorWidget::runningStateChanged);
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &ScriptEditorWidget::onScriptError);
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &ScriptEditorWidget::onScriptPrint);
connect(_scriptEngine, &ScriptEngine::update, this, &ScriptEditorWidget::onScriptModified);
connect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
}
_console->setScriptEngine(_scriptEngine);
}
bool ScriptEditorWidget::save() {
@ -210,19 +211,11 @@ bool ScriptEditorWidget::questionSave() {
QMessageBox::StandardButton button = QMessageBox::warning(this, tr("Interface"),
tr("The script has been modified.\nDo you want to save your changes?"), QMessageBox::Save | QMessageBox::Discard |
QMessageBox::Cancel, QMessageBox::Save);
return button == QMessageBox::Save ? save() : (button == QMessageBox::Cancel ? false : true);
return button == QMessageBox::Save ? save() : (button == QMessageBox::Discard);
}
return true;
}
void ScriptEditorWidget::onScriptError(const QString& message) {
_scriptEditorWidgetUI->debugText->appendPlainText("ERROR: " + message);
}
void ScriptEditorWidget::onScriptPrint(const QString& message) {
_scriptEditorWidgetUI->debugText->appendPlainText("> " + message);
}
void ScriptEditorWidget::onWindowActivated() {
if (!_isReloading) {
_isReloading = true;
@ -241,10 +234,8 @@ void ScriptEditorWidget::onWindowActivated() {
setRunning(false);
// Script is restarted once current script instance finishes.
}
}
}
_isReloading = false;
}
}

View file

@ -14,6 +14,7 @@
#include <QDockWidget>
#include "JSConsole.h"
#include "ScriptEngine.h"
namespace Ui {
@ -47,12 +48,11 @@ public slots:
void onWindowActivated();
private slots:
void onScriptError(const QString& message);
void onScriptPrint(const QString& message);
void onScriptModified();
void onScriptFinished(const QString& scriptName);
private:
JSConsole* _console;
Ui::ScriptEditorWidget* _scriptEditorWidgetUI;
ScriptEngine* _scriptEngine;
QString _currentScript;

View file

@ -28,7 +28,6 @@
#include <QWidget>
#include "Application.h"
#include "JSConsole.h"
#include "PathUtils.h"
ScriptEditorWindow::ScriptEditorWindow(QWidget* parent) :
@ -59,10 +58,6 @@ ScriptEditorWindow::ScriptEditorWindow(QWidget* parent) :
_ScriptEditorWindowUI->newButton->setIcon(QIcon(QPixmap(PathUtils::resourcesPath() + "icons/new-script.svg")));
_ScriptEditorWindowUI->saveButton->setIcon(QIcon(QPixmap(PathUtils::resourcesPath() + "icons/save-script.svg")));
_ScriptEditorWindowUI->toggleRunButton->setIcon(QIcon(QPixmap(PathUtils::resourcesPath() + "icons/start-script.svg")));
QWidget* console = new JSConsole(this);
console->setFixedHeight(CONSOLE_HEIGHT);
this->layout()->addWidget(console);
}
ScriptEditorWindow::~ScriptEditorWindow() {
@ -77,10 +72,11 @@ void ScriptEditorWindow::setRunningState(bool run) {
}
void ScriptEditorWindow::updateButtons() {
bool isRunning = _ScriptEditorWindowUI->tabWidget->currentIndex() != -1 &&
static_cast<ScriptEditorWidget*>(_ScriptEditorWindowUI->tabWidget->currentWidget())->isRunning();
_ScriptEditorWindowUI->toggleRunButton->setEnabled(_ScriptEditorWindowUI->tabWidget->currentIndex() != -1);
_ScriptEditorWindowUI->toggleRunButton->setIcon(_ScriptEditorWindowUI->tabWidget->currentIndex() != -1 &&
static_cast<ScriptEditorWidget*>(_ScriptEditorWindowUI->tabWidget->currentWidget())->isRunning() ?
QIcon("../resources/icons/stop-script.svg") : QIcon("../resources/icons/start-script.svg"));
_ScriptEditorWindowUI->toggleRunButton->setIcon(QIcon(QPixmap(PathUtils::resourcesPath() + ((isRunning ?
"icons/stop-script.svg" : "icons/start-script.svg")))));
}
void ScriptEditorWindow::loadScriptMenu(const QString& scriptName) {

View file

@ -128,25 +128,6 @@
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="debugText">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">font: 15px &quot;Courier&quot;;</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="plainText">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
@ -158,22 +139,4 @@
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>clearButton</sender>
<signal>clicked()</signal>
<receiver>debugText</receiver>
<slot>clear()</slot>
<hints>
<hint type="sourcelabel">
<x>663</x>
<y>447</y>
</hint>
<hint type="destinationlabel">
<x>350</x>
<y>501</y>
</hint>
</hints>
</connection>
</connections>
</ui>