mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-05 05:07:48 +00:00
GEEKING
This commit is contained in:
@@ -0,0 +1,994 @@
|
||||
/**
|
||||
* RobloxScriptDoc.cpp
|
||||
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "RobloxScriptDoc.h"
|
||||
|
||||
// Qt Headers
|
||||
#include <QTextStream>
|
||||
#include <QClipboard>
|
||||
#include <QFileInfo>
|
||||
|
||||
// 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<QMenu*>("breakErrorsIntoDebuggerMenu");
|
||||
if (pMenu)
|
||||
delete pMenu;
|
||||
}
|
||||
|
||||
qRegisterMetaType<RBX::ScriptAnalyzer::Result>("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<RBX::LuaSourceContainer> 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<RBX::DataModel> dm, LuaSourceBuffer lsb,
|
||||
bool* weHaveTheLock, std::string* nameOfPlayerWhoOwnsTheLock, std::string* oldSource)
|
||||
{
|
||||
using namespace RBX;
|
||||
|
||||
CEvent cevent(true);
|
||||
shared_ptr<LuaSourceContainer> 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<Network::ClientReplicator>();
|
||||
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<bool> 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<RBX::DataModel>(), 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<QWidget*>(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<RBX::DataModel> 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<RBX::LuaSourceContainer> 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<RBX::Instance> 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<RBX::Instance> 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<RBX::DataModel>(), 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<bool> 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<RBX::LuaSourceContainer> 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<QWidget*>("scriptMenu");
|
||||
if (scriptMenu)
|
||||
{
|
||||
if (mainWindow.isRibbonStyle())
|
||||
{
|
||||
scriptMenu->setVisible(visible);
|
||||
RobloxRibbonMainWindow& ribbonMainWindow = static_cast<RobloxRibbonMainWindow&>(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<QMenu*>(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<RBX::DataModel> dataModel = ideDoc->getEditDataModel();
|
||||
// if edit mode use the current script else for play solo get the corresponding script in Edit datamodel
|
||||
boost::shared_ptr<RBX::LuaSourceContainer> lsc = ideDoc->isPlaySolo() ? RBX::Instance::fastSharedDynamicCast<RBX::LuaSourceContainer>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user