Files
2025-09-18 17:55:52 -04:00

561 lines
13 KiB
C++

/**
* RobloxDocManager.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "RobloxDocManager.h"
// Qt Headers
#include <QString>
#include <QMessageBox>
// Roblox Headers
#include "Script/LuaSourceContainer.h"
#include "Util/standardout.h"
#include "Util/ContentId.h"
#include "V8DataModel/PluginManager.h"
// Roblox Studio Headers
#include "DocDockManager.h"
#include "DocTabManager.h"
#include "LuaSourceBuffer.h"
#include "RobloxMainWindow.h"
#include "RobloxGameExplorer.h"
#include "RobloxIDEDoc.h"
#include "RobloxScriptDoc.h"
#include "RobloxWebDoc.h"
#include "RobloxObjectBrowserDoc.h"
#include "UpdateUIManager.h"
#include "ScriptTextEditor.h"
RobloxDocManager& RobloxDocManager::Instance()
{
static RobloxDocManager singleInstance;
return singleInstance;
}
RobloxDocManager::RobloxDocManager()
: m_pMainWindow(NULL)
, m_pCurrentDoc(NULL)
, m_pPlayDoc(NULL)
, m_pObjectBrowser(NULL)
, m_pWebDoc(NULL)
, m_DocTabManager(NULL)
, m_DocDockManager(NULL)
{
}
RobloxDocManager::~RobloxDocManager()
{
}
/**
* Only call initialize once.
*/
void RobloxDocManager::initialize(RobloxMainWindow& mainWindow)
{
RBXASSERT(!m_pMainWindow);
m_pMainWindow = &mainWindow;
m_DocTabManager = new DocTabManager(mainWindow);
m_DocDockManager = new DocDockManager(mainWindow);
}
void RobloxDocManager::shutDown()
{
m_pCurrentDoc = NULL;
m_pPlayDoc = NULL;
m_KeyDocMap.clear();
delete m_DocTabManager;
delete m_DocDockManager;
if(m_pObjectBrowser)
delete m_pObjectBrowser;
if(m_pWebDoc)
delete m_pWebDoc;
m_DocTabManager = NULL;
m_DocDockManager = NULL;
m_pMainWindow = NULL;
}
/**
* Attempt to close all docs.
* Any failure to close aborts the operation.
*
* @return true if all docs closed successfully
*/
bool RobloxDocManager::requestCloseAllDocs()
{
while ( !m_KeyDocMap.empty() )
{
RBX::BaldPtr<IRobloxDoc> doc = m_KeyDocMap.begin().value();
if ( !m_pMainWindow->requestDocClose(doc) )
return false;
}
return true;
}
/**
* Attempts to save and close all docs.
* Any failure to save will abort the process.
*
* @return true if all docs were saved successfully
*/
bool RobloxDocManager::requestCloseAndSaveAllDocs()
{
while ( !m_KeyDocMap.empty() )
{
RBX::BaldPtr<IRobloxDoc> doc = m_KeyDocMap.begin().value();
if ( !m_pMainWindow->fileSave(doc) )
return false;
removeDoc(*doc);
}
return true;
}
/**
* Closes all docs without saving.
*/
void RobloxDocManager::closeAllDocs()
{
while ( !m_KeyDocMap.empty() )
{
RBX::BaldPtr<IRobloxDoc> doc = m_KeyDocMap.begin().value();
removeDoc(*doc);
}
}
/**
* Registers a script doc.
* Used for attempting to reopen scripts.
*/
void RobloxDocManager::registerOpenedScriptDoc(RobloxScriptDoc& doc, LuaSourceBuffer script)
{
RBXASSERT(!m_OpenScriptDocs.contains(&doc));
m_OpenScriptDocs.push_back(&doc);
}
/**
* Unregisters a script doc.
* Used for attempting to reopen scripts.
*/
void RobloxDocManager::deregisterScriptDoc(RobloxScriptDoc& doc)
{
RBXASSERT(m_OpenScriptDocs.contains(&doc));
m_OpenScriptDocs.removeAll(&doc);
}
/**
* Returns a handle to the ObjectBrowser, or NULL if it hasnt been created.
*/
RobloxObjectBrowserDoc* RobloxDocManager::getObjectBrowserDoc()
{
return m_pObjectBrowser;
}
RobloxScriptDoc* RobloxDocManager::findOpenScriptDoc(LuaSourceBuffer script)
{
for (tScriptDocs::iterator itr = m_OpenScriptDocs.begin(); itr != m_OpenScriptDocs.end(); ++itr)
{
if ((*itr)->getCurrentScript() == script)
{
return *itr;
}
}
return NULL;
}
/**
* Creates a doc or returns an existing doc of the given type.
* The Object Browser and Start Page get created only once.
*/
IRobloxDoc* RobloxDocManager::getOrCreateDoc(IRobloxDoc::RBXDocType docType)
{
RBX::BaldPtr<IRobloxDoc> pDoc = NULL;
switch ( docType )
{
case IRobloxDoc::IDE:
{
m_pPlayDoc = new RobloxIDEDoc(m_pMainWindow);
pDoc = m_pPlayDoc;
break;
}
case IRobloxDoc::SCRIPT:
{
pDoc = new RobloxScriptDoc();
break;
}
case IRobloxDoc::BROWSER:
{
if ( !m_pWebDoc )
m_pWebDoc = new RobloxWebDoc(tr("Start Page"), "StartPage");
pDoc = m_pWebDoc;
break;
}
case IRobloxDoc::OBJECTBROWSER:
{
if ( !m_pObjectBrowser )
m_pObjectBrowser = new RobloxObjectBrowserDoc();
pDoc = m_pObjectBrowser;
break;
}
default:
RBXASSERT(false);
}
return pDoc;
}
/**
* Attempt to open a script document.
* If the script is already open, the doc will be returned.
* If the script is not already open, a new doc will be returned.
*/
RobloxScriptDoc* RobloxDocManager::openDoc(LuaSourceBuffer script)
{
if (script.empty())
return NULL;
boost::scoped_ptr<RBX::DataModel::LegacyLock> lock;
// Double click scripts in game explorer will attempt to open doc, only open
// script doc if we have a play doc.
if (m_pPlayDoc == NULL)
{
return NULL;
}
// RobloxScriptDoc::setScript needs a write lock
lock.reset(new RBX::DataModel::LegacyLock(m_pPlayDoc->getDataModel(), RBX::DataModelJob::Write));
// check if it is a linked script, and if so substitute opening linked source
if (shared_ptr<RBX::LuaSourceContainer> lsc = script.toInstance())
{
if (!lsc->getScriptId().isNull())
{
script = LuaSourceBuffer::fromContentId(lsc->getScriptId());
}
}
if (!script.sourceAvailable())
{
// release the lock before entering message box event loop
lock.reset();
QMessageBox mb;
mb.setText("Unable to load script. If the script uses LinkedSource, the "
"LinkedSource may have been renamed or removed.");
mb.exec();
return NULL;
}
// If the doc for this script already exists, then return the same
if (RobloxScriptDoc* doc = findOpenScriptDoc(script))
{
setCurrentDoc(doc);
return doc;
}
// Create new script document
RobloxScriptDoc* pDoc = new RobloxScriptDoc();
if ( !pDoc->open(m_pMainWindow,"") )
{
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Error in opening script");
pDoc->close();
delete pDoc;
return NULL;
}
configureDocWidget(*pDoc);
pDoc->setScript(shared_from(getPlayDoc()->getDataModel()), script);
return pDoc;
}
void RobloxDocManager::openDoc(shared_ptr<RBX::Instance> script, int lineNumber)
{
if (RobloxScriptDoc* scriptDoc = openDoc(LuaSourceBuffer::fromInstance(script)))
scriptDoc->getTextEditor()->moveCursorToLine(lineNumber);
}
/**
* Requests a script doc to be closed.
*/
void RobloxDocManager::closeDoc(const LuaSourceBuffer& script)
{
RBXASSERT(!script.empty());
if (RobloxScriptDoc* pDoc = findOpenScriptDoc(script))
{
m_pMainWindow->requestDocClose(*pDoc);
}
}
/**
* Adds the doc to the doc tab manager.
*/
void RobloxDocManager::configureDocWidget(IRobloxDoc& doc)
{
const RBX::BaldPtr<QWidget> widget = doc.getViewer();
RBXASSERT(widget);
const QString keyName = doc.keyName();
RBXASSERT(!keyName.isEmpty());
if (!m_KeyDocMap.contains(keyName))
{
m_KeyDocMap[keyName] = &doc;
m_DocTabManager->addDoc(doc);
}
setCurrentDoc(&doc);
}
/**
* Removes a doc from the tab or dock manager.
*/
void RobloxDocManager::removeDoc(IRobloxDoc& doc)
{
m_KeyDocMap.remove(doc.keyName());
if ( &doc == m_pCurrentDoc )
{
setCurrentDoc(NULL);
m_pCurrentDoc = NULL;
}
if ( &doc == m_pPlayDoc )
{
restoreCentralWidget();
tScriptDocs copyOfScriptDocs = m_OpenScriptDocs;
for (tScriptDocs::iterator itr = copyOfScriptDocs.begin();
itr != copyOfScriptDocs.end(); ++itr)
{
m_pMainWindow->requestDocClose(*itr);
}
RBXASSERT(m_OpenScriptDocs.empty());
m_pPlayDoc = NULL;
}
if (m_DocTabManager && !m_DocTabManager->removeDoc(doc) )
{
m_DocDockManager->removeDoc(doc);
setCurrentDoc(m_DocTabManager->getCurrentDoc());
}
doc.close();
updateWindowMenu();
}
/**
* Sets the current doc.
* The doc may be NULL. The old doc is deactivated.
*/
void RobloxDocManager::setCurrentDoc(IRobloxDoc* doc)
{
if ( doc == m_pCurrentDoc )
{
if (m_pCurrentDoc)
m_pCurrentDoc->activate();
return;
}
bool docTypeChanged = (m_pCurrentDoc ? m_pCurrentDoc->docType() : -1) != (doc ? doc->docType() : -1);
if ( m_pCurrentDoc )
{
m_pCurrentDoc->deActivate();
if (docTypeChanged)
m_pCurrentDoc->updateUI(false);
}
m_pCurrentDoc = doc;
if ( m_pCurrentDoc )
{
m_pCurrentDoc->activate();
if (docTypeChanged)
m_pCurrentDoc->updateUI(true);
}
else
{
RBX::PluginManager::singleton()->setCurrentDataModel(NULL);
}
UpdateUIManager::Instance().updateToolBars();
m_pMainWindow->updateWindowTitle();
if ( !m_DocTabManager->setCurrentDoc(*doc) )
{
m_pMainWindow->setFocus();
m_DocDockManager->setCurrentDoc(*doc);
}
else if (m_pCurrentDoc)
{
m_pCurrentDoc->getViewer()->setFocus();
}
updateWindowMenu();
Q_EMIT currentDocChanged();
}
/**
* Updates the list of open documents in the Window menu.
* The current open doc is marked with a check.
*/
void RobloxDocManager::updateWindowMenu()
{
if (!m_pMainWindow)
return;
int count = 0;
tKeyDocMap::iterator iter = m_KeyDocMap.begin();
while ( iter != m_KeyDocMap.end() && count < RobloxMainWindow::MAX_DOC_WINDOWS )
{
RBX::BaldPtr<IRobloxDoc> doc = iter.value();
QString text = QObject::tr("&%1 %2").arg(count + 1).arg(doc->displayName());
m_pMainWindow->currentOpenedfiles[count]->setText(text);
m_pMainWindow->currentOpenedfiles[count]->setData(count);
m_pMainWindow->currentOpenedfiles[count]->setVisible(true);
bool selected = (doc == m_pCurrentDoc);
m_pMainWindow->currentOpenedfiles[count]->setChecked(selected);
++iter;
++count;
}
while ( count < RobloxMainWindow::MAX_DOC_WINDOWS )
{
m_pMainWindow->currentOpenedfiles[count]->setVisible(false);
++count;
}
}
/**
* Callback for user clicking on a doc in the Window menu.
*/
void RobloxDocManager::onActivateWindow()
{
const RBX::BaldPtr<QAction> action = qobject_cast<QAction*>(sender());
int index = action->data().toInt();
activateDoc(index);
updateWindowMenu();
}
/**
* Activate a doc at the given index.
* Used by onActivateWindow.
*/
void RobloxDocManager::activateDoc(int index)
{
tKeyDocMap::iterator iter = m_KeyDocMap.begin();
iter = iter + index;
if ( iter != m_KeyDocMap.end() )
setCurrentDoc(iter.value());
}
/**
* Changes the title of a doc.
* If the doc is a tab, the tab name is changed. If the doc is a dock widget, the window
* title is changed.
*/
void RobloxDocManager::setDocTitle(IRobloxDoc& doc,const QString& displayName,const QString& fileName,const QIcon& icon)
{
const QString oldKey = m_KeyDocMap.key(&doc);
RBXASSERT(!oldKey.isEmpty());
m_KeyDocMap.remove(oldKey);
m_KeyDocMap[doc.keyName()] = &doc;
if ( !m_DocTabManager->renameDoc(doc,displayName,fileName,icon) )
m_DocDockManager->renameDoc(doc,displayName,fileName);
updateWindowMenu();
m_pMainWindow->updateWindowTitle();
}
/**
* Detaches the given doc from the tab manager.
* Optionally begins dragging the title bar to move the window.
*/
void RobloxDocManager::detachDoc(IRobloxDoc& doc,bool dragging)
{
if ( m_DocTabManager->removeDoc(doc) )
{
m_DocDockManager->addDoc(doc);
if ( dragging )
m_DocDockManager->startDrag(doc);
m_DocDockManager->setCurrentDoc(doc);
}
}
/**
* Attaches a dock widget to the tab manager.
*/
void RobloxDocManager::attachDoc(IRobloxDoc& doc, int index)
{
if ( m_DocDockManager->removeDoc(doc) )
m_DocTabManager->addDoc(doc, index);
}
/**
* Sets the current state of the mouse hover over.
* When a dock widget is hovered over the tab widget while dragging,
* the tab widget shows if the dock can be reattached.
*/
void RobloxDocManager::setDockHoverOverPos(const QPoint& globalPos)
{
m_DocTabManager->setDockHoverOverPos(globalPos);
}
/**
* Attempts to attach a dock widget.
* If the mouse position is inside the region allowable for reattachment,
* the doc is reattached to the tab bar.
*
* @param doc doc to attach
* @param worldPos position the mouse button was released
*/
bool RobloxDocManager::attemptAttach(IRobloxDoc& doc,const QPoint& globalPos)
{
return m_DocTabManager->attemptAttach(doc,globalPos);
}
/**
* Sets the central widget for the main window to just show the IDE 3D view.
*/
void RobloxDocManager::setPlayDocCentralWidget()
{
RBXASSERT(m_pPlayDoc);
QWidget* centralWidget = m_pMainWindow->centralWidget();
if ( centralWidget != m_pPlayDoc->getViewer() )
{
if ( !m_DocDockManager->removeDoc(*m_pPlayDoc) )
m_DocTabManager->removeDoc(*m_pPlayDoc);
m_pMainWindow->setCentralWidget(m_pPlayDoc->getViewer());
QApplication::removePostedEvents(centralWidget); // don't allow to be deleted
m_pPlayDoc->getViewer()->show();
setCurrentDoc(m_pPlayDoc);
}
}
/**
* Resets the central widget for the main window back to the tab manager.
*/
void RobloxDocManager::restoreCentralWidget()
{
m_DocTabManager->restoreAsCentralWidget();
}