diff --git a/interface/resources/icons/start-script.svg b/interface/resources/icons/start-script.svg new file mode 100644 index 0000000000..86354a555d --- /dev/null +++ b/interface/resources/icons/start-script.svg @@ -0,0 +1,557 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Maximillian Merlin + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/stop-script.svg b/interface/resources/icons/stop-script.svg new file mode 100644 index 0000000000..31cdcee749 --- /dev/null +++ b/interface/resources/icons/stop-script.svg @@ -0,0 +1,163 @@ + + + + + + + + + + image/svg+xml + + + + + Maximillian Merlin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 438722df17..769791d09f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -797,6 +797,7 @@ void Application::keyPressEvent(QKeyEvent* event) { if (activeWindow() == _window) { bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier); bool isMeta = event->modifiers().testFlag(Qt::ControlModifier); + bool isOption = event->modifiers().testFlag(Qt::AltModifier); switch (event->key()) { break; case Qt::Key_BracketLeft: @@ -839,9 +840,11 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_S: - if (isShifted && isMeta) { + if (isShifted && isMeta && !isOption) { Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings); - } else if (!isShifted && isMeta) { + } else if (isOption && !isShifted && !isMeta) { + Menu::getInstance()->triggerOption(MenuOption::ScriptEditor); + } else if (!isOption && !isShifted && isMeta) { takeSnapshot(); } else { _myAvatar->setDriveKeys(BACK, 1.f); @@ -3299,13 +3302,14 @@ void Application::stopAllScripts() { bumpSettings(); } -void Application::stopScript(const QString &scriptName) -{ - _scriptEnginesHash.value(scriptName)->stop(); - qDebug() << "stopping script..." << scriptName; - _scriptEnginesHash.remove(scriptName); - _runningScriptsWidget->setRunningScripts(getRunningScripts()); - bumpSettings(); +void Application::stopScript(const QString &scriptName) { + if (_scriptEnginesHash.contains(scriptName)) { + _scriptEnginesHash.value(scriptName)->stop(); + qDebug() << "stopping script..." << scriptName; + _scriptEnginesHash.remove(scriptName); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); + bumpSettings(); + } } void Application::reloadAllScripts() { @@ -3366,7 +3370,10 @@ void Application::uploadSkeleton() { uploadFST(false); } -void Application::loadScript(const QString& scriptName) { +ScriptEngine* Application::loadScript(const QString& scriptName, bool focusMainWindow) { + if(_scriptEnginesHash.contains(scriptName) && !_scriptEnginesHash[scriptName]->isFinished()){ + return _scriptEnginesHash[scriptName]; + } // start the script on a new thread... ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), &_controllerScriptingInterface); @@ -3374,7 +3381,7 @@ void Application::loadScript(const QString& scriptName) { if (!scriptEngine->hasScript()) { qDebug() << "Application::loadScript(), script failed to load..."; - return; + return NULL; } _runningScriptsWidget->setRunningScripts(getRunningScripts()); @@ -3422,8 +3429,12 @@ void Application::loadScript(const QString& scriptName) { workerThread->start(); // restore the main window's active state - _window->activateWindow(); + if (focusMainWindow) { + _window->activateWindow(); + } bumpSettings(); + + return scriptEngine; } void Application::loadDialog() { diff --git a/interface/src/Application.h b/interface/src/Application.h index 00b71c4ce7..3254c874b6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -121,7 +121,7 @@ public: ~Application(); void restoreSizeAndPosition(); - void loadScript(const QString& fileNameString); + ScriptEngine* loadScript(const QString& fileNameString, bool focusMainWindow = true); void loadScripts(); void storeSizeAndPosition(); void clearScriptsBeforeRunning(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6f70c5616c..4e6dd2eec2 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -195,7 +195,7 @@ Menu::Menu() : QMenu* toolsMenu = addMenu("Tools"); addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0, this, SLOT(showMetavoxelEditor())); - addActionToQMenuAndActionHash(toolsMenu, MenuOption::ScriptEditor, Qt::CTRL | Qt::SHIFT | Qt::Key_S, this, SLOT(showScriptEditor())); + addActionToQMenuAndActionHash(toolsMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S, this, SLOT(showScriptEditor())); #ifdef HAVE_QXMPP _chatAction = addActionToQMenuAndActionHash(toolsMenu, @@ -1125,7 +1125,7 @@ void Menu::showMetavoxelEditor() { } void Menu::showScriptEditor() { - if(!_ScriptEditor) { + if(!_ScriptEditor || !_ScriptEditor->isVisible()) { _ScriptEditor = new ScriptEditorWindow(); } _ScriptEditor->raise(); diff --git a/interface/src/ScriptHighlighting.cpp b/interface/src/ScriptHighlighting.cpp index 119926742c..b2c5ca2ec6 100644 --- a/interface/src/ScriptHighlighting.cpp +++ b/interface/src/ScriptHighlighting.cpp @@ -10,6 +10,7 @@ // #include "ScriptHighlighting.h" +#include ScriptHighlighting::ScriptHighlighting(QTextDocument* parent) : QSyntaxHighlighter(parent) @@ -19,13 +20,16 @@ ScriptHighlighting::ScriptHighlighting(QTextDocument* parent) : multiLineCommentBegin = QRegExp("/\\*"); multiLineCommentEnd = QRegExp("\\*/"); numberRegex = QRegExp("[0-9]+(\\.[0-9]+){0,1}"); + singleLineComment = QRegExp("//[^\n]*"); + truefalseRegex = QRegExp("\\b(true|false)\\b"); } void ScriptHighlighting::highlightBlock(const QString &text) { this->highlightKeywords(text); - this->formatComments(text); - this->formatQoutedText(text); this->formatNumbers(text); + this->formatTrueFalse(text); + this->formatQoutedText(text); + this->formatComments(text); } void ScriptHighlighting::highlightKeywords(const QString &text) { @@ -50,6 +54,13 @@ void ScriptHighlighting::formatComments(const QString &text) { start = text.indexOf(multiLineCommentBegin, start + length); if (end == -1) setCurrentBlockState(BlockStateInMultiComment); } + + int index = singleLineComment.indexIn(text); + while (index >= 0) { + int length = singleLineComment.matchedLength(); + setFormat(index, length, Qt::lightGray); + index = singleLineComment.indexIn(text, index + length); + } } void ScriptHighlighting::formatQoutedText(const QString &text){ @@ -69,3 +80,14 @@ void ScriptHighlighting::formatNumbers(const QString &text){ index = numberRegex.indexIn(text, index + length); } } + +void ScriptHighlighting::formatTrueFalse(const QString text){ + int index = truefalseRegex.indexIn(text); + while (index >= 0) { + int length = truefalseRegex.matchedLength(); + QFont* font = new QFont(this->document()->defaultFont()); + font->setBold(true); + setFormat(index, length, *font); + index = truefalseRegex.indexIn(text, index + length); + } +} \ No newline at end of file diff --git a/interface/src/ScriptHighlighting.h b/interface/src/ScriptHighlighting.h index b9567cb06a..9cbbf277cc 100644 --- a/interface/src/ScriptHighlighting.h +++ b/interface/src/ScriptHighlighting.h @@ -26,11 +26,12 @@ public: }; protected: - void highlightBlock(const QString &text); - void highlightKeywords(const QString &text); - void formatComments(const QString &text); - void formatQoutedText(const QString &text); - void formatNumbers(const QString &text); + void highlightBlock(const QString& text); + void highlightKeywords(const QString& text); + void formatComments(const QString& text); + void formatQoutedText(const QString& text); + void formatNumbers(const QString& text); + void formatTrueFalse(const QString text); private: QRegExp keywordRegex; @@ -38,6 +39,8 @@ private: QRegExp multiLineCommentBegin; QRegExp multiLineCommentEnd; QRegExp numberRegex; + QRegExp singleLineComment; + QRegExp truefalseRegex; }; #endif // hifi_ScriptHighlighting_h diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index 618e405448..98b6f2fe96 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -30,13 +31,126 @@ ScriptEditorWidget::ScriptEditorWidget() : { ui->setupUi(this); + scriptEngine = NULL; + + connect(ui->scriptEdit->document(), SIGNAL(modificationChanged(bool)), this, SIGNAL(scriptModified())); + connect(ui->scriptEdit->document(), SIGNAL(contentsChanged()), this, SLOT(onScriptModified())); + // remove the title bar (see the Qt docs on setTitleBarWidget) setTitleBarWidget(new QWidget()); QFontMetrics fm(this->ui->scriptEdit->font()); this->ui->scriptEdit->setTabStopWidth(fm.width('0') * 4); ScriptHighlighting* highlighting = new ScriptHighlighting(this->ui->scriptEdit->document()); + QTimer::singleShot(0, this->ui->scriptEdit, SLOT(setFocus())); } ScriptEditorWidget::~ScriptEditorWidget() { delete ui; +} + +void ScriptEditorWidget::onScriptModified() { + if(ui->onTheFlyCheckBox->isChecked() && isRunning()) { + setRunning(false); + setRunning(true); + } +} + +bool ScriptEditorWidget::isModified() { + return ui->scriptEdit->document()->isModified(); +} + +bool ScriptEditorWidget::isRunning() { + return (scriptEngine != NULL) ? scriptEngine->isRunning() : false; +} + +bool ScriptEditorWidget::setRunning(bool run) { + if (run && !save()) { + return false; + } + // Clean-up old connections. + disconnect(this, SLOT(onScriptError(const QString&))); + disconnect(this, SLOT(onScriptPrint(const QString&))); + + if (run) { + scriptEngine = Application::getInstance()->loadScript(this->currentScript, false); + connect(scriptEngine, SIGNAL(runningStateChanged()), this, SIGNAL(runningStateChanged())); + + // Make new connections. + connect(scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(onScriptError(const QString&))); + connect(scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(onScriptPrint(const QString&))); + } else { + Application::getInstance()->stopScript(this->currentScript); + scriptEngine = NULL; + } + return true; +} + +bool ScriptEditorWidget::saveFile(const QString &scriptPath) { + QFile file(scriptPath); + if (!file.open(QFile::WriteOnly | QFile::Text)) { + QMessageBox::warning(this, tr("Interface"), tr("Cannot write script %1:\n%2.").arg(scriptPath).arg(file.errorString())); + return false; + } + + QTextStream out(&file); + out << ui->scriptEdit->toPlainText(); + + setScriptFile(scriptPath); + return true; +} + +void ScriptEditorWidget::loadFile(const QString &scriptPath) { + QFile file(scriptPath); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath).arg(file.errorString())); + return; + } + + QTextStream in(&file); + ui->scriptEdit->setPlainText(in.readAll()); + + setScriptFile(scriptPath); + + disconnect(this, SLOT(onScriptError(const QString&))); + disconnect(this, SLOT(onScriptPrint(const QString&))); + + scriptEngine = Application::getInstance()->getScriptEngine(scriptPath); + if (scriptEngine != NULL) { + connect(scriptEngine, SIGNAL(runningStateChanged()), this, SIGNAL(runningStateChanged())); + connect(scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(onScriptError(const QString&))); + connect(scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(onScriptPrint(const QString&))); + } +} + +bool ScriptEditorWidget::save() { + return currentScript.isEmpty() ? saveAs() : saveFile(currentScript); +} + +bool ScriptEditorWidget::saveAs() { + QString fileName = QFileDialog::getSaveFileName(this, tr("Save script"), QString(), tr("Javascript (*.js)")); + return !fileName.isEmpty() ? saveFile(fileName) : false; +} + +void ScriptEditorWidget::setScriptFile(const QString& scriptPath) { + currentScript = scriptPath; + ui->scriptEdit->document()->setModified(false); + setWindowModified(false); + + emit scriptnameChanged(); +} + +bool ScriptEditorWidget::questionSave() { + if (ui->scriptEdit->document()->isModified()) { + 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 true; +} + +void ScriptEditorWidget::onScriptError(const QString& message) { + ui->debugText->appendPlainText("ERROR: "+ message); +} + +void ScriptEditorWidget::onScriptPrint(const QString& message) { + ui->debugText->appendPlainText("> "+message); } \ No newline at end of file diff --git a/interface/src/ui/ScriptEditorWidget.h b/interface/src/ui/ScriptEditorWidget.h index 931ec105c9..674304acb6 100644 --- a/interface/src/ui/ScriptEditorWidget.h +++ b/interface/src/ui/ScriptEditorWidget.h @@ -27,8 +27,30 @@ public: ScriptEditorWidget(); ~ScriptEditorWidget(); + bool isModified(); + bool isRunning(); + bool setRunning(bool run); + bool saveFile(const QString& scriptPath); + void loadFile(const QString& scriptPath); + void setScriptFile(const QString& scriptPath); + bool save(); + bool saveAs(); + bool questionSave(); + const QString getScriptName() const { return currentScript; }; +signals: + void runningStateChanged(); + void scriptnameChanged(); + void scriptModified(); + +private slots: + void onScriptError(const QString& message); + void onScriptPrint(const QString& message); + void onScriptModified(); + private: Ui::ScriptEditorWidget* ui; + ScriptEngine* scriptEngine; + QString currentScript; }; #endif // hifi_ScriptEditorWidget_h diff --git a/interface/src/ui/ScriptEditorWindow.cpp b/interface/src/ui/ScriptEditorWindow.cpp index 38fa26622a..687b992c5f 100644 --- a/interface/src/ui/ScriptEditorWindow.cpp +++ b/interface/src/ui/ScriptEditorWindow.cpp @@ -13,8 +13,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -31,32 +33,158 @@ ScriptEditorWindow::ScriptEditorWindow() : { ui->setupUi(this); show(); + addScriptEditorWidget("New script"); + loadMenu = new QMenu(); + connect(loadMenu, SIGNAL(aboutToShow()), this, SLOT(loadMenuAboutToShow())); + ui->loadButton->setMenu(loadMenu); + + saveMenu = new QMenu(); + saveMenu->addAction("Save as..", this, SLOT(saveScriptAsClicked()), Qt::CTRL|Qt::SHIFT|Qt::Key_S); + + ui->saveButton->setMenu(saveMenu); + + connect(new QShortcut(QKeySequence("Ctrl+N"), this), SIGNAL(activated()), this, SLOT(newScriptClicked())); + connect(new QShortcut(QKeySequence("Ctrl+S"), this), SIGNAL(activated()), this, SLOT(saveScriptClicked())); + connect(new QShortcut(QKeySequence("Ctrl+O"), this), SIGNAL(activated()), this, SLOT(loadScriptClicked())); + connect(new QShortcut(QKeySequence("F5"), this), SIGNAL(activated()), this, SLOT(toggleRunScriptClicked())); } ScriptEditorWindow::~ScriptEditorWindow() { delete ui; } -void ScriptEditorWindow::loadScriptClicked(){ - +void ScriptEditorWindow::setRunningState(bool run) { + if (ui->tabWidget->currentIndex() != -1) { + ((ScriptEditorWidget*)ui->tabWidget->currentWidget())->setRunning(run); + } + this->updateButtons(); } -void ScriptEditorWindow::newScriptClicked(){ - addScriptEditorWidget(QString("new Script")); +void ScriptEditorWindow::updateButtons() { + ui->toggleRunButton->setEnabled(ui->tabWidget->currentIndex() != -1); + ui->toggleRunButton->setIcon(ui->tabWidget->currentIndex() != -1 && ((ScriptEditorWidget*)ui->tabWidget->currentWidget())->isRunning() ? QIcon("../resources/icons/stop-script.svg"):QIcon("../resources/icons/start-script.svg")); } -void ScriptEditorWindow::toggleRunScriptClicked(){ - +void ScriptEditorWindow::loadScriptMenu(const QString& scriptName) { + addScriptEditorWidget("loading...")->loadFile(scriptName); + updateButtons(); } -void ScriptEditorWindow::saveScriptClicked(){ - +void ScriptEditorWindow::loadScriptClicked() { + QString scriptName = QFileDialog::getOpenFileName(this, tr("Interface"), QString(), tr("Javascript (*.js)")); + if (!scriptName.isEmpty()) { + addScriptEditorWidget("loading...")->loadFile(scriptName); + updateButtons(); + } } -void ScriptEditorWindow::addScriptEditorWidget(QString title){ - ScriptEditorWidget* newScriptEditorWidget = new ScriptEditorWidget();//ScriptEditorWidget(); +void ScriptEditorWindow::loadMenuAboutToShow() { + loadMenu->clear(); + QStringList runningScripts = Application::getInstance()->getRunningScripts(); + if (runningScripts.count() > 0) { + QSignalMapper* signalMapper = new QSignalMapper(this); + foreach (const QString& runningScript, runningScripts) { + QAction* runningScriptAction = new QAction(runningScript, loadMenu); + connect(runningScriptAction, SIGNAL(triggered()), signalMapper, SLOT(map())); + signalMapper->setMapping(runningScriptAction, runningScript); + loadMenu->addAction(runningScriptAction); + } + connect(signalMapper, SIGNAL(mapped(const QString &)), this, SLOT(loadScriptMenu(const QString &))); + } else { + QAction* naAction = new QAction("(no running scripts)",loadMenu); + naAction->setDisabled(true); + loadMenu->addAction(naAction); + } +} + +void ScriptEditorWindow::newScriptClicked() { + addScriptEditorWidget(QString("New script")); +} + +void ScriptEditorWindow::toggleRunScriptClicked() { + this->setRunningState(!(ui->tabWidget->currentIndex() !=-1 && ((ScriptEditorWidget*)ui->tabWidget->currentWidget())->isRunning())); +} + +void ScriptEditorWindow::saveScriptClicked() { + if (ui->tabWidget->currentIndex() != -1) { + ScriptEditorWidget* currentScriptWidget = (ScriptEditorWidget*)ui->tabWidget->currentWidget(); + currentScriptWidget->save(); + } +} + +void ScriptEditorWindow::saveScriptAsClicked() { + if (ui->tabWidget->currentIndex() != -1) { + ScriptEditorWidget* currentScriptWidget = (ScriptEditorWidget*)ui->tabWidget->currentWidget(); + currentScriptWidget->saveAs(); + } +} + +ScriptEditorWidget* ScriptEditorWindow::addScriptEditorWidget(QString title) { + ScriptEditorWidget* newScriptEditorWidget = new ScriptEditorWidget(); + connect(newScriptEditorWidget, SIGNAL(scriptnameChanged()), this, SLOT(updateScriptNameOrStatus())); + connect(newScriptEditorWidget, SIGNAL(scriptModified()), this, SLOT(updateScriptNameOrStatus())); + connect(newScriptEditorWidget, SIGNAL(runningStateChanged()), this, SLOT(updateButtons())); ui->tabWidget->addTab(newScriptEditorWidget, title); ui->tabWidget->setCurrentWidget(newScriptEditorWidget); newScriptEditorWidget->setUpdatesEnabled(true); newScriptEditorWidget->adjustSize(); + return newScriptEditorWidget; +} + +void ScriptEditorWindow::tabSwitched(int tabIndex) { + this->updateButtons(); + if (ui->tabWidget->currentIndex() != -1) { + ScriptEditorWidget* currentScriptWidget = (ScriptEditorWidget*)ui->tabWidget->currentWidget(); + QString modifiedStar = (currentScriptWidget->isModified()?"*":""); + if (currentScriptWidget->getScriptName().length() > 0) { + this->setWindowTitle("Script Editor ["+currentScriptWidget->getScriptName()+modifiedStar+"]"); + } else { + this->setWindowTitle("Script Editor [New script"+modifiedStar+"]"); + } + } else { + this->setWindowTitle("Script Editor"); + } +} + +void ScriptEditorWindow::tabCloseRequested(int tabIndex) { + ScriptEditorWidget* closingScriptWidget = (ScriptEditorWidget*)ui->tabWidget->widget(tabIndex); + if(closingScriptWidget->questionSave()) { + ui->tabWidget->removeTab(tabIndex); + } +} + +void ScriptEditorWindow::closeEvent(QCloseEvent *event) { + bool unsaved_docs_warning = false; + for (int i = 0; i < ui->tabWidget->count(); i++ && !unsaved_docs_warning){ + if(((ScriptEditorWidget*)ui->tabWidget->widget(i))->isModified()){ + unsaved_docs_warning = true; + } + } + + if (!unsaved_docs_warning || QMessageBox::warning(this, tr("Interface"), tr("There are some unsaved scripts, are you sure you want to close the editor? Changes will be lost!"), QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel) == QMessageBox::Discard) { + event->accept(); + } else { + event->ignore(); + } +} + +void ScriptEditorWindow::updateScriptNameOrStatus() { + ScriptEditorWidget* source = (ScriptEditorWidget*)QObject::sender(); + QString modifiedStar = (source->isModified()?"*":""); + if (source->getScriptName().length() > 0) { + for (int i = 0; i < ui->tabWidget->count(); i++){ + if (ui->tabWidget->widget(i) == source) { + ui->tabWidget->setTabText(i,modifiedStar+QFileInfo(source->getScriptName()).fileName()); + ui->tabWidget->setTabToolTip(i, source->getScriptName()); + } + } + } + + if (ui->tabWidget->currentWidget() == source) { + if (source->getScriptName().length() > 0) { + this->setWindowTitle("Script Editor ["+source->getScriptName()+modifiedStar+"]"); + } else { + this->setWindowTitle("Script Editor [New script"+modifiedStar+"]"); + } + } } \ No newline at end of file diff --git a/interface/src/ui/ScriptEditorWindow.h b/interface/src/ui/ScriptEditorWindow.h index 718826cf9d..290a9d6051 100644 --- a/interface/src/ui/ScriptEditorWindow.h +++ b/interface/src/ui/ScriptEditorWindow.h @@ -13,6 +13,7 @@ #define hifi_ScriptEditorWindow_h #include +#include "ScriptEditorWidget.h" namespace Ui { class ScriptEditorWindow; @@ -25,15 +26,29 @@ public: ScriptEditorWindow(); ~ScriptEditorWindow(); +protected: + void closeEvent(QCloseEvent *event); + private: Ui::ScriptEditorWindow* ui; - void addScriptEditorWidget(QString title); + QMenu* loadMenu; + QMenu* saveMenu; + ScriptEditorWidget* addScriptEditorWidget(QString title); + void setRunningState(bool run); + void setScriptName(const QString& scriptName); private slots: + void loadScriptMenu(const QString& scriptName); void loadScriptClicked(); void newScriptClicked(); void toggleRunScriptClicked(); void saveScriptClicked(); + void saveScriptAsClicked(); + void loadMenuAboutToShow(); + void tabSwitched(int tabIndex); + void tabCloseRequested(int tabIndex); + void updateScriptNameOrStatus(); + void updateButtons(); }; #endif // hifi_ScriptEditorWindow_h diff --git a/interface/ui/ScriptEditorWidget.ui b/interface/ui/ScriptEditorWidget.ui index 5878f26c69..88761c91c5 100644 --- a/interface/ui/ScriptEditorWidget.ui +++ b/interface/ui/ScriptEditorWidget.ui @@ -6,8 +6,8 @@ 0 0 - 702 - 543 + 691 + 549 @@ -18,7 +18,7 @@ - 400 + 541 238 @@ -91,6 +91,13 @@ + + + + Run on the fly (Careful: Any valid change made to the code will run immediately) + + + diff --git a/interface/ui/ScriptEditorWindow.ui b/interface/ui/ScriptEditorWindow.ui index a612b2b1c9..9e1b08de3e 100644 --- a/interface/ui/ScriptEditorWindow.ui +++ b/interface/ui/ScriptEditorWindow.ui @@ -9,8 +9,8 @@ 0 0 - 474 - 638 + 706 + 682 @@ -20,7 +20,7 @@ - Script editor [] + Script Editor font-family: Helvetica, Arial, sans-serif; @@ -58,7 +58,7 @@ - New Script + New Script (Ctrl+N) New @@ -66,7 +66,7 @@ ../resources/icons/new-script.svg - ../resources/images/pinned.svg../resources/icons/new-script.svg + ../resources/icons/new-script.svg../resources/icons/new-script.svg @@ -91,7 +91,7 @@ - Load Script + Load Script (Ctrl+O) Load @@ -119,11 +119,17 @@ - - - 0 - 2 - + + + 30 + 0 + + + + + 32 + 0 + Qt::NoFocus @@ -132,7 +138,7 @@ Qt::NoContextMenu - Save Script + Save Script (Ctrl+S) Save @@ -150,19 +156,22 @@ 316 + + QToolButton::MenuButtonPopup + - Toggle Run Script + Toggle Run Script (F5) Run/Stop - ../resources/images/plus.svg../resources/images/plus.svg + ../resources/icons/start-script.svg../resources/icons/start-script.svg @@ -286,5 +295,37 @@ + + tabWidget + currentChanged(int) + ScriptEditorWindow + tabSwitched(int) + + + 352 + 360 + + + 352 + 340 + + + + + tabWidget + tabCloseRequested(int) + ScriptEditorWindow + tabCloseRequested(int) + + + 352 + 360 + + + 352 + 340 + + + diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 684c55fbb0..eeb1cebe09 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -43,6 +43,11 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng return soundScriptValue; } +static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ + qDebug() << "script:print()<<" << context->argument(0).toString(); + engine->evaluate("Script.print('"+context->argument(0).toString()+"')"); + return QScriptValue(); +} ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, AbstractControllerScriptingInterface* controllerScriptingInterface) : @@ -115,6 +120,7 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, _scriptContents = in.readAll(); } else { qDebug() << "ERROR Loading file:" << fileName; + emit errorMessage("ERROR Loading file:" + fileName); } } else { QNetworkAccessManager* networkManager = new QNetworkAccessManager(this); @@ -200,6 +206,9 @@ void ScriptEngine::init() { qScriptRegisterSequenceMetaType >(&_engine); qScriptRegisterSequenceMetaType >(&_engine); + QScriptValue printConstructorValue = _engine.newFunction(debugPrint); + _engine.globalObject().setProperty("print", printConstructorValue); + QScriptValue soundConstructorValue = _engine.newFunction(soundConstructor); QScriptValue soundMetaObject = _engine.newQMetaObject(&Sound::staticMetaObject, soundConstructorValue); _engine.globalObject().setProperty("Sound", soundMetaObject); @@ -246,6 +255,7 @@ void ScriptEngine::evaluate() { if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); qDebug() << "Uncaught exception at line" << line << ":" << result.toString(); + emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + result.toString()); } } @@ -266,11 +276,14 @@ void ScriptEngine::run() { init(); } _isRunning = true; + emit runningStateChanged(); QScriptValue result = _engine.evaluate(_scriptContents); if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); + qDebug() << "Uncaught exception at line" << line << ":" << result.toString(); + emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + result.toString()); } timeval startTime; @@ -401,6 +414,7 @@ void ScriptEngine::run() { if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); qDebug() << "Uncaught exception at line" << line << ":" << _engine.uncaughtException().toString(); + emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + _engine.uncaughtException().toString()); } } emit scriptEnding(); @@ -436,10 +450,12 @@ void ScriptEngine::run() { emit finished(_fileNameString); _isRunning = false; + emit runningStateChanged(); } void ScriptEngine::stop() { _isFinished = true; + emit runningStateChanged(); } void ScriptEngine::timerFired() { @@ -510,6 +526,10 @@ QUrl ScriptEngine::resolveInclude(const QString& include) const { return url; } +void ScriptEngine::print(const QString& message) { + emit printedMessage(message); +} + void ScriptEngine::include(const QString& includeFile) { QUrl url = resolveInclude(includeFile); QString includeContents; @@ -523,6 +543,7 @@ void ScriptEngine::include(const QString& includeFile) { includeContents = in.readAll(); } else { qDebug() << "ERROR Loading file:" << fileName; + emit errorMessage("ERROR Loading file:" + fileName); } } else { QNetworkAccessManager* networkManager = new QNetworkAccessManager(this); @@ -538,5 +559,6 @@ void ScriptEngine::include(const QString& includeFile) { if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); qDebug() << "Uncaught exception at (" << includeFile << ") line" << line << ":" << result.toString(); + emit errorMessage("Uncaught exception at (" + includeFile + ") line" + QString::number(line) + ":" + result.toString()); } -} +} \ No newline at end of file diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 941c6bbe27..9ea99276d3 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -80,6 +80,9 @@ public: bool hasScript() const { return !_scriptContents.isEmpty(); } + bool isFinished() const { return _isFinished; } + bool isRunning() const { return _isRunning; } + public slots: void stop(); @@ -88,12 +91,16 @@ public slots: void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } void include(const QString& includeFile); + void print(const QString& message); signals: void update(float deltaTime); void scriptEnding(); void finished(const QString& fileNameString); void cleanupMenuItem(const QString& menuItemString); + void printedMessage(const QString& message); + void errorMessage(const QString& message); + void runningStateChanged(); protected: QString _scriptContents;