From 524a41468cae6a9ddb6039984352be6e1f1a47cf Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 16 Dec 2013 09:16:51 -0800 Subject: [PATCH] multiple scripts running at same time, menu interface for scripts --- interface/src/Application.cpp | 33 +++++++---------- interface/src/Application.h | 2 -- interface/src/Menu.cpp | 8 +++++ interface/src/Menu.h | 21 +++++++---- libraries/particles/src/Particle.cpp | 2 +- libraries/scriptengine/src/ScriptEngine.cpp | 37 ++++++++++++++++---- libraries/scriptengine/src/ScriptEngine.h | 14 ++++++-- libraries/shared/src/AbstractMenuInterface.h | 29 +++++++++++++++ 8 files changed, 106 insertions(+), 40 deletions(-) create mode 100644 libraries/shared/src/AbstractMenuInterface.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0804b3522c..dc57730aa0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -139,8 +139,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _recentMaxPackets(0), _resetRecentMaxPacketsSoon(true), _swatch(NULL), - _pasteMode(false), - _scriptEngine(NULL) + _pasteMode(false) { _applicationStartupTime = startup_time; _window->setWindowTitle("Interface"); @@ -259,7 +258,7 @@ Application::~Application() { _sharedVoxelSystem.changeTree(new VoxelTree); VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown - delete Menu::getInstance(); + Menu::getInstance()->deleteLater(); delete _settings; delete _followMode; @@ -4388,12 +4387,6 @@ void Application::packetSentNotification(ssize_t length) { void Application::loadScript() { // shut down and stop any existing script - if (_scriptEngine) { - _scriptEngine->stop(); - _scriptEngine = NULL; - } - - QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); QString suggestedName = desktopLocation.append("/script.js"); @@ -4425,32 +4418,30 @@ void Application::loadScript() { delete[] entireFile; // start the script on a new thread... - _scriptEngine = new ScriptEngine(script); + bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself + ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance()); // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so // we can use the same ones from the application. - _scriptEngine->getVoxelScriptingInterface()->setPacketSender(&_voxelEditSender); - _scriptEngine->getParticleScriptingInterface()->setPacketSender(&_particleEditSender); + scriptEngine->getVoxelScriptingInterface()->setPacketSender(&_voxelEditSender); + scriptEngine->getParticleScriptingInterface()->setPacketSender(&_particleEditSender); - //_scriptEngine->getVoxelScriptingInterface()->setJurisdictionListener(); - //_scriptEngine->getParticleScriptingInterface()->setJurisdictionListener(); - QThread* workerThread = new QThread(this); // when the worker thread is started, call our engine's run.. - connect(workerThread, SIGNAL(started()), _scriptEngine, SLOT(run())); + connect(workerThread, SIGNAL(started()), scriptEngine, SLOT(run())); // when the engine emits finished, call our threads quit - connect(_scriptEngine, SIGNAL(finished()), workerThread, SLOT(quit())); + connect(scriptEngine, SIGNAL(finished()), workerThread, SLOT(quit())); - // when the thread is terminated, add both _scriptEngine and thread to the deleteLater queue - connect(workerThread, SIGNAL(terminated()), _scriptEngine, SLOT(deleteLater())); + // when the thread is terminated, add both scriptEngine and thread to the deleteLater queue + connect(workerThread, SIGNAL(terminated()), scriptEngine, SLOT(deleteLater())); connect(workerThread, SIGNAL(terminated()), workerThread, SLOT(deleteLater())); // when the application is about to quit, stop our script engine so it unwinds properly - connect(this, SIGNAL(aboutToQuit()), _scriptEngine, SLOT(stop())); + connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop())); - _scriptEngine->moveToThread(workerThread); + scriptEngine->moveToThread(workerThread); // Starts an event loop, and emits workerThread->started() workerThread->start(); diff --git a/interface/src/Application.h b/interface/src/Application.h index 8b9127032f..df1e4bdeae 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -497,8 +497,6 @@ private: std::vector _voxelFades; std::vector _avatarFades; - - ScriptEngine* _scriptEngine; }; #endif /* defined(__interface__Application__) */ diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 44c04b685d..37dbbde6a3 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -92,6 +92,7 @@ Menu::Menu() : addDisabledActionAndSeparator(fileMenu, "Scripts"); addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadScript())); + _activeScriptsMenu = fileMenu->addMenu("Running Scripts"); addDisabledActionAndSeparator(fileMenu, "Voxels"); addActionToQMenuAndActionHash(fileMenu, MenuOption::ExportVoxels, Qt::CTRL | Qt::Key_E, appInstance, SLOT(exportVoxels())); @@ -709,6 +710,13 @@ QAction* Menu::addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, return action; } +void Menu::removeAction(QMenu* menu, const QString& actionName) { +qDebug() << "removeAction() menu=" << menu << " actionName=" << actionName << "\n"; + + menu->removeAction(_actionHash.value(actionName)); +} + + bool Menu::isOptionChecked(const QString& menuOption) { return _actionHash.value(menuOption)->isChecked(); } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index a1dce998bd..38a1a5e06e 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -13,6 +13,8 @@ #include #include +#include + enum FrustumDrawMode { FRUSTUM_DRAW_MODE_ALL, FRUSTUM_DRAW_MODE_VECTORS, @@ -37,7 +39,7 @@ class BandwidthDialog; class VoxelStatsDialog; class LodToolsDialog; -class Menu : public QMenuBar { +class Menu : public QMenuBar, public AbstractMenuInterface { Q_OBJECT public: static Menu* getInstance(); @@ -71,6 +73,15 @@ public: // User Tweakable PPS from Voxel Server int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPacketsPerSecond; } + virtual QMenu* getActiveScriptsMenu() { return _activeScriptsMenu;} + virtual QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu, + const QString actionName, + const QKeySequence& shortcut = 0, + const QObject* receiver = NULL, + const char* member = NULL, + QAction::MenuRole role = QAction::NoRole); + virtual void removeAction(QMenu* menu, const QString& actionName); + public slots: void bandwidthDetails(); void voxelStatsDetails(); @@ -110,12 +121,6 @@ private: /// helper method to have separators with labels that are also compatible with OS X void addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& actionName); - QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu, - const QString actionName, - const QKeySequence& shortcut = 0, - const QObject* receiver = NULL, - const char* member = NULL, - QAction::MenuRole role = QAction::NoRole); QAction* addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, const QString actionName, @@ -141,6 +146,8 @@ private: int _boundaryLevelAdjust; QAction* _useVoxelShader; int _maxVoxelPacketsPerSecond; + + QMenu* _activeScriptsMenu; }; namespace MenuOption { diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index f66a05608a..d23815da3a 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -416,7 +416,7 @@ void Particle::update() { void Particle::runScript() { if (!_updateScript.isEmpty()) { - qDebug() << "Script: " << _updateScript << "\n"; + //qDebug() << "Script: " << _updateScript << "\n"; QScriptEngine engine; diff --git a/libraries/scriptengine/src/ScriptEngine.cpp b/libraries/scriptengine/src/ScriptEngine.cpp index 0c486cb30f..24c72aaf65 100644 --- a/libraries/scriptengine/src/ScriptEngine.cpp +++ b/libraries/scriptengine/src/ScriptEngine.cpp @@ -21,12 +21,40 @@ #include "ScriptEngine.h" -ScriptEngine::ScriptEngine(QString scriptContents) { +int ScriptEngine::_scriptNumber = 1; + +ScriptEngine::ScriptEngine(QString scriptContents, bool wantMenuItems, + const char* scriptMenuName, AbstractMenuInterface* menu) { _scriptContents = scriptContents; _isFinished = false; + _wantMenuItems = wantMenuItems; + if (scriptMenuName) { + _scriptMenuName = "Stop "; + _scriptMenuName.append(scriptMenuName); + } else { + _scriptMenuName = "Stop Script "; + _scriptNumber++; + _scriptMenuName.append(_scriptNumber); + } + _menu = menu; +} + +void ScriptEngine::setupMenuItems() { + if (_menu && _wantMenuItems) { + _menu->addActionToQMenuAndActionHash(_menu->getActiveScriptsMenu(), _scriptMenuName, 0, this, SLOT(stop())); + } +} + +void ScriptEngine::cleanMenuItems() { + if (_menu && _wantMenuItems) { + _menu->removeAction(_menu->getActiveScriptsMenu(), _scriptMenuName); + } } void ScriptEngine::run() { + + setupMenuItems(); + QScriptEngine engine; _voxelScriptingInterface.init(); @@ -68,12 +96,7 @@ void ScriptEngine::run() { int thisFrame = 0; - qDebug() << "before while... thisFrame:" << thisFrame << "\n"; - while (!_isFinished) { - - qDebug() << "while... thisFrame:" << thisFrame << "\n"; - int usecToSleep = usecTimestamp(&startTime) + (thisFrame++ * VISUAL_DATA_CALLBACK_USECS) - usecTimestampNow(); if (usecToSleep > 0) { usleep(usecToSleep); @@ -105,7 +128,6 @@ void ScriptEngine::run() { } if (willSendVisualDataCallBack) { - qDebug() << "willSendVisualDataCallback thisFrame:" << thisFrame << "\n"; emit willSendVisualDataCallback(); } @@ -115,5 +137,6 @@ void ScriptEngine::run() { qDebug() << "Uncaught exception at line" << line << ":" << engine.uncaughtException().toString() << "\n"; } } + cleanMenuItems(); emit finished(); } diff --git a/libraries/scriptengine/src/ScriptEngine.h b/libraries/scriptengine/src/ScriptEngine.h index 9be9a5e0c0..9047399c79 100644 --- a/libraries/scriptengine/src/ScriptEngine.h +++ b/libraries/scriptengine/src/ScriptEngine.h @@ -15,13 +15,15 @@ #include #include -#include +#include #include +#include class ScriptEngine : public QObject { Q_OBJECT public: - ScriptEngine(QString scriptContents); + ScriptEngine(QString scriptContents, bool wantMenuItems = false, + const char* scriptMenuName = NULL, AbstractMenuInterface* menu = NULL); /// Access the VoxelScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener VoxelScriptingInterface* getVoxelScriptingInterface() { return &_voxelScriptingInterface; } @@ -42,9 +44,17 @@ signals: protected: QString _scriptContents; bool _isFinished; + + void setupMenuItems(); + void cleanMenuItems(); + private: VoxelScriptingInterface _voxelScriptingInterface; ParticleScriptingInterface _particleScriptingInterface; + bool _wantMenuItems; + QString _scriptMenuName; + AbstractMenuInterface* _menu; + static int _scriptNumber; }; #endif /* defined(__hifi__ScriptEngine__) */ diff --git a/libraries/shared/src/AbstractMenuInterface.h b/libraries/shared/src/AbstractMenuInterface.h new file mode 100644 index 0000000000..6af0ea2d00 --- /dev/null +++ b/libraries/shared/src/AbstractMenuInterface.h @@ -0,0 +1,29 @@ +// +// AbstractMenuInterface.h +// hifi +// +// Created by Brad Hefta-Gaub on 12/16/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// + +#ifndef __hifi__AbstractMenuInterface__ +#define __hifi__AbstractMenuInterface__ + +#include +//#include +//#include + +class AbstractMenuInterface { +public: + virtual QMenu* getActiveScriptsMenu() = 0; + virtual QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu, + const QString actionName, + const QKeySequence& shortcut = 0, + const QObject* receiver = NULL, + const char* member = NULL, + QAction::MenuRole role = QAction::NoRole) = 0; + virtual void removeAction(QMenu* menu, const QString& actionName) = 0; +}; + +#endif /* defined(__hifi__AbstractMenuInterface__) */ \ No newline at end of file