mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
3112 lines
98 KiB
C++
3112 lines
98 KiB
C++
/**
|
|
* RobloxMainWindow.cpp
|
|
* Copyright (c) 2013 ROBLOX Corp. All rights reserved.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "RobloxMainWindow.h"
|
|
|
|
// Qt Headers
|
|
#include <QNetworkProxyFactory>
|
|
#include <QFileOpenEvent>
|
|
#include <QDateTime>
|
|
#include <QScrollBar>
|
|
#include <QFileInfo>
|
|
#include <QDesktopServices>
|
|
#include <QDir>
|
|
#include <QComboBox>
|
|
#include <QToolTip>
|
|
#include <QFileDialog>
|
|
#include <QLineEdit>
|
|
#include <QLabel>
|
|
#include <QToolBar>
|
|
#include <QToolButton>
|
|
#include <QCompleter>
|
|
#include <QWidgetAction>
|
|
#include <QProcess>
|
|
#include <QSignalMapper>
|
|
#include <QSplashScreen>
|
|
#include <QSharedMemory>
|
|
#include <QShortcut>
|
|
#include <QPrintDialog>
|
|
#include <QPrintPreviewDialog>
|
|
#include <QPageSetupDialog>
|
|
#include <QResource>
|
|
#include <QDesktopWidget>
|
|
#include <QNetworkProxy>
|
|
#include <QtConcurrentRun>
|
|
|
|
// 3rd Party Headers
|
|
#include "boost/filesystem/path.hpp"
|
|
#include "boost/iostreams/stream.hpp"
|
|
#include "boost/iostreams/device/array.hpp"
|
|
|
|
// Roblox Headers
|
|
#include "util/standardout.h"
|
|
#include "util/ScopedAssign.h"
|
|
#include "util/RbxStringTable.h"
|
|
#include "util/SoundService.h"
|
|
#include "script/ScriptContext.h"
|
|
#include "v8datamodel/DebugSettings.h"
|
|
#include "v8datamodel/PartInstance.h"
|
|
#include "v8datamodel/ModelInstance.h"
|
|
#include "v8datamodel/Explosion.h"
|
|
#include "v8datamodel/PluginManager.h"
|
|
#include "v8datamodel/GameBasicSettings.h"
|
|
#include "v8datamodel/FastLogSettings.h"
|
|
#include "v8datamodel/Stats.h"
|
|
#include "v8world/Contact.h"
|
|
#include "v8kernel/Body.h"
|
|
#include "v8kernel/ContactConnector.h"
|
|
#include "rbx/BaldPtr.h"
|
|
#include "rbx/CEvent.h"
|
|
#include "Network/Players.h"
|
|
#include "CountersClient.h"
|
|
#include "FastLog.h"
|
|
#include "SharedLauncher.h"
|
|
#include "RobloxStudioVersion.h"
|
|
#include "RobloxQuickAccessConfig.h"
|
|
#include "RenderSettingsItem.h"
|
|
#include "RobloxServicesTools.h"
|
|
|
|
#include "v8datamodel/CSGMesh.h"
|
|
#include "../CSG/CSGKernel.h"
|
|
|
|
#ifdef _WIN32
|
|
#include "VersionInfo.h"
|
|
#include "WinInet.h"
|
|
#include <atlsync.h>
|
|
#else
|
|
#include "BreakpadCrashReporter.h"
|
|
#include <semaphore.h>
|
|
#endif
|
|
|
|
// Roblox Studio Headers
|
|
#include "FunctionMarshaller.h"
|
|
#include "RobloxDocManager.h"
|
|
#include "RobloxCustomWidgets.h"
|
|
#include "Roblox.h"
|
|
#include "RobloxSettings.h"
|
|
#include "RobloxSettingsDialog.h"
|
|
#include "RobloxDiagnosticsView.h"
|
|
#include "RobloxScriptReview.h"
|
|
#include "RobloxTaskScheduler.h"
|
|
#include "CommonInsertWidget.h"
|
|
#include "RobloxToolBox.h"
|
|
#include "StudioUtilities.h"
|
|
#include "RobloxTextOutputWidget.h"
|
|
#include "QtUtilities.h"
|
|
#include "AuthoringSettings.h"
|
|
#include "UpdateUIManager.h"
|
|
#include "RobloxPluginHost.h"
|
|
#include "WebDialog.h"
|
|
#include "RobloxApplicationManager.h"
|
|
#include "AuthenticationHelper.h"
|
|
#include "RobloxIDEDoc.h"
|
|
#include "RobloxInputConfigDialog.h"
|
|
#include "RobloxKeyboardConfig.h"
|
|
#include "RobloxMouseConfig.h"
|
|
#include "RobloxStudioVerbs.h"
|
|
#include "RobloxScriptDoc.h"
|
|
#include "ShortcutHelpDialog.h"
|
|
#include "RobloxUser.h"
|
|
#include "AutoSaveDialog.h"
|
|
#include "InsertServiceDialog.h"
|
|
#include "StudioIntellesense.h"
|
|
#include "RobloxWebDoc.h"
|
|
#include "RbxWorkspace.h"
|
|
#include "FindDialog.h"
|
|
#include "ScriptTextEditor.h"
|
|
|
|
#include "Util/RobloxGoogleAnalytics.h"
|
|
#include "StudioDeviceEmulator.h"
|
|
|
|
FASTFLAGVARIABLE(StudioCheckForUpgradeEnabled, false)
|
|
FASTFLAGVARIABLE(StudioSeparateActionByActivationMethod, false)
|
|
FASTFLAGVARIABLE(StudioFixMacStartPage, false)
|
|
FASTFLAGVARIABLE(StudioSplashScreenUntilOpen, false)
|
|
FASTFLAGVARIABLE(StudioEarlyCookieConstraintCheckGlobal, false)
|
|
FASTSTRINGVARIABLE(StudioCookieConstraintUrlFragment, "fulfillconstraint")
|
|
|
|
FASTINTVARIABLE(StudioRobloxAnalyticsLoad, 100);
|
|
FASTINTVARIABLE(StudioBootstrapperVersionNumber, 52886)
|
|
FASTINTVARIABLE(StudioInsertDeletionCheckTimeMS, 8000)
|
|
|
|
FASTFLAGVARIABLE(StudioCustomStatsEnabled, false)
|
|
FASTFLAGVARIABLE(StudioSettingsGAEnabled, true)
|
|
FASTFLAGVARIABLE(AssertOnConfigurationReportToGA, true)
|
|
FASTFLAGVARIABLE(ReportBuildVSEditMode, true)
|
|
FASTFLAGVARIABLE(TeamCreateEnableDownloadLocalCopy, true)
|
|
FASTFLAGVARIABLE(StudioDoublingOnUploadFixEnabled, true)
|
|
|
|
FASTFLAG(GoogleAnalyticsTrackingEnabled)
|
|
FASTFLAG(StudioInSyncWebKitAuthentication)
|
|
FASTFLAG(StudioDataModelIsStudioFix)
|
|
FASTFLAG(StudioShowTutorialsByDefault)
|
|
FASTFLAG(DontSwallowInputForStudioShortcuts)
|
|
FASTFLAG(StudioSettingsGAEnabled)
|
|
FASTFLAG(UseBuildGenericGameUrl)
|
|
|
|
FASTINT(StudioWebDialogMinimumWidth)
|
|
FASTINT(StudioWebDialogMinimumHeight)
|
|
|
|
DYNAMIC_LOGGROUP(GoogleAnalyticsTracking)
|
|
LOGGROUP(Network)
|
|
|
|
FASTFLAGVARIABLE(Dep, true)
|
|
|
|
static const int MaxLengthFilenameMRU = 64; //!< maximum length of a filename in the MRU list
|
|
static const char* sWindow_Title = "ROBLOX Studio"; //!< default main window title when no place selected
|
|
static const char* sDialog_Title = "ROBLOX Studio"; //!< default child dialog title
|
|
static const int SplashTotalTime = 3505; //!< minimum time to display splashscreen
|
|
|
|
bool RobloxMainWindow::sIsAppRunning = false;
|
|
const QString RobloxMainWindow::NEW_PLACE_FILENAME = "ROBLOX_NEW_PLACE";
|
|
|
|
bool RobloxMainWindow::preventActionCounterSending = false;
|
|
|
|
QString RobloxMainWindow::sWindowStateKey = "window_state";
|
|
QString RobloxMainWindow::sGeometryKey = "window_geometry";
|
|
|
|
#ifdef _WIN32
|
|
#include "LogManager.h"
|
|
#include "DumpErrorUploader.h"
|
|
|
|
static MainLogManager mainLogManager("RobloxStudio", ".RobloxStudio.dmp", ".RobloxStudio.crashevent");
|
|
static boost::scoped_ptr<DumpErrorUploader> dumpErrorUploader;
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
#include "LogProvider.h"
|
|
static LogProvider logProvider;
|
|
|
|
#include "StudioMacUtilities.h"
|
|
#endif
|
|
|
|
RobloxMainWindow* RobloxMainWindow::get(QObject* context)
|
|
{
|
|
while(context != NULL && dynamic_cast<RobloxMainWindow*>(context) == NULL)
|
|
{
|
|
context = context->parent();
|
|
}
|
|
|
|
return static_cast<RobloxMainWindow*>(context);
|
|
}
|
|
|
|
RobloxMainWindow::RobloxMainWindow(const QMap<QString, QString> argMap)
|
|
: RobloxRibbonMainWindow(this)
|
|
, m_pSettingsDialog(NULL)
|
|
, m_pMinutesPlayedTimer(NULL)
|
|
, m_pInsertServiceDlg(NULL)
|
|
, m_pShortcutHelpDialog(NULL)
|
|
, m_pInputConfigDialog(NULL)
|
|
, m_pPluginHost(new RobloxPluginHost(this))
|
|
, m_splashScreen(NULL)
|
|
, m_publishGameDialog(NULL)
|
|
, m_pCounterSenderTimer(NULL)
|
|
, m_AutoSaveAccum(0)
|
|
, m_IsInitialized(false)
|
|
, m_pPublishedProjectsWebDialog(NULL)
|
|
, m_isRibbon(false)
|
|
, m_fileOpenHandled(false)
|
|
, m_BuildMode(BM_ADVANCED)
|
|
, m_IgnoreCloudEditDisconnect(false)
|
|
, m_editScriptActions("EditScript", &studioAnalytics)
|
|
, m_mouseActions("ManipulatePart", &studioAnalytics)
|
|
, m_inserts("Inserts", &studioAnalytics)
|
|
, m_cookieConstraintChecker(NULL)
|
|
, m_cookieConstraintCheckDone(NULL)
|
|
{
|
|
try
|
|
{
|
|
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
|
|
|
// grab all of our known args from the map (to be used later)
|
|
parseCommandLineOptions(argMap);
|
|
|
|
QTime splashTime = QTime::currentTime();
|
|
if ( showEventArg.isEmpty() )
|
|
{
|
|
// can't set window flags in constructor because other ones are appended that we don't want
|
|
m_splashScreen = new QSplashScreen(this,QPixmap(":/images/RobloxStudioSplash.png"));
|
|
|
|
#ifdef Q_WS_WIN32
|
|
Qt::WindowFlags flags = Qt::SplashScreen | Qt::FramelessWindowHint;
|
|
#else
|
|
// Qt::Tool makes the window on top of the Z order on Mac
|
|
Qt::WindowFlags flags = Qt::Tool | Qt::FramelessWindowHint;
|
|
#endif
|
|
|
|
// now we can set window flags
|
|
m_splashScreen->setWindowFlags(flags);
|
|
|
|
m_splashScreen->raise();
|
|
m_splashScreen->show();
|
|
}
|
|
|
|
//start the bootstrapper on Mac, start the Event on Windows
|
|
checkUpdater(true,startEventArg);
|
|
|
|
SetBaseURL(RobloxSettings::getBaseURL().toStdString());
|
|
|
|
// make sure we call this first, this will initialize boost related stuff also
|
|
// or else we can run into raise condition which doing HTTP init as being mentioned here
|
|
// - https://svn.boost.org/trac/boost/ticket/6320
|
|
boost::filesystem::path::codecvt();
|
|
|
|
RBX::Game::globalInit(true);
|
|
|
|
// get logging going quickly, so any output is recorded
|
|
setupLogging();
|
|
RBX::Log::current()->writeEntry(RBX::Log::Information,"RobloxMainWindow::RobloxMainWindow - start");
|
|
|
|
RBX::HttpFuture settingsFuture = FetchClientSettingsDataAsync(CLIENT_APP_SETTINGS_STRING, CLIENT_SETTINGS_API_KEY);
|
|
try
|
|
{
|
|
LoadClientSettingsFromString(CLIENT_APP_SETTINGS_STRING, settingsFuture.get(), &RBX::ClientAppSettings::singleton());
|
|
}
|
|
catch(std::exception&)
|
|
{
|
|
FASTLOG(FLog::Error, "Failed to load settings");
|
|
}
|
|
|
|
AuthenticationHelper::Instance().authenticateUserAsync(urlArg, ticketArg);
|
|
|
|
//set up ui using the .ui file
|
|
setupUi(this);
|
|
|
|
// set up external analytics reporting variables
|
|
QString country = QLocale::countryToString(QLocale::system().country());
|
|
#ifdef _WIN32
|
|
RBX::Analytics::setReporter("PC Studio");
|
|
#else
|
|
RBX::Analytics::setReporter("Mac Studio");
|
|
#endif
|
|
RBX::Analytics::setLocation(country.toStdString());
|
|
RBX::Analytics::setAppVersion(RobloxSettings::getVersionString().toStdString());
|
|
|
|
// set up influxdb reporting
|
|
RBX::Analytics::InfluxDb::init();
|
|
|
|
//initializing textOutputWidget early to catch any output in globalInit
|
|
m_pTextOutput = new RobloxTextOutputWidget(dockWidgetContents_2);
|
|
|
|
RBX::Http robloxRequest(AuthenticationHelper::getLoggedInUserUrl().toStdString());
|
|
RBX::Http externalRequest("http://www.google.com");
|
|
|
|
QSettings retentionData("Roblox", "Retention");
|
|
|
|
static const char* const kRetentionInstallDateKey = "InstallDate";
|
|
static const char* const kRetentionLastRunDateKey = "LastRunDate";
|
|
QString dateString = QDate::currentDate().toString("yyyyMMdd");
|
|
QString installDate = retentionData.value(kRetentionInstallDateKey, "").toString();
|
|
|
|
if (installDate.isEmpty())
|
|
{
|
|
StudioUtilities::setIsFirstTimeOpeningStudio(true);
|
|
}
|
|
|
|
// Init our engine - can only use ClientAppSettings AFTER this!!!
|
|
Roblox::globalInit(urlArg, ticketArg, settingsFuture);
|
|
RBX::Stats::reportGameStatus("AppStarted"); // must be after gloablInit due to http initialization
|
|
|
|
Qtitan::RibbonStyle::setStyleVersion(1);
|
|
|
|
// Disable FRM in studio by default
|
|
CRenderSettingsItem::singleton().setEnableFRM(false);
|
|
|
|
// Following are the different modes launched from website
|
|
// (this is required since website conveys 'only' to be launched from Studio but doesn't specify the mode
|
|
// 1) Build mode: BuildArgument == True (default mode set)
|
|
// 2) Edit mode: BuildArgument == True && ScriptArgument has edit.ashx
|
|
// 3) Start Page mode: BuildArgument == True && ScriptArgument is empty
|
|
if (StudioUtilities::containsEditScript(argMap[SharedLauncher::ScriptArgument])
|
|
|| ((argMap[SharedLauncher::BuildArgument]=="TRUE") && argMap[SharedLauncher::ScriptArgument].isEmpty()))
|
|
{
|
|
m_BuildMode = BM_ADVANCED;
|
|
StudioUtilities::setAvatarMode(false);
|
|
}
|
|
|
|
{
|
|
#ifdef __APPLE__
|
|
int useCurlPercentage = RBX::ClientAppSettings::singleton().GetValueHttpUseCurlPercentageMacStudio();
|
|
#else
|
|
int useCurlPercentage = RBX::ClientAppSettings::singleton().GetValueHttpUseCurlPercentageWinStudio();
|
|
#endif
|
|
bool useCurl = rand() % 100 < useCurlPercentage;
|
|
#ifdef _WIN32
|
|
{
|
|
INTERNET_PER_CONN_OPTION_LIST List;
|
|
INTERNET_PER_CONN_OPTION Option[1];
|
|
unsigned long nSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
|
|
|
Option[0].dwOption = INTERNET_PER_CONN_FLAGS;
|
|
|
|
List.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST);
|
|
List.pszConnection = NULL;
|
|
List.dwOptionCount = 1;
|
|
List.dwOptionError = 0;
|
|
List.pOptions = Option;
|
|
|
|
if (InternetQueryOption(NULL, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize))
|
|
{
|
|
if (Option[0].Value.dwValue & PROXY_TYPE_PROXY)
|
|
{
|
|
// As of when this was added, curl could not handle ssl over proxies
|
|
// so fall back to win inet when proxy is detected
|
|
useCurl = false;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
FASTLOG1(FLog::Network, "Use CURL = %d", useCurl);
|
|
RBX::Http::SetUseCurl(useCurl);
|
|
}
|
|
|
|
RBX::Http::useDefaultTimeouts = false;
|
|
|
|
// init google analytics
|
|
std::string googleAnalyticsAccountPropId = RBX::ClientAppSettings::singleton().GetValueGoogleAnalyticsAccountPropertyID();
|
|
if (m_BuildMode == BM_BASIC)
|
|
{
|
|
// Set the account property id to either the Build Mode production account or the test account
|
|
// by checking the baseURL for the company domain "roblox.com".
|
|
QString baseURL(RobloxSettings().getBaseURL());
|
|
if (baseURL.endsWith("roblox.com", Qt::CaseInsensitive))
|
|
googleAnalyticsAccountPropId = "UA-43420590-4"; // Production
|
|
else
|
|
googleAnalyticsAccountPropId = "UA-43420590-5"; // Test
|
|
}
|
|
|
|
RBX::RobloxGoogleAnalytics::lotteryInit(googleAnalyticsAccountPropId,
|
|
RBX::ClientAppSettings::singleton().GetValueGoogleAnalyticsThreadPoolMaxScheduleSize(), RBX::ClientAppSettings::singleton().GetValueGoogleAnalyticsLoadStudio(), "studio",
|
|
FInt::StudioRobloxAnalyticsLoad, "studioSid=");
|
|
|
|
if(StudioUtilities::isFirstTimeOpeningStudio())
|
|
{
|
|
installDate = dateString;
|
|
retentionData.setValue(kRetentionInstallDateKey, dateString);
|
|
RBX::RobloxGoogleAnalytics::trackEventWithoutThrottling("Retention-Install",
|
|
dateString.toStdString().c_str(), "none", 1);
|
|
}
|
|
|
|
QString lastRun = retentionData.value(kRetentionLastRunDateKey, "").toString();
|
|
if (lastRun != dateString)
|
|
{
|
|
retentionData.setValue(kRetentionLastRunDateKey, dateString);
|
|
RBX::RobloxGoogleAnalytics::trackEventWithoutThrottling("Retention-Run",
|
|
installDate.toStdString().c_str(), dateString.toStdString().c_str(), 1);
|
|
}
|
|
|
|
// Send tracking information about the application.
|
|
if (FFlag::GoogleAnalyticsTrackingEnabled)
|
|
{
|
|
QString label = m_BuildMode == BM_BASIC ? "Basic" : "IDE";
|
|
|
|
// Extract everything from scriptArg before ".ashx"
|
|
int httpIndex = scriptArg.indexOf("http");
|
|
int ashxIndex = scriptArg.indexOf(".ashx");
|
|
if(httpIndex != -1 && ashxIndex > httpIndex)
|
|
label += " " + scriptArg.mid(httpIndex, ashxIndex - httpIndex + 5);
|
|
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Start", label.toStdString().c_str());
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Version", RobloxSettings::getVersionString().toStdString().c_str());
|
|
}
|
|
|
|
QtConcurrent::run(this, &RobloxMainWindow::checkInternetConnectionSendCounter, robloxRequest, externalRequest);
|
|
|
|
if (AuthoringSettings::singleton().getUIStyle() == AuthoringSettings::Ribbon)
|
|
{
|
|
m_isRibbon = true;
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_COUNTERS, "RibbonBar", "Launches", 1);
|
|
}
|
|
|
|
if (FFlag::StudioSettingsGAEnabled)
|
|
{
|
|
RBX::Reflection::PropertyIterator iter = AuthoringSettings::singleton().properties_begin();
|
|
RBX::Reflection::PropertyIterator end = AuthoringSettings::singleton().properties_end();
|
|
|
|
while (iter!=end)
|
|
{
|
|
RBX::Reflection::Property property = *iter;
|
|
std::string valueString;
|
|
if (!FFlag::AssertOnConfigurationReportToGA)
|
|
{
|
|
valueString = property.getStringValue();
|
|
}
|
|
std::string description = property.getName().str;
|
|
std::transform(description.begin(), description.end(), description.begin(), tolower);
|
|
|
|
if (!(description == "parent" || description == "archivable" || description == "classname" || description == "datacost"
|
|
|| description == "name" || description == "robloxlocked"))
|
|
{
|
|
description += " On Start";
|
|
|
|
if (FFlag::AssertOnConfigurationReportToGA)
|
|
{
|
|
valueString = "N/A";
|
|
if (property.hasStringValue())
|
|
{
|
|
valueString = property.getStringValue();
|
|
}
|
|
}
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO_SETTINGS, description.c_str(), valueString.c_str());
|
|
}
|
|
++iter;
|
|
}
|
|
|
|
AuthoringSettings::singleton().setDoSettingsChangedGAEvents(true);
|
|
}
|
|
|
|
//check for crash
|
|
RobloxSettings settings;
|
|
if (RobloxApplicationManager::instance().getApplicationCount() == 1 && settings.contains("appClosed") && !settings.value("appClosed").toBool())
|
|
{
|
|
sendCounterEvent("QTStudioCrashRecorded");
|
|
if (FFlag::GoogleAnalyticsTrackingEnabled)
|
|
{
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ERROR, "Crash");
|
|
}
|
|
}
|
|
settings.setValue("appClosed", false);
|
|
|
|
#ifdef _WIN32
|
|
if (FFlag::Dep)
|
|
{
|
|
typedef BOOL (WINAPI *SetProcessDEPPolicyPfn)(DWORD);
|
|
SetProcessDEPPolicyPfn pfn= reinterpret_cast<SetProcessDEPPolicyPfn>(GetProcAddress(GetModuleHandle("Kernel32"), "SetProcessDEPPolicy"));
|
|
if (pfn)
|
|
{
|
|
static const DWORD kEnable = 1;
|
|
pfn(kEnable);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (FFlag::StudioCheckForUpgradeEnabled)
|
|
{
|
|
RobloxSettings settings;
|
|
if(settings.value("studioVersion").toString() != RobloxSettings::getVersionString())
|
|
{
|
|
settings.setValue("studioVersion", RobloxSettings::getVersionString());
|
|
sendCounterEvent("StudioUpgradedCounter", false);
|
|
if (FFlag::GoogleAnalyticsTrackingEnabled)
|
|
{
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "Upgrade", RobloxSettings::getVersionString().toStdString().c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the CSGMesh Factory to use sgCore.
|
|
RBX::CSGMeshFactory::set(new RBX::CSGMeshFactorySgCore());
|
|
|
|
initializeUI();
|
|
|
|
//setup WikiSearch in scripts
|
|
ScriptTextEditor::setupWikiLookup();
|
|
|
|
// reset the position of the toolbars. so they are identical to the old studio.
|
|
setToolbarPosition();
|
|
|
|
//set up command toolbar
|
|
setupCommandToolBar();
|
|
|
|
//create material and color tool button
|
|
setupCustomToolButton();
|
|
|
|
//sends out slow counters every X seconds
|
|
setupCounterSender();
|
|
|
|
//set default commands
|
|
UpdateUIManager::Instance().setDefaultApplicationState();
|
|
|
|
sIsAppRunning = true;
|
|
|
|
// Set Security context Identity
|
|
RBX::Security::Impersonator impersonate(RBX::Security::LocalGUI_);
|
|
|
|
//setup statusbar and docks
|
|
UpdateUIManager::Instance().init(this);
|
|
|
|
RobloxScriptDoc::init(*this);
|
|
|
|
if (isRibbonStyle())
|
|
{
|
|
setupRibbonBar();
|
|
}
|
|
else
|
|
{
|
|
setupViewMenu();
|
|
}
|
|
|
|
//set up slots
|
|
setupSlots();
|
|
|
|
// set up shortcut key sequences
|
|
assignAccelerators();
|
|
|
|
updateRecentFilesUI();
|
|
|
|
// Idempotent function - and if there is no open tab, we need to update actions (disable ones that need to be)
|
|
UpdateUIManager::Instance().updateToolBars();
|
|
|
|
setBuildMode(m_BuildMode);
|
|
|
|
// save current state as default state
|
|
saveDefaultApplicationState();
|
|
|
|
// load the application states from memory
|
|
loadApplicationStates();
|
|
|
|
m_PropertyChangedConnection = AuthoringSettings::singleton().propertyChangedSignal.connect(
|
|
boost::bind(&RobloxMainWindow::onPropertyChanged,this,_1));
|
|
onPropertyChanged(NULL);
|
|
|
|
if (FFlag::StudioEarlyCookieConstraintCheckGlobal ||
|
|
RobloxSettings::getBaseURL().contains("robloxlabs", Qt::CaseInsensitive))
|
|
{
|
|
m_cookieConstraintChecker = new QWebView(NULL);
|
|
m_cookieConstraintCheckDone = new QEventLoop(this);
|
|
m_cookieConstraintChecker->setAttribute(Qt::WA_DeleteOnClose);
|
|
m_cookieConstraintChecker->page()->setNetworkAccessManager(&RobloxNetworkAccessManager::Instance());
|
|
connect(m_cookieConstraintChecker->page(), SIGNAL(loadStarted()), m_cookieConstraintChecker, SLOT(hide()));
|
|
connect(m_cookieConstraintChecker, SIGNAL(loadFinished(bool)), this, SLOT(cookieConstraintCheckerLoadFinished(bool)));
|
|
connect(m_cookieConstraintChecker, SIGNAL(destroyed(QObject*)), m_cookieConstraintCheckDone, SLOT(quit()));
|
|
m_cookieConstraintChecker->load(RobloxSettings::getBaseURL());
|
|
m_cookieConstraintCheckDone->exec();
|
|
m_cookieConstraintCheckDone->deleteLater();
|
|
m_cookieConstraintCheckDone = NULL;
|
|
// if the window is destroyed from close we want to null it out here.
|
|
m_cookieConstraintChecker = NULL;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
if ( showEventArg.isEmpty() )
|
|
show();
|
|
#else
|
|
show();
|
|
|
|
// bootstrapper will wait for this event to exit
|
|
sem_t *sem = sem_open("/robloxStudioStartedEvent", 0);
|
|
if (sem != SEM_FAILED)
|
|
{
|
|
sem_post(sem);
|
|
sem_close(sem);
|
|
}
|
|
#endif
|
|
|
|
// take the args that were passed in and act upon them
|
|
// has to be at the very end of the constructor, or else the menu bar will behave weird.
|
|
|
|
handleCommandLineOptions();
|
|
m_fileOpenHandled = false;
|
|
|
|
//Mac will open file through finder event; need to handle open start page this way
|
|
IRobloxDoc* currentPlayDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
if (!currentPlayDoc)
|
|
{
|
|
// if nothing else opened, open a start web page
|
|
actionStartPage->setChecked(true);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
if ( !showEventArg.isEmpty() )
|
|
{
|
|
// Application waits for this event before showing to the user
|
|
ATL::CEvent showEvent;
|
|
if ( showEvent.Open(EVENT_MODIFY_STATE, FALSE, qPrintable(showEventArg)) )
|
|
::WaitForSingleObject(showEvent,30 * 1000);
|
|
}
|
|
|
|
show();
|
|
|
|
if ( !readyEventArg.isEmpty() )
|
|
{
|
|
// Bootstrapper will wait for this event, to make sure that app is loaded and ready
|
|
ATL::CEvent readyEvent;
|
|
if ( readyEvent.Open(EVENT_MODIFY_STATE, FALSE, qPrintable(readyEventArg)) )
|
|
::WaitForSingleObject(readyEvent,30 * 1000);
|
|
}
|
|
#endif
|
|
|
|
// Unfortunately has to be called after show due to windowState not loading until then.
|
|
UpdateUIManager::Instance().updateDockActionsCheckedStates();
|
|
|
|
viewCommandBarAction->setChecked(commandToolBar->isVisible());
|
|
|
|
//set window layout setting according to application states.
|
|
//it has to be after loadApplicationStates. or else full screen button will not be initialized correctly.
|
|
setWindowLayout();
|
|
|
|
if ( AutoSaveDialog::checkForAutoSaveFiles() )
|
|
{
|
|
AutoSaveDialog dialog(this);
|
|
while ( dialog.exec() == QDialog::Rejected )
|
|
{
|
|
/* nothing */
|
|
}
|
|
}
|
|
|
|
if ( m_splashScreen )
|
|
{
|
|
// show splash screen for a minimum amount of time
|
|
int waitTime = 0;
|
|
if ( RobloxApplicationManager::instance().getApplicationCount() == 1 )
|
|
waitTime = qMax(0,SplashTotalTime - splashTime.elapsed());
|
|
QTimer::singleShot(waitTime,this,SLOT(onDeleteSplashScreen()));
|
|
}
|
|
|
|
setupActionCounters();
|
|
|
|
RobloxKeyboardConfig::singleton().storeDefaults(*this);
|
|
RobloxKeyboardConfig::singleton().loadKeyboardConfig(*this);
|
|
RobloxMouseConfig::singleton().loadMouseConfig();
|
|
|
|
m_IsInitialized = true;
|
|
}
|
|
catch (std::runtime_error const& exp)
|
|
{
|
|
// must make sure the splash screen is hidden before popping up any errors
|
|
try
|
|
{
|
|
if ( m_splashScreen )
|
|
onDeleteSplashScreen();
|
|
}
|
|
catch (...)
|
|
{
|
|
// ignore errors
|
|
}
|
|
|
|
QtUtilities::RBXMessageBox msgBox;
|
|
msgBox.setIcon(QMessageBox::Critical);
|
|
msgBox.setText(exp.what());
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.setDefaultButton(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
throw;
|
|
}
|
|
catch (...)
|
|
{
|
|
// must make sure the splash screen is hidden before popping up any errors
|
|
try
|
|
{
|
|
if ( m_splashScreen )
|
|
onDeleteSplashScreen();
|
|
}
|
|
catch (...)
|
|
{
|
|
// ignore errors
|
|
}
|
|
throw;
|
|
}
|
|
|
|
if (FFlag::StudioSplashScreenUntilOpen && m_splashScreen)
|
|
{
|
|
m_splashScreen->raise();
|
|
m_splashScreen->show();
|
|
}
|
|
|
|
RBX::Log::current()->writeEntry(RBX::Log::Information,"RobloxMainWindow::RobloxMainWindow - end");
|
|
}
|
|
|
|
RobloxMainWindow::~RobloxMainWindow()
|
|
{
|
|
RBX::Log::current()->writeEntry(RBX::Log::Information,"RobloxMainWindow::~RobloxMainWindow");
|
|
StudioUtilities::stopMobileDevelopmentDeployer();
|
|
|
|
m_PropertyChangedConnection.disconnect();
|
|
|
|
//remove all documents
|
|
RobloxDocManager::Instance().shutDown();
|
|
|
|
//stop UI update
|
|
UpdateUIManager::Instance().shutDown();
|
|
|
|
//do a global shut down
|
|
Roblox::globalShutdown();
|
|
|
|
//Set appClosed true before shutdown
|
|
if (RobloxApplicationManager::instance().getApplicationCount() == 1)
|
|
{
|
|
RobloxSettings settings;
|
|
settings.setValue("appClosed", true);
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::setupLogging()
|
|
{
|
|
#ifdef _WIN32
|
|
RBX::Log::setLogProvider(&mainLogManager);
|
|
mainLogManager.WriteCrashDump();
|
|
|
|
dumpErrorUploader.reset(new DumpErrorUploader(true, "Studio"));
|
|
std::string dmpHandlerUrl = GetDmpUrl(::GetBaseURL(), true);
|
|
dumpErrorUploader->InitCrashEvent(dmpHandlerUrl, mainLogManager.getCrashEventName());
|
|
dumpErrorUploader->Upload(dmpHandlerUrl);
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
RBX::Log::setLogProvider(&logProvider);
|
|
#endif
|
|
}
|
|
|
|
void RobloxMainWindow::causeCrash()
|
|
{
|
|
RBXCRASH();
|
|
}
|
|
|
|
bool RobloxMainWindow::checkUpdater(bool showUpdateOptionsDialog, const QString& initDoneEventName) const
|
|
{
|
|
#ifdef Q_WS_MAC
|
|
QString bootstrapper = QCoreApplication::applicationFilePath();
|
|
if(bootstrapper.lastIndexOf("RobloxStudio") > -1)
|
|
bootstrapper = bootstrapper.replace(bootstrapper.lastIndexOf("RobloxStudio"), 12, "RobloxStudio.app/Contents/MacOS/RobloxStudio");
|
|
else
|
|
return false; //can't find proper path
|
|
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "boostrapper path: %s", qPrintable(bootstrapper));
|
|
|
|
QStringList args;
|
|
pid_t processID = getpid();
|
|
QString ppid;
|
|
ppid.setNum(processID);
|
|
QString showUpdate = QString(showUpdateOptionsDialog ? "true" : "false");
|
|
args << "-check" << "true" << "-ppid" << ppid << "-updateUI" << showUpdate;
|
|
QProcess::startDetached(bootstrapper, args);
|
|
#else
|
|
if ( !initDoneEventName.isEmpty() )
|
|
{
|
|
// Bootstrapper will wait for this event, to make sure that app was started
|
|
ATL::CEvent robloxQtStudioStartedEvent;
|
|
if ( robloxQtStudioStartedEvent.Open(EVENT_MODIFY_STATE, FALSE, qPrintable(initDoneEventName)) )
|
|
robloxQtStudioStartedEvent.Set();
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
void RobloxMainWindow::parseCommandLineOptions(const QMap<QString, QString> argMap)
|
|
{
|
|
fileLocationArg = argMap[SharedLauncher::FileLocationArgument];
|
|
urlArg = argMap[SharedLauncher::AuthUrlArgument];
|
|
ticketArg = argMap[SharedLauncher::AuthTicketArgument];
|
|
startEventArg = argMap[SharedLauncher::StartEventArgument];
|
|
readyEventArg = argMap[SharedLauncher::ReadyEventArgument];
|
|
showEventArg = argMap[SharedLauncher::ShowEventArgument];
|
|
scriptArg = argMap[SharedLauncher::ScriptArgument];
|
|
QString sBrowserTrackerId = argMap[SharedLauncher::BrowserTrackerId];
|
|
|
|
QString sWidth = argMap[StudioUtilities::StudioWidthArgument];
|
|
QString sHeight = argMap[StudioUtilities::StudioHeightArgument];
|
|
|
|
// convert loadfile('http://www.roblox.com/game/join.ashx')() to just the url
|
|
if (StudioUtilities::containsJoinScript(scriptArg) && scriptArg.contains("loadfile("))
|
|
{
|
|
int urlBegin = scriptArg.indexOf("(")+2; // skip over the qoute
|
|
int urlEnd = scriptArg.indexOf(")")-1;
|
|
scriptArg = scriptArg.mid(urlBegin, urlEnd - urlBegin);
|
|
}
|
|
|
|
if ( argMap[SharedLauncher::BuildArgument] == "TRUE" )
|
|
m_BuildMode = BM_BASIC;
|
|
else if ( argMap[SharedLauncher::IDEArgument] == "TRUE" )
|
|
m_BuildMode = BM_ADVANCED;
|
|
|
|
if ( argMap[SharedLauncher::TestModeArgument] == "TRUE" )
|
|
StudioUtilities::setTestMode(true);
|
|
if ( argMap[SharedLauncher::AvatarModeArgument] == "TRUE" )
|
|
StudioUtilities::setAvatarMode(true);
|
|
|
|
if ( argMap[StudioUtilities::EmulateTouchArgument] == "TRUE" )
|
|
StudioDeviceEmulator::Instance().setIsEmulatingTouch(true);
|
|
|
|
if ( !sWidth.isEmpty())
|
|
StudioDeviceEmulator::Instance().setCurrentDeviceWidth(sWidth.toInt());
|
|
|
|
if ( !sHeight.isEmpty())
|
|
StudioDeviceEmulator::Instance().setCurrentDeviceHeight(sHeight.toInt());
|
|
|
|
if ( !sBrowserTrackerId.isEmpty())
|
|
RBX::Stats::setBrowserTrackerId(sBrowserTrackerId.toStdString());
|
|
}
|
|
|
|
void RobloxMainWindow::handleCommandLineOptions()
|
|
{
|
|
// make sure sound is disabled if it should be before we create datamodel
|
|
if (StudioUtilities::isTestMode() && !StudioUtilities::isAvatarMode() && StudioUtilities::containsGameServerScript(scriptArg))
|
|
{
|
|
if (AuthoringSettings::singleton().getTestServerAudioBehavior() == AuthoringSettings::OnlineGame)
|
|
{
|
|
RBX::Soundscape::SoundService::soundDisabled = true;
|
|
}
|
|
}
|
|
|
|
// check if fileOpen has already been handled
|
|
if (m_fileOpenHandled)
|
|
return;
|
|
|
|
bool openedPlace = handleFileOpen(fileLocationArg,IRobloxDoc::IDE,scriptArg);
|
|
if(!openedPlace)
|
|
{
|
|
// if nothing else opened, open a start web page
|
|
actionStartPage->setChecked(true);
|
|
}
|
|
#ifdef __APPLE__
|
|
// if Studio is opening in sever/client mode then diable App Nap on Mac (DE13936)
|
|
else if (StudioUtilities::containsGameServerScript(scriptArg) || StudioUtilities::containsJoinScript(scriptArg))
|
|
{
|
|
StudioMacUtilities::disableAppNap("Server/Client Mode");
|
|
}
|
|
#endif
|
|
|
|
if (openedPlace && isRibbonStyle() && StudioUtilities::isTestMode() && !StudioUtilities::isAvatarMode() && StudioUtilities::containsGameServerScript(scriptArg))
|
|
{
|
|
// start local server, so we can cleanup processes started by 'server'
|
|
RobloxApplicationManager::instance().startLocalServer();
|
|
// this is required to update client server related actions
|
|
UpdateUIManager::Instance().updateToolBars();
|
|
|
|
RobloxIDEDoc* pIDEDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
|
|
int numPlayers = RobloxSettings().value("rbxRibbonNumPlayer").toInt();
|
|
if (numPlayers > 0)
|
|
{
|
|
if (pIDEDoc && pIDEDoc->getDataModel())
|
|
{
|
|
StartServerAndPlayerVerb* pVerb = dynamic_cast<StartServerAndPlayerVerb*>(pIDEDoc->getDataModel()->getVerb("StartServerAndPlayerVerb"));
|
|
if (pVerb)
|
|
pVerb->launchPlayers(numPlayers);
|
|
}
|
|
}
|
|
StudioUtilities::startMobileDevelopmentDeployer();
|
|
|
|
if (pIDEDoc && pIDEDoc->getDataModel())
|
|
{
|
|
pIDEDoc->initServerAudioBehavior();
|
|
}
|
|
}
|
|
|
|
if (!openedPlace)
|
|
{
|
|
if (m_splashScreen)
|
|
onDeleteSplashScreen();
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::moveEvent(QMoveEvent * event)
|
|
{
|
|
RobloxRibbonMainWindow::moveEvent(event);
|
|
updateEmbeddedFindPosition();
|
|
}
|
|
|
|
bool RobloxMainWindow::eventFilter(QObject* watched,QEvent* evt)
|
|
{
|
|
if (FFlag::StudioSeparateActionByActivationMethod && evt->type() == QEvent::Shortcut)
|
|
{
|
|
if (QAction* action = dynamic_cast<QAction*>(watched))
|
|
{
|
|
if (commonSlotShortcut(action, !action->isChecked()))
|
|
{
|
|
evt->accept();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef Q_WS_MAC
|
|
// handle special file open command on Mac coming from Finder
|
|
if ( evt->type() == QEvent::FileOpen && watched == qApp )
|
|
{
|
|
if (FFlag::StudioFixMacStartPage)
|
|
{
|
|
// Make sure the start page is the first thing opened.
|
|
actionStartPage->setChecked(true);
|
|
}
|
|
|
|
m_fileOpenHandled = handleFileOpen(static_cast<QFileOpenEvent*>(evt)->file(), IRobloxDoc::IDE);
|
|
return true;
|
|
}
|
|
else if (isRibbonStyle() && (evt->type() == QEvent::Polish) && watched->inherits("QMenu"))
|
|
{
|
|
static_cast<QMenu*>(watched)->setFont(QApplication::font());
|
|
}
|
|
else
|
|
#endif
|
|
if (evt->type() == QEvent::KeyPress || evt->type() == QEvent::ShortcutOverride)
|
|
{
|
|
// ignore shortcut events during busy state
|
|
if ( UpdateUIManager::Instance().isBusy() )
|
|
{
|
|
evt->accept();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (evt->type() == QEvent::NonClientAreaMouseButtonPress)
|
|
Studio::Intellesense::singleton().deactivate();
|
|
|
|
if (evt->type() == QEvent::MouseMove ||
|
|
evt->type() == QEvent::KeyPress)
|
|
{
|
|
keepAliveAnalyticsSession();
|
|
}
|
|
|
|
if ( (evt->type() == QEvent::FocusIn || evt->type() == QEvent::FocusOut) &&
|
|
AuthoringSettings::singleton().onlyPlayFocusWindowAudio )
|
|
{
|
|
if (IRobloxDoc* playDoc = RobloxDocManager::Instance().getPlayDoc())
|
|
{
|
|
if (RobloxIDEDoc* ide = dynamic_cast<RobloxIDEDoc*>(playDoc))
|
|
{
|
|
ide->setMuteAllSounds(!QApplication::activeWindow());
|
|
if (QAction* audioToggleWidget = findChild<QAction*>("audioToggleAction"))
|
|
{
|
|
audioToggleWidget->setChecked(!QApplication::activeWindow());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( watched == qApp )
|
|
return qApp->eventFilter(watched,evt);
|
|
else
|
|
return QMainWindow::eventFilter(watched,evt);
|
|
}
|
|
|
|
void RobloxMainWindow::setWindowLayout()
|
|
{
|
|
actionFullScreen->setChecked(isFullScreen());
|
|
}
|
|
|
|
void RobloxMainWindow::setupViewMenu()
|
|
{
|
|
menuView->insertAction(menuView->actions().value(1), resetViewAction);
|
|
menuView->insertSeparator(resetViewAction);
|
|
|
|
// Add dock widgets to view menu
|
|
// Insert after Start Page. Start Page is first (index 0).
|
|
UpdateUIManager::Instance().setupDockWidgetsViewMenu(*menuView->actions().value(1));
|
|
|
|
// Set up the toolbar
|
|
menuToolBars->addAction(standardToolBar->toggleViewAction());
|
|
menuToolBars->addAction(advToolsToolBar->toggleViewAction());
|
|
menuToolBars->addAction(editCameraToolBar->toggleViewAction());
|
|
menuToolBars->addAction(commandToolBar->toggleViewAction());
|
|
menuToolBars->addAction(runToolBar->toggleViewAction());
|
|
menuToolBars->addAction(viewToolsToolBar->toggleViewAction());
|
|
if (!RBX::MouseCommand::isAdvArrowToolEnabled()) // Remove this, this flag is true
|
|
menuToolBars->addAction(oldToolsToolBar->toggleViewAction());
|
|
}
|
|
|
|
void RobloxMainWindow::toggleFullScreen(bool state)
|
|
{
|
|
if (isFullScreen() == state)
|
|
return;
|
|
|
|
// Toggle
|
|
setWindowState(windowState() ^ Qt::WindowFullScreen);
|
|
|
|
// Toggle fullscreen glitches with layout on Mac for Ribbon Bar (send event to update layout)
|
|
#ifdef Q_WS_MAC
|
|
if (isRibbonStyle())
|
|
{
|
|
Qtitan::RibbonPage* pPage = ribbonBar()->getPage(ribbonBar()->currentIndexPage());
|
|
if (pPage)
|
|
{
|
|
QEvent evt(QEvent::LayoutRequest);
|
|
QApplication::sendEvent(pPage, &evt);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// if we have an IDE, we need to lock the datamodel
|
|
IRobloxDoc* playDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
if ( playDoc )
|
|
{
|
|
RobloxIDEDoc* ide = dynamic_cast<RobloxIDEDoc*>(playDoc);
|
|
if ( ide )
|
|
{
|
|
RBX::DataModel::LegacyLock lock(ide->getDataModel(),RBX::DataModelJob::Write);
|
|
RBX::GameBasicSettings::singleton().setFullScreen(state);
|
|
return;
|
|
}
|
|
}
|
|
|
|
RBX::GameBasicSettings::singleton().setFullScreen(state);
|
|
}
|
|
|
|
bool RobloxMainWindow::commonSlotHelper(const QString& objectName, bool isChecked)
|
|
{
|
|
bool handled = false;
|
|
|
|
//action to be handled by the relevant document
|
|
if (RobloxDocManager::Instance().getCurrentDoc())
|
|
handled = RobloxDocManager::Instance().getCurrentDoc()->handleAction(objectName, isChecked);
|
|
|
|
// Otherwise handle it ourselves
|
|
if (!handled)
|
|
{
|
|
|
|
// Handle global dock actions here
|
|
if (UpdateUIManager::Instance().getDockActionNames().contains(objectName))
|
|
{
|
|
handled = UpdateUIManager::Instance().toggleDockView(objectName);
|
|
}
|
|
else
|
|
{
|
|
// Check if IDE Doc can handle the action
|
|
IRobloxDoc* pPlayDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
if (pPlayDoc)
|
|
handled = pPlayDoc->handleAction(objectName, isChecked);
|
|
}
|
|
}
|
|
|
|
//update toolbar status
|
|
UpdateUIManager::Instance().updateToolBars();
|
|
|
|
return handled;
|
|
}
|
|
|
|
void RobloxMainWindow::commonSlot(bool isChecked)
|
|
{
|
|
const QObject *pSender = sender();
|
|
if (!pSender || dynamic_cast<QuickAccessBarProxyAction*>(sender()))
|
|
return;
|
|
|
|
if (!preventActionCounterSending || !FFlag::StudioSeparateActionByActivationMethod)
|
|
sendActionCounterEvent(pSender->objectName());
|
|
|
|
commonSlotHelper(pSender->objectName(), isChecked);
|
|
}
|
|
|
|
bool RobloxMainWindow::commonSlotShortcut(QAction* action, bool isChecked)
|
|
{
|
|
if (!preventActionCounterSending)
|
|
sendActionCounterEvent(action->objectName() + "Shortcut");
|
|
|
|
return commonSlotHelper(action->objectName(), isChecked);
|
|
}
|
|
|
|
bool RobloxMainWindow::commonSlotQuickAccess(QAction* action)
|
|
{
|
|
if (!preventActionCounterSending)
|
|
sendActionCounterEvent(action->objectName() + "QuickAccess");
|
|
|
|
return commonSlotHelper(action->objectName(), !action->isChecked());
|
|
}
|
|
|
|
void RobloxMainWindow::processAppEvent(void* pClosure)
|
|
{
|
|
// Handle the event
|
|
if (sIsAppRunning)
|
|
RBX::FunctionMarshaller::handleAppEvent(pClosure);
|
|
else
|
|
RBX::FunctionMarshaller::freeAppEvent(pClosure); // control flow comes here means application is closed
|
|
}
|
|
|
|
bool RobloxMainWindow::requestDocClose(IRobloxDoc& doc, bool closeIfLastDoc)
|
|
{
|
|
if ( !doc.getViewer() )
|
|
{
|
|
RobloxDocManager::Instance().removeDoc(doc);
|
|
return true;
|
|
}
|
|
|
|
if ( StudioUtilities::isVideoUploading() || StudioUtilities::isScreenShotUploading())
|
|
{
|
|
QtUtilities::RBXMessageBox msgBox;
|
|
msgBox.setIcon(QMessageBox::Question);
|
|
msgBox.setText(QString("ROBLOX is uploading %1. Quit anyway?").arg(StudioUtilities::isVideoUploading() ? "a video" : "an image"));
|
|
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
msgBox.setDefaultButton(QMessageBox::No);
|
|
|
|
//user has pressed NO or close button, do not close the document
|
|
if ( msgBox.exec() != QMessageBox::Yes )
|
|
return false;
|
|
|
|
// user has pressed YES, modifying the uploading status to make sure
|
|
// we don't show the message multiple times
|
|
StudioUtilities::setVideoUploading(false);
|
|
StudioUtilities::setScreenShotUploading(false);
|
|
}
|
|
|
|
if ( doc.isModified() )
|
|
{
|
|
IRobloxDoc::RBXCloseRequest closeMode = doc.requestClose();
|
|
|
|
if ( closeMode == IRobloxDoc::CLOSE_CANCELED )
|
|
{
|
|
if ( &doc != RobloxDocManager::Instance().getCurrentDoc() )
|
|
RobloxDocManager::Instance().setCurrentDoc(&doc);
|
|
return false;
|
|
}
|
|
else if ( closeMode == IRobloxDoc::REQUEST_HANDLED )
|
|
{
|
|
// LUA will handle it from here on out
|
|
return false;
|
|
}
|
|
else if ( closeMode != IRobloxDoc::NO_SAVE_NEEDED )
|
|
{
|
|
// ask the user if they want to save before close
|
|
|
|
QtUtilities::RBXMessageBox msgBox;
|
|
msgBox.setIcon(QMessageBox::Question);
|
|
msgBox.setText("Save changes to " + doc.displayName() + " ?");
|
|
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
|
|
msgBox.setDefaultButton(QMessageBox::Yes);
|
|
int ret = msgBox.exec();
|
|
|
|
//if the user wants to save
|
|
if ( ret == QMessageBox::Yes && !fileSave(&doc) )
|
|
{
|
|
// User hit the Yes to Save in the Yes No Cancel MsgBox,
|
|
// but then did a Cancel on Save Dialog, change the state to Cancel
|
|
ret = QMessageBox::Cancel;
|
|
}
|
|
|
|
//if the user want to cancel close
|
|
if ( ret == QMessageBox::Cancel )
|
|
{
|
|
if ( &doc != RobloxDocManager::Instance().getCurrentDoc() )
|
|
RobloxDocManager::Instance().setCurrentDoc(&doc);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// special case Start Page since it can be toggled in the View menu
|
|
if ( doc.keyName() == "StartPage" && doc.docType() == IRobloxDoc::BROWSER )
|
|
actionStartPage->setChecked(false);
|
|
else if ( doc.docType() == IRobloxDoc::OBJECTBROWSER )
|
|
objectBrowserAction->setChecked(false);
|
|
#ifdef Q_WS_MAC
|
|
// On Mac, if we do not make the IDE document current then it results in DE7073
|
|
// Combination of QWidget and ViewBase creation coupled with deletion of IDE document
|
|
// without making it current results in non responding keyboard (?)
|
|
else if ((doc.docType() == IRobloxDoc::IDE) && (&doc != RobloxDocManager::Instance().getCurrentDoc()))
|
|
RobloxDocManager::Instance().setCurrentDoc(&doc);
|
|
#endif
|
|
|
|
// WARNING: Do not use doc to call any function after this (it will get deleted)
|
|
RobloxDocManager::Instance().removeDoc(doc);
|
|
|
|
updateWindowTitle();
|
|
|
|
if ( RobloxDocManager::Instance().getDocCount() == 0 && closeIfLastDoc &&
|
|
(RobloxApplicationManager::instance().getApplicationCount() > 1 || getBuildMode() == BM_BASIC))
|
|
{
|
|
// If there are no tabs remaining and this is another instance of Studio open, close this one
|
|
close();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void RobloxMainWindow::fileNew()
|
|
{
|
|
handleFileOpen(NEW_PLACE_FILENAME, IRobloxDoc::IDE);
|
|
}
|
|
|
|
void RobloxMainWindow::openStartPage(bool checked, QString optionalQueryParams /* = "" */)
|
|
{
|
|
if(checked)
|
|
{
|
|
QString fileToOpen;
|
|
|
|
const char* startPageUrl = RBX::ClientAppSettings::singleton().GetValueStartPageUrl();
|
|
if (startPageUrl && startPageUrl[0] != '\0')
|
|
{
|
|
// Generate start page url from our recent files -- these files are put into the query string
|
|
fileToOpen = QString::fromStdString(GetBaseURL()).append(startPageUrl).append("?");
|
|
RobloxSettings settings;
|
|
QStringList recentFiles = settings.value("rbxRecentFiles").toStringList();
|
|
for(int i = 0; i < qMin(recentFiles.length(), (int)MAX_RECENT_FILES); i++)
|
|
{
|
|
QFileInfo info(recentFiles.at(i));
|
|
fileToOpen.append("&filepath=" + QUrl::toPercentEncoding(info.filePath()) + "&filename=" + QUrl::toPercentEncoding(info.fileName()));
|
|
}
|
|
}
|
|
else
|
|
fileToOpen = QString::fromStdString(GetBaseURL() + "/My/Places.aspx");
|
|
|
|
if (!optionalQueryParams.isEmpty())
|
|
fileToOpen.append("&").append(optionalQueryParams);
|
|
|
|
// make sure we are authenticated (if there's any authentication happening) before we open the browser
|
|
AuthenticationHelper::Instance().waitForQtWebkitAuthentication();
|
|
|
|
handleFileOpen(fileToOpen, IRobloxDoc::BROWSER);
|
|
}
|
|
else
|
|
{
|
|
IRobloxDoc* pDoc = RobloxDocManager::Instance().getOrCreateDoc(IRobloxDoc::BROWSER);
|
|
requestDocClose(*pDoc, false);
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::fileOpen()
|
|
{
|
|
QString fileName;
|
|
|
|
fileName = QFileDialog::getOpenFileName(
|
|
this,
|
|
tr("Open Roblox File"),
|
|
m_LastDirectory,
|
|
tr(RobloxIDEDoc::getOpenFileFilters()));
|
|
|
|
if (fileName.isEmpty())
|
|
return;
|
|
|
|
setCurrentDirectory(fileName);
|
|
handleFileOpen(fileName, IRobloxDoc::IDE);
|
|
}
|
|
|
|
void RobloxMainWindow::fileOpenRecentSaves()
|
|
{
|
|
QString fileName;
|
|
|
|
fileName = QFileDialog::getOpenFileName(
|
|
this,
|
|
tr("Open Roblox File"),
|
|
AuthoringSettings::singleton().recentSavesDir.absolutePath(),
|
|
tr(RobloxIDEDoc::getOpenFileFilters()));
|
|
|
|
if (fileName.isEmpty())
|
|
return;
|
|
|
|
setCurrentDirectory(fileName);
|
|
handleFileOpen(fileName, IRobloxDoc::IDE);
|
|
}
|
|
|
|
void RobloxMainWindow::fileClose()
|
|
{
|
|
IRobloxDoc* doc = RobloxDocManager::Instance().getCurrentDoc();
|
|
if ( doc )
|
|
requestDocClose(*doc);
|
|
}
|
|
|
|
bool RobloxMainWindow::verifyFilePermissions(const QString &fileName)
|
|
{
|
|
bool retVal = false;
|
|
|
|
if (!fileName.isEmpty())
|
|
{
|
|
QFileInfo fileInfo(fileName);
|
|
if (fileInfo.exists() && !fileInfo.isWritable())
|
|
{
|
|
QtUtilities::RBXMessageBox msgBox;
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.setText(fileName + "\nThis file is set to read-only.\nTry again with a different name");
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.setDefaultButton(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
}
|
|
else
|
|
{
|
|
retVal = true;
|
|
}
|
|
}
|
|
else /// In case of Empty File return it as True as the file is not present and new file should be created
|
|
{
|
|
retVal = true;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
bool RobloxMainWindow::fileSave(IRobloxDoc *pDoc)
|
|
{
|
|
bool retVal = false;
|
|
|
|
//if no document is provided then get the play document.
|
|
if (!pDoc)
|
|
pDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
|
|
if (pDoc && pDoc->isModified())
|
|
{
|
|
if (verifyFilePermissions(pDoc->fileName()))
|
|
{
|
|
if ( pDoc->fileName().isEmpty() || !pDoc->save() )
|
|
retVal = fileSaveAs(pDoc);
|
|
else
|
|
retVal = true;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
bool RobloxMainWindow::fileSaveAs(IRobloxDoc *pDoc)
|
|
{
|
|
bool retVal = false;
|
|
|
|
//if no document is provided then get the play document
|
|
if (!pDoc)
|
|
pDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
|
|
if (pDoc)
|
|
{
|
|
QString fileName(pDoc->fileName());
|
|
if ( fileName.isEmpty() &&
|
|
boost::filesystem::portable_file_name(pDoc->displayName().toStdString()) )
|
|
fileName = m_LastDirectory + "/" + pDoc->displayName();
|
|
|
|
fileName = QFileDialog::getSaveFileName(this,tr("Save As"),fileName,pDoc->saveFileFilters());
|
|
|
|
if( !fileName.isEmpty() && verifyFilePermissions(fileName) && pDoc->saveAs(fileName))
|
|
{
|
|
setCurrentDirectory(fileName);
|
|
RobloxDocManager::Instance().setDocTitle(*pDoc,pDoc->displayName(),pDoc->titleTooltip(),pDoc->titleIcon());
|
|
updateRecentFile(fileName);
|
|
|
|
m_AutoSaveAccum = 0;
|
|
retVal = true;
|
|
}
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
bool RobloxMainWindow::filePublishedProjects()
|
|
{
|
|
QString url = QString(RBX::ClientAppSettings::singleton().GetValuePublishedProjectsPageUrl());
|
|
if (url.isEmpty())
|
|
return false;
|
|
|
|
if (!StudioUtilities::checkNetworkAndUserAuthentication())
|
|
return false;
|
|
|
|
WebDialog dlg(
|
|
this,
|
|
url.prepend(RobloxSettings::getBaseURL()),
|
|
NULL,
|
|
RBX::ClientAppSettings::singleton().GetValuePublishedProjectsPageWidth(),
|
|
RBX::ClientAppSettings::singleton().GetValuePublishedProjectsPageHeight() );
|
|
dlg.setMinimumSize( FInt::StudioWebDialogMinimumWidth, FInt::StudioWebDialogMinimumHeight);
|
|
dlg.exec();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool RobloxMainWindow::openRecentFile(const QString &fileName)
|
|
{
|
|
if(!fileName.isEmpty())
|
|
{
|
|
QFileInfo fileInfo(fileName);
|
|
if(!fileInfo.exists())
|
|
{
|
|
updateRecentFile(fileName);
|
|
|
|
QtUtilities::RBXMessageBox msgBox;
|
|
msgBox.setIcon(QMessageBox::Warning);
|
|
msgBox.setText(fileName + "\nThis file doesn't exist.");
|
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
msgBox.setDefaultButton(QMessageBox::Ok);
|
|
msgBox.exec();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return handleFileOpen(fileName, IRobloxDoc::IDE);
|
|
}
|
|
|
|
bool RobloxMainWindow::openRecentFile()
|
|
{
|
|
QAction *action = qobject_cast<QAction *>(sender());
|
|
QString fileName = action->data().toString();
|
|
if (action)
|
|
{
|
|
return openRecentFile(fileName);
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void RobloxMainWindow::publishGame()
|
|
{
|
|
if (!StudioUtilities::checkNetworkAndUserAuthentication())
|
|
return;
|
|
|
|
QString initialUrl = QString("%1/IDE/publishgameas").arg(RobloxSettings::getBaseURL());
|
|
|
|
if (!m_publishGameDialog)
|
|
{
|
|
m_publishGameDialog = new WebDialog(this, initialUrl, NULL);
|
|
m_publishGameDialog->setMinimumSize( FInt::StudioWebDialogMinimumWidth, FInt::StudioWebDialogMinimumHeight);
|
|
}
|
|
else
|
|
{
|
|
m_publishGameDialog->load(initialUrl);
|
|
}
|
|
|
|
m_publishGameDialog->show();
|
|
m_publishGameDialog->raise();
|
|
m_publishGameDialog->activateWindow();
|
|
}
|
|
|
|
void RobloxMainWindow::closePublishGameWindow()
|
|
{
|
|
RBXASSERT(m_publishGameDialog);
|
|
if (m_publishGameDialog)
|
|
{
|
|
m_publishGameDialog->close();
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::updateRecentFilesUI()
|
|
{
|
|
if (isRibbonStyle())
|
|
{
|
|
RibbonPageSystemRecentFileList* pRecentFileList = findChild<RibbonPageSystemRecentFileList*>("recentFileList");
|
|
if (pRecentFileList)
|
|
pRecentFileList->updateRecentFileActions(getRecentFiles());
|
|
}
|
|
else
|
|
{
|
|
updateRecentFilesList(getRecentFiles());
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::updateRecentFilesList(const QStringList& files)
|
|
{
|
|
int numRecentFiles = qMin(files.size(), (int)MAX_RECENT_FILES_SHOWN);
|
|
|
|
for (int i = 0; i < numRecentFiles; ++i)
|
|
{
|
|
QFileInfo fileInfo(files[i]);
|
|
QString fullFilename = fileInfo.absoluteFilePath();
|
|
QString filename = fullFilename;
|
|
|
|
if ( filename.startsWith(m_LastDirectory) )
|
|
filename = filename.right(filename.length() - 1 - m_LastDirectory.length());
|
|
|
|
bool isTextModified = false;
|
|
if ( filename.length() > MaxLengthFilenameMRU )
|
|
{
|
|
QFontMetrics fm = fontMetrics();
|
|
filename = fm.elidedText(filename,Qt::ElideMiddle,MaxLengthFilenameMRU * fm.width('0'));
|
|
isTextModified = true;
|
|
}
|
|
|
|
recentOpenedFiles[i]->setText(QString("&%1 %2").arg(i + 1).arg(filename));
|
|
|
|
if(isTextModified)
|
|
recentOpenedFiles[i]->setToolTip(fullFilename);
|
|
|
|
recentOpenedFiles[i]->setData(fullFilename);
|
|
recentOpenedFiles[i]->setStatusTip(fullFilename);
|
|
recentOpenedFiles[i]->setVisible(true);
|
|
}
|
|
|
|
for (int j = numRecentFiles; j < (int)MAX_RECENT_FILES_SHOWN; ++j)
|
|
recentOpenedFiles[j]->setVisible(false);
|
|
|
|
separator->setVisible(numRecentFiles > 0);
|
|
}
|
|
|
|
void RobloxMainWindow::updateInternalWidgetsState(QAction* pAction, bool enabledState,bool checkedState)
|
|
{
|
|
if (isRibbonStyle())
|
|
RobloxRibbonMainWindow::updateInternalWidgetsState(pAction, enabledState, checkedState);
|
|
}
|
|
|
|
#define COPYRIGHT_PREFIX 0xA9
|
|
|
|
void RobloxMainWindow::about()
|
|
{
|
|
QString aboutMsg(tr("<div style=\"text-align: center\">"));
|
|
aboutMsg.append(tr("<p style=\"font-weight: bold\">"));
|
|
aboutMsg.append(tr("ROBLOX Studio "));
|
|
aboutMsg.append(tr("Version %1</p>"));
|
|
|
|
#if defined(_NOOPT) || defined(_DEBUG)
|
|
aboutMsg.append(tr("<br><hr><br><br>DEBUG ONLY<br><hr>"));
|
|
|
|
QString build_type;
|
|
|
|
#ifdef _NOOPT
|
|
build_type = "NoOpt";
|
|
#else
|
|
build_type = "Debug";
|
|
#endif
|
|
|
|
aboutMsg.append("Build Type: " + build_type);
|
|
aboutMsg.append("<br>Path: ");
|
|
|
|
const int threshold = 50;
|
|
QString full_path = QCoreApplication::applicationFilePath();
|
|
while ( !full_path.isEmpty() )
|
|
{
|
|
aboutMsg.append(full_path.left(threshold) + "<br>");
|
|
if ( full_path.length() >= threshold )
|
|
{
|
|
full_path = full_path.right(full_path.length() - threshold);
|
|
if ( !full_path.isEmpty() )
|
|
aboutMsg.append(" ");
|
|
}
|
|
else
|
|
{
|
|
full_path = "";
|
|
}
|
|
}
|
|
|
|
aboutMsg.append("Qt Version: ");
|
|
aboutMsg.append(qVersion());
|
|
|
|
// display command line argument
|
|
aboutMsg.append("<br><br>");
|
|
aboutMsg.append("Arguments:<br>");
|
|
|
|
QStringList args = QApplication::arguments();
|
|
for ( int i = 1 ; i < args.size() ; ++i )
|
|
aboutMsg.append(QString::number(i) + ") '" + args[i] + "'<br>");
|
|
|
|
aboutMsg.append("<hr><br>");
|
|
#endif
|
|
|
|
aboutMsg = aboutMsg.arg(RobloxSettings::getVersionString());
|
|
aboutMsg.append(tr("<p>ROBLOX, \"Online Building Toy\", characters, logos, names, and all related indicia "));
|
|
|
|
QString copyrightString;
|
|
copyrightString.sprintf("are trademarks of ROBLOX Corporation %c2014. ", COPYRIGHT_PREFIX);
|
|
|
|
aboutMsg.append(copyrightString);
|
|
aboutMsg.append(tr("US Patents 7,874,921. Patents pending.</p>"));
|
|
|
|
aboutMsg.append(tr("<p>Contact us at info@roblox.com.</p></div>"));
|
|
|
|
QMessageBox::about(this, tr("Welcome to ROBLOX!"), aboutMsg);
|
|
}
|
|
|
|
void RobloxMainWindow::onlineHelp()
|
|
{
|
|
QUrl helpUrl("http://wiki.roblox.com/index.php/Studio", QUrl::TolerantMode);
|
|
QDesktopServices::openUrl(helpUrl);
|
|
}
|
|
|
|
void RobloxMainWindow::shortcutHelp()
|
|
{
|
|
if ( !m_pInputConfigDialog )
|
|
m_pInputConfigDialog = new RobloxInputConfigDialog(*this);
|
|
|
|
if ( m_pInputConfigDialog->isVisible() )
|
|
m_pInputConfigDialog->hide();
|
|
else
|
|
{
|
|
m_pInputConfigDialog->show();
|
|
m_pInputConfigDialog->raise();
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::fastLogDump()
|
|
{
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Log Dumps");
|
|
#ifdef _WIN32
|
|
mainLogManager.CreateFakeCrashDump();
|
|
#else
|
|
CrashReporter::CreateLogDump();
|
|
#endif
|
|
}
|
|
|
|
void RobloxMainWindow::instanceDump()
|
|
{
|
|
}
|
|
|
|
void RobloxMainWindow::openPluginsFolder()
|
|
{
|
|
QString userPluginPath = RobloxPluginHost::userPluginPath();
|
|
QDir pluginDir(userPluginPath);
|
|
|
|
if (!pluginDir.exists())
|
|
pluginDir.mkpath(userPluginPath);
|
|
|
|
QDesktopServices::openUrl(QUrl::fromLocalFile(userPluginPath));
|
|
}
|
|
|
|
void RobloxMainWindow::managePlugins()
|
|
{
|
|
if (!StudioUtilities::checkNetworkAndUserAuthentication())
|
|
return;
|
|
|
|
if (!m_managePluginsDoc)
|
|
{
|
|
m_managePluginsDoc.reset(new RobloxWebDoc(tr("Plugin Management"), "PluginManagement"));
|
|
RobloxWebDoc* webDoc = dynamic_cast<RobloxWebDoc*>(
|
|
RobloxDocManager::Instance().getOrCreateDoc(IRobloxDoc::BROWSER));
|
|
connect(webDoc->getWorkspace().get(), SIGNAL(PluginInstallComplete(bool, int)),
|
|
m_managePluginsDoc.get(), SLOT( refreshPage()));
|
|
}
|
|
m_managePluginsDoc->open(this,
|
|
QString("%1/studio/plugins/manage").arg(RobloxSettings::getBaseURL()));
|
|
QWidget *widgetToAddInTab = m_managePluginsDoc->getViewer();
|
|
if (widgetToAddInTab)
|
|
{
|
|
RobloxDocManager::Instance().configureDocWidget(*m_managePluginsDoc);
|
|
}
|
|
}
|
|
|
|
RobloxWebDoc* RobloxMainWindow::getConfigureWebDoc()
|
|
{
|
|
if (!m_configureGameDoc)
|
|
{
|
|
m_configureGameDoc.reset(new RobloxWebDoc(tr("Configure"), "GameEntityConfigure"));
|
|
RobloxDocManager::Instance().getOrCreateDoc(IRobloxDoc::BROWSER);
|
|
}
|
|
// The widget doesn't show up in the tabs unless open() has been called at least once.
|
|
m_configureGameDoc->open(this, "");
|
|
QWidget *widgetToAddInTab = m_configureGameDoc->getViewer();
|
|
if (widgetToAddInTab)
|
|
{
|
|
RobloxDocManager::Instance().configureDocWidget(*m_configureGameDoc);
|
|
}
|
|
|
|
return m_configureGameDoc.get();
|
|
}
|
|
|
|
void RobloxMainWindow::closeConfigureWebDoc()
|
|
{
|
|
if (m_configureGameDoc)
|
|
{
|
|
requestDocClose(m_configureGameDoc.get());
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::openObjectBrowser(bool checked)
|
|
{
|
|
if ( checked )
|
|
{
|
|
handleFileOpen(QString(""), IRobloxDoc::OBJECTBROWSER);
|
|
}
|
|
else
|
|
{
|
|
IRobloxDoc* pDoc = RobloxDocManager::Instance().getOrCreateDoc(IRobloxDoc::OBJECTBROWSER);
|
|
requestDocClose(*pDoc);
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::openSettingsDialog()
|
|
{
|
|
if ( !m_pSettingsDialog )
|
|
m_pSettingsDialog = new RobloxSettingsDialog(this);
|
|
m_pSettingsDialog->exec();
|
|
|
|
// perform any fixup if settings changed
|
|
assignAccelerators();
|
|
}
|
|
|
|
void RobloxMainWindow::executeScriptFile()
|
|
{
|
|
RBXASSERT(RobloxDocManager::Instance().getCurrentDoc());
|
|
RBXASSERT(RobloxDocManager::Instance().getCurrentDoc() == RobloxDocManager::Instance().getPlayDoc());
|
|
|
|
//check is there's no current document then create one
|
|
if (!RobloxDocManager::Instance().getCurrentDoc())
|
|
fileNew();
|
|
|
|
//still no document? can't do anything!
|
|
if (!RobloxDocManager::Instance().getCurrentDoc())
|
|
return;
|
|
|
|
//open the script file
|
|
QString fileName;
|
|
|
|
fileName = QFileDialog::getOpenFileName(
|
|
this,
|
|
tr("Open Script File"),
|
|
AuthoringSettings::singleton().defaultScriptFileDir.absolutePath(),
|
|
tr("Scripts (*.rbxs *.lua *.txt);;All Files (*.*)"));
|
|
|
|
if (fileName.isEmpty())
|
|
return;
|
|
|
|
QFileInfo info(fileName);
|
|
AuthoringSettings::singleton().defaultScriptFileDir = info.dir();
|
|
|
|
QFile file(fileName);
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
return;
|
|
|
|
//read file
|
|
QTextStream inStream(&file);
|
|
QString toExecute = inStream.readAll();
|
|
|
|
if (!toExecute.isEmpty())
|
|
{
|
|
if (RobloxIDEDoc* ideDoc = RobloxDocManager::Instance().getPlayDoc())
|
|
ideDoc->handleScriptCommand(toExecute);
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::onCustomToolButton(const QString& selectedItem)
|
|
{
|
|
const QObject *pSender = sender();
|
|
if (!pSender)
|
|
return;
|
|
|
|
//action to be handled by the relevant document
|
|
if (!RobloxDocManager::Instance().getCurrentDoc())
|
|
return;
|
|
|
|
QString modifiedActionName(pSender->objectName());
|
|
modifiedActionName.append("_");
|
|
modifiedActionName.append(selectedItem);
|
|
|
|
RobloxDocManager::Instance().getCurrentDoc()->handleAction(modifiedActionName);
|
|
|
|
//update toolbar status
|
|
UpdateUIManager::Instance().updateToolBars();
|
|
}
|
|
|
|
void RobloxMainWindow::onMenuActionHovered(QAction* action)
|
|
{
|
|
if(!action)
|
|
return;
|
|
|
|
for (int i = 0; i < MAX_RECENT_FILES_SHOWN; ++i)
|
|
{
|
|
if(action == recentOpenedFiles[i])
|
|
{
|
|
QString tip = action->toolTip();
|
|
if(tip != action->text().remove(0,1))
|
|
QToolTip::showText(QCursor::pos(), tip);
|
|
else
|
|
QToolTip::hideText();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::closeEvent(QCloseEvent* evt)
|
|
{
|
|
RBX::Log::current()->writeEntry(RBX::Log::Information,"RobloxMainWindow::closeEvent");
|
|
|
|
if ( !m_IsInitialized || UpdateUIManager::Instance().isBusy() )
|
|
{
|
|
evt->ignore();
|
|
return;
|
|
}
|
|
|
|
if (FFlag::StudioSettingsGAEnabled)
|
|
AuthoringSettings::singleton().setDoSettingsChangedGAEvents(false);
|
|
|
|
Studio::Intellesense::singleton().deactivate();
|
|
|
|
// cleanup Wiki search lookup table
|
|
ScriptTextEditor::cleanupWikiLookup();
|
|
|
|
|
|
// first save and close out the play doc if we're in build mode
|
|
if ( m_BuildMode == BM_BASIC )
|
|
{
|
|
IRobloxDoc* playDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
if ( playDoc && !requestDocClose(*playDoc) )
|
|
{
|
|
evt->ignore();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// save and close the rest of the docs
|
|
if ( !RobloxDocManager::Instance().requestCloseAllDocs() )
|
|
{
|
|
evt->ignore();
|
|
return;
|
|
}
|
|
|
|
//before close the application, save its states.
|
|
saveApplicationStates();
|
|
// remove event filter
|
|
if (FFlag::StudioInSyncWebKitAuthentication)
|
|
qApp->removeEventFilter(&AuthenticationHelper::Instance());
|
|
|
|
hide();
|
|
|
|
studioAnalytics.savePersistentVariables();
|
|
studioAnalytics.reportPersistentVariables();
|
|
|
|
QMainWindow::closeEvent(evt);
|
|
}
|
|
|
|
/**
|
|
* Close all documents and save locally.
|
|
*/
|
|
void RobloxMainWindow::saveAndClose()
|
|
{
|
|
if ( RobloxDocManager::Instance().requestCloseAndSaveAllDocs() )
|
|
close();
|
|
}
|
|
|
|
/**
|
|
* Closes all documents but doesn't save.
|
|
*/
|
|
void RobloxMainWindow::forceClose()
|
|
{
|
|
RobloxDocManager::Instance().closeAllDocs();
|
|
close();
|
|
}
|
|
|
|
void RobloxMainWindow::contextMenuEvent(QContextMenuEvent* evt)
|
|
{
|
|
// don't show the main window context menu if in basic mode
|
|
if ( m_BuildMode != BM_BASIC )
|
|
QMainWindow::contextMenuEvent(evt);
|
|
}
|
|
|
|
void RobloxMainWindow::dragEnterEvent(QDragEnterEvent *evt)
|
|
{
|
|
const QMimeData *pMimeData = evt->mimeData();
|
|
if (!pMimeData || !pMimeData->hasUrls())
|
|
return QMainWindow::dragEnterEvent(evt);
|
|
|
|
bool isValidFileList = false;
|
|
|
|
QList<QUrl> urlList = pMimeData->urls();
|
|
for (int i = 0; i < urlList.size() && i < 6; ++i)
|
|
{
|
|
QString filePath = urlList.at(i).toLocalFile();
|
|
isValidFileList =
|
|
!filePath.isEmpty() &&
|
|
(filePath.endsWith(".rbxl", Qt::CaseInsensitive) ||
|
|
filePath.endsWith(".rbxlx", Qt::CaseInsensitive) ||
|
|
filePath.endsWith(".rbxm", Qt::CaseInsensitive) ||
|
|
filePath.endsWith(".rbxmx", Qt::CaseInsensitive));
|
|
|
|
if (!isValidFileList)
|
|
break;
|
|
}
|
|
|
|
if (!isValidFileList)
|
|
return QMainWindow::dragEnterEvent(evt);
|
|
|
|
evt->acceptProposedAction();
|
|
}
|
|
|
|
void RobloxMainWindow::dragMoveEvent(QDragMoveEvent *evt)
|
|
{
|
|
evt->acceptProposedAction();
|
|
}
|
|
|
|
void RobloxMainWindow::dropEvent(QDropEvent *evt)
|
|
{
|
|
const QMimeData *pMimeData = evt->mimeData();
|
|
|
|
if (!pMimeData || !pMimeData->hasUrls())
|
|
return QMainWindow::dropEvent(evt);
|
|
|
|
QList<QUrl> urlList = pMimeData->urls();
|
|
for (int i = 0; i < urlList.size() && i < 6; ++i)
|
|
{
|
|
QString fileName = urlList.at(i).toLocalFile();
|
|
if (!fileName.isEmpty())
|
|
{
|
|
if(fileName.endsWith(".rbxl", Qt::CaseInsensitive) || fileName.endsWith(".rbxlx", Qt::CaseInsensitive))
|
|
handleFileOpen(fileName, IRobloxDoc::IDE);
|
|
else
|
|
{
|
|
if(RobloxDocManager::Instance().getCurrentDoc())
|
|
RobloxDocManager::Instance().getCurrentDoc()->handleDrop(fileName);
|
|
}
|
|
}
|
|
}
|
|
|
|
evt->acceptProposedAction();
|
|
}
|
|
|
|
void RobloxMainWindow::dragLeaveEvent(QDragLeaveEvent *evt)
|
|
{
|
|
evt->accept();
|
|
}
|
|
|
|
void RobloxMainWindow::initializeUI()
|
|
{
|
|
|
|
// Apply the global Roblox Studio style sheet to the entire application.
|
|
// RobloxStudio.css is where we can tweak and tune the look-and-feel
|
|
// of the Studio UI. All style overrides that are application-wide
|
|
// should be put there.
|
|
setStyleSheet(QtUtilities::getResourceFileText(":/RobloxStudio.css"));
|
|
|
|
// force height so we don't get any resizing when adding and removing controls to the status bar
|
|
statusBar()->setFixedHeight(32);
|
|
// Apply the global Roblox Studio style sheet to the entire application.
|
|
|
|
QString css;
|
|
|
|
if ( isRibbonStyle() )
|
|
{
|
|
//css = QtUtilities::getResourceFileText(":/RobloxStudioRibbon.css");
|
|
}
|
|
else
|
|
{
|
|
//css = QtUtilities::getResourceFileText(":/RobloxStudio.css");
|
|
|
|
//TODO: Move all these customizations to .ui file
|
|
menuTools->insertAction(openPluginsFolderAction, screenShotAction);
|
|
#ifdef _WIN32
|
|
menuTools->insertAction(openPluginsFolderAction, toggleVideoRecordAction);
|
|
#endif
|
|
|
|
menuTools->insertSeparator(openPluginsFolderAction);
|
|
|
|
advToolsToolBar->insertAction(advanceJointCreationManualAction, toggleCollisionCheckAction);
|
|
advToolsToolBar->insertSeparator(advanceJointCreationManualAction);
|
|
|
|
if (RBX::MouseCommand::isAdvArrowToolEnabled())
|
|
removeToolBar(oldToolsToolBar);
|
|
|
|
// Added so that we can dump Crash for Breakpad as needed
|
|
// To enable crashing add following to AppSettings.xml
|
|
// <CrashMenu>1</CrashMenu>
|
|
if(RobloxSettings::showCrashMenu())
|
|
{
|
|
QAction *crashAction = new QAction("&Crash", this);
|
|
connect(crashAction, SIGNAL(triggered()), this, SLOT(causeCrash()));
|
|
QMenu* debugMenu = menuBar()->addMenu("&Debug");
|
|
debugMenu->addAction(crashAction);
|
|
}
|
|
|
|
advToolsToolBar->insertAction(glueSurfaceAction, smoothNoOutlinesAction);
|
|
}
|
|
|
|
//setStyleSheet(css);
|
|
Studio::Intellesense::singleton().setStyleSheet(css);
|
|
|
|
//support drag drop
|
|
setAcceptDrops(true);
|
|
|
|
// fix focus issues in Mac
|
|
setFocusPolicy(Qt::StrongFocus);
|
|
|
|
//default is max screen
|
|
setWindowState(Qt::WindowMaximized);
|
|
|
|
QTimer* minute_timer = new QTimer(this);
|
|
connect(minute_timer,SIGNAL(timeout()),this,SLOT(onMinuteTimer()));
|
|
minute_timer->start(60 * 1000);
|
|
|
|
m_AnalyticTrackedConnection = RBX::RobloxGoogleAnalytics::analyticTrackedSignal().connect(boost::bind(&RobloxMainWindow::onAnalyticTracked, this));
|
|
|
|
qApp->installEventFilter(this);
|
|
|
|
// Initialize the doc manager
|
|
RobloxDocManager::Instance().initialize(*this);
|
|
}
|
|
|
|
void RobloxMainWindow::setupSlots()
|
|
{
|
|
//file menu
|
|
connect(menuFile, SIGNAL(hovered(QAction*)), this, SLOT(onMenuActionHovered(QAction*)));
|
|
connect(fileNewAction, SIGNAL(triggered()), this, SLOT(fileNew()));
|
|
connect(fileOpenAction, SIGNAL(triggered()), this, SLOT(fileOpen()));
|
|
connect(fileCloseAction, SIGNAL(triggered()), this, SLOT(fileClose()));
|
|
connect(fileSaveAction, SIGNAL(triggered()), this, SLOT(fileSave()));
|
|
connect(fileSaveAsAction, SIGNAL(triggered()), this, SLOT(fileSaveAs()));
|
|
connect(fileExitAction, SIGNAL(triggered()), this, SLOT(close()));
|
|
connect(publishGameAction, SIGNAL(triggered()), this, SLOT(publishGame()));
|
|
publishGameAction->setEnabled(false);
|
|
|
|
connect(fileOpenRecentSavesAction, SIGNAL(triggered()), this, SLOT(fileOpenRecentSaves()));
|
|
|
|
// create global shortcuts that should always work even if actions are disabled
|
|
|
|
QShortcut* shortcut = new QShortcut(fileExitAction->shortcut(),this);
|
|
connect(shortcut,SIGNAL(activated()),this,SLOT(close()));
|
|
|
|
shortcut = new QShortcut(toggleBuildModeAction->shortcut(),this);
|
|
connect(shortcut,SIGNAL(activated()),this,SLOT(toggleBuildMode()));
|
|
|
|
addAction(zoomExtentsAction);
|
|
addAction(toggleLocalSpaceAction);
|
|
addAction(quickInsertAction);
|
|
addAction(explorerFilterAction);
|
|
|
|
pairRbxDeviceAction->setEnabled(true);
|
|
playRbxDeviceAction->setEnabled(true);
|
|
|
|
if (RBX::ClientAppSettings::singleton().GetValuePublishedProjectsPageUrl() && RBX::ClientAppSettings::singleton().GetValuePublishedProjectsPageUrl()[0] != '\0')
|
|
{
|
|
connect(filePublishedProjectsAction, SIGNAL(triggered()), this, SLOT(filePublishedProjects()));
|
|
}
|
|
else
|
|
{
|
|
filePublishedProjectsAction->setEnabled(false);
|
|
filePublishedProjectsAction->setVisible(false);
|
|
}
|
|
|
|
//set up recent open files slots
|
|
for (int i = 0; i < MAX_RECENT_FILES_SHOWN; ++i)
|
|
{
|
|
if (!isRibbonStyle())
|
|
{
|
|
recentOpenedFiles[i] = new QAction(this);
|
|
menuFile->insertAction(fileExitAction, recentOpenedFiles[i]);
|
|
recentOpenedFiles[i]->setVisible(false);
|
|
connect(recentOpenedFiles[i], SIGNAL(triggered()), this, SLOT(openRecentFile()));
|
|
}
|
|
else
|
|
{
|
|
recentOpenedFiles[i] = NULL;
|
|
}
|
|
}
|
|
|
|
QMenu* pMenu = isRibbonStyle() ? findChild<QMenu*>("switchWindowsMenu") : menuWindow;
|
|
|
|
//for window menu open tabs slots
|
|
for ( int i = 0 ; i < MAX_DOC_WINDOWS ; ++i )
|
|
{
|
|
currentOpenedfiles[i] = new QAction(this);
|
|
|
|
if (pMenu)
|
|
pMenu->addAction(currentOpenedfiles[i]);
|
|
|
|
currentOpenedfiles[i]->setVisible(false);
|
|
connect(
|
|
currentOpenedfiles[i],SIGNAL(triggered()),
|
|
&RobloxDocManager::Instance(),SLOT(onActivateWindow()) );
|
|
currentOpenedfiles[i]->setCheckable(true);
|
|
}
|
|
|
|
separator = menuFile->insertSeparator(fileExitAction);
|
|
|
|
//reset view
|
|
connect(resetViewAction, SIGNAL(triggered()), this, SLOT(loadDefaultApplicationState()));
|
|
|
|
//execute script
|
|
connect(executeScriptAction, SIGNAL(triggered()), this, SLOT(executeScriptFile()));
|
|
|
|
// Tools->Settings....
|
|
connect(settingsAction, SIGNAL(triggered()), this, SLOT(openSettingsDialog()));
|
|
|
|
// Tools Menu
|
|
instanceDumpAction->setVisible(false);
|
|
|
|
connect(openPluginsFolderAction, SIGNAL(triggered()), this, SLOT(openPluginsFolder()));
|
|
connect(managePluginsAction, SIGNAL(triggered()), this, SLOT(managePlugins()));
|
|
|
|
// Help Menu
|
|
connect(aboutRobloxAction, SIGNAL(triggered()), this, SLOT(about()));
|
|
connect(onlineHelpAction, SIGNAL(triggered()), this, SLOT(onlineHelp()));
|
|
connect(shortcutHelpAction, SIGNAL(triggered()), this, SLOT(shortcutHelp()));
|
|
connect(objectBrowserAction, SIGNAL(toggled(bool)), this, SLOT(openObjectBrowser(bool)));
|
|
connect(fastLogDumpAction, SIGNAL(triggered()), this, SLOT(fastLogDump()));
|
|
|
|
connect(viewCommandBarAction, SIGNAL(toggled(bool)), commandToolBar, SLOT(setVisible(bool)));
|
|
|
|
//Insert Object
|
|
if (FFlag::StudioSeparateActionByActivationMethod)
|
|
connect(quickInsertAction, SIGNAL(triggered()), &UpdateUIManager::Instance(), SLOT(commonSlot()));
|
|
else
|
|
connect(quickInsertAction, SIGNAL(triggered()), &UpdateUIManager::Instance(), SLOT(onQuickInsertFocus()));
|
|
|
|
connect(explorerFilterAction, SIGNAL(triggered()), &UpdateUIManager::Instance(), SLOT(filterExplorer()));
|
|
|
|
//Cleaup players and servers
|
|
connect(cleanupServersAndPlayersAction, SIGNAL(triggered()), this, SLOT(cleanupPlayersAndServers()));
|
|
|
|
// Insert Service
|
|
if (!m_pInsertServiceDlg)
|
|
m_pInsertServiceDlg = new InsertServiceDialog(this);
|
|
connect(insertServiceAction, SIGNAL(triggered()), m_pInsertServiceDlg, SLOT(show()));
|
|
|
|
connect(findInScriptsAction, SIGNAL(triggered()), this, SLOT(showFindAllDialog()));
|
|
|
|
emulateDeviceAction->setEnabled(true);
|
|
manageEmulationDeviceAction->setEnabled(true);
|
|
|
|
viewCommandBarAction->setEnabled(true);
|
|
testCustomStatsAction->setEnabled(FFlag::StudioCustomStatsEnabled);
|
|
|
|
QAction* commonActions[] =
|
|
{
|
|
rotateSelectionAction, tiltSelectionAction, groupSelectionAction, ungroupSelectionAction,
|
|
selectChildrenAction, moveUpBrickAction, moveDownBrickAction, deleteSelectedAction,
|
|
selectAllAction, lockAction, glueSurfaceAction,
|
|
|
|
// stats menu
|
|
testStatsAction, testRenderStatsAction, testPhysicsStatsAction, testNetworkStatsAction,
|
|
testSummaryStatsAction, testCustomStatsAction, testClearStatsAction,
|
|
|
|
smoothSurfaceAction, weldSurfaceAction, studsAction, inletAction, universalsAction,
|
|
hingeAction, anchorAction, motorRightAction, smoothNoOutlinesAction,
|
|
dropperAction, simulationRunAction, simulationPlayAction, simulationStopAction, simulationResetAction,
|
|
zoomInAction, zoomOutAction, tiltUpAction, tiltDownAction,
|
|
zoomExtentsAction, panRightAction, panLeftAction, advanceJointCreationManualAction,
|
|
gridToOneAction,gridToOneFifthAction, gridToOffAction,
|
|
actionFillColor, actionMaterial, advTranslateAction, advRotateAction, resizeAction,
|
|
cutAction, copyAction, pasteAction, duplicateSelectionAction,
|
|
pasteIntoAction, undoAction, redoAction, playSoloAction, startServerAction,
|
|
startPlayerAction, pairRbxDeviceAction, insertModelAction, insertIntoFileAction,
|
|
selectionSaveToFileAction, publishToRobloxAction, publishToRobloxAsAction, publishGameAction, publishSelectionToRobloxAction,
|
|
saveToRobloxAction, publishAsPluginAction, createNewLinkedSourceAction, advArrowToolAction, toggleAxisWidgetAction, toggle3DGridAction,
|
|
toggleVideoRecordAction, viewDiagnosticsAction, viewTaskSchedulerAction, viewToolboxAction,
|
|
viewBasicObjectsAction, viewScriptPerformanceAction, viewObjectExplorerAction,
|
|
viewPropertiesAction, viewOutputWindowAction, viewContextualHelpAction, viewFindResultsWindowAction, viewScriptAnalysisAction,
|
|
gameExplorerAction, toggleCollisionCheckAction,
|
|
renameObjectAction, unlockAllAction, openPluginsFolderAction, screenShotAction, toggleLocalSpaceAction,
|
|
quickInsertAction, explorerFilterAction, exportSelectionAction, exportPlaceAction,
|
|
unionSelectionAction, negateSelectionAction, separateSelectionAction, viewTutorialsAction, viewTeamCreateAction,
|
|
|
|
// script actions
|
|
goToScriptErrorAction, commentSelectionAction, uncommentSelectionAction, toggleCommentAction,
|
|
expandAllFoldsAction, collapseAllFoldsAction, findAction, replaceAction, findNextAction, goToLineAction,
|
|
findPreviousAction, resetScriptZoomAction, reloadScriptAction, neverBreakOnScriptErrorsAction, breakOnAllScriptErrorsAction,
|
|
breakOnUnhandledScriptErrorsAction, manageEmulationDeviceAction,
|
|
|
|
gridSizeToTwoAction, gridSizeToFourAction, gridSizeToSixteenAction, launchHelpForSelectionAction, downloadPlaceCopyAction
|
|
};
|
|
|
|
viewTutorialsAction->setVisible(FFlag::StudioShowTutorialsByDefault);
|
|
downloadPlaceCopyAction->setVisible(false);
|
|
|
|
int numActions = sizeof(commonActions) / sizeof(QAction*);
|
|
for ( int i = 0; i < numActions; ++i )
|
|
{
|
|
RBX::BaldPtr<QAction> action = commonActions[i];
|
|
if (!FFlag::StudioSeparateActionByActivationMethod)
|
|
connect(action,SIGNAL(triggered(bool)),this,SLOT(commonSlot(bool)));
|
|
|
|
QList<QKeySequence> shortcuts = action->shortcuts();
|
|
if ( !shortcuts.isEmpty() )
|
|
{
|
|
// add the shortcuts to the tooltip
|
|
QtUtilities::setActionShortcuts(*action,shortcuts);
|
|
}
|
|
else
|
|
{
|
|
// set status tip
|
|
if ( action->statusTip().isEmpty() )
|
|
action->setStatusTip(action->toolTip());
|
|
}
|
|
}
|
|
|
|
connect(&Roblox::Instance(),SIGNAL(marshallAppEvent(void*,bool)), this,SLOT(processAppEvent(void*)));
|
|
connect(actionStartPage, SIGNAL(toggled(bool)), this, SLOT(openStartPage(bool)));
|
|
connect(actionFullScreen, SIGNAL(toggled(bool)), this, SLOT(toggleFullScreen(bool)));
|
|
|
|
updateShortcutSet();
|
|
}
|
|
|
|
void RobloxMainWindow::updateShortcutSet()
|
|
{
|
|
if (FFlag::DontSwallowInputForStudioShortcuts)
|
|
{
|
|
shortcutSet.clear();
|
|
QObjectList objects = UpdateUIManager::Instance().getMainWindow().children();
|
|
for (QObjectList::iterator iter = objects.begin(); iter != objects.end() ; ++iter )
|
|
{
|
|
RBX::BaldPtr<QAction> action = dynamic_cast<QAction*>(*iter);
|
|
if (action && !action->text().isEmpty() &&!action->text().startsWith("&"))
|
|
{
|
|
shortcutSet.insert(action->shortcut().toString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool RobloxMainWindow::isShortcut(const QKeySequence& keySequence)
|
|
{
|
|
return (shortcutSet.find(keySequence.toString()) != shortcutSet.end());
|
|
}
|
|
|
|
/**
|
|
* Configure the shortcuts for the actions that are platform specific.
|
|
* Common actions have different shortcuts on different platforms. This
|
|
* function will reconfigure the common action shortcuts for the platform.
|
|
*/
|
|
void RobloxMainWindow::assignAccelerators()
|
|
{
|
|
// file menu
|
|
QtUtilities::setActionShortcuts(*fileNewAction,QKeySequence::keyBindings(QKeySequence::New));
|
|
QtUtilities::setActionShortcuts(*fileOpenAction,QKeySequence::keyBindings(QKeySequence::Open));
|
|
QtUtilities::setActionShortcuts(*fileSaveAction,QKeySequence::keyBindings(QKeySequence::Save));
|
|
QtUtilities::setActionShortcuts(*fileSaveAsAction,QKeySequence::keyBindings(QKeySequence::SaveAs));
|
|
QtUtilities::setActionShortcuts(*fileCloseAction,QKeySequence::keyBindings(QKeySequence::Close));
|
|
QtUtilities::setActionShortcuts(*fileExitAction,QKeySequence::keyBindings(QKeySequence::Quit));
|
|
|
|
// edit menu
|
|
QtUtilities::setActionShortcuts(*copyAction,QKeySequence::keyBindings(QKeySequence::Copy));
|
|
QtUtilities::setActionShortcuts(*cutAction,QKeySequence::keyBindings(QKeySequence::Cut));
|
|
QtUtilities::setActionShortcuts(*pasteAction,QKeySequence::keyBindings(QKeySequence::Paste));
|
|
QtUtilities::setActionShortcuts(*redoAction,QKeySequence::keyBindings(QKeySequence::Redo));
|
|
QtUtilities::setActionShortcuts(*undoAction,QKeySequence::keyBindings(QKeySequence::Undo));
|
|
QtUtilities::setActionShortcuts(*selectAllAction,QKeySequence::keyBindings(QKeySequence::SelectAll));
|
|
|
|
// set up delete action
|
|
QList<QKeySequence> shortcuts = QKeySequence::keyBindings(QKeySequence::Delete);
|
|
shortcuts.append(QKeySequence(Qt::Key_Backspace));
|
|
QtUtilities::setActionShortcuts(*deleteSelectedAction,shortcuts);
|
|
|
|
// script menu
|
|
QtUtilities::setActionShortcuts(*findAction,QKeySequence::keyBindings(QKeySequence::Find));
|
|
QtUtilities::setActionShortcuts(*replaceAction,QKeySequence::keyBindings(QKeySequence::Replace));
|
|
|
|
shortcuts.clear();
|
|
shortcuts.append(QKeySequence("F3"));
|
|
QtUtilities::setActionShortcuts(*findNextAction,shortcuts);
|
|
|
|
shortcuts.clear();
|
|
shortcuts.append(QKeySequence("Shift+F3"));
|
|
QtUtilities::setActionShortcuts(*findPreviousAction,shortcuts);
|
|
|
|
// tools menu
|
|
QtUtilities::setActionShortcuts(*settingsAction,QKeySequence::keyBindings(QKeySequence::Preferences));
|
|
|
|
// help menu
|
|
shortcuts.clear();
|
|
shortcuts.append(QKeySequence("F1"));
|
|
QtUtilities::setActionShortcuts(*onlineHelpAction, shortcuts);
|
|
|
|
// camera toolbar
|
|
QtUtilities::setActionShortcuts(*zoomInAction, QList<QKeySequence>() << QKeySequence(QKeySequence::ZoomIn) << QKeySequence("Ctrl+="));
|
|
QtUtilities::setActionShortcuts(*zoomOutAction, QList<QKeySequence>() << QKeySequence(QKeySequence::ZoomOut) << QKeySequence("Ctrl+_"));
|
|
|
|
shortcuts.clear();
|
|
#ifndef Q_WS_MAC
|
|
// TODO: remove shortcut collision for debugger action - stepinto
|
|
QtUtilities::setActionShortcuts(*stepIntoAction,shortcuts);
|
|
// F11 works differently on Mac
|
|
shortcuts.append(QKeySequence("F11"));
|
|
#endif
|
|
QtUtilities::setActionShortcuts(*actionFullScreen,shortcuts);
|
|
|
|
if (isRibbonStyle())
|
|
{
|
|
// remove shortcut set for startServerAction
|
|
shortcuts.clear();
|
|
QtUtilities::setActionShortcuts(*startServerAction,shortcuts);
|
|
|
|
// add shortcut F7 for startServerAndPlayersAction
|
|
QAction* pAction = findChild<QAction*>("startServerAndPlayersAction");
|
|
if (pAction)
|
|
{
|
|
shortcuts.append(QKeySequence("F7"));
|
|
QtUtilities::setActionShortcuts(*pAction,shortcuts);
|
|
}
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::showInsertServiceDialog()
|
|
{
|
|
if (!m_pInsertServiceDlg)
|
|
m_pInsertServiceDlg = new InsertServiceDialog(this);
|
|
|
|
if(RobloxDocManager::Instance().getCurrentDoc())
|
|
RobloxDocManager::Instance().getCurrentDoc()->handleAction("actionUpdateInsertServiceDialog");
|
|
|
|
m_pInsertServiceDlg->setVisible(true);
|
|
}
|
|
|
|
void RobloxMainWindow::showFindAllDialog()
|
|
{
|
|
FindReplaceProvider::instance().getFindAllDialog()->show();
|
|
FindReplaceProvider::instance().getFindAllDialog()->activateWindow();
|
|
FindReplaceProvider::instance().getFindAllDialog()->setFocus();
|
|
}
|
|
|
|
void RobloxMainWindow::updateEmbeddedFindPosition()
|
|
{
|
|
if (IRobloxDoc* doc = RobloxDocManager::Instance().getCurrentDoc())
|
|
{
|
|
if (RobloxScriptDoc* scriptDoc = dynamic_cast<RobloxScriptDoc*>(doc))
|
|
{
|
|
if (ScriptTextEditor* textEditor = scriptDoc->getTextEditor())
|
|
{
|
|
textEditor->updateEmbeddedFindPosition();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::setupCommandToolBar()
|
|
{
|
|
m_pScriptComboBox = new ScriptComboBox(commandToolBar);
|
|
commandToolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea);
|
|
commandToolBar->addWidget(m_pScriptComboBox);
|
|
|
|
connect(
|
|
commandToolBar,SIGNAL(topLevelChanged(bool)),
|
|
this,SLOT(onCommandToolBarTopLevelChanged(bool)) );
|
|
}
|
|
|
|
void RobloxMainWindow::sendCounterEvent(std::string counterName, bool fireImmediately)
|
|
{
|
|
if (!RBX::ClientAppSettings::singleton().GetValueCaptureQTStudioCountersEnabled())
|
|
return;
|
|
|
|
#ifdef _WIN32
|
|
// Can't initialize in global init so must do lazy init
|
|
static boost::scoped_ptr<CountersClient> m_pCountersClient;
|
|
|
|
if (!m_pCountersClient)
|
|
m_pCountersClient.reset(new CountersClient(GetBaseURL().c_str(), "76E5A40C-3AE1-4028-9F10-7C62520BD94F", NULL));
|
|
|
|
// TODO - change project settings to not have wchar as a native type. Qt can't do wide string operations.
|
|
std::wstring wstringCounterName = convert_s2w(counterName);
|
|
m_pCountersClient->registerEvent(wstringCounterName, fireImmediately);
|
|
#endif
|
|
}
|
|
|
|
void RobloxMainWindow::setupCounterSender()
|
|
{
|
|
#ifdef _WIN32
|
|
int interval = RBX::ClientAppSettings::singleton().GetValueCaptureSlowCountersIntervalInSeconds();
|
|
m_pCounterSenderTimer = new QTimer(this);
|
|
connect(m_pCounterSenderTimer, SIGNAL(timeout()), this, SLOT(sendOffCounters()));
|
|
m_pCounterSenderTimer->start(interval * 1000);
|
|
#endif
|
|
}
|
|
|
|
void RobloxMainWindow::sendOffCounters()
|
|
{
|
|
#ifdef _WIN32
|
|
boost::scoped_ptr<CountersClient> countersClient;
|
|
|
|
if (!countersClient)
|
|
countersClient.reset(new CountersClient(GetBaseURL().c_str(), "76E5A40C-3AE1-4028-9F10-7C62520BD94F", NULL));
|
|
|
|
countersClient->reportEvents();
|
|
#endif
|
|
}
|
|
|
|
void RobloxMainWindow::logIntervalCounter()
|
|
{
|
|
int interval = RBX::ClientAppSettings::singleton().GetValueCaptureCountersIntervalInMinutes();
|
|
|
|
sendCounterEvent(QString("QTStudio_%1MinCounter").arg(interval).toStdString(), false);
|
|
}
|
|
|
|
void RobloxMainWindow::handleFileOpenSendCounter(const QString& fileName)
|
|
{
|
|
// This person is a builder, set up a timer that starts recording every minute they're using our app
|
|
if (!m_pMinutesPlayedTimer && RBX::ClientAppSettings::singleton().GetValueCaptureQTStudioCountersEnabled())
|
|
{
|
|
// Record counters every XX minutes (based on config)
|
|
int interval = RBX::ClientAppSettings::singleton().GetValueCaptureCountersIntervalInMinutes();
|
|
m_pMinutesPlayedTimer = new QTimer(this);
|
|
connect(m_pMinutesPlayedTimer, SIGNAL(timeout()), this, SLOT(logIntervalCounter()));
|
|
m_pMinutesPlayedTimer->start(interval * 60 * 1000);
|
|
}
|
|
sendCounterEvent(fileName.isEmpty() ? "QTStudio_FileNew" : "QTStudio_FileOpen", false);
|
|
}
|
|
|
|
void RobloxMainWindow::checkInternetConnectionSendCounter(RBX::Http& robloxRequest, RBX::Http& externalRequest)
|
|
{
|
|
bool robloxAccessible;
|
|
std::string result;
|
|
RobloxSettings settings;
|
|
|
|
int unconnectedRuns = settings.value("appNoInternet", 0).toInt();
|
|
|
|
try
|
|
{
|
|
robloxRequest.get(result);
|
|
robloxAccessible = true;
|
|
}
|
|
catch(...)
|
|
{
|
|
robloxAccessible = false;
|
|
}
|
|
|
|
if (robloxAccessible)
|
|
{
|
|
if (unconnectedRuns)
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ERROR, "StudioInternetReconnection", "none", unconnectedRuns);
|
|
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ERROR, "StudioInternetConnection");
|
|
|
|
settings.setValue("appNoInternet", 0);
|
|
}
|
|
else
|
|
{
|
|
bool googleAccessible;
|
|
try
|
|
{
|
|
externalRequest.get(result, true);
|
|
googleAccessible = true;
|
|
}
|
|
catch(...)
|
|
{
|
|
googleAccessible = false;
|
|
}
|
|
|
|
if (googleAccessible)
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ERROR, "RobloxDomainBlocked");
|
|
else
|
|
settings.setValue("appNoInternet", unconnectedRuns + 1);
|
|
}
|
|
|
|
}
|
|
|
|
bool RobloxMainWindow::handleFileOpen(const QString &fileName, IRobloxDoc::RBXDocType type, const QString &script)
|
|
{
|
|
RBX::Log::current()->writeEntry(RBX::Log::Information, RBX::format("RobloxMainWindow::handleFileOpen %s", script.toStdString().c_str()).c_str());
|
|
|
|
QString fileToOpen = fileName;
|
|
|
|
if (type == IRobloxDoc::IDE)
|
|
{
|
|
// we aren't specifying a file to open or a script to open, we aren't opening anything
|
|
if(fileName.isEmpty() && script.isEmpty())
|
|
return false;
|
|
|
|
if (fileName == NEW_PLACE_FILENAME)
|
|
{
|
|
fileToOpen = "";
|
|
}
|
|
|
|
// Gross, if it's a build button click, add the avatar (and guis)
|
|
StudioUtilities::setAvatarMode(false);
|
|
|
|
if (StudioUtilities::containsVisitScript(script) ||
|
|
StudioUtilities::containsJoinScript(script))
|
|
StudioUtilities::setAvatarMode(true);
|
|
|
|
handleFileOpenSendCounter(fileToOpen);
|
|
|
|
// We already have an IDE doc open, launch a new version of Studio (SDI)
|
|
IRobloxDoc* currentPlayDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
if (currentPlayDoc)
|
|
{
|
|
// If we're opening a branch new place in our new process, send over the new place file name so it can differentiate
|
|
QString fileNameToOpen = fileName.isEmpty() ? NEW_PLACE_FILENAME : fileName;
|
|
RobloxApplicationManager::instance().createNewStudioInstance(script, fileNameToOpen);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
IRobloxDoc* newDoc = RobloxDocManager::Instance().getOrCreateDoc(type);
|
|
|
|
if ( type == IRobloxDoc::IDE )
|
|
{
|
|
// static cast ok because we know we made a IDEDoc due to type
|
|
// only ideDoc needs a script, set it here before we open file
|
|
RobloxIDEDoc* ideDoc = static_cast<RobloxIDEDoc*>(newDoc);
|
|
ideDoc->setInitializationScript(script);
|
|
|
|
m_AutoSaveAccum = 0;
|
|
}
|
|
|
|
if (!newDoc->open(this, fileToOpen))
|
|
{
|
|
QString text = tr("Error in opening file - %1").arg(fileToOpen);
|
|
if (RBX::StandardOut::singleton() && (!fileToOpen.isEmpty()) && type == IRobloxDoc::IDE)
|
|
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR,qPrintable(text));
|
|
|
|
requestDocClose(*newDoc);
|
|
|
|
if ( type == IRobloxDoc::IDE )
|
|
QMessageBox::critical(this,"Open File Failure",text);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (RBX::StandardOut::singleton() && (!fileToOpen.isEmpty()) && type == IRobloxDoc::IDE)
|
|
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "Successfully opened file - %s", qPrintable(fileToOpen));
|
|
}
|
|
|
|
if ( fileToOpen.isEmpty() && type == IRobloxDoc::IDE && script.isEmpty())
|
|
{
|
|
RobloxIDEDoc* idedoc = static_cast<RobloxIDEDoc*>(newDoc);
|
|
idedoc->initializeNewPlace();
|
|
}
|
|
|
|
|
|
QWidget *widgetToAddInTab = newDoc->getViewer();
|
|
if (widgetToAddInTab)
|
|
{
|
|
RobloxDocManager::Instance().configureDocWidget(*newDoc);
|
|
|
|
// update build mode with new doc information
|
|
//saveApplicationStates(); // save state in case update reloads it
|
|
UpdateUIManager::Instance().updateBuildMode();
|
|
}
|
|
|
|
if ( type == IRobloxDoc::IDE && !fileToOpen.isEmpty() )
|
|
updateRecentFile(fileToOpen);
|
|
|
|
if (FFlag::ReportBuildVSEditMode)
|
|
{
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, "EditMode", script.contains("visit.ashx") ? "Build" : "Edit");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//for making the position of toolbar identical to old studio.
|
|
void RobloxMainWindow::setToolbarPosition()
|
|
{
|
|
insertToolBar(runToolBar, advToolsToolBar);
|
|
|
|
if (RBX::MouseCommand::isAdvArrowToolEnabled())
|
|
{
|
|
insertToolBar(advToolsToolBar, runToolBar);
|
|
}
|
|
else
|
|
{
|
|
insertToolBar(advToolsToolBar, oldToolsToolBar);
|
|
insertToolBar(oldToolsToolBar, runToolBar);
|
|
}
|
|
|
|
insertToolBar(runToolBar, editCameraToolBar);
|
|
insertToolBar(editCameraToolBar, viewToolsToolBar);
|
|
}
|
|
|
|
QAction* RobloxMainWindow::getActionByName(const QString& actionName)
|
|
{
|
|
QList<QAction*> actionList = actions();
|
|
for (QList<QAction*>::const_iterator iter = actionList.begin(); iter != actionList.end(); ++iter)
|
|
if ((*iter)->objectName() == actionName)
|
|
return *iter;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void RobloxMainWindow::saveDefaultApplicationState()
|
|
{
|
|
RobloxSettings settings;
|
|
|
|
//save all the positions and sizes for all dockWidgets and toolbars.
|
|
settings.setValue("default_window_state", saveState());
|
|
settings.setValue("default_geometry_state", saveGeometry());
|
|
}
|
|
|
|
void RobloxMainWindow::saveApplicationStates()
|
|
{
|
|
// don't save anything in basic mode
|
|
if ( m_BuildMode == BM_BASIC )
|
|
return;
|
|
|
|
if ( !isVisible() )
|
|
return;
|
|
|
|
UpdateUIManager::Instance().saveDocksGeometry();
|
|
|
|
RobloxSettings settings;
|
|
// Clear legacy values
|
|
settings.remove("geometry");
|
|
settings.remove("windowState");
|
|
|
|
//save all the positions and sizes for all dockWidgets and toolbars.
|
|
settings.setValue(sGeometryKey, saveGeometry());
|
|
settings.setValue(sWindowStateKey, saveState());
|
|
|
|
// Save command history from output window
|
|
settings.setValue("rbxCommandHistory", m_pScriptComboBox->commandHistory());
|
|
|
|
//TODO: Move this to RobloxRibbonMainWindow.cpp
|
|
if (isRibbonStyle())
|
|
RobloxQuickAccessConfig::singleton().saveQuickAccessConfig();
|
|
}
|
|
|
|
void RobloxMainWindow::loadDefaultApplicationState()
|
|
{
|
|
RobloxSettings settings;
|
|
restoreState(settings.value("default_window_state").toByteArray());
|
|
restoreGeometry(settings.value("default_geometry_state").toByteArray());
|
|
|
|
// since players dockwidget and chat dockwidget gets created only after saving application default state so it doesn't get restored
|
|
QDockWidget* playersDockWidget = findChild<QDockWidget*>("playersDockWidget");
|
|
if (playersDockWidget)
|
|
playersDockWidget->setVisible(false);
|
|
QDockWidget* chatDockWidget = findChild<QDockWidget*>("chatDockWidget");
|
|
if (chatDockWidget)
|
|
chatDockWidget->setVisible(false);
|
|
|
|
UpdateUIManager::Instance().setDockVisibility(eDW_OBJECT_EXPLORER, true);
|
|
|
|
saveApplicationStates();
|
|
}
|
|
|
|
void RobloxMainWindow::loadApplicationStates()
|
|
{
|
|
if (isRibbonStyle()) // Ribbon mode saves to its own settings
|
|
{
|
|
sGeometryKey = sGeometryKey.append("_ribbon");
|
|
sWindowStateKey = sWindowStateKey.append("_ribbon");
|
|
}
|
|
|
|
//load all the positions and sizes for all dockWidgets and toolbars from memory.
|
|
RobloxSettings settings;
|
|
restoreState(settings.value(sWindowStateKey).toByteArray());
|
|
restoreGeometry(settings.value(sGeometryKey).toByteArray());
|
|
|
|
m_pScriptComboBox->setCommandHistory(settings.value("rbxCommandHistory").toStringList());
|
|
|
|
UpdateUIManager::Instance().loadDocksGeometry();
|
|
|
|
setCurrentDirectory(settings.value("rbxl_last_directory").toString());
|
|
}
|
|
|
|
/**
|
|
* Update the window title to show the current doc name.
|
|
* If a doc is not selected, show the default.
|
|
*
|
|
* @see getDialogTitle
|
|
*/
|
|
void RobloxMainWindow::updateWindowTitle()
|
|
{
|
|
IRobloxDoc* pDoc = RobloxDocManager::Instance().getCurrentDoc();
|
|
|
|
// if no document or we're in basic build mode just use the simple title
|
|
if ( !pDoc || m_BuildMode == BM_BASIC )
|
|
setWindowTitle(sWindow_Title);
|
|
else
|
|
{
|
|
setWindowTitle(pDoc->windowTitle() + " - " + QString(sWindow_Title));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the title string to be used for child dialogs.
|
|
* This is different than the main window's title because the main one can
|
|
* have the current file opened and [*] in the name.
|
|
*
|
|
* @see updateWindowTitle()
|
|
* @return default child dialog title
|
|
*/
|
|
QString RobloxMainWindow::getDialogTitle() const
|
|
{
|
|
return sDialog_Title;
|
|
}
|
|
|
|
/**
|
|
* Callback every minute.
|
|
*/
|
|
void RobloxMainWindow::onMinuteTimer()
|
|
{
|
|
// prevent re-entrancy
|
|
static bool processing = false;
|
|
if ( processing )
|
|
return;
|
|
processing = true;
|
|
|
|
// periodically save state in case we crash and lose all the layout changes
|
|
saveApplicationStates();
|
|
|
|
// auto-save
|
|
if ( AuthoringSettings::singleton().autoSaveEnabled )
|
|
{
|
|
++m_AutoSaveAccum;
|
|
|
|
// correct for bad values
|
|
const int autoSaveMinutes = qMax(AuthoringSettings::singleton().autoSaveMinutesInterval,1);
|
|
AuthoringSettings::singleton().autoSaveMinutesInterval = autoSaveMinutes;
|
|
|
|
if ( m_AutoSaveAccum >= autoSaveMinutes )
|
|
{
|
|
m_AutoSaveAccum = 0;
|
|
RobloxIDEDoc* playDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
if ( playDoc && !playDoc->autoSave(false) )
|
|
{
|
|
const int result = QMessageBox::question(
|
|
this,
|
|
tr("Failed to AutoSave"),
|
|
tr("Failed to AutoSave. Do you want to temporarily disable AutoSave?"),
|
|
QMessageBox::Yes | QMessageBox::No );
|
|
if ( result == QMessageBox::Yes )
|
|
AuthoringSettings::singleton().autoSaveEnabled = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
trackUserActive();
|
|
|
|
processing = false;
|
|
}
|
|
|
|
/**
|
|
* Deletes the splash screen.
|
|
* After deleting, shows the tip of the day dialog.
|
|
*/
|
|
void RobloxMainWindow::onDeleteSplashScreen()
|
|
{
|
|
if ( m_splashScreen )
|
|
{
|
|
delete m_splashScreen;
|
|
m_splashScreen = NULL;
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::toggleBuildMode()
|
|
{
|
|
if ( m_BuildMode == BM_ADVANCED )
|
|
setBuildMode(BM_BASIC);
|
|
else if ( m_BuildMode == BM_BASIC )
|
|
setBuildMode(BM_ADVANCED);
|
|
|
|
UpdateUIManager::Instance().updateBuildMode();
|
|
|
|
// TODO - handle other build modes
|
|
}
|
|
|
|
/**
|
|
* Sets the current mode for building.
|
|
* Basic mode disables all dock widgets and toolbars.
|
|
* Advanced mode shows everything.
|
|
*/
|
|
void RobloxMainWindow::setBuildMode(eBuildMode buildMode)
|
|
{
|
|
//saveApplicationStates();
|
|
|
|
QSettings settings;
|
|
|
|
// if using the default, set it from the settings
|
|
if ( buildMode == BM_LASTMODE )
|
|
buildMode = (eBuildMode)settings.value("BuildMode",BM_ADVANCED).toInt();
|
|
|
|
m_BuildMode = buildMode;
|
|
settings.setValue("BuildMode",m_BuildMode);
|
|
}
|
|
|
|
void RobloxMainWindow::closePlayDoc()
|
|
{
|
|
IRobloxDoc* playDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
if (playDoc)
|
|
requestDocClose(playDoc);
|
|
}
|
|
|
|
/**
|
|
* Callback for the command toolbar changing floating/docking state.
|
|
* Handles setting the maximum size of the script input combobox so it works as expected.
|
|
*/
|
|
void RobloxMainWindow::onCommandToolBarTopLevelChanged(bool topLevel)
|
|
{
|
|
if ( topLevel )
|
|
m_pScriptComboBox->setFixedWidth(800);
|
|
else
|
|
{
|
|
m_pScriptComboBox->setMinimumWidth(300);
|
|
m_pScriptComboBox->setMaximumWidth(QWIDGETSIZE_MAX);
|
|
}
|
|
|
|
commandToolBar->layout()->invalidate();
|
|
}
|
|
|
|
void RobloxMainWindow::onPropertyChanged(const RBX::Reflection::PropertyDescriptor* pDescriptor)
|
|
{
|
|
if (AuthoringSettings::singleton().getOutputLayoutMode() == AuthoringSettings::OutputLayoutHorizontal)
|
|
{
|
|
setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea);
|
|
setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea);
|
|
setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea);
|
|
setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea);
|
|
}
|
|
else
|
|
{
|
|
setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
|
|
setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
|
|
setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
|
|
setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
|
|
}
|
|
|
|
if (AuthoringSettings::singleton().diagnosticsBarEnabled)
|
|
{
|
|
UpdateUIManager::Instance().updateStatusBar();
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::setupActionCounters()
|
|
{
|
|
if (!FFlag::GoogleAnalyticsTrackingEnabled)
|
|
return;
|
|
|
|
QString ignoreActions("renameObjectAction");
|
|
|
|
QList<QAction*> actionList = findChildren<QAction*>();
|
|
|
|
if (FFlag::StudioSeparateActionByActivationMethod)
|
|
{
|
|
//We should eventually subclass QAction for this
|
|
static QString noSlotActions("startServerCB fileNewAction fileOpenAction fileCloseAction "
|
|
"fileSaveAction fileSaveAsAction fileExitAction");
|
|
|
|
for(QList<QAction*>::const_iterator iter = actionList.begin(); iter != actionList.end(); iter++)
|
|
{
|
|
if (noSlotActions.contains((*iter)->objectName()))
|
|
continue;
|
|
|
|
QAction* currentAction = *iter;
|
|
connect(currentAction, SIGNAL(triggered(bool)), this, SLOT(commonSlot(bool)));
|
|
currentAction->installEventFilter(this);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QSignalMapper* signalMapper = new QSignalMapper(this);
|
|
for(QList<QAction*>::const_iterator iter = actionList.begin(); iter != actionList.end(); iter++)
|
|
{
|
|
QAction* currentAction = *iter;
|
|
if(!currentAction->objectName().isEmpty() && !ignoreActions.contains(currentAction->objectName()))
|
|
{
|
|
connect(currentAction, SIGNAL(triggered()), signalMapper, SLOT(map()));
|
|
signalMapper->setMapping(currentAction, currentAction->objectName());
|
|
}
|
|
}
|
|
connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(sendActionCounterEvent(QString)));
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::trackToolboxInserts(RBX::ContentId contentId, const RBX::Instances& instances)
|
|
{
|
|
std::vector<weak_ptr<RBX::Instance> > weakInstances;
|
|
|
|
for (RBX::Instances::const_iterator iter = instances.begin(); iter != instances.end(); ++iter)
|
|
weakInstances.push_back(weak_from(iter->get()));
|
|
|
|
m_insertObjectItems.push(InsertObjectItem(contentId.getAssetId(), weakInstances));
|
|
|
|
QTimer::singleShot(FInt::StudioInsertDeletionCheckTimeMS, this, SLOT(checkInsertedObjects()));
|
|
}
|
|
|
|
void RobloxMainWindow::checkInsertedObjects()
|
|
{
|
|
if (m_insertObjectItems.size() == 0)
|
|
return;
|
|
|
|
InsertObjectItem insertedInstance = m_insertObjectItems.front();
|
|
m_insertObjectItems.pop();
|
|
|
|
bool objectsStillExist = true;
|
|
|
|
for (std::vector<weak_ptr<RBX::Instance> >::const_iterator iter = insertedInstance.weakInstances.begin(); iter != insertedInstance.weakInstances.end(); ++iter)
|
|
{
|
|
if (shared_ptr<RBX::Instance> sharedInstance = iter->lock())
|
|
{
|
|
if (!RBX::DataModel::get(sharedInstance.get()))
|
|
{
|
|
objectsStillExist = false;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
objectsStillExist = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO, objectsStillExist ? "StudioInsertRemains" : "StudioInsertDeleted", insertedInstance.contentId.c_str());
|
|
}
|
|
|
|
void RobloxMainWindow::sendActionCounterEvent(const QString& action)
|
|
{
|
|
if (FFlag::GoogleAnalyticsTrackingEnabled)
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ACTION, "StudioUserAction", action.toStdString().c_str());
|
|
}
|
|
|
|
void RobloxMainWindow::notifyCloudEditConnectionClosed()
|
|
{
|
|
if (m_cloudEditAwaitingShutdown)
|
|
{
|
|
m_cloudEditAwaitingShutdown->Set();
|
|
return;
|
|
}
|
|
|
|
RobloxIDEDoc* ideDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
if (ideDoc && !m_IgnoreCloudEditDisconnect)
|
|
{
|
|
QString script = ideDoc->initializationScript();
|
|
QtUtilities::RBXMessageBox notificationDialog;
|
|
notificationDialog.setIcon(QMessageBox::Warning);
|
|
if (FFlag::TeamCreateEnableDownloadLocalCopy)
|
|
{
|
|
notificationDialog.setText("You have lost connection to the server. "
|
|
"Do you want to reconnect or save a copy of the place to a new file?");
|
|
}
|
|
else
|
|
{
|
|
notificationDialog.setText("You have lost connection to the server. "
|
|
"Do you want to reconnect or work locally (you have to save local work to a new file)?");
|
|
}
|
|
notificationDialog.addButton("Reconnect", QMessageBox::ResetRole);
|
|
if (FFlag::TeamCreateEnableDownloadLocalCopy)
|
|
notificationDialog.addButton("Save Local Copy", QMessageBox::ActionRole);
|
|
else
|
|
notificationDialog.addButton("Work Offline", QMessageBox::DestructiveRole);
|
|
notificationDialog.exec();
|
|
QMessageBox::ButtonRole role = notificationDialog.buttonRole(notificationDialog.clickedButton());
|
|
if (role == QMessageBox::ResetRole)
|
|
{
|
|
// close the play doc if it is still open
|
|
if (IRobloxDoc* playDoc = RobloxDocManager::Instance().getPlayDoc())
|
|
{
|
|
requestDocClose(*playDoc, false /*closeiflastdoc*/);
|
|
}
|
|
handleFileOpen(fileLocationArg, IRobloxDoc::IDE, script);
|
|
}
|
|
else if (FFlag::TeamCreateEnableDownloadLocalCopy && (role == QMessageBox::ActionRole))
|
|
{
|
|
if (RobloxIDEDoc* playDoc = RobloxDocManager::Instance().getPlayDoc())
|
|
{
|
|
// let users save a local copy
|
|
QString fileName = playDoc->downloadPlaceCopy();
|
|
// close the online document
|
|
requestDocClose(*playDoc, false /*closeiflastdoc*/);
|
|
if (!fileName.isEmpty())
|
|
handleFileOpen(fileName, IRobloxDoc::IDE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::cloudEditStatusChanged(int placeId, int universeId)
|
|
{
|
|
RBX::ScopedAssign<bool> ignoreDocActivate(m_IgnoreCloudEditDisconnect, true);
|
|
|
|
if (IRobloxDoc* playDoc = RobloxDocManager::Instance().getPlayDoc())
|
|
{
|
|
requestDocClose(*playDoc, false /*closeiflastdoc*/);
|
|
}
|
|
|
|
QString initScript;
|
|
if (FFlag::UseBuildGenericGameUrl)
|
|
{
|
|
QString pathStr = QString("Game/edit.ashx?PlaceID=%1&upload=%1&testmode=false&universeId=%2").arg(placeId).arg(universeId);
|
|
initScript = QString::fromStdString(BuildGenericGameUrl(RobloxSettings::getBaseURL().toStdString(), pathStr.toStdString()));
|
|
}
|
|
else
|
|
{
|
|
initScript = QString("%1/Game/edit.ashx?PlaceID=%2&upload=%2&testmode=false&universeId=%3").arg(RobloxSettings::getBaseURL()).arg(placeId).arg(universeId);
|
|
}
|
|
|
|
handleFileOpen(FFlag::StudioDoublingOnUploadFixEnabled ? "" : fileLocationArg,
|
|
IRobloxDoc::IDE, initScript);
|
|
}
|
|
|
|
void RobloxMainWindow::shutDownCloudEditServer()
|
|
{
|
|
using namespace RBX;
|
|
using namespace Network;
|
|
|
|
RBXASSERT(QThread::currentThread() == qApp->thread());
|
|
|
|
RobloxIDEDoc* playDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
RBXASSERT(playDoc);
|
|
if (!playDoc) return;
|
|
|
|
RBXASSERT(playDoc->isCloudEditSession());
|
|
if (!playDoc->isCloudEditSession()) return;
|
|
|
|
shared_ptr<DataModel> dm = playDoc->getEditDataModel();
|
|
RBXASSERT(dm);
|
|
if (!dm) return;
|
|
|
|
long placeId, universeId;
|
|
{
|
|
{
|
|
DataModel::LegacyLock lock(dm.get(), DataModelJob::Write);
|
|
|
|
placeId = dm->getPlaceID();
|
|
universeId = dm->getUniverseId();
|
|
|
|
Players* players = dm->find<Players>();
|
|
RBXASSERT(players);
|
|
if (players)
|
|
{
|
|
RBXASSERT(!m_cloudEditAwaitingShutdown);
|
|
m_cloudEditAwaitingShutdown.reset(new RBX::CEvent(true /* manual reset */));
|
|
Players::event_requestCloudEditShutdown.replicateEvent(players);
|
|
}
|
|
}
|
|
|
|
if (m_cloudEditAwaitingShutdown)
|
|
{
|
|
UpdateUIManager::Instance().waitForLongProcess("Waiting for Shutdown", boost::bind(&RBX::CEvent::Wait, m_cloudEditAwaitingShutdown.get()));
|
|
m_cloudEditAwaitingShutdown.reset();
|
|
}
|
|
}
|
|
|
|
QMetaObject::invokeMethod(this, "cloudEditStatusChanged", Qt::QueuedConnection, Q_ARG(int, placeId), Q_ARG(int, universeId));
|
|
}
|
|
|
|
void RobloxMainWindow::setupCustomToolButton()
|
|
{
|
|
m_pFillColorToolButton = new FillColorPickerToolButton(this);
|
|
m_pFillColorToolButton->setObjectName("actionFillColor");
|
|
m_pFillColorToolButton->setDefaultAction(actionFillColor);
|
|
connect(m_pFillColorToolButton, SIGNAL(changed(const QString &)), this, SLOT(onCustomToolButton(const QString &)));
|
|
|
|
m_pMaterialToolButton = new MaterialPickerToolButton(this);
|
|
m_pMaterialToolButton->setObjectName("actionMaterial");
|
|
m_pMaterialToolButton->setDefaultAction(actionMaterial);
|
|
connect(m_pMaterialToolButton, SIGNAL(changed(const QString &)), this, SLOT(onCustomToolButton(const QString &)));
|
|
|
|
//add tool buttons into toolbar
|
|
QAction *fillAction = advToolsToolBar->insertWidget(dropperAction, m_pFillColorToolButton);
|
|
fillAction->setObjectName("actionFillColor_WidgetAction");
|
|
|
|
QAction *materialAction = advToolsToolBar->insertWidget(smoothSurfaceAction, m_pMaterialToolButton);
|
|
materialAction->setObjectName("actionMaterial_WidgetAction");
|
|
|
|
advToolsToolBar->insertSeparator(materialAction);
|
|
advToolsToolBar->insertSeparator(smoothSurfaceAction);
|
|
}
|
|
|
|
void RobloxMainWindow::trackUserActive()
|
|
{
|
|
// We want an accurate session length so we send up a special event
|
|
// if there is any user interaction with app that is not tracked
|
|
// by other metrics.
|
|
|
|
const int eventIdleTime = 60 * 1000; // in milliseconds
|
|
const int stillAliveTime = 20 * 1000; // in milliseconds
|
|
|
|
// If it has been some time (eventIdleTime) since the last event tracked.
|
|
if (m_lastAnalyticTrackedTime.elapsed() > eventIdleTime
|
|
|
|
// And the user is not completely idle and some system is posting
|
|
// a keep alive.
|
|
&& m_lastAnalyticKeepAliveTime.elapsed() < stillAliveTime)
|
|
{
|
|
// Send an empty timing event.
|
|
RBX::RobloxGoogleAnalytics::trackUserTiming("", "", 0);
|
|
}
|
|
}
|
|
|
|
void RobloxMainWindow::onAnalyticTracked()
|
|
{
|
|
m_lastAnalyticTrackedTime = QTime::currentTime();
|
|
}
|
|
|
|
|
|
void RobloxMainWindow::keepAliveAnalyticsSession()
|
|
{
|
|
m_lastAnalyticKeepAliveTime = QTime::currentTime();
|
|
}
|
|
|
|
void RobloxMainWindow::cleanupPlayersAndServers()
|
|
{
|
|
RobloxApplicationManager::instance().cleanupChildProcesses();
|
|
cleanupServersAndPlayersAction->setEnabled(false);
|
|
}
|
|
|
|
void RobloxMainWindow::onIDEDocViewInitialized()
|
|
{
|
|
if ( m_splashScreen )
|
|
onDeleteSplashScreen();
|
|
}
|
|
|
|
void RobloxMainWindow::cookieConstraintCheckerLoadFinished(bool ok)
|
|
{
|
|
RBXASSERT(m_cookieConstraintChecker);
|
|
if (!m_cookieConstraintChecker)
|
|
return;
|
|
|
|
if (ok && m_cookieConstraintChecker->url().toString().contains(
|
|
FString::StudioCookieConstraintUrlFragment.c_str(), Qt::CaseInsensitive))
|
|
{
|
|
m_cookieConstraintChecker->raise();
|
|
m_cookieConstraintChecker->show();
|
|
}
|
|
else
|
|
{
|
|
m_cookieConstraintChecker->hide();
|
|
disconnect(
|
|
m_cookieConstraintChecker, SIGNAL( loadFinished(bool)),
|
|
this, SLOT(cookieConstraintCheckerLoadFinished(bool)));
|
|
m_cookieConstraintChecker->deleteLater();
|
|
m_cookieConstraintChecker = NULL;
|
|
m_cookieConstraintCheckDone->exit();
|
|
}
|
|
}
|
|
|
|
|