/** * RobloxScriptDoc.cpp * Copyright (c) 2013 ROBLOX Corp. All Rights Reserved. */ #include "stdafx.h" #include "RobloxScriptDoc.h" // Qt Headers #include #include #include // 3rd Party Headers #include "boost/iostreams/copy.hpp" // Roblox Headers #include "script/script.h" #include "Script/ScriptContext.h" #include "util/ProtectedString.h" #include "Util/ScopedAssign.h" #include "v8datamodel/ContentProvider.h" #include "v8datamodel/DataModel.h" #include "v8datamodel/Selection.h" #include "V8Tree/Service.h" #include "Client.h" #include "ClientReplicator.h" #include "Network/Players.h" // Roblox Studio Headers #include "AuthoringSettings.h" #include "DebuggerClient.h" #include "DebuggerWidgets.h" #include "FindDialog.h" #include "LuaSourceBuffer.h" #include "QtUtilities.h" #include "RobloxMainWindow.h" #include "RobloxDocManager.h" #include "RobloxIDEDoc.h" #include "ScriptTextEditor.h" #include "UpdateUIManager.h" #include "ScriptAnalysisWidget.h" FASTFLAG(LuaDebugger) FASTFLAG(LuaDebuggerBreakOnError) FASTFLAG(StudioEmbeddedFindDialogEnabled) FASTFLAG(StudioSeparateActionByActivationMethod) FASTFLAGVARIABLE(CloudEditRemoveScriptLockOnDocClose, false) int RobloxScriptDoc::sScriptCount = 1; int RobloxScriptDoc::slastActivatedRibbonPage = -1; RobloxScriptDoc::RobloxScriptDoc() : m_pMainWindow(NULL) , m_pParentDoc(NULL) , m_pTextEdit(NULL) , m_displayName(QString("Script%1").arg(sScriptCount)) , m_undoAvailable(false) , m_redoAvailable(false) , m_copyAvailable(false) , m_updatingText(false) , m_currentlyAttemptingToGetScriptLock(false) , m_haveCloudEditScriptLock(false) { // get a temporarily name that we're going to overwrite when the script changes. // the doc manager needs to have some way to refer to this doc while configuring. m_keyName = QString::number((int)this); } RobloxScriptDoc::~RobloxScriptDoc() { } void RobloxScriptDoc::init(RobloxMainWindow& mainWindow) { setMenuVisibility(false); if (FFlag::LuaDebugger && (mainWindow.getBuildMode() != BM_BASIC)) DebuggerViewsManager::Instance(); if (!FFlag::LuaDebuggerBreakOnError || (mainWindow.getBuildMode() == BM_BASIC)) { FFlag::LuaDebuggerBreakOnError = false; QMenu* pMenu = UpdateUIManager::Instance().getMainWindow().findChild("breakErrorsIntoDebuggerMenu"); if (pMenu) delete pMenu; } qRegisterMetaType("RBX::ScriptAnalyzer::Result"); } QString RobloxScriptDoc::fileName() const { if ( m_pParentDoc ) return m_pParentDoc->fileName(); return QFileInfo(m_fileName).fileName(); } bool RobloxScriptDoc::open(RobloxMainWindow *pMainWindow, const QString &fileName) { bool success = true; try { m_pMainWindow = pMainWindow; // create an editor m_pTextEdit = new ScriptTextEditor(this, m_pMainWindow); if (RobloxIDEDoc::getIsCloudEditSession()) { connect(m_pTextEdit, SIGNAL(textChanged()), this, SLOT(editingContents())); } if (!fileName.isEmpty()) { QFile file(fileName); if (!file.open(QFile::ReadOnly | QFile::Text)) return false; //loading QTextStream in(&file); m_pTextEdit->setPlainText(in.readAll()); m_fileName = fileName; } else //open a new script file sScriptCount++; } catch (...) { success = false; } return success; } static void receiveStateAndSetEvent(bool value, shared_ptr lsc, bool* state, std::string* ownerOfLock, RBX::CEvent* event) { *state = value; if (RBX::Instance* currentEditor = lsc->getCurrentEditor()) { *ownerOfLock = currentEditor->getName(); } event->Set(); } static void disconnected(bool* state, RBX::CEvent* event) { *state = false; event->Set(); } static void attemptToLockScript(shared_ptr dm, LuaSourceBuffer lsb, bool* weHaveTheLock, std::string* nameOfPlayerWhoOwnsTheLock, std::string* oldSource) { using namespace RBX; CEvent cevent(true); shared_ptr lsc; rbx::signals::scoped_connection conn1, conn2; { DataModel::LegacyLock l(dm, DataModelJob::Write); *oldSource = lsb.getScriptText(); lsc = lsb.toInstance(); if (!lsc) // Linked source { *weHaveTheLock = true; return; } Instance* localPlayer = Network::Players::findLocalPlayer(dm.get()); if (!localPlayer) { StandardOut::singleton()->print(MESSAGE_ERROR, "Unable to find local player when trying to lock script for editing"); *weHaveTheLock = false; return; } Instance* currentEditor = lsc->getCurrentEditor(); if (currentEditor != NULL) { *weHaveTheLock = currentEditor == localPlayer; *nameOfPlayerWhoOwnsTheLock = currentEditor->getName(); return; } // handle disconnect Network::Client* client = Network::Client::findClient(dm.get()); if (!client) { *weHaveTheLock = false; return; } Network::ClientReplicator* rep = client->findFirstChildOfType(); if (!rep) { *weHaveTheLock = false; return; } conn1 = rep->disconnectionSignal.connect(boost::bind(&disconnected, weHaveTheLock, &cevent)); conn2 = lsc->lockGrantedOrNot.connect(boost::bind(&receiveStateAndSetEvent, _1, lsc, weHaveTheLock, nameOfPlayerWhoOwnsTheLock, &cevent)); LuaSourceContainer::event_requestLock.replicateEvent(lsc.get()); } cevent.Wait(); } void RobloxScriptDoc::editingContents() { RobloxIDEDoc* ideDoc = RobloxDocManager::Instance().getPlayDoc(); RBXASSERT(ideDoc && ideDoc->isCloudEditSession()); if (m_updatingText || m_currentlyAttemptingToGetScriptLock || ideDoc == NULL) { return; } bool isOverlaySimulation = ideDoc->getDataModel() != ideDoc->getEditDataModel().get(); if (isOverlaySimulation) { QtUtilities::RBXMessageBox warningBox; warningBox.setText("WARNING: You are in testing mode, any changes you make will be lost when you press the stop button"); warningBox.exec(); } else if (!m_haveCloudEditScriptLock) { RBX::ScopedAssign sa(m_currentlyAttemptingToGetScriptLock, true); std::string nameOfPlayerWhoOwnsTheLock; std::string oldSource; UpdateUIManager::Instance().waitForLongProcess(QString("Locking script..."), boost::bind(&attemptToLockScript, m_dataModel, m_script, &m_haveCloudEditScriptLock, &nameOfPlayerWhoOwnsTheLock, &oldSource)); if (!m_haveCloudEditScriptLock) { // restore old script onScriptSourcePropertyChanged(QString::fromStdString(oldSource)); // notify user QtUtilities::RBXMessageBox messageBox; messageBox.setStandardButtons(QMessageBox::Ok); if (nameOfPlayerWhoOwnsTheLock.empty()) { messageBox.setText("Cannot gain write access to script, reverting your change"); } else { messageBox.setText(QString::fromStdString(nameOfPlayerWhoOwnsTheLock + " is currently modifying this script, your changes will be reverted")); } messageBox.exec(); } } } IRobloxDoc::RBXCloseRequest RobloxScriptDoc::requestClose() { return IRobloxDoc::NO_SAVE_NEEDED; } bool RobloxScriptDoc::doClose() { deActivate(); FindReplaceProvider::instance().setFindListener(NULL); if (FFlag::StudioEmbeddedFindDialogEnabled) { FindReplaceProvider::instance().getEmbeddedFindDialog()->hide(); } else { FindReplaceProvider::instance().getFindDialog()->hide(); FindReplaceProvider::instance().getReplaceDialog()->hide(); } //this will get deleted once the data model gets removed if (DebuggerClient *pLuaDebuggerClient = DebuggerClientManager::Instance().getDebuggerClient(m_script.toInstance())) pLuaDebuggerClient->setDocument(NULL); m_pTextEdit->deleteLater(); setScript(shared_ptr(), LuaSourceBuffer()); //self deletion (maybe we must use reference counting!) deleteLater(); if ( m_pParentDoc ) RobloxDocManager::Instance().setCurrentDoc(m_pParentDoc); return true; } bool RobloxScriptDoc::isModified() { if (m_pParentDoc) return false; return m_pTextEdit->document()->isModified(); } const QIcon& RobloxScriptDoc::titleIcon() const { static QIcon empty; static QIcon cloud; if (cloud.isNull()) cloud.addFile(QString::fromUtf8(":/images/onlinePlaceIcon.png"), QSize(), QIcon::Normal, QIcon::Off); if (m_script.empty() || m_script.toInstance()) { return empty; } else { return cloud; } } bool RobloxScriptDoc::save() { if (m_pParentDoc) { applyEditChanges(); return m_pParentDoc->save(); } return saveAs(m_fileName); } bool RobloxScriptDoc::saveAs(const QString &fileName) { if (fileName.isEmpty()) return false; if (m_pParentDoc) { applyEditChanges(); m_pParentDoc->saveAs(fileName); return false;//intentional } QString _fileName(fileName); if (!_fileName.endsWith(".lua", Qt::CaseInsensitive)) _fileName.append(".lua"); //doSave QFile file(_fileName); if (!file.open(QFile::WriteOnly | QFile::Text)) return false; try { QTextStream out(&file); out << m_pTextEdit->toPlainText(); m_fileName = _fileName; RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Successfully saved at %s", qPrintable(_fileName)); } catch (...) { RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Error in saving document at %s", qPrintable(_fileName)); return false; } return true; } QString RobloxScriptDoc::saveFileFilters() { if (m_pParentDoc) return m_pParentDoc->saveFileFilters(); return "Lua Script Files (*.lua)"; } QWidget* RobloxScriptDoc::getViewer() { return static_cast(m_pTextEdit); } void RobloxScriptDoc::activate() { if (m_bActive) return; m_bActive = true; if (DebuggerClient *pLuaDebuggerClient = DebuggerClientManager::Instance().getDebuggerClient(m_script.toInstance())) pLuaDebuggerClient->activate(); // Init Find Replace dialogs FindReplaceProvider::instance().setFindListener(m_pTextEdit); //update toolbars UpdateUIManager::Instance().updateToolBars(); this->setupScriptConnection(); m_pTextEdit->setFocus(); } void RobloxScriptDoc::deActivate() { if (!m_bActive) return; m_bActive = false; applyEditChanges(); this->disconnectScriptConnection(); if (DebuggerClient *pLuaDebuggerClient = DebuggerClientManager::Instance().getDebuggerClient(m_script.toInstance())) pLuaDebuggerClient->deActivate(); } void RobloxScriptDoc::updateUI(bool state) { setMenuVisibility(state); } bool RobloxScriptDoc::handleDrop(const QString &fileName) { if(fileName.isEmpty()) return false; if(!m_pParentDoc) return false; return m_pParentDoc->handleDrop(fileName); } bool RobloxScriptDoc::doHandleAction(const QString& actionID, bool isChecked) { bool handled = false; const QString zoomActions = "zoomInAction zoomOutAction"; // let script text editor have a chance first to handle the action if ((m_bActive || zoomActions.contains(actionID)) && m_pTextEdit->doHandleAction(actionID) ) return true; // All Actions that we do not want to be handled by the Parent RobloxIDEDoc when we are in Script Doc QKeySequence keySequence; if (QAction* action = UpdateUIManager::Instance().getMainWindow().getActionByName(actionID)) keySequence = action->shortcut(); if (keySequence.matches(QKeySequence::MoveToNextPage) == QKeySequence::ExactMatch) { if (m_pTextEdit) m_pTextEdit->keyPressEvent(new QKeyEvent(QEvent::KeyPress, Qt::Key_PageDown, 0)); handled = true; } if (keySequence.matches(QKeySequence::MoveToPreviousPage) == QKeySequence::ExactMatch) { if (m_pTextEdit) m_pTextEdit->keyPressEvent(new QKeyEvent(QEvent::KeyPress, Qt::Key_PageUp, 0)); handled = true; } if (actionID == "reloadScriptAction") { reloadLiveScript(); handled = true; } else if ( actionID == "actionCustomContextMenuHide" ) { if ( m_pMainWindow->treeWidgetStack().currentWidget()->hasFocus() ) setupScriptConnection(); handled = true; } else if ( actionID == "actionCustomContextMenuShow" ) { if ( m_pMainWindow->treeWidgetStack().currentWidget()->hasFocus() ) { QTextCursor textCursor = m_pTextEdit->textCursor(); textCursor.clearSelection(); m_pTextEdit->setTextCursor(textCursor); disconnectScriptConnection(); m_pMainWindow->copyAction->setEnabled(true); m_pMainWindow->cutAction->setEnabled(true); m_pMainWindow->undoAction->setEnabled(true); m_pMainWindow->redoAction->setEnabled(true); QClipboard* pClipboard = QApplication::clipboard(); const QMimeData* pMimeData = pClipboard->mimeData(); if ( pMimeData && pMimeData->hasFormat("application/x-roblox-studio") ) m_pMainWindow->pasteIntoAction->setEnabled(true); } handled = true; } if (m_pParentDoc && !handled) { // Delegate all script enabled actions to parent RobloxIDEDoc & give parent the chance to handle handled = m_pParentDoc->doHandleAction(actionID,isChecked); } if (FFlag::LuaDebugger && !handled) { DebuggerClient *pLuaDebuggerClient = DebuggerClientManager::Instance().getDebuggerClient(m_script.toInstance()); if (pLuaDebuggerClient) handled = pLuaDebuggerClient->handleAction(actionID,isChecked); } return handled; } void RobloxScriptDoc::handleScriptCommand(const QString &execCommand) { if(m_pParentDoc) { m_pParentDoc->handleScriptCommand(execCommand); } } bool RobloxScriptDoc::actionState(const QString &actionID, bool &enableState, bool &checkedState) { static QString disabledActions = "rotateSelectionAction"; if (disabledActions.contains(actionID)) { enableState = false; return true; } if (!m_bActive && actionID == "goToLineAction") { enableState = false; return true; } const QString zoomActions = "zoomInAction zoomOutAction"; // let script text editor handle first if ((m_bActive || zoomActions.contains(actionID)) && m_pTextEdit->actionState(actionID,enableState,checkedState) ) return true; static QString scriptActiveActions = "fileSaveAction fileSaveAsAction fileCloseAction toggleCommentAction goToLineAction " "findAction replaceAction expandAllFoldsAction collapseAllFoldsAction resetScriptZoomAction actionFindMenu actionSelectMenu "; if ( scriptActiveActions.contains(actionID) ) { enableState = true; checkedState = false; return true; } if (actionID == "reloadScriptAction") { if ((RobloxDocManager::Instance().getPlayDoc() && RobloxDocManager::Instance().getPlayDoc()->isSimulating()) || m_script.isModuleScript()) { enableState = true; checkedState = false; return true; } } if (UpdateUIManager::Instance().getDockActionNames().contains(actionID)) { enableState = true; checkedState = UpdateUIManager::Instance().getAction(actionID)->isChecked(); return true; } /// Anything that needs to be handled by RobloxIDEDoc goes in here, the enabled/checked state is all delegated to parent. static QString delegateToParentActions( "saveToRobloxAction insertIntoFileAction selectionSaveToFileAction playSoloAction " "startServerAction startPlayerAction publishToRobloxAction publishToRobloxAsAction " "publishAsPluginAction createNewLinkedSourceAction startServerAndPlayersAction startServerCB playersMode findInScriptsAction explorerFilterAction"); static QString explorerActionsEnabledInScriptView("copyAction cutAction pasteAction undoAction redoAction " "deleteSelectedAction groupSelectionAction ungroupSelectionAction duplicateSelectionAction " "selectAllAction selectChildrenAction pasteIntoAction renameObjectAction launchHelpForSelectionAction"); if(m_pParentDoc && (delegateToParentActions.contains(actionID) || explorerActionsEnabledInScriptView.contains(actionID))) return m_pParentDoc->actionState(actionID, enableState, checkedState); //once we remove the fast flag, move the following actions to delegateToParentActions if (FFlag::LuaDebugger) { static QString simulationActions("simulationRunAction simulationPlayAction simulationStopAction simulationResetAction " "simulationRunSelectorSim simulationRunSelectorDebug simulationRunSelectorTest" ); if(m_pParentDoc && simulationActions.contains(actionID)) return m_pParentDoc->actionState(actionID, enableState, checkedState); DebuggerClient *pLuaDebuggerClient = DebuggerClientManager::Instance().getDebuggerClient(m_script.toInstance()); if (pLuaDebuggerClient && pLuaDebuggerClient->actionState(actionID, enableState, checkedState)) return true; } enableState = false; checkedState = false; return true; } void RobloxScriptDoc::setupScriptConnection() { m_pTextEdit->setUndoRedoEnabled(true); m_pMainWindow->copyAction->setEnabled(m_copyAvailable); m_pMainWindow->cutAction->setEnabled(m_copyAvailable); m_pMainWindow->undoAction->setEnabled(m_undoAvailable); m_pMainWindow->redoAction->setEnabled(m_redoAvailable); //set copy and cut to be available when texts are selected connect(m_pTextEdit, SIGNAL(copyAvailable(bool)), m_pMainWindow->cutAction, SLOT(setEnabled(bool))); connect(m_pTextEdit, SIGNAL(copyAvailable(bool)), m_pMainWindow->copyAction, SLOT(setEnabled(bool))); //set undo/redo to be available when there are changes. connect(m_pTextEdit, SIGNAL(undoAvailable(bool)), m_pMainWindow->undoAction, SLOT(setEnabled(bool))); connect(m_pTextEdit, SIGNAL(redoAvailable(bool)), m_pMainWindow->redoAction, SLOT(setEnabled(bool))); connect(m_pTextEdit, SIGNAL(selectionChanged()), this, SLOT(onSelectionChanged())); this->activateConnection(); } void RobloxScriptDoc::setScript(shared_ptr dm, LuaSourceBuffer script) { if (m_script == script) return; if (!m_script.empty()) { m_ancestryChangedConnection.disconnect(); m_propertyChangedConnection.disconnect(); disconnect(m_pTextEdit.get(), SIGNAL(toggleBreakpoint(int)), this, SLOT(explainBreakpointsDisabled())); if (m_haveCloudEditScriptLock && m_dataModel && script.empty()) { if (!FFlag::CloudEditRemoveScriptLockOnDocClose) { RBX::DataModel::LegacyLock lock(m_dataModel, RBX::DataModelJob::Write); if (shared_ptr lsc = m_script.toInstance()) { RBX::Network::Player* localPlayer = RBX::Network::Players::findLocalPlayer(m_dataModel.get()); if (localPlayer && lsc->getCurrentEditor() == localPlayer) { lsc->setCurrentEditor(NULL); lsc->fireSourceChanged(); m_haveCloudEditScriptLock = false; } } } else { removeScriptLock(); } } m_script = LuaSourceBuffer(); m_dataModel.reset(); m_pParentDoc = NULL; RobloxDocManager::Instance().deregisterScriptDoc(*this); } if (!script.empty()) // adding an associated script instance { m_script = script; m_pParentDoc = RobloxDocManager::Instance().getPlayDoc(); m_dataModel = dm; if (script.toInstance() == NULL) { connect(m_pTextEdit.get(), SIGNAL(toggleBreakpoint(int)), this, SLOT(explainBreakpointsDisabled())); } RBX::DataModel::LegacyLock lock(m_dataModel, RBX::DataModelJob::Write); if (shared_ptr inst = script.toInstance()) { m_ancestryChangedConnection = inst->ancestryChangedSignal.connect(boost::bind(&RobloxScriptDoc::onAncestryChanged, this, _2)); m_propertyChangedConnection = inst->propertyChangedSignal.connect(boost::bind(&RobloxScriptDoc::onPropertyChanged, this, _1)); } onScriptNamePropertyChanged(buildTabTitle()); onScriptSourcePropertyChanged(QString::fromStdString(m_script.getScriptText())); RobloxDocManager::Instance().registerOpenedScriptDoc(*this,m_script); if (FFlag::LuaDebugger && m_pMainWindow->getBuildMode() != BM_BASIC) { //create if (DebuggerClient *pLuaDebuggerClient = DebuggerClientManager::Instance().getOrCreateDebuggerClient(m_script.toInstance())) { pLuaDebuggerClient->setDocument(this); if (pLuaDebuggerClient && m_bActive) pLuaDebuggerClient->activate(); } } // need to update actions once we set the script pointer UpdateUIManager::Instance().updateToolBars(); // title icon can change based on the value of m_script, so update doc title RobloxDocManager::Instance().setDocTitle(*this, displayName(), titleTooltip(), titleIcon()); } } void RobloxScriptDoc::onAncestryChanged(shared_ptr newParent) { if (newParent==NULL) { if (this->thread() == QThread::currentThread()) requestScriptDeletion(); else QTimer::singleShot(0, this, SLOT(requestScriptDeletion())); } } void RobloxScriptDoc::requestScriptDeletion() { if (!m_script.empty()) RobloxDocManager::Instance().closeDoc(m_script); // Set script to NULL because it is no longer valid setScript(shared_ptr(), LuaSourceBuffer()); } void RobloxScriptDoc::onPropertyChanged(const RBX::Reflection::PropertyDescriptor* desc) { RBXASSERT(!m_dataModel || m_dataModel->currentThreadHasWriteLock()); if (m_script.empty() || !m_pTextEdit) { return; } if (*desc==RBX::Instance::desc_Name || desc->name == "CurrentEditor") { QMetaObject::invokeMethod(this, "onScriptNamePropertyChanged", Qt::QueuedConnection, Q_ARG(QString, buildTabTitle())); } else if ((*desc==RBX::Script::prop_EmbeddedSourceCode) || (*desc==RBX::ModuleScript::prop_Source)) { QMetaObject::invokeMethod(this, "onScriptSourcePropertyChanged", Qt::QueuedConnection, Q_ARG(QString, QString::fromStdString(m_script.getScriptText()))); } else if (desc->name == "LinkedSource") { QTimer::singleShot(0, this, SLOT(requestScriptDeletion())); } } void RobloxScriptDoc::onScriptNamePropertyChanged(const QString& newName) { m_displayName = newName; RobloxDocManager::Instance().setDocTitle(*this, displayName(), titleTooltip(), titleIcon()); } void RobloxScriptDoc::onScriptSourcePropertyChanged(const QString& newSource) { RBX::ScopedAssign sa(m_updatingText, true); maybeUpdateText(newSource); } void RobloxScriptDoc::maybeUpdateText(const QString& code) { if (m_script.empty() || !m_pTextEdit) return; QString currentText = m_pTextEdit->toPlainText(); if (code != currentText) m_pTextEdit->setPlainText(code); } void RobloxScriptDoc::refreshEditorFromSourceBuffer() { QString source; { RBX::DataModel::LegacyLock l(m_dataModel, RBX::DataModelJob::Write); source = QString::fromStdString(m_script.getScriptText()); } onScriptSourcePropertyChanged(source); } void RobloxScriptDoc::applyEditChanges() { if (m_script.empty() || !m_dataModel) return; if (RobloxIDEDoc::getIsCloudEditSession() && !m_haveCloudEditScriptLock) { return; } if (m_pTextEdit->document()->isModified() && !m_updatingText) { m_pTextEdit->document()->setModified(false); if (RobloxDocManager::Instance().getPlayDoc() && RobloxDocManager::Instance().getPlayDoc()->isSimulating()) { RobloxDocManager::Instance().getPlayDoc()->notifyScriptEdited(getCurrentScript().toInstance()); } RBX::DataModel::LegacyLock lock(m_dataModel, RBX::DataModelJob::Write); QString currentText = m_pTextEdit->toPlainText(); m_script.setScriptText(currentText.toStdString()); m_pMainWindow->m_editScriptActions.increment(); } } LuaSourceBuffer RobloxScriptDoc::getCurrentScript() { return m_script; } void RobloxScriptDoc::disconnectScriptConnection() { m_copyAvailable = m_pMainWindow->copyAction->isEnabled(); m_undoAvailable = m_pMainWindow->undoAction->isEnabled(); m_redoAvailable = m_pMainWindow->redoAction->isEnabled(); disconnect(m_pTextEdit, SIGNAL(copyAvailable(bool)), m_pMainWindow->cutAction, SLOT(setEnabled(bool))); disconnect(m_pTextEdit, SIGNAL(copyAvailable(bool)), m_pMainWindow->copyAction, SLOT(setEnabled(bool))); disconnect(m_pTextEdit, SIGNAL(undoAvailable(bool)), m_pMainWindow->undoAction, SLOT(setEnabled(bool))); disconnect(m_pTextEdit, SIGNAL(redoAvailable(bool)), m_pMainWindow->redoAction, SLOT(setEnabled(bool))); disconnect(m_pTextEdit, SIGNAL(selectionChanged()), this, SLOT(onSelectionChanged())); this->deactivateConnection(); } void RobloxScriptDoc::deactivateConnection() { //connect the edit tools for script editor with QTextEdit public slots. if (!FFlag::StudioSeparateActionByActivationMethod) { disconnect(m_pMainWindow->cutAction, SIGNAL(triggered()), m_pTextEdit, SLOT(cut())); disconnect(m_pMainWindow->copyAction, SIGNAL(triggered()), m_pTextEdit, SLOT(copy())); disconnect(m_pMainWindow->pasteAction, SIGNAL(triggered()), m_pTextEdit, SLOT(paste())); disconnect(m_pMainWindow->undoAction, SIGNAL(triggered()), m_pTextEdit, SLOT(undo())); disconnect(m_pMainWindow->redoAction, SIGNAL(triggered()), m_pTextEdit, SLOT(redo())); } disconnect(UpdateUIManager::Instance().getExplorerWidget(), SIGNAL(focusGained()), this, SLOT(deActivate())); } void RobloxScriptDoc::activateConnection() { //connect the edit tools for script editor with QTextEdit public slots. if (!FFlag::StudioSeparateActionByActivationMethod) { connect(m_pMainWindow->cutAction, SIGNAL(triggered()), m_pTextEdit, SLOT(cut())); connect(m_pMainWindow->copyAction, SIGNAL(triggered()), m_pTextEdit, SLOT(copy())); connect(m_pMainWindow->pasteAction, SIGNAL(triggered()), m_pTextEdit, SLOT(paste())); connect(m_pMainWindow->undoAction, SIGNAL(triggered()), m_pTextEdit, SLOT(undo())); connect(m_pMainWindow->redoAction, SIGNAL(triggered()), m_pTextEdit, SLOT(redo())); } connect(UpdateUIManager::Instance().getExplorerWidget(), SIGNAL(focusGained()), this, SLOT(deActivate())); } QString RobloxScriptDoc::buildTabTitle() { QString tabTitle = QString::fromStdString(m_script.getScriptName()); if (shared_ptr lsc = m_script.toInstance()) { if (RBX::Instance* editor = lsc->getCurrentEditor()) { tabTitle += QString(" (%1 Editing)").arg(QString::fromStdString(editor->getName())); } } return tabTitle; } void RobloxScriptDoc::setMenuVisibility(bool visible) { if (!visible) { FindReplaceProvider::instance().setFindListener(NULL); if (FFlag::StudioEmbeddedFindDialogEnabled) { FindReplaceProvider::instance().getEmbeddedFindDialog()->hide(); } else { FindReplaceProvider::instance().getFindDialog()->hide(); FindReplaceProvider::instance().getReplaceDialog()->hide(); } } RobloxMainWindow& mainWindow = UpdateUIManager::Instance().getMainWindow(); QWidget* scriptMenu = mainWindow.findChild("scriptMenu"); if (scriptMenu) { if (mainWindow.isRibbonStyle()) { scriptMenu->setVisible(visible); RobloxRibbonMainWindow& ribbonMainWindow = static_cast(mainWindow); if (visible) { slastActivatedRibbonPage = ribbonMainWindow.ribbonBar()->currentIndexPage(); ribbonMainWindow.ribbonBar()->setAllowPopups(false); ribbonMainWindow.ribbonBar()->setCurrentPage(scriptMenu->property("index").toInt()); ribbonMainWindow.ribbonBar()->setAllowPopups(true); } else if (slastActivatedRibbonPage >= 0) { ribbonMainWindow.ribbonBar()->setAllowPopups(false); ribbonMainWindow.ribbonBar()->setCurrentPage(slastActivatedRibbonPage); ribbonMainWindow.ribbonBar()->setAllowPopups(true); } } else { QMenu* pMenu = dynamic_cast(scriptMenu); RBXASSERT(pMenu); if (pMenu) pMenu->menuAction()->setVisible(visible); } } } void RobloxScriptDoc::onSelectionChanged() { // update actions modified on selection change UpdateUIManager& updateUIManager = UpdateUIManager::Instance(); RobloxMainWindow& rbxMainWindow = updateUIManager.getMainWindow(); if (rbxMainWindow.isRibbonStyle()) { updateUIManager.updateAction(*(rbxMainWindow.commentSelectionAction)); updateUIManager.updateAction(*(rbxMainWindow.uncommentSelectionAction)); } } void RobloxScriptDoc::reloadLiveScript() { if (!m_pParentDoc || (!m_pParentDoc->isSimulating() && !m_script.isModuleScript())) return; applyEditChanges(); try { RBX::DataModel::LegacyLock lock(m_dataModel, RBX::DataModelJob::Write); m_script.reloadLiveScript(); } catch (std::exception& e) { RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e.what()); } } void RobloxScriptDoc::explainBreakpointsDisabled() { QMessageBox mb; mb.setText("Breakpoints are currently disabled for LinkedSource."); mb.exec(); } void RobloxScriptDoc::removeScriptLock() { RobloxIDEDoc* ideDoc = RobloxDocManager::Instance().getPlayDoc(); RBXASSERT(ideDoc && ideDoc->isCloudEditSession()); boost::shared_ptr dataModel = ideDoc->getEditDataModel(); // if edit mode use the current script else for play solo get the corresponding script in Edit datamodel boost::shared_ptr lsc = ideDoc->isPlaySolo() ? RBX::Instance::fastSharedDynamicCast(ideDoc->getEditScriptByPlayInstance(m_script.toInstance().get())) : m_script.toInstance(); if (lsc) { RBX::DataModel::LegacyLock lock(dataModel, RBX::DataModelJob::Write); RBX::Network::Player* localPlayer = RBX::Network::Players::findLocalPlayer(dataModel.get()); if (localPlayer && lsc->getCurrentEditor() == localPlayer) { lsc->setCurrentEditor(NULL); lsc->fireSourceChanged(); m_haveCloudEditScriptLock = false; } } }