From d85d4fea5decbf22008a58a8a79306f91a13a455 Mon Sep 17 00:00:00 2001 From: Mohammed Nafees Date: Wed, 2 Apr 2014 12:46:57 +0530 Subject: [PATCH] Added the new Running Scripts widget --- interface/interface_en.ts | 67 +++- interface/resources/images/kill-script.svg | 7 + interface/resources/images/reload.svg | 24 ++ interface/resources/images/stop.svg | 7 + interface/resources/resources.qrc | 3 + interface/src/Application.cpp | 320 ++++++++++--------- interface/src/Application.h | 39 ++- interface/src/Menu.cpp | 121 +++---- interface/src/Menu.h | 10 +- interface/src/ui/RunningScriptsWidget.cpp | 203 ++++++++++++ interface/src/ui/RunningScriptsWidget.h | 46 +++ interface/ui/runningScriptsWidget.ui | 248 ++++++++++++++ libraries/script-engine/src/ScriptEngine.cpp | 131 +++----- libraries/script-engine/src/ScriptEngine.h | 33 +- 14 files changed, 920 insertions(+), 339 deletions(-) create mode 100644 interface/resources/images/kill-script.svg create mode 100644 interface/resources/images/reload.svg create mode 100644 interface/resources/images/stop.svg create mode 100644 interface/src/ui/RunningScriptsWidget.cpp create mode 100644 interface/src/ui/RunningScriptsWidget.h create mode 100644 interface/ui/runningScriptsWidget.ui diff --git a/interface/interface_en.ts b/interface/interface_en.ts index 7c5d1ecbcf..cbe0ac1f25 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -4,22 +4,22 @@ Application - + Export Voxels - + Sparse Voxel Octree Files (*.svo) - + Open Script - + JavaScript Files (*.js) @@ -113,18 +113,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file @@ -158,4 +158,55 @@ + + RunningScriptsWidget + + + + Form + + + + + + <html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html> + + + + + + <html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html> + + + + + + Reload All + + + + + + Stop All + + + + + + <html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html> + + + + + + (click a script or use the 1-9 keys to load and run it) + + + + + + There are no scripts currently running. + + + diff --git a/interface/resources/images/kill-script.svg b/interface/resources/images/kill-script.svg new file mode 100644 index 0000000000..d98fc4555a --- /dev/null +++ b/interface/resources/images/kill-script.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/interface/resources/images/reload.svg b/interface/resources/images/reload.svg new file mode 100644 index 0000000000..a596f03301 --- /dev/null +++ b/interface/resources/images/reload.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/interface/resources/images/stop.svg b/interface/resources/images/stop.svg new file mode 100644 index 0000000000..ea22bb592a --- /dev/null +++ b/interface/resources/images/stop.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/interface/resources/resources.qrc b/interface/resources/resources.qrc index 372fa8b1d4..35c0e40270 100644 --- a/interface/resources/resources.qrc +++ b/interface/resources/resources.qrc @@ -1,5 +1,8 @@ images/close.svg + images/kill-script.svg + images/reload.svg + images/stop.svg diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d91e686844..386e81aa0b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -49,7 +49,7 @@ #include #include #include -#include +#include #include #include @@ -118,7 +118,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt if (message.size() > 0) { QString dateString = QDateTime::currentDateTime().toTimeSpec(Qt::LocalTime).toString(Qt::ISODate); QString formattedMessage = QString("[%1] %2\n").arg(dateString).arg(message); - + fprintf(stdout, "%s", qPrintable(formattedMessage)); Application::getInstance()->getLogger()->addMessage(qPrintable(formattedMessage)); } @@ -174,23 +174,23 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : { // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); - + // set the associated application properties applicationInfo.beginGroup("INFO"); - + qDebug() << "[VERSION] Build sequence: " << qPrintable(applicationVersion()); - + setApplicationName(applicationInfo.value("name").toString()); setApplicationVersion(BUILD_VERSION); setOrganizationName(applicationInfo.value("organizationName").toString()); setOrganizationDomain(applicationInfo.value("organizationDomain").toString()); - + QSettings::setDefaultFormat(QSettings::IniFormat); - + _myAvatar = _avatarManager.getMyAvatar(); _applicationStartupTime = startup_time; - + QFontDatabase::addApplicationFont(Application::resourcesPath() + "styles/Inconsolata.otf"); _window->setWindowTitle("Interface"); @@ -205,19 +205,19 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : if (portStr) { listenPort = atoi(portStr); } - + // start the nodeThread so its event loop is running _nodeThread->start(); - + // make sure the node thread is given highest priority _nodeThread->setPriority(QThread::TimeCriticalPriority); - + // put the NodeList and datagram processing on the node thread NodeList* nodeList = NodeList::createInstance(NodeType::Agent, listenPort); - + nodeList->moveToThread(_nodeThread); _datagramProcessor.moveToThread(_nodeThread); - + // connect the DataProcessor processDatagrams slot to the QUDPSocket readyRead() signal connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), &_datagramProcessor, SLOT(processDatagrams())); @@ -239,20 +239,20 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer))); connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle); connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset); - + // connect to appropriate slots on AccountManager AccountManager& accountManager = AccountManager::getInstance(); connect(&accountManager, &AccountManager::authRequired, Menu::getInstance(), &Menu::loginForCurrentDomain); connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle); - + // set the account manager's root URL and trigger a login request if we don't have the access token accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL); - + // once the event loop has started, check and signal for an access token QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); _settings = new QSettings(this); - + // Check to see if the user passed in a command line option for loading a local // Voxel File. _voxelsFilename = getCmdOption(argc, constArgv, "-i"); @@ -266,7 +266,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::MetavoxelServer); - + // connect to the packet sent signal of the _voxelEditSender and the _particleEditSender connect(&_voxelEditSender, &VoxelEditPacketSender::packetSent, this, &Application::packetSent); connect(&_particleEditSender, &ParticleEditPacketSender::packetSent, this, &Application::packetSent); @@ -276,7 +276,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeTimer->moveToThread(_nodeThread); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); - + // send the identity packet for our avatar each second to our avatar mixer QTimer* identityPacketTimer = new QTimer(); connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket); @@ -324,14 +324,18 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : // Set the sixense filtering _sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense)); - + checkVersion(); - + _overlays.init(_glWidget); // do this before scripts load - + LocalVoxelsList::getInstance()->addPersistantTree(DOMAIN_TREE_NAME, _voxels.getTree()); LocalVoxelsList::getInstance()->addPersistantTree(CLIPBOARD_TREE_NAME, &_clipboard); + _window->addDockWidget(Qt::NoDockWidgetArea, _runningScriptsWidget = new RunningScriptsWidget()); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); + connect(_runningScriptsWidget, &RunningScriptsWidget::stopScriptName, this, &Application::stopScript); + // check first run... QVariant firstRunValue = _settings->value("firstRun",QVariant(true)); if (firstRunValue.isValid() && firstRunValue.toBool()) { @@ -339,7 +343,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : // clear the scripts, and set out script to our default scripts clearScriptsBeforeRunning(); loadScript("http://public.highfidelity.io/scripts/defaultScripts.js"); - + _settings->setValue("firstRun",QVariant(false)); } else { // do this as late as possible so that all required subsystems are inialized @@ -350,31 +354,31 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : Application::~Application() { qInstallMessageHandler(NULL); - + // make sure we don't call the idle timer any more delete idleTimer; - + Menu::getInstance()->saveSettings(); _rearMirrorTools->saveSettings(_settings); - + _sharedVoxelSystem.changeTree(new VoxelTree); if (_voxelImporter) { _voxelImporter->saveSettings(_settings); delete _voxelImporter; } _settings->sync(); - + // let the avatar mixer know we're out MyAvatar::sendKillAvatar(); - + // ask the datagram processing thread to quit and wait until it is done _nodeThread->quit(); _nodeThread->wait(); - + // ask the audio thread to quit and wait until it is done _audio.thread()->quit(); _audio.thread()->wait(); - + _voxelProcessor.terminate(); _voxelHideShowThread.terminate(); _voxelEditSender.terminate(); @@ -387,9 +391,9 @@ Application::~Application() { Menu::getInstance()->deleteLater(); _myAvatar = NULL; - + delete _glWidget; - + AccountManager::getInstance().destroy(); } @@ -581,7 +585,7 @@ void Application::paintGL() { if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { renderRearViewMirror(_mirrorViewRect); - + } else if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { _rearMirrorTools->render(true); } @@ -654,10 +658,10 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod if (type == NodeType::VoxelServer && !Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { continue; } - + // Perform the broadcast for one type int nReceivingNodes = NodeList::getInstance()->broadcastToNodes(packet, NodeSet() << type); - + // Feed number of bytes to corresponding channel of the bandwidth meter, if any (done otherwise) BandwidthMeter::ChannelIndex channel; switch (type) { @@ -676,7 +680,7 @@ void Application::controlledBroadcastToNodes(const QByteArray& packet, const Nod } bool Application::event(QEvent* event) { - + // handle custom URL if (event->type() == QEvent::FileOpen) { QFileOpenEvent* fileEvent = static_cast(event); @@ -687,11 +691,11 @@ bool Application::event(QEvent* event) { if (urlParts.count() > 1) { // if url has 2 or more parts, the first one is domain name Menu::getInstance()->goToDomain(urlParts[0]); - + // location coordinates Menu::getInstance()->goToDestination(urlParts[1]); if (urlParts.count() > 2) { - + // location orientation Menu::getInstance()->goToOrientation(urlParts[2]); } @@ -701,7 +705,7 @@ bool Application::event(QEvent* event) { Menu::getInstance()->goToDestination(urlParts[0]); } } - + return false; } return QApplication::event(event); @@ -712,7 +716,7 @@ void Application::keyPressEvent(QKeyEvent* event) { _keysPressed.insert(event->key()); _controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts - + // if one of our scripts have asked to capture this event, then stop processing it if (_controllerScriptingInterface.isKeyCaptured(event)) { return; @@ -1071,7 +1075,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { checkBandwidthMeterClick(); if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { checkStatsClick(); - } + } } } } @@ -1120,7 +1124,7 @@ void Application::touchBeginEvent(QTouchEvent* event) { if (_controllerScriptingInterface.isTouchCaptured()) { return; } - + // put any application specific touch behavior below here.. _lastTouchAvgX = _touchAvgX; _lastTouchAvgY = _touchAvgY; @@ -1163,13 +1167,13 @@ void Application::dropEvent(QDropEvent *event) { break; } } - + SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath); if (snapshotData) { if (!snapshotData->getDomain().isEmpty()) { Menu::getInstance()->goToDomain(snapshotData->getDomain()); } - + _myAvatar->setPosition(snapshotData->getLocation()); _myAvatar->setOrientation(snapshotData->getOrientation()); } else { @@ -1197,19 +1201,19 @@ void Application::timer() { } _fps = (float)_frameCount / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); - + _packetsPerSecond = (float) _datagramProcessor.getPacketCount() / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); _bytesPerSecond = (float) _datagramProcessor.getByteCount() / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); _frameCount = 0; - + _datagramProcessor.resetCounters(); gettimeofday(&_timerStart, NULL); // ask the node list to check in with the domain server NodeList::getInstance()->sendDomainServerCheckIn(); - - + + } void Application::idle() { @@ -1246,11 +1250,11 @@ void Application::idle() { _idleLoopMeasuredJitter = _idleLoopStdev.getStDev(); _idleLoopStdev.reset(); } - + if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) { _buckyBalls.simulate(timeSinceLastUpdate / 1000.f, Application::getInstance()->getAvatar()->getHandData()); } - + // After finishing all of the above work, restart the idle timer, allowing 2ms to process events. idleTimer->start(2); } @@ -1382,7 +1386,7 @@ void Application::exportVoxels(const VoxelDetail& sourceVoxel) { tr("Sparse Voxel Octree Files (*.svo)")); QByteArray fileNameAscii = fileNameString.toLocal8Bit(); const char* fileName = fileNameAscii.data(); - + VoxelTreeElement* selectedNode = _voxels.getTree()->getVoxelAt(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s); if (selectedNode) { VoxelTree exportTree; @@ -1396,12 +1400,12 @@ void Application::exportVoxels(const VoxelDetail& sourceVoxel) { void Application::importVoxels() { _importSucceded = false; - + if (!_voxelImporter) { _voxelImporter = new VoxelImporter(_window); _voxelImporter->loadSettings(_settings); } - + if (!_voxelImporter->exec()) { qDebug() << "[DEBUG] Import succeeded." << endl; _importSucceded = true; @@ -1415,7 +1419,7 @@ void Application::importVoxels() { // restore the main window's active state _window->activateWindow(); - + emit importDone(); } @@ -1471,7 +1475,7 @@ void Application::pasteVoxels(const VoxelDetail& sourceVoxel) { } pasteVoxelsToOctalCode(octalCodeDestination); - + if (calculatedOctCode) { delete[] calculatedOctCode; } @@ -1508,9 +1512,9 @@ void Application::init() { // Cleanup of the original shared tree _sharedVoxelSystem.init(); - + _voxelImporter = new VoxelImporter(_window); - + _environment.init(); _glowEffect.init(); @@ -1552,11 +1556,11 @@ void Application::init() { _audio.setJitterBufferSamples(Menu::getInstance()->getAudioJitterBufferSamples()); } qDebug("Loaded settings"); - + // initialize Visage and Faceshift after loading the menu settings _faceshift.init(); _visage.init(); - + // fire off an immediate domain-server check in now that settings are loaded NodeList::getInstance()->sendDomainServerCheckIn(); @@ -1575,20 +1579,20 @@ void Application::init() { _particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager); // connect the _particleCollisionSystem to our script engine's ParticleScriptingInterface - connect(&_particleCollisionSystem, + connect(&_particleCollisionSystem, SIGNAL(particleCollisionWithVoxel(const ParticleID&, const VoxelDetail&, const glm::vec3&)), - ScriptEngine::getParticlesScriptingInterface(), + ScriptEngine::getParticlesScriptingInterface(), SLOT(forwardParticleCollisionWithVoxel(const ParticleID&, const VoxelDetail&, const glm::vec3&))); - connect(&_particleCollisionSystem, + connect(&_particleCollisionSystem, SIGNAL(particleCollisionWithParticle(const ParticleID&, const ParticleID&, const glm::vec3&)), - ScriptEngine::getParticlesScriptingInterface(), + ScriptEngine::getParticlesScriptingInterface(), SLOT(forwardParticleCollisionWithParticle(const ParticleID&, const ParticleID&, const glm::vec3&))); - + _audio.init(_glWidget); _rearMirrorTools = new RearMirrorTools(_glWidget, _mirrorViewRect, _settings); - + connect(_rearMirrorTools, SIGNAL(closeView()), SLOT(closeMirrorView())); connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView())); connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView())); @@ -1711,7 +1715,7 @@ void Application::updateMyAvatarLookAtPosition() { float distance = TREE_SCALE; if (_myAvatar->getLookAtTargetAvatar() && _myAvatar != _myAvatar->getLookAtTargetAvatar()) { distance = glm::distance(_mouseRayOrigin, - static_cast(_myAvatar->getLookAtTargetAvatar())->getHead()->calculateAverageEyePosition()); + static_cast(_myAvatar->getLookAtTargetAvatar())->getHead()->calculateAverageEyePosition()); } const float FIXED_MIN_EYE_DISTANCE = 0.3f; float minEyeDistance = FIXED_MIN_EYE_DISTANCE + (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON ? 0.0f : @@ -1724,7 +1728,7 @@ void Application::updateMyAvatarLookAtPosition() { eyePitch = _faceshift.getEstimatedEyePitch(); eyeYaw = _faceshift.getEstimatedEyeYaw(); trackerActive = true; - + } else if (_visage.isActive()) { eyePitch = _visage.getEstimatedEyePitch(); eyeYaw = _visage.getEstimatedEyeYaw(); @@ -1907,9 +1911,9 @@ void Application::update(float deltaTime) { _particles.update(); // update the particles... _particleCollisionSystem.update(); // collide the particles... - + _overlays.update(deltaTime); - + // let external parties know we're updating emit simulating(deltaTime); } @@ -1933,7 +1937,7 @@ void Application::updateMyAvatar(float deltaTime) { // actually need to calculate the view frustum planes to send these details // to the server. loadViewFrustum(_myCamera, _viewFrustum); - + // Update my voxel servers with my current voxel query... quint64 now = usecTimestampNow(); quint64 sinceLastQuery = now - _lastQueriedTime; @@ -2209,7 +2213,7 @@ void Application::updateShadowMap() { } center = inverseRotation * center; glm::vec3 minima(center.x - radius, center.y - radius, center.z - radius); - glm::vec3 maxima(center.x + radius, center.y + radius, center.z + radius); + glm::vec3 maxima(center.x + radius, center.y + radius, center.z + radius); // stretch out our extents in z so that we get all of the avatars minima.z -= _viewFrustum.getFarClip() * 0.5f; @@ -2230,7 +2234,7 @@ void Application::updateShadowMap() { _shadowViewFrustum.setEyeOffsetPosition(glm::vec3()); _shadowViewFrustum.setEyeOffsetOrientation(glm::quat()); _shadowViewFrustum.calculate(); - + glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); @@ -2285,19 +2289,19 @@ void Application::setupWorldLight() { QImage Application::renderAvatarBillboard() { _textureCache.getPrimaryFramebufferObject()->bind(); - + glDisable(GL_BLEND); const int BILLBOARD_SIZE = 64; renderRearViewMirror(QRect(0, _glWidget->height() - BILLBOARD_SIZE, BILLBOARD_SIZE, BILLBOARD_SIZE), true); - + QImage image(BILLBOARD_SIZE, BILLBOARD_SIZE, QImage::Format_ARGB32); glReadPixels(0, 0, BILLBOARD_SIZE, BILLBOARD_SIZE, GL_BGRA, GL_UNSIGNED_BYTE, image.bits()); - + glEnable(GL_BLEND); - + _textureCache.getPrimaryFramebufferObject()->release(); - + return image; } @@ -2398,7 +2402,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { "Application::displaySide() ... metavoxels..."); _metavoxels.render(); } - + if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... bucky balls..."); @@ -2411,7 +2415,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { "Application::displaySide() ... particles..."); _particles.render(); } - + // render the ambient occlusion effect if enabled if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), @@ -2455,7 +2459,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // give external parties a change to hook in emit renderingInWorldInterface(); - + // render JS/scriptable overlays _overlays.render3D(); } @@ -2536,8 +2540,8 @@ void Application::displayOverlay() { char frameTimer[10]; quint64 mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5); sprintf(frameTimer, "%d\n", (int)(mSecsNow % 1000)); - int timerBottom = - (Menu::getInstance()->isOptionChecked(MenuOption::Stats) && + int timerBottom = + (Menu::getInstance()->isOptionChecked(MenuOption::Stats) && Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) ? 80 : 20; drawText(_glWidget->width() - 100, _glWidget->height() - timerBottom, 0.30f, 1.0f, 0.f, frameTimer, WHITE_TEXT); @@ -2552,7 +2556,7 @@ void Application::displayOverlay() { void Application::displayStatsBackground(unsigned int rgba, int x, int y, int width, int height) { glBegin(GL_QUADS); glColor4f(((rgba >> 24) & 0xff) / 255.0f, - ((rgba >> 16) & 0xff) / 255.0f, + ((rgba >> 16) & 0xff) / 255.0f, ((rgba >> 8) & 0xff) / 255.0f, (rgba & 0xff) / 255.0f); glVertex3f(x, y, 0); @@ -2560,7 +2564,7 @@ void Application::displayStatsBackground(unsigned int rgba, int x, int y, int wi glVertex3f(x + width, y + height, 0); glVertex3f(x , y + height, 0); glEnd(); - glColor4f(1, 1, 1, 1); + glColor4f(1, 1, 1, 1); } // display expanded or contracted stats @@ -2654,12 +2658,12 @@ void Application::displayStats() { (float) (_audio.getNetworkBufferLengthSamplesPerChannel() + (float) _audio.getJitterBufferSamples()) / (float)_audio.getNetworkSampleRate() * 1000.f); drawText(30, _glWidget->height() - 22, 0.10f, 0.f, 2.f, audioJitter, WHITE_TEXT); - - + + char audioPing[30]; sprintf(audioPing, "Audio ping: %d", pingAudio); - - + + char avatarPing[30]; sprintf(avatarPing, "Avatar ping: %d", pingAvatar); char voxelAvgPing[30]; @@ -2697,7 +2701,7 @@ void Application::displayStats() { } else { // longhand way sprintf(avatarPosition, "Position: %.1f, %.1f, %.1f", avatarPos.x, avatarPos.y, avatarPos.z); - } + } char avatarVelocity[30]; sprintf(avatarVelocity, "Velocity: %.1f", glm::length(_myAvatar->getVelocity())); char avatarBodyYaw[30]; @@ -2723,7 +2727,7 @@ void Application::displayStats() { verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarMixerStats, WHITE_TEXT); - + stringstream downloadStats; downloadStats << "Downloads: "; foreach (Resource* resource, ResourceCache::getLoadingRequests()) { @@ -2731,7 +2735,7 @@ void Application::displayStats() { downloadStats << roundf(resource->getProgress() * MAXIMUM_PERCENTAGE) << "% "; } downloadStats << "(" << ResourceCache::getPendingRequestCount() << " pending)"; - + verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, downloadStats.str().c_str(), WHITE_TEXT); } @@ -2751,7 +2755,7 @@ void Application::displayStats() { drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); voxelStats.str(""); - voxelStats << + voxelStats << "Geometry RAM: " << _voxels.getVoxelMemoryUsageRAM() / 1000000.f << "MB / " << "VBO: " << _voxels.getVoxelMemoryUsageVBO() / 1000000.f << "MB"; if (_voxels.hasVoxelMemoryUsageGPU()) { @@ -2803,7 +2807,7 @@ void Application::displayStats() { totalNodes += stats.getTotalElements(); if (_statsExpanded) { totalInternal += stats.getTotalInternal(); - totalLeaves += stats.getTotalLeaves(); + totalLeaves += stats.getTotalLeaves(); } } if (_statsExpanded) { @@ -2827,7 +2831,7 @@ void Application::displayStats() { QString packetsString = locale.toString((int)voxelPacketsToProcess); QString maxString = locale.toString((int)_recentMaxPackets); voxelStats << "Voxel Packets to Process: " << qPrintable(packetsString) - << " [Recent Max: " << qPrintable(maxString) << "]"; + << " [Recent Max: " << qPrintable(maxString) << "]"; verticalOffset += STATS_PELS_PER_LINE; drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); } @@ -2989,12 +2993,12 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { _mirrorCamera.setFieldOfView(BILLBOARD_FIELD_OF_VIEW); // degees _mirrorCamera.setDistance(BILLBOARD_DISTANCE * _myAvatar->getScale()); _mirrorCamera.setTargetPosition(_myAvatar->getPosition()); - + } else if (_rearMirrorTools->getZoomLevel() == BODY) { _mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees _mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale()); _mirrorCamera.setTargetPosition(_myAvatar->getChestPosition()); - + } else { // HEAD zoom level _mirrorCamera.setFieldOfView(MIRROR_FIELD_OF_VIEW); // degrees _mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale()); @@ -3009,7 +3013,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { } } _mirrorCamera.setAspectRatio((float)region.width() / region.height()); - + _mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f))); _mirrorCamera.update(1.0f/_fps); @@ -3054,7 +3058,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { if (!billboard) { _rearMirrorTools->render(false); } - + // reset Viewport and projection matrix glViewport(0, 0, _glWidget->width(), _glWidget->height()); glDisable(GL_SCISSOR_TEST); @@ -3274,14 +3278,14 @@ void Application::setMenuShortcutsEnabled(bool enabled) { } void Application::updateWindowTitle(){ - + QString buildVersion = " (build " + applicationVersion() + ")"; NodeList* nodeList = NodeList::getInstance(); - + QString username = AccountManager::getInstance().getUsername(); QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString() + " @ " + nodeList->getDomainInfo().getHostname() + buildVersion; - + qDebug("Application title set to: %s", title.toStdString().c_str()); _window->setWindowTitle(title); } @@ -3296,7 +3300,7 @@ void Application::domainChanged(const QString& domainHostname) { _voxelServerJurisdictions.clear(); _octreeServerSceneStats.clear(); _particleServerJurisdictions.clear(); - + // reset the particle renderer _particles.clear(); @@ -3306,12 +3310,12 @@ void Application::domainChanged(const QString& domainHostname) { void Application::connectedToDomain(const QString& hostname) { AccountManager& accountManager = AccountManager::getInstance(); - + if (accountManager.isLoggedIn()) { // update our domain-server with the data-server we're logged in with - + QString domainPutJsonString = "{\"address\":{\"domain\":\"" + hostname + "\"}}"; - + accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation, JSONCallbackParameters(), domainPutJsonString.toUtf8()); } @@ -3476,13 +3480,13 @@ void Application::loadScripts() { // loads all saved scripts QSettings* settings = new QSettings(this); int size = settings->beginReadArray("Settings"); - + for (int i = 0; i < size; ++i){ settings->setArrayIndex(i); QString string = settings->value("script").toString(); loadScript(string); } - + settings->endArray(); } @@ -3497,42 +3501,72 @@ void Application::saveScripts() { // saves all current running scripts QSettings* settings = new QSettings(this); settings->beginWriteArray("Settings"); - for (int i = 0; i < _activeScripts.size(); ++i){ + for (int i = 0; i < getRunningScripts().size(); ++i){ settings->setArrayIndex(i); - settings->setValue("script", _activeScripts.at(i)); + settings->setValue("script", getRunningScripts().at(i)); } - + settings->endArray(); } void Application::stopAllScripts() { // stops all current running scripts - QList scriptActions = Menu::getInstance()->getActiveScriptsMenu()->actions(); - foreach (QAction* scriptAction, scriptActions) { - scriptAction->activate(QAction::Trigger); - qDebug() << "stopping script..." << scriptAction->text(); + for (int i = 0; i < _scriptEnginesHash.size(); ++i) { + _scriptEnginesHash.values().at(i)->stop(); + qDebug() << "stopping script..." << getRunningScripts().at(i); } - _activeScripts.clear(); + _scriptEnginesHash.clear(); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); +} + +void Application::stopScript(const QString &scriptName) +{ + _scriptEnginesHash.value(scriptName)->stop(); + qDebug() << "stopping script..." << scriptName; + _scriptEnginesHash.remove(scriptName); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); } void Application::reloadAllScripts() { // remember all the current scripts so we can reload them - QStringList reloadList = _activeScripts; + QStringList reloadList = getRunningScripts(); // reloads all current running scripts - QList scriptActions = Menu::getInstance()->getActiveScriptsMenu()->actions(); - foreach (QAction* scriptAction, scriptActions) { - scriptAction->activate(QAction::Trigger); - qDebug() << "stopping script..." << scriptAction->text(); - } + stopAllScripts(); - // NOTE: we don't need to clear the _activeScripts list because that is handled on script shutdown. - foreach (QString scriptName, reloadList){ qDebug() << "reloading script..." << scriptName; loadScript(scriptName); } } +void Application::toggleRunningScriptsWidget() +{ + if (!_runningScriptsWidget->toggleViewAction()->isChecked()) { + _runningScriptsWidget->move(_window->geometry().topLeft().x(), _window->geometry().topLeft().y()); + _runningScriptsWidget->resize(0, _window->height()); + _runningScriptsWidget->toggleViewAction()->trigger(); + _runningScriptsWidget->grabKeyboard(); + + QPropertyAnimation* slideAnimation = new QPropertyAnimation(_runningScriptsWidget, "geometry", _runningScriptsWidget); + slideAnimation->setStartValue(_runningScriptsWidget->geometry()); + slideAnimation->setEndValue(QRect(_window->geometry().topLeft().x(), _window->geometry().topLeft().y(), + 310, _runningScriptsWidget->height())); + slideAnimation->setDuration(250); + slideAnimation->start(QAbstractAnimation::DeleteWhenStopped); + } else { + _runningScriptsWidget->releaseKeyboard(); + + QPropertyAnimation* slideAnimation = new QPropertyAnimation(_runningScriptsWidget, "geometry", _runningScriptsWidget); + slideAnimation->setStartValue(_runningScriptsWidget->geometry()); + slideAnimation->setEndValue(QRect(_window->geometry().topLeft().x(), _window->geometry().topLeft().y(), + 0, _runningScriptsWidget->height())); + slideAnimation->setDuration(250); + slideAnimation->start(QAbstractAnimation::DeleteWhenStopped); + + QTimer::singleShot(260, _runningScriptsWidget->toggleViewAction(), SLOT(trigger())); + } +} + void Application::uploadFST() { FstReader reader; if (reader.zip()) { @@ -3540,29 +3574,17 @@ void Application::uploadFST() { } } -void Application::removeScriptName(const QString& fileNameString) { - _activeScripts.removeOne(fileNameString); -} - -void Application::cleanupScriptMenuItem(const QString& scriptMenuName) { - Menu::getInstance()->removeAction(Menu::getInstance()->getActiveScriptsMenu(), scriptMenuName); -} - void Application::loadScript(const QString& scriptName) { // start the script on a new thread... - bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself - ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), wantMenuItems, &_controllerScriptingInterface); + ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), &_controllerScriptingInterface); + _scriptEnginesHash.insert(scriptName, scriptEngine); if (!scriptEngine->hasScript()) { qDebug() << "Application::loadScript(), script failed to load..."; return; } - _activeScripts.append(scriptName); - - // add a stop menu item - Menu::getInstance()->addActionToQMenuAndActionHash(Menu::getInstance()->getActiveScriptsMenu(), - scriptEngine->getScriptMenuName(), 0, scriptEngine, SLOT(stop())); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so // we can use the same ones from the application. @@ -3570,7 +3592,7 @@ void Application::loadScript(const QString& scriptName) { scriptEngine->getVoxelsScriptingInterface()->setVoxelTree(_voxels.getTree()); scriptEngine->getParticlesScriptingInterface()->setPacketSender(&_particleEditSender); scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree()); - + // hook our avatar object into this script engine scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features @@ -3595,8 +3617,6 @@ void Application::loadScript(const QString& scriptName) { // when the thread is terminated, add both scriptEngine and thread to the deleteLater queue connect(scriptEngine, SIGNAL(finished(const QString&)), scriptEngine, SLOT(deleteLater())); connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater())); - connect(scriptEngine, SIGNAL(finished(const QString&)), this, SLOT(removeScriptName(const QString&))); - connect(scriptEngine, SIGNAL(cleanupMenuItem(const QString&)), this, SLOT(cleanupScriptMenuItem(const QString&))); // when the application is about to quit, stop our script engine so it unwinds properly connect(this, SIGNAL(aboutToQuit()), scriptEngine, SLOT(stop())); @@ -3620,12 +3640,12 @@ void Application::loadDialog() { suggestedName = _previousScriptLocation; } - QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName, + QString fileNameString = QFileDialog::getOpenFileName(_glWidget, tr("Open Script"), suggestedName, tr("JavaScript Files (*.js)")); if (!fileNameString.isEmpty()) { _previousScriptLocation = fileNameString; } - + loadScript(fileNameString); } @@ -3636,7 +3656,7 @@ void Application::loadScriptURLDialog() { scriptURLDialog.setLabelText("Script:"); scriptURLDialog.setWindowFlags(Qt::Sheet); const float DIALOG_RATIO_OF_WINDOW = 0.30f; - scriptURLDialog.resize(scriptURLDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, + scriptURLDialog.resize(scriptURLDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, scriptURLDialog.size().height()); int dialogReturn = scriptURLDialog.exec(); @@ -3674,29 +3694,29 @@ void Application::checkVersion() { } void Application::parseVersionXml() { - + #ifdef Q_OS_WIN32 QString operatingSystem("win"); #endif - + #ifdef Q_OS_MAC QString operatingSystem("mac"); #endif - + #ifdef Q_OS_LINUX QString operatingSystem("ubuntu"); #endif - + QString releaseDate; QString releaseNotes; QString latestVersion; QUrl downloadUrl; QObject* sender = QObject::sender(); - + QXmlStreamReader xml(qobject_cast(sender)); while (!xml.atEnd() && !xml.hasError()) { QXmlStreamReader::TokenType token = xml.readNext(); - + if (token == QXmlStreamReader::StartElement) { if (xml.name() == "ReleaseDate") { xml.readNext(); diff --git a/interface/src/Application.h b/interface/src/Application.h index bab7578ca4..b6a9618cd8 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,7 @@ #include "ui/LogDialog.h" #include "ui/UpdateDialog.h" #include "ui/overlays/Overlays.h" +#include "ui/RunningScriptsWidget.h" #include "voxels/VoxelFade.h" #include "voxels/VoxelHideShowThread.h" #include "voxels/VoxelImporter.h" @@ -112,7 +114,7 @@ public: ~Application(); void restoreSizeAndPosition(); - void loadScript(const QString& fileNameString); + void loadScript(const QString& fileNameString); void loadScripts(); void storeSizeAndPosition(); void clearScriptsBeforeRunning(); @@ -136,9 +138,9 @@ public: void wheelEvent(QWheelEvent* event); void dropEvent(QDropEvent *event); - + bool event(QEvent* event); - + void makeVoxel(glm::vec3 position, float scale, unsigned char red, @@ -226,6 +228,8 @@ public: void skipVersion(QString latestVersion); + QStringList getRunningScripts() { return _scriptEnginesHash.keys(); } + signals: /// Fired when we're simulating; allows external parties to hook in. @@ -233,10 +237,10 @@ signals: /// Fired when we're rendering in-world interface elements; allows external parties to hook in. void renderingInWorldInterface(); - + /// Fired when the import window is closed void importDone(); - + public slots: void domainChanged(const QString& domainHostname); void updateWindowTitle(); @@ -259,31 +263,30 @@ public slots: void toggleLogDialog(); void initAvatarAndViewFrustum(); void stopAllScripts(); + void stopScript(const QString& scriptName); void reloadAllScripts(); - + void toggleRunningScriptsWidget(); + void uploadFST(); private slots: void timer(); void idle(); - + void connectedToDomain(const QString& hostname); void setFullscreen(bool fullscreen); void setEnable3DTVMode(bool enable3DTVMode); void cameraMenuChanged(); - + glm::vec2 getScaledScreenPoint(glm::vec2 projectedPoint); void closeMirrorView(); void restoreMirrorView(); void shrinkMirrorView(); void resetSensors(); - - void parseVersionXml(); - void removeScriptName(const QString& fileNameString); - void cleanupScriptMenuItem(const QString& scriptMenuName); + void parseVersionXml(); private: void resetCamerasOnResizeGL(Camera& camera, int width, int height); @@ -354,7 +357,7 @@ private: bool _statsExpanded; BandwidthMeter _bandwidthMeter; - + QThread* _nodeThread; DatagramProcessor _datagramProcessor; @@ -373,7 +376,7 @@ private: timeval _lastTimeUpdated; bool _justStarted; Stars _stars; - + BuckyBalls _buckyBalls; VoxelSystem _voxels; @@ -407,7 +410,6 @@ private: Visage _visage; SixenseManager _sixenseManager; - QStringList _activeScripts; Camera _myCamera; // My view onto the world Camera _viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode @@ -491,10 +493,13 @@ private: void displayUpdateDialog(); bool shouldSkipVersion(QString latestVersion); void takeSnapshot(); - + TouchEvent _lastTouchEvent; - + Overlays _overlays; + + QPointer _runningScriptsWidget; + QHash _scriptEnginesHash; }; #endif /* defined(__interface__Application__) */ diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ec20401fef..313a6728fb 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -87,7 +87,7 @@ Menu::Menu() : _loginAction(NULL) { Application *appInstance = Application::getInstance(); - + QMenu* fileMenu = addMenu("File"); #ifdef Q_OS_MAC @@ -100,23 +100,24 @@ Menu::Menu() : #endif AccountManager& accountManager = AccountManager::getInstance(); - + _loginAction = addActionToQMenuAndActionHash(fileMenu, MenuOption::Logout); - + // call our toggle login function now so the menu option is setup properly toggleLoginMenuItem(); - + // connect to the appropriate slots of the AccountManager so that we can change the Login/Logout menu item connect(&accountManager, &AccountManager::accessTokenChanged, this, &Menu::toggleLoginMenuItem); connect(&accountManager, &AccountManager::logoutComplete, this, &Menu::toggleLoginMenuItem); addDisabledActionAndSeparator(fileMenu, "Scripts"); addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog())); - addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScriptURL, + addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScriptURL, Qt::CTRL | Qt::SHIFT | Qt::Key_O, appInstance, SLOT(loadScriptURLDialog())); addActionToQMenuAndActionHash(fileMenu, MenuOption::StopAllScripts, 0, appInstance, SLOT(stopAllScripts())); addActionToQMenuAndActionHash(fileMenu, MenuOption::ReloadAllScripts, 0, appInstance, SLOT(reloadAllScripts())); - _activeScriptsMenu = fileMenu->addMenu("Running Scripts"); + addActionToQMenuAndActionHash(fileMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J, + appInstance, SLOT(toggleRunningScriptsWidget())); addDisabledActionAndSeparator(fileMenu, "Go"); addActionToQMenuAndActionHash(fileMenu, @@ -147,7 +148,7 @@ Menu::Menu() : addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model"); addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadFST, 0, Application::getInstance(), SLOT(uploadFST())); - + addDisabledActionAndSeparator(fileMenu, "Settings"); addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsImport, 0, this, SLOT(importSettings())); addActionToQMenuAndActionHash(fileMenu, MenuOption::SettingsExport, 0, this, SLOT(exportSettings())); @@ -172,8 +173,8 @@ Menu::Menu() : addDisabledActionAndSeparator(editMenu, "Physics"); addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::Gravity, Qt::SHIFT | Qt::Key_G, false); - - + + addAvatarCollisionSubMenu(editMenu); @@ -207,7 +208,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H, false, appInstance, SLOT(cameraMenuChanged())); - + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Enable3DTVMode, 0, false, appInstance, @@ -333,16 +334,16 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings, Qt::CTRL | Qt::SHIFT | Qt::Key_P); addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::SuppressShortTimings, Qt::CTRL | Qt::SHIFT | Qt::Key_S); - addCheckableActionToQMenuAndActionHash(renderDebugMenu, - MenuOption::CullSharedFaces, - Qt::CTRL | Qt::SHIFT | Qt::Key_C, + addCheckableActionToQMenuAndActionHash(renderDebugMenu, + MenuOption::CullSharedFaces, + Qt::CTRL | Qt::SHIFT | Qt::Key_C, false, appInstance->getVoxels(), SLOT(cullSharedFaces())); - addCheckableActionToQMenuAndActionHash(renderDebugMenu, - MenuOption::ShowCulledSharedFaces, - Qt::CTRL | Qt::SHIFT | Qt::Key_X, + addCheckableActionToQMenuAndActionHash(renderDebugMenu, + MenuOption::ShowCulledSharedFaces, + Qt::CTRL | Qt::SHIFT | Qt::Key_X, false, appInstance->getVoxels(), SLOT(showCulledSharedFaces())); @@ -360,14 +361,14 @@ Menu::Menu() : false, appInstance->getAudio(), SLOT(toggleMute())); - + addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel, Qt::CTRL | Qt::SHIFT | Qt::Key_V, this, SLOT(pasteToVoxel())); connect(appInstance->getAudio(), SIGNAL(muteToggled()), this, SLOT(audioMuteToggled())); - + #ifndef Q_OS_MAC QMenu* helpMenu = addMenu("Help"); QAction* helpAction = helpMenu->addAction(MenuOption::AboutApp); @@ -571,7 +572,7 @@ void Menu::addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& QAction* separatorText = new QAction(actionName,destinationMenu); separatorText->setEnabled(false); destinationMenu->insertAction(actionBefore, separatorText); - + } else { destinationMenu->addSeparator(); (destinationMenu->addAction(actionName))->setEnabled(false); @@ -623,7 +624,7 @@ QAction* Menu::addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, const char* member, int menuItemLocation) { - QAction* action = addActionToQMenuAndActionHash(destinationMenu, actionName, shortcut, receiver, member, + QAction* action = addActionToQMenuAndActionHash(destinationMenu, actionName, shortcut, receiver, member, QAction::NoRole, menuItemLocation); action->setCheckable(true); action->setChecked(checked); @@ -677,35 +678,35 @@ const float DIALOG_RATIO_OF_WINDOW = 0.30f; void Menu::loginForCurrentDomain() { QDialog loginDialog(Application::getInstance()->getWindow()); loginDialog.setWindowTitle("Login"); - + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom); loginDialog.setLayout(layout); loginDialog.setWindowFlags(Qt::Sheet); - + QFormLayout* form = new QFormLayout(); layout->addLayout(form, 1); - + QLineEdit* loginLineEdit = new QLineEdit(); loginLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); form->addRow("Login:", loginLineEdit); - + QLineEdit* passwordLineEdit = new QLineEdit(); passwordLineEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); passwordLineEdit->setEchoMode(QLineEdit::Password); form->addRow("Password:", passwordLineEdit); - + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); loginDialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); loginDialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); layout->addWidget(buttons); - + int dialogReturn = loginDialog.exec(); - + if (dialogReturn == QDialog::Accepted && !loginLineEdit->text().isEmpty() && !passwordLineEdit->text().isEmpty()) { // attempt to get an access token given this username and password AccountManager::getInstance().requestAccessToken(loginLineEdit->text(), passwordLineEdit->text()); } - + sendFakeEnterEvent(); } @@ -713,19 +714,19 @@ void Menu::editPreferences() { Application* applicationInstance = Application::getInstance(); ModelBrowser headBrowser(Head); ModelBrowser skeletonBrowser(Skeleton); - + const QString BROWSE_BUTTON_TEXT = "Browse"; QDialog dialog(applicationInstance->getWindow()); dialog.setWindowTitle("Interface Preferences"); - + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom); dialog.setLayout(layout); - + QFormLayout* form = new QFormLayout(); layout->addLayout(form, 1); - + QHBoxLayout headModelLayout; QString faceURLString = applicationInstance->getAvatar()->getHead()->getFaceModel().getURL().toString(); QLineEdit headURLEdit(faceURLString); @@ -737,7 +738,7 @@ void Menu::editPreferences() { headModelLayout.addWidget(&headURLEdit); headModelLayout.addWidget(&headBrowseButton); form->addRow("Head URL:", &headModelLayout); - + QHBoxLayout skeletonModelLayout; QString skeletonURLString = applicationInstance->getAvatar()->getSkeletonModel().getURL().toString(); QLineEdit skeletonURLEdit(skeletonURLString); @@ -749,7 +750,7 @@ void Menu::editPreferences() { skeletonModelLayout.addWidget(&skeletonURLEdit); skeletonModelLayout.addWidget(&SkeletonBrowseButton); form->addRow("Skeleton URL:", &skeletonModelLayout); - + QString displayNameString = applicationInstance->getAvatar()->getDisplayName(); QLineEdit* displayNameEdit = new QLineEdit(displayNameString); @@ -826,12 +827,12 @@ void Menu::editPreferences() { } QString displayNameStr(displayNameEdit->text()); - + if (displayNameStr != displayNameString) { applicationInstance->getAvatar()->setDisplayName(displayNameStr); shouldDispatchIdentityPacket = true; } - + if (shouldDispatchIdentityPacket) { applicationInstance->getAvatar()->sendIdentityPacket(); } @@ -864,10 +865,10 @@ void Menu::editPreferences() { void Menu::goToDomain(const QString newDomain) { if (NodeList::getInstance()->getDomainInfo().getHostname() != newDomain) { - + // send a node kill request, indicating to other clients that they should play the "disappeared" effect Application::getInstance()->getAvatar()->sendKillAvatar(); - + // give our nodeList the new domain-server hostname NodeList::getInstance()->getDomainInfo().setHostname(newDomain); } @@ -897,7 +898,7 @@ void Menu::goToDomainDialog() { // the user input a new hostname, use that newHostname = domainDialog.textValue(); } - + goToDomain(newHostname); } @@ -913,7 +914,7 @@ bool Menu::goToDestination(QString destination) { } void Menu::goTo() { - + QInputDialog gotoDialog(Application::getInstance()->getWindow()); gotoDialog.setWindowTitle("Go to"); gotoDialog.setLabelText("Destination:"); @@ -921,7 +922,7 @@ void Menu::goTo() { gotoDialog.setTextValue(destination); gotoDialog.setWindowFlags(Qt::Sheet); gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height()); - + int dialogReturn = gotoDialog.exec(); if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) { goToUser(gotoDialog.textValue()); @@ -1070,7 +1071,7 @@ void Menu::toggleLoginMenuItem() { AccountManager& accountManager = AccountManager::getInstance(); disconnect(_loginAction, 0, 0, 0); - + if (accountManager.isLoggedIn()) { // change the menu item to logout _loginAction->setText("Logout " + accountManager.getUsername()); @@ -1078,7 +1079,7 @@ void Menu::toggleLoginMenuItem() { } else { // change the menu item to login _loginAction->setText("Login"); - + connect(_loginAction, &QAction::triggered, this, &Menu::loginForCurrentDomain); } } @@ -1185,7 +1186,7 @@ QString Menu::getLODFeedbackText() { } break; } - // distance feedback + // distance feedback float voxelSizeScale = getVoxelSizeScale(); float relativeToDefault = voxelSizeScale / DEFAULT_OCTREE_SIZE_SCALE; QString result; @@ -1200,7 +1201,7 @@ QString Menu::getLODFeedbackText() { } void Menu::autoAdjustLOD(float currentFPS) { - // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't + // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't // really want to count them in our average, so we will ignore the real frame rates and stuff // our moving average with simulated good data const int IGNORE_THESE_SAMPLES = 100; @@ -1212,7 +1213,7 @@ void Menu::autoAdjustLOD(float currentFPS) { _fastFPSAverage.updateAverage(currentFPS); quint64 now = usecTimestampNow(); - + const quint64 ADJUST_AVATAR_LOD_DOWN_DELAY = 1000 * 1000; if (_fastFPSAverage.getAverage() < ADJUST_LOD_DOWN_FPS) { if (now - _lastAvatarDetailDrop > ADJUST_AVATAR_LOD_DOWN_DELAY) { @@ -1231,11 +1232,11 @@ void Menu::autoAdjustLOD(float currentFPS) { _avatarLODDistanceMultiplier = qMax(MINIMUM_DISTANCE_MULTIPLIER, _avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE); } - + bool changed = false; quint64 elapsed = now - _lastAdjust; - if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS + if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS && _voxelSizeScale > ADJUST_LOD_MIN_SIZE_SCALE) { _voxelSizeScale *= ADJUST_LOD_DOWN_BY; @@ -1248,7 +1249,7 @@ void Menu::autoAdjustLOD(float currentFPS) { << "_voxelSizeScale=" << _voxelSizeScale; } - if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > ADJUST_LOD_UP_FPS + if (elapsed > ADJUST_LOD_UP_DELAY && _fpsAverage.getAverage() > ADJUST_LOD_UP_FPS && _voxelSizeScale < ADJUST_LOD_MAX_SIZE_SCALE) { _voxelSizeScale *= ADJUST_LOD_UP_BY; if (_voxelSizeScale > ADJUST_LOD_MAX_SIZE_SCALE) { @@ -1259,7 +1260,7 @@ void Menu::autoAdjustLOD(float currentFPS) { qDebug() << "adjusting LOD up... average fps for last approximately 5 seconds=" << _fpsAverage.getAverage() << "_voxelSizeScale=" << _voxelSizeScale; } - + if (changed) { if (_lodToolsDialog) { _lodToolsDialog->reloadSliders(); @@ -1337,13 +1338,13 @@ void Menu::addAvatarCollisionSubMenu(QMenu* overMenu) { Application* appInstance = Application::getInstance(); QObject* avatar = appInstance->getAvatar(); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment, + addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithEnvironment, 0, false, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars, + addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithAvatars, 0, true, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels, + addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithVoxels, 0, false, avatar, SLOT(updateCollisionFlags())); - addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles, + addCheckableActionToQMenuAndActionHash(subMenu, MenuOption::CollideWithParticles, 0, true, avatar, SLOT(updateCollisionFlags())); } @@ -1352,9 +1353,9 @@ QAction* Menu::getActionFromName(const QString& menuName, QMenu* menu) { if (menu) { menuActions = menu->actions(); } else { - menuActions = actions(); + menuActions = actions(); } - + foreach (QAction* menuAction, menuActions) { if (menuName == menuAction->text()) { return menuAction; @@ -1461,7 +1462,7 @@ QMenu* Menu::addMenu(const QString& menuName) { void Menu::removeMenu(const QString& menuName) { QAction* action = getMenuAction(menuName); - + // only proceed if the menu actually exists if (action) { QString finalMenuPart; @@ -1513,7 +1514,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) { if (!properties.shortcutKeySequence.isEmpty()) { shortcut = new QShortcut(properties.shortcutKeySequence, this); } - + // check for positioning requests int requestedPosition = properties.position; if (requestedPosition == UNSPECIFIED_POSITION && !properties.beforeItem.isEmpty()) { @@ -1527,13 +1528,13 @@ void Menu::addMenuItem(const MenuItemProperties& properties) { requestedPosition = afterPosition + 1; } } - + QAction* menuItemAction = NULL; if (properties.isSeparator) { addDisabledActionAndSeparator(menuObj, properties.menuItemName, requestedPosition); } else if (properties.isCheckable) { menuItemAction = addCheckableActionToQMenuAndActionHash(menuObj, properties.menuItemName, - properties.shortcutKeySequence, properties.isChecked, + properties.shortcutKeySequence, properties.isChecked, MenuScriptingInterface::getInstance(), SLOT(menuItemTriggered()), requestedPosition); } else { menuItemAction = addActionToQMenuAndActionHash(menuObj, properties.menuItemName, properties.shortcutKeySequence, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index c7c4c6ecea..b69d44061d 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -95,8 +95,6 @@ public: // User Tweakable PPS from Voxel Server int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPacketsPerSecond; } - QMenu* getActiveScriptsMenu() { return _activeScriptsMenu;} - QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu, const QString& actionName, const QKeySequence& shortcut = 0, @@ -124,7 +122,7 @@ public slots: void goTo(); void goToUser(const QString& user); void pasteToVoxel(); - + void toggleLoginMenuItem(); QMenu* addMenu(const QString& menuName); @@ -166,7 +164,7 @@ private: void scanMenu(QMenu* menu, settingsAction modifySetting, QSettings* set); /// helper method to have separators with labels that are also compatible with OS X - void addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& actionName, + void addDisabledActionAndSeparator(QMenu* destinationMenu, const QString& actionName, int menuItemLocation = UNSPECIFIED_POSITION); QAction* addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, @@ -189,7 +187,7 @@ private: int findPositionOfMenuItem(QMenu* menu, const QString& searchMenuItem); int positionBeforeSeparatorIfNeeded(QMenu* menu, int requestedPosition); QMenu* getMenu(const QString& menuName); - + QHash _actionHash; int _audioJitterBufferSamples; /// number of extra samples to wait before starting audio playback @@ -208,7 +206,6 @@ private: int _boundaryLevelAdjust; QAction* _useVoxelShader; int _maxVoxelPacketsPerSecond; - QMenu* _activeScriptsMenu; QString replaceLastOccurrence(QChar search, QChar replace, QString string); quint64 _lastAdjust; quint64 _lastAvatarDetailDrop; @@ -290,6 +287,7 @@ namespace MenuOption { const QString RenderSkeletonCollisionProxies = "Skeleton Collision Proxies"; const QString RenderHeadCollisionProxies = "Head Collision Proxies"; const QString ResetAvatarSize = "Reset Avatar Size"; + const QString RunningScripts = "Running Scripts"; const QString RunTimingTests = "Run Timing Tests"; const QString SettingsImport = "Import Settings"; const QString Shadows = "Shadows"; diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp new file mode 100644 index 0000000000..2238cab2df --- /dev/null +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -0,0 +1,203 @@ +// +// RunningScripts.cpp +// interface +// +// Created by Mohammed Nafees on 03/28/2014. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. + +#include "ui_runningScriptsWidget.h" +#include "RunningScriptsWidget.h" + +#include +#include + +#include "Application.h" + +RunningScriptsWidget::RunningScriptsWidget(QDockWidget *parent) : + QDockWidget(parent), + ui(new Ui::RunningScriptsWidget) +{ + ui->setupUi(this); + + // remove the title bar (see the Qt docs on setTitleBarWidget) + setTitleBarWidget(new QWidget()); + + ui->runningScriptsTableWidget->setColumnCount(2); + ui->runningScriptsTableWidget->verticalHeader()->setVisible(false); + ui->runningScriptsTableWidget->horizontalHeader()->setVisible(false); + ui->runningScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection); + ui->runningScriptsTableWidget->setShowGrid(false); + ui->runningScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->runningScriptsTableWidget->setColumnWidth(0, 235); + ui->runningScriptsTableWidget->setColumnWidth(1, 25); + connect(ui->runningScriptsTableWidget, &QTableWidget::cellClicked, this, &RunningScriptsWidget::stopScript); + + ui->recentlyLoadedScriptsTableWidget->setColumnCount(2); + ui->recentlyLoadedScriptsTableWidget->verticalHeader()->setVisible(false); + ui->recentlyLoadedScriptsTableWidget->horizontalHeader()->setVisible(false); + ui->recentlyLoadedScriptsTableWidget->setSelectionMode(QAbstractItemView::NoSelection); + ui->recentlyLoadedScriptsTableWidget->setShowGrid(false); + ui->recentlyLoadedScriptsTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->recentlyLoadedScriptsTableWidget->setColumnWidth(0, 25); + ui->recentlyLoadedScriptsTableWidget->setColumnWidth(1, 235); + connect(ui->recentlyLoadedScriptsTableWidget, &QTableWidget::cellClicked, + this, &RunningScriptsWidget::loadScript); + + connect(ui->hideWidgetButton, &QPushButton::clicked, + Application::getInstance(), &Application::toggleRunningScriptsWidget); + connect(ui->reloadAllButton, &QPushButton::clicked, + Application::getInstance(), &Application::reloadAllScripts); + connect(ui->stopAllButton, &QPushButton::clicked, + this, &RunningScriptsWidget::allScriptsStopped); +} + +RunningScriptsWidget::~RunningScriptsWidget() +{ + delete ui; +} + +void RunningScriptsWidget::setRunningScripts(const QStringList& list) +{ + ui->runningScriptsTableWidget->setRowCount(list.size()); + + ui->noRunningScriptsLabel->setVisible(list.isEmpty()); + ui->currentlyRunningLabel->setVisible(!list.isEmpty()); + ui->line1->setVisible(!list.isEmpty()); + ui->runningScriptsTableWidget->setVisible(!list.isEmpty()); + ui->reloadAllButton->setVisible(!list.isEmpty()); + ui->stopAllButton->setVisible(!list.isEmpty()); + + for (int i = 0; i < list.size(); ++i) { + QTableWidgetItem *scriptName = new QTableWidgetItem; + scriptName->setText(list.at(i)); + scriptName->setToolTip(list.at(i)); + scriptName->setTextAlignment(Qt::AlignCenter); + QTableWidgetItem *closeIcon = new QTableWidgetItem; + closeIcon->setIcon(QIcon(":/images/kill-script.svg")); + + ui->runningScriptsTableWidget->setItem(i, 0, scriptName); + ui->runningScriptsTableWidget->setItem(i, 1, closeIcon); + } + + createRecentlyLoadedScriptsTable(); +} + +void RunningScriptsWidget::keyPressEvent(QKeyEvent *e) +{ + switch(e->key()) { + case Qt::Key_Escape: + Application::getInstance()->toggleRunningScriptsWidget(); + break; + + case Qt::Key_1: + if (_recentlyLoadedScripts.size() > 0) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(0)); + } + break; + + case Qt::Key_2: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 2) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(1)); + } + break; + + case Qt::Key_3: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 3) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(2)); + } + break; + + case Qt::Key_4: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 4) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(3)); + } + break; + case Qt::Key_5: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 5) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(4)); + } + break; + + case Qt::Key_6: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 6) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(5)); + } + break; + + case Qt::Key_7: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 7) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(6)); + } + break; + case Qt::Key_8: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 8) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(7)); + } + break; + + case Qt::Key_9: + if (_recentlyLoadedScripts.size() > 0 && _recentlyLoadedScripts.size() >= 9) { + Application::getInstance()->loadScript(_recentlyLoadedScripts.at(8)); + } + break; + + default: + break; + } +} + +void RunningScriptsWidget::stopScript(int row, int column) +{ + if (column == 1) { // make sure the user has clicked on the close icon + _lastStoppedScript = ui->runningScriptsTableWidget->item(row, 0)->text(); + emit stopScriptName(ui->runningScriptsTableWidget->item(row, 0)->text()); + } +} + +void RunningScriptsWidget::loadScript(int row, int column) +{ + Application::getInstance()->loadScript(ui->recentlyLoadedScriptsTableWidget->item(row, column)->text()); +} + +void RunningScriptsWidget::allScriptsStopped() +{ + QStringList list = Application::getInstance()->getRunningScripts(); + for (int i = 0; i < list.size(); ++i) { + _recentlyLoadedScripts.prepend(list.at(i)); + } + + Application::getInstance()->stopAllScripts(); +} + +void RunningScriptsWidget::createRecentlyLoadedScriptsTable() +{ + if (!_recentlyLoadedScripts.contains(_lastStoppedScript) && !_lastStoppedScript.isEmpty()) { + _recentlyLoadedScripts.prepend(_lastStoppedScript); + _lastStoppedScript = ""; + } + + for (int i = 0; i < _recentlyLoadedScripts.size(); ++i) { + if (Application::getInstance()->getRunningScripts().contains(_recentlyLoadedScripts.at(i))) { + _recentlyLoadedScripts.removeOne(_recentlyLoadedScripts.at(i)); + } + } + + ui->recentlyLoadedLabel->setVisible(!_recentlyLoadedScripts.isEmpty()); + ui->line2->setVisible(!_recentlyLoadedScripts.isEmpty()); + ui->recentlyLoadedScriptsTableWidget->setVisible(!_recentlyLoadedScripts.isEmpty()); + ui->recentlyLoadedInstruction->setVisible(!_recentlyLoadedScripts.isEmpty()); + + int limit = _recentlyLoadedScripts.size() > 9 ? 9 : _recentlyLoadedScripts.size(); + ui->recentlyLoadedScriptsTableWidget->setRowCount(limit); + for (int i = 0; i < limit; ++i) { + QTableWidgetItem *scriptName = new QTableWidgetItem; + scriptName->setText(_recentlyLoadedScripts.at(i)); + scriptName->setToolTip(_recentlyLoadedScripts.at(i)); + scriptName->setTextAlignment(Qt::AlignCenter); + QTableWidgetItem *number = new QTableWidgetItem; + number->setText(QString::number(i+1) + "."); + + ui->recentlyLoadedScriptsTableWidget->setItem(i, 0, number); + ui->recentlyLoadedScriptsTableWidget->setItem(i, 1, scriptName); + } +} diff --git a/interface/src/ui/RunningScriptsWidget.h b/interface/src/ui/RunningScriptsWidget.h new file mode 100644 index 0000000000..212980ecff --- /dev/null +++ b/interface/src/ui/RunningScriptsWidget.h @@ -0,0 +1,46 @@ +// +// RunningScripts.h +// interface +// +// Created by Mohammed Nafees on 03/28/2014. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. + +#ifndef __hifi__RunningScriptsWidget__ +#define __hifi__RunningScriptsWidget__ + +// Qt +#include + +namespace Ui { + class RunningScriptsWidget; +} + +class RunningScriptsWidget : public QDockWidget +{ + Q_OBJECT +public: + explicit RunningScriptsWidget(QDockWidget *parent = 0); + ~RunningScriptsWidget(); + + void setRunningScripts(const QStringList& list); + +signals: + void stopScriptName(QString name); + +protected: + void keyPressEvent(QKeyEvent *e); + +private slots: + void stopScript(int row, int column); + void loadScript(int row, int column); + void allScriptsStopped(); + +private: + Ui::RunningScriptsWidget *ui; + QStringList _recentlyLoadedScripts; + QString _lastStoppedScript; + + void createRecentlyLoadedScriptsTable(); +}; + +#endif /* defined(__hifi__RunningScriptsWidget__) */ diff --git a/interface/ui/runningScriptsWidget.ui b/interface/ui/runningScriptsWidget.ui new file mode 100644 index 0000000000..9494d4ed09 --- /dev/null +++ b/interface/ui/runningScriptsWidget.ui @@ -0,0 +1,248 @@ + + + RunningScriptsWidget + + + + 0 + 0 + 310 + 651 + + + + Form + + + background: #f7f7f7; +font-family: Helvetica, Arial, "DejaVu Sans"; + + + + + 20 + 10 + 221 + 31 + + + + color: #0e7077; + + + <html><head/><body><p><span style=" font-size:18pt;">Running Scripts</span></p></body></html> + + + + + + 20 + 40 + 301 + 20 + + + + color: #0e7077; + + + <html><head/><body><p><span style=" font-weight:600;">Currently running</span></p></body></html> + + + + + + 40 + 230 + 111 + 31 + + + + PointingHandCursor + + + background: #0e7077; +color: #fff; +border-radius: 6px; + + + Reload All + + + + :/images/reload.svg:/images/reload.svg + + + + + + 160 + 230 + 101 + 31 + + + + PointingHandCursor + + + background: #0e7077; +color: #fff; +border-radius: 6px; + + + Stop All + + + + :/images/stop.svg:/images/stop.svg + + + + + + 20 + 280 + 301 + 20 + + + + color: #0e7077; + + + <html><head/><body><p><span style=" font-weight:600;">Recently loaded</span></p></body></html> + + + + + + 20 + 300 + 271 + 8 + + + + + + + Qt::Horizontal + + + + + + 20 + 590 + 271 + 41 + + + + color: #95a5a6; + + + (click a script or use the 1-9 keys to load and run it) + + + true + + + + + + 270 + 10 + 31 + 31 + + + + PointingHandCursor + + + + + + + :/images/close.svg:/images/close.svg + + + + 20 + 20 + + + + true + + + + + + 20 + 70 + 271 + 141 + + + + background: transparent; + + + + + + 20 + 60 + 271 + 8 + + + + + + + Qt::Horizontal + + + + + + 20 + 310 + 271 + 281 + + + + background: transparent; + + + + + + 20 + 40 + 271 + 51 + + + + font: 14px; + + + There are no scripts currently running. + + + Qt::AlignCenter + + + + + + + + diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 1f1eab6baf..7572638a30 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -29,7 +29,6 @@ #include "LocalVoxels.h" #include "ScriptEngine.h" -int ScriptEngine::_scriptNumber = 1; VoxelsScriptingInterface ScriptEngine::_voxelsScriptingInterface; ParticlesScriptingInterface ScriptEngine::_particlesScriptingInterface; @@ -41,7 +40,7 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng } -ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString, +ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, AbstractControllerScriptingInterface* controllerScriptingInterface) : _scriptContents(scriptContents), @@ -58,26 +57,15 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co _numAvatarSoundSentBytes(0), _controllerScriptingInterface(controllerScriptingInterface), _avatarData(NULL), - _wantMenuItems(wantMenuItems), - _scriptMenuName(), + _scriptName(), _fileNameString(fileNameString), _quatLibrary(), _vec3Library() { - // some clients will use these menu features - if (!fileNameString.isEmpty()) { - _scriptMenuName = "Stop "; - _scriptMenuName.append(qPrintable(fileNameString)); - _scriptMenuName.append(QString(" [%1]").arg(_scriptNumber)); - } else { - _scriptMenuName = "Stop Script "; - _scriptMenuName.append(_scriptNumber); - } - _scriptNumber++; } -ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems, - AbstractControllerScriptingInterface* controllerScriptingInterface) : +ScriptEngine::ScriptEngine(const QUrl& scriptURL, + AbstractControllerScriptingInterface* controllerScriptingInterface) : _scriptContents(), _isFinished(false), _isRunning(false), @@ -92,32 +80,21 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems, _numAvatarSoundSentBytes(0), _controllerScriptingInterface(controllerScriptingInterface), _avatarData(NULL), - _wantMenuItems(wantMenuItems), - _scriptMenuName(), + _scriptName(), _fileNameString(), _quatLibrary(), _vec3Library() { QString scriptURLString = scriptURL.toString(); _fileNameString = scriptURLString; - // some clients will use these menu features - if (!scriptURLString.isEmpty()) { - _scriptMenuName = "Stop "; - _scriptMenuName.append(qPrintable(scriptURLString)); - _scriptMenuName.append(QString(" [%1]").arg(_scriptNumber)); - } else { - _scriptMenuName = "Stop Script "; - _scriptMenuName.append(_scriptNumber); - } - _scriptNumber++; - + QUrl url(scriptURL); - + // if the scheme is empty, maybe they typed in a file, let's try if (url.scheme().isEmpty()) { url = QUrl::fromLocalFile(scriptURLString); } - + // ok, let's see if it's valid... and if so, load it if (url.isValid()) { if (url.scheme() == "file") { @@ -144,16 +121,16 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems, void ScriptEngine::setIsAvatar(bool isAvatar) { _isAvatar = isAvatar; - + if (_isAvatar && !_avatarIdentityTimer) { // set up the avatar timers _avatarIdentityTimer = new QTimer(this); _avatarBillboardTimer = new QTimer(this); - + // connect our slot connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket); connect(_avatarBillboardTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarBillboardPacket); - + // start the timers _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); _avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); @@ -162,20 +139,14 @@ void ScriptEngine::setIsAvatar(bool isAvatar) { void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) { _avatarData = avatarData; - + // remove the old Avatar property, if it exists _engine.globalObject().setProperty(objectName, QScriptValue()); - + // give the script engine the new Avatar script property registerGlobalObject(objectName, _avatarData); } -void ScriptEngine::cleanupMenuItems() { - if (_wantMenuItems) { - emit cleanupMenuItem(_scriptMenuName); - } -} - bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) { if (_isRunning) { return false; @@ -203,7 +174,7 @@ void ScriptEngine::init() { registerVoxelMetaTypes(&_engine); registerEventTypes(&_engine); registerMenuItemProperties(&_engine); - + qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue); qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue); qScriptRegisterSequenceMetaType >(&_engine); @@ -216,7 +187,7 @@ void ScriptEngine::init() { QScriptValue injectionOptionValue = _engine.scriptValueFromQMetaObject(); _engine.globalObject().setProperty("AudioInjectionOptions", injectionOptionValue); - + QScriptValue localVoxelsValue = _engine.scriptValueFromQMetaObject(); _engine.globalObject().setProperty("LocalVoxels", localVoxelsValue); @@ -285,9 +256,9 @@ void ScriptEngine::run() { gettimeofday(&startTime, NULL); int thisFrame = 0; - + NodeList* nodeList = NodeList::getInstance(); - + qint64 lastUpdate = usecTimestampNow(); while (!_isFinished) { @@ -325,36 +296,36 @@ void ScriptEngine::run() { _particlesScriptingInterface.getParticlePacketSender()->process(); } } - + if (_isAvatar && _avatarData) { - + const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * SAMPLE_RATE) / (1000 * 1000)) + 0.5); const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t); - + QByteArray avatarPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarData); avatarPacket.append(_avatarData->toByteArray()); - + nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer); - + if (_isListeningToAudioStream || _avatarSound) { // if we have an avatar audio stream then send it out to our audio-mixer bool silentFrame = true; - + int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES; const int16_t* nextSoundOutput = NULL; - + if (_avatarSound) { - + const QByteArray& soundByteArray = _avatarSound->getByteArray(); nextSoundOutput = reinterpret_cast(soundByteArray.data() + _numAvatarSoundSentBytes); - + int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES ? SCRIPT_AUDIO_BUFFER_BYTES : soundByteArray.size() - _numAvatarSoundSentBytes; numAvailableSamples = numAvailableBytes / sizeof(int16_t); - - + + // check if the all of the _numAvatarAudioBufferSamples to be sent are silence for (int i = 0; i < numAvailableSamples; ++i) { if (nextSoundOutput[i] != 0) { @@ -362,7 +333,7 @@ void ScriptEngine::run() { break; } } - + _numAvatarSoundSentBytes += numAvailableBytes; if (_numAvatarSoundSentBytes == soundByteArray.size()) { // we're done with this sound object - so set our pointer back to NULL @@ -371,24 +342,24 @@ void ScriptEngine::run() { _numAvatarSoundSentBytes = 0; } } - + QByteArray audioPacket = byteArrayWithPopulatedHeader(silentFrame ? PacketTypeSilentAudioFrame : PacketTypeMicrophoneAudioNoEcho); - + QDataStream packetStream(&audioPacket, QIODevice::Append); - + // use the orientation and position of this avatar for the source of this audio packetStream.writeRawData(reinterpret_cast(&_avatarData->getPosition()), sizeof(glm::vec3)); glm::quat headOrientation = _avatarData->getHeadOrientation(); packetStream.writeRawData(reinterpret_cast(&headOrientation), sizeof(glm::quat)); - + if (silentFrame) { if (!_isListeningToAudioStream) { // if we have a silent frame and we're not listening then just send nothing and break out of here break; } - + // write the number of silent samples so the audio-mixer can uphold timing packetStream.writeRawData(reinterpret_cast(&SCRIPT_AUDIO_BUFFER_SAMPLES), sizeof(int16_t)); } else if (nextSoundOutput) { @@ -396,7 +367,7 @@ void ScriptEngine::run() { packetStream.writeRawData(reinterpret_cast(nextSoundOutput), numAvailableSamples * sizeof(int16_t)); } - + nodeList->broadcastToNodes(audioPacket, NodeSet() << NodeType::AudioMixer); } } @@ -412,10 +383,10 @@ void ScriptEngine::run() { } } emit scriptEnding(); - + // kill the avatar identity timer delete _avatarIdentityTimer; - + if (_voxelsScriptingInterface.getVoxelPacketSender()->serversExist()) { // release the queue of edit voxel messages. _voxelsScriptingInterface.getVoxelPacketSender()->releaseQueuedMessages(); @@ -435,8 +406,6 @@ void ScriptEngine::run() { _particlesScriptingInterface.getParticlePacketSender()->process(); } } - - cleanupMenuItems(); // If we were on a thread, then wait till it's done if (thread()) { @@ -444,7 +413,7 @@ void ScriptEngine::run() { } emit finished(_fileNameString); - + _isRunning = false; } @@ -454,13 +423,13 @@ void ScriptEngine::stop() { void ScriptEngine::timerFired() { QTimer* callingTimer = reinterpret_cast(sender()); - + // call the associated JS function, if it exists QScriptValue timerFunction = _timerFunctionMap.value(callingTimer); if (timerFunction.isValid()) { timerFunction.call(); } - + if (!callingTimer->isActive()) { // this timer is done, we can kill it delete callingTimer; @@ -471,14 +440,14 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int // create the timer, add it to the map, and start it QTimer* newTimer = new QTimer(this); newTimer->setSingleShot(isSingleShot); - + connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired); - + // make sure the timer stops when the script does connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop); - + _timerFunctionMap.insert(newTimer, function); - + newTimer->start(intervalMS); return newTimer; } @@ -505,17 +474,17 @@ QUrl ScriptEngine::resolveInclude(const QString& include) const { if (!url.scheme().isEmpty()) { return url; } - - // we apparently weren't a fully qualified url, so, let's assume we're relative + + // we apparently weren't a fully qualified url, so, let's assume we're relative // to the original URL of our script QUrl parentURL(_fileNameString); - + // if the parent URL's scheme is empty, then this is probably a local file... if (parentURL.scheme().isEmpty()) { parentURL = QUrl::fromLocalFile(_fileNameString); } - - // at this point we should have a legitimate fully qualified URL for our parent + + // at this point we should have a legitimate fully qualified URL for our parent url = parentURL.resolved(url); return url; } @@ -543,7 +512,7 @@ void ScriptEngine::include(const QString& includeFile) { loop.exec(); includeContents = reply->readAll(); } - + QScriptValue result = _engine.evaluate(includeContents); if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 4fc90d2959..964f64a005 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -33,11 +33,11 @@ const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 10 class ScriptEngine : public QObject { Q_OBJECT public: - ScriptEngine(const QUrl& scriptURL, bool wantMenuItems = false, + ScriptEngine(const QUrl& scriptURL, AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); - ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false, - const QString& fileNameString = QString(""), + ScriptEngine(const QString& scriptContents = NO_SCRIPT, + const QString& fileNameString = QString(""), AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); /// Access the VoxelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener @@ -49,39 +49,39 @@ public: /// sets the script contents, will return false if failed, will fail if script is already running bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString("")); - const QString& getScriptMenuName() const { return _scriptMenuName; } + const QString& getScriptName() const { return _scriptName; } void cleanupMenuItems(); void registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name - + Q_INVOKABLE void setIsAvatar(bool isAvatar); bool isAvatar() const { return _isAvatar; } - + void setAvatarData(AvatarData* avatarData, const QString& objectName); - + bool isListeningToAudioStream() const { return _isListeningToAudioStream; } void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } - + void setAvatarSound(Sound* avatarSound) { _avatarSound = avatarSound; } bool isPlayingAvatarSound() const { return _avatarSound != NULL; } - + void init(); void run(); /// runs continuously until Agent.stop() is called void evaluate(); /// initializes the engine, and evaluates the script, but then returns control to caller - + void timerFired(); bool hasScript() const { return !_scriptContents.isEmpty(); } public slots: void stop(); - + QObject* setInterval(const QScriptValue& function, int intervalMS); QObject* setTimeout(const QScriptValue& function, int timeoutMS); void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } void include(const QString& includeFile); - + signals: void update(float deltaTime); void scriptEnding(); @@ -106,19 +106,18 @@ private: QUrl resolveInclude(const QString& include) const; void sendAvatarIdentityPacket(); void sendAvatarBillboardPacket(); - + QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); void stopTimer(QTimer* timer); - + static VoxelsScriptingInterface _voxelsScriptingInterface; static ParticlesScriptingInterface _particlesScriptingInterface; static int _scriptNumber; - + AbstractControllerScriptingInterface* _controllerScriptingInterface; AudioScriptingInterface _audioScriptingInterface; AvatarData* _avatarData; - bool _wantMenuItems; - QString _scriptMenuName; + QString _scriptName; QString _fileNameString; Quat _quatLibrary; Vec3 _vec3Library;