This commit is contained in:
watrabi
2025-09-18 17:55:52 -04:00
commit 977f1ff4b8
15030 changed files with 17324420 additions and 0 deletions
+253
View File
@@ -0,0 +1,253 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddEmulationDevDialog</class>
<widget class="QDialog" name="AddEmulationDevDialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>266</width>
<height>172</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="windowIcon">
<iconset resource="RobloxStudio.qrc">
<normaloff>:/images/RobloxStudio.png</normaloff>:/images/RobloxStudio.png</iconset>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="0,0">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Name: </string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditName">
<property name="placeholderText">
<string>Device Name</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="title">
<string>Size</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>8</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Width: </string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxWidth">
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="value">
<number>640</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Height: </string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinBoxHeight">
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>5000</number>
</property>
<property name="value">
<number>480</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Other</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Resolution:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="dpiValue">
<property name="text">
<string>96 DPI</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkBoxMobile">
<property name="text">
<string>Mobile</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>5</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="RobloxStudio.qrc"/>
</resources>
<connections/>
</ui>
+63
View File
@@ -0,0 +1,63 @@
/**
* AddEmulationDeviceDialog.cpp
* Copyright (c) 2014 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "AddEmulationDeviceDialog.h"
// Qt Headers
#include <QSettings>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QApplication>
#include <QDesktopWidget>
// Roblox Studio Headers
#include "AuthoringSettings.h"
#include "RobloxApplicationManager.h"
#include "RobloxDocManager.h"
#include "RobloxIDEDoc.h"
#include "RobloxMainWindow.h"
#include "StudioDeviceEmulator.h"
#include "UpdateUIManager.h"
AddEmulationDeviceDialog::AddEmulationDeviceDialog(QWidget* Parent)
: QDialog(Parent,Qt::WindowTitleHint | Qt::WindowSystemMenuHint)
{
setAttribute(Qt::WA_DeleteOnClose,false);
m_UI.setupUi(this);
setFixedSize(size());
setWindowFlags(Qt::MSWindowsFixedSizeDialogHint);
m_UI.dpiValue->setText(QString("%1 DPI").arg(QApplication::desktop()->physicalDpiX()));
connect(m_UI.cancelButton, SIGNAL(clicked()), this, SLOT(onCancel()));
connect(m_UI.okButton, SIGNAL(clicked()), this, SLOT(submitDevice()));
}
/**
* Callback for closing the dialog.
*
*/
void AddEmulationDeviceDialog::closeEvent(QCloseEvent* event)
{
QDialog::closeEvent(event);
}
void AddEmulationDeviceDialog::onCancel()
{
accept();
}
void AddEmulationDeviceDialog::submitDevice()
{
StudioDeviceEmulator::EmulationDevice device(m_UI.lineEditName->text(), QApplication::desktop()->physicalDpiX(), m_UI.spinBoxWidth->value(), m_UI.spinBoxHeight->value(), m_UI.checkBoxMobile->checkState());
StudioDeviceEmulator::Instance().addDevice(device);
accept();
}
+35
View File
@@ -0,0 +1,35 @@
/**
* AddEmulationDeviceDialog.h
* Copyright (c) 2014 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
#include <QDialog>
#include "ui_AddEmulationDevDialog.h"
/**
* Dialog for asking the user what to do with auto-save recovery files
* that were not cleaned up properly.
*/
class AddEmulationDeviceDialog : public QDialog
{
Q_OBJECT
public:
AddEmulationDeviceDialog(QWidget* Parent);
public Q_SLOTS:
void submitDevice();
void onCancel();
protected:
void closeEvent(QCloseEvent* event);
private:
static QStringList getFileList();
Ui::AddEmulationDevDialog m_UI;
};
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- DO NOT CHANGE THIS FILE, AS IT GETS DEPLOYED -->
<Settings>
<BaseUrl>http://www.roblox.com</BaseUrl>
<ContentFolder>..\..\..\content</ContentFolder>
</Settings>
+150
View File
@@ -0,0 +1,150 @@
//
// AppleCrashReporter.cpp
// RobloxStudio
//
// standard C/C++ Headers
#include <fstream>
// 3rd Party Headers
#include "boost/filesystem.hpp"
// Roblox Headers
#include "util/standardout.h"
#include "util/Http.h"
#include "RobloxServicesTools.h"
// Qt Headers
#include <QApplication>
#include <QSettings>
#include <QStringList>
#include <QDir>
#include <QFile>
#include <QStringBuilder>
// Roblox Studio Headers
#include "AppleCrashReporter.h"
#include "RobloxSettings.h"
const int AppleCrashReporter::maxReport = 1;
void AppleCrashReporter::check()
{
QString plistPath = QApplication::applicationDirPath() % "/../info.plist";
QSettings settings(plistPath, QSettings::NativeFormat);
if (settings.value("ENABLE_APPLE_CRASH_REPORTER").toString() == "YES")
{
QStringList fileList;
getReports(fileList);
int max = qMin(maxReport,fileList.count());
if (max == 0) max = fileList.count();
for (int i = 0; i < max; i++)
submitFile(QDir::homePath() % "/Library/Logs/DiagnosticReports/" % fileList.at(i));
}
}
void AppleCrashReporter::getReports(QStringList& fileList)
{
QStringList filters;
filters << "RobloxStudio_*.crash";
QDir crashReportDir(QDir::homePath() % "/Library/Logs/DiagnosticReports/");
fileList = crashReportDir.entryList(filters);
}
void AppleCrashReporter::submitFile(QString fileName)
{
QFile file(fileName);
if(file.exists())
uploadAndDeletFileAsync(GetDmpUrl(RobloxSettings::getBaseURL().toStdString(), true), fileName.toStdString());
}
static void handler(std::exception* ex, std::string file)
{
if (ex)
return;
std::remove(file.c_str());
// Remove the link as well in CrashReporter Dir
file.replace(file.find("DiagnosticReports"), 17, "CrashReporter");
std::remove(file.c_str());
}
void AppleCrashReporter::uploadAndDeletFileAsync(std::string url, std::string file)
{
boost::shared_ptr<std::fstream> data(new std::fstream(file.c_str(), std::ios_base::in | std::ios_base::binary));
size_t begin = data->tellg();
data->seekg (0, std::ios::end);
size_t end = data->tellg();
if (end > begin)
{
data->seekg (0, std::ios::beg);
std::string version;
while (*data)
{
char buff[255];
data->getline(buff, 255);
std::string line = buff;
if (line.substr(0, 8) == "Version:")
{
// "Version: 0.34.0.107 (107)"
for (size_t i = 8; i < line.size(); ++i)
if (line[i] != ' ')
{
line = line.substr(i);
break;
}
// "0.34.0.107 (107)"
for (size_t i = 0; i < line.size(); ++i)
if (line[i] == ' ')
{
line = line.substr(0, i);
// "0.34.0.107"
while (true)
{
size_t j = line.find('.');
if (j == std::string::npos)
break;
line = line.replace(j, 1, ",%20");
}
// "0,%2034,%200,%20107"
version = line;
break;
}
}
}
boost::filesystem::path p(file);
std::string filename = "RobloxStudio_log_";
// extract file "guid"
filename += p.filename().string().substr(13, 17);
filename += "%20";
filename += version;
filename += ".crash";
std::string fullUrl = url;
fullUrl += "?filename=" + filename;
boost::shared_ptr<std::fstream> dataPost(new std::fstream(file.c_str(), std::ios_base::in));
dataPost->seekg (0, std::ios::beg);
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_OUTPUT, "Uploading %s", fullUrl.c_str());
RBX::Http(fullUrl).post(dataPost, RBX::Http::kContentTypeUrlEncoded, true, boost::bind(handler, _2, p.string()));
}
}
+23
View File
@@ -0,0 +1,23 @@
//
// AppleCrashReporter.h
// RobloxStudio
//
#pragma once
class AppleCrashReporter
{
public:
static void check();
private:
static void getReports(QStringList& fileList);
static void submitFile(QString fileName);
static void uploadAndDeletFileAsync(std::string url, std::string file);
private:
static const int maxReport;
};
+666
View File
@@ -0,0 +1,666 @@
/**
* AuthenticationHelper.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "AuthenticationHelper.h"
// Qt Headers
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkInterface>
#include <QTimer>
#include <QCoreApplication>
#include <QTime>
#include <QtConcurrentRun>
// Roblox Headers
#include "util/standardout.h"
#include "util/Http.h"
#include "util/Statistics.h"
#include "RobloxServicesTools.h"
// Roblox Studio Headers
#include "RobloxNetworkAccessManager.h"
#include "RobloxSettings.h"
#include "QtUtilities.h"
#include "UpdateUIManager.h"
#include "RobloxNetworkReply.h"
#include "RobloxMainWindow.h"
#include "RobloxCookieJar.h"
#include "Util/RobloxGoogleAnalytics.h"
#include "Util/MachineIdUploader.h"
#include "V8DataModel/ContentProvider.h"
#include "RobloxWebDoc.h"
#include "RobloxDocManager.h"
#include "RobloxServicesTools.h"
FASTFLAG(GoogleAnalyticsTrackingEnabled)
FASTFLAGVARIABLE(StudioInSyncWebKitAuthentication, false)
FASTFLAGVARIABLE(UseAssetGameSubdomainForGetCurrentUser, true)
FASTFLAG(UseBuildGenericGameUrl)
//////////////////////////////////////////////////////////////////////////////////////////////////////
// AuthenticationHelper
QString AuthenticationHelper::userAgentStr = "";
bool AuthenticationHelper::httpAuthenticationPending = false;
bool AuthenticationHelper::verifyUserAuthenticationPending = false;
bool AuthenticationHelper::previouslyAuthenticated = false;
QString AuthenticationHelper::getLoggedInUserUrl()
{
QString result;
if (FFlag::UseBuildGenericGameUrl) {
result = QString::fromStdString(BuildGenericGameUrl(RobloxSettings::getBaseURL().toStdString(), "game/GetCurrentUser.ashx"));
}
else
{
result = RobloxSettings::getBaseURL() + "/game/GetCurrentUser.ashx";
}
if (FFlag::UseAssetGameSubdomainForGetCurrentUser)
{
return QString::fromStdString(
ReplaceTopSubdomain(result.toStdString(), "assetgame"));
}
return result;
}
bool AuthenticationHelper::verifyUserAndAuthenticate(int timeOutTime)
{
if (verifyUserAuthenticationPending || httpAuthenticationPending)
return false;
verifyUserAuthenticationPending = true;
bool result = true;
try
{
QString requestUserUrl = getLoggedInUserUrl();
QUrl url(requestUserUrl);
QNetworkRequest networkRequest(url);
networkRequest.setRawHeader("User-Agent", userAgentStr.toAscii());
RobloxNetworkReply* networkReply = RobloxNetworkAccessManager::Instance().get(networkRequest);
if (!networkReply->waitForFinished(timeOutTime, 100))
{
verifyUserAuthenticationPending = false;
networkReply->deleteLater();
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, "Unable to get current user. Request timed out.");
networkReply->deleteLater();
RobloxMainWindow::sendCounterEvent("StudioAuthenticationFailure", false);
if (FFlag::GoogleAnalyticsTrackingEnabled)
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ERROR, "StudioAuthenticationFailure");
return false;
}
// Get the logged in user Webkit
QString userIdWebkit(networkReply->readAll());
int statusCode = networkReply->error();
networkReply->deleteLater();
switch (statusCode)
{
case QNetworkReply::NoError:
{
if(userIdWebkit.toInt() > 0)
{
// Get the logged in user HTTP
QString currentUser = getLoggedInUserUrl();
QString userIdHttp = doHttpGetRequest(currentUser);
// If different authenticate the HTTP layer with the webkit user
if(userIdHttp != userIdWebkit)
result = authenticateHttpLayer();
}
else
{
// Sync with HTTP layer: Curl will be using the saved cookie file if any, to login!
result = authenticateWebKitFromHttp();
if (!result)
{
// Error (probably user not authenticated) time to logout the user from Http
deAuthenticateHttpLayer();
result = false;
}
}
break;
}
default:
{
// Error (probably user not authenticated) time to logout the user from Http
deAuthenticateHttpLayer();
result = false;
break;
}
}
}
catch (std::exception const& e)
{
result = false;
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e.what());
RobloxMainWindow::sendCounterEvent("StudioAuthenticationFailure", false);
if (FFlag::GoogleAnalyticsTrackingEnabled)
{
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ERROR, "StudioAuthenticationFailure");
}
}
verifyUserAuthenticationPending = false;
return result;
}
int AuthenticationHelper::getHttpUserId()
{
QString currentUser = getLoggedInUserUrl();
QString userIdHttp = doHttpGetRequest(currentUser);
bool ok;
int result = userIdHttp.toInt(&ok, 10);
if (!ok)
{
return 0;
}
return result;
}
/*
* Gets user-id of the user logged into webkit layer
* @Returns: user-id, 0 if no user is logged in
*/
int AuthenticationHelper::getWebKitUserId()
{
QString requestUserUrl = getLoggedInUserUrl();
QUrl url(requestUserUrl);
int userId = 0;
QNetworkRequest networkRequest(url);
networkRequest.setRawHeader("User-Agent", userAgentStr.toAscii());
RobloxNetworkReply* networkReply = RobloxNetworkAccessManager::Instance().get(networkRequest);
if (networkReply && networkReply->waitForFinished(5000, 100) && (networkReply->error() == QNetworkReply::NoError))
{
QString userIdWebkit(networkReply->readAll());
userId = userIdWebkit.toInt();
networkReply->deleteLater();
}
return userId;
}
bool AuthenticationHelper::authenticateHttpLayer()
{
Q_EMIT authenticationChanged(true);
Q_EMIT authenticationDone(true);
return true;
}
void AuthenticationHelper::deAuthenticateHttpLayer()
{
QString logoutUrl;
if (FFlag::UseBuildGenericGameUrl)
{
logoutUrl = QString::fromStdString(BuildGenericGameUrl(RobloxSettings::getBaseURL().toStdString(),"game/logout.aspx"));
}
else
{
logoutUrl = RobloxSettings::getBaseURL() + "/game/logout.aspx";
}
//
doHttpGetRequest(logoutUrl);
Q_EMIT authenticationChanged(false);
}
QString AuthenticationHelper::generateAuthenticationUrl(const QString& authUrl, const QString& ticket)
{
QString compoundStr(authUrl);
if (!ticket.isEmpty())
{
compoundStr.append("?suggest=");
compoundStr.append(ticket);
}
return compoundStr;
}
int AuthenticationHelper::authenticateWebKitFromUrlWithTicket(const QString& url, const QString& ticket)
{
if(!url.isEmpty() && !ticket.isEmpty())
{
return doWebKitAuthentication(generateAuthenticationUrl(url, ticket));
}
return 0;
}
/*
* Authenticate the webkit layer by making a request to this url. Follows redirects recursively.
* @Returns: status code of response
*/
int AuthenticationHelper::doWebKitAuthentication(const QString& url)
{
QNetworkRequest networkRequest(url);
networkRequest.setRawHeader("RBXAuthenticationNegotiation:", QByteArray(GetBaseURL().c_str(), GetBaseURL().length()));
networkRequest.setRawHeader("User-Agent", userAgentStr.toAscii());
RobloxNetworkReply *networkReply = RobloxNetworkAccessManager::Instance().get(networkRequest);
if (!networkReply->waitForFinished(5000, 100))
return 0; // timed out
networkReply->deleteLater();
return networkReply->statusCode();
}
/*
* De-authenticate the webkit layer.
*/
void AuthenticationHelper::deAuthenticateWebKitLayer()
{
QString logoutUrl;
if (FFlag::UseBuildGenericGameUrl)
{
logoutUrl = QString::fromStdString(BuildGenericGameUrl(RobloxSettings::getBaseURL().toStdString(), "game/logout.aspx"));
}
else
{
logoutUrl = RobloxSettings::getBaseURL() + "/game/logout.aspx";
}
QNetworkRequest networkRequest(logoutUrl);
networkRequest.setRawHeader("User-Agent", userAgentStr.toAscii());
RobloxNetworkReply* networkReply = RobloxNetworkAccessManager::Instance().get(networkRequest);
if (networkReply)
{
networkReply->waitForFinished(5000, 100);
networkReply->deleteLater();
}
}
/*
* Authenticate the WinINet layer by making a request to this url.
* @Returns: userId of authenticated user, empty if failed
*/
QString AuthenticationHelper::authenticateWinINetFromUrlWithTicket(const QString& url, const QString& ticket)
{
if(!url.isEmpty() && !ticket.isEmpty())
{
QString compoundStr = generateAuthenticationUrl(url, ticket);
return doHttpGetRequest(compoundStr);
}
return "";
}
QString AuthenticationHelper::doHttpGetRequest(const QString& str, const RBX::HttpAux::AdditionalHeaders& externalHeaders)
{
std::string result = "";
try
{
QByteArray ba = str.toAscii();
const char *c_str = ba.data();
RBX::Http http(c_str);
#ifdef Q_WS_MAC
http.setAuthDomain(::GetBaseURL());
#else
http.additionalHeaders["RBXAuthenticationNegotiation:"] = ::GetBaseURL();
#endif
http.additionalHeaders.insert(externalHeaders.begin(), externalHeaders.end());
http.get(result);
}
catch (std::exception& e)
{
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e.what());
return "";
}
return QString::fromStdString(result);
}
AuthenticationHelper::AuthenticationHelper()
: m_CurrentHttpRequestState(-1)
, m_bHttpSessionAuthenticationState(false)
, m_bIgnoreFinishedSignal(false)
{
userAgentStr = RobloxNetworkAccessManager::Instance().getUserAgent();
if (FFlag::StudioInSyncWebKitAuthentication)
qApp->installEventFilter(this);
connect(&RobloxNetworkAccessManager::Instance(), SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )),
this, SLOT(onSSLErrors(QNetworkReply*, const QList<QSslError> & )));
connect(&m_HttpAuthenticationFutureWatcher, SIGNAL(finished()), this, SLOT(httpSessionAuthenticationDone()));
}
bool AuthenticationHelper::validateMachine()
{
return RBX::MachineIdUploader::uploadMachineId(qPrintable(RobloxSettings::getBaseURL()))
!= RBX::MachineIdUploader::RESULT_MachineBanned;
}
bool AuthenticationHelper::eventFilter(QObject * watched, QEvent * evt)
{
QEvent::Type eventType = evt->type();
if (eventType == QEvent::ApplicationDeactivate)
{
// save webkit cookies
savedCookieKey = RobloxNetworkAccessManager::Instance().cookieJar()->getCookieValue(RobloxSettings::getBaseURL(), ".ROBLOSECURITY");
}
else if (eventType == QEvent::ApplicationActivate)
{
QString currentCookieKey = RobloxNetworkAccessManager::Instance().cookieJar()->getCookieValue(RobloxSettings::getBaseURL(), ".ROBLOSECURITY");
if (QString::compare(savedCookieKey, currentCookieKey, Qt::CaseSensitive) != 0)
{
// if we've some cookies saved, then authenticate studio with the saved cookies
if (!currentCookieKey.isEmpty())
{
RobloxNetworkAccessManager::Instance().cookieJar()->reloadCookies(true);
}
// if no cookies, just deauthenticate http layer too
else
{
deAuthenticateWebKitLayer();
}
// to avoid multiple reload of start page, we must call refresh here
RobloxWebDoc* startPage = dynamic_cast<RobloxWebDoc*>(RobloxDocManager::Instance().getStartPageDoc());
if (startPage)
startPage->refreshPage();
// http user and webkit user must be in sync
RBXASSERT(getHttpUserId() == getWebKitUserId());
}
}
return QObject::eventFilter(watched, evt);
}
int AuthenticationHelper::authenticateWebKitFromHttp()
{
// first verify is we've an user id for http, if not then just return
if (getHttpUserId() <=0 )
return 0;
QString requestURL = RobloxSettings::getBaseURL() + "/login/RequestAuth.ashx";
requestURL.replace("http", "https");
// need to add user agent, to make sure we get authorization URL
RBX::HttpAux::AdditionalHeaders externalHeaders;
externalHeaders["User-Agent"] = userAgentStr.toStdString();
// got the URL, now we can authorize webkit
QString returnURL = doHttpGetRequest(requestURL, externalHeaders);
if (!returnURL.isEmpty())
return doWebKitAuthentication(returnURL);
return 0;
}
void AuthenticationHelper::onSSLErrors(QNetworkReply* networkReply, const QList<QSslError> & errlist)
{
QString urlString = networkReply->url().toString();
if (urlString.contains(".robloxlabs.com"))
networkReply->ignoreSslErrors();
}
void AuthenticationHelper::authenticateUserAsync(const QString& url, const QString& ticket)
{
if (!url.isEmpty() && !ticket.isEmpty())
{
// make sure network manager instance get's created in Main thread
RobloxNetworkAccessManager::Instance();
QString authenticationUrl = generateAuthenticationUrl(url, ticket);
m_CurrentHttpRequestState = true;
m_HttpAuthenticationFuture = QtConcurrent::run(this, &AuthenticationHelper::authenticateHttpSession, authenticationUrl);
m_HttpAuthenticationFutureWatcher.setFuture(m_HttpAuthenticationFuture);
m_QtWebkitAuthenticationFuture = QtConcurrent::run(this, &AuthenticationHelper::authenticateQtWebkitSession, authenticationUrl);
m_QtWebkitAuthenticationFutureWatcher.setFuture(m_QtWebkitAuthenticationFuture);
}
else
{
// if we do not have security cookie, then try to authentication using Http session
QString secCookie = RobloxNetworkAccessManager::Instance().cookieJar()->getCookieValue(RobloxSettings::getBaseURL(), ".ROBLOSECURITY");
if (secCookie.isEmpty())
{
m_QtWebkitAuthenticationFuture = QtConcurrent::run(this, &AuthenticationHelper::authenticateQtWebkitSession);
m_QtWebkitAuthenticationFutureWatcher.setFuture(m_QtWebkitAuthenticationFuture);
}
else
{
m_CurrentHttpRequestState = true;
m_HttpAuthenticationFuture = QtConcurrent::run(this, &AuthenticationHelper::authenticateHttpSession);
m_HttpAuthenticationFutureWatcher.setFuture(m_HttpAuthenticationFuture);
}
}
}
void AuthenticationHelper::authenticateHttpSessionAsync(bool state)
{
// if the authentication state is same as the requested then do nothing
if (state && m_bHttpSessionAuthenticationState)
return;
// if either of the concurrent jobs are running then we will have the authentication done
if (((int)state == m_CurrentHttpRequestState) && (m_HttpAuthenticationFutureWatcher.isRunning() || m_QtWebkitAuthenticationFuture.isRunning()))
return;
// make sure there's no other http authentication request is running
waitForHttpAuthentication();
m_CurrentHttpRequestState = state;
if (state)
{
m_HttpAuthenticationFuture = QtConcurrent::run(this, &AuthenticationHelper::authenticateHttpSession);
}
else
{
m_HttpAuthenticationFuture = QtConcurrent::run(this, &AuthenticationHelper::deAuthenticateHttpSession);
}
m_HttpAuthenticationFutureWatcher.setFuture(m_HttpAuthenticationFuture);
}
bool AuthenticationHelper::authenticateHttpSession(const QString& authenticationURL)
{
return getHttpRequest(authenticationURL).isEmpty();
}
bool AuthenticationHelper::authenticateHttpSession()
{
bool result = false;
QString baseURL = RobloxSettings::getBaseURL();
QString authRequest = baseURL + "/login/RequestAuth.ashx";
authRequest.replace("http", "https");
QNetworkRequest networkRequest(authRequest);
networkRequest.setRawHeader("User-Agent", userAgentStr.toAscii());
// we need to create new AccessManager as we are in a different thread, also set cookies loaded from disk to new cookiejar
QNetworkCookieJar* cookieJar = new QNetworkCookieJar;
cookieJar->setCookiesFromUrl(RobloxNetworkAccessManager::Instance().cookieJar()->cookiesForUrl(baseURL), baseURL);
QNetworkAccessManager* accessManager = new QNetworkAccessManager;
accessManager->setCookieJar(cookieJar);
QNetworkReply* networkReply = accessManager->get(networkRequest);
if (baseURL.contains(".robloxlabs.com"))
networkReply->ignoreSslErrors();
RobloxNetworkReply* rbxNetworkReply = new RobloxNetworkReply(networkReply, true);
if (rbxNetworkReply->waitForFinished(10000, 100))
{
if (rbxNetworkReply->error() == QNetworkReply::NoError)
{
result = getHttpRequest(rbxNetworkReply->readAll()).isEmpty();
}
else
{
m_CurrentHttpRequestState = false;
result = deAuthenticateHttpSession();
}
}
else
{
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, "Unable to authenticate user. Request timed out.");
}
rbxNetworkReply->deleteLater();
accessManager->deleteLater();
return result;
}
bool AuthenticationHelper::deAuthenticateHttpSession()
{
QString logoutUrl;
if (FFlag::UseBuildGenericGameUrl)
{
logoutUrl = QString::fromStdString(BuildGenericGameUrl(RobloxSettings::getBaseURL().toStdString(), "game/logout.aspx"));
}
else
{
logoutUrl = RobloxSettings::getBaseURL() + "/game/logout.aspx";
}
getHttpRequest(logoutUrl);
return true;
}
bool AuthenticationHelper::authenticateQtWebkitSession(const QString& authenticationUrl)
{
bool result = false;
QString baseURL = RobloxSettings::getBaseURL();
QNetworkRequest networkRequest(authenticationUrl);
networkRequest.setRawHeader("RBXAuthenticationNegotiation:", baseURL.toAscii());
networkRequest.setRawHeader("User-Agent", userAgentStr.toAscii());
// we need to create new AccessManager as we are in a different thread
QNetworkAccessManager* accessManager = new QNetworkAccessManager;
QNetworkReply* networkReply = accessManager->get(networkRequest);
if (baseURL.contains(".robloxlabs.com"))
networkReply->ignoreSslErrors();
RobloxNetworkReply* rbxNetworkReply = new RobloxNetworkReply(networkReply, true);
if (rbxNetworkReply->waitForFinished(10000, 100))
{
if (rbxNetworkReply->statusCode() == 200)
{
// pass on the cookies from authenticated session to the static network access manager's cookiejar
RobloxNetworkAccessManager::Instance().cookieJar()->setCookiesFromUrl(accessManager->cookieJar()->cookiesForUrl(baseURL), baseURL);
result = true;
}
}
else
{
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, "Unable to authenticate user. Request timed out.");
}
rbxNetworkReply->deleteLater();
accessManager->deleteLater();
return result;
}
bool AuthenticationHelper::authenticateQtWebkitSession()
{
// check if already authenticated
QString result = getHttpRequest(getLoggedInUserUrl());
int httpUserId = result.toInt();
// if authenticated then authenticate webkit session too
if (httpUserId > 0)
{
QString requestURL = RobloxSettings::getBaseURL() + "/login/RequestAuth.ashx";
requestURL.replace("http", "https");
// need to add user agent, to make sure we get authorization URL
RBX::HttpAux::AdditionalHeaders externalHeaders;
externalHeaders["User-Agent"] = userAgentStr.toStdString();
// get negotiate URL and authenticate QtWebkit using this
QString negotiateURL = getHttpRequest(requestURL, externalHeaders);
if (!negotiateURL.isEmpty())
return authenticateQtWebkitSession(negotiateURL);
}
return false;
}
QString AuthenticationHelper::getHttpRequest(const QString& urlString, const RBX::HttpAux::AdditionalHeaders& externalHeaders)
{
static QMutex httpRequestMutex;
QMutexLocker lock(&httpRequestMutex);
RBX::HttpOptions options;
options.addHeader("RBXAuthenticationNegotiation:", ::GetBaseURL());
for (RBX::HttpAux::AdditionalHeaders::const_iterator iter = externalHeaders.begin(); iter != externalHeaders.end(); ++iter)
options.addHeader(iter->first, iter->second);
RBX::HttpFuture request = RBX::HttpAsync::get(urlString.toStdString(), options);
std::string result;
try
{
result = request.get();
}
catch (const RBX::base_exception& e)
{
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Error while authenticating user: %s", e.what());
}
return QString::fromStdString(result);
}
void AuthenticationHelper::httpSessionAuthenticationDone()
{
if (m_bIgnoreFinishedSignal)
{
m_bIgnoreFinishedSignal = false;
return;
}
RBXASSERT(m_CurrentHttpRequestState != -1);
bool requestedState = (bool)m_CurrentHttpRequestState;
m_CurrentHttpRequestState = -1;
if (m_HttpAuthenticationFutureWatcher.result())
{
m_bHttpSessionAuthenticationState = requestedState;
Q_EMIT authenticationChanged(requestedState);
}
else
{
m_bHttpSessionAuthenticationState = false;
}
}
void AuthenticationHelper::waitForHttpAuthentication()
{
if (m_HttpAuthenticationFutureWatcher.isRunning())
{
m_HttpAuthenticationFutureWatcher.waitForFinished();
// if a function is waiting for authentication to complete then call 'httpSessionAuthenticationDone' here itself
httpSessionAuthenticationDone();
// make sure we do not calling 'httpSessionAuthenticationDone' again called on 'finished' signal
m_bIgnoreFinishedSignal = true;
}
}
void AuthenticationHelper::waitForQtWebkitAuthentication()
{
if (m_QtWebkitAuthenticationFutureWatcher.isRunning())
m_QtWebkitAuthenticationFutureWatcher.waitForFinished();
}
+103
View File
@@ -0,0 +1,103 @@
/**
* AuthenticationHelper.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QObject>
#include <QFuture>
#include <QFutureWatcher>
// Roblox Headers
#include "util/HttpAux.h"
#include "RobloxNetworkAccessManager.h"
class AuthenticationHelper : public QObject
{
Q_OBJECT
public:
static AuthenticationHelper& Instance()
{
static AuthenticationHelper instance;
return instance;
}
static QString userAgentStr;
// synchronus authentication
QString authenticateWinINetFromUrlWithTicket(const QString& url, const QString& ticket);
static bool validateMachine();
void authenticateUserAsync(const QString& url, const QString& ticket);
void authenticateHttpSessionAsync(bool state);
void waitForHttpAuthentication();
void waitForQtWebkitAuthentication();
static QString getLoggedInUserUrl();
Q_SIGNALS:
void authenticationDone(bool authSuccess);
void authenticationChanged(bool isAuthenticated) const;
public Q_SLOTS:
bool authenticateHttpLayer();
void deAuthenticateHttpLayer();
bool verifyUserAndAuthenticate(int timeOutTime = 10000);
private Q_SLOTS:
void onSSLErrors(QNetworkReply* networkReply, const QList<QSslError> & errlist);
void httpSessionAuthenticationDone();
private:
AuthenticationHelper();
// Helper functions
QString generateAuthenticationUrl(const QString& authUrl, const QString& ticket);
int getHttpUserId();
int getWebKitUserId();
// WebKit auth
int authenticateWebKitFromHttp();
int authenticateWebKitFromUrlWithTicket(const QString& url, const QString& ticket);
int doWebKitAuthentication(const QString& url);
void deAuthenticateWebKitLayer();
// WinINet auth
QString doHttpGetRequest(const QString& url, const RBX::HttpAux::AdditionalHeaders& externalHeaders = RBX::HttpAux::AdditionalHeaders());
/*override*/bool eventFilter(QObject * watched, QEvent * evt);
void authenticateQtWebkitSessionAsync();
bool authenticateHttpSession(const QString& authenticationURL);
bool authenticateHttpSession();
bool authenticateQtWebkitSession(const QString& authenticationURL);
bool authenticateQtWebkitSession();
bool deAuthenticateHttpSession();
QString getHttpRequest(const QString& urlString, const RBX::HttpAux::AdditionalHeaders& externalHeaders = RBX::HttpAux::AdditionalHeaders());
QFuture<bool> m_HttpAuthenticationFuture;
QFutureWatcher<bool> m_HttpAuthenticationFutureWatcher;
QFuture<bool> m_QtWebkitAuthenticationFuture;
QFutureWatcher<bool> m_QtWebkitAuthenticationFutureWatcher;
int m_CurrentHttpRequestState;
bool m_bHttpSessionAuthenticationState;
bool m_bIgnoreFinishedSignal;
// Member variables
QString savedCookieKey;
static bool httpAuthenticationPending;
static bool verifyUserAuthenticationPending;
static bool previouslyAuthenticated;
};
+523
View File
@@ -0,0 +1,523 @@
/**
* AuthoringSettings.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
// Qt Headers
#include <QDesktopServices>
#include <qmath.h>
// Roblox Headers
#include "v8datamodel/Camera.h"
#include "tool/ToolsArrow.h"
#include "tool/AdvRunDragger.h"
#include "AppDraw/Draw.h"
#include "script/script.h"
// Roblox Studio Headers
#include "AuthoringSettings.h"
#include "RobloxSettings.h"
#include "QFontBoundProp.h"
#include "QDirBoundProp.h"
#include "MobileDevelopmentDeployer.h"
#include "util/RobloxGoogleAnalytics.h"
FASTFLAG(LuaDebugger)
FASTFLAG(LuaDebuggerBreakOnError)
FASTFLAG(StudioSettingsGAEnabled)
FASTFLAG(StudioUseDraggerGrid)
RBX_REGISTER_CLASS(AuthoringSettings);
RBX_REGISTER_ENUM(AuthoringSettings::PermissionLevelShown);
RBX_REGISTER_ENUM(AuthoringSettings::HoverAnimateSpeed);
RBX_REGISTER_ENUM(AuthoringSettings::OutputLayoutMode);
RBX_REGISTER_ENUM(AuthoringSettings::ListDisplayMode);
RBX_REGISTER_ENUM(AuthoringSettings::UIStyle);
RBX_REGISTER_ENUM(AuthoringSettings::TestServerAudioBehavior);
const char* const sAuthoringSettings = "Studio";
const char* const sScriptCategoryName = "Script Editor";
const char* const sScriptColorsCategoryName = "Script Editor Colors";
// AutoSave
RBX::Reflection::BoundProp<QDir> prop_AutoSaveDir("Auto-Save Path", "Auto-Save", &AuthoringSettings::autoSaveDir);
RBX::Reflection::BoundProp<bool> prop_AutoSaveEnable("Auto-Save Enabled", "Auto-Save", &AuthoringSettings::autoSaveEnabled);
RBX::Reflection::BoundProp<int> prop_AutoSaveMinutes("Auto-Save Interval (Minutes)", "Auto-Save", &AuthoringSettings::autoSaveMinutesInterval);
// Browsing
RBX::Reflection::BoundProp<bool> prop_ShowDepricatedItems("DeprecatedObjectsShown", "Browsing", &AuthoringSettings::showDeprecated);
RBX::Reflection::EnumPropDescriptor<AuthoringSettings, AuthoringSettings::PermissionLevelShown> prop_PermissionLevelShown("PermissionLevelShown", "Browsing", &AuthoringSettings::getPermissionLevelShown, &AuthoringSettings::setPermissionLevelShown);
// Scripting - all script properties must be in sScriptCategoryName or ScriptTextEditor::onPropertyChange will not update properly
RBX::Reflection::BoundProp<int> prop_EditorTabWidth("Tab Width", sScriptCategoryName, &AuthoringSettings::editorTabWidth);
RBX::Reflection::BoundProp<bool> prop_EditorAutoIndent("Auto Indent", sScriptCategoryName, &AuthoringSettings::editorAutoIndent);
RBX::Reflection::BoundProp<QFont> prop_EditorFont("Font", sScriptCategoryName, &AuthoringSettings::editorFont);
RBX::Reflection::BoundProp<bool> prop_EditorTextWrap("Text Wrapping", sScriptCategoryName, &AuthoringSettings::editorTextWrap);
// Script Colors - all script properties must be in sScriptColorsCategoryName or ScriptSyntaxHighlighter::onPropertyChange will not update properly
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorTextColor("Text Color", sScriptColorsCategoryName, &AuthoringSettings::editorTextColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorBackgroundColor("Background Color", sScriptColorsCategoryName, &AuthoringSettings::editorBackgroundColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorSelectionColor("Selection Color", sScriptColorsCategoryName, &AuthoringSettings::editorSelectionColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorSelectionBackgroundColor("Selection Background Color", sScriptColorsCategoryName, &AuthoringSettings::editorSelectionBackgroundColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorOperatorColor("Operator Color", sScriptColorsCategoryName, &AuthoringSettings::editorOperatorColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorNumberColor("Number Color", sScriptColorsCategoryName, &AuthoringSettings::editorNumberColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorStringColor("String Color", sScriptColorsCategoryName, &AuthoringSettings::editorStringColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorCommentColor("Comment Color", sScriptColorsCategoryName, &AuthoringSettings::editorCommentColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorPreprocessorColor("Preprocessor Color", sScriptColorsCategoryName, &AuthoringSettings::editorPreprocessorColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorKeywordColor("Keyword Color", sScriptColorsCategoryName, &AuthoringSettings::editorKeywordColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorErrorColor("Error Color", sScriptColorsCategoryName, &AuthoringSettings::editorErrorColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorWarningColor("Warning Color", sScriptColorsCategoryName, &AuthoringSettings::editorWarningColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_EditorFindSelectionBackgroundColor("Find Selection Background Color", sScriptColorsCategoryName, &AuthoringSettings::editorFindSelectionBackgroundColor);
// General
RBX::Reflection::BoundProp<bool> prop_AlwaysSaveScriptChangesWhileRunning("Always Save Script Changes", "General", &AuthoringSettings::alwaysSaveScriptChangesWhileRunning);
RBX::Reflection::EnumPropDescriptor<AuthoringSettings,AuthoringSettings::ListDisplayMode> prop_BasicObjectsDisplayMode("Basic Objects Display Mode","General",&AuthoringSettings::getBasicObjectsDisplayMode,&AuthoringSettings::setBasicObjectsDisplayMode);
RBX::Reflection::BoundProp<int> prop_MaximumOutputLines("Maximum Output Lines", "General", &AuthoringSettings::maximumOutputLines);
RBX::Reflection::EnumPropDescriptor<AuthoringSettings,AuthoringSettings::OutputLayoutMode> prop_OutputLayoutMode("Output Layout Mode", "General", &AuthoringSettings::getOutputLayoutMode, &AuthoringSettings::setOutputLayoutMode);
RBX::Reflection::BoundProp<int> prop_RenderThrottlePercentage("Render Throttle Percentage", "General", &AuthoringSettings::renderThrottlePercentage);
RBX::Reflection::EnumPropDescriptor<AuthoringSettings, AuthoringSettings::UIStyle> prop_UIStyle("UI Style", "General", &AuthoringSettings::getUIStyle, &AuthoringSettings::setUIStyle);
RBX::Reflection::BoundProp<bool> prop_ClearOuputOnStart("Clear Output On Start", "General", &AuthoringSettings::clearOutputOnStart);
// Directories
RBX::Reflection::BoundProp<QDir> prop_DefaultScriptFileDir("DefaultScriptFileDir", "Directories", &AuthoringSettings::defaultScriptFileDir);
RBX::Reflection::BoundProp<QDir> prop_PluginsDir("PluginsDir", "Directories", &AuthoringSettings::pluginsDir);
RBX::Reflection::PropDescriptor<AuthoringSettings, QDir> prop_CoreScriptsDir("OverrideCoreScriptsDir", "Directories", &AuthoringSettings::getCoreScriptsDir, &AuthoringSettings::setCoreScriptsDir);
RBX::Reflection::PropDescriptor<AuthoringSettings, bool> prop_useCoreScriptsDir("OverrideCoreScripts", "Directories", &AuthoringSettings::getOverrideCoreScripts, &AuthoringSettings::setOverrideCoreScripts);
RBX::Reflection::BoundProp<QDir> prop_RecentSavesDir("RecentSavesDir", "Directories", &AuthoringSettings::recentSavesDir);
// Colors
RBX::Reflection::BoundProp<G3D::Color3> prop_SelectColor("Select Color", "Colors", &AuthoringSettings::selectColor);
RBX::Reflection::BoundProp<G3D::Color3> prop_HoverOverColor("Hover Over Color", "Colors", &AuthoringSettings::hoverOverColor);
RBX::Reflection::PropDescriptor<AuthoringSettings, G3D::Color3> prop_PrimaryPartSelectColor("Select/Hover Color", "Primary Part", &AuthoringSettings::getPrimaryPartSelectColor, &AuthoringSettings::setPrimaryPartSelectColor);
RBX::Reflection::PropDescriptor<AuthoringSettings, float> prop_PrimaryPartLineThickness("Line Thickness", "Primary Part", &AuthoringSettings::getPrimaryPartLineThickness, &AuthoringSettings::setPrimaryPartLineThickness);
// Undo
RBX::Reflection::EnumPropDescriptor<AuthoringSettings, RBX::ChangeHistoryService::RuntimeUndoBehavior> prop_RuntimeUndoBehavior("RuntimeUndoBehavior", "Undo", &AuthoringSettings::getRuntimeUndoBehavior, &AuthoringSettings::setRuntimeUndoBehavior);
// Camera
RBX::Reflection::PropDescriptor<AuthoringSettings, float> prop_CameraKeyMoveSpeed("Camera Speed", "Camera", &AuthoringSettings::getCameraKeyMoveSpeed, &AuthoringSettings::setCameraKeyMoveSpeed);
RBX::Reflection::PropDescriptor<AuthoringSettings, float> prop_CameraShiftMoveSpeed("Camera Shift Speed", "Camera", &AuthoringSettings::getCameraShiftMoveSpeed, &AuthoringSettings::setCameraShiftMoveSpeed);
RBX::Reflection::PropDescriptor<AuthoringSettings, float> prop_CameraMouseWheelMoveSpeed("Camera Mouse Wheel Speed", "Camera", &AuthoringSettings::getCameraMouseWheelMoveSpeed, &AuthoringSettings::setCameraMouseWheelMoveSpeed);
// Tools
RBX::Reflection::PropDescriptor<AuthoringSettings, bool> prop_showDraggerGrid("Show Dragger Grid", "Tools", &AuthoringSettings::getShowDraggerGrid, &AuthoringSettings::setShowDraggerGrid);
RBX::Reflection::PropDescriptor<AuthoringSettings, bool> prop_showHoverOver("Show Hover Over", "Tools", &AuthoringSettings::getShowHoverOver, &AuthoringSettings::setShowHoverOver);
RBX::Reflection::BoundProp<bool> prop_AnimateHoverOver("Animate Hover Over", "Tools", &AuthoringSettings::animateHoverOver);
RBX::Reflection::EnumPropDescriptor<AuthoringSettings,AuthoringSettings::HoverAnimateSpeed> prop_HoverAnimateSpeed("Hover Animate Speed","Tools",&AuthoringSettings::getHoverAnimateSpeed,&AuthoringSettings::setHoverAnimateSpeed);
// Lua Debugger
RBX::Reflection::PropDescriptor<AuthoringSettings, bool> prop_LuaDebuggerEnabled("LuaDebuggerEnabled", "Lua Debugger", &AuthoringSettings::getLuaDebuggerEnableState, &AuthoringSettings::setLuaDebuggerEnableState);
// Mobile Deployment
RBX::Reflection::PropDescriptor<AuthoringSettings, int> prop_DeploymentPairing("Device Pairing Code", "Device Deployment", &AuthoringSettings::getDeviceDeploymentPairingCode, &AuthoringSettings::setDeviceDeploymentPairingCode);
// Advanced
RBX::Reflection::BoundProp<bool> prop_diagnosticsBarEnabled("Show Diagnostics Bar", "Advanced", &AuthoringSettings::diagnosticsBarEnabled);
RBX::Reflection::BoundProp<bool> prop_intellisenseEnabled("Enable Intellisense", "Advanced", &AuthoringSettings::intellisenseEnabled);
RBX::Reflection::PropDescriptor<AuthoringSettings, bool> prop_dragMultiPartsAsSinglePartEnabled("Drag Multiple Parts As Single Part", "Advanced", &AuthoringSettings::getDragMutliplePartsAsSinglePart, &AuthoringSettings::setDragMutliplePartsAsSinglePart);
// Audio
RBX::Reflection::EnumPropDescriptor<AuthoringSettings,AuthoringSettings::TestServerAudioBehavior> prop_testServerAudioBehavior("Server Audio Behavior","Audio",&AuthoringSettings::getTestServerAudioBehavior,&AuthoringSettings::setTestServerAudioBehavior);
RBX::Reflection::BoundProp<bool> prop_onlyPlayAudioInFocus("Only Play Audio from Window in Focus", "Audio", &AuthoringSettings::onlyPlayFocusWindowAudio);
namespace RBX {
namespace Reflection {
template<>
EnumDesc<AuthoringSettings::PermissionLevelShown>::EnumDesc()
:EnumDescriptor("PermissionLevelShown")
{
addPair(AuthoringSettings::Game, "Game" );
addPair(AuthoringSettings::RobloxGame, "RobloxGame" );
addPair(AuthoringSettings::RobloxScript, "RobloxScript" );
addPair(AuthoringSettings::Studio, "Studio" );
addPair(AuthoringSettings::Roblox, "Roblox" );
}
}}
namespace RBX {
namespace Reflection {
template<>
EnumDesc<AuthoringSettings::HoverAnimateSpeed>::EnumDesc()
:EnumDescriptor("HoverAnimateSpeed")
{
addPair(AuthoringSettings::VerySlow, "VerySlow" );
addPair(AuthoringSettings::Slow, "Slow" );
addPair(AuthoringSettings::Medium, "Medium" );
addPair(AuthoringSettings::Fast, "Fast" );
addPair(AuthoringSettings::VeryFast, "VeryFast" );
}
}}
namespace RBX {
namespace Reflection {
template<>
EnumDesc<AuthoringSettings::ListDisplayMode>::EnumDesc()
:EnumDescriptor("ListDisplayMode")
{
addPair(AuthoringSettings::Horizontal, "Horizontal" );
addPair(AuthoringSettings::Vertical, "Vertical" );
}
}}
namespace RBX {
namespace Reflection {
template<>
EnumDesc<AuthoringSettings::OutputLayoutMode>::EnumDesc()
:EnumDescriptor("OutputLayoutMode")
{
addPair(AuthoringSettings::OutputLayoutHorizontal, "Horizontal");
addPair(AuthoringSettings::OutputLayoutVertical, "Vertical");
}
}}
namespace RBX {
namespace Reflection {
template<>
EnumDesc<AuthoringSettings::UIStyle>::EnumDesc()
:EnumDescriptor("UIStyle")
{
addPair(AuthoringSettings::Ribbon, "RibbonBar");
addPair(AuthoringSettings::Menu, "SystemMenu");
}
}}
namespace RBX {
namespace Reflection {
template<>
EnumDesc<AuthoringSettings::TestServerAudioBehavior>::EnumDesc()
:EnumDescriptor("ServerAudioBehavior")
{
addPair(AuthoringSettings::Enabled, "Enabled");
addPair(AuthoringSettings::Muted, "Muted");
addPair(AuthoringSettings::OnlineGame, "OnlineGame");
}
}}
AuthoringSettings::AuthoringSettings()
:showDeprecated(false)
,permissionLevelShown(Game)
,defaultScriptFileDir(QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation) + "/ROBLOX/Scripts")
,pluginsDir(AppSettings::instance().tempLocation() + "/Plugins")
,modelPluginsDir(AppSettings::instance().tempLocation() + "/InstalledPlugins")
,coreScriptsDir(AppSettings::instance().tempLocation() + "/CoreScriptOverrides")
,recentSavesDir(QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation) + "/ROBLOX/RecentSaves")
,overrideCoreScripts(false)
,basicObjectsDisplayMode(Vertical)
,maximumOutputLines(5000)
,outputLayoutMode(OutputLayoutVertical)
,renderThrottlePercentage(75)
,alwaysSaveScriptChangesWhileRunning(false)
,clearOutputOnStart(true)
,doSettingsChangedGAEvents(false)
// Script Editor
#ifdef Q_WS_WIN32
,editorFont("Courier New",10)
#else
,editorFont("Courier New",14)
#endif
,editorTabWidth(4)
,editorAutoIndent(true)
,editorTextWrap(false)
,editorTextColor(G3D::Color3::black())
,editorBackgroundColor(G3D::Color3::white())
,editorSelectionColor(G3D::Color3::white())
,editorSelectionBackgroundColor(G3D::Color3uint8(0x6E,0xA1,0xF1))
,editorOperatorColor(G3D::Color3uint8(0x7F,0x7F,0x00))
,editorNumberColor(G3D::Color3uint8(0x00,0x7F,0x7F))
,editorStringColor(G3D::Color3uint8(0x7F,0x00,0x7F))
,editorCommentColor(G3D::Color3uint8(0x00,0x7F,0x00))
,editorPreprocessorColor(G3D::Color3uint8(0x7F,0x00,0x00))
,editorKeywordColor(G3D::Color3uint8(0x00,0x00,0x7F))
,editorErrorColor(G3D::Color3::red())
,editorWarningColor(G3D::Color3::blue())
,editorFindSelectionBackgroundColor(G3D::Color3uint8(0xF6, 0xB9, 0x3F))
// HoverOver
,hoverOverColor(RBX::Draw::hoverOverColor().rgb())
,selectColor(RBX::Draw::selectColor().rgb())
,animateHoverOver(true)
,hoverAnimateSpeed(Slow)
// AutoSave
, autoSaveEnabled(true)
, autoSaveMinutesInterval(5)
, autoSaveDir(QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation) + "/ROBLOX/AutoSaves")
// Advanced
, diagnosticsBarEnabled(false)
, intellisenseEnabled(true)
//Lua debugger
, luaDebuggerEnabled(true)
// Ribbon
,uiStyle(AuthoringSettings::Ribbon)
// Device Deployment
,deploymentPairingCode(0)
// Audio Settings
, onlyPlayFocusWindowAudio(true)
, testServerAudioBehavior(AuthoringSettings::OnlineGame)
, dragMultiPartsAsSinglePart(false)
{}
/**
* Configure settings after the fast flags have loaded to do any special adjustments/handling.
* Must be called after RBX::GlobalAdvancedSettings::singleton()->loadState() has been called.
*/
void AuthoringSettings::configureBasedOnFastFlags()
{
FFlag::LuaDebugger = luaDebuggerEnabled;
// make sure we do not enable break on error if there's no debugger
if (!FFlag::LuaDebugger)
FFlag::LuaDebuggerBreakOnError = false;
if (!FFlag::StudioUseDraggerGrid)
prop_showDraggerGrid.setEditable(false);
setDragMutliplePartsAsSinglePart(dragMultiPartsAsSinglePart);
}
void AuthoringSettings::onPropertyChanged(const RBX::Reflection::PropertyDescriptor& pDescriptor)
{
if (FFlag::StudioSettingsGAEnabled && doSettingsChangedGAEvents)
{
std::string description = pDescriptor.name.str;
description += " On Edit";
std::string valueString = pDescriptor.getStringValue(this);
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_STUDIO_SETTINGS, description.c_str(), valueString.c_str());
}
Instance::onPropertyChanged(pDescriptor);
}
void AuthoringSettings::setDoSettingsChangedGAEvents(bool value)
{
doSettingsChangedGAEvents = value;
}
void AuthoringSettings::setRuntimeUndoBehavior( RBX::ChangeHistoryService::RuntimeUndoBehavior value )
{
RBX::ChangeHistoryService::runtimeUndoBehavior = value;
}
RBX::Security::Permissions AuthoringSettings::getPermissionShown() const
{
switch (permissionLevelShown)
{
default:
RBXASSERT(false);
case Game:
return RBX::Security::None;
case RobloxGame:
return RBX::Security::RobloxPlace;
case RobloxScript:
return RBX::Security::RobloxScript;
case Studio:
return RBX::Security::LocalUser;
case Roblox:
return RBX::Security::Roblox;
}
}
void AuthoringSettings::setPermissionLevelShown( PermissionLevelShown value )
{
if (value == permissionLevelShown)
return;
permissionLevelShown = value;
raiseChanged(prop_PermissionLevelShown);
}
float AuthoringSettings::getCameraKeyMoveSpeed() const
{
return RBX::Camera::CameraKeyMoveFactor;
}
void AuthoringSettings::setCameraKeyMoveSpeed(float value)
{
RBX::Camera::CameraKeyMoveFactor = value;
raiseChanged(prop_CameraKeyMoveSpeed);
}
float AuthoringSettings::getCameraShiftMoveSpeed() const
{
return RBX::Camera::CameraShiftKeyMoveFactor;
}
void AuthoringSettings::setCameraShiftMoveSpeed(float value)
{
RBX::Camera::CameraShiftKeyMoveFactor = value;
raiseChanged(prop_CameraShiftMoveSpeed);
}
float AuthoringSettings::getCameraMouseWheelMoveSpeed() const
{
return RBX::Camera::CameraMouseWheelMoveFactor;
}
void AuthoringSettings::setCameraMouseWheelMoveSpeed(float value)
{
RBX::Camera::CameraMouseWheelMoveFactor = value;
raiseChanged(prop_CameraMouseWheelMoveSpeed);
}
bool AuthoringSettings::getShowDraggerGrid() const
{
return RBX::ArrowToolBase::showDraggerGrid;
}
void AuthoringSettings::setShowDraggerGrid(bool value)
{
RBX::ArrowToolBase::showDraggerGrid = value;
raiseChanged(prop_showDraggerGrid);
}
bool AuthoringSettings::getShowHoverOver() const
{
return RBX::Draw::isHoverOver();
}
void AuthoringSettings::setShowHoverOver(bool value)
{
RBX::Draw::showHoverOver(value);
raiseChanged(prop_showHoverOver);
}
void AuthoringSettings::setHoverAnimateSpeed(HoverAnimateSpeed speed)
{
hoverAnimateSpeed = speed;
raiseChanged(prop_HoverAnimateSpeed);
}
void AuthoringSettings::setBasicObjectsDisplayMode(ListDisplayMode mode)
{
basicObjectsDisplayMode = mode;
raiseChanged(prop_BasicObjectsDisplayMode);
}
void AuthoringSettings::setEditorFont(const QFont& font)
{
editorFont = font;
raiseChanged(prop_EditorFont);
}
void AuthoringSettings::setOutputLayoutMode(OutputLayoutMode mode)
{
outputLayoutMode = mode;
raiseChanged(prop_OutputLayoutMode);
}
bool AuthoringSettings::getLuaDebuggerEnableState() const
{
return luaDebuggerEnabled;
}
void AuthoringSettings::setLuaDebuggerEnableState(bool state)
{
luaDebuggerEnabled = state;
}
int AuthoringSettings::getDeviceDeploymentPairingCode() const
{
return deploymentPairingCode;
}
void AuthoringSettings::setDeviceDeploymentPairingCode(int newCode)
{
if (newCode <= 0)
{
raiseChanged(prop_DeploymentPairing);
}
else if (deploymentPairingCode != newCode)
{
int sizeInDecimal = MobileDevelopmentDeployer::getNumOfDigits(newCode);
//forcing deploymentPairingCode to only have 4 digits
deploymentPairingCode = newCode * qPow(10, MobileDevelopmentDeployer::forcedPairCodeSize() - sizeInDecimal);
raiseChanged(prop_DeploymentPairing);
}
}
void AuthoringSettings::setUIStyle( UIStyle value )
{
if (value == uiStyle)
return;
uiStyle = value;
raiseChanged(prop_UIStyle);
}
AuthoringSettings::UIStyle AuthoringSettings::getUIStyle() const
{
return uiStyle;
}
void AuthoringSettings::setOverrideCoreScripts(bool value)
{
if (value != overrideCoreScripts)
{
overrideCoreScripts = value;
RBX::BaseScript::adminScriptsPath = (overrideCoreScripts) ? coreScriptsDir.absolutePath().toStdString() : "";
raiseChanged(prop_useCoreScriptsDir);
}
}
void AuthoringSettings::setCoreScriptsDir(QDir value)
{
if (value != coreScriptsDir)
{
coreScriptsDir = value;
if (overrideCoreScripts)
{
RBX::BaseScript::adminScriptsPath = coreScriptsDir.absolutePath().toStdString();
}
raiseChanged(prop_CoreScriptsDir);
}
}
void AuthoringSettings::setTestServerAudioBehavior(TestServerAudioBehavior value)
{
if (value == testServerAudioBehavior)
return;
testServerAudioBehavior = value;
raiseChanged(prop_testServerAudioBehavior);
}
G3D::Color3 AuthoringSettings::getPrimaryPartSelectColor() const
{ return RBX::ModelInstance::primaryPartSelectColor().rgb(); }
void AuthoringSettings::setPrimaryPartSelectColor(G3D::Color3 color)
{
// as of now we are using same color for selection and hover over
RBX::ModelInstance::setPrimaryPartSelectColor(color);
RBX::ModelInstance::setPrimaryPartHoverOverColor(color);
}
G3D::Color3 AuthoringSettings::getPrimaryPartHoverOverColor() const
{ return RBX::ModelInstance::primaryPartHoverOverColor().rgb(); }
void AuthoringSettings::setPrimaryPartHoverOverColor(G3D::Color3 color)
{ RBX::ModelInstance::setPrimaryPartHoverOverColor(color); }
float AuthoringSettings::getPrimaryPartLineThickness() const
{ return RBX::ModelInstance::primaryPartLineThickness(); }
void AuthoringSettings::setPrimaryPartLineThickness(float thickness)
{
if (thickness > 0.0f && thickness < 0.05f)
{
RBX::ModelInstance::setPrimaryPartLineThickness(thickness);
return;
}
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Invalid line thickness. Input value must be less than 0.05");
}
bool AuthoringSettings::getDragMutliplePartsAsSinglePart() const
{ return dragMultiPartsAsSinglePart;}
void AuthoringSettings::setDragMutliplePartsAsSinglePart(bool state)
{
dragMultiPartsAsSinglePart = state;
RBX::AdvRunDragger::dragMultiPartsAsSinglePart = state;
}
+239
View File
@@ -0,0 +1,239 @@
/**
* AuthoringSettings.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QFont>
#include <QDir>
// Roblox Headers
#include "v8datamodel/GlobalSettings.h"
#include "v8datamodel/ChangeHistory.h"
#include "ReflectionMetadata.h"
// Script properties must be in these categories
extern const char* const sScriptCategoryName;
extern const char* const sScriptColorsCategoryName;
/**
* This class holds settings used by game authors, especially script-related settings.
* permissionLevelShown is used by the Object Browser to filter descriptors based on
* permission level. By default we only show APIs that Game Scripts can show.
* TODO: Consider moving isShown into a class that isn't called "Settings"
**/
extern const char* const sAuthoringSettings;
class AuthoringSettings : public RBX::GlobalAdvancedSettingsItem<AuthoringSettings, sAuthoringSettings>
{
public:
AuthoringSettings(void);
void configureBasedOnFastFlags();
void setDoSettingsChangedGAEvents(bool);
typedef enum {
Game,
RobloxGame,
RobloxScript,
Studio,
Roblox
} PermissionLevelShown;
PermissionLevelShown permissionLevelShown;
typedef enum
{
VerySlow,
Slow,
Medium,
Fast,
VeryFast,
} HoverAnimateSpeed;
HoverAnimateSpeed hoverAnimateSpeed;
typedef enum
{
Horizontal,
Vertical,
} ListDisplayMode;
ListDisplayMode basicObjectsDisplayMode;
typedef enum
{
OutputLayoutHorizontal,
OutputLayoutVertical,
} OutputLayoutMode;
OutputLayoutMode outputLayoutMode;
typedef enum
{
Ribbon,
Menu
} UIStyle;
typedef enum
{
Enabled,
Muted,
OnlineGame
} TestServerAudioBehavior;
bool showDeprecated;
QDir defaultScriptFileDir;
QDir pluginsDir;
QDir modelPluginsDir;
QDir coreScriptsDir;
QDir recentSavesDir;
bool overrideCoreScripts;
bool alwaysSaveScriptChangesWhileRunning;
int maximumOutputLines;
int renderThrottlePercentage;
bool clearOutputOnStart;
// Device Deployment
int deploymentPairingCode;
// AutoSave
QDir autoSaveDir;
bool autoSaveEnabled;
int autoSaveMinutesInterval;
// Hover Over
bool animateHoverOver;
G3D::Color3 hoverOverColor;
G3D::Color3 selectColor;
// Script Editor
QFont editorFont;
int editorTabWidth;
bool editorAutoIndent;
bool editorTextWrap;
// Script Editor Colors
G3D::Color3 editorTextColor;
G3D::Color3 editorBackgroundColor;
G3D::Color3 editorSelectionColor;
G3D::Color3 editorSelectionBackgroundColor;
G3D::Color3 editorOperatorColor;
G3D::Color3 editorNumberColor;
G3D::Color3 editorStringColor;
G3D::Color3 editorCommentColor;
G3D::Color3 editorPreprocessorColor;
G3D::Color3 editorKeywordColor;
G3D::Color3 editorErrorColor;
G3D::Color3 editorWarningColor;
G3D::Color3 editorFindSelectionBackgroundColor;
// Lua debugger
bool luaDebuggerEnabled;
//Diagnostics bar
bool diagnosticsBarEnabled;
//Intellisense
bool intellisenseEnabled;
//Audio
bool onlyPlayFocusWindowAudio;
TestServerAudioBehavior getTestServerAudioBehavior() const {return testServerAudioBehavior;}
void setTestServerAudioBehavior(TestServerAudioBehavior value);
// Ribbon or menu
UIStyle getUIStyle() const;
void setUIStyle(UIStyle value);
RBX::ChangeHistoryService::RuntimeUndoBehavior getRuntimeUndoBehavior() const { return RBX::ChangeHistoryService::runtimeUndoBehavior; }
void setRuntimeUndoBehavior(RBX::ChangeHistoryService::RuntimeUndoBehavior value);
RBX::Security::Permissions getPermissionShown() const;
PermissionLevelShown getPermissionLevelShown() const { return permissionLevelShown; }
void setPermissionLevelShown(PermissionLevelShown value);
template<class DescriptorType>
bool isShown(RBX::Reflection::Metadata::Class* metadata, const DescriptorType& descriptor)
{
if (descriptor.security > getPermissionShown())
return false;
if (RBX::Reflection::Metadata::Item::isDeprecated(metadata, descriptor) && !showDeprecated)
return false;
if (!metadata)
return false;
if (!metadata->getInsertable())
return false;
return metadata->isBrowsable();
}
template<class DescriptorType>
bool isHiddenInBrowser(RBX::Reflection::Metadata::Item* metadata, const DescriptorType& descriptor)
{
if (descriptor.security > getPermissionShown())
return true;
if (RBX::Reflection::Metadata::Item::isDeprecated(metadata, descriptor) && !showDeprecated)
return true;
if (!metadata)
return false;
return !metadata->isBrowsable();
}
QDir getCoreScriptsDir() const { return coreScriptsDir; }
void setCoreScriptsDir(QDir value);
bool getOverrideCoreScripts() const { return overrideCoreScripts; }
void setOverrideCoreScripts(bool value);
void onPluginsDirChanged(const RBX::Reflection::PropertyDescriptor&);
float getCameraKeyMoveSpeed() const;
float getCameraShiftMoveSpeed() const;
float getCameraMouseWheelMoveSpeed() const;
void setCameraKeyMoveSpeed(float value);
void setCameraShiftMoveSpeed(float value);
void setCameraMouseWheelMoveSpeed(float value);
bool getShowDraggerGrid() const;
void setShowDraggerGrid(bool value);
bool getShowHoverOver() const;
void setShowHoverOver(bool value);
HoverAnimateSpeed getHoverAnimateSpeed() const { return hoverAnimateSpeed; }
void setHoverAnimateSpeed(HoverAnimateSpeed speed);
ListDisplayMode getBasicObjectsDisplayMode() const { return basicObjectsDisplayMode; }
void setBasicObjectsDisplayMode(ListDisplayMode mode);
void setEditorFont(const QFont& font);
OutputLayoutMode getOutputLayoutMode() const { return outputLayoutMode; }
void setOutputLayoutMode(OutputLayoutMode mode);
bool getLuaDebuggerEnableState() const;
void setLuaDebuggerEnableState(bool state);
int getDeviceDeploymentPairingCode() const;
void setDeviceDeploymentPairingCode(int newCode);
G3D::Color3 getPrimaryPartSelectColor() const;
void setPrimaryPartSelectColor(G3D::Color3);
G3D::Color3 getPrimaryPartHoverOverColor() const;
void setPrimaryPartHoverOverColor(G3D::Color3);
float getPrimaryPartLineThickness() const;
void setPrimaryPartLineThickness(float thickness);
bool getDragMutliplePartsAsSinglePart() const;
void setDragMutliplePartsAsSinglePart(bool state);
private:
void persistPluginsDir();
UIStyle uiStyle;
TestServerAudioBehavior testServerAudioBehavior;
bool dragMultiPartsAsSinglePart;
bool doSettingsChangedGAEvents;
protected:
void onPropertyChanged(const RBX::Reflection::PropertyDescriptor& pDescriptor);
};
+134
View File
@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AutoSave</class>
<widget class="QDialog" name="AutoSave">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>650</width>
<height>350</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>650</width>
<height>350</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>650</width>
<height>375</height>
</size>
</property>
<property name="windowTitle">
<string>Auto-Save Recovery</string>
</property>
<property name="windowOpacity">
<double>3.000000000000000</double>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>350</height>
</size>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="openButton">
<property name="text">
<string>Open</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ignoreButton">
<property name="text">
<string>Ignore</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteButton">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+229
View File
@@ -0,0 +1,229 @@
/**
* AutoSaveDialog.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "AutoSaveDialog.h"
// Qt Headers
#include <QSettings>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
// Roblox Studio Headers
#include "AuthoringSettings.h"
#include "RobloxDocManager.h"
#include "UpdateUIManager.h"
#include "RobloxMainWindow.h"
#include "RobloxIDEDoc.h"
#include "RobloxApplicationManager.h"
LOGVARIABLE(AutoSave, 0)
AutoSaveDialog::AutoSaveDialog(QWidget* Parent)
: QDialog(Parent,Qt::WindowTitleHint | Qt::WindowSystemMenuHint)
{
setAttribute(Qt::WA_DeleteOnClose,false);
m_UI.setupUi(this);
#ifdef Q_WS_WIN32
QString text = tr(
"<font size=4><b>An auto-save recovery file was detected!</b></font><br><br>"
"This means that Roblox Studio was not shut down properly and <u><i>may not have saved your data</i></u>!<br><br>"
"Do you want to load the auto-save recovery file?<br>"
"<ul>"
" <li>"
" <font size=4><b><i>Open</i></b></font> will open the file. You can choose which file to load if there is more than one "
" recovery file. Once an auto-save is loaded, it will not be deleted until the file is re-saved "
" with a different filename.<br>"
" </li>"
" <li>"
" <font size=4><b><i>Ignore</i></b></font> will continue and leave the files alone. However, the next time ROBLOX Studio "
" is run, this message box will pop up again.<br>"
" </li>"
" <li>"
" <font size=4><b><i>Delete</i></b></font> will ask to delete all auto-save files.<br>"
" </li>"
"</ul>"
"<br><br>" );
#else
QString text = tr(
"<font size=4><font size=6><b>An auto-save recovery file was detected!</b></font><br><br>"
"This means that Roblox Studio was not shut down properly and <u><i>may not have saved your data</i></u>!<br><br>"
"Do you want to load the auto-save recovery file?<br>"
"<ul>"
" <li>"
" <font size=5><b><i>Open</i></b></font> will open the file. You can choose which file to load if there is more than one "
" recovery file. Once an auto-save is loaded, it will not be deleted until the file is re-saved "
" with a different filename.<br>"
" </li>"
" <li>"
" <font size=5><b><i>Ignore</i></b></font> will continue and leave the files alone. However, the next time ROBLOX Studio "
" is run, this message box will pop up again.<br>"
" </li>"
" <li>"
" <font size=5><b><i>Delete</i></b></font> will ask to delete all auto-save files.<br>"
" </li>"
"</ul>"
"<br><br></font>" );
#endif
m_UI.label->setText(text);
connect(m_UI.openButton,SIGNAL(clicked()),this,SLOT(onOpen()));
connect(m_UI.ignoreButton,SIGNAL(clicked()),this,SLOT(onIgnore()));
connect(m_UI.deleteButton,SIGNAL(clicked()),this,SLOT(onDelete()));
}
AutoSaveDialog::~AutoSaveDialog()
{
}
/**
* Checks to see if there are any auto-save files.
*/
bool AutoSaveDialog::checkForAutoSaveFiles()
{
// don't show if we've loaded a place
if ( RobloxDocManager::Instance().getPlayDoc() )
return false;
// don't check for auto-saves if there are other instances running
if ( RobloxApplicationManager::instance().getApplicationCount() != 1 )
return false;
return !getFileList().empty();
}
/**
* Gets the list of auto-save files in the auto-save directory.
* Filters out files that do not have "_autosave_" in their name.
*/
QStringList AutoSaveDialog::getFileList()
{
QStringList list;
FASTLOGS(FLog::AutoSave, "Getting list of AutoSave files in dir %s", AuthoringSettings::singleton().autoSaveDir.absolutePath().toStdString());
const QDir dir(AuthoringSettings::singleton().autoSaveDir);
const QStringList entries = dir.entryList(QStringList() << "*.rbxl");
if ( entries.empty() )
return list;
for ( int i = 0 ; i < entries.size() ; i++ )
{
FASTLOGS(FLog::AutoSave, "Found file %s", entries[i].toStdString());
if ( entries[i].contains("_AUTOSAVE_",Qt::CaseInsensitive) )
{
FASTLOG(FLog::AutoSave, "Pattern matches, add to list");
list.append(entries[i]);
}
}
return list;
}
/**
* Callback for closing the dialog.
* Calls onIgnore().
*/
void AutoSaveDialog::closeEvent(QCloseEvent* event)
{
QDialog::closeEvent(event);
onIgnore();
}
/**
* Callback for user clicking the open button.
* Attempts to open the file if there is just one file or pops a file open
* dialog if there are more. Failing to open the file will delete the file.
*/
void AutoSaveDialog::onOpen()
{
const QDir dir(AuthoringSettings::singleton().autoSaveDir);
const QStringList entries = getFileList();
QString fileName;
if ( entries.size() == 1 )
{
// if only one file, try to load it
fileName = dir.filePath(entries[0]);
}
else
{
fileName = QFileDialog::getOpenFileName(
this,
tr("Open Roblox File"),
dir.absolutePath(),
tr("Roblox Places Files (*.rbxl)"));
}
FASTLOGS(FLog::AutoSave, "AutoSave open: file %s", fileName.toStdString());
if ( !fileName.isEmpty() )
{
accept();
bool openResult = UpdateUIManager::Instance().getMainWindow().handleFileOpen(fileName,IRobloxDoc::IDE);
if ( openResult )
{
FASTLOG(FLog::AutoSave, "File opened successfully");
RobloxDocManager::Instance().getPlayDoc()->setAutoSaveLoad();
}
else
{
FASTLOG(FLog::AutoSave, "Bad file, remove");
// kill the bad file, it's useless to us
QFile::remove(fileName);
if ( !getFileList().empty() )
reject();
}
}
}
/**
* Callback for user clicking the ignore button.
* Exits the dialog.
*/
void AutoSaveDialog::onIgnore()
{
FASTLOG(FLog::AutoSave, "AutoSave ignore");
accept();
}
/**
* Callback for user clicking the delete button.
* Deletes all files after confirmation.
*/
void AutoSaveDialog::onDelete()
{
FASTLOG(FLog::AutoSave, "AutoSave delete");
const QDir dir(AuthoringSettings::singleton().autoSaveDir);
const QStringList entries = getFileList();
const QString confirmMessage =
QString(tr("Do you want to delete all auto-save files? There are %1 file(s).")).
arg(entries.size());
const int confirmationResult = QMessageBox::question(
this,
tr("Confirm Auto-Save Delete"),
confirmMessage,
QMessageBox::Yes | QMessageBox::No );
if ( confirmationResult == QMessageBox::Yes )
{
for ( int i = 0 ; i < entries.size() ; ++i )
{
FASTLOGS(FLog::AutoSave, "Removing %s", entries[i].toStdString());
QFile::remove(dir.filePath(entries[i]));
}
accept();
}
}
+43
View File
@@ -0,0 +1,43 @@
/**
* AutoSaveDialog.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
#include <QDialog>
#include "ui_AutoSave.h"
/**
* Dialog for asking the user what to do with auto-save recovery files
* that were not cleaned up properly.
*/
class AutoSaveDialog : public QDialog
{
Q_OBJECT
public:
AutoSaveDialog(QWidget* Parent);
virtual ~AutoSaveDialog();
static bool checkForAutoSaveFiles();
public Q_SLOTS:
void onOpen();
void onIgnore();
void onDelete();
protected:
void closeEvent(QCloseEvent* event);
private:
static QStringList getFileList();
Ui::AutoSave m_UI;
};
+30
View File
@@ -0,0 +1,30 @@
//
// Crash.h
// RobloxStudio
//
// Created by agrawal on 11/16/11.
// Copyright 2011 Personal. All rights reserved.
//
#pragma once
typedef void *BreakpadRef;
class CrashReporter
{
public:
static void setupCrashReporter();
static void shutDownCrashReporter();
static void addBreakpadLog(const char* logfile);
static void addBreakpadKeyValue(const char* key, const char* value);
static void CreateLogDump();
private:
static void addDebugInfoToBreakpad();
static void initBreakpadCallback(BreakpadRef breakpad);
static BreakpadRef breakpad;
static char* context;
};
+165
View File
@@ -0,0 +1,165 @@
//
// Crash.mm
// RobloxStudio
//
// Created by agrawal on 11/16/11.
// Copyright 2011 Personal. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#include <QString>
#include "BreakpadCrashReporter.h"
#include "breakpad.h"
#include "RobloxSettings.h"
#include "RobloxServicesTools.h"
#include "util/Guid.h"
#include "util/FileSystem.h"
#include "v8datamodel/Stats.h"
#include "rbx/RbxDbgInfo.h"
BreakpadRef CrashReporter::breakpad = NULL;
char* CrashReporter::context = NULL;
DYNAMIC_FASTINTVARIABLE(MacInfluxHundredthsPercentage, 1000)
void CrashReporter::setupCrashReporter()
{
if(breakpad != NULL)
return;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSMutableDictionary *plist = [[NSMutableDictionary alloc] init];
[plist addEntriesFromDictionary:[[NSBundle mainBundle] infoDictionary]];
NSString* breakpadUrl = [NSString stringWithUTF8String:GetBreakpadUrl(RobloxSettings::getBaseURL().toStdString(), true).c_str()];
[plist setValue:breakpadUrl forKey:@BREAKPAD_URL];
// Out-of-process breakpad is broken on 10.10 and newer
[plist setValue:[NSNumber numberWithBool:TRUE] forKey:@BREAKPAD_IN_PROCESS];
[plist setValue:NSTemporaryDirectory() forKey:@BREAKPAD_DUMP_DIRECTORY];
breakpad = BreakpadCreate(plist);
initBreakpadCallback(breakpad);
addBreakpadKeyValue("RobloxProduct", "RobloxStudio");
NSString* version = [plist objectForKey:@"CFBundleShortVersionString"];
BreakpadAddUploadParameter(breakpad, @"Version", version);
NSString* session = [NSString stringWithFormat:@"%s", RBX::Guid().readableString(6).substr(0,5).c_str()];
BreakpadAddUploadParameter(breakpad, @"Session", session);
addBreakpadKeyValue("Platform", "Mac");
addDebugInfoToBreakpad();
[pool release];
}
void CrashReporter::addBreakpadLog(const char* file)
{
if(breakpad == NULL)
return;
NSString* s = [NSString stringWithFormat:@"%s", file];
BreakpadAddLogFile(breakpad, s);
}
void CrashReporter::addBreakpadKeyValue(const char* key, const char* value)
{
if(breakpad == NULL)
return;
NSString* sKey = [NSString stringWithFormat:@"%s", key];
NSString* sVal = [NSString stringWithFormat:@"%s", value];
BreakpadAddUploadParameter(breakpad, sKey, sVal);
}
void CrashReporter::shutDownCrashReporter()
{
if(breakpad == NULL)
return;
// This will generate the symbol dump every time we quit
// If you need this then just enable the following line
// BreakpadGenerateAndSendReport(breakpad);
BreakpadRelease(breakpad);
breakpad = NULL;
}
void CrashReporter::addDebugInfoToBreakpad()
{
char result[64];
sprintf(result, "%lu", RBX::RbxDbgInfo::s_instance.cPhysicalTotal);
addBreakpadKeyValue("cPhysicalTotal", result);
sprintf(result, "%lu", RBX::RbxDbgInfo::s_instance.cbPageSize);
addBreakpadKeyValue("cbPageSize", result);
sprintf(result, "%lu", RBX::RbxDbgInfo::s_instance.TotalVideoMemory);
addBreakpadKeyValue("TotalVideoMemory", result);
sprintf(result, "%lu", RBX::RbxDbgInfo::s_instance.NumCores);
addBreakpadKeyValue("NumCores", result);
addBreakpadKeyValue("GfxCardName", RBX::RbxDbgInfo::s_instance.GfxCardName);
addBreakpadKeyValue("GfxCardDriverVersion", RBX::RbxDbgInfo::s_instance.GfxCardDriverVersion);
addBreakpadKeyValue("GfxCardVendorName", RBX::RbxDbgInfo::s_instance.GfxCardVendorName);
addBreakpadKeyValue("AudioDeviceName", RBX::RbxDbgInfo::s_instance.AudioDeviceName);
}
extern "C" void writeFastLogDumpHelper(const char *fileName, int numEntries);
//After the crash happens this will be called from Breakpad where we will ask the FastLog to dump teh log data to a file provided as context
bool BreakpadCallback(int exception_type, int exception_code, mach_port_t crashing_thread, void *context)
{
RBX::Analytics::InfluxDb::Points points;
points.addPoint("SessionReport" , "MacROBLOXStudioCrash");
points.report("Mac-RobloxStudio-SessionReport", DFInt::MacInfluxHundredthsPercentage);
// FIXME: https://jira.roblox.com/browse/CLI-10561
// These calls are not safe to be called in crash handling context:
// 1. They allocate
// 2. There's a code path that leads to interacting with dead NSRunLoop, thus freezing crash generation
RBX::Analytics::EphemeralCounter::reportCounter("Mac-ROBLOXStudio-Crash", 1, true);
RBX::Analytics::EphemeralCounter::reportCounter("ROBLOXStudio-Crash", 1, true);
if (context)
writeFastLogDumpHelper((const char *)context, 200);
return true;
}
void CrashReporter::initBreakpadCallback(BreakpadRef breakpad)
{
//get the log file
boost::filesystem::path logFile = RBX::FileSystem::getLogsDirectory();
std::string guid;
RBX::Guid::generateRBXGUID(guid);
logFile /= "log_" + guid.substr(3,6) + ".txt";
//we will set the log file as context so can get it back (DO NOT delete)
context = new char[strlen(logFile.c_str())+1];
strcpy(context, logFile.c_str());
// Give Fast Log a chance to put the FastLog into a file so that breakpad can pick it up
BreakpadSetFilterCallback(breakpad, BreakpadCallback, (void *)context);
//add log file with Breakpad
BreakpadAddLogFile(breakpad, [NSString stringWithFormat:@"%s", logFile.c_str()]);
}
void CrashReporter::CreateLogDump()
{
// Dump the FastLog into Logs
writeFastLogDumpHelper(context, 200);
}
+203
View File
@@ -0,0 +1,203 @@
/*
Copyright (c) 2009, Aleksey Palazhchenko
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "BreakpadHandler.h"
#include <QtCore/QDir>
#include <QtCore/QProcess>
#include <QtCore/QCoreApplication>
#if defined(Q_OS_MAC)
#include "client/mac/handler/exception_handler.h"
#elif defined(Q_OS_LINUX)
#include "client/linux/handler/exception_handler.h"
#elif defined(Q_OS_WIN32)
#include "client/windows/handler/exception_handler.h"
#endif
namespace BreakpadQt
{
class GlobalHandlerPrivate
{
public:
GlobalHandlerPrivate();
~GlobalHandlerPrivate();
public:
static char reporter_[1024];
static char reporterArguments_[8*1024];
static google_breakpad::ExceptionHandler* handler_;
static ReportCrashesToSystem reportCrashesToSystem_;
};
char GlobalHandlerPrivate::reporter_[1024] = {0};
char GlobalHandlerPrivate::reporterArguments_[8*1024] = {0};
google_breakpad::ExceptionHandler* GlobalHandlerPrivate::handler_ = 0;
ReportCrashesToSystem GlobalHandlerPrivate::reportCrashesToSystem_ = ReportUnhandled;
bool launcher(const char* program, const char* const arguments[])
{
// TODO launcher
// if(!GlobalHandlerPrivate::reporter_.isEmpty()) {
// QProcess::startDetached(GlobalHandlerPrivate::reporter_); // very likely we will die there
// }
Q_UNUSED(program);
Q_UNUSED(arguments);
return false;
}
#if defined(Q_OS_WIN32)
bool DumpCallback(const wchar_t* _dump_dir,
const wchar_t* _minidump_id,
void* context,
EXCEPTION_POINTERS* exinfo,
MDRawAssertionInfo* assertion,
bool success)
#else
bool DumpCallback(const char* _dump_dir,
const char* _minidump_id,
void *context, bool success)
#endif
{
Q_UNUSED(_dump_dir);
Q_UNUSED(_minidump_id);
Q_UNUSED(context);
#if defined(Q_OS_WIN32)
Q_UNUSED(assertion);
Q_UNUSED(exinfo);
#endif
/*
NO STACK USE, NO HEAP USE THERE !!!
Creating QString's, using qDebug, etc. - everything is crash-unfriendly.
*/
launcher(GlobalHandlerPrivate::reporter_, 0);
return (GlobalHandlerPrivate::reportCrashesToSystem_ == ReportUnhandled) ? success : false;
}
GlobalHandlerPrivate::GlobalHandlerPrivate()
{
handler_ = new google_breakpad::ExceptionHandler(/*DumpPath*/ "", /*FilterCallback*/ 0, DumpCallback, /*context*/ 0, true, 0);
}
GlobalHandlerPrivate::~GlobalHandlerPrivate()
{
//delete handler_;
//handler_ = 0;
}
GlobalHandler* GlobalHandler::instance()
{
static GlobalHandler globalHandler;
return &globalHandler;
}
GlobalHandler::GlobalHandler()
{
d = new GlobalHandlerPrivate();
}
GlobalHandler::~GlobalHandler()
{
//delete d;
//d = 0;
}
void GlobalHandler::setDumpPath(const QString& path)
{
QString absPath = path;
if(!QDir::isAbsolutePath(absPath)) {
absPath = QDir::cleanPath(qApp->applicationDirPath() + QLatin1String("/") + path);
qDebug("BreakpadQt: setDumpPath: %s -> %s", qPrintable(path), qPrintable(absPath));
}
Q_ASSERT(QDir::isAbsolutePath(absPath));
QDir().mkpath(absPath);
Q_ASSERT(QDir().exists(absPath));
# if defined(Q_OS_WIN32)
d->handler_->set_dump_path(absPath.toStdWString());
# else
d->handler_->set_dump_path(absPath.toStdString());
# endif
}
void GlobalHandler::setReporter(const QString& reporter)
{
QString rep = reporter;
if(!QDir::isAbsolutePath(rep)) {
# if defined(Q_OS_MAC)
// TODO(AlekSi) What to do if we are not inside bundle?
rep = QDir::cleanPath(qApp->applicationDirPath() + QLatin1String("/../Resources/") + rep);
# elif defined(Q_OS_LINUX) || defined(Q_OS_WIN32)
// MAYBE(AlekSi) Better place for Linux? libexec? or what?
rep = QDir::cleanPath(qApp->applicationDirPath() + QLatin1String("/") + rep);
# else
What is this?!
# endif
qDebug("BreakpadQt: setReporter: %s -> %s", qPrintable(reporter), qPrintable(rep));
}
Q_ASSERT(QDir::isAbsolutePath(rep));
// add .exe for Windows if needed
# if defined(Q_OS_WIN32)
if(!QDir().exists(rep)) {
rep += QLatin1String(".exe");
}
# endif
Q_ASSERT(QDir().exists(rep));
qstrcpy(d->reporter_, QFile::encodeName(rep));
}
void GlobalHandler::setReportCrashesToSystem(ReportCrashesToSystem report)
{
d->reportCrashesToSystem_ = report;
}
bool GlobalHandler::writeMinidump()
{
bool res = d->handler_->WriteMinidump();
if (res) {
qDebug("BreakpadQt: writeMinidump() successed.");
} else {
qWarning("BreakpadQt: writeMinidump() failed.");
}
return res;
}
} // namespace
+66
View File
@@ -0,0 +1,66 @@
/*
Copyright (c) 2009, Aleksey Palazhchenko
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef BREAKPAD_HANDLER_H
#define BREAKPAD_HANDLER_H
#include <QtCore/QString>
namespace google_breakpad { class ExceptionHandler; }
namespace BreakpadQt
{
class GlobalHandlerPrivate;
enum ReportCrashesToSystem
{
ReportUnhandled = 1,
AlwaysReport = 2
};
class GlobalHandler
{
public:
static GlobalHandler* instance();
void setDumpPath(const QString& path);
void setReporter(const QString& reporter);
void setReportCrashesToSystem(ReportCrashesToSystem report);
bool writeMinidump();
private:
GlobalHandler();
~GlobalHandler();
Q_DISABLE_COPY(GlobalHandler)
GlobalHandlerPrivate* d;
};
} // namespace
#endif // BREAKPAD_HANDLER_H
+921
View File
@@ -0,0 +1,921 @@
/* Copyright 2014 ROBLOX Corporation, All Rights Reserved */
#include "stdafx.h"
#include "CSGOperations.h"
#include "Util/RobloxGoogleAnalytics.h"
#include "v8datamodel/PartOperation.h"
#include "V8DataModel/CSGDictionaryService.h"
#include "V8Xml/SerializerBinary.h"
#include "../Rendering/GfxRender/GeometryGenerator.h"
#include "V8Datamodel/SpecialMesh.h"
#include "v8DataModel/Workspace.h"
#include "v8DataModel/DataModel.h"
#include "V8DataModel/NonReplicatedCSGDictionaryService.h"
#include "V8DataModel/ChangeHistory.h"
#include "../CSG/CSGKernel.h"
DYNAMIC_FASTFLAG(MaterialPropertiesEnabled)
FASTFLAGVARIABLE(CSGMeshColorToolsEnabled, false)
FASTFLAGVARIABLE(StudioCSGChildDataNoSizeLimit, false)
FASTFLAGVARIABLE(CSGNewTriangulateFallBack, true)
FASTFLAGVARIABLE(CSGDelayParentingOperationToEnd, false)
RBX::CSGOperations::CSGOperations( DataModel* pDatamodel, MessageCallback failedFun )
: mDatamodel(pDatamodel)
, operationFailed(failedFun)
{
}
bool RBX::CSGOperations::doUnion(
std::vector<boost::shared_ptr<RBX::Instance> >::const_iterator start,
std::vector<boost::shared_ptr<RBX::Instance> >::const_iterator end,
boost::shared_ptr<PartOperation>& partOperation )
{
bool allNeg = true;
for (Instances::const_iterator iter = start; iter != end; ++iter)
{
if (!RBX::Instance::fastSharedDynamicCast<NegateOperation>(*iter))
{
allNeg = false;
break;
}
}
//shared_ptr<RBX::PartOperation> partOperation;
try
{
if (allNeg)
partOperation = Creatable<Instance>::create<NegateOperation>();
else
partOperation = Creatable<Instance>::create<UnionOperation>();
if (!FFlag::CSGDelayParentingOperationToEnd)
partOperation->setParent(mDatamodel->getWorkspace());
}
catch (std::exception &)
{
return false;
}
Instance* newOperationParent = NULL;
if (FFlag::CSGDelayParentingOperationToEnd)
{
newOperationParent = getCommonParentOrWorkspace(start, end);
}
//Convert to mesh
if (!setUnionMesh(partOperation, start, end, false))
{
partOperation->setParent(NULL);
partOperation = boost::shared_ptr<RBX::PartOperation>();
return false;
}
CSGDictionaryService* dictionaryService = ServiceProvider::create< CSGDictionaryService >(mDatamodel);
NonReplicatedCSGDictionaryService* nrDictionaryService = ServiceProvider::create<NonReplicatedCSGDictionaryService>(mDatamodel);
if (FFlag::CSGDelayParentingOperationToEnd)
{
Instances selectionCopy(start, end);
for (auto iter : selectionCopy)
{
iter->setParent(partOperation.get());
if (shared_ptr<PartOperation> partOp = RBX::Instance::fastSharedDynamicCast<PartOperation>(iter))
{
dictionaryService->retrieveData(*partOp);
nrDictionaryService->retrieveData(*partOp);
partOp->setMeshData(RBX::BinaryString(""));
partOp->setPhysicsData(RBX::BinaryString(""));
}
}
}
else
{
for (RBX::Instances::const_iterator iter = start; iter != end; ++iter)
{
(*iter)->setParent(partOperation.get());
if (shared_ptr<PartOperation> partOp = RBX::Instance::fastSharedDynamicCast<PartOperation>(*iter))
{
dictionaryService->retrieveData(*partOp);
nrDictionaryService->retrieveData(*partOp);
partOp->setMeshData(RBX::BinaryString(""));
partOp->setPhysicsData(RBX::BinaryString(""));
}
}
}
//Serialize children
std::stringstream ss;
SerializerBinary::serialize(ss, partOperation.get());
BinaryString childDataBinaryStr(ss.str());
if (!FFlag::StudioCSGChildDataNoSizeLimit && childDataBinaryStr.value().size() > PartOperation::getMaximumMeshStreamSize())
{
char buf[128];
sprintf( buf, "This union is too complex. There is a %d triangle limit for unions. Please undo the last change.", (int)RBX::PartOperation::getMaximumTriangleCount());
partOperation->setParent(NULL);
partOperation = boost::shared_ptr<RBX::PartOperation>();
operationFailed("Union Rejected", buf);
return true;
}
partOperation->setChildData(childDataBinaryStr);
if (!FFlag::CSGDelayParentingOperationToEnd)
{
dictionaryService->storeData(*partOperation);
nrDictionaryService->storeData(*partOperation);
}
dictionaryService->insertMesh(*partOperation);
//Remove Children
while(partOperation->numChildren())
partOperation->getChild(0)->setParent(NULL);
if (FFlag::CSGDelayParentingOperationToEnd)
{
RBXASSERT(partOperation->getParent() == NULL);
RBXASSERT(newOperationParent != NULL);
partOperation->setParent(newOperationParent);
}
return true;
}
bool RBX::CSGOperations::doNegate(
std::vector<boost::shared_ptr<RBX::Instance> >::const_iterator start,
std::vector<boost::shared_ptr<RBX::Instance> >::const_iterator end,
std::vector<shared_ptr<RBX::Instance> >& toSelect )
{
std::vector<shared_ptr<RBX::Instance> > toRemove;
bool success = negateSelection(toRemove, toSelect, start, end);
if (!success)
{
for (std::vector<shared_ptr<RBX::Instance> >::iterator iter = toSelect.begin(); iter != toSelect.end(); ++iter)
(*iter)->setParent(NULL);
return true;
}
for (std::vector<shared_ptr<RBX::Instance> >::iterator iter = toRemove.begin(); iter != toRemove.end(); ++iter)
(*iter)->setParent(NULL);
return true;
}
bool RBX::CSGOperations::doSeparate(
std::vector<boost::shared_ptr<RBX::Instance> >::const_iterator start,
std::vector<boost::shared_ptr<RBX::Instance> >::const_iterator end,
std::vector<shared_ptr<RBX::Instance> >& ungroupedItems )
{
// First make a copy of the selection list
Instances itemsToUngroup;
for (std::vector<boost::shared_ptr<RBX::Instance> >::const_iterator iter = start; iter != end; ++iter)
{
if (shared_ptr<PartOperation> o = RBX::Instance::fastSharedDynamicCast<PartOperation>(*iter))
itemsToUngroup.push_back(o);
}
// Now iterate over the objects to be ungrouped and ungroup them
Instances itemsToDelete;
bool didSomething = false;
for ( Instances::const_iterator iter = itemsToUngroup.begin(); iter != itemsToUngroup.end(); iter++ )
didSomething |= separate( (*iter), ungroupedItems, itemsToDelete );
if (didSomething)
{
for (Instances::const_iterator iter = itemsToDelete.begin(); iter != itemsToDelete.end(); ++iter)
(*iter)->setParent(NULL);
}
else
debugAssertM(0, "Calling seperate command without checking is-enabled");
return true;
}
bool RBX::CSGOperations::setOperationMesh(shared_ptr<PartOperation> partOperation, Instances instances)
{
bool negativeChildren = true;
for (Instances::const_iterator iter = instances.begin(); iter != instances.end(); ++iter)
{
if (!Instance::fastSharedDynamicCast<NegateOperation>(*iter))
{
negativeChildren = false;
break;
}
}
if (Instance::fastSharedDynamicCast<UnionOperation>(partOperation) || negativeChildren)
return setUnionMesh(partOperation, instances.begin(), instances.end(), true);
else if (shared_ptr<NegateOperation> negateOperation = Instance::fastSharedDynamicCast<NegateOperation>(partOperation))
return setNegateMesh(negateOperation, *instances.begin());
return false;
}
bool RBX::CSGOperations::recalculateMesh(shared_ptr<PartOperation> partOperation)
{
std::stringstream ss(partOperation->peekChildData(mDatamodel).value());
Instances instances;
SerializerBinary::deserialize(ss, instances);
for (std::vector<boost::shared_ptr<Instance> >::const_iterator iter = instances.begin(); iter != instances.end(); ++iter)
{
if (shared_ptr<PartOperation> childOperation = Instance::fastSharedDynamicCast<PartOperation>(*iter))
{
bool opSuccess = recalculateMesh(childOperation);
if (!opSuccess)
return false;
}
}
Color3 partColor = partOperation->getColor3();
CoordinateFrame cFrame = partOperation->getCoordinateFrame();
Vector3 operationSize = partOperation->getPartSizeUi();
PartMaterial partMaterial = partOperation->getRenderMaterial();
float transparency = partOperation->getTransparencyXml();
bool opSuccess = setOperationMesh(partOperation, instances);
if (!opSuccess)
return false;
partOperation->setCoordinateFrame(cFrame);
partOperation->setPartSizeXml(operationSize);
partOperation->setColor3(partColor);
partOperation->setRenderMaterial(partMaterial);
partOperation->setTransparency(transparency);
return true;
}
bool RBX::CSGOperations::createPartMesh(PartInstance* part, CSGMesh* modelData)
{
std::vector<Graphics::GeometryGenerator::Vertex> vertices;
std::vector<unsigned short> indices;
std::vector<CSGVertex> meshVertices;
std::vector<unsigned int> meshIndices;
{
Graphics::GeometryGenerator geomGen;
geomGen.addCSGPrimitive(part);
vertices.resize(geomGen.getVertexCount());
indices.resize(geomGen.getIndexCount());
meshVertices.resize(geomGen.getVertexCount());
meshIndices.resize(geomGen.getIndexCount());
}
if (vertices.size() == 0 || indices.size() < 3)
return false;
{
Graphics::GeometryGenerator geomGen(&vertices[0], &indices[0]);
geomGen.addCSGPrimitive(part);
}
for (size_t i = 0; i < vertices.size(); i++)
{
meshVertices[i].pos = vertices[i].pos;
meshVertices[i].uv = vertices[i].uv;
meshVertices[i].color = vertices[i].color;
meshVertices[i].uvStuds = vertices[i].uvStuds;
meshVertices[i].normal = vertices[i].normal;
meshVertices[i].tangent = vertices[i].tangent;
}
for (size_t i = 0; i < indices.size(); i++)
{
meshIndices[i] = (unsigned int)indices[i];
}
modelData->set(meshVertices, meshIndices);
modelData->weldMesh(true);
modelData->buildBRep();
return true;
}
void RBX::CSGOperations::resetInstanceCFrame(
std::vector<boost::shared_ptr<Instance> >::const_iterator start,
std::vector<boost::shared_ptr<Instance> >::const_iterator end,
boost::unordered_map<boost::shared_ptr<Instance>, CoordinateFrame> originalCFrames)
{
for (std::vector<boost::shared_ptr<Instance> >::const_iterator iter = start; iter != end; ++iter)
if (shared_ptr<PartInstance> partInstance = Instance::fastSharedDynamicCast<PartInstance>(*iter))
if (originalCFrames.count(*iter))
partInstance->setCoordinateFrame(originalCFrames.at(*iter));
}
static void warnIfChildInstance(shared_ptr<RBX::Instance> instance,
shared_ptr<RBX::PartInstance> partInstance,
const std::string& operationType)
{
if (instance->fastDynamicCast<RBX::DataModelMesh>())
return;
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "Warning: Child object \"%s\" of parent \"%s\" is not supported by %s.", instance->getFullName().c_str(), partInstance->getFullName().c_str(), operationType.c_str());
}
void RBX::CSGOperations::warnIfChildInstances(shared_ptr<PartInstance> partInstance, const std::string& operationType)
{
partInstance->visitDescendants(boost::bind(&warnIfChildInstance, _1, partInstance, operationType));
}
const char* RBX::CSGOperations::unionFailedMsg = "This union is not currently solvable. Try offseting the parts from each other or try unioning each part individually.";
bool RBX::CSGOperations::setUnionMesh(shared_ptr<PartOperation> finalPartOperation,
std::vector<boost::shared_ptr<Instance> >::const_iterator start,
std::vector<boost::shared_ptr<Instance> >::const_iterator end,
bool isChildRecalculation)
{
boost::scoped_ptr<CSGMesh> positiveMesh;
boost::scoped_ptr<CSGMesh> negativeMesh;
DataModel* dataModel = FFlag::CSGDelayParentingOperationToEnd ? mDatamodel : DataModel::get(finalPartOperation.get());
CSGDictionaryService* dictionaryService = ServiceProvider::create< CSGDictionaryService >(dataModel);
Instance* parentToSet = FFlag::CSGDelayParentingOperationToEnd ? NULL : (*start)->getParent();
CoordinateFrame primaryDelta;
bool primaryDeltaSet = false;
boost::unordered_map<boost::shared_ptr<Instance>, CoordinateFrame> originalCFrames;
for (std::vector<boost::shared_ptr<Instance> >::const_iterator iter = start; iter != end; ++iter)
{
if (!FFlag::CSGDelayParentingOperationToEnd)
{
if (parentToSet != (*iter)->getParent())
parentToSet = NULL;
}
if (shared_ptr<PartInstance> partInstance = Instance::fastSharedDynamicCast<PartInstance>(*iter))
{
partInstance->destroyJoints();
if (!isChildRecalculation)
{
if (!primaryDeltaSet)
{
primaryDeltaSet = true;
primaryDelta = CoordinateFrame(partInstance->getCoordinateFrame().rotation.inverse(), -partInstance->getCoordinateFrame().translation);
}
originalCFrames.emplace(*iter, partInstance->getCoordinateFrame());
partInstance->setCoordinateFrame(CoordinateFrame(primaryDelta.rotation) * (partInstance->getCoordinateFrame() + primaryDelta.translation));
}
}
boost::scoped_ptr<CSGMesh> meshData;
if (shared_ptr<PartOperation> partOperation = Instance::fastSharedDynamicCast<PartOperation>(*iter))
{
if (!partOperation->getMesh())
continue;
if (!partOperation->getMesh()->isValid())
{
partOperation->refreshMesh();
if (!partOperation->getMesh()->isValid())
{
if (!recalculateMesh(partOperation))
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
return false;
}
}
}
meshData.reset(partOperation->getMesh()->clone());
meshData->applyScale(partOperation->getSizeDifference());
if (FFlag::CSGMeshColorToolsEnabled && partOperation->getUsePartColor())
meshData->applyColor(Vector3(partOperation->getColor3().r, partOperation->getColor3().g, partOperation->getColor3().b));
meshData->applyCoordinateFrame(partOperation->getCoordinateFrame());
}
if (shared_ptr<UnionOperation> unionOperation = Instance::fastSharedDynamicCast<UnionOperation>(*iter))
{
warnIfChildInstances(unionOperation, "union");
if (!positiveMesh)
{
finalPartOperation->setColor(unionOperation->getColor());
finalPartOperation->setRenderMaterial(unionOperation->getRenderMaterial());
finalPartOperation->setReflectance(unionOperation->getReflectance());
finalPartOperation->setTransparency(unionOperation->getTransparencyUi());
finalPartOperation->setAnchored(unionOperation->getAnchored());
finalPartOperation->setCanCollide(unionOperation->getCanCollide());
finalPartOperation->setElasticity(unionOperation->getElasticity());
finalPartOperation->setFriction(unionOperation->getFriction());
if (DFFlag::MaterialPropertiesEnabled)
{
finalPartOperation->setPhysicalProperties(unionOperation->getPhysicalProperties());
}
positiveMesh.swap(meshData);
}
else
{
if (!positiveMesh->unionMesh(positiveMesh.get(), meshData.get()))
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Union Unsolvable -1", unionFailedMsg);
return false;
}
}
}
else if (shared_ptr<NegateOperation> negateOperation = Instance::fastSharedDynamicCast<NegateOperation>(*iter))
{
warnIfChildInstances(negateOperation, "union");
if (!negativeMesh)
{
negativeMesh.swap(meshData);
}
else
{
if (!negativeMesh->unionMesh(negativeMesh.get(), meshData.get()))
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Union Unsolvable -2", unionFailedMsg);
return false;
}
}
}
else if (shared_ptr<PartInstance> partInstance = Instance::fastSharedDynamicCast<PartInstance>(*iter))
{
warnIfChildInstances(partInstance, "union");
if (!positiveMesh)
{
finalPartOperation->setColor(partInstance->getColor());
finalPartOperation->setRenderMaterial(partInstance->getRenderMaterial());
finalPartOperation->setReflectance(partInstance->getReflectance());
finalPartOperation->setTransparency(partInstance->getTransparencyUi());
finalPartOperation->setAnchored(partInstance->getAnchored());
finalPartOperation->setCanCollide(partInstance->getCanCollide());
finalPartOperation->setElasticity(partInstance->getElasticity());
finalPartOperation->setFriction(partInstance->getFriction());
if (DFFlag::MaterialPropertiesEnabled)
{
finalPartOperation->setPhysicalProperties(partInstance->getPhysicalProperties());
}
positiveMesh.reset(CSGMeshFactory::singleton()->createMesh());
bool partMeshCreated = createPartMesh(partInstance.get(), positiveMesh.get());
if (partMeshCreated)
{
positiveMesh->applyCoordinateFrame(partInstance->getCoordinateFrame());
}
else
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Warning", "This part or mesh type is not supported.");
return false;
}
}
else
{
boost::scoped_ptr<CSGMesh> model(CSGMeshFactory::singleton()->createMesh());
bool partMeshCreated = createPartMesh(partInstance.get(), model.get());
if (partMeshCreated)
{
model->applyCoordinateFrame(partInstance->getCoordinateFrame());
if (!positiveMesh->unionMesh(positiveMesh.get(), model.get()))
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Union Unsolvable -3", unionFailedMsg);
return false;
}
}
else
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Warning", "This part or mesh type is not supported.");
return false;
}
}
}
else
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Warning", "Only parts, union parts, and negative parts are supported by union.");
return false;
}
}
if (!positiveMesh && !negativeMesh)
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Union Unsolvable -6", unionFailedMsg);
return false;
}
if (negativeMesh)
{
if (!positiveMesh)
{
positiveMesh.swap(negativeMesh);
negativeMesh.reset();
}
else if (!positiveMesh->subractMesh(positiveMesh.get(), negativeMesh.get()))
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Union Unsolvable -7", unionFailedMsg);
return false;
}
}
if (!positiveMesh->isValid())
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Union Unsolvable -8", unionFailedMsg);
return false;
}
if ( !positiveMesh->newTriangulate() )
{
if (FFlag::CSGNewTriangulateFallBack)
positiveMesh->triangulate();
else
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
operationFailed("Union Unsolvable -9", unionFailedMsg);
return false;
}
}
size_t numTriangles = positiveMesh->getIndices().size() / 3;
if (numTriangles > PartOperation::getMaximumTriangleCount())
{
char buf[128];
sprintf( buf, "This union would be %d triangles. There is a %d triangle limit for unions.", (int)numTriangles, (int)PartOperation::getMaximumTriangleCount());
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Union Rejected", buf);
return false;
}
if (positiveMesh->toBinaryString().size() > PartOperation::getMaximumMeshStreamSize())
{
char buf[128];
sprintf( buf, "This union is too complex. There is a %d triangle limit for unions.", (int)PartOperation::getMaximumTriangleCount());
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Union Rejected", buf);
return false;
}
G3D::Vector3 trans = positiveMesh->extentsCenter();
finalPartOperation->setCoordinateFrame(trans);
for (std::vector<boost::shared_ptr<Instance> >::const_iterator iter = start; iter != end; ++iter)
{
if (boost::shared_ptr<PartInstance> partInstance = Instance::fastSharedDynamicCast<PartInstance>(*iter))
{
CoordinateFrame tmpFrame = partInstance->getCoordinateFrame();
tmpFrame.translation = tmpFrame.translation - trans;
partInstance->setCoordinateFrame(tmpFrame);
}
}
if (!FFlag::CSGDelayParentingOperationToEnd && dataModel)
finalPartOperation->setParent(parentToSet != NULL ? parentToSet : dataModel->getWorkspace());
finalPartOperation->setPartSizeXml(positiveMesh->extentsSize());
finalPartOperation->setInitialSize(finalPartOperation->getPartSizeUi());
G3D::Vector3 negTrans = -trans;
positiveMesh->applyTranslation(negTrans);
positiveMesh->translate(negTrans);
dictionaryService->retrieveMeshData(*finalPartOperation);
finalPartOperation->setMesh(positiveMesh->clone());
if (!finalPartOperation->checkDecompExists() && !finalPartOperation->createPhysicsData(positiveMesh.get()))
{
if (!isChildRecalculation)
resetInstanceCFrame(start, end, originalCFrames);
finalPartOperation->setParent(NULL);
operationFailed("Union Rejected", "This union's Physics Data is too complex, simplify the model and try again");
return false;
}
if (!isChildRecalculation)
{
//Move back to original position
CoordinateFrame identityFrameDelta = CoordinateFrame(-finalPartOperation->getCoordinateFrame().translation);
identityFrameDelta = CoordinateFrame(primaryDelta.rotation).inverse() * identityFrameDelta;
finalPartOperation->setCoordinateFrame(CoordinateFrame(primaryDelta.rotation.inverse(), G3D::Vector3::zero()));
finalPartOperation->setCoordinateFrame(finalPartOperation->getCoordinateFrame() - identityFrameDelta.translation - primaryDelta.translation);
}
return true;
}
bool RBX::CSGOperations::setNegateMesh(shared_ptr<NegateOperation> negateOperation, shared_ptr<Instance> instance)
{
boost::scoped_ptr<CSGMesh> model;
CSGDictionaryService* dictionaryService = FFlag::CSGDelayParentingOperationToEnd ?
mDatamodel->create<CSGDictionaryService>() :
ServiceProvider::create< CSGDictionaryService >(DataModel::get(negateOperation.get()));
NonReplicatedCSGDictionaryService* nrDictionaryService = FFlag::CSGDelayParentingOperationToEnd ?
mDatamodel->create<NonReplicatedCSGDictionaryService>() : ServiceProvider::create<NonReplicatedCSGDictionaryService>(DataModel::get(negateOperation.get()));
if (shared_ptr<PartInstance> partInstance = Instance::fastSharedDynamicCast<PartInstance>(instance))
partInstance->destroyJoints();
if (shared_ptr<UnionOperation> unionOperation = Instance::fastSharedDynamicCast<UnionOperation>(instance))
{
warnIfChildInstances(unionOperation, "negation");
dictionaryService->retrieveData(*unionOperation);
nrDictionaryService->retrieveData(*unionOperation);
if (!unionOperation->getMesh() || !unionOperation->getMesh()->isValid())
{
if (!recalculateMesh(unionOperation))
return false;
}
model.reset(unionOperation->getMesh()->clone());
model->applyScale(unionOperation->getSizeDifference());
negateOperation->setInitialSize(unionOperation->getPartSizeUi());
negateOperation->setCoordinateFrame(unionOperation->getCoordinateFrame());
negateOperation->setPartSizeXml(unionOperation->getPartSizeUi());
}
else if (shared_ptr<PartInstance> partInstance = Instance::fastSharedDynamicCast<PartInstance>(instance))
{
warnIfChildInstances(partInstance, "negation");
negateOperation->setCoordinateFrame(partInstance->getCoordinateFrame());
negateOperation->setPartSizeXml(partInstance->getPartSizeUi());
negateOperation->setInitialSize(negateOperation->getPartSizeUi());
partInstance->getColor3();
model.reset(CSGMeshFactory::singleton()->createMesh());
bool partMeshCreated = createPartMesh(partInstance.get(), model.get());
if (!partMeshCreated)
{
operationFailed("Warning", "This part or mesh type is not supported.");
return false;
}
}
else
{
operationFailed("Warning", "Only parts, union parts, and negative parts are supported for Negation.");
return false;
}
if (!model || !model->isValid())
{
operationFailed("Warning", "Could not create model.");
return false;
}
if (boost::shared_ptr<PartInstance> partInstance = Instance::fastSharedDynamicCast<PartInstance>(instance))
{
CoordinateFrame tmpFrame = partInstance->getCoordinateFrame();
tmpFrame.translation = tmpFrame.translation - negateOperation->getCoordinateFrame().translation;
partInstance->setCoordinateFrame(tmpFrame);
}
if ( !model->newTriangulate() )
{
if (FFlag::CSGNewTriangulateFallBack)
model->triangulate();
else
{
operationFailed("Union Unsolvable -10", unionFailedMsg);
return false;
}
}
negateOperation->setMesh(model->clone());
// Temporary, Remove and fix after Merging with CO
CSGMeshSgCore meshData = *static_cast<CSGMeshSgCore*>(negateOperation->getMesh().get());
if (!negateOperation->checkDecompExists())
negateOperation->createPhysicsData(&meshData);
else
negateOperation->createPlaceholderPhysicsData();
return true;
}
bool RBX::CSGOperations::negateSelection(Instances& toRemove,
Instances& toSelect,
std::vector<boost::shared_ptr<Instance> >::const_iterator start,
std::vector<boost::shared_ptr<Instance> >::const_iterator end)
{
CSGDictionaryService* dictionaryService = ServiceProvider::create< CSGDictionaryService >(mDatamodel);
NonReplicatedCSGDictionaryService* nrDictionaryService = ServiceProvider::create<NonReplicatedCSGDictionaryService>(mDatamodel);
for (std::vector<boost::shared_ptr<Instance> >::const_iterator iter = start; iter != end; ++iter)
{
if (!(Instance::fastSharedDynamicCast<PartOperation>(*iter) ||
Instance::fastSharedDynamicCast<NegateOperation>(*iter) ||
Instance::fastSharedDynamicCast<PartInstance>(*iter)))
{
operationFailed("Warning", "Only parts, union parts, and negative parts are supported by negation.");
return false;
}
}
for (std::vector<boost::shared_ptr<Instance> >::const_iterator iter = start; iter != end; ++iter)
{
if (shared_ptr<NegateOperation> negateOperation = Instance::fastSharedDynamicCast<NegateOperation>(*iter))
{
dictionaryService->retrieveData(*negateOperation);
nrDictionaryService->retrieveData(*negateOperation);
Instances instances;
std::stringstream ss(negateOperation->getChildDataBlocking().value());
SerializerBinary::deserialize(ss, instances);
Instances separatedItems;
Instances itemsToDelete;
bool didSomething = separate( negateOperation, separatedItems, itemsToDelete );
if (didSomething)
{
toSelect.insert(toSelect.end(), separatedItems.begin(), separatedItems.end());
toRemove.insert(toRemove.end(), itemsToDelete.begin(), itemsToDelete.end());
}
toRemove.push_back(negateOperation);
}
else if (shared_ptr<PartInstance> pInst = Instance::fastSharedDynamicCast<PartInstance>(*iter))
{
shared_ptr<NegateOperation> negateOperation;
try
{
negateOperation = NegateOperation::createInstance();
if (!FFlag::CSGDelayParentingOperationToEnd)
negateOperation->setParent(pInst->getParent());
toSelect.push_back(negateOperation);
}
catch (std::exception &)
{
operationFailed("Error", "Could not create part instance.");
return false;
}
if (shared_ptr<PartOperation> pOperation = Instance::fastSharedDynamicCast<PartOperation>(pInst))
{
dictionaryService->retrieveData(*pOperation);
nrDictionaryService->retrieveData(*pOperation);
pOperation->setMeshData(BinaryString(""));
pOperation->setPhysicsData(BinaryString(""));
negateOperation->setUsePartColor(pOperation->getUsePartColor());
negateOperation->setColor(pOperation->getColor());
}
bool validNegation = setNegateMesh(negateOperation, pInst);
if (!validNegation)
return false;
shared_ptr<ModelInstance> groupInstance = ModelInstance::createInstance();
shared_ptr<Instance> dupe = pInst->clone(SerializationCreator);
dupe->setParent(groupInstance.get());
std::stringstream ss;
SerializerBinary::serialize(ss, groupInstance.get());
negateOperation->setChildData(BinaryString(ss.str()));
groupInstance->setParent(NULL);
if (FFlag::CSGDelayParentingOperationToEnd)
{
negateOperation->setParent(pInst->getParent());
}
else
{
dictionaryService->storeData(*negateOperation);
nrDictionaryService->storeData(*negateOperation);
}
toRemove.push_back(pInst);
}
}
return true;
}
bool RBX::CSGOperations::separate(const shared_ptr<Instance>& wInstance,
Instances &separatedItems,
Instances &itemsToDelete)
{
CSGDictionaryService* dictionaryService = ServiceProvider::create<CSGDictionaryService>(DataModel::get(wInstance.get()));
NonReplicatedCSGDictionaryService* nrDictionaryService = ServiceProvider::create<NonReplicatedCSGDictionaryService>(DataModel::get(wInstance.get()));
bool didSomethingHere = false;
if (!dictionaryService || !nrDictionaryService)
return didSomethingHere;
if (shared_ptr<PartOperation> partOperation = Instance::fastSharedDynamicCast<PartOperation>(wInstance))
{
bool isNegate = false;
if (Instance::fastSharedDynamicCast<NegateOperation>(partOperation))
isNegate = true;
Instances instances;
dictionaryService->retrieveData(*partOperation);
nrDictionaryService->retrieveData(*partOperation);
std::stringstream ss(partOperation->getChildDataBlocking().value());
SerializerBinary::deserialize(ss, instances);
for (std::vector<boost::shared_ptr<Instance> >::const_iterator iter = instances.begin(); iter != instances.end(); ++iter)
{
if (shared_ptr<PartOperation> partOperationChild = Instance::fastSharedDynamicCast<PartOperation>(*iter))
if (!recalculateMesh(partOperationChild))
return didSomethingHere;
bool childNegate = false;
if (Instance::fastSharedDynamicCast<NegateOperation>(*iter))
childNegate = true;
if (shared_ptr<PartInstance> part = Instance::fastSharedDynamicCast<PartInstance>(*iter))
{
CoordinateFrame newFrame = part->getCoordinateFrame();
if (!isNegate || childNegate)
newFrame.rotation = partOperation->getCoordinateFrame().rotation * newFrame.rotation;
else
newFrame.rotation = partOperation->getCoordinateFrame().rotation;
newFrame.translation *= partOperation->getSizeDifference();
newFrame.translation = (partOperation->getCoordinateFrame().rotation * newFrame.translation) + partOperation->getCoordinateFrame().translation;
part->setCoordinateFrame(newFrame);
part->setPartSizeXml(part->getPartSizeUi() * partOperation->getSizeDifference());
separatedItems.push_back(part);
}
(*iter)->setParent(wInstance->getParent());
}
itemsToDelete.push_back(partOperation);
didSomethingHere = true;
}
return didSomethingHere;
}
RBX::Instance* RBX::CSGOperations::getCommonParentOrWorkspace(Instances::const_iterator start,
Instances::const_iterator end)
{
Instance* parentMatch = (*start)->getParent();
for (Instances::const_iterator itr = start; itr != end; ++itr)
{
if (parentMatch != (*itr)->getParent())
{
parentMatch = mDatamodel->getWorkspace();
break;
}
}
return parentMatch;
}
+62
View File
@@ -0,0 +1,62 @@
/* Copyright 2014 ROBLOX Corporation, All Rights Reserved */
#pragma once
#include "rbx/boost.hpp"
#include <boost/unordered_map.hpp>
#include <vector>
#include "util/Extents.h"
#include "v8tree/Instance.h"
namespace RBX
{
class DataModel;
class PartInstance;
class PartOperation;
class NegateOperation;
class CSGMesh;
typedef void (*MessageCallback)( const char* title, const char* msg );
class CSGOperations
{
public:
CSGOperations( DataModel* pDatamodel, MessageCallback failedFun );
bool doUnion(
Instances::const_iterator start,
Instances::const_iterator end,
boost::shared_ptr<PartOperation>& partOperation );
bool doNegate(
Instances::const_iterator start,
Instances::const_iterator end,
Instances& toSelect );
bool doSeparate(
Instances::const_iterator start,
Instances::const_iterator end,
Instances& ungroupedItems );
private:
DataModel* mDatamodel;
MessageCallback operationFailed;
bool setUnionMesh(boost::shared_ptr<PartOperation> finalPartOperation,
Instances::const_iterator start,
Instances::const_iterator end,
bool isChildRecalculation);
bool setNegateMesh(shared_ptr<NegateOperation> negateOperation, shared_ptr<Instance> instance );
bool recalculateMesh(shared_ptr<PartOperation> partOperation);
bool negateSelection(Instances& toRemove,
Instances& toSelect,
Instances::const_iterator start,
Instances::const_iterator end);
bool separate(const shared_ptr<Instance>& wInstance,
Instances& separatedItems,
Instances& itemsToDelete);
bool setOperationMesh(shared_ptr<PartOperation> partOperation, Instances instances);
bool createPartMesh(PartInstance* part, CSGMesh* modelData);
void resetInstanceCFrame(Instances::const_iterator start,
Instances::const_iterator end,
boost::unordered_map<boost::shared_ptr<Instance>, CoordinateFrame> originalCFrames);
void warnIfChildInstances(shared_ptr<PartInstance> partInstance, const std::string& operationType);
Instance* getCommonParentOrWorkspace(Instances::const_iterator start, Instances::const_iterator end);
static const char* unionFailedMsg;
};
}
+82
View File
@@ -0,0 +1,82 @@
#include "stdafx.h"
#include "CloudEditAdornable.h"
#include "RobloxDocManager.h"
#include "RobloxIDEDoc.h"
#include "PlayersDataManager.h"
#include "AppDraw/Draw.h"
#include "GfxBase/IAdornableCollector.h"
#include "Network/Player.h"
#include "Network/Players.h"
#include "V8DataModel/Workspace.h"
using namespace RBX;
void CloudEditAdornable::renderSelection(Color3 playerColor, shared_ptr<Instance> instance, Part part, float lineThickness, Adorn* adorn)
{
static const Vector3 kLipSize = Vector3::one() * .015;
Vector3 size = (part.gridSize * .5) + kLipSize;
adorn->box(part.coordinateFrame, size, Color4(playerColor, .25), -1, false);
Draw::selectionBox(part, adorn, Color4(playerColor, 1), lineThickness);
}
void CloudEditAdornable::renderCamera(Color3 playerColor, shared_ptr<RBX::Network::Player> player, const Camera* localCamera, Adorn* adorn)
{
CoordinateFrame cameraCFrame = player->getCloudEditCameraCoordinateFrame();
Color4 cameraColor(playerColor, 0.25);
adorn->setObjectToWorldMatrix(cameraCFrame);
adorn->ray(RbxRay(Vector3(0, 0, 1.2), Vector3(0, 0, -3)), cameraColor);
adorn->sphere(Sphere(Vector3::zero(), .75), cameraColor);
Vector3 screenLoc = localCamera->project(cameraCFrame.translation);
adorn->drawFont2D(
player->getName(),
screenLoc.xy(),
14 /*fontSize*/,
false,
Color3::white(),
Color3::black(),
Text::FONT_ARIALBOLD,
Text::XALIGN_CENTER,
Text::YALIGN_BOTTOM);
}
void CloudEditAdornable::attach(shared_ptr<RBX::DataModel> dataModel, shared_ptr<PlayersDataManager> playersDataManager)
{
m_pDataModel = dataModel;
m_pPlayersDataManager = playersDataManager;
RBXASSERT(m_pDataModel->currentThreadHasWriteLock());
m_pDataModel->getWorkspace()->getAdornableCollector()->onRenderableDescendantAdded(this);
}
void CloudEditAdornable::render3dAdorn(Adorn* adorn)
{
if (Network::Players* players = m_pDataModel->find<Network::Players>())
{
for (auto& playerInstance : *(players->getPlayers()))
{
if (shared_ptr<Network::Player> p = Instance::fastSharedDynamicCast<Network::Player>(playerInstance))
{
if (p.get() == players->getLocalPlayer())
{
continue;
}
Color3 playerColor = RBX::BrickColor(m_pPlayersDataManager->getPlayerColor(p->getUserID())).color3();
renderCamera(playerColor, p, m_pDataModel->getWorkspace()->getConstCamera(), adorn);
shared_ptr<const RBX::Instances> selectedInstances = m_pPlayersDataManager->getLastKnownSelection(p->getUserID());
if (selectedInstances->size() > 0)
{
SelectionHighlightAdornable::renderSelection(m_pDataModel.get(), *selectedInstances, adorn,
boost::bind(&CloudEditAdornable::renderSelection, playerColor, _1, _2, _3, _4));
}
}
}
}
}
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include "GfxBase/IAdornable.h"
#include "GfxBase/Part.h"
#include "V8Tree/Instance.h"
#include "V8DataModel/DataModel.h"
#include <boost/shared_ptr.hpp>
namespace RBX
{
class Camera;
}
class PlayersDataManager;
class CloudEditAdornable : public RBX::IAdornable
{
shared_ptr<RBX::DataModel> m_pDataModel;
shared_ptr<PlayersDataManager> m_pPlayersDataManager;
static void renderSelection(RBX::Color3 playerColor,
shared_ptr<RBX::Instance> instance, RBX::Part part, float lineThickness, RBX::Adorn* adorn);
static void renderCamera(RBX::Color3 playerColor, shared_ptr<RBX::Network::Player> player,
const RBX::Camera* localCamera, RBX::Adorn* adorn);
protected:
bool shouldRender3dAdorn() const override { return true; }
public:
void attach(shared_ptr<RBX::DataModel> dataModel, shared_ptr<PlayersDataManager> playersDataManager); // requires DataModel write lock is held
void render3dAdorn(RBX::Adorn* adorn) override;
};
+918
View File
@@ -0,0 +1,918 @@
/**
* CommonInsertWidget.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "CommonInsertWidget.h"
// Qt Headers
#include <QTabWidget>
#include <QGridLayout>
#include <QLineEdit>
#include <QCheckBox>
#include <QTimer>
#include <QDockWidget>
#include <QPushButton>
#include <QDialogButtonBox>
#include <QMouseEvent>
#include <QDrag>
#include <QKeyEvent>
// Roblox Headers
#include "RobloxSettings.h"
#include "script/script.h"
#include "Util/RobloxGoogleAnalytics.h"
#include "util/Math.h"
#include "v8datamodel/DataModel.h"
#include "v8datamodel/Workspace.h"
#include "v8datamodel/Selection.h"
#include "v8datamodel/MegaCluster.h"
#include "v8datamodel/Explosion.h"
#include "v8datamodel/SpecialMesh.h"
#include "V8DataModel/GuiObject.h"
#include "V8DataModel/BillboardGui.h"
#include "V8DataModel/TextBox.h"
#include "V8DataModel/TextLabel.h"
#include "V8DataModel/TextButton.h"
#include "v8datamodel/ImageButton.h"
#include "v8datamodel/ImageLabel.h"
#include "v8datamodel/Commands.h"
#include "v8datamodel/Decal.h"
#include "v8datamodel/SpawnLocation.h"
#include "v8datamodel/Filters.h"
#include "V8World/World.h"
#include "v8tree/Service.h"
// Roblox Studio Headers
#include "AuthoringSettings.h"
#include "InsertObjectListWidget.h"
#include "InsertObjectListWidgetItem.h"
#include "LuaSourceBuffer.h"
#include "QtUtilities.h"
#include "ReflectionMetadata.h"
#include "RobloxMainWindow.h"
#include "RobloxToolBox.h"
#include "RobloxTreeWidget.h"
#include "RobloxCustomWidgets.h"
#include "RobloxDocManager.h"
#include "RobloxIDEDoc.h"
#include "UpdateUIManager.h"
#include "v8datamodel/BasicPartInstance.h"
#include "v8datamodel/VehicleSeat.h"
FASTFLAG(GoogleAnalyticsTrackingEnabled)
static const char* kInsertedItemSelectedSetting = "insertedItemSelectedSetting";
QSet<QString> InsertObjectWidget::m_sObjectExceptions;
QHash<QString, QVariant> InsertObjectWidget::m_objectWeights;
RBX::Vector3 InsertObjectWidget::m_sCachedInsertLocation;
RBX::CoordinateFrame InsertObjectWidget::m_sCameraCFrame;
bool InsertObjectWidget::m_sIsInsertLocationValid = false;;
QSize InsertObjectWidget::sizeHint() const
{
return QSize(width(), 700);
}
/*
Creates an item to populate the Insert -> Basic Objects and Insert -> Service panels.
Rules:
Don't do Terrain
Don't do DataModel
AuthoringSettings::isShown() must return true
The object must be creatable by Scripts
The object must be visible in the tree view
Don't show Services in the Basic Objects panel
*/
void InsertObjectWidget::populateListHelper(bool services, QList<InsertObjectListWidgetItem*> *itemList, const RBX::Reflection::ClassDescriptor& pDescriptor, shared_ptr<RBX::DataModel> pDataModel)
{
if (pDescriptor.name == RBX::DataModel::className())
return;
// TODO: A better generic solution if we encounter more such cases
// Currently hardcoding the removal of Terrain from the Insert Objects' list
if(pDescriptor.isA(RBX::MegaClusterInstance::classDescriptor()) && (pDescriptor.name == "Terrain"))
return;
RBX::Reflection::Metadata::Class* pMetadata = RBX::Reflection::Metadata::Reflection::singleton()->get(pDescriptor, false);
if (AuthoringSettings::singleton().isShown(pMetadata, pDescriptor))
{
shared_ptr<RBX::Instance> instance;
try
{
instance = RBX::Creatable<RBX::Instance>::createByName(pDescriptor.name, RBX::EngineCreator);
bool isService = dynamic_cast<RBX::Service*>(instance.get()) != NULL;
if (isService != services)
return;
}
catch (std::exception &)
{
// We couldn't create it, so we're done
return;
}
if (instance)
{
if (services && pDataModel && instanceExistsInDataModel(instance, pDataModel))
return;
std::string preferredParentStr = pMetadata->getPreferredParent();
itemList->append(new InsertObjectListWidgetItem(
pDescriptor.name.c_str(),
pMetadata->description.c_str(),
instance,
preferredParentStr ));
}
}
}
/*
Recursively creates items to populate the Insert -> Basic Object and Insert -> Service panels.
*/
void InsertObjectWidget::populateList(bool services, QList<InsertObjectListWidgetItem*> *itemList, const RBX::Reflection::ClassDescriptor* pDescriptor, shared_ptr<RBX::DataModel> pDataModel)
{
RBXASSERT(pDescriptor);
// recursive
std::for_each(pDescriptor->derivedClasses_begin(), pDescriptor->derivedClasses_end(), boost::bind(&populateList, services, itemList, _1, pDataModel));
populateListHelper(services, itemList, *pDescriptor, pDataModel);
}
void InsertObjectWidget::createWidgets(bool services, InsertObjectListWidget* listWidget, const RBX::Reflection::ClassDescriptor* pDescriptor, shared_ptr<RBX::DataModel> pDataModel)
{
QList<InsertObjectListWidgetItem*>* itemList = new QList<InsertObjectListWidgetItem*>();
populateList(services, itemList, pDescriptor, pDataModel);
for (QList<InsertObjectListWidgetItem*>::iterator i = itemList->begin(); i != itemList->end(); i++)
listWidget->addItem(*i);
}
bool InsertObjectWidget::instanceExistsInDataModel(shared_ptr<RBX::Instance> instance, shared_ptr<RBX::DataModel> pDataModel)
{
if (!pDataModel)
return false;
RBX::Instance* pInstance = pDataModel.get();
if (pInstance->getChildren())
for (RBX::Instances::const_iterator iter = pInstance->getChildren()->begin(); iter != pInstance->getChildren()->end(); ++iter)
if (instance->getClassNameStr() == iter->get()->getClassNameStr())
return true;
return false;
}
/**
* Generates a list of instances that can be used to populate basic objects or service insert dialogs/widgets.
* Populates the map recusively. If a parent is set, the objects must be able to be children
* of the parent.
*
* @param descriptor parent descriptor to start getting child descriptors
* @param instanceMap output mapping of class names to instances
* @param parent optional parent to check if the instances can be a child of
*/
void InsertObjectWidget::generateInstanceMap(
const RBX::Reflection::ClassDescriptor* descriptor,
tInstanceMap* instanceMap,
RBX::Instance* parent )
{
std::for_each(
descriptor->derivedClasses_begin(),
descriptor->derivedClasses_end(),
boost::bind(&generateInstanceMap,_1,instanceMap,parent) );
if ( descriptor->name == RBX::DataModel::className() )
return;
if ( descriptor->isA(RBX::MegaClusterInstance::classDescriptor()) && (descriptor->name == "Terrain") )
return;
RBX::Reflection::Metadata::Class* pMetadata = RBX::Reflection::Metadata::Reflection::singleton()->get(*descriptor,false);
if ( AuthoringSettings::singleton().isShown(pMetadata,*descriptor) )
{
shared_ptr<RBX::Instance> instance;
try
{
instance = RBX::Creatable<RBX::Instance>::createByName(descriptor->name, RBX::EngineCreator);
bool isService = dynamic_cast<RBX::Service*>(instance.get()) != NULL;
if ( isService )
return;
}
catch (std::exception &)
{
// We couldn't create it, so we're done
return;
}
if ( instance )
{
if ( !descriptor->isScriptCreatable() )
return;
if ( !pMetadata || !pMetadata->isExplorerItem() )
return;
if ( parent && !instance->canSetParent(parent) )
return;
if(m_sObjectExceptions.contains(QString(instance->getClassName().c_str())))
return;
(*instanceMap)[descriptor->name.c_str()] = instance;
}
}
}
/**
* Creates a menu for inserting basic objects.
*
* @param parent optional parent that the instances can be children of
* @param receiver SLOT receiver object when clicking on the menu actions
* @param member SLOT when clicking on the menu actions
*/
QMenu* InsertObjectWidget::createMenu(
RBX::Instance* parent,
const QObject* receiver,
const char* member )
{
tInstanceMap instanceMap;
generateInstanceMap(
&RBX::Reflection::ClassDescriptor::rootDescriptor(),
&instanceMap,
parent );
QMenu* insertObjectMenu = new QMenu("Insert Object");
QList<QString> keys = instanceMap.keys();
qSort(keys);
QList<QString>::const_iterator iter = keys.begin();
for ( ; iter != keys.end() ; ++iter )
{
QAction* action = insertObjectMenu->addAction(*iter);
shared_ptr<RBX::Instance> instance = instanceMap[*iter];
action->setIcon(QIcon(QtUtilities::getPixmap(":/images/ClassImages.PNG",RobloxTreeWidgetItem::getImageIndex(instance))));
connect(action,SIGNAL(triggered(bool)),receiver,member);
}
return insertObjectMenu;
}
InsertObjectWidget::InsertObjectWidget(QWidget *pParentWidget)
: m_bRedrawRequested(false)
, m_bInitializationRequired(true)
, m_ItemList(new QList<InsertObjectListWidgetItem*>())
, m_quickInsertPreviousWidget(NULL)
, m_closeDockWhenDoneWithQuickInsert(false)
{
m_pInsertObjectListWidget = new InsertObjectListWidget(this);
m_pInsertObjectListWidget->setAutoScroll(true);
m_pInsertObjectListWidget->setDragEnabled(true);
m_pInsertObjectListWidget->setDragDropMode(QAbstractItemView::DragOnly);
m_pInsertObjectListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
m_pInsertObjectListWidget->setDropIndicatorShown(true);
m_pInsertObjectListWidget->setObjectName("InsertObjectListWidget");
m_pInsertObjectListWidget->setResizeMode(QListView::Adjust);
m_pInsertObjectListWidget->setAlternatingRowColors(true);
m_sObjectExceptions.insert("Accoutrement");
m_sObjectExceptions.insert("AnimationController");
m_sObjectExceptions.insert("ArcHandles");
m_sObjectExceptions.insert("Backpack");
m_sObjectExceptions.insert("BodyColors");
m_sObjectExceptions.insert("Camera");
m_sObjectExceptions.insert("CharacterMesh");
m_sObjectExceptions.insert("FileMesh");
m_sObjectExceptions.insert("Flag");
m_sObjectExceptions.insert("FlagStand");
m_sObjectExceptions.insert("FloorWire");
m_sObjectExceptions.insert("Glue");
m_sObjectExceptions.insert("Handles");
m_sObjectExceptions.insert("Hole");
m_sObjectExceptions.insert("KeyFrame");
m_sObjectExceptions.insert("Motor");
m_sObjectExceptions.insert("MotorFeature");
m_sObjectExceptions.insert("Pants");
m_sObjectExceptions.insert("PlayerGui");
m_sObjectExceptions.insert("Plugin");
m_sObjectExceptions.insert("Pose");
m_sObjectExceptions.insert("RotateP");
m_sObjectExceptions.insert("RotateV");
m_sObjectExceptions.insert("SelectionBox");
m_sObjectExceptions.insert("SelectionSphere");
m_sObjectExceptions.insert("Shirt");
m_sObjectExceptions.insert("ShirtGraphic");
m_sObjectExceptions.insert("SkateboardPlatform");
m_sObjectExceptions.insert("Skin");
m_sObjectExceptions.insert("Snap");
m_sObjectExceptions.insert("StarterGear");
m_sObjectExceptions.insert("Status");
m_sObjectExceptions.insert("SurfaceSelection");
m_sObjectExceptions.insert("TerrainRegion");
m_sObjectExceptions.insert("TextureTrail");
m_sObjectExceptions.insert("VelocityMotor");
m_sObjectExceptions.insert("Weld");
m_pFilterEdit = new QLineEdit(this);
m_pFilterEdit->setPlaceholderText(QString("Search object (%1)").arg(UpdateUIManager::Instance().getMainWindow().quickInsertAction->shortcut().toString()));
setFocusProxy(m_pFilterEdit);
m_pFilterEdit->installEventFilter(this);
m_pCheckBox = new QCheckBox(this);
m_pCheckBox->setText("Select inserted object");
QGridLayout *pGridLayout = new QGridLayout();
pGridLayout->setContentsMargins(0, 0, 0, 0);
pGridLayout->setHorizontalSpacing(4);
QGridLayout *checkBoxLayout = new QGridLayout();
checkBoxLayout->setContentsMargins(5, 0, 0, 0);
checkBoxLayout->setHorizontalSpacing(4);
checkBoxLayout->addWidget(m_pCheckBox, 0, 0);
pGridLayout->addWidget(m_pFilterEdit, 0, 0);
pGridLayout->addWidget(m_pInsertObjectListWidget, 1, 0);
pGridLayout->addLayout(checkBoxLayout, 2, 0);
m_pCheckBox->setChecked(RobloxSettings().value(kInsertedItemSelectedSetting, false).toBool());
connect(m_pCheckBox, SIGNAL(toggled(bool)), this, SLOT(onSelectOnInsertClicked(bool)));
connect(m_pFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(onFilter(QString)));
connect(m_pFilterEdit, SIGNAL(returnPressed()), this, SLOT(onItemInsertRequested()));
setLayout(pGridLayout);
//update dockwidget and action name
if (UpdateUIManager::Instance().getMainWindow().isRibbonStyle())
{
QDockWidget* pDockWidget = qobject_cast<QDockWidget*>(pParentWidget);
if (pDockWidget)
pDockWidget->setWindowTitle(QObject::tr("Advanced Objects"));
QAction* pDockAction = UpdateUIManager::Instance().getDockAction(eDW_BASIC_OBJECTS);
if (pDockAction)
{
pDockAction->setText(QObject::tr("Advanced Objects"));
pDockAction->setToolTip(QObject::tr("Advanced Objects"));
}
}
// Get Object Weights
RobloxSettings settings;
m_objectWeights = settings.value("rbxObjectWeights").toHash();
}
void InsertObjectWidget::onSelectOnInsertClicked(bool checked)
{
RobloxSettings().setValue(kInsertedItemSelectedSetting, checked);
}
InsertObjectWidget::~InsertObjectWidget()
{
// Set Object Weights
RobloxSettings settings;
settings.setValue("rbxObjectWeights", m_objectWeights);
m_ItemList->clear();
m_cSelectionChanged.disconnect();
}
void InsertObjectWidget::setDataModel(boost::shared_ptr<RBX::DataModel> pDataModel)
{
if(m_pDataModel == pDataModel)
return;
//RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, "InsertObjectWidget::setDataModel");
m_pFilterEdit->clear();
m_pInsertObjectListWidget->clear();
m_cSelectionChanged.disconnect();
m_bInitializationRequired = true;
m_pDataModel = pDataModel;
if (m_pDataModel)
{
requestDialogRedraw();
connectSelectionChangeEvent(true);
}
}
void InsertObjectWidget::updateWidget(bool state)
{
//RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "InsertObjectWidget::updateWidget %d", state);
if (state)
requestDialogRedraw();
connectSelectionChangeEvent(state);
}
void InsertObjectWidget::setVisible(bool visible)
{
//RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "InsertObjectWidget::setVisible %d", visible);
updateWidget(visible);
QWidget::setVisible(visible);
}
bool InsertObjectWidget::event(QEvent* e)
{
bool isHandled = QWidget::event(e);
if ( e->type() == QEvent::ShortcutOverride )
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);
QChar key = keyEvent->key();
// capture single key alpha numerical presses - we want these to be considered
// as shortcuts in the tree to jumped to items starting with that key, not global shortcuts
if ( !(key >= Qt::Key_F1 && key <= Qt::Key_F35) &&
key.isLetterOrNumber() &&
keyEvent->modifiers() == Qt::NoModifier )
{
keyEvent->accept();
isHandled = true;
}
}
return isHandled;
}
bool InsertObjectWidget::eventFilter(QObject* watched, QEvent* evt)
{
if (evt->type() == QEvent::FocusIn)
{
QFocusEvent* focusEvent = static_cast<QFocusEvent*>(evt);
if (focusEvent && focusEvent->reason() == Qt::ShortcutFocusReason)
m_pFilterEdit->selectAll();
}
else if (evt->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(evt);
QChar key = keyEvent->key();
if (key == Qt::Key_Up)
if (m_pInsertObjectListWidget->currentRow() > 0)
m_pInsertObjectListWidget->setCurrentRow(m_pInsertObjectListWidget->currentRow() - 1);
if (key == Qt::Key_Down)
if (m_pInsertObjectListWidget->currentRow() < m_pInsertObjectListWidget->count() - 1)
m_pInsertObjectListWidget->setCurrentRow(m_pInsertObjectListWidget->currentRow() + 1);
}
return false;
}
void InsertObjectWidget::SetObjectDefaultValues(boost::shared_ptr<RBX::Instance> pObjectToInsert)
{
if(RBX::GuiObject* guiObject = RBX::Instance::fastDynamicCast<RBX::GuiObject>(pObjectToInsert.get()))
{
guiObject->setSize(RBX::UDim2(0,100,0,100));
guiObject->setBackgroundColor3(RBX::Color3(1.0f,1.0f,1.0f));
RBX::TextService::FontSize defaultFontSize = RBX::TextService::SIZE_14;
RBX::TextService::Font defaultFont = RBX::TextService::FONT_SOURCESANS;
std::string defaultImage = "rbxassetid://133293265";
// have to check for text/image elements separately (no inheritance is being used, only macros....)
// trying to get rid of macros leads to diamond inheritance, needs to spend some time on that
if(RBX::TextLabel* textLabel = RBX::Instance::fastDynamicCast<RBX::TextLabel>(pObjectToInsert.get()))
{
textLabel->setFont(defaultFont);
textLabel->setFontSize(defaultFontSize);
textLabel->setSize(RBX::UDim2(0,200,0,50));
}
else if(RBX::GuiTextButton* textButton = RBX::Instance::fastDynamicCast<RBX::GuiTextButton>(pObjectToInsert.get()))
{
textButton->setFont(defaultFont);
textButton->setFontSize(defaultFontSize);
textButton->setSize(RBX::UDim2(0,200,0,50));
}
else if(RBX::TextBox* textBox = RBX::Instance::fastDynamicCast<RBX::TextBox>(pObjectToInsert.get()))
{
textBox->setFont(defaultFont);
textBox->setFontSize(defaultFontSize);
textBox->setSize(RBX::UDim2(0,200,0,50)); // Make TextBox more the size of a traditional text box
}
else if(RBX::GuiImageButton* imageButton = RBX::Instance::fastDynamicCast<RBX::GuiImageButton>(pObjectToInsert.get()))
imageButton->setImage(defaultImage);
else if(RBX::ImageLabel* imageLabel = RBX::Instance::fastDynamicCast<RBX::ImageLabel>(pObjectToInsert.get()))
imageLabel->setImage(defaultImage);
}
else if (RBX::BillboardGui* bbgui = RBX::Instance::fastDynamicCast<RBX::BillboardGui>(pObjectToInsert.get()))
{
bbgui->setSize(RBX::UDim2(0,200,0,50));
}
else if (RBX::Script* script = RBX::Instance::fastDynamicCast<RBX::Script>(pObjectToInsert.get()))
{
script->setEmbeddedCode(RBX::ProtectedString::fromTrustedSource("print 'Hello world!'\n"));
}
else if (RBX::SpawnLocation *spawnLocation = RBX::Instance::fastDynamicCast<RBX::SpawnLocation>(pObjectToInsert.get()))
{
spawnLocation->setAnchored(true);
spawnLocation->setPartSizeUi(RBX::Vector3(6, 1, 6));
spawnLocation->setSurfaceType(RBX::NORM_Y, RBX::NO_SURFACE);
// create default decal
boost::shared_ptr<RBX::Decal> decal = RBX::Creatable<RBX::Instance>::create<RBX::Decal>();
decal->setTexture("rbxasset://Textures/SpawnLocation.png");
decal->setFace(RBX::NORM_Y);
decal->setParent(spawnLocation);
}
else if (RBX::ModuleScript* moduleScript = RBX::Instance::fastDynamicCast<RBX::ModuleScript>(pObjectToInsert.get()))
{
moduleScript->setSource(RBX::ProtectedString::fromTrustedSource("local module = {}\n\nreturn module\n"));
}
// FormFactor deprication
/* if (RBX::FormFactorPart* formFactorPart = RBX::Instance::fastDynamicCast<RBX::FormFactorPart>(pObjectToInsert.get()))
formFactorPart->setFormFactorUi(RBX::PartInstance::SYMETRIC);
*/
if (RBX::VehicleSeat* vehicleSeat = RBX::Instance::fastDynamicCast<RBX::VehicleSeat>(pObjectToInsert.get()))
{
vehicleSeat->setPartSizeUi(RBX::Vector3(4, 1, 2));
vehicleSeat->setColor(RBX::BrickColor::brickBlack());
}
if (RBX::Seat* seat = RBX::Instance::fastDynamicCast<RBX::Seat>(pObjectToInsert.get()))
seat->setColor(RBX::BrickColor::brickBlack());
}
void InsertObjectWidget::InsertObject(
boost::shared_ptr<RBX::Instance> pObjectToInsert,
boost::shared_ptr<RBX::DataModel> pDataModel,
InsertMode insertMode,
QPoint* mousePosition)
{
try
{
RBX::Instance* pTargetInstance = getTargetInstance(pObjectToInsert.get(), pDataModel);
bool ribbonBarMode = insertMode == InsertMode_RibbonAction;
if (ribbonBarMode &&
RBX::Instance::fastDynamicCast<RBX::PartInstance>(pObjectToInsert.get()) &&
RBX::Instance::fastDynamicCast<RBX::ModelInstance>(pTargetInstance))
{
pTargetInstance = pDataModel->getWorkspace();
}
if (!pTargetInstance)
{
// in ribbon bar, in case of multiple selections allow part insertion
if (ribbonBarMode && RBX::Instance::fastDynamicCast<RBX::PartInstance>(pObjectToInsert.get()))
pTargetInstance = pDataModel->getWorkspace();
else
throw std::runtime_error("InsertObject: Cannot do insert object when there are multiple selections. Please make sure you select a valid target object.");
}
{
RBX::DataModel::LegacyLock lock(pDataModel, RBX::DataModelJob::Write);
if (!pObjectToInsert->canSetParent(pTargetInstance))
{
RBX::Selection* selection = RBX::ServiceProvider::create< RBX::Selection >(pDataModel.get());
RBX::Instance* intendedTarget;
if (selection && selection->size() > 0)
intendedTarget = selection->front().get();
else
intendedTarget = pTargetInstance;
throw RBX::runtime_error("%s cannot be a child of %s", pObjectToInsert->getClassNameStr().c_str(), intendedTarget->getName().c_str());
}
if (FFlag::GoogleAnalyticsTrackingEnabled)
{
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ACTION, "InsertObject", pObjectToInsert->getClassNameStr().c_str());
}
RBX::PartInstance* pPartInstance = RBX::Instance::fastDynamicCast<RBX::PartInstance>(pObjectToInsert.get());
// If it's a part, let's set the material to our current material (dont set a waypoint)
if (UpdateUIManager::Instance().getMainWindow().isRibbonStyle() && pPartInstance)
{
pPartInstance->setRenderMaterial(RBX::MaterialVerb::getCurrentMaterial());
pPartInstance->setColor(RBX::ColorVerb::getCurrentColor());
}
SetObjectDefaultValues(pObjectToInsert);
if( (pDataModel->getWorkspace() == pTargetInstance) && pPartInstance)
{
// get position to insert at
RBX::Vector3 pos = getInsertLocation(pDataModel, mousePosition);
// move part up by it's height (required for Smooth Terrain alignment)
pos.y += pPartInstance->getPartSizeUi().y;
// paste instances, this will suppress move and use the position hint to insert the part
RBX::Instances instances;
instances.push_back(pObjectToInsert);
pDataModel->getWorkspace()->insertPasteInstances(instances, pTargetInstance, RBX::INSERT_TO_3D_VIEW, RBX::SUPPRESS_PROMPTS, &pos, NULL, false);
// save camera position
m_sCameraCFrame = pDataModel->getWorkspace()->getConstCamera()->getCameraCoordinateFrame();
//TODO: check if we need to position camera, for non mouse insert i.e. doing an insert from TreeView or Ribbon bar?
}
else
{
pObjectToInsert->setParent(pTargetInstance);
if (pPartInstance &&
RBX::PartInstance::nonNullInWorkspace(shared_from(pPartInstance)))
{
std::vector<boost::weak_ptr<RBX::PartInstance> > partArray;
partArray.push_back(weak_from(pPartInstance));
pDataModel->getWorkspace()->movePartsToCameraFocus(partArray);
}
else
{
if (RBX::Decal *decal = RBX::Instance::fastDynamicCast<RBX::Decal>(pObjectToInsert.get()))
{
pDataModel->getWorkspace()->startDecalDrag(decal, RBX::INSERT_TO_3D_VIEW);
}
}
}
// If int limit is reached, halve weights
if (m_objectWeights.value(pObjectToInsert->getClassNameStr().c_str()).toInt() >= std::numeric_limits<int>::max() - 1)
for (QHash<QString, QVariant>::iterator iter = m_objectWeights.begin(); iter != m_objectWeights.end(); ++iter)
m_objectWeights[iter.key()] = iter.value().toInt() / 2;
m_objectWeights[pObjectToInsert->getClassNameStr().c_str()] = m_objectWeights.value(pObjectToInsert->getClassNameStr().c_str()).toInt() + 1;
//for undo redo (DecalTool will record Decal insertion history)
if (!RBX::Instance::fastDynamicCast<RBX::Decal>(pObjectToInsert.get()))
{
std::string action = RBX::format("Insert %s", pObjectToInsert->getName().c_str());
RBX::ChangeHistoryService::requestWaypoint(action.c_str(), pDataModel.get());
}
// check if we need to select inserted object
InsertObjectWidget& insertObjectWidget = UpdateUIManager::Instance().getViewWidget<InsertObjectWidget>(eDW_BASIC_OBJECTS);
if ( insertObjectWidget.isSelectObject() || ((insertMode != InsertMode_InsertWidget) && pPartInstance) )
{
RBX::Selection* selection = RBX::ServiceProvider::create<RBX::Selection>(pDataModel.get());
if (selection) selection->setSelection(pObjectToInsert.get());
}
else
{
RobloxExplorerWidget& explorer = UpdateUIManager::Instance().getViewWidget<RobloxExplorerWidget>(eDW_OBJECT_EXPLORER);
RobloxTreeWidget* pTreeWidget = explorer.getTreeWidget();
if ( pTreeWidget && pTreeWidget->dataModel() )
QApplication::postEvent(
pTreeWidget,
new RobloxCustomEventWithArg(TREE_SCROLL_TO_INSTANCE,
boost::bind(
&RobloxTreeWidget::scrollToInstance,
pTreeWidget,
pObjectToInsert.get() ) ) );
}
}
// if inserted object is a script then open it
LuaSourceBuffer buffer = LuaSourceBuffer::fromInstance(pObjectToInsert);
if (!buffer.empty())
RobloxDocManager::Instance().openDoc(buffer);
else
UpdateUIManager::Instance().updateToolBars(); //update toolbars
}
catch(std::bad_alloc&)
{
throw;
}
catch(std::exception& e)
{
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e.what());
}
}
/**
* Inserts an object based on a class name.
*
* @param className name of instance class
* @param pDataModel model to insert object into
*/
void InsertObjectWidget::InsertObject(
const QString& className,
boost::shared_ptr<RBX::DataModel> pDataModel,
InsertMode insertMode,
QPoint* mousePosition)
{
tInstanceMap instanceMap;
generateInstanceMap(
&RBX::Reflection::ClassDescriptor::rootDescriptor(),
&instanceMap,
NULL );
InsertObject(instanceMap[className],pDataModel, insertMode, mousePosition);
}
void InsertObjectWidget::setFilter(QString filter)
{
while(m_pInsertObjectListWidget->count() > 0)
m_pInsertObjectListWidget->takeItem(0);
RBX::Instance* pParent = getTargetInstance(NULL, m_pDataModel);
for (int i = 0; i < m_ItemList->count(); ++i)
{
InsertObjectListWidgetItem* listItem = m_ItemList->at(i);
if (listItem->checkFilter(filter, pParent))
m_pInsertObjectListWidget->addItem(listItem);
}
m_pInsertObjectListWidget->sortItems(m_objectWeights, filter);
}
void InsertObjectWidget::startingQuickInsert(QWidget* widget, bool closeDockWhenDoneWithQuickInsert)
{
if (widget)
{
m_quickInsertPreviousWidget = widget;
connect(widget, SIGNAL(destroyed()), this, SLOT(onPreviousWidgetDestroyed()));
}
m_closeDockWhenDoneWithQuickInsert = closeDockWhenDoneWithQuickInsert;
}
void InsertObjectWidget::connectSelectionChangeEvent(bool connectSignal)
{
//RBX::StandardOut::singleton()->printf(RBX::MESSAGE_INFO, "InsertObjectWidget::connectSelectionChangeEvent - %d", connectSignal);
m_cSelectionChanged.disconnect();
if (connectSignal && m_pDataModel)
{
RBX::Selection* pSelection = m_pDataModel->create<RBX::Selection>();
if (pSelection)
m_cSelectionChanged = pSelection->selectionChanged.connect(boost::bind(&InsertObjectWidget::onInstanceSelectionChanged, this, _1));
}
}
void InsertObjectWidget::onInstanceSelectionChanged(const RBX::SelectionChanged&)
{
requestDialogRedraw();
}
void InsertObjectWidget::requestDialogRedraw()
{
if(m_bRedrawRequested || !m_pDataModel)
return;
m_bRedrawRequested = true;
QMetaObject::invokeMethod(
this,
"redrawDialog",
Qt::QueuedConnection );
}
void InsertObjectWidget::redrawDialog()
{
m_bRedrawRequested = false;
if (m_pDataModel)
{
if (m_bInitializationRequired)
{
//RBX::StandardOut::singleton()->print(RBX::MESSAGE_INFO, "InsertObjectWidget::createWidgets");
m_pInsertObjectListWidget->setUpdatesEnabled(false);
m_ItemList->clear();
populateList(false, m_ItemList, &RBX::Reflection::ClassDescriptor::rootDescriptor(), m_pDataModel);
m_pInsertObjectListWidget->setUpdatesEnabled(true);
m_bInitializationRequired = false;
}
setFilter(m_pFilterEdit->text());
}
}
void InsertObjectWidget::onItemInsertRequested()
{
QListWidgetItem* currentItem = m_pInsertObjectListWidget->currentItem();
if (m_quickInsertPreviousWidget)
{
m_quickInsertPreviousWidget->activateWindow();
m_quickInsertPreviousWidget->setFocus(Qt::MouseFocusReason);
m_quickInsertPreviousWidget = NULL;
UpdateUIManager::Instance().setDockVisibility(eDW_BASIC_OBJECTS, !m_closeDockWhenDoneWithQuickInsert);
m_closeDockWhenDoneWithQuickInsert = false;
}
m_pInsertObjectListWidget->onItemInsertRequested(currentItem);
}
RBX::Instance* InsertObjectWidget::getSelectionFrontPart()
{
RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write);
RBX::Selection* selection = RBX::ServiceProvider::create< RBX::Selection >(m_pDataModel.get());
if (selection && selection->size() > 0)
{
return selection->front().get();
}
return NULL;
}
RBX::Instance* InsertObjectWidget::getTargetInstance(RBX::Instance* pInstance, boost::shared_ptr<RBX::DataModel> pDataModel)
{
// If there is a selected part, put it under that
RBX::DataModel::LegacyLock lock(pDataModel, RBX::DataModelJob::Write);
if(dynamic_cast<RBX::Service*>(pInstance))
return pDataModel.get();
RBX::Selection* selection = RBX::ServiceProvider::create< RBX::Selection >(pDataModel.get());
if (selection && selection->size() > 0)
{
if(selection->size() > 1)
return NULL;
RBX::Instance* selFront = selection->front().get();
if (!pInstance || pInstance->canSetParent(selFront))
return selFront;
}
if(pInstance)
{
// Otherwise check to see if there is a preferred parent
RBX::Reflection::Metadata::Class* pMetadata = RBX::Reflection::Metadata::Reflection::singleton()->get(pInstance->getDescriptor(), false);
std::string preferredParent = pMetadata->getPreferredParent(); //itemToInsert->getPreferredParent();
if (preferredParent != "")
{
shared_ptr<RBX::Instance> serviceParent = pDataModel->getPublicServiceByClassNameString(preferredParent);
if (serviceParent)
{
return serviceParent.get();
}
}
}
return pDataModel->getWorkspace();
}
void InsertObjectWidget::onFilter( QString filter )
{
setFilter(filter);
m_pInsertObjectListWidget->setCurrentRow(0);
}
RBX::Vector3 InsertObjectWidget::getInsertLocation(boost::shared_ptr<RBX::DataModel> pDataModel, QPoint* insertPosition, bool* isOnPart)
{
RBX::Vector3 location;
if (RobloxDocManager::Instance().getPlayDoc())
{
QWidget* viewer = RobloxDocManager::Instance().getPlayDoc()->getViewer();
if (viewer)
{
RBX::Workspace* workspace = pDataModel->getWorkspace();
// check if camera has moved
m_sIsInsertLocationValid = RBX::Math::fuzzyEq(workspace->getConstCamera()->getCameraCoordinateFrame(), m_sCameraCFrame);
// if we are inserting at screen center and we have a valid insert location, then insert it at the cached insert location
if (!insertPosition && m_sIsInsertLocationValid)
return m_sCachedInsertLocation;
// else calculate the insert location
int w = viewer->width();
int h = viewer->height();
std::vector<const RBX::Primitive*> excludeParts;
RBX::PartByLocalCharacter filter(workspace);
// if no insert position then insert at the center on the window
RBX::RbxRay mouseRay = workspace->getConstCamera()->worldRay(insertPosition ? insertPosition->x() : w/2,
insertPosition ? insertPosition->y() : h/2);
// check if we have a part at the insert position?
RBX::PartInstance* foundPart = RBX::MouseCommand::getMousePart(mouseRay,
*workspace->getWorld()->getContactManager(),
excludeParts,
&filter,
location);
if (isOnPart)
*isOnPart = (foundPart != NULL);
// if no part found, try to get a point based on the camera
if (!foundPart)
{
RBX::Vector3 pos = workspace->getConstCamera()->getCameraFocus().lookVector();
// if we do not get a valid point, insert at origin
if (!RBX::Math::intersectRayPlane(RBX::MouseCommand::getSearchRay(mouseRay), RBX::Plane(RBX::Vector3::unitY(), pos), location))
return RBX::Vector3::zero();
}
}
}
// cache insert location for insertion done at center of the screen
if (!insertPosition)
{
m_sIsInsertLocationValid = true;
m_sCachedInsertLocation = location;
}
return location;
}
+151
View File
@@ -0,0 +1,151 @@
/**
* CommonInsertWidget.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
//this will make sure that we can use qt with boost (else moc compiler errors)
#ifndef QT_NO_KEYWORDS
#define QT_NO_KEYWORDS
#endif
// Qt Headers
#include <QWidget>
#include <QListWidgetItem>
#include <QDialog>
#include <QCheckBox>
#include <QSet>
// Roblox Headers
#include "rbx/signal.h"
#include "reflection/Property.h"
namespace RBX {
class DataModel;
class Instance;
class SelectionChanged;
namespace Reflection {
class ClassDescriptor;
}
}
class QTabWidget;
class QListWidget;
class QLineEdit;
class QPushButton;
class RobloxToolBox;
class InsertObjectWidget;
class InsertObjectListWidget;
class InsertObjectListWidgetItem;
class InsertObjectWidget : public QWidget
{
Q_OBJECT
public:
InsertObjectWidget(QWidget *pParentWidget);
virtual ~InsertObjectWidget();
void setDataModel(boost::shared_ptr<RBX::DataModel> pDataModel);
enum InsertMode
{
InsertMode_InsertWidget,
InsertMode_TreeWidget,
InsertMode_RibbonAction,
InsertMode_ContextMenu
};
static void InsertObject(
boost::shared_ptr<RBX::Instance> pObjectToInsert,
boost::shared_ptr<RBX::DataModel> pDataModel,
InsertMode insertMode = InsertMode_InsertWidget,
QPoint* mousePosition = NULL);
static void InsertObject(
const QString& className,
boost::shared_ptr<RBX::DataModel> pDataModel,
InsertMode insertMode = InsertMode_InsertWidget,
QPoint* mousePosition = NULL);
static void createWidgets(bool services, InsertObjectListWidget* listWidget, const RBX::Reflection::ClassDescriptor* pDescriptor, shared_ptr<RBX::DataModel> pDataModel);
static void populateList(bool services, QList<InsertObjectListWidgetItem*> *itemList, const RBX::Reflection::ClassDescriptor* pDescriptor, shared_ptr<RBX::DataModel> pDataModel);
static void populateListHelper(bool services, QList<InsertObjectListWidgetItem*> *itemList, const RBX::Reflection::ClassDescriptor& pDescriptor, shared_ptr<RBX::DataModel> pDataModel);
static QMenu* createMenu(
RBX::Instance* parent,
const QObject* receiver,
const char* member );
void setFilter(QString filter = QString(""));
bool isSelectObject() const { return m_pCheckBox->isChecked(); }
void startingQuickInsert(QWidget* widget, bool closeDockWhenDoneWithQuickInsert);
static void SetObjectDefaultValues(boost::shared_ptr<RBX::Instance> pObjectToInsert);
static RBX::Vector3 getInsertLocation(boost::shared_ptr<RBX::DataModel> pDataModel, QPoint* insertPosition, bool* isOnPart = NULL);
virtual QSize sizeHint() const;
public Q_SLOTS:
void updateWidget(bool state);
virtual void setVisible(bool visible);
private Q_SLOTS:
void onFilter(QString filter);
void redrawDialog();
void onSelectOnInsertClicked(bool checked);
void onItemInsertRequested();
void onPreviousWidgetDestroyed() { m_quickInsertPreviousWidget = NULL; }
protected:
virtual bool event(QEvent* e);
virtual bool eventFilter(QObject* watched, QEvent* evt);
private:
void onInstanceSelectionChanged(const RBX::SelectionChanged& evt);
void connectSelectionChangeEvent(bool connectSignal);
void requestDialogRedraw();
static bool instanceExistsInDataModel(shared_ptr<RBX::Instance> instance, shared_ptr<RBX::DataModel> pDataModel);
static RBX::Instance* getTargetInstance(RBX::Instance* pInstance, boost::shared_ptr<RBX::DataModel> pDataModel);
RBX::Instance* getSelectionFrontPart();
typedef QMap<QString,boost::shared_ptr<RBX::Instance> > tInstanceMap;
static void generateInstanceMap(
const RBX::Reflection::ClassDescriptor* pDescriptor,
tInstanceMap* instanceMap,
RBX::Instance* parent );
boost::shared_ptr<RBX::DataModel> m_pDataModel;
InsertObjectListWidget* m_pInsertObjectListWidget;
QLineEdit* m_pFilterEdit;
QCheckBox* m_pCheckBox;
rbx::signals::connection m_cSelectionChanged;
bool m_bInitializationRequired;
bool m_bRedrawRequested;
static QHash<QString, QVariant> m_objectWeights;
static QSet<QString> m_sObjectExceptions;
QWidget* m_quickInsertPreviousWidget;
bool m_closeDockWhenDoneWithQuickInsert;
QList<InsertObjectListWidgetItem*>* m_ItemList;
static RBX::Vector3 m_sCachedInsertLocation;
static RBX::CoordinateFrame m_sCameraCFrame;
static bool m_sIsInsertLocationValid;
};
File diff suppressed because it is too large Load Diff
+330
View File
@@ -0,0 +1,330 @@
/**
* DebuggerClient.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
//this will make sure that we can use qt with boost (else moc compiler errors)
#ifndef QT_NO_KEYWORDS
#define QT_NO_KEYWORDS
#endif
// 3rd Party Headers
#include "boost/shared_ptr.hpp"
// Roblox Headers
#include "rbx/signal.h"
#include "script/DebuggerManager.h"
// Qt Headers
#include <QObject>
#include <QTreeWidget>
// Roblox Studio Headers
#include "RobloxScriptDoc.h"
namespace RBX {
class Script;
class DataModel;
class Heartbeat;
class RunTransition;
namespace Scripting
{
class ScriptDebugger;
}
}
class QTextBlock;
class QDockWidget;
class ScriptTextEditor;
class DebuggerToolTipWidget;
class DebuggerClient;
class IRobloxDoc;
class QDebuggerClient;
class QStringList;
class DebuggerClientManager : public QObject
{
Q_OBJECT
public:
enum eStepMode
{
STEP_INTO,
STEP_OUT,
STEP_OVER
};
static DebuggerClientManager& Instance();
void setDataModel(shared_ptr<RBX::DataModel> spDataModel);
boost::shared_ptr<RBX::DataModel> getDataModel();
bool actionState(const QString &actionID, bool &enableState, bool &checkedState);
bool handleAction(const QString &actionID, bool checkedState);
void addDebuggerClient(DebuggerClient* pDebuggerClient);
void removeDebuggerClient(DebuggerClient* pDebuggerClient);
void setActiveDebuggerClient(DebuggerClient* pDebuggerClient);
void syncBreakpointState(shared_ptr<RBX::Scripting::DebuggerBreakpoint> spBreakpoint);
RBX::Reflection::Variant getWatchValue(boost::shared_ptr<RBX::Scripting::DebuggerWatch> spWatch);
void addWatch(const QString& watchKey);
DebuggerClient* getOrCreateDebuggerClient(shared_ptr<RBX::Instance> spScript);
DebuggerClient* getDebuggerClient(shared_ptr<RBX::Instance> spScript);
const std::vector<DebuggerClient *>& getDebuggerClients();
DebuggerClient* getCurrentDebuggerClient() { return m_pCurrentDebuggerClient; }
void breakpointEncounter(DebuggerClient *pDebuggerClient);
void onRunTransition(RBX::RunTransition evt);
//signals emitted
rbx::signal<void(int, RBX::Scripting::ScriptDebugger::Stack)> clientActivated;
rbx::signal<void()> clientDeactivated;
rbx::signal<void()> clearAll;
rbx::signal<void()> debuggingStarted;
rbx::signal<void()> debuggingStopped;
rbx::signal<void()> executionDataCleared;
rbx::signal<void(int, DebuggerClient*)> breakpointEncountered;
rbx::signal<void(shared_ptr<RBX::Scripting::DebuggerBreakpoint>)> breakpointAdded;
rbx::signal<void(shared_ptr<RBX::Scripting::DebuggerBreakpoint>)> breakpointRemoved;
rbx::signal<void(shared_ptr<RBX::Scripting::DebuggerWatch>)> watchAdded;
rbx::signal<void(shared_ptr<RBX::Scripting::DebuggerWatch>)> watchRemoved;
rbx::signal<void(DebuggerClient*)> debuggersListUpdated;
private Q_SLOTS:
void onStepAction();
void updateScriptDocument();
void updateDebuggersList();
private:
DebuggerClientManager();
void displayBreakOnErrorDialog();
void updateDocumentData(bool openDoc = true, bool updateMarkers = true);
void onDebuggerAdded(boost::shared_ptr<RBX::Instance> spInstance);
void onDebuggerRemoved(boost::shared_ptr<RBX::Instance> spInstance);
void startDebugging(RBX::RunTransition evt);
void pauseDebugging(RBX::RunTransition evt);
void stopDebugging(RBX::RunTransition evt);
void resumeAllClients(bool forceAll = false);
void pauseAllClients(bool forceAll = false);
void resetAllClients();
Q_INVOKABLE void updateDebugActions(bool add);
std::vector<DebuggerClient *> debuggerClients;
boost::shared_ptr<RBX::DataModel> m_spDataModel;
rbx::signals::connection m_cRunTransitionConnection;
rbx::signals::connection m_cDebuggerAddedConnection;
rbx::signals::connection m_cDebuggerRemovedConnection;
DebuggerClient* m_pCurrentDebuggerClient;
DebuggerClient* m_pActiveDebuggerClient;
QAction* m_pSeparatorAction;
QMutex m_debuggerClientMutex;
bool m_bIgnorePauseExecution;
bool m_bDebuggerListUpdateRequested;
};
class DebuggerClient
{
public:
struct BreakpointDetail
{
BreakpointDetail(int iLine, bool iIsEnabled)
: line(iLine), isEnabled(iIsEnabled)
{}
int line;
bool isEnabled;
//std::string condition;
};
typedef std::vector<BreakpointDetail> BreakpointDetails;
DebuggerClient(boost::shared_ptr<RBX::Instance> script);
~DebuggerClient();
void activate();
void deActivate();
void setDocument(IRobloxDoc *pDocument);
boost::shared_ptr<RBX::Instance> getScript();
boost::shared_ptr<RBX::Instance> getScript(int frameNo);
bool updateDocument(bool openDoc = true, bool updateMarkers = true);
void setExternalDebuggerClient(DebuggerClient* pExtDebuggerClient);
void highlightLine(int textEditLine);
int getCurrentLine();
void setCurrentLine(int textEditLine);
void setMarker(int textEditLine, const QString& marker, bool setBlockCurrent);
void setCurrentFrame(int frame);
int getCurrentFrame() { return m_CurrentFrame; }
RBX::Scripting::ScriptDebugger::Stack getCurrentCallStack() { return m_CallStackAtPausedLine; }
bool getValue(const QString& key, RBX::Reflection::Variant& value, int frame = -1);
RBX::Reflection::Variant getWatchValue(boost::shared_ptr<RBX::Scripting::DebuggerWatch> spWatch);
void addWatch(const QString& watchKey);
QString getSourceCodeAtLine(int breakpointLine);
void syncBreakpointState(int breakpointLine);
void syncScriptWithTextEditor(const BreakpointDetails& textEditBreakpoints);
void syncTextEdtiorWithScript();
void toggleBreakpoint(int breakpointLine);
void toggleBreakpointState(int breakpointLine);
void pauseExecution();
void resumeExecution();
void resetExecution();
bool isDebugging();
bool isPaused();
bool hasError();
std::string getErrorMessage() { return m_ErrorMessage; }
const RBX::Scripting::ScriptDebugger::PausedThreads& getPausedThreads();
int getCurrentThread();
void setCurrentThread(int threadID);
bool isHighlightingRequired();
bool isTopFrameCurrent();
bool actionState(const QString &actionID, bool &enableState, bool &checkedState);
bool handleAction(const QString &actionID, bool checkedState);
//signals emitted
rbx::signal<void()> debuggerResuming;
private:
bool isValid();
void onDebuggerResume();
void onEncounteredBreakpoint(int line);
void onBreakpointAdded(shared_ptr<RBX::Instance> spInstance);
void onBreakpointRemoved(shared_ptr<RBX::Instance> spInstance);
void onWatchAdded(shared_ptr<RBX::Instance> spInstance);
void onWatchRemoved(shared_ptr<RBX::Instance> spInstance);
void onScriptErrorDetected(int errorLine, std::string errorMessage, RBX::Scripting::ScriptDebugger::Stack stack);
void onScriptStopped();
bool isKeyDefined(boost::shared_ptr<const RBX::Reflection::ValueMap> variables, const QString &wordUnderCursor, RBX::Reflection::Variant &value);
boost::shared_ptr<RBX::Instance> m_spScript;
boost::shared_ptr<RBX::DataModel> m_spDataModel;
boost::shared_ptr<RBX::Scripting::ScriptDebugger> m_spDebugger;
std::list<rbx::signals::connection> m_cConnections;
DebuggerClient* m_pExtDebuggerClient;
QDebuggerClient* m_pQDebuggerClient;
QStringList m_ScriptLines;
RBX::Scripting::ScriptDebugger::Stack m_CallStackAtPausedLine;
int m_CurrentFrame;
std::string m_ErrorMessage;
int m_currentThreadID;
bool m_bIgnoreBreakpointAddRemove;
bool m_bIsActive;
};
class QDebuggerClient : public QObject
{
Q_OBJECT
public:
QDebuggerClient(DebuggerClient* pDebuggerClient, ScriptTextEditor* pTextEdit);
~QDebuggerClient();
void activate();
void deActivate();
QString getSourceCodeAtLine(int textEditLine);
void updateBreakpointState(int textEditLine, RBXTextUserData::eBreakpointState state = RBXTextUserData::NO_BREAKPOINT);
void highlightLine(int textEditLine);
void clearLineHighlight();
void setCurrentLine(int textEditLine);
void setMarker(int textEditLine, const QString& marker, bool setBlockCurrent);
void setExternalDebuggerClient(DebuggerClient* pExtDebuggerClient) { m_pExtDebuggerClient = pExtDebuggerClient; }
void updateEditor();
private Q_SLOTS:
void onInsertBreakpoint();
void onDeleteBreakpoint();
void onToggleBreakpoint(int textEditLine);
void onToggleBreakpointState();
void onAddWatch();
//slot methods to ensure public functions are called from main thread
void highlightLine_MT(int textEditLine);
void clearLineHighlight_MT();
void setCurrentLine_MT(int textEditLine);
void setMarker_MT(int textEditLine, QString marker, bool setBlockCurrent);
bool onShowToolTip(const QPoint& pos, const QString& key);
void onContentsChanged();
void onUpdateContextualMenu(QMenu *pMenu, QPoint pos);
private:
void updateTextUserData(int textEditLineNumber, RBXTextUserData::eBreakpointState state);
void modifyContextualMenu(QMenu* pContextualMenu);
void cleanupContextMenu();
void setCurrentBlock(const QTextBlock &blockToSet);
void highlightBlock(const QTextBlock &blockToHighlight);
DebuggerClient::BreakpointDetails getBreakpointDetails();
void removeBreakpointState();
bool eventFilter(QObject *obj, QEvent *evt) ;
DebuggerClient* m_pDebuggerClient;
DebuggerClient* m_pExtDebuggerClient;
QAction* m_pBreakpointMenuAction;
QAction* m_pSeparatorAction1;
QAction* m_pSeparatorAction2;
ScriptTextEditor* m_pTextEdit;
DebuggerToolTipWidget* m_pToolTipWidget;
QList<QTextEdit::ExtraSelection> m_extraSelections;
int m_lastBlockCount;
bool m_isModifiedByKeyBoard;
bool m_ignoreAutoRepeatEvent;
};
File diff suppressed because it is too large Load Diff
+393
View File
@@ -0,0 +1,393 @@
/**
* DebuggerWidgets.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
//this will make sure that we can use qt with boost (else moc compiler errors)
#ifndef QT_NO_KEYWORDS
#define QT_NO_KEYWORDS
#endif
// standard C/C++ Headers
#include <map>
// 3rd Party Headers
#include "boost/shared_ptr.hpp"
// Roblox Headers
#include "script/DebuggerManager.h"
// Qt Headers
#include <QObject>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QComboBox>
// Roblox Studio Headers
namespace RBX {
namespace Reflection {
class PropertyDescriptor;
}
}
class QDockWidget;
class QToolButton;
class BreakpointsWidget;
class CallStackWidget;
class DebuggerClient;
class DebuggerViewsManager
{
public:
static DebuggerViewsManager& Instance();
enum EDebuggerViewID
{
eDV_BREAKPOINTS,
eDV_CALLSTACK,
eDV_WATCH,
eDV_MAX
};
template<class T>
T& getViewWidget(EDebuggerViewID id) const
{
RBXASSERT(m_ViewWidgets.contains(id));
RBXASSERT(m_ViewWidgets[id]);
QWidget* widget = m_ViewWidgets[id];
RBXASSERT(dynamic_cast<T*>(widget));
return static_cast<T&>(*widget);
}
void showDockWidget(EDebuggerViewID dockId);
private:
DebuggerViewsManager();
void restoreViews();
void saveViews();
void onDebuggingStarted();
void onDebuggingStopped();
void onDebuggerClientActivated(int currentBreakpoint, RBX::Scripting::ScriptDebugger::Stack currentStack);
void onDebuggerClientDeactivated();
void onExecutionDataCleared();
void onBreakpointEncountered(int currentBreakpoint, DebuggerClient* pDebuggerClient);
void onClearAll();
void onBreakpointAdded(shared_ptr<RBX::Scripting::DebuggerBreakpoint> spBreakpoint);
void onBreakpointRemoved(shared_ptr<RBX::Scripting::DebuggerBreakpoint> spBreakpoint);
void onWatchAdded(shared_ptr<RBX::Scripting::DebuggerWatch> spWatch);
void onWatchRemoved(shared_ptr<RBX::Scripting::DebuggerWatch> spWatch);
void onDebuggersListUpdated(DebuggerClient* pDebuggerClient);
QMap<EDebuggerViewID,QDockWidget*> m_DockWidgets;
QMap<EDebuggerViewID,QWidget*> m_ViewWidgets;
std::list<rbx::signals::connection> m_cConnections;
bool m_bBreakpointEncountered;
};
class DebuggerTreeWidgetItem : public QTreeWidgetItem
{
public:
DebuggerTreeWidgetItem() {}
//no-op functions
virtual void activate() {}
virtual void columnModified(int column) {}
};
class DebuggerTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
DebuggerTreeWidget(QWidget* pParent);
Q_SIGNALS:
void deleteSelectedItem();
protected Q_SLOTS:
virtual void onItemActivated(QTreeWidgetItem*);
virtual void onItemChanged(QTreeWidgetItem*, int);
protected:
virtual void mousePressEvent(QMouseEvent *evt);
virtual void keyPressEvent(QKeyEvent * evt);
};
class StackDetailsItem : public DebuggerTreeWidgetItem
{
public:
StackDetailsItem(const RBX::Scripting::ScriptDebugger::FunctionInfo& funcInfo, bool isFirstItem);
~StackDetailsItem();
void activate();
void updateIcon(bool isCurrent);
private:
int getLine();
int getFrame();
boost::shared_ptr<RBX::Instance> m_spAssociatedScript;
bool m_bIsFirstItem;
};
class CallStackTreeWidget : public DebuggerTreeWidget
{
Q_OBJECT
public:
CallStackTreeWidget(QWidget* pParent=0);
~CallStackTreeWidget();
void setCallStack(const RBX::Scripting::ScriptDebugger::Stack& stack);
private:
void onItemActivated(QTreeWidgetItem* pItem);
QTreeWidgetItem* m_pPreviousItem;
};
class CallStackDebuggersList: public QComboBox
{
Q_OBJECT
public:
CallStackDebuggersList(QWidget* pWidget);
/*override*/ void showPopup();
bool isDirty() { return m_bIsDirty;}
void setDirty(bool state) { m_bIsDirty = state;}
Q_SIGNALS:
void popupAboutToShow();
private:
bool m_bIsDirty;
};
class CallStackWidget : public QWidget
{
Q_OBJECT
public:
CallStackWidget(QWidget* pParent=0);
void populateDebuggers(DebuggerClient* pDebuggerClient);
void clearDebuggers();
void setCallStack(const RBX::Scripting::ScriptDebugger::Stack& stack);
void clear();
private Q_SLOTS:
void onCurrentIndexChanged(int index);
void onDebuggerListPopupShow();
private:
CallStackTreeWidget *m_pCallStackTreeWidget;
QComboBox *m_pScriptDebuggersList;
DebuggerClient *m_pCurrentDebuggerClient;
};
class BreakpointItem : public DebuggerTreeWidgetItem
{
public:
BreakpointItem(boost::shared_ptr<RBX::Scripting::DebuggerBreakpoint> breakpoint);
~BreakpointItem();
bool hasBreakpoint(boost::shared_ptr<RBX::Scripting::DebuggerBreakpoint> breakpoint);
void columnModified(int column);
void activate();
void setBreakpointEnabled(bool state);
bool isBreakpointEnabled();
void deleteBreakpoint();
private:
void onBreakpointPropertyChanged(const RBX::Reflection::PropertyDescriptor* pPropertyDescriptor);
void onScriptAncestryChanged(shared_ptr<RBX::Instance> newParent);
boost::shared_ptr<RBX::Scripting::DebuggerBreakpoint> m_spBreakpoint;
rbx::signals::scoped_connection m_cPropertyChangedConnection;
rbx::signals::scoped_connection m_cAncestoryChangedConnection;
bool m_bIgnoreBreakpointModification;
bool m_bIgnoreWaypointRequest;
};
class BreakpointsTreeWidget : public DebuggerTreeWidget
{
public:
BreakpointsTreeWidget(QWidget* pParent=0);
void addBreakpoint(boost::shared_ptr<RBX::Scripting::DebuggerBreakpoint> breakpoint);
void removeBreakpoint(boost::shared_ptr<RBX::Scripting::DebuggerBreakpoint> breakpoint);
void setCurrentBreakpoint(int breakpointLine);
void deleteSelectedBreakpoint();
void deleteAllBreakpoints();
void disableEnableAllBreakpoints();
void goToSourceCode();
bool hasBreakpointsEnabled();
private:
BreakpointItem* findBreakpointItem(boost::shared_ptr<RBX::Scripting::DebuggerBreakpoint> breakpoint);
BreakpointItem* findBreakpointItem(int breakpointLine);
};
class BreakpointsWidget: public QWidget
{
Q_OBJECT
public:
BreakpointsWidget(QWidget* pParent=0);
//delegate functions
void addBreakpoint(boost::shared_ptr<RBX::Scripting::DebuggerBreakpoint> breakpoint);
void removeBreakpoint(boost::shared_ptr<RBX::Scripting::DebuggerBreakpoint> breakpoint);
void setCurrentBreakpoint(int breakpointLine);
void clear();
int numBreakpoints() { return m_pBreakpointTreeWidget ? m_pBreakpointTreeWidget->topLevelItemCount() : 0; }
private Q_SLOTS:
void onPushButtonClicked();
void onDeleteSelectedItem();
void requestButtonsStatusUpdate();
void updateButtonsStatus();
private:
BreakpointsTreeWidget* m_pBreakpointTreeWidget;
QToolButton* m_pDeleteBreakpointTB;
QToolButton* m_pDeleteAllBreakpointsTB;
QToolButton* m_pDisableEnableAllBreakpointsTB;
QToolButton* m_pGoToSourceCodeTB;
bool m_bPushButtonStateUpdateRequested;
bool m_bIgnoreBreakpointModification;
};
class WatchItem : public DebuggerTreeWidgetItem
{
public:
WatchItem(boost::shared_ptr<RBX::Scripting::DebuggerWatch> spWatch);
~WatchItem();
void updateWatchValue();
bool hasWatch(boost::shared_ptr<RBX::Scripting::DebuggerWatch> spWatch);
void columnModified(int column);
void deleteWatch();
virtual void setInvalid(bool state);
bool isInvalid() { return m_bIsInValid; }
private:
void onWatchPropertyChanged(const RBX::Reflection::PropertyDescriptor* pPropertyDescriptor);
void onScriptAncestryChanged(shared_ptr<RBX::Instance> newParent);
void removeChildren();
boost::shared_ptr<RBX::Scripting::DebuggerWatch> m_spWatch;
rbx::signals::scoped_connection m_cPropertyChangedConnection;
rbx::signals::scoped_connection m_cAncestoryChangedConnection;
bool m_bIgnoreWatchModification;
bool m_bIsInValid;
};
class WatchTreeWidget : public DebuggerTreeWidget
{
Q_OBJECT
public:
WatchTreeWidget(QWidget *pParent);
void addWatch(boost::shared_ptr<RBX::Scripting::DebuggerWatch> spWatch);
void removeWatch(boost::shared_ptr<RBX::Scripting::DebuggerWatch> spWatch);
void deleteSelectedWatch();
QTreeWidgetItem* itemFromIndex(QModelIndex index) const { return QTreeWidget::itemFromIndex(index); }
void clear();
public Q_SLOTS:
void updateWatchValues(bool enable);
private Q_SLOTS:
void copySelectedItem();
void addWatch();
void removeWatch();
void deleteAllWatches();
private:
virtual void contextMenuEvent(QContextMenuEvent* evt);
virtual void mousePressEvent(QMouseEvent *event);
WatchItem* findWatchItem(boost::shared_ptr<RBX::Scripting::DebuggerWatch> spWatch);
QString getFullName(QTreeWidgetItem *pItem);
void drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void addDummyWatchItem();
QMenu* m_pContextualMenu;
QAction* m_pCopyAction;
QAction* m_pAddWatchAction;
QAction* m_pDeleteWatchAction;
QAction* m_pDeleteAllWatchesAction;
bool m_bIgnoreWatchModification;
};
class WatchWidget : public QWidget
{
Q_OBJECT
public:
WatchWidget(QWidget *pParent=0);
void addWatch(shared_ptr<RBX::Scripting::DebuggerWatch> spWatch);
void removeWatch(shared_ptr<RBX::Scripting::DebuggerWatch> spWatch);
void updateWatchValues(bool enable = false);
void clear();
private Q_SLOTS:
void onDeleteSelectedItem();
private:
WatchTreeWidget* m_pWatchTreeWidget;
};
class DebuggerToolTipWidget: public QTreeWidget
{
Q_OBJECT
public:
DebuggerToolTipWidget(QWidget *pParent);
void showToolTip(const QPoint& pos, const QString &key, const RBX::Reflection::Variant &value);
void hideToolTip();
bool isKeyValid(const QString& key) { return key == m_Key; }
void showAt(const QPoint& pos);
private Q_SLOTS:
void computeToolTipSize();
void onTimeout();
private:
void mousePressEvent(QMouseEvent * evt);
void mouseMoveEvent(QMouseEvent * evt);
bool eventFilter(QObject *obj, QEvent *evt);
void wheelEvent(QWheelEvent * e);
int getDepth(QTreeWidgetItem *pItem);
int computeToolTipHeight(const QModelIndex &index);
QTimer* m_pExpansionTimer;
QTreeWidgetItem* m_pModifiedByMouse;
QString m_Key;
RBX::Reflection::Variant m_Value;
};
+122
View File
@@ -0,0 +1,122 @@
/**
* DocDockWidget.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "DocDockManager.h"
// Qt Headers
#include <QVBoxLayout>
#include <QToolBar>
// Roblox Studio Headers
#include "RobloxMainWindow.h"
#include "RobloxDocManager.h"
#include "StudioUtilities.h"
#include "DocDockWidget.h"
DocDockManager::DocDockManager(RobloxMainWindow& mainWindow)
: QObject(&mainWindow),
m_MainWindow(mainWindow)
{
}
DocDockManager::~DocDockManager()
{
RBXASSERT(m_Docs.isEmpty());
}
/**
* Adds a new dock widget for a doc.
*/
void DocDockManager::addDoc(IRobloxDoc& doc)
{
RBXASSERT(!m_Docs.contains(&doc));
RBXASSERT(doc.getViewer());
RBX::BaldPtr<DocDockWidget> dockWidget = new DocDockWidget(m_MainWindow,*this,doc);
m_Docs[&doc] = dockWidget;
}
/**
* Removes the dock widget for the doc but does not delete the doc.
*
* @return false if the doc is not handled by this manager.
*/
bool DocDockManager::removeDoc(IRobloxDoc& doc)
{
if ( !m_Docs.contains(&doc) )
return false;
RBX::BaldPtr<DocDockWidget> dockWidget = m_Docs[&doc];
dockWidget->cleanup();
dockWidget->deleteLater();
m_Docs.remove(&doc);
return true;
}
/**
* Changes the dock widget's window title.
*
* @return false if the doc is not handled by this manager.
*/
bool DocDockManager::renameDoc(IRobloxDoc& doc,const QString& text,const QString& tooltip)
{
if ( !m_Docs.contains(&doc) )
return false;
RBX::BaldPtr<DocDockWidget> dockWidget = m_Docs[&doc];
dockWidget->setTitle(text,tooltip);
return true;
}
/**
* Sets dock widget focus for the doc, causing the doc to be set current.
*
* @return false if the doc is not handled by this manager.
*/
bool DocDockManager::setCurrentDoc(IRobloxDoc& doc)
{
if ( !m_Docs.contains(&doc) )
return false;
RBX::BaldPtr<DocDockWidget> dockWidget = m_Docs[&doc];
dockWidget->setFocus();
dockWidget->raise();
return true;
}
/**
* Gets the current dock widget that has focus.
*
* @return doc if has focus, NULL if not found or does not have focus
*/
IRobloxDoc* DocDockManager::getCurrentDoc() const
{
RBXASSERT(QThread::currentThread() == QCoreApplication::instance()->thread());
tDocDockMap::const_iterator iter = m_Docs.begin();
while ( iter != m_Docs.end() )
{
if ( iter.value()->hasFocus() )
return iter.key();
++iter;
}
return NULL;
}
/**
* Initializes dragging a dock widget.
* This is called when a tab is detached, creating a dock widget. The dock widget
* is immediately set to be dragging the window.
* This function generates OS specific mouse events.
*/
void DocDockManager::startDrag(IRobloxDoc& doc)
{
RBXASSERT(m_Docs.contains(&doc));
RBX::BaldPtr<DocDockWidget> dockWidget = m_Docs[&doc];
dockWidget->startDragging();
}
+50
View File
@@ -0,0 +1,50 @@
/**
* DocDockWidget.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QDockWidget>
#include <QMap>
// Roblox Headers
#include "RBX/BaldPtr.h"
// Roblox Studio Headers
#include "IRobloxDoc.h"
class RobloxMainWindow;
class DocDockWidget;
class DocDockManager : public QObject
{
Q_OBJECT
public:
DocDockManager(RobloxMainWindow& mainWindow);
virtual ~DocDockManager();
void addDoc(IRobloxDoc& doc);
bool removeDoc(IRobloxDoc& doc);
bool renameDoc(IRobloxDoc& doc,const QString& text,const QString& tooltip);
bool setCurrentDoc(IRobloxDoc& doc);
IRobloxDoc* getCurrentDoc() const;
void startDrag(IRobloxDoc& doc);
Q_SIGNALS:
void attachTab(IRobloxDoc& doc);
private:
typedef QMap< RBX::BaldPtr<IRobloxDoc>,RBX::BaldPtr<DocDockWidget> > tDocDockMap;
RobloxMainWindow& m_MainWindow;
tDocDockMap m_Docs;
};
+333
View File
@@ -0,0 +1,333 @@
/**
* DocDockWidget.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "DocDockWidget.h"
// Qt Headers
#include <QSettings>
#include <QMouseEvent>
// Roblox Studio Headers
#include "RobloxMainWindow.h"
#include "DocDockManager.h"
#include "RobloxDocManager.h"
#include "StudioUtilities.h"
static const char* DocDockWidgetSetting = "DocDockWidget/";
DocDockWidget::DocDockWidget(
RobloxMainWindow& mainWindow,
DocDockManager& docManager,
IRobloxDoc& doc )
: QDockWidget(&mainWindow),
m_MainWindow(mainWindow),
m_DocManager(docManager),
m_Doc(doc),
m_DocType(doc.docType()),
m_IsMoving(false),
m_Viewer(doc.getViewer()),
m_IsSimulatingDrag(false)
{
RBXASSERT(m_Viewer);
setAttribute(Qt::WA_DeleteOnClose);
setWidget(m_Viewer);
setFloating(true);
setFocusPolicy(Qt::StrongFocus);
setTitle(m_Doc.displayName(),m_Doc.fileName());
setMinimumSize(m_Viewer->minimumSize());
setMaximumSize(m_Viewer->maximumSize());
setSizePolicy(QSizePolicy::Maximum,QSizePolicy::Maximum);
connect(this,SIGNAL(topLevelChanged(bool)),this,SLOT(onTopLevelChanged(bool)));
connect(this,SIGNAL(dockLocationChanged(Qt::DockWidgetArea)),this,SLOT(onDockLocationChanged(Qt::DockWidgetArea)));
// restore geometry for this type of doc
const QSettings settings;
const QString settingName = DocDockWidgetSetting + QString::number(m_DocType) + "/geometry";
restoreGeometry(settings.value(settingName).toByteArray());
connect(qApp,SIGNAL(focusChanged(QWidget*,QWidget*)),this,SLOT(onFocusChanged(QWidget*,QWidget*)));
show();
}
/**
* Saves geometry for the next time this type of doc is opened.
* Must call cleanup() before destruction.
*/
DocDockWidget::~DocDockWidget()
{
RBXASSERT(!widget());
QSettings settings;
const QString settingName = DocDockWidgetSetting + QString::number(m_DocType) + "/geometry";
settings.setValue(settingName,saveGeometry());
}
/**
* Cleans up the widget and makes sure it won't be deleted.
* Must be called before the destructor. We need to do an immediate clean up, we can't
* wait for the destructor to be called since we use deleteLater.
*/
void DocDockWidget::cleanup()
{
if ( !widget() )
return;
stopDragging();
setFloating(false);
// make sure we're not the ones deleting the viewer
setWidget(NULL);
QCoreApplication::removePostedEvents(m_Viewer);
m_Viewer->setParent(NULL);
m_Viewer->hide();
m_MainWindow.removeDockWidget(this);
}
/**
* Simulates a window dragging operation.
*/
void DocDockWidget::startDragging()
{
if ( !m_IsSimulatingDrag )
{
m_IsSimulatingDrag = true;
m_StartPos = QCursor::pos();
grabMouse();
simulateDrag();
}
}
/**
* Begins a simulated window drag.
* This forces the window to be moved as the cursor is moved until the left button is released.
*/
void DocDockWidget::simulateDrag()
{
if ( m_IsSimulatingDrag )
{
const QPoint& worldPos = QCursor::pos();
const QPoint offset(
width() / 2,
style()->pixelMetric(QStyle::PM_TitleBarHeight) / 2 );
move(worldPos - offset);
}
}
/**
* Changes the dock widget title when the doc names change.
* Updates the object name to the new title.
*/
void DocDockWidget::setTitle(const QString& displayName,const QString& fileName)
{
QString title = displayName;
if ( !fileName.isEmpty() )
title += " - " + fileName;
setWindowTitle(title);
setObjectName(title);
}
/**
* Callback for user clicking on the attach button.
* Attaches doc to the tab widget.
*/
void DocDockWidget::onAttachDoc()
{
RobloxDocManager::Instance().attachDoc(m_Doc);
}
/**
* Requests doc close so that it will be saved properly.
*/
void DocDockWidget::closeEvent(QCloseEvent* event)
{
RBXASSERT(widget());
if ( m_MainWindow.requestDocClose(m_Doc) )
QDockWidget::closeEvent(event);
else
event->ignore();
}
/**
* Callback when the window is being moved.
* This causes dragging to begin and the tab hover over location to be detected.
*/
void DocDockWidget::moveEvent(QMoveEvent* event)
{
QDockWidget::moveEvent(event);
// ignore move event if we're not floating
if ( !isFloating() )
return;
if ( !m_IsMoving )
{
m_IsMoving = true;
m_StartPos = QCursor::pos() + event->oldPos() - event->pos();
setMouseTracking(true);
}
else
{
// only hover over if we attempted to move the window a minimum distance
const QPoint moveDelta = QCursor::pos() - m_StartPos;
if ( moveDelta.manhattanLength() > QApplication::startDragDistance() )
RobloxDocManager::Instance().setDockHoverOverPos(QCursor::pos());
}
m_MainWindow.updateEmbeddedFindPosition();
}
/**
* Callback when the window is being resized.
* This causes dragging to abort.
*/
void DocDockWidget::resizeEvent(QResizeEvent* event)
{
QDockWidget::resizeEvent(event);
m_StartPos = QCursor::pos(); // ignore any dragging that might have happened
stopDragging();
}
/**
* Callback for the mouse moving during a drag.
* If we're simulating a drag, move the window.
*/
void DocDockWidget::mouseMoveEvent(QMouseEvent* event)
{
QDockWidget::mouseMoveEvent(event);
if ( m_IsSimulatingDrag )
simulateDrag();
else if ( event->button() != Qt::LeftButton )
stopDragging();
}
/**
* Ends the window drag operation.
* Attempts to attach the dock to the tab widget if over the tab widget.
*
* @return true if the doc was attached
*/
bool DocDockWidget::stopDragging()
{
bool attached = false;
if ( m_IsMoving || m_IsSimulatingDrag )
{
if ( m_IsSimulatingDrag )
{
releaseMouse();
m_IsSimulatingDrag = false;
}
setMouseTracking(false);
m_IsMoving = false;
// only attach if we attempted to move the window and didn't just click on it
const QPoint moveDelta = QCursor::pos() - m_StartPos;
QPoint clickPos;
if ( moveDelta.manhattanLength() > QApplication::startDragDistance() )
clickPos = QCursor::pos();
else
clickPos = QPoint(INT_MAX,INT_MAX); // don't want to attach it
attached = RobloxDocManager::Instance().attemptAttach(m_Doc,clickPos);
if ( !attached )
RobloxDocManager::Instance().setCurrentDoc(&m_Doc);
}
return attached;
}
/**
* Handles non-client mouse press events to begin window dragging and setting the
* current doc if the window is activated.
*/
bool DocDockWidget::event(QEvent* event)
{
bool result = QDockWidget::event(event);
if ( event->type() == QEvent::NonClientAreaMouseButtonRelease ||
event->type() == QEvent::MouseButtonRelease )
{
// Must catch mouse events here because dock widget doesn't give em to us in mouesReleaseEvent.
// On Mac, this is the only way to detect an end window drag event.
RBX::BaldPtr<QMouseEvent> mouseEvent = static_cast<QMouseEvent*>(event);
if ( mouseEvent->button() == Qt::LeftButton )
{
stopDragging();
result = true;
}
}
else if ( event->type() == QEvent::NonClientAreaMouseButtonPress ||
event->type() == QEvent::MouseButtonPress )
{
// Reset start pos in case this is just a click.
RBX::BaldPtr<QMouseEvent> mouseEvent = static_cast<QMouseEvent*>(event);
if ( mouseEvent->button() == Qt::LeftButton )
m_StartPos = QCursor::pos();
}
else if ( event->type() == QEvent::WindowActivate && widget() )
{
RobloxDocManager::Instance().setCurrentDoc(&m_Doc);
}
return result;
}
/**
* Detect changes in widget focus.
* Only if our new focus is an ancestor of the dockwidget do we need to set the current doc.
*/
void DocDockWidget::onFocusChanged(QWidget* oldWidget,QWidget* newWidget)
{
if ( isAncestorOf(newWidget) && widget() )
RobloxDocManager::Instance().setCurrentDoc(&m_Doc);
}
#ifdef Q_WS_WIN32
/**
* Windows event callback to handle the end of the window drag operation.
* This is the only way to detect when the mouse button has been released during a
* drag on Windows.
*/
bool DocDockWidget::winEvent(MSG* msg,long* result)
{
if ( msg->message == WM_EXITSIZEMOVE )
stopDragging();
return QDockWidget::winEvent(msg,result);
}
#endif
/**
* Callback when the dock is either docked or undocked (plugged or unplugged).
* Begin dragging when undocked.
* Stop dragging if it's been docked. This is just overkill.
*/
void DocDockWidget::onTopLevelChanged(bool TopLevel)
{
if ( TopLevel )
startDragging();
else
stopDragging();
}
/**
* Callback when the dock widget docks somewhere else.
* Stop dragging when it's been docked. This is just overkill.
*/
void DocDockWidget::onDockLocationChanged(Qt::DockWidgetArea area)
{
stopDragging();
}
+73
View File
@@ -0,0 +1,73 @@
/**
* DocDockWidget.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QDockWidget>
// Roblox Headers
#include "rbx/BaldPtr.h"
// Roblox Studio Headers
#include "IRobloxDoc.h"
class RobloxMainWindow;
class DocDockManager;
class DocDockWidget : public QDockWidget
{
Q_OBJECT
public:
DocDockWidget(
RobloxMainWindow& mainWindow,
DocDockManager& docManager,
IRobloxDoc& doc );
virtual ~DocDockWidget();
void setTitle(const QString& displayName,const QString& fileName);
void cleanup();
void startDragging();
public Q_SLOTS:
void onAttachDoc();
void onTopLevelChanged(bool TopLevel);
void onDockLocationChanged(Qt::DockWidgetArea area);
void onFocusChanged(QWidget* oldWidget,QWidget* newWidget);
protected:
virtual void closeEvent(QCloseEvent* event);
virtual void moveEvent(QMoveEvent* event);
virtual void resizeEvent(QResizeEvent* event);
virtual void mouseMoveEvent(QMouseEvent* event);
virtual bool event(QEvent* event);
virtual QSize sizeHint() const { return maximumSize(); }
#ifdef Q_WS_WIN32
virtual bool winEvent(MSG* msg,long* result);
#endif
private:
void simulateDrag();
bool stopDragging();
RobloxMainWindow& m_MainWindow;
DocDockManager& m_DocManager;
IRobloxDoc& m_Doc;
IRobloxDoc::RBXDocType m_DocType;
RBX::BaldPtr<QWidget> m_Viewer;
bool m_IsMoving;
bool m_IsSimulatingDrag;
QPoint m_StartPos;
};
+513
View File
@@ -0,0 +1,513 @@
/**
* DocTabWidget.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "DocTabManager.h"
// Qt Headers
#include <QToolButton>
#include <QMouseEvent>
#include <QResizeEvent>
// Roblox Studio Headers
#include "RobloxMainWindow.h"
#include "RobloxDocManager.h"
#include "StudioUtilities.h"
#include "RobloxTabWidget.h"
#include "RobloxIDEDoc.h"
static const int ValidAttachRegionHeight = 256;
DocTabManager::DocTabManager(RobloxMainWindow& mainWindow)
: QObject(&mainWindow),
m_RubberBand(QRubberBand::Rectangle),
m_MainWindow(mainWindow),
m_MouseDown(false)
{
m_TabWidget = new RobloxTabWidget(&m_MainWindow);
m_RubberBand.setParent(m_TabWidget);
m_RubberBand.move(0,0);
// create some buttons in the upper right
RBX::BaldPtr<QWidget> cornerWidget = new QWidget(m_TabWidget);
RBX::BaldPtr<QLayout> layout = new QHBoxLayout(cornerWidget);
layout->setContentsMargins(0,0,0,0);
layout->setSpacing(0);
cornerWidget->setLayout(layout);
cornerWidget->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum);
m_TabWidget->setCornerWidget(cornerWidget);
m_TabWidget->setTabsClosable(true);
m_TabWidget->setMovable(false); // do not enable - causing redraw problems when detaching
m_TabWidget->setTabShape(QTabWidget::Rounded);
m_TabWidget->setDocumentMode(true);
m_TabWidget->installEventFilter(this);
m_TabWidget->getTabBar().setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab);
m_TabWidget->getTabBar().installEventFilter(this);
connect(m_TabWidget,SIGNAL(currentChanged(int)),this,SLOT(onCurrentChanged(int)));
connect(m_TabWidget,SIGNAL(tabCloseRequested(int)),this,SLOT(onTabCloseRequested(int)));
m_FakeTab = new QWidget(m_TabWidget);
m_FakeTab->hide();
connect(qApp,SIGNAL(focusChanged(QWidget*,QWidget*)),this,SLOT(onFocusChanged(QWidget*,QWidget*)));
m_MainWindow.setCentralWidget(m_TabWidget);
#ifdef Q_WS_MAC
setupTabPageSwitchingShortcuts();
#endif
}
DocTabManager::~DocTabManager()
{
RBXASSERT(m_Docs.isEmpty());
}
/**
* Adds a new tab for the doc.
*/
void DocTabManager::addDoc(IRobloxDoc& doc, int insertAtIndex)
{
RBXASSERT(m_Docs.indexOf(&doc) == -1);
RBXASSERT(doc.getViewer());
RBX::BaldPtr<QWidget> widget = doc.getViewer();
RBXASSERT(widget);
if (insertAtIndex != -1)
{
m_Docs.insert(insertAtIndex, &doc);
}
else
{
m_Docs.append(&doc);
}
m_TabWidget->setUpdatesEnabled(false);
const int index = m_TabWidget->insertTab(insertAtIndex,widget,doc.titleIcon(),doc.displayName());
RBXASSERT((index != -1) && (index < m_Docs.size()));
m_TabWidget->setTabToolTip(index, !doc.fileName().isEmpty() ? doc.fileName() : doc.displayName());
m_TabWidget->setCurrentWidget(widget);
m_TabWidget->setUpdatesEnabled(true);
}
/**
* Removes the tab for the doc but does not delete the doc.
*
* @return false if the doc is not handled by this manager.
*/
bool DocTabManager::removeDoc(IRobloxDoc& doc)
{
const int index = m_Docs.indexOf(&doc);
if ( index == -1 )
return false;
m_Docs.remove(index);
m_TabWidget->removeTab(index);
return true;
}
/**
* Changes the tab's window title.
*
* @return false if the doc is not handled by this manager.
*/
bool DocTabManager::renameDoc(IRobloxDoc& doc,const QString& text,const QString& tooltip,const QIcon& icon)
{
const int index = m_Docs.indexOf(&doc);
if ( index == -1 )
return false;
m_TabWidget->setTabText(index,text);
m_TabWidget->setTabToolTip(index,tooltip);
m_TabWidget->setTabIcon(index, icon);
return true;
}
/**
* Sets the current tab index, causing the doc to be set current.
*
* @return false if the doc is not handled by this manager.
*/
bool DocTabManager::setCurrentDoc(IRobloxDoc& doc)
{
const int index = m_Docs.indexOf(&doc);
if ( index == -1 )
return false;
m_TabWidget->setCurrentIndex(index);
return true;
}
/**
* Gets the current selected tab.
*
* @return doc if tab is selected, NULL if no tab is selected or doc not found
*/
IRobloxDoc* DocTabManager::getCurrentDoc() const
{
const int index = m_TabWidget->currentIndex();
if ( index == -1 )
return NULL;
if ( m_FakeTab->isVisible() )
return NULL;
return m_Docs[index];
}
/**
* Callback for the current tab selected changing.
* If the index is valid, the doc is set current.
*/
void DocTabManager::onCurrentChanged(int index)
{
if ( index == -1 )
{
RobloxDocManager::Instance().setCurrentDoc(NULL);
}
else if ( !m_FakeTab->isVisible() && index < m_Docs.size())
{
RBX::BaldPtr<IRobloxDoc> doc = m_Docs[index];
RobloxDocManager::Instance().setCurrentDoc(doc);
}
}
/**
* Callback for user clicking on the close button for the tab.
* Requests the doc to be closed.
*/
bool DocTabManager::onTabCloseRequested(int index)
{
// it's possible to have already removed the doc but not the tab yet
if ( index == -1 )
return false;
RBX::BaldPtr<IRobloxDoc> doc = m_Docs[index];
if (doc->docType() == IRobloxDoc::IDE)
{
RobloxIDEDoc* ideDoc = static_cast<RobloxIDEDoc*>(doc.get());
ideDoc->setReopenLastSavedPlace(false);
}
bool hasClosed = m_MainWindow.requestDocClose(*doc);
if (!hasClosed)
{
if (doc->docType() == IRobloxDoc::IDE)
{
RobloxIDEDoc* ideDoc = static_cast<RobloxIDEDoc*>(doc.get());
ideDoc->setReopenLastSavedPlace(true);
}
}
return hasClosed;
}
/**
* Callback if the user clicks on the close button in the corner widget.
* Requests doc to be closed.
*/
void DocTabManager::onCurrentTabCloseRequested()
{
onTabCloseRequested(m_TabWidget->currentIndex());
}
/**
* Callback for user clicking the detach button.
* Detaches tab from doc and creates a dock widget.
*/
void DocTabManager::onDetachDoc()
{
RBX::BaldPtr<IRobloxDoc> doc = getCurrentDoc();
if ( doc )
RobloxDocManager::Instance().detachDoc(*doc,false);
}
/**
* Event filter for the tab bar.
* Handles mouse events for dragging the tab.
* Handles resizing the tab widget to update the rubber band size.
*/
bool DocTabManager::eventFilter(QObject* object,QEvent* event)
{
bool handled = false;
if ( object == m_TabWidget && event->type() == QEvent::Resize )
{
handled = m_TabWidget->eventFilter(object,event);
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(event);
m_RubberBand.resize(resizeEvent->size());
}
else if ( object == &m_TabWidget->getTabBar() )
{
handled = m_TabWidget->eventFilter(object,event);
switch ( event->type() )
{
case QEvent::MouseButtonPress:
onMousePressEvent(static_cast<QMouseEvent*>(event));
break;
case QEvent::MouseMove:
onMouseMoveEvent(static_cast<QMouseEvent*>(event));
break;
case QEvent::MouseButtonRelease:
onMouseReleaseEvent(static_cast<QMouseEvent*>(event));
break;
default:
break;
}
}
else
{
handled = QObject::eventFilter(object,event);
}
return handled;
}
/**
* Callback for mouse press event from the tab bar.
* Begins dragging if the left button is down.
*/
void DocTabManager::onMousePressEvent(QMouseEvent* event)
{
const int index = m_TabWidget->getTabBar().tabAt(event->pos());
if ( index != -1 && event->button() == Qt::LeftButton )
{
m_DragTabIndex = index;
m_MouseDown = true;
m_ClickPos = event->pos();
}
m_TabWidget->setFocus();
}
/**
* Callback for the mouse movement event from the tab bar.
* If the mouse button is down and the vertical distance is far enough, the tab is detached.
*/
void DocTabManager::onMouseMoveEvent(QMouseEvent* event)
{
const QPoint deltaPos = event->pos() - m_ClickPos;
// Make the drag distance longer than typical because Doc tabs are easy to mistakenly
// tear off.
const int dragDistanceFudge = 32;
if ( m_MouseDown && deltaPos.manhattanLength() > QApplication::startDragDistance() + dragDistanceFudge)
{
RobloxDocManager::Instance().detachDoc(*m_Docs[m_DragTabIndex],true);
m_MouseDown = false;
}
}
/**
* Callback for the mouse button release from the tab bar.
* Resets the mouse down flag if left button is released.
*/
void DocTabManager::onMouseReleaseEvent(QMouseEvent* event)
{
if ( event->button() == Qt::LeftButton )
m_MouseDown = false;
}
/**
* Sets the current state of the mouse hover over.
* When a dock widget is hovered over the tab widget while dragging,
* the tab widget shows if the dock can be reattached.
*/
void DocTabManager::setDockHoverOverPos(const QPoint& globalPos)
{
if ( isValidHoverOver(globalPos) )
{
m_RubberBand.setVisible(true);
m_RubberBand.resize(m_TabWidget->size());
// add tab if it's not visible yet
if ( !m_FakeTab->isVisible() )
m_TabWidget->addTab(m_FakeTab,"New Tab");
// get faketab index
int fakeTabIndex = m_TabWidget->indexOf(m_FakeTab);
if (fakeTabIndex == -1)
return;
QPoint localPointInTabWidget(m_TabWidget->getTabBar().mapFromGlobal(globalPos).x(), m_TabWidget->getTabBar().height()/2);
int moveOnTabIndex = m_TabWidget->getTabBar().tabAt(localPointInTabWidget);
// check if cursor is on some other tab page
if (moveOnTabIndex != -1 && (moveOnTabIndex != fakeTabIndex))
{
QRect moveOnTabRect = m_TabWidget->getTabBar().tabRect(moveOnTabIndex);
moveOnTabRect.setWidth(m_TabWidget->getTabBar().tabRect(fakeTabIndex).width());
// make sure after moving tabs cursor will remain inside the fake tab or else we will have flicker
if (moveOnTabRect.contains(localPointInTabWidget, true))
{
bool enabled = m_TabWidget->updatesEnabled();
m_TabWidget->getTabBar().setUpdatesEnabled(false);
m_TabWidget->getTabBar().moveTab(fakeTabIndex, moveOnTabIndex);
// correct index to set as current
fakeTabIndex = moveOnTabIndex;
// while moving tabs, fake tab some how gets hidden (?) so again remove and re-insert
m_TabWidget->removeTab(fakeTabIndex);
m_TabWidget->insertTab(fakeTabIndex, m_FakeTab, "New Tab");
m_TabWidget->getTabBar().setUpdatesEnabled(enabled);
}
}
// set current index
m_TabWidget->setCurrentIndex(fakeTabIndex);
}
else
{
m_RubberBand.setVisible(false);
if ( m_FakeTab->isVisible() )
{
RBXASSERT(m_TabWidget->indexOf(m_FakeTab) != -1);
m_TabWidget->removeTab(m_TabWidget->indexOf(m_FakeTab));
}
}
}
/**
* Attempts to attach a dock widget.
* If the mouse position is inside the region allowable for reattachment,
* the doc is reattached to the tab bar.
*
* @param doc doc to attach
* @param worldPos position the mouse button was released
*/
bool DocTabManager::attemptAttach(IRobloxDoc& doc,const QPoint& globalPos)
{
m_RubberBand.setVisible(false);
int fakeTabIndex = -1;
if ( m_FakeTab->isVisible() )
{
fakeTabIndex = m_TabWidget->indexOf(m_FakeTab);
m_TabWidget->removeTab(fakeTabIndex);
}
if ( isValidHoverOver(globalPos) )
{
RobloxDocManager::Instance().attachDoc(doc, fakeTabIndex);
return true;
}
else
{
return false;
}
}
/**
* Checks if a location is valid for attaching a dock to a tab.
* Only the upper left corner is valid.
*/
bool DocTabManager::isValidHoverOver(const QPoint& globalPos)
{
const QPoint mainLocalPos = m_MainWindow.mapFromGlobal(globalPos);
const QWidget* child = m_MainWindow.childAt(mainLocalPos);
// check if we're visible first
if ( child && (child == m_TabWidget || m_TabWidget->isAncestorOf(child)) )
{
// check if the location is inside the valid region
QRect worldRect(m_MainWindow.mapToGlobal(m_TabWidget->pos()),m_TabWidget->size() / 2);
const int validHeight = qMin(ValidAttachRegionHeight,m_TabWidget->height());
worldRect.setHeight(validHeight);
return worldRect.contains(globalPos);
}
else
return false;
}
/**
* Detect changes in widget focus.
* Only if our new focus is an ancestor of the tab manager do we set the current tab
* as the current doc.
*/
void DocTabManager::onFocusChanged(QWidget* oldWidget,QWidget* newWidget)
{
if ( m_TabWidget->isAncestorOf(newWidget) &&
m_TabWidget->currentIndex() != -1 &&
m_TabWidget->currentIndex() < m_Docs.size() )
{
RBX::BaldPtr<IRobloxDoc> doc = m_Docs[m_TabWidget->currentIndex()];
RobloxDocManager::Instance().setCurrentDoc(doc);
}
}
/**
* Resets the central widget for the main window back to the tab manager.
*/
void DocTabManager::restoreAsCentralWidget()
{
if ( m_MainWindow.centralWidget() != m_TabWidget )
{
QWidget* oldCentralWidget = m_MainWindow.centralWidget();
m_MainWindow.setCentralWidget(m_TabWidget);
QApplication::removePostedEvents(oldCentralWidget); // don't allow to be deleted
m_TabWidget->show();
IRobloxDoc* playDoc = RobloxDocManager::Instance().getPlayDoc();
RBXASSERT(playDoc);
addDoc(*playDoc);
}
}
/**
* Sets up tab page switching shortcuts on Mac (standard shortcut Ctrl+Tab is not supported in Qt on Mac)
* Supported standard shortcuts on Mac:
* - Cmd + Shift + Arrow Key
* - Cmd + { or }
*/
void DocTabManager::setupTabPageSwitchingShortcuts()
{
QList<QKeySequence> shortcuts;
QAction* nextTabAction = new QAction(&m_MainWindow);
shortcuts.append(QKeySequence::NextChild);
shortcuts.append(QKeySequence(Qt::CTRL+Qt::ShiftModifier+Qt::Key_Right));
nextTabAction->setShortcuts(shortcuts);
connect(nextTabAction, SIGNAL(triggered()), this, SLOT(showNextTabPage()));
shortcuts.clear();
QAction* prevTabAction = new QAction(&m_MainWindow);
shortcuts.append(QKeySequence::PreviousChild);
shortcuts.append(QKeySequence(Qt::CTRL+Qt::ShiftModifier+Qt::Key_Left));
prevTabAction->setShortcuts(shortcuts);
connect(prevTabAction, SIGNAL(triggered()), this, SLOT(showPreviousTabPage()));
m_MainWindow.addAction(nextTabAction);
m_MainWindow.addAction(prevTabAction);
}
void DocTabManager::showNextTabPage()
{
if (m_TabWidget->count() > 0)
setCurrentTabPage(m_TabWidget->currentIndex()+1);
}
void DocTabManager::showPreviousTabPage()
{
if (m_TabWidget->count() > 0)
setCurrentTabPage(m_TabWidget->currentIndex()-1);
}
void DocTabManager::setCurrentTabPage(int index)
{
if (index > (m_TabWidget->count() - 1))
index = 0;
else if (index < 0)
index = m_TabWidget->count() - 1;
m_TabWidget->setCurrentIndex(index);
m_MainWindow.setFocus();
}
+81
View File
@@ -0,0 +1,81 @@
/**
* DocTabWidget.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QRubberBand>
#include <QIcon>
// Roblox Headers
#include "RBX/BaldPtr.h"
// Roblox Studio Headers
#include "IRobloxDoc.h"
class QRubberBand;
class RobloxMainWindow;
class RobloxTabWidget;
class DocTabManager : public QObject
{
Q_OBJECT
public:
DocTabManager(RobloxMainWindow& mainWindow);
virtual ~DocTabManager();
void addDoc(IRobloxDoc& doc, int index = -1);
bool removeDoc(IRobloxDoc& doc);
bool renameDoc(IRobloxDoc& doc,const QString& text,const QString& tooltip,const QIcon& icon = QIcon());
bool setCurrentDoc(IRobloxDoc& doc);
IRobloxDoc* getCurrentDoc() const;
void setDockHoverOverPos(const QPoint& globalPos);
bool attemptAttach(IRobloxDoc& doc,const QPoint& globalPos);
void restoreAsCentralWidget();
public Q_SLOTS:
void showNextTabPage();
void showPreviousTabPage();
protected:
virtual bool eventFilter(QObject* object,QEvent* event);
private Q_SLOTS:
void onCurrentChanged(int index);
bool onTabCloseRequested(int index);
void onCurrentTabCloseRequested();
void onDetachDoc();
void onFocusChanged(QWidget* oldWidget,QWidget* newWidget);
private:
void onMousePressEvent(QMouseEvent* event);
void onMouseMoveEvent(QMouseEvent* event);
void onMouseReleaseEvent(QMouseEvent* event);
bool isValidHoverOver(const QPoint& globalPos);
void setupTabPageSwitchingShortcuts();
void setCurrentTabPage(int index);
typedef QVector< RBX::BaldPtr<IRobloxDoc> > tDocList;
tDocList m_Docs;
RobloxMainWindow& m_MainWindow;
QPoint m_ClickPos;
bool m_MouseDown;
int m_DragTabIndex;
QRubberBand m_RubberBand;
RBX::BaldPtr<QWidget> m_FakeTab;
RBX::BaldPtr<RobloxTabWidget> m_TabWidget;
};
+29
View File
@@ -0,0 +1,29 @@
/**
* ExternalHandlers.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "ExternalHandlers.h"
// Qt Headers
#include <QUrl>
// Roblox Studio Headers
#include "FastLog.h"
#include "RobloxSettings.h"
#include "StudioUtilities.h"
TeleportHandler::TeleportHandler(const QString &handlerId, const QString &url, const QString& ticket, const QString &teleportScript)
: m_handlerId(handlerId)
, m_url(url)
, m_ticket(ticket)
, m_teleportScript(teleportScript)
{
}
bool TeleportHandler::handle()
{
return true;
}
+37
View File
@@ -0,0 +1,37 @@
/**
* ExternalHandlers.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QString>
// 3rd Party Headers
#include "boost/shared_ptr.hpp"
// Roblox Studio Headers
#include "IExternalHandler.h"
namespace RBX {
class DataModel;
}
class TeleportHandler : public IExternalHandler
{
public:
TeleportHandler(const QString &handlerId, const QString &url, const QString& ticket, const QString &teleportScript);
QString className() { return "TeleportHandler"; }
QString handlerId() { return m_handlerId; }
void setDataModel(boost::shared_ptr<RBX::DataModel> pDataModel) { m_pDataModel = pDataModel; }
bool handle();
private:
boost::shared_ptr<RBX::DataModel> m_pDataModel;
QString m_handlerId;
QString m_url;
QString m_ticket;
QString m_teleportScript;
};
+954
View File
@@ -0,0 +1,954 @@
/**
* FindDialog.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "FindDialog.h"
// Qt Headers
#include <QLineEdit>
#include <QRadioButton>
#include <QCheckBox>
#include <QTextDocument>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QKeyEvent>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QGroupBox>
#include <QTextBlock>
// Roblox Studio Headers
#include "RobloxMainWindow.h"
#include "UpdateUIManager.h"
#include "ScriptTextEditor.h"
#include "RobloxFindWidget.h"
#include "RobloxDocManager.h"
#include "RobloxScriptDoc.h"
FASTFLAG(StudioEmbeddedFindDialogEnabled)
BasicFindDialog::BasicFindDialog(QWidget *parent)
: QDialog(parent,Qt::WindowTitleHint | Qt::WindowSystemMenuHint)
, m_pFindListener(0)
, m_pFindEdit(0)
, m_pFindNextButton(0)
, m_pFindPreviousButton(0)
, m_pCloseButton(0)
, m_pDownRadioButton(0)
, m_pCaseCheckBox(0)
, m_pWholeCheckBox(0)
, m_pRegExCheckBox(0)
{
setWindowFlags(Qt::WindowStaysOnTopHint | windowFlags());
}
BasicFindDialog::~BasicFindDialog()
{
m_pFindListener = NULL;
}
void BasicFindDialog::setFindListener(IFindListener* listener)
{
m_pFindListener = listener;
}
void BasicFindDialog::setFocus()
{
QDialog::setFocus();
if(isActiveWindow())
{
m_pFindEdit->setFocus();
if(m_pFindEdit->text().size() > 0)
{
m_pFindEdit->setCursorPosition(m_pFindEdit->text().size());
m_pFindEdit->selectAll();
}
}
}
void BasicFindDialog::setVisible(bool visible)
{
QDialog::setVisible(visible);
if ( visible )
{
if ( m_pFindListener )
{
if ( m_pFindListener->hasSelection() )
m_pFindEdit->setText(m_pFindListener->getSelectedText());
else
m_pFindEdit->setText(FindReplaceProvider::instance().getSearchStr());
}
m_pFindEdit->setFocus();
}
}
bool BasicFindDialog::isFindNextEnabled()
{
return (m_pFindEdit->text().size() > 0);
}
void BasicFindDialog::textToFindChanged()
{
bool bEnable = m_pFindEdit->text().size() > 0;
m_pFindNextButton->setEnabled(bEnable);
m_pFindPreviousButton->setEnabled(bEnable);
UpdateUIManager::Instance().getMainWindow().findNextAction->setEnabled(bEnable);
UpdateUIManager::Instance().getMainWindow().findPreviousAction->setEnabled(bEnable);
}
bool BasicFindDialog::getSearchParameters(bool forward,QRegExp& outRegExp,QTextDocument::FindFlags& outflags)
{
QString toSearch = m_pFindEdit->text();
FindReplaceProvider::instance().setSearchStr(toSearch);
if ( toSearch.isEmpty() )
return false;
outflags = 0;
bool down = m_pDownRadioButton->isChecked();
if( (!down && forward) || (down && !forward) )
outflags |= QTextDocument::FindBackward;
if(m_pCaseCheckBox->isChecked())
outflags |= QTextDocument::FindCaseSensitively;
if(m_pWholeCheckBox->isChecked())
outflags |= QTextDocument::FindWholeWords;
outRegExp.setPattern(toSearch);
if ( !m_pRegExCheckBox->isChecked() )
outRegExp.setPatternSyntax(QRegExp::FixedString);
outRegExp.setCaseSensitivity(m_pCaseCheckBox->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive);
if ( !outRegExp.isValid() )
return false;
return true;
}
bool BasicFindDialog::find(bool forward,bool loop)
{
QRegExp regExp;
QTextDocument::FindFlags flags;
if ( !getSearchParameters(forward,regExp,flags) )
return false;
if ( m_pFindListener )
return m_pFindListener->find(loop,regExp,flags);
else
return false;
}
void BasicFindDialog::keyPressEvent(QKeyEvent* evt)
{
QAction* previous = UpdateUIManager::Instance().getMainWindow().findPreviousAction;
if ( evt->matches(QKeySequence::FindPrevious) )
{
previous->trigger();
evt->accept();
return;
}
QAction* next = UpdateUIManager::Instance().getMainWindow().findNextAction;
if ( evt->matches(QKeySequence::FindNext) )
{
next->trigger();
evt->accept();
return;
}
QDialog::keyPressEvent(evt);
}
FindAllDialog::FindAllDialog(QWidget *parent)
: BasicFindDialog(parent),
m_pLookInComboBox(NULL)
{
initDialog();
}
void FindAllDialog::initDialog()
{
QLabel *findLabel = new QLabel("Find what: ");
m_pFindEdit = new QLineEdit;
QLabel *lookInLabel = new QLabel("Look in: ");
m_pFindAllButton = new QPushButton("Find All");
m_pFindAllButton->setEnabled(false);
m_pCloseButton = new QPushButton("Close");
//Find Options
m_pFindOptions = new QGroupBox("Find Options");
m_pFindOptions->setCheckable(true);
m_pFindOptions->setFlat(true);
m_pCaseCheckBox = new QCheckBox("Match case");
m_pWholeCheckBox = new QCheckBox("Match whole word only");
m_pRegExCheckBox = new QCheckBox("Regular expression");
QVBoxLayout *findOptionsLayout = new QVBoxLayout();
findOptionsLayout->addWidget(m_pCaseCheckBox);
findOptionsLayout->addWidget(m_pWholeCheckBox);
findOptionsLayout->addWidget(m_pRegExCheckBox);
m_pFindOptions->setLayout(findOptionsLayout);
m_pFindLayout = new QGridLayout();
m_pFindLayout->addWidget(findLabel, 0, 0);
m_pFindLayout->addWidget(m_pFindEdit, 1, 0);
m_pFindLayout->addWidget(lookInLabel, 4, 0);
QVBoxLayout *firstColLayout = new QVBoxLayout();
firstColLayout->addLayout(m_pFindLayout);
firstColLayout->addWidget(m_pFindOptions);
QVBoxLayout *buttonsLayout = new QVBoxLayout();
buttonsLayout->addWidget(m_pFindAllButton);
buttonsLayout->addWidget(m_pCloseButton);
QGridLayout *mainLayout = new QGridLayout();
mainLayout->addLayout(firstColLayout, 0, 0);
mainLayout->addLayout(buttonsLayout, 0, 1, Qt::AlignTop);
setLayout(mainLayout);
setWindowTitle("Find in all scripts");
setMaximumHeight(minimumHeight());
setMaximumWidth(minimumWidth());
connect(m_pFindAllButton, SIGNAL(clicked()), this, SLOT(findAll()));
connect(m_pFindEdit, SIGNAL(textChanged(QString)), this, SLOT(textToFindChanged()));
connect(m_pCloseButton, SIGNAL(clicked()), this, SLOT(close()));
connect(m_pFindOptions, SIGNAL(toggled(bool)), this , SLOT(onFindOptionsToggled(bool)));
}
void FindAllDialog::showEvent(QShowEvent * event)
{
// This cludgy code is added to fix a QT bug on Mac. Combobox should be
// initialized when its parent already created. Otherwise it displays a weird
// frame around drowpdown list.
m_pLookInComboBox = new QComboBox(this);
m_pLookInComboBox->addItem("All Scripts");
m_pLookInComboBox->addItem("Current Script");
m_pFindLayout->addWidget(m_pLookInComboBox, 5, 0);
BasicFindDialog::showEvent(event);
}
RobloxFindWidget::eFindFlags FindAllDialog::getCurrentFlags()
{
RobloxFindWidget::eFindFlags flagSetter = RobloxFindWidget::NO_FLAGS;
if (!m_pFindOptions->isChecked())
return flagSetter;
if (m_pCaseCheckBox->isChecked())
flagSetter = flagSetter | RobloxFindWidget::MATCH_CASE;
if (m_pWholeCheckBox->isChecked())
flagSetter = flagSetter | RobloxFindWidget::MATCH_WORD;
if (m_pRegExCheckBox->isChecked())
flagSetter = flagSetter | RobloxFindWidget::REG_EXP;
return flagSetter;
}
void FindAllDialog::findAll()
{
FindReplaceProvider::instance().setSearchStr(m_pFindEdit->text());
RobloxFindWidget::singleton().findAll(m_pFindEdit->text(), (m_pLookInComboBox->currentIndex() == 0), getCurrentFlags());
close();
}
void FindAllDialog::textToFindChanged()
{
m_pFindAllButton->setEnabled(m_pFindEdit->text().size() > 0);
}
void FindAllDialog::onFindOptionsToggled(bool on)
{
m_pFindOptions->setMinimumWidth(m_pFindOptions->width());
m_pCaseCheckBox->setVisible(on);
m_pWholeCheckBox->setVisible(on);
m_pRegExCheckBox->setVisible(on);
//Need to adjust size but children have not resized yet
QMetaObject::invokeMethod(this, "adjustSize", Qt::QueuedConnection);
}
void FindAllDialog::setFindAllRunning(bool value)
{
if (value)
{
m_pFindAllButton->setText("Stop");
disconnect(m_pFindAllButton, SIGNAL(clicked()), this, SLOT(findAll()));
connect(m_pFindAllButton, SIGNAL(clicked()), &RobloxFindWidget::singleton(), SLOT(stopPopulatingItems()));
}
else
{
m_pFindAllButton->setText("Find All");
disconnect(m_pFindAllButton, SIGNAL(clicked()), &RobloxFindWidget::singleton(), SLOT(stopPopulatingItems()));
connect(m_pFindAllButton, SIGNAL(clicked()), this, SLOT(findAll()));
}
}
FindDialog::FindDialog(QWidget *parent)
: BasicFindDialog(parent)
{
initDialog();
}
void FindDialog::initDialog()
{
QLabel *findLabel = new QLabel("Find what: ");
m_pFindEdit = new QLineEdit;
m_pFindNextButton = new QPushButton("Find Next");
m_pFindNextButton->setEnabled(false);
m_pFindPreviousButton = new QPushButton("Find Previous");
m_pFindPreviousButton->setEnabled(false);
m_pCloseButton = new QPushButton("Close");
m_pCaseCheckBox = new QCheckBox("Match case");
m_pWholeCheckBox = new QCheckBox("Match whole word only");
m_pRegExCheckBox = new QCheckBox("Regular expression");
QGroupBox *directionBox = new QGroupBox("Direction");
QRadioButton *upRadioButton = new QRadioButton("Up");
m_pDownRadioButton = new QRadioButton("Down");
QHBoxLayout *directionLayout = new QHBoxLayout();
directionLayout->addWidget(upRadioButton);
directionLayout->addWidget(m_pDownRadioButton);
directionBox->setLayout(directionLayout);
m_pDownRadioButton->setChecked(true);
QGridLayout *findLayout = new QGridLayout();
findLayout->addWidget(findLabel, 0, 0);
findLayout->addWidget(m_pFindEdit, 0, 1);
QVBoxLayout *conditionsLayout = new QVBoxLayout();
conditionsLayout->addWidget(m_pWholeCheckBox);
conditionsLayout->addWidget(m_pCaseCheckBox);
conditionsLayout->addWidget(m_pRegExCheckBox);
QHBoxLayout *secondRowLayout = new QHBoxLayout();
secondRowLayout->addLayout(conditionsLayout);
secondRowLayout->addWidget(directionBox);
QVBoxLayout *firstColLayout = new QVBoxLayout();
firstColLayout->addLayout(findLayout);
firstColLayout->addLayout(secondRowLayout);
QVBoxLayout *buttonsLayout = new QVBoxLayout();
buttonsLayout->addWidget(m_pFindNextButton);
buttonsLayout->addWidget(m_pFindPreviousButton);
buttonsLayout->addWidget(m_pCloseButton);
QGridLayout *mainLayout = new QGridLayout();
mainLayout->addLayout(firstColLayout, 0, 0);
mainLayout->addLayout(buttonsLayout, 0, 1, Qt::AlignTop);
setLayout(mainLayout);
setWindowTitle("Find");
connect(m_pFindEdit, SIGNAL(textChanged(QString)), this, SLOT(textToFindChanged()));
connect(m_pFindNextButton, SIGNAL(clicked()), this, SLOT(findNext()));
connect(m_pFindPreviousButton, SIGNAL(clicked()), this, SLOT(findPrevious()));
connect(m_pCloseButton, SIGNAL(clicked()), this, SLOT(close()));
}
ReplaceDialog::ReplaceDialog(QWidget *parent)
: BasicFindDialog(parent)
{
initDialog();
}
void ReplaceDialog::initDialog()
{
QLabel *findLabel = new QLabel("Find what: ");
m_pFindEdit = new QLineEdit;
QLabel *replaceLabel = new QLabel("Replace with: ");
m_pReplaceEdit = new QLineEdit;
m_pFindNextButton = new QPushButton("Find Next");
m_pFindNextButton->setEnabled(false);
m_pFindPreviousButton = new QPushButton("Find Previous");
m_pFindPreviousButton->setEnabled(false);
m_pReplaceButton = new QPushButton("Replace");
m_pReplaceButton->setEnabled(false);
m_pReplaceAllButton = new QPushButton("Replace All");
m_pReplaceAllButton->setEnabled(false);
m_pCloseButton = new QPushButton("Close");
m_pCaseCheckBox = new QCheckBox("Match case");
m_pWholeCheckBox = new QCheckBox("Match whole word only");
m_pRegExCheckBox = new QCheckBox("Regular expression");
QGroupBox *directionBox = new QGroupBox("Direction");
QRadioButton *upRadioButton = new QRadioButton("Up");
m_pDownRadioButton = new QRadioButton("Down");
QHBoxLayout *directionLayout = new QHBoxLayout();
directionLayout->addWidget(upRadioButton);
directionLayout->addWidget(m_pDownRadioButton);
directionBox->setLayout(directionLayout);
m_pDownRadioButton->setChecked(true);
QGridLayout *findLayout = new QGridLayout();
findLayout->addWidget(findLabel, 0, 0);
findLayout->addWidget(m_pFindEdit, 0, 1);
findLayout->addWidget(replaceLabel, 1, 0);
findLayout->addWidget(m_pReplaceEdit, 1, 1);
QVBoxLayout *conditionsLayout = new QVBoxLayout();
conditionsLayout->addWidget(m_pWholeCheckBox);
conditionsLayout->addWidget(m_pCaseCheckBox);
conditionsLayout->addWidget(m_pRegExCheckBox);
QHBoxLayout *secondRowLayout = new QHBoxLayout();
secondRowLayout->addLayout(conditionsLayout);
secondRowLayout->addWidget(directionBox);
QVBoxLayout *firstColLayout = new QVBoxLayout();
firstColLayout->addLayout(findLayout);
firstColLayout->addLayout(secondRowLayout);
QVBoxLayout *buttonsLayout = new QVBoxLayout();
buttonsLayout->addWidget(m_pFindNextButton);
buttonsLayout->addWidget(m_pFindPreviousButton);
buttonsLayout->addWidget(m_pReplaceButton);
buttonsLayout->addWidget(m_pReplaceAllButton);
buttonsLayout->addWidget(m_pCloseButton);
QGridLayout *mainLayout = new QGridLayout();
mainLayout->addLayout(firstColLayout, 0, 0);
mainLayout->addLayout(buttonsLayout, 0, 1, Qt::AlignTop);
setLayout(mainLayout);
setWindowTitle("Replace");
connect(m_pFindEdit, SIGNAL(textChanged(QString)), this, SLOT(textToFindChanged()));
connect(m_pFindNextButton, SIGNAL(clicked()), this, SLOT(findNext()));
connect(m_pFindPreviousButton, SIGNAL(clicked()), this, SLOT(findPrevious()));
connect(m_pCloseButton, SIGNAL(clicked()), this, SLOT(close()));
connect(m_pReplaceButton, SIGNAL(clicked()), this, SLOT(replace()));
connect(m_pReplaceAllButton, SIGNAL(clicked()), this, SLOT(replaceAll()));
}
void ReplaceDialog::replace()
{
if ( !m_pFindListener )
return;
QRegExp regExp;
QTextDocument::FindFlags flags;
if ( !getSearchParameters(true,regExp,flags) )
return;
if ( m_pFindListener->hasSelection() && isSelectionValid() )
m_pFindListener->replace(m_pReplaceEdit->text(),regExp,flags);
find(true,true);
}
void ReplaceDialog::replaceAll()
{
if ( !m_pFindListener )
return;
QRegExp regExp;
QTextDocument::FindFlags flags;
if ( !getSearchParameters(true,regExp,flags) )
return;
m_pFindListener->goToStart(flags);
while ( find(true,false) )
m_pFindListener->replace(m_pReplaceEdit->text(),regExp,flags);
}
void ReplaceDialog::textToFindChanged()
{
bool bEnable = m_pFindEdit->text().size() > 0;
m_pFindNextButton->setEnabled(bEnable);
m_pFindPreviousButton->setEnabled(bEnable);
m_pReplaceButton->setEnabled(bEnable);
m_pReplaceAllButton->setEnabled(bEnable);
}
bool ReplaceDialog::isSelectionValid()
{
QString selectedText = m_pFindListener->getSelectedText();
if ( !selectedText.isEmpty() )
{
QString toSearch = m_pFindEdit->text();
if ( !toSearch.isEmpty() )
{
QRegExp reg(toSearch);
if ( !m_pRegExCheckBox->isChecked() )
reg.setPatternSyntax(QRegExp::FixedString);
reg.setCaseSensitivity(m_pCaseCheckBox->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive);
//selection and replacement string should be matching
if ( selectedText.indexOf(reg) == 0 )
return true;
}
}
return false;
}
FindReplaceProvider& FindReplaceProvider::instance()
{
static FindReplaceProvider inst;
return inst;
}
FindReplaceProvider::FindReplaceProvider()
: m_pEmbeddedFindReplaceWidget(NULL)
, m_pFindDialog(NULL)
, m_pReplaceDialog(NULL)
, m_pFindAllDialog(NULL)
, m_pGotoLineDialog(NULL)
{
if (FFlag::StudioEmbeddedFindDialogEnabled)
{
m_pEmbeddedFindReplaceWidget = new EmbeddedFindReplaceWidget(&UpdateUIManager::Instance().getMainWindow());
m_pEmbeddedFindReplaceWidget->hide();
}
else
{
m_pFindDialog = new FindDialog(&UpdateUIManager::Instance().getMainWindow());
m_pFindDialog->hide();
m_pReplaceDialog = new ReplaceDialog(&UpdateUIManager::Instance().getMainWindow());
m_pReplaceDialog->hide();
}
m_pFindAllDialog = new FindAllDialog(&UpdateUIManager::Instance().getMainWindow());
m_pFindAllDialog->hide();
m_pGotoLineDialog = new GoToLineDialog(&UpdateUIManager::Instance().getMainWindow());
m_pGotoLineDialog->hide();
}
FindReplaceProvider::~FindReplaceProvider()
{
m_pFindDialog = NULL; // Will be deleted by parent i.e. main window
m_pReplaceDialog = NULL; // Will be deleted by parent i.e. main window
m_pFindAllDialog = NULL;
m_pGotoLineDialog = NULL;
m_pEmbeddedFindReplaceWidget = NULL;
}
void FindReplaceProvider::setFindListener(IFindListener* listener)
{
if (m_pFindDialog)
m_pFindDialog->setFindListener(listener);
if (m_pReplaceDialog)
m_pReplaceDialog->setFindListener(listener);
if (m_pFindAllDialog)
m_pFindAllDialog->setFindListener(listener);
if (m_pGotoLineDialog)
m_pGotoLineDialog->setFindListener(listener);
if (FFlag::StudioEmbeddedFindDialogEnabled)
m_pEmbeddedFindReplaceWidget->setFindListener(listener);
}
bool FindReplaceProvider::isFindNextEnabled()
{
return (m_pFindDialog->isFindNextEnabled() || m_pReplaceDialog->isFindNextEnabled());
}
void FindReplaceProvider::find(bool forward)
{
if(m_pReplaceDialog->isVisible())
m_pReplaceDialog->find(forward,true);
else
m_pFindDialog->find(forward,true);
}
GoToLineDialog::GoToLineDialog(QWidget *parent)
: QDialog(parent)
{
initDialog();
}
void GoToLineDialog::initDialog()
{
setModal(true);
setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint);
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_pLineNumberLabel = new QLabel("Line number:");
m_pNumberValidator = new QIntValidator(this);
m_pNumberValidator->setBottom(0);
m_pLineNumberEdit = new QLineEdit;
m_pLineNumberEdit->setValidator(m_pNumberValidator);
m_pOkButton = new QPushButton("OK");
m_pCancelButton = new QPushButton("Cancel");
QHBoxLayout *pushButtons = new QHBoxLayout();
pushButtons->addStretch();
pushButtons->addWidget(m_pOkButton);
pushButtons->addWidget(m_pCancelButton);
QVBoxLayout *mainLayout = new QVBoxLayout();
mainLayout->addWidget(m_pLineNumberLabel);
mainLayout->addWidget(m_pLineNumberEdit);
mainLayout->addLayout(pushButtons);
setLayout(mainLayout);
setWindowTitle("Go to line");
connect(m_pLineNumberEdit, SIGNAL(returnPressed()), this, SLOT(goToLine()));
connect(m_pOkButton, SIGNAL(clicked()), this, SLOT(goToLine()));
connect(m_pCancelButton, SIGNAL(clicked()), this, SLOT(close()));
}
void GoToLineDialog::activate(int currentLine, int totalLines)
{
lineCount = totalLines;
m_pLineNumberLabel->setText(QString("Line number (1-%1):").arg(lineCount));
m_pLineNumberEdit->setText(QString::number(currentLine));
show();
activateWindow();
m_pLineNumberEdit->setFocus();
m_pLineNumberEdit->selectAll();
}
void GoToLineDialog::goToLine()
{
if (m_pFindListener)
m_pFindListener->moveCursorToLine(qMin(m_pLineNumberEdit->text().toInt() - 1, lineCount - 1));
close();
}
EmbeddedFindReplaceWidget::EmbeddedFindReplaceWidget(QWidget *parent)
: QDialog(parent)
, m_replaceVisible(false)
{
initDialog();
}
void EmbeddedFindReplaceWidget::activate(bool showReplace)
{
if (showReplace != m_replaceVisible)
toggleReplaceVisible();
show();
activateWindow();
setFocus();
m_pFindComboBox->lineEdit()->selectAll();
}
void EmbeddedFindReplaceWidget::initDialog()
{
setWindowFlags(Qt::Window | Qt::Dialog | Qt::FramelessWindowHint);
setStyleSheet("background-color: #f3f3f3");
setMinimumWidth(280);
setMaximumHeight(0);
QString pushButtonStyleSheet = STRINGIFY(
QPushButton
{
border: 0px;
}
QPushButton:hover
{
background: #E4EEFE;
}
QPushButton:checked
{
border: 1px solid #B8B8B8;
background: #DBDBDB;
}
);
QString comboBoxStyleSheet = STRINGIFY(
QComboBox
{
border: 0px;
}
);
m_pFindComboBox = new QComboBox();
m_pFindComboBox->setEditable(true);
m_pFindComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_pFindComboBox->lineEdit()->setPlaceholderText("Find...");
m_pFindComboBox->setAutoCompletion(false);
setFocusProxy(m_pFindComboBox);
m_pReplaceComboBox = new QComboBox();
m_pReplaceComboBox->setEditable(true);
m_pReplaceComboBox->hide();
m_pReplaceComboBox->lineEdit()->setPlaceholderText("Replace...");
m_pReplaceComboBox->setAutoCompletion(false);
m_pToggleReplaceButton = new QPushButton(QIcon(":/16x16/images/Studio 2.0 icons/16x16/expand_gray_16.png"), "");
m_pToggleReplaceButton->setFixedSize(18, 18);
m_pToggleReplaceButton->setStyleSheet(pushButtonStyleSheet);
m_pToggleReplaceButton->setAutoDefault(false);
m_pFindNextButton = new QPushButton(QIcon(":/16x16/images/Studio 2.0 icons/16x16/arrowright_black_16.png"), "");
m_pFindNextButton->setFixedSize(18, 18);
m_pFindNextButton->setStyleSheet(pushButtonStyleSheet);
m_pFindNextButton->setAutoDefault(true);
m_pFindNextButton->setToolTip("Find Next");
m_pFindPreviousButton = new QPushButton(QIcon(":/16x16/images/Studio 2.0 icons/16x16/arrowleft_black_16.png"), "");
m_pFindPreviousButton->setFixedSize(18, 18);
m_pFindPreviousButton->setStyleSheet(pushButtonStyleSheet);
m_pFindPreviousButton->setAutoDefault(false);
m_pFindPreviousButton->setToolTip("Find Previous");
m_pCloseDialogButton = new QPushButton(QIcon(":/16x16/images/Studio 2.0 icons/16x16/delete_x_16_b.png"), "");
m_pCloseDialogButton->setFixedSize(18, 18);
m_pCloseDialogButton->setStyleSheet(pushButtonStyleSheet);
m_pCloseDialogButton->setAutoDefault(false);
m_pCloseDialogButton->setToolTip("Close");
m_pReplaceNextButton = new QPushButton(QIcon(":/16x16/images/Studio 2.0 icons/16x16/replace.png"), "");
m_pReplaceNextButton->setFixedSize(18, 18);
m_pReplaceNextButton->setStyleSheet(pushButtonStyleSheet);
m_pReplaceNextButton->hide();
m_pReplaceNextButton->setAutoDefault(false);
m_pReplaceNextButton->setToolTip("Replace Next");
m_pReplaceAllButton = new QPushButton(QIcon(":/16x16/images/Studio 2.0 icons/16x16/replace_all.png"), "");
m_pReplaceAllButton->setFixedSize(18, 18);
m_pReplaceAllButton->setStyleSheet(pushButtonStyleSheet);
m_pReplaceAllButton->hide();
m_pReplaceAllButton->setAutoDefault(false);
m_pReplaceAllButton->setToolTip("Replace All");
m_pMatchCaseButton = new QPushButton(QIcon(":/16x16/images/Studio 2.0 icons/16x16/match_case.png"), "");
m_pMatchCaseButton->setFixedSize(18, 18);
m_pMatchCaseButton->setStyleSheet(pushButtonStyleSheet);
m_pMatchCaseButton->setCheckable(true);
m_pMatchCaseButton->setAutoDefault(false);
m_pMatchCaseButton->setToolTip("Match Case");
m_pMatchWholeWordButton = new QPushButton(QIcon(":/16x16/images/Studio 2.0 icons/16x16/match_whole_word.png"), "");
m_pMatchWholeWordButton->setFixedSize(18, 18);
m_pMatchWholeWordButton->setStyleSheet(pushButtonStyleSheet);
m_pMatchWholeWordButton->setCheckable(true);
m_pMatchWholeWordButton->setAutoDefault(false);
m_pMatchWholeWordButton->setToolTip("Match Whole Word");
m_pRegularExpressionButton = new QPushButton(QIcon(":/16x16/images/Studio 2.0 icons/16x16/regexp.png"), "");
m_pRegularExpressionButton->setFixedSize(18, 18);
m_pRegularExpressionButton->setStyleSheet(pushButtonStyleSheet);
m_pRegularExpressionButton->setCheckable(true);
m_pRegularExpressionButton->setAutoDefault(false);
m_pRegularExpressionButton->setToolTip("Regular Expression");
m_pNumberOfResultsFound = new QLabel();
m_pNumberOfResultsFound->setText("");
QBrush colorBrush(QColor(128, 128, 128, 255));
QPalette palette;
palette.setBrush(QPalette::Inactive, QPalette::WindowText, colorBrush);
palette.setBrush(QPalette::Active, QPalette::WindowText, colorBrush);
m_pNumberOfResultsFound->setPalette(palette);
QFrame* bottomFrame = new QFrame();
bottomFrame->setStyleSheet("background-color: #c4c4c4");
bottomFrame->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
bottomFrame->setFixedHeight(4);
QSpacerItem* spacer1 = new QSpacerItem(2, 10, QSizePolicy::Fixed, QSizePolicy::Fixed);
QSpacerItem* spacer2 = new QSpacerItem(2, 10, QSizePolicy::Fixed, QSizePolicy::Fixed);
QGridLayout* mainLayout = new QGridLayout();
mainLayout->setSpacing(2);
mainLayout->setContentsMargins(QMargins(0, 4, 0, 0));
mainLayout->addItem(spacer1, 1, 0);
mainLayout->addWidget(m_pToggleReplaceButton, 1, 1);
mainLayout->addWidget(m_pFindComboBox, 1, 2, 1, 4);
mainLayout->addWidget(m_pFindPreviousButton, 1, 6);
mainLayout->addWidget(m_pFindNextButton, 1, 7);
mainLayout->addWidget(m_pCloseDialogButton, 1, 8);
mainLayout->addItem(spacer2, 1, 9);
mainLayout->addWidget(m_pReplaceComboBox, 2, 2, 1, 4);
mainLayout->addWidget(m_pReplaceNextButton, 2, 6);
mainLayout->addWidget(m_pReplaceAllButton, 2, 7);
mainLayout->addWidget(m_pMatchCaseButton, 3, 2);
mainLayout->addWidget(m_pMatchWholeWordButton, 3, 3);
mainLayout->addWidget(m_pRegularExpressionButton, 3, 4);
mainLayout->addWidget(m_pNumberOfResultsFound, 3, 5);
mainLayout->addWidget(bottomFrame, 4, 0, 1, 10);
setLayout(mainLayout);
connect(m_pCloseDialogButton, SIGNAL(pressed()), this, SLOT(onCloseDialog()));
connect(m_pFindComboBox->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(find()));
connect(m_pMatchCaseButton, SIGNAL(clicked()), this, SLOT(find()));
connect(m_pMatchWholeWordButton, SIGNAL(clicked()), this, SLOT(find()));
connect(m_pRegularExpressionButton, SIGNAL(clicked()), this, SLOT(find()));
connect(m_pToggleReplaceButton, SIGNAL(pressed()), this, SLOT(toggleReplaceVisible()));
connect(m_pFindPreviousButton, SIGNAL(pressed()), this, SLOT(findPrevious()));
connect(m_pFindNextButton, SIGNAL(pressed()), this, SLOT(findNext()));
connect(m_pReplaceNextButton, SIGNAL(pressed()), this, SLOT(replaceNext()));
connect(m_pReplaceAllButton, SIGNAL(pressed()), this, SLOT(replaceAll()));
}
void EmbeddedFindReplaceWidget::keyPressEvent(QKeyEvent* evt)
{
QKeySequence key = QKeySequence(evt->modifiers()|evt->key());
QAction* previous = UpdateUIManager::Instance().getMainWindow().findPreviousAction;
if (previous && key == previous->shortcut())
{
previous->trigger();
evt->accept();
return;
}
QAction* next = UpdateUIManager::Instance().getMainWindow().findNextAction;
if (next && key == next->shortcut())
{
next->trigger();
evt->accept();
return;
}
if (evt->key() == Qt::Key_Enter ||
evt->key() == Qt::Key_Return)
{
if (m_pFindComboBox->hasFocus())
findNext();
else if (m_pReplaceComboBox->hasFocus())
replaceNext();
evt->accept();
return;
}
if (evt->key() == Qt::Key_Escape)
{
onCloseDialog();
evt->accept();
return;
}
QWidget::keyPressEvent(evt);
}
void EmbeddedFindReplaceWidget::setNumberOfFoundResults(int number)
{
if (number == -1)
{
m_pNumberOfResultsFound->setText("");
return;
}
m_pNumberOfResultsFound->setText(QString(" %1 occurrences found").arg(number));
}
QRegExp EmbeddedFindReplaceWidget::getSearchString()
{
QRegExp rx(m_pFindComboBox->lineEdit()->text());
if (!m_pRegularExpressionButton->isChecked())
rx.setPatternSyntax(QRegExp::FixedString);
rx.setCaseSensitivity(m_pMatchCaseButton->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive);
return rx;
}
void EmbeddedFindReplaceWidget::toggleReplaceVisible()
{
m_replaceVisible = !m_replaceVisible;
QString toggleButtonIconLocation = m_replaceVisible ? "collapse_gray_16.png" : "expand_gray_16.png";
m_pToggleReplaceButton->setIcon(QIcon(":/16x16/images/Studio 2.0 icons/16x16/" + toggleButtonIconLocation));
m_pReplaceComboBox->setVisible(m_replaceVisible);
m_pReplaceNextButton->setVisible(m_replaceVisible);
m_pReplaceAllButton->setVisible(m_replaceVisible);
adjustSize();
}
void EmbeddedFindReplaceWidget::updateFindPosition(QPoint position, QSize size)
{
this->setGeometry(position.x() + size.width() - geometry().width(), position.y(), geometry().width(), geometry().height());
}
void EmbeddedFindReplaceWidget::find()
{
QTextDocument::FindFlags flags = m_pMatchWholeWordButton->isChecked() ?
QTextDocument::FindWholeWords :
(QTextDocument::FindFlags)0;
m_pFindListener->find(false, getSearchString(), flags);
}
void EmbeddedFindReplaceWidget::findNext()
{
m_pFindListener->moveNext(true);
}
void EmbeddedFindReplaceWidget::findPrevious()
{
m_pFindListener->moveNext(false);
}
void EmbeddedFindReplaceWidget::replaceNext()
{
m_pFindListener->replaceNext(getSearchString(), m_pReplaceComboBox->lineEdit()->text());
}
void EmbeddedFindReplaceWidget::replaceAll()
{
m_pFindListener->replaceAll(getSearchString(), m_pReplaceComboBox->lineEdit()->text());
}
void EmbeddedFindReplaceWidget::onCloseDialog()
{
m_pFindComboBox->lineEdit()->setText("");
close();
}
+286
View File
@@ -0,0 +1,286 @@
/**
* FindDialog.h
* Copyright (c) 2012 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QDialog>
#include <QTextDocument>
#include <QRegExp>
#include <QComboBox>
#include "RobloxFindWidget.h"
class QLineEdit;
class QPushButton;
class QRadioButton;
class QCheckBox;
class QGroupBox;
class QLabel;
class QGridLayout;
class IFindListener
{
public:
/**
* Finds the item after the current cursor location.
* The cursor will be moved to the new item and the item selected.
*/
virtual bool find(bool loop,const QRegExp& regExp,QTextDocument::FindFlags flags) = 0;
virtual void moveNext(bool forward) = 0;
/**
* Replaces the text for the current item.
*/
virtual void replace(const QString& text,const QRegExp& regExp,QTextDocument::FindFlags flags) = 0;
virtual void replaceNext(const QRegExp& rx, const QString& text) = 0;
virtual void replaceAll(const QRegExp& rx, const QString& text) = 0;
/**
* Get the text at the current cursor location.
* The current text will be used to validate the selection and for populating the
* find dialog when shown.
*/
virtual QString getSelectedText() = 0;
/**
* Checks if there is a current selected item.
*/
virtual bool hasSelection() = 0;
/**
* Move the current item to the beginning.
*/
virtual void goToStart(QTextDocument::FindFlags flags) = 0;
/**
* Go to line
*/
virtual void moveCursorToLine(int line) = 0;
};
class BasicFindDialog : public QDialog
{
Q_OBJECT
public:
virtual ~BasicFindDialog();
void setFindListener(IFindListener* listener);
bool isFindNextEnabled();
bool find(bool forward,bool loop);
bool getSearchParameters(bool forward,QRegExp& outRegExp,QTextDocument::FindFlags& outflags);
public Q_SLOTS:
virtual void setFocus();
protected Q_SLOTS:
void findNext() { find(true,true); }
void findPrevious() { find(false,true); }
protected:
BasicFindDialog(QWidget *parent = 0);
virtual void keyPressEvent(QKeyEvent* e);
protected Q_SLOTS:
virtual void textToFindChanged();
virtual void setVisible(bool);
protected:
IFindListener *m_pFindListener;
QLineEdit *m_pFindEdit;
QPushButton *m_pFindButton;
QPushButton *m_pFindNextButton;
QPushButton *m_pFindPreviousButton;
QPushButton *m_pCloseButton;
QRadioButton *m_pDownRadioButton;
QCheckBox *m_pCaseCheckBox;
QCheckBox *m_pWholeCheckBox;
QCheckBox *m_pRegExCheckBox;
};
class FindAllDialog: public BasicFindDialog
{
Q_OBJECT
public:
FindAllDialog(QWidget *parent);
void showError(const QString& error);
void setFindAllRunning(bool value);
private Q_SLOTS:
void findAll();
void onFindOptionsToggled(bool on);
virtual void textToFindChanged();
virtual void adjustSize() { QWidget::adjustSize(); }
private:
RobloxFindWidget::eFindFlags getCurrentFlags();
void initDialog();
QComboBox *m_pLookInComboBox;
QPushButton *m_pFindAllButton;
QGroupBox *m_pFindOptions;
QGridLayout *m_pFindLayout;
protected:
void showEvent(QShowEvent * event);
};
class FindDialog : public BasicFindDialog
{
Q_OBJECT
public:
FindDialog(QWidget *parent);
void initDialog();
};
class ReplaceDialog : public BasicFindDialog
{
Q_OBJECT
public:
ReplaceDialog(QWidget *parent);
void initDialog();
private Q_SLOTS:
void replace();
void replaceAll();
virtual void textToFindChanged();
private:
bool isSelectionValid();
QLineEdit *m_pReplaceEdit;
QPushButton *m_pReplaceButton;
QPushButton *m_pReplaceAllButton;
};
class EmbeddedFindReplaceWidget : public QDialog
{
Q_OBJECT
public:
EmbeddedFindReplaceWidget(QWidget *parent);
void initDialog();
void setFindListener(IFindListener* listener) { m_pFindListener = listener; }
void updateFindPosition(QPoint position, QSize size);
void activate(bool showReplace = false);
QRegExp getSearchString();
Q_INVOKABLE void setNumberOfFoundResults(int number);
virtual void keyPressEvent(QKeyEvent* evt);
private Q_SLOTS:
void find();
void findNext();
void findPrevious();
void replaceNext();
void replaceAll();
void onCloseDialog();
void toggleReplaceVisible();
private:
bool m_replaceVisible;
IFindListener *m_pFindListener;
QComboBox *m_pFindComboBox;
QComboBox *m_pReplaceComboBox;
QPushButton *m_pToggleReplaceButton;
QPushButton *m_pFindNextButton;
QPushButton *m_pFindPreviousButton;
QPushButton *m_pCloseDialogButton;
QPushButton *m_pReplaceNextButton;
QPushButton *m_pReplaceAllButton;
QPushButton *m_pMatchCaseButton;
QPushButton *m_pMatchWholeWordButton;
QPushButton *m_pRegularExpressionButton;
QLabel *m_pNumberOfResultsFound;
};
class GoToLineDialog : public QDialog
{
Q_OBJECT
public:
GoToLineDialog(QWidget *parent);
void initDialog();
void activate(int currentLine, int totalLines);
void setFindListener(IFindListener* listener) { m_pFindListener = listener; }
private Q_SLOTS:
void goToLine();
private:
IFindListener *m_pFindListener;
QLabel *m_pLineNumberLabel;
QLineEdit *m_pLineNumberEdit;
QPushButton *m_pOkButton;
QPushButton *m_pCancelButton;
QIntValidator *m_pNumberValidator;
int lineCount;
};
class FindReplaceProvider
{
public:
static FindReplaceProvider& instance();
FindDialog* getFindDialog() { RBXASSERT(m_pFindDialog); return m_pFindDialog; }
ReplaceDialog* getReplaceDialog() { RBXASSERT(m_pReplaceDialog); return m_pReplaceDialog; }
FindAllDialog* getFindAllDialog() { RBXASSERT(m_pFindAllDialog); return m_pFindAllDialog; }
GoToLineDialog* getGotoLineDialog() { RBXASSERT(m_pGotoLineDialog); return m_pGotoLineDialog; }
EmbeddedFindReplaceWidget* getEmbeddedFindDialog() { RBXASSERT(m_pEmbeddedFindReplaceWidget); return m_pEmbeddedFindReplaceWidget; }
void setFindListener(IFindListener* listener);
bool isFindNextEnabled();
void find(bool forward);
void setSearchStr(const QString& str) { m_searchStr = str; }
const QString& getSearchStr() { return m_searchStr; }
private:
FindReplaceProvider();
~FindReplaceProvider();
FindDialog* m_pFindDialog;
ReplaceDialog* m_pReplaceDialog;
FindAllDialog* m_pFindAllDialog;
GoToLineDialog* m_pGotoLineDialog;
EmbeddedFindReplaceWidget* m_pEmbeddedFindReplaceWidget;
QString m_searchStr;
};
+138
View File
@@ -0,0 +1,138 @@
/**
* FunctionMarshaller.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "FunctionMarshaller.h"
// 3rd Party Headers
#include "boost/function.hpp"
#include "boost/thread/recursive_mutex.hpp"
// Roblox Headers
#include "util/standardout.h"
#include "rbx/Boost.hpp"
#include "rbx/CEvent.h"
// Roblox Studio Headers
#include "Roblox.h"
// TODO Clean up this file
using namespace RBX;
FunctionMarshaller::FunctionMarshaller(DWORD threadID)
:refCount(0)
{
this->threadID = threadID;
}
FunctionMarshaller::~FunctionMarshaller()
{
RBXASSERT(threadID == GetCurrentThreadId());
#ifdef _DEBUG
{
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
RBXASSERT (refCount==0);
// Nobody is using this window
RBXASSERT (staticData().windows.find(threadID) == staticData().windows.end());
}
#endif
}
FunctionMarshaller* FunctionMarshaller::GetWindow()
{
// Share a common FunctionMarshaller in a given Thread
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
DWORD threadID = GetCurrentThreadId();
std::map<DWORD, FunctionMarshaller*>::iterator find = staticData().windows.find(threadID);
if (find != staticData().windows.end())
{
// We already created a window, so use it again
find->second->refCount++;
return find->second;
}
else
{
// Create a new window
FunctionMarshaller* window = new FunctionMarshaller(threadID);
staticData().windows[threadID] = window;
window->refCount++;
return window;
}
}
void FunctionMarshaller::ReleaseWindow(FunctionMarshaller* window)
{
boost::recursive_mutex::scoped_lock lock(staticData().windowsCriticalSection);
window->refCount--;
if (window->refCount==0)
{
// Nobody is using this window
staticData().windows.erase(window->threadID);
delete window;
}
}
void FunctionMarshaller::handleAppEvent(void *pClosure)
{
FunctionMarshaller::Closure* closure = (FunctionMarshaller::Closure*)pClosure;
RBX::CEvent *pWaitEvent = closure->waitEvent;
try
{
boost::function<void()>* pF = closure->f;
(*pF)();
delete pF;
delete closure;
}
catch (std::exception& e)
{
StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e);
closure->errorMessage = e.what();
}
// If a task is waiting on an event, set it
if (pWaitEvent)
{
pWaitEvent->Set();
}
}
void FunctionMarshaller::freeAppEvent(void *pClosure)
{
FunctionMarshaller::Closure* closure = (FunctionMarshaller::Closure*)pClosure;
boost::function<void()>* pF = closure->f;
delete pF;
delete closure;
}
void FunctionMarshaller::Execute(boost::function<void()> job, CEvent *waitEvent)
{
if (threadID == GetCurrentThreadId())
job();
else
{
Closure *pClosure = new Closure;
pClosure->f = new boost::function<void()>(job);
pClosure->waitEvent = waitEvent;
Roblox::sendAppEvent(pClosure);
}
}
void FunctionMarshaller::Submit(boost::function<void()> job)
{
Closure *pClosure = new Closure;
pClosure->f = new boost::function<void()>(job);
pClosure->waitEvent = NULL;
Roblox::postAppEvent(pClosure);
}
FunctionMarshaller::StaticData::~StaticData()
{
}
+60
View File
@@ -0,0 +1,60 @@
/**
* FunctionMarshaller.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Standard C/C++ Headers
#include <map>
// Roblox Headers
#include "rbx/threadsafe.h"
namespace RBX {
class CEvent;
// A very handy class for marshaling a function across Windows threads (sync and async)
class FunctionMarshaller
{
public:
private:
struct StaticData
{
std::map<DWORD, FunctionMarshaller*> windows;
boost::recursive_mutex windowsCriticalSection; // TODO: Would non-recursive be safe here?
~StaticData();
};
SAFE_STATIC(StaticData, staticData)
int refCount;
DWORD threadID;
FunctionMarshaller(DWORD threadID);
~FunctionMarshaller();
public:
// TODO: Wrap with a reference counter and then remove ~StaticData() cleanup code and remove ReleaseWindow()
static FunctionMarshaller* GetWindow();
static void ReleaseWindow(FunctionMarshaller* window);
static void handleAppEvent(void *pClosure);
static void freeAppEvent(void *pClosure);
struct Closure
{
boost::function<void()>* f;
std::string errorMessage;
RBX::CEvent *waitEvent;
};
void Execute(boost::function<void()> job, CEvent *waitEvent);
void Submit(boost::function<void()> job);
// Call this only from the Window's thread
void ProcessMessages() {};
};
}
+113
View File
@@ -0,0 +1,113 @@
/**
* GalleryItemColor.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "GalleryItemColor.h"
// Qt headers
#include <QPainter>
#include <QStyleOption>
#include <QtnStyleHelpers.h>
#include <QRgb.h>
// Roblox headers
#include "util/BrickColor.h"
// Roblox Studio Headers
#include "QtUtilities.h"
void StudioGalleryItem::draw(QPainter* pPainter, Qtitan::RibbonGallery* pGallery, QRect rectItem, bool enabled, bool selected, bool pressed, bool checked)
{
if (icon().isNull())
{
Qtitan::RibbonGalleryItem::draw(pPainter, pGallery, rectItem, enabled, selected, pressed, checked);
return;
}
rectItem.adjust(2, 2, -2, -2);
pPainter->setPen(QPen(Qt::black, 1));
pPainter->drawRect(rectItem);
if (!enabled)
{
pPainter->drawPixmap(rectItem, icon().pixmap(rectItem.size(), QIcon::Disabled));
return;
}
pPainter->drawPixmap(rectItem, icon().pixmap(rectItem.size()));
if (selected || checked)
{
rectItem.adjust(0, 0, -1, -1);
if (selected)
Qtitan::DrawHelpers::draw3DRect(*pPainter, QColor(242, 148, 54), QColor(242, 148, 54), rectItem.left(), rectItem.top(), rectItem.width(), rectItem.height(), true);
else
Qtitan::DrawHelpers::draw3DRect(*pPainter, QColor(239, 72, 16), QColor(239, 72, 16), rectItem.left(), rectItem.top(), rectItem.width(), rectItem.height(), true);
rectItem.adjust(1, 1, -1, -1);
Qtitan::DrawHelpers::draw3DRect(*pPainter, QColor(255, 226, 148), QColor(255, 226, 148), rectItem.left(), rectItem.top(), rectItem.width(), rectItem.height(), true);
}
}
void GalleryItemColor::addStandardColors(Qtitan::RibbonGalleryGroup* pGalleryGroup)
{
std::vector<RBX::BrickColor>::const_iterator iter = RBX::BrickColor::colorPalette().begin();
while (iter != RBX::BrickColor::colorPalette().end())
{
GalleryItemColor* pItem = new GalleryItemColor(*iter);
pItem->setSizeHint(pGalleryGroup->getSize());
pItem->setToolTip(iter->name().c_str());
pItem->setData(GALLERY_ITEM_USER_DATA, iter->number);
pGalleryGroup->appendItem(pItem);
iter++;
}
}
GalleryItemColor::GalleryItemColor(const RBX::BrickColor& color)
: m_brickColor(color)
{
m_color = QtUtilities::toQColor(color.color3());
}
void GalleryItemColor::draw(QPainter* pPainter, Qtitan::RibbonGallery* pGallery, QRect rectItem, bool enabled, bool selected, bool pressed, bool checked)
{
// set margin
rectItem.adjust(2, 2, -2, -2);
pPainter->setPen(QPen(Qt::black, 1));
pPainter->drawRect(rectItem);
if (!enabled)
{
// draw disabled
int grayScale = qGray(m_color.rgb());
pPainter->fillRect(rectItem, QColor(grayScale, grayScale, grayScale));
return;
}
// fill the item color
pPainter->fillRect(rectItem, m_color);
if (selected || checked)
{
rectItem.adjust(0, 0, -1, -1);
if (selected)
Qtitan::DrawHelpers::draw3DRect(*pPainter, QColor(242, 148, 54), QColor(242, 148, 54), rectItem.left(), rectItem.top(), rectItem.width(), rectItem.height(), true);
else
Qtitan::DrawHelpers::draw3DRect(*pPainter, QColor(239, 72, 16), QColor(239, 72, 16), rectItem.left(), rectItem.top(), rectItem.width(), rectItem.height(), true);
rectItem.adjust(1, 1, -1, -1);
Qtitan::DrawHelpers::draw3DRect(*pPainter, QColor(255, 226, 148), QColor(255, 226, 148), rectItem.left(), rectItem.top(), rectItem.width(), rectItem.height(), true);
}
else
{
pPainter->fillRect(rectItem.left(), rectItem.top(), rectItem.width(), 1, m_color);
pPainter->fillRect(rectItem.left(), rectItem.top(), 1, rectItem.height(), m_color);
pPainter->fillRect(rectItem.right(), rectItem.top(), 1, rectItem.height(), m_color);
pPainter->fillRect(rectItem.left(), rectItem.bottom(), rectItem.width(), 1, m_color);
}
}
+44
View File
@@ -0,0 +1,44 @@
/**
* GalleryItemColor.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
#define signals protected
#define slots
#include <QtnRibbonGallery.h>
#undef signals
#undef slots
#include "util/BrickColor.h"
#define GALLERY_ITEM_USER_DATA 100
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class StudioGalleryItem : public Qtitan::RibbonGalleryItem
{
public:
StudioGalleryItem()
{}
protected:
virtual void draw(QPainter* pPainter, Qtitan::RibbonGallery* pGallery, QRect rectItem, bool enabled, bool selected, bool pressed, bool checked);
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class GalleryItemColor : public Qtitan::RibbonGalleryItem
{
public:
static void addStandardColors(Qtitan::RibbonGalleryGroup* items);
QColor getColor() const { return m_color; }
RBX::BrickColor getBrickColor() const { return m_brickColor; }
private:
GalleryItemColor(const RBX::BrickColor& color);
virtual void draw(QPainter* pPainter, Qtitan::RibbonGallery* pGallery, QRect rectItem, bool enabled, bool selected, bool pressed, bool checked);
QColor m_color;
RBX::BrickColor m_brickColor;
};
+24
View File
@@ -0,0 +1,24 @@
/**
* IExternalHandler.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// 3rd Party Headers
#include "boost/shared_ptr.hpp"
class QString;
namespace RBX {
class DataModel;
}
class IExternalHandler
{
public:
virtual QString className() = 0;
virtual QString handlerId() = 0;
virtual void setDataModel(boost::shared_ptr<RBX::DataModel> pDataModel) = 0;
virtual bool handle() = 0;
};
+93
View File
@@ -0,0 +1,93 @@
/**
* IRobloxDoc.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
class QString;
class QWidget;
class IExternalHandler;
class RobloxMainWindow;
class QIcon;
enum EDockWindowID
{
eDW_TOOLBOX,
eDW_BASIC_OBJECTS,
eDW_OBJECT_EXPLORER,
eDW_PROPERTIES,
eDW_OUTPUT,
eDW_CONTEXTUAL_HELP,
eDW_SCRIPT_REVIEW,
eDW_TASK_SCHEDULER,
eDW_DIAGNOSTICS,
eDW_FIND,
eDW_GAME_EXPLORER,
eDW_TUTORIALS,
eDW_SCRIPT_ANALYSIS,
eDW_TEAM_CREATE,
eDW_MAX
};
class IRobloxDoc
{
public:
enum RBXDocType
{
IDE,
BROWSER,
SCRIPT,
OBJECTBROWSER
};
enum RBXCloseRequest
{
REQUEST_HANDLED,
LOCAL_SAVE_NEEDED,
NO_SAVE_NEEDED,
CLOSE_CANCELED
};
virtual bool open(RobloxMainWindow *pMainWindow, const QString &fileName) = 0;
virtual bool close() = 0;
virtual RBXCloseRequest requestClose() = 0;
virtual RBXDocType docType() = 0;
virtual QString fileName() const = 0;
virtual QString displayName() const = 0;
virtual QString windowTitle() const = 0;
virtual QString keyName() const = 0;
virtual const QIcon& titleIcon() const = 0;
virtual const QString& titleTooltip() const = 0;
virtual QWidget* getViewer() = 0;
virtual bool isModified() = 0;
virtual bool save() = 0;
virtual bool saveAs(const QString &fileName) = 0;
virtual QString saveFileFilters() = 0;
virtual void activate() = 0;
virtual void deActivate() = 0;
virtual void updateUI(bool state) = 0;
virtual void addExternalHandler(IExternalHandler* extHandler) = 0;
virtual void removeExternalHandler(const QString &handlerId) = 0;
virtual IExternalHandler* getExternalHandler(const QString &handlerId) = 0;
virtual bool handleAction(const QString &actionID, bool isChecked = false) = 0;
virtual bool actionState(const QString &actionID, bool &enableState, bool &checkedState) = 0;
virtual bool handleDrop(const QString &fileName) = 0;
virtual bool handlePluginAction(void *pNotifier, void *pAction) = 0;
virtual void handleScriptCommand(const QString &execCommand) = 0;
};
+60
View File
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BreakpadVendor</key>
<string>Roblox</string>
<key>BreakpadReportInterval</key>
<string>0</string>
<key>BreakpadSkipConfirm</key>
<string>YES</string>
<key>BreakpadSendAndExit</key>
<string>NO</string>
<key>CFBundleShortVersionString</key>
<string>0.75.0.691</string>
<key>CFBundleExecutable</key>
<string>RobloxStudio</string>
<key>CFBundleVersion</key>
<string>691</string>
<key>CFBundleIconFile</key>
<string>RobloxStudio</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleIdentifier</key>
<string>wtf.watrbx.RobloxStudio</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleName</key>
<string>RobloxStudio</string>
<key>ENABLE_APPLE_CRASH_REPORTER</key>
<string>YES</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleTypeName</key>
<string>Roblox Model File</string>
<key>LSHandlerRank</key>
<string>Default</string>
<key>CFBundleTypeIconFile</key>
<string>RobloxStudio</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>rbxl</string>
</array>
</dict>
</array>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</dict>
</plist>
+218
View File
@@ -0,0 +1,218 @@
/**
* InsertObjectListWidget.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "InsertObjectListWidget.h"
// Qt Headers
#include <QMouseEvent>
#include <QDrag>
#include <QMimeData>
#include <QApplication>
#include "V8DataModel/DataModel.h"
// ROBLOX Headers
#include "AuthoringSettings.h"
#include "CommonInsertWidget.h"
#include "InsertObjectListWidgetItem.h"
#include "RobloxContextualHelp.h"
#include "RobloxIDEDoc.h"
#include "RobloxDocManager.h"
FASTFLAG(StudioNewWiki)
InsertObjectListWidget::InsertObjectListWidget(QWidget *pParent)
: QListWidget(pParent)
{
setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
// listen for changes to the editor settings
m_PropertyChangedConnection = AuthoringSettings::singleton().propertyChangedSignal.connect(
boost::bind(&InsertObjectListWidget::onPropertyChanged,this,_1));
onPropertyChanged(NULL);
connect(this, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(onItemInsertRequested(QListWidgetItem*)));
connect(this, SIGNAL(enterKeyPressed(QListWidgetItem*)), this, SLOT(onItemInsertRequested(QListWidgetItem*)));
connect(this, SIGNAL(helpTopicChanged(const QString&)),
&RobloxContextualHelpService::singleton(), SLOT(onHelpTopicChanged(const QString&)));
}
InsertObjectListWidget::~InsertObjectListWidget()
{
m_PropertyChangedConnection.disconnect();
}
void InsertObjectListWidget::mousePressEvent(QMouseEvent *evt)
{
if (evt->button() == Qt::LeftButton)
m_dragStartPosition = evt->pos();
InsertObjectListWidgetItem* pListWidgetItem = dynamic_cast<InsertObjectListWidgetItem*>(itemAt(evt->pos()));
if (pListWidgetItem)
{
QString className = pListWidgetItem->getInstance()->getClassName().c_str();
if(FFlag::StudioNewWiki)
{
className.prepend("API:Class/");
}
Q_EMIT(helpTopicChanged(className));
}
QListWidget::mousePressEvent(evt);
}
void InsertObjectListWidget::mouseMoveEvent(QMouseEvent *evt)
{
if (!(evt->buttons() & Qt::LeftButton) || ((evt->pos() - m_dragStartPosition).manhattanLength() < QApplication::startDragDistance()))
return QListWidget::mouseMoveEvent(evt);
QDrag *pDrag = new QDrag(this);
pDrag->setMimeData(new QMimeData);
QPixmap pixMap(1, 1);
pDrag->setPixmap(pixMap);
#ifdef Q_WS_MAC
pDrag->exec();
#else
pDrag->exec(Qt::CopyAction);
#endif
pDrag->deleteLater();
}
bool InsertObjectListWidget::event(QEvent *e)
{
QListWidget::event(e);
if (isVisible() && currentRow() == -1 && count() > 0)
setCurrentRow(0);
return true;
}
void InsertObjectListWidget::keyPressEvent(QKeyEvent *evt)
{
if ((evt->key() == Qt::Key_Enter) || (evt->key() == Qt::Key_Return))
{
if(currentItem())
{
Q_EMIT enterKeyPressed(currentItem());
evt->accept();
return;
}
}
QListWidget::keyPressEvent(evt);
}
int weightSorter(const InsertObjectListWidgetItem * a, const InsertObjectListWidgetItem * b)
{
if (a->data(Qt::UserRole + 1).toInt() == b->data(Qt::UserRole + 1).toInt())
return a->text().compare(b->text()) > 0;
return a->data(Qt::UserRole + 1).toInt() < b->data(Qt::UserRole + 1).toInt();
}
void InsertObjectListWidget::sortItems(const QHash<QString, QVariant>& itemWeights, QString filter, Qt::SortOrder order)
{
QListWidget::sortItems(order);
if (filter.isEmpty())
return;
QList<InsertObjectListWidgetItem*> objectWeightList;
for (int i = this->count() - 1; i >= 0; --i)
{
InsertObjectListWidgetItem* item = dynamic_cast<InsertObjectListWidgetItem*>(takeItem(i));
if (item->text().toLower().startsWith(filter.toLower()))
{
item->setData(Qt::UserRole + 1, itemWeights.value(item->text()).toInt());
objectWeightList.append(item);
}
else
{
insertItem(i, item);
}
}
qSort(objectWeightList.begin(), objectWeightList.end(), weightSorter);
for(QList<InsertObjectListWidgetItem*>::iterator iter = objectWeightList.begin(); iter != objectWeightList.end(); ++iter)
insertItem(0, *iter);
}
void InsertObjectListWidget::InsertObject(QListWidgetItem* item)
{
onItemInsertRequested(item);
}
void InsertObjectListWidget::onItemInsertRequested(QListWidgetItem* pListWidgetItem)
{
if(!pListWidgetItem)
pListWidgetItem = currentItem();
if(!pListWidgetItem)
return;
InsertObjectListWidgetItem* pInsertListWidgetItem = dynamic_cast<InsertObjectListWidgetItem*>(pListWidgetItem);
if(!pInsertListWidgetItem || !pInsertListWidgetItem->getInstance())
return;
try
{
shared_ptr<RBX::Instance> pObjectToInsert = pInsertListWidgetItem->getInstance()->clone(RBX::EngineCreator);
if (!pObjectToInsert)
throw std::runtime_error(RBX::format("InsertObject: Cannot find the object selected."));
RobloxIDEDoc* pCurrentDoc = RobloxDocManager::Instance().getPlayDoc();
if (pCurrentDoc && pCurrentDoc->getDataModel())
InsertObjectWidget::InsertObject(pObjectToInsert,shared_from(pCurrentDoc->getDataModel()));
Q_EMIT itemInserted();
}
catch(std::bad_alloc&)
{
throw;
}
catch(std::exception& e)
{
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e.what());
}
}
void InsertObjectListWidget::onPropertyChanged(const RBX::Reflection::PropertyDescriptor* pDescriptor)
{
if ( AuthoringSettings::singleton().basicObjectsDisplayMode == AuthoringSettings::Vertical )
setWrapping(false);
else
setWrapping(true);
}
void InsertObjectListWidget::onSelectionChanged()
{
QList<QListWidgetItem *> selected = selectedItems();
QListWidgetItem *item;
Q_FOREACH (item, selected)
{
if (!item->text().isEmpty())
{
QString itemName = item->text();
if(FFlag::StudioNewWiki)
{
itemName.prepend("API:Class/");
}
Q_EMIT(helpTopicChanged(itemName));
return;
}
}
}
+52
View File
@@ -0,0 +1,52 @@
/**
* InsertObjectListWidget.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
//this will make sure that we can use qt with boost (else moc compiler errors)
#ifndef QT_NO_KEYWORDS
#define QT_NO_KEYWORDS
#endif
#include <QListWidget>
#include "rbx/signal.h"
#include "Reflection/Property.h"
class QListWidgetItem;
class InsertObjectListWidget: public QListWidget
{
Q_OBJECT
public:
InsertObjectListWidget(QWidget *pParent);
virtual ~InsertObjectListWidget();
void InsertObject(QListWidgetItem* item);
virtual void keyPressEvent(QKeyEvent *event);
virtual void sortItems(const QHash<QString, QVariant> &itemWeights = (QHash<QString, QVariant>()), QString filter = QString(), Qt::SortOrder order = Qt::AscendingOrder);
protected:
virtual bool event(QEvent *e);
Q_SIGNALS:
void enterKeyPressed(QListWidgetItem *pItem);
void itemInserted();
void helpTopicChanged(const QString& topic);
public Q_SLOTS:
void onItemInsertRequested(QListWidgetItem* item = NULL);
private Q_SLOTS:
void onSelectionChanged();
private:
virtual void mouseMoveEvent(QMouseEvent *event);
virtual void mousePressEvent(QMouseEvent *event);
void onPropertyChanged(const RBX::Reflection::PropertyDescriptor* pDescriptor);
QPoint m_dragStartPosition;
rbx::signals::scoped_connection m_PropertyChangedConnection;
};
@@ -0,0 +1,35 @@
/**
* InsertObjectListWidgetItem.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "InsertObjectListWidgetItem.h"
// Qt Headers
#include <QIcon>
// ROBLOX Headers
#include "QtUtilities.h"
#include "RobloxTreeWidget.h"
InsertObjectListWidgetItem::InsertObjectListWidgetItem(
const QString& name,
const QString& description,
boost::shared_ptr<RBX::Instance> instance,
std::string preferredParentName )
: m_instance(instance)
, m_preferredParent(preferredParentName)
{
setIcon(QIcon(QtUtilities::getPixmap(":/images/ClassImages.PNG", RobloxTreeWidgetItem::getImageIndex(m_instance))));
setText(name);
QString desc(description);
setToolTip(QtUtilities::wrapText(desc.replace(QRegExp("<a href.*(\\/a>|\\/>)"), ""), 80));
}
bool InsertObjectListWidgetItem::checkFilter(QString &filterString, RBX::Instance* pParent)
{
return pParent && m_instance && m_instance->canSetParent(pParent) && (filterString.isEmpty() || text().contains(filterString, Qt::CaseInsensitive));
}
+34
View File
@@ -0,0 +1,34 @@
/**
* InsertObjectListWidgetItem.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
//this will make sure that we can use qt with boost (else moc compiler errors)
#ifndef QT_NO_KEYWORDS
#define QT_NO_KEYWORDS
#endif
#include <QListWidgetItem>
#include "V8Tree/Instance.h"
class InsertObjectListWidgetItem : public QListWidgetItem
{
public:
InsertObjectListWidgetItem(
const QString& name,
const QString& description,
boost::shared_ptr<RBX::Instance> instance,
std::string preferredParentName );
boost::shared_ptr<RBX::Instance> getInstance() { return m_instance; }
std::string getPreferredParent() { return m_preferredParent; }
bool checkFilter(QString &filterString, RBX::Instance* pParent);
private:
boost::shared_ptr<RBX::Instance> m_instance;
std::string m_preferredParent;
};
+214
View File
@@ -0,0 +1,214 @@
/**
* InsertServiceDialog.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "InsertServiceDialog.h"
#include "V8DataModel/ChangeHistory.h"
#include "V8DataModel/DataModel.h"
// Qt Headers
#include <QPushButton>
#include <QDialogButtonBox>
#include <QTimer>
#include <QGridLayout>
// Roblox Headers
#include "CommonInsertWidget.h"
#include "InsertObjectListWidgetItem.h"
#include "InsertObjectListWidget.h"
InsertServiceDialog::InsertServiceDialog(QWidget *pParentWidget)
: QDialog(pParentWidget, Qt::WindowTitleHint | Qt::WindowSystemMenuHint)
, m_bRedrawRequested(false)
, m_bInitializationRequired(true)
{
setWindowTitle("Insert Service");
setObjectName("Insert Service");
m_pInsertObjectListWidget = new InsertObjectListWidget(this);
m_pInsertObjectListWidget->setAutoScroll(true);
m_pInsertObjectListWidget->setDragEnabled(true);
m_pInsertObjectListWidget->setDragDropMode(QAbstractItemView::DragOnly);
m_pInsertObjectListWidget->setSelectionMode(QAbstractItemView::SingleSelection);
m_pInsertObjectListWidget->setDropIndicatorShown(true);
m_pInsertObjectListWidget->setObjectName("InsertServiceListWidget");
m_pInsertObjectListWidget->setWrapping(true);
m_pInsertObjectListWidget->setResizeMode(QListView::Adjust);
m_pInsertObjectListWidget->setAlternatingRowColors(true);
m_pInsertButton = new QPushButton("Insert");
m_pInsertButton->setEnabled(false);
QDialogButtonBox* pDlgBtnBox = new QDialogButtonBox;
pDlgBtnBox->addButton(m_pInsertButton, QDialogButtonBox::AcceptRole);
pDlgBtnBox->addButton("Done", QDialogButtonBox::RejectRole);
QGridLayout *pGridLayout = new QGridLayout();
pGridLayout->setContentsMargins(0, 0, 0, 4);
pGridLayout->setHorizontalSpacing(4);
pGridLayout->addWidget(m_pInsertObjectListWidget, 0, 0);
pGridLayout->addWidget(pDlgBtnBox, 1, 0);
connect(pDlgBtnBox, SIGNAL(accepted()), this, SLOT(onAccepted()));
connect(pDlgBtnBox, SIGNAL(rejected()), this, SLOT(reject()));
connect(m_pInsertObjectListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(onItemInsertRequested(QListWidgetItem*)));
connect(m_pInsertObjectListWidget, SIGNAL(enterKeyPressed(QListWidgetItem*)), this, SLOT(onItemInsertRequested(QListWidgetItem*)));
connect(m_pInsertObjectListWidget, SIGNAL(itemSelectionChanged()), this, SLOT(onItemSelectionChanged()));
setLayout(pGridLayout);
resize(200,250);
}
InsertServiceDialog::~InsertServiceDialog()
{
m_pInsertObjectListWidget->clear();
}
void InsertServiceDialog::setDataModel(boost::shared_ptr<RBX::DataModel> pDataModel)
{
if(m_pDataModel == pDataModel)
return;
m_pInsertObjectListWidget->clear();
m_bInitializationRequired = true;
m_pDataModel = pDataModel;
if (m_pDataModel && isVisible())
{
requestDialogRedraw();
}
}
void InsertServiceDialog::updateWidget(bool state)
{
if (state)
requestDialogRedraw();
}
void InsertServiceDialog::setVisible(bool visible)
{
updateWidget(visible);
QWidget::setVisible(visible);
}
void InsertServiceDialog::recreateWidget()
{
m_pInsertObjectListWidget->clear();
m_pInsertObjectListWidget->setUpdatesEnabled(false);
InsertObjectWidget::createWidgets(true, m_pInsertObjectListWidget, &RBX::Reflection::ClassDescriptor::rootDescriptor(), m_pDataModel);
m_pInsertObjectListWidget->sortItems();
m_pInsertObjectListWidget->setUpdatesEnabled(true);
}
bool InsertServiceDialog::isAvailable()
{
m_bInitializationRequired = false;
recreateWidget();
if (m_pInsertObjectListWidget->count() > 0)
return true;
return false;
}
void InsertServiceDialog::onItemInsertRequested(QListWidgetItem* pListWidgetItem)
{
if(!pListWidgetItem)
return;
InsertObjectListWidgetItem* pInsertListWidgetItem = dynamic_cast<InsertObjectListWidgetItem*>(pListWidgetItem);
if(!pInsertListWidgetItem)
return;
try
{
RBX::DataModel::LegacyLock lock(m_pDataModel, RBX::DataModelJob::Write);
// The user requested a Service. Don't use the one we had already created.
// Instead, have the ServiceProvider create it if necessary
boost::shared_ptr<RBX::Instance> pService = RBX::ServiceProvider::create(m_pDataModel.get(), pInsertListWidgetItem->getInstance()->getClassName());
if (!pService)
throw std::runtime_error(QString("Failed to create Service object of type %s").arg(pInsertListWidgetItem->getInstance()->getClassNameStr().c_str()).toStdString());
// TODO -- currently this undo/redo doesn't work
//for undo redo --
std::string action = RBX::format("Insert %s", pService->getName().c_str());
RBX::ChangeHistoryService::requestWaypoint(action.c_str(), m_pDataModel.get());
}
catch(std::bad_alloc&)
{
throw;
}
catch(std::exception& e)
{
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e.what());
}
if (!isAvailable())
{
RobloxMainWindow* mainWindow = static_cast<RobloxMainWindow*>(this->parentWidget());
mainWindow->insertServiceAction->setEnabled(false);
}
accept();
}
void InsertServiceDialog::requestDialogRedraw()
{
if(m_bRedrawRequested || !m_pDataModel)
return;
QTimer::singleShot(0, this, SLOT(redrawDialog()));
m_bRedrawRequested = true;
}
void InsertServiceDialog::redrawDialog()
{
if (m_pDataModel)
{
if (m_bInitializationRequired)
{
m_bInitializationRequired = false;
recreateWidget();
}
int numChild = m_pInsertObjectListWidget->count();
int currentChild = 0;
InsertObjectListWidgetItem *pCurrentItem = NULL;
while (currentChild < numChild)
{
pCurrentItem = static_cast<InsertObjectListWidgetItem*>(m_pInsertObjectListWidget->item(currentChild));
if (pCurrentItem)
{
RBXASSERT(dynamic_cast<RBX::Service*>(pCurrentItem->getInstance().get()));
//RBX::StandardOut::singleton()->printf(RBX::MESSAGE_WARNING, "InsertService: current object = %s and visible = %d", pCurrentItem->getInstance()->getClassNameStr().c_str(), visible);
pCurrentItem->setHidden(false);
}
++currentChild;
}
}
m_bRedrawRequested = false;
}
void InsertServiceDialog::onAccepted()
{
if(m_pInsertObjectListWidget->currentItem())
onItemInsertRequested(m_pInsertObjectListWidget->currentItem());
}
void InsertServiceDialog::onItemSelectionChanged()
{
if(m_pInsertObjectListWidget->currentItem() && !m_pInsertButton->isEnabled())
{
m_pInsertButton->setEnabled(true);
}
}
+57
View File
@@ -0,0 +1,57 @@
/**
* InsertServiceDialog.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
#include <QDialog>
#include <boost/shared_ptr.hpp>
#include "V8DataModel/DataModel.h"
#include "RobloxMainWindow.h"
class InsertObjectListWidget;
class QListWidgetItem;
namespace RBX
{
class SelectionChanged;
}
class InsertServiceDialog : public QDialog
{
Q_OBJECT
public:
InsertServiceDialog(QWidget *pParentWidget);
virtual ~InsertServiceDialog();
void setDataModel(boost::shared_ptr<RBX::DataModel> pDataModel);
bool isAvailable();
public Q_SLOTS:
void updateWidget(bool state);
/*override*/ void setVisible(bool visible);
private Q_SLOTS:
void onAccepted();
void onItemInsertRequested(QListWidgetItem* item);
void onItemSelectionChanged();
void redrawDialog();
private:
void onInstanceSelectionChanged(const RBX::SelectionChanged& evt);
void requestDialogRedraw();
void recreateWidget();
boost::shared_ptr<RBX::DataModel> m_pDataModel;
InsertObjectListWidget *m_pInsertObjectListWidget;
QPushButton *m_pInsertButton;
bool m_bInitializationRequired;
bool m_bRedrawRequested;
};
+245
View File
@@ -0,0 +1,245 @@
<?xml version="1.0" encoding="UTF-8" ?>
<StudioAutocomplete>
<LuaLibrary name="math">
<StaticFunction name ="abs" tooltip="number math.abs(x)%1Returns the absolute value of x." />
<StaticFunction name ="acos" tooltip="number math.acos(x)%1Returns the arc cosine of x (in radians)." />
<StaticFunction name ="asin" tooltip="number math.asin(x)%1Returns the arc sine of x (in radians)." />
<StaticFunction name ="atan" tooltip="number math.atan(x)%1Returns the arc tangent of x (in radians)." />
<StaticFunction name ="atan2" tooltip="number math.atan2(y, x)%1Returns the arc tangent of y/x (in radians), but uses the signs of both parameters to find the quadrant of the result. (It also handles correctly the case of x being zero.)" />
<StaticFunction name ="ceil" tooltip="number math.ceil(x)%1Returns the smallest integer larger than or equal to x." />
<StaticFunction name ="cos" tooltip="number math.cos(x)%1Returns the cosine of x (assumed to be in radians)." />
<StaticFunction name ="cosh" tooltip="number math.cosh(x)%1Returns the hyperbolic cosine of x." />
<StaticFunction name ="deg" tooltip="number math.deg(x)%1Returns the angle x (given in radians) in degrees." />
<StaticFunction name ="exp" tooltip="number math.exp(x)%1Returns the value e^x." />
<StaticFunction name ="floor" tooltip="number math.floor(x)%1Returns the largest integer smaller than or equal to x." />
<StaticFunction name ="fmod" tooltip="number math.fmod(x, y)%1Returns the remainder of the division of x by y that rounds the quotient towards zero." />
<StaticFunction name ="frexp" tooltip="number math.frexp(x)%1Returns m and e such that x = m2^e, e is an integer and the absolute value of m is in the range [0.5, 1) (or zero when x is zero)." />
<StaticFunction name ="ldexp" tooltip="number math.ldexp(x, e)%1Returns m2^e (e should be an integer)." />
<StaticFunction name ="log" tooltip="number math.log(x)%1Returns the natural logarithm of x." />
<StaticFunction name ="log10" tooltip="number math.log10(x)%1Returns the base-10 logarithm of x." />
<StaticFunction name ="max" tooltip="number math.max(x, ...)%1Returns the maximum value among its arguments." />
<StaticFunction name ="min" tooltip="number math.min(x, ...)%1Returns the minimum value among its arguments." />
<StaticFunction name ="modf" tooltip="number math.modf(x)%1Returns two numbers, the integral part of x and the fractional part of x." />
<StaticFunction name ="noise" tooltip="number math.noise(x [, y [, z]])%1Returns a value between -0.5 and 0.5 generated from its arguments" />
<StaticFunction name ="pow" tooltip="number math.pow(x, y)%1Returns x^y. (You can also use the expression x^y to compute this value.)" />
<StaticFunction name ="rad" tooltip="number math.rad(x)%1Returns the angle x (given in degrees) in radians." />
<StaticFunction name ="random" tooltip="number math.random([m [, n]])%1This function is an interface to the simple pseudo-random generator function rand provided by ANSI C. (No guarantees can be given for its statistical properties.)%1%1When called without arguments, returns a uniform pseudo-random real number in the range [0,1). When called with an integer number m, math.random returns a uniform%1pseudo-random integer in the range [1, m]. When called with two integer numbers m and n, math.random returns a uniform pseudo-random integer in the range [m, n]." />
<StaticFunction name ="randomseed" tooltip="number math.randomseed(x)%1Sets x as the seed for the pseudo-random generator: equal seeds produce equal sequences of numbers." />
<StaticFunction name ="sin" tooltip="number math.sin(x)%1Returns the sine of x (assumed to be in radians)." />
<StaticFunction name ="sinh" tooltip="number math.sinh(x)%1Returns the hyperbolic sine of x." />
<StaticFunction name ="sqrt" tooltip="number math.sqrt(x)%1Returns the square root of x. (You can also use the expression x^0.5 to compute this value.)" />
<StaticFunction name ="tan" tooltip="number math.tan(x)%1Returns the tangent of x (assumed to be in radians)." />
<StaticFunction name ="tanh" tooltip="number math.tanh(x)%1Returns the hyperbolic tangent of x." />
<StaticProperty name="huge" tooltip="The value HUGE_VAL, a value larger than or equal to any other numerical value."/>
<StaticProperty name ="pi" tooltip="The value of pi."/>
</LuaLibrary>
<LuaLibrary name="string">
<StaticFunction name="byte" tooltip="string string.byte(s[,i[,j]])%1Returns the internal numerical codes of the characters s[i], s[i+1], ..., s[j]. The default value for i is 1; the default value for j is i. These indices are corrected following the same rules of function string.sub."/>
<StaticFunction name="char" tooltip="string string.char(...)%1Receives zero or more integers. Returns a string with length equal to the number of arguments, in which each character has the internal numerical code equal to its corresponding argument."/>
<StaticFunction name="dump" tooltip="string string.dump(function)%1Returns a string containing a binary representation of the given function, so that a later load on this string returns a copy of the function (but with new upvalues)."/>
<StaticFunction name="find" tooltip="number string.find(s, pattern [, init [, plain]])%1Looks for the first match of pattern in the string s. If it finds a match, then find returns the indices of s where this occurrence starts and ends; otherwise, it returns nil. A third, optional numerical argument init specifies where to start the search; its default value is 1 and can be negative. A value of true as a fourth, optional argument plain turns off the pattern matching facilities, so the function does a plain 'find='' substring=''' operation"/>
<StaticFunction name="format" tooltip="string string.format(formatstring, ...)%1Returns a formatted version of its variable number of arguments following the description given in its first argument (which must be a string)."/>
<StaticFunction name="gmatch" tooltip="function string.gmatch(s, pattern)%1Returns an iterator function that, each time it is called, returns the next captures from pattern over the string s."/>
<StaticFunction name="gsub" tooltip="string string.gsub(s, pattern, repl [, n])%1Returns a copy of s in which all (or the first n, if given) occurrences of the pattern have been replaced by a replacement string specified by repl, which can be a string, a table, or a function. gsub also returns, as its second value, the total number of matches that occurred."/>
<StaticFunction name="len" tooltip="number string.len(s)%1Receives a string and returns its length."/>
<StaticFunction name="lower" tooltip="string string.lower(s)%1Receives a string and returns a copy of this string with all uppercase letters changed to lowercase."/>
<StaticFunction name="rep" tooltip="string string.rep(s, n [, sep])%1Returns a string that is the concatenation of n copies of the string s separated by the string sep."/>
<StaticFunction name="reverse" tooltip="string string.reverse(s)%1Returns a string that is the string s reversed."/>
<StaticFunction name="sub" tooltip="string string.sub(s, i [, j])%1Returns the substring of s that starts at i and continues until j; i and j can be negative. If j is absent, then it is assumed to be equal to -1 (which is the same as the string length). "/>
<StaticFunction name="upper" tooltip="string string.upper(s)%1Receives a string and returns a copy of this string with all lowercase letters changed to uppercase. All other characters are left unchanged."/>
</LuaLibrary>
<LuaLibrary name="table">
<StaticFunction name="concat" tooltip="string table.concat(list [, sep [, i [, j]]])%1Given a list where all elements are strings or numbers, returns the string list[i]..sep..list[i+1] ... sep..list[j]. The default value for sep is the empty string, the default for i is 1, and the default for j is #list. If i is greater than j, returns the empty string."/>
<StaticFunction name="foreach" tooltip="void table.foreach(table, f)%1Apply the function f to the elements of the table passed" />
<StaticFunction name="foreachi" tooltip="void table.foreachi(table, f)%1This is similar to table.foreach() except that index-value pairs are passed, not key-value pairs." />
<StaticFunction name="getn" tooltip="number table.getn(table)%1Returns the number of elements in the table passed" />
<StaticFunction name="insert" tooltip="void table.insert(list, [pos,] value)%1Inserts element value at position pos in list, shifting up the elements list[pos], list[pos+1], ..., list[#list]. The default value for pos is #list+1, so that a call table.insert(t,x) inserts x at the end of list t."/>
<StaticFunction name="remove" tooltip="value table.remove(list [, pos])%1Removes from list the element at position pos, returning the value of the removed element. When pos is an integer between 1 and #list, it shifts down the elements list[pos+1], list[pos+2], ..., list[#list] and erases element list[#list]; The index pos can also be 0 when #list is 0, or #list + 1; in those cases, the function erases the element list[pos]."/>
<StaticFunction name="sort" tooltip="bool table.sort(list [, comp])%1Sorts list elements in a given order, in-place, from list[1] to list[#list]. If comp is given, then it must be a function that receives two list elements and returns true when the first element must come before the second in the final order (so that not comp(list[i+1],list[i]) will be true after the sort). If comp is not given, then the standard Lua operator [less than] is used instead."/>
</LuaLibrary>
<LuaLibrary name="coroutine">
<StaticFunction name="create" tooltip="thread coroutine.create(f)%1Creates a new coroutine, with body f. f must be a Lua function."/>
<StaticFunction name="resume" tooltip="bool coroutine.resume(co [, val1, ...])%1Starts or continues the execution of coroutine co. The first time you resume a coroutine, it starts running its body. The values val1, ... are passed as the arguments to the body function. If the coroutine has yielded, resume restarts it; the values val1, ... are passed as the results from the yield. If the coroutine runs without any errors, resume returns true plus any values passed to yield (if the coroutine yields) or any values returned by the body function (if the coroutine terminates). If there is any error, resume returns false plus the error message."/>
<StaticFunction name="running" tooltip="thread coroutine.running()%1Returns the running coroutine plus a boolean, true when the running coroutine is the main one."/>
<StaticFunction name="status" tooltip="string coroutine.status(co)%1Returns the status of coroutine co, as a string: 'running', if the coroutine is running (that is, it called status); 'suspended', if the coroutine is suspended in a call to yield, or if it has not started running yet; 'normal' if the coroutine is active but not running (that is, it has resumed another coroutine); and 'dead' if the coroutine has finished its body function, or if it has stopped with an error."/>
<StaticFunction name="wrap" tooltip="function coroutine.wrap(f)%1Creates a new coroutine, with body f. f must be a Lua function. Returns a function that resumes the coroutine each time it is called. Any arguments passed to the function behave as the extra arguments to resume. Returns the same values returned by resume, except the first boolean. In case of error, propagates the error."/>
<StaticFunction name="yield" tooltip="void coroutine.yield(...)%1Suspends the execution of the calling coroutine. Any arguments to yield are passed as extra results to resume."/>
</LuaLibrary>
<ItemStruct name="Instance">
<StaticFunction name="new" tooltip="Instance Instance.new(val [, parent])%1Creates an new object of type val. The parent argument is optional; If it is supplied, the object will be parented to that object"/>
</ItemStruct>
<LuaLibrary name="os">
<StaticFunction name="time" tooltip="number os.time()%1Returns the number of seconds since the epoch (1 January 1970, 00:00:00)" />
<StaticFunction name="difftime" tooltip="number os.difftime(number t1, number t2)%1Returns the number of seconds from t1 to t2" />
</LuaLibrary>
<ItemStruct name="Vector2">
<StaticFunction name="new" tooltip="Vector2 Vector2.new(x, y)%1Creates a new Vector2 using ordinates x and y"/>
<Property name="X" tooltip="Number Vector2.X%1The x-coordinate"/>
<Property name="Y" tooltip="Number Vector2.Y%1The y-coordinate"/>
<Property name="unit" tooltip="Vector2 Vector2.unit%1A normalized copy of the vector"/>
<Property name="magnitude" tooltip="Number Vector2.magnitudeThe length of the vector"/>
<Function name="lerp" tooltip="Vector2 Vector2:lerp(Vector2 v, number alpha)%1Returns a Vector2 linearly interpolated between this Vector2 and v by the fraction alpha"/>
</ItemStruct>
<ItemStruct name="Vector2int16">
<StaticFunction name="new" tooltip="Vector2int16 Vector2int16.new(x, y)%1Creates a new Vector2int16 using ordinates x and y. Similar to Vector2, but uses integral coordinates"/>
<Property name="X" tooltip="Number Vector2int16.X%1The x-coordinate"/>
<Property name="Y" tooltip="Number Vector2int16.Y%1The y-coordinate"/>
</ItemStruct>
<ItemStruct name="Vector3">
<StaticFunction name="new" tooltip="Vector3 Vector3.new(x, y, z)%1Constructs a new Vector3 using coordinates x, y, z." />
<StaticFunction name="FromNormalId" tooltip="Vector3 Vector3.FromNormalID(Enum.NormalId normalId)%1Constructs a new Vector3 in a particular direction." />
<StaticFunction name="FromAxis" tooltip="Vector3 Vector3.FromAxis(Enum.Axis axis)%1Constructs a new Vector3 for a particular axis." />
<Property name="X" tooltip="number Vector3.X%1The x-coordinate" />
<Property name="Y" tooltip="number Vector3.Y%1The y-coordinate" />
<Property name="Z" tooltip="number Vector3.Z%1The z-coordinate" />
<Property name="Unit" tooltip="Vector3 Vector3.Unit%1A normalized copy of the vector - one which has the same direction as the original but a magnitude of 1"/>
<Property name="Magnitude" tooltip="Number Vector3.Magnitude%1The length of the vector"/>
<Function name="Lerp" tooltip="Vector3 Vector3:Lerp(Vector3 goal, number alpha)%1Returns a Vector3 linearly interpolated between this Vector3 and the goal by the fraction alpha"/>
<Function name="Dot" tooltip="number Vector3:Dot(Vector3 other)%1Returns a scalar dot product of the two vectors"/>
<Function name="Cross" tooltip="Vector3 Vector3:Cross(Vector3 other)%1Returns the cross product of the two vectors"/>
<Function name="isClose" tooltip="bool Vector3:isClose(Vector3 other [, number epsilon]%1"/>
</ItemStruct>
<ItemStruct name="Vector3int16">
<StaticFunction name="new" tooltip="Vector3int16 Vector3int16.new(x, y, z)%1Creates a new Vector3int16 using coordinate x, y, z."/>
<Property name="X" tooltip="Number Vector3int16.X%1The x-coordinate" />
<Property name="Y" tooltip="Number Vector3int16.Y%1The y-coordinate" />
<Property name="Z" tooltip="Number Vector3int16.Z%1The z-coordinate" />
</ItemStruct>
<ItemStruct name="CFrame">
<StaticFunction name="new" tooltip="CFrame CFrame.new(number x, number y, number z)%1Creates a CFrame from position (x, y, z) &lt;a href=&#34;http://wiki.roblox.com/index.php/CFrame&#34;&gt;More Info&lt;/a&gt;"/>
<StaticFunction name="fromEulerAnglesXYZ" tooltip="CFrame CFrame.fromEulerAnglesXYZ(number rx, number ry, number rz)%1Creates a rotated CFrame using angles (rx, ry, rz) in radians"/>
<StaticFunction name="Angles" tooltip="CFrame CFrame.Angles(number rx, number ry, number rz)%1Equivalent to fromEulerAnglesXYZ"/>
<StaticFunction name="fromAxisAngle" tooltip="CFrame CFrame.fromAxisAngle(Vector3 v, number r)%1Creates a rotated CFrame from a Unit Vector3 and a rotation in radians"/>
<Property name="p" tooltip="Vector3 CFrame.p%1The 3D position of the CFrame"/>
<Property name="X" tooltip="Number CFrame.X%1The x-coordinate of the position" />
<Property name="Y" tooltip="Number CFrame.Y%1The y-coordinate of the position" />
<Property name="Z" tooltip="Number CFrame.Z%1The z-coordinate of the position" />
<Property name="lookVector" tooltip="Vector3 CFrame.lookVector%1Returns the facing direction"/>
<Function name="inverse" tooltip="CFrame CFrame:inverse()%1Returns the inverse of this CFrame"/>
<Function name="lerp" tooltip="CFrame CFrame:lerp(CFrame goal, float alpha)%1Returns a CFrame interpolated between this CFrame and the goal by the fraction alpha"/>
<Function name="toWorldSpace" tooltip="CFrame CFrame:toWorldSpace(CFrame cf)%1Returns a CFrame transformed from Object to World space. Equivalent to [CFrame * cf]"/>
<Function name="toObjectSpace" tooltip="CFrame CFrame:toObjectSpace(CFrame cf)%1Returns a CFrame transformed from World to Object space. Equivalent to [CFrame:inverse() * cf]"/>
<Function name="pointToWorldSpace" tooltip="Vector3 CFrame:pointToWorldSpace(Vector3 v3)%1Returns a Vector3 transformed from Object to World space. Equivalent to [CFrame * v3]"/>
<Function name="pointToObjectSpace" tooltip="Vector3 CFrame:pointtoObjectSpace(Vector3 v3)%1Returns a Vector3 transformed from World to Object space. Equivalent to [CFrame:inverse() * v3]"/>
<Function name="vectorToWorldSpace" tooltip="Vector3 CFrame:vectorToWorldSpace(Vector3 v3)%1Returns a Vector3 rotated from Object to World space. Equivalent to [(CFrame - CFrame.p) *v3]"/>
<Function name="vectorToObjectSpace" tooltip="Vector3 CFrame:vectorToObjectSpace(Vector3 v3)%1Returns a Vector3 rotated from World to Object space.%1Equivalent to [(CFrame:inverse() - CFrame:inverse().p) * v3]"/>
<Function name="components" tooltip="... CFrame:components()%1Returns the values: x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22"/>
<Function name="toEulerAnglesXYZ" tooltip="... CFrame:toEulerAnglesXYZ()%1Returns approximate angles that could be used to generate CFrame"/>
</ItemStruct>
<ItemStruct name="Region3">
<StaticFunction name="new" tooltip="Region3 Region3.new(Vector3, Vector3)%1Creates a new Region3 out of two Vector3 values."/>
<Property name="CFrame" tooltip="CFrame%1The center location and rotation of the Region3"/>
<Property name="Size" tooltip="Vector3%1The 3D size of the Region3"/>
</ItemStruct>
<ItemStruct name="Region3int16">
<StaticFunction name="new" tooltip="Region3int16 Region3int16.new(Vector3int16, Vector3int16)%1Creates a new Region3int16 out of two Vector3int16 structs"/>
</ItemStruct>
<ItemStruct name="BrickColor">
<StaticFunction name="new" tooltip="BrickColor BrickColor.new(r, g, b)%1Returns a BrickColor from the values r, g and b. &lt;a href=&#34;http://wiki.roblox.com/index.php/BrickColor&#34;&gt;More Info&lt;/a&gt;"/>
<StaticFunction name="palette" tooltip="BrickColor BrickColor.palette(val)%1Returns a BrickColor from val"/>
<StaticFunction name="Random" tooltip="BrickColor BrickColor.Random()%1Returns a random BrickColor"/>
<StaticFunction name="White" tooltip="BrickColor BrickColor.White()%1Returns the BrickColor white"/>
<StaticFunction name="Gray" tooltip="BrickColor BrickColor.Gray()%1Returns the BrickColor Medium stone grey"/>
<StaticFunction name="DarkGray" tooltip="BrickColor BrickColor.DarkGray()%1Returns the BrickColor Dark stone grey" />
<StaticFunction name="Black" tooltip="BrickColor BrickColor.Black()%1Returns the BrickColor Black" />
<StaticFunction name="Red" tooltip="BrickColor BrickColor.Red()%1Returns the BrickColor Bright Red" />
<StaticFunction name="Yellow" tooltip="BrickColor BrickColor.Yellow()%1Returns the BrickColor Bright Yellow" />
<StaticFunction name="Green" tooltip="BrickColor BrickColor.Green()%1Returns the BrickColor Dark Green" />
<StaticFunction name="Blue" tooltip="BrickColor BrickColor.Blue()%1Returns the BrickColor Bright Blue" />
<Property name="Number" tooltip="Number BrickColor.Number%1The unique number that identifies the BrickColor"/>
<Property name="Name" tooltip="String BrickColor.Name%1The name associated with the BrickColor"/>
<Property name="Color" tooltip="Color3 BrickColor.Color%1The Color3 associated with the BrickColor"/>
<Property name="r" tooltip="Number BrickColor.R%1The red component (between 0 and 1)"/>
<Property name="g" tooltip="Number BrickColor.G%1The green component (between 0 and 1)"/>
<Property name="b" tooltip="Number BrickColor.B%1The blue component (between 0 and 1)"/>
</ItemStruct>
<ItemStruct name="Ray">
<StaticFunction name="new" tooltip="Ray Ray.new(Vector3 Origin, Vector3 Direction)%1Creates a new Ray with given Origin and Direction"/>
<Property name="Origin" tooltip="Vector3 Ray.Origin%1The position of the origin"/>
<Property name="Direction" tooltip="Vector3 Ray.Direction%1The direction vector of the ray"/>
<Property name="Unit" tooltip="Ray Ray.Unit%1The Ray with a normalized direction"/>
<Function name="ClosestPoint" tooltip="Vector3 Ray:ClosestPoint(Vector3 point)%1Returns the closest point on the Ray to point. Note Rays are unidirectional"/>
<Function name="Distance" tooltip="Number Ray:Distance(Vector3 point)%1 Returns the distance from point to ClosestPoint(point)"/>
</ItemStruct>
<ItemStruct name="UDim">
<StaticFunction name="new" tooltip="UDim UDim.new(number Scale, number Offset)%1Creates a new UDim from components"/>
<Property name="Scale" tooltip="Number UDim.Scale%1The scale value"/>
<Property name="Offset" tooltip="Number UDim.Offset%1The offset value"/>
</ItemStruct>
<ItemStruct name="UDim2">
<StaticFunction name="new" tooltip="UDim2 UDim2.new(Number XScale, Number XOffset, Number YScale, Number YOffset)%1"/>
<Property name="X" tooltip="The x dimension scale and offset" />
<Property name="Y" tooltip="The y dimension scale and offset" />
</ItemStruct>
<ItemStruct name="Color3">
<StaticFunction name="new" tooltip="Color3 Color3.new(r, g, b)%1Returns a Color3 from the values r, g and b. &lt;a href=&#34;http://wiki.roblox.com/index.php/Color3&#34;&gt;More Info&lt;/a&gt;"/>
<Property name="r" tooltip="Number Color3.r%1The red value of the color"/>
<Property name="g" tooltip="Number Color3.g%1The green value of the color"/>
<Property name="b" tooltip="Number Color3.b%1The blue value of the color"/>
</ItemStruct>
<ItemStruct name="ColorSequence">
<StaticFunction name="new" tooltip="ColorSequence ColorSequence.new(Color3 c)%1Creates a sequence of two keypoints with `c` for each value"/>
<StaticFunction name="new" tooltip="ColorSequence ColorSequence.new(Color3 c0, Color3 c1)%1Creates a sequence of two keypoints with `c0` and `c1` as the value"/>
<Property name="Keypoints" tooltip="Array ColorSequence.Keypoints%1An array containing keypoint values for the ColorSequence"/>
</ItemStruct>
<ItemStruct name="NumberSequence">
<StaticFunction name="new" tooltip="NumberSequence NumberSequence.new(float n)%1Creates a sequence of two keypoints with `n` for each value"/>
<StaticFunction name="new" tooltip="NumberSequence NumberSequence.new(float n0, float n1)%1Creates a sequence of two keypoints with `n0` and `n1` as the value"/>
<StaticFunction name="new" tooltip="NumberSequence NumberSequence.new(Array keypoints)%1Creates a sequence from a list of keypoints"/>
<Property name="Keypoints" tooltip="Array NumberSequence.Keypoints%1An array containing keypoint values for the NumberSequence"/>
</ItemStruct>
<ItemStruct name="NumberRange">
<StaticFunction name="new" tooltip="NumberRange NumberRange.new(float value)%1Creates a new NumberRange with the same minimum and maximum"/>
<StaticFunction name="new" tooltip="NumberRange NumberRange.new(float min, float max)%1Creates a new NumberRange with the provided minimum and maximum."/>
<Property name="Min" tooltip="float NumberRange.Min%1Minimum value. Will always be less than or equal to the maximum."/>
<Property name="Max" tooltip="float NumberRange.Max%1Maximum value. Will always be greater than or equal to the minimum."/>
</ItemStruct>
<ItemStruct name="Faces">
<StaticFunction name="new" tooltip="Faces Faces.new(NormalId, NormalId, ...)%1Creates a new Faces using list of faces" />
<Property name="Top" tooltip="bool Faces.Top%1Whether the top face is included" />
<Property name="Bottom" tooltip="bool Faces.Bottom%1Whether the bottom face is included" />
<Property name="Left" tooltip="bool Faces.Left%1Whether the left face is included" />
<Property name="Right" tooltip="bool Faces.Right%1Whether the right face is included" />
<Property name="Back" tooltip="bool Faces.Back%1Whether the back face is included" />
<Property name="Front" tooltip="bool Faces.Front%1Whether the front face is included" />
</ItemStruct>
<ItemStruct name="Axes">
<StaticFunction name="new" tooltip="Axes Axes.new(Axis\NormalId, Axis/NormalId, ...)%1Creates a new Axes using list of axes and/or faces. NormalIds (faces) are converted to the corresponding axes." />
<Property name="X" tooltip="bool Axes.X%1Whether the X axis is enabled" />
<Property name="Y" tooltip="bool Axes.Y%1Whether the Y axis is enabled" />
<Property name="Z" tooltip="bool Axes.Z%1Whether the Z axis is enabled" />
<Property name="Top" tooltip="bool Axes.Top%1Whether the top face is included" />
<Property name="Bottom" tooltip="bool Axes.Bottom%1Whether the bottom face is included" />
<Property name="Left" tooltip="bool Axes.Left%1Whether the left face is included" />
<Property name="Right" tooltip="bool Axes.Right%1Whether the right face is included" />
<Property name="Back" tooltip="bool Axes.Back%1Whether the back face is included" />
<Property name="Front" tooltip="bool Axes.Front%1Whether the front face is included" />
</ItemStruct>
<ItemStruct name="ColorSequenceKeypoint">
<StaticFunction name="new" tooltip="ColorSequenceKeypoint ColorSequenceKeypoint.new(float time, Color3 color)%1Creates a keypoint with a specified time and color." />
<Property name="Time" tooltip="float ColorSequenceKeypoint.Time%1The relative time at which the keypoint is located." />
<Property name="Value" tooltip="Color3 ColorSequenceKeypoint.Value%1The Color3 value at the keypoint." />
</ItemStruct>
<ItemStruct name="NumberSequenceKeypoint">
<StaticFunction name="new" tooltip="NumberSequenceKeypoint NumberSequenceKeypoint.new(float time, float value)%1Creates a keypoint with a specified time and value." />
<StaticFunction name="new" tooltip="NumberSequenceKeypoint NumberSequenceKeypoint.new(float time, float value, float envelop)%1Creates a keypoint with a specified time, value, and envelope." />
<Property name="Envelope" tooltip="float NumberSequenceKeypoint.Envelope%1Indicates the amount of variance allowed from the Value. A computed value." />
<Property name="Time" tooltip="float NumberSequenceKeypoint.Time%1The relative time at which the keypoint is positioned." />
</ItemStruct>
<ItemStruct name="Rect">
<StaticFunction name="new" tooltip="Rect Rect.new(min, max)%1Constructs a new Rect with Vector2 min as top left corner and Vector2 max as bottom right corner." />
<Property name="Min" tooltip="Number Rect.Min%1The top-left corner" />
<Property name="Max" tooltip="Number Rect.Max%1The bottom-right corner" />
<Property name="Width" tooltip="Number Rect.Width%1The x width of rect" />
<Property name="Height" tooltip="Number Rect.Height%1The y height of rect"/>
</ItemStruct>
<ItemStruct name="PhysicalProperties">
<StaticFunction name="new" tooltip="PhysicalProperties PhysicalProperties.new(material)%1 Constructs a new PhysicalProperties container from the Enum.Material object." />
<StaticFunction name="new" tooltip="PhysicalProperties PhysicalProperties.new(density, friction, elasticity, frictionWeight, elasticityWeight)%1Constructs a new PhysicalProperties container." />
<Property name="Density" tooltip="Number PhysicalProperties.Density%1Density" />
<Property name="Friction" tooltip="Number PhysicalProperties.Friction%1Friction" />
<Property name="Elasticity" tooltip="Number PhysicalProperties.Elasticity%1Elasticity" />
<Property name="FrictionWeight" tooltip="Number PhysicalProperties.FrictionWeight%1FrictionWeight determines how dominant the object's friction is when interacting with other objects. Default = 1"/>
<Property name="ElasticityWeight" tooltip="Number PhysicalProperties.ElasticityWeight%1ElasticityWeight determins how dominant the object's elasticity is when interacting with other objects. Default = 1"/>
</ItemStruct>
</StudioAutocomplete>
+104
View File
@@ -0,0 +1,104 @@
/**
* KeepSavedChangesDialog.cpp
* Copyright (c) 2014 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "KeepSavedChangesDialog.h"
#include "AuthenticationHelper.h"
#include "RobloxDocManager.h"
#include "RobloxStudioVerbs.h"
#include "RobloxIDEDoc.h"
#include "V8DataModel/DataModel.h"
#include "RobloxMainWindow.h"
#include "Util/RobloxGoogleAnalytics.h"
// Qt Headers
#include <QMessageBox>
KeepSavedChangesDialog::KeepSavedChangesDialog(QWidget* parent)
: QMessageBox(parent)
, dialogResult(Cancel)
{
setAttribute(Qt::WA_DeleteOnClose,false);
setIcon(QMessageBox::Question);
QPushButton* button = addButton("Save Online", QMessageBox::AcceptRole);
button->setDefault(true);
addButton("Discard", QMessageBox::DestructiveRole);
addButton("Cancel", QMessageBox::RejectRole);
setText("Would you like to save these changes?");
connect(this, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(onClicked(QAbstractButton*)));
}
KeepSavedChangesDialog::~KeepSavedChangesDialog()
{
}
void KeepSavedChangesDialog::onClicked(QAbstractButton* button)
{
if (buttonRole(button) == QMessageBox::AcceptRole)
onSave();
else if (buttonRole(button) == QMessageBox::DestructiveRole)
onDiscard();
else
onCancel();
}
void KeepSavedChangesDialog::onSave()
{
if (!AuthenticationHelper::Instance().verifyUserAndAuthenticate() ||
!AuthenticationHelper::validateMachine())
{
dialogResult = Fail;
close();
return;
}
try
{
RobloxIDEDoc* pIDEDoc = RobloxDocManager::Instance().getPlayDoc();
if (!pIDEDoc)
{
dialogResult = Fail;
close();
return;
}
// need to save with edit datamodel
shared_ptr<RBX::DataModel> dataModel = pIDEDoc->getEditDataModel();
PublishToRobloxVerb* pPublishVerb = dataModel ? dynamic_cast<PublishToRobloxVerb*>(dataModel->getVerb("PublishToRobloxVerb")) : NULL;
if (!pPublishVerb)
{
dialogResult = Fail;
close();
return;
}
pPublishVerb->doIt(dataModel.get());
}
catch (std::exception&)
{
RobloxMainWindow::sendCounterEvent("QTStudioPublishSaveFailure");
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ERROR, "StudioPublishSaveFailure");
dialogResult = Fail;
}
dialogResult = Save;
close();
}
void KeepSavedChangesDialog::onDiscard()
{
dialogResult = Discard;
close();
}
void KeepSavedChangesDialog::onCancel()
{
dialogResult = Cancel;
close();
}
+37
View File
@@ -0,0 +1,37 @@
/**
* KeepSavedChangesDialog.h
* Copyright (c) 2014 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
#include <QMessageBox>
class QAbstractButton;
class KeepSavedChangesDialog : public QMessageBox
{
Q_OBJECT
public:
KeepSavedChangesDialog(QWidget* Parent);
virtual ~KeepSavedChangesDialog();
enum DialogResult
{
Save,
Discard,
Fail,
Cancel
};
enum DialogResult getResult() { return dialogResult; }
public Q_SLOTS:
void onClicked(QAbstractButton* button);
void onSave();
void onDiscard();
void onCancel();
private:
enum DialogResult dialogResult;
};
+80
View File
@@ -0,0 +1,80 @@
/*
* LogProvider.cpp
* RobloxStudio
*
* Created by Ganesh Agrawal on 12/12/11.
* Copyright 2011 __MyCompanyName__. All rights reserved.
*
*/
// Roblox Headers
#include "util/FileSystem.h"
#include "util/Guid.h"
#include "rbx/boost.hpp"
// Roblox Studio Headers
#include "LogProvider.h"
#ifdef __APPLE__
#include "BreakpadCrashReporter.h"
#endif
RBX::mutex LogProvider::fastLogChannelsLock;
LogProvider* LogProvider::mainLogManager;
LogProvider::LogProvider()
{
logDir = RBX::FileSystem::getLogsDirectory();
RBX::Guid::generateRBXGUID(logGuid);
mainLogManager = this;
FLog::SetExternalLogFunc(LogProvider::FastLogMessage);
}
void LogProvider::FastLogMessage(FLog::Channel id, const char* message) {
RBX::mutex::scoped_lock lock(fastLogChannelsLock);
if(mainLogManager)
{
if(id >= mainLogManager->fastLogChannels.size())
mainLogManager->fastLogChannels.resize(id+1, NULL);
if(mainLogManager->fastLogChannels[id] == NULL)
{
char temp[20];
snprintf(temp, 19, "log_%s_%u.txt", mainLogManager->logGuid.substr(3,6).c_str(), id);
temp[19] = 0;
boost::filesystem::path logFile = mainLogManager->logDir / temp;
mainLogManager->fastLogChannels[id] = new RBX::Log(logFile.c_str(), "Log Channel");
#ifndef _DEBUG
CrashReporter::addBreakpadLog(logFile.c_str());
#endif
}
mainLogManager->fastLogChannels[id]->writeEntry(RBX::Log::Information, message);
}
}
LogProvider::~LogProvider()
{
}
RBX::Log* LogProvider::provideLog()
{
RBX::Log* result = log.get();
if (!result)
{
std::string name = RBX::get_thread_name();
boost::filesystem::path logFile = logDir / ("log_" + logGuid.substr(3,6) +".txt");
result = new RBX::Log(logFile.c_str(), name.c_str());
log.reset(result);
#ifndef _DEBUG
CrashReporter::addBreakpadLog(logFile.c_str());
#endif
}
return result;
}
+35
View File
@@ -0,0 +1,35 @@
/*
* LogProvider.h
* RobloxStudio
*
* Created by Ganesh Agrawal on 12/12/11.
* Copyright 2011 __MyCompanyName__. All rights reserved.
*
*/
#pragma once
// 3rd Party Headers
#include "boost/filesystem.hpp"
#include "boost/thread/tss.hpp"
// Roblox Headers
#include "rbx/Log.h"
#include "rbx/threadsafe.h"
class LogProvider : public RBX::ILogProvider
{
static RBX::mutex fastLogChannelsLock;
static LogProvider* mainLogManager;
std::vector<RBX::Log*> fastLogChannels;
static void FastLogMessage(FLog::Channel id, const char* message);
private:
boost::thread_specific_ptr<RBX::Log> log;
boost::filesystem::path logDir;
std::string logGuid;
public:
LogProvider();
~LogProvider();
virtual RBX::Log* provideLog();
};
+244
View File
@@ -0,0 +1,244 @@
#include "stdafx.h"
#include "LuaSourceBuffer.h"
#include "Script/ModuleScript.h"
#include "Script/Script.h"
#include "Script/ScriptContext.h"
#include "Util/ContentId.h"
#include "Util/ProtectedString.h"
#include "V8DataModel/ContentProvider.h"
#include "V8DataModel/DataModel.h"
#include "V8Tree/Service.h"
#include "RobloxDocManager.h"
#include "RobloxGameExplorer.h"
#include "RobloxIDEDoc.h"
#include "RobloxSettings.h"
#include "UpdateUIManager.h"
using namespace boost;
using namespace RBX;
LuaSourceBuffer LuaSourceBuffer::fromInstance(shared_ptr<Instance> instance)
{
if (shared_ptr<Script> script = Instance::fastSharedDynamicCast<Script>(instance))
{
return LuaSourceBuffer(script);
}
else if (shared_ptr<ModuleScript> moduleScript = Instance::fastSharedDynamicCast<ModuleScript>(instance))
{
return LuaSourceBuffer(moduleScript);
}
else
{
return LuaSourceBuffer();
}
}
LuaSourceBuffer LuaSourceBuffer::fromContentId(const ContentId& contentId)
{
if (!contentId.isNull())
{
return LuaSourceBuffer(contentId);
}
else
{
return LuaSourceBuffer();
}
}
HttpFuture LuaSourceBuffer::getScriptAssetSource(const ContentId& contentId, int universeId)
{
RobloxGameExplorer& rge = UpdateUIManager::Instance().getViewWidget<RobloxGameExplorer>(eDW_GAME_EXPLORER);
if (boost::optional<std::string> source = rge.findCachedAssetOrEmpty(contentId, universeId))
{
boost::promise<std::string> prom;
prom.set_value(source.get());
return HttpFuture(prom.get_future());
}
ContentId copy = contentId;
const std::string baseUrl = RobloxSettings::getBaseURL().toStdString();
copy.convertAssetId(baseUrl, universeId);
copy.convertToLegacyContent(baseUrl);
HttpOptions options;
options.setDoNotUseCachedResponse();
return HttpAsync::get(copy.toString(), options);
}
LuaSourceBuffer::LuaSourceBuffer()
{
RBXASSERT(empty());
}
LuaSourceBuffer::LuaSourceBuffer(shared_ptr<Script> script)
: script(script)
{
}
LuaSourceBuffer::LuaSourceBuffer(shared_ptr<ModuleScript> moduleScript)
: moduleScript(moduleScript)
{
}
LuaSourceBuffer::LuaSourceBuffer(const ContentId& contentId)
: contentId(contentId)
{
RBXASSERT(!contentId.isNull());
}
shared_ptr<LuaSourceContainer> LuaSourceBuffer::toInstance() const
{
return script ? shared_ptr<LuaSourceContainer>(script) : shared_ptr<LuaSourceContainer>(moduleScript);
}
bool LuaSourceBuffer::isNamedAsset() const
{
return contentId.isNamedAsset();
}
bool LuaSourceBuffer::isModuleScript() const
{
return moduleScript != NULL;
}
void LuaSourceBuffer::reloadLiveScript()
{
if (moduleScript)
{
DataModel* dm = DataModel::get(moduleScript.get());
dm->create<ScriptContext>()->reloadModuleScript(moduleScript);
}
else if (script)
{
DataModel* dm = DataModel::get(script.get());
if (!script->getDisabled())
{
script->setDisabled(true);
script->setDisabled(false);
}
}
}
bool LuaSourceBuffer::isCodeEmbedded() const
{
if (script)
return script->isCodeEmbedded();
if (moduleScript)
return true;
return false;
}
bool LuaSourceBuffer::sourceAvailable() const
{
if (empty())
return false;
if (script || moduleScript)
return true;
try
{
getScriptText();
return true;
}
catch (const RBX::base_exception&)
{
return false;
}
}
std::string LuaSourceBuffer::getScriptText() const
{
RBXASSERT(!empty());
if (script)
{
return script->getEmbeddedCodeSafe().getSource();
}
else if (moduleScript)
{
return moduleScript->getSource().getSource();
}
else if (!contentId.isNull())
{
return getScriptAssetSource(contentId, UpdateUIManager::Instance()
.getViewWidget<RobloxGameExplorer>(eDW_GAME_EXPLORER).getCurrentGameId()).get();
}
else
{
RBXASSERT(false);
return "";
}
}
void LuaSourceBuffer::setScriptText(const std::string& newText)
{
RBXASSERT(!empty());
if (script)
{
script->setEmbeddedCode(ProtectedString::fromTrustedSource(newText));
}
else if (moduleScript)
{
moduleScript->setSource(ProtectedString::fromTrustedSource(newText));
}
else if (!contentId.isNull())
{
UpdateUIManager::Instance().getViewWidget<RobloxGameExplorer>(eDW_GAME_EXPLORER)
.updateAsset(contentId, newText);
}
}
std::string LuaSourceBuffer::getScriptName() const
{
RBXASSERT(!empty());
if (shared_ptr<Instance> inst = toInstance())
{
return inst->getName();
}
else
{
if (contentId.isNamedAsset())
{
return contentId.getAssetName();
}
else
{
return contentId.toString();
}
}
}
std::string LuaSourceBuffer::getFullName() const
{
RBXASSERT(!empty());
if (shared_ptr<RBX::Instance> inst = toInstance())
{
return inst->getFullName();
}
else
{
return contentId.toString();
}
}
bool LuaSourceBuffer::empty() const
{
return !(script || moduleScript || !contentId.isNull());
}
bool LuaSourceBuffer::operator==(const LuaSourceBuffer& other) const
{
return script == other.script && moduleScript == other.moduleScript &&
contentId == other.contentId;
}
bool LuaSourceBuffer::operator!=(const LuaSourceBuffer& other) const
{
return !(*this == other);
}
+59
View File
@@ -0,0 +1,59 @@
#pragma once
#include "Script/Script.h"
#include "Script/ModuleScript.h"
#include "Script/LuaSourceContainer.h"
#include "Util/ContentId.h"
#include "Util/HttpAsync.h"
#include "Util/ProtectedString.h"
class LuaSourceBuffer
{
public:
static LuaSourceBuffer fromInstance(boost::shared_ptr<RBX::Instance> instance);
static LuaSourceBuffer fromContentId(const RBX::ContentId& contentId);
static RBX::HttpFuture getScriptAssetSource(const RBX::ContentId& contentId, int universeId);
LuaSourceBuffer();
boost::shared_ptr<RBX::LuaSourceContainer> toInstance() const;
bool isNamedAsset() const;
bool isModuleScript() const;
void reloadLiveScript();
bool isCodeEmbedded() const;
bool sourceAvailable() const;
std::string getScriptText() const;
void setScriptText(const std::string& newText);
std::string getScriptName() const;
std::string getFullName() const;
bool empty() const;
bool operator==(const LuaSourceBuffer& other) const;
bool operator!=(const LuaSourceBuffer& other) const;
inline size_t hash_value() const
{
return boost::hash_value(script) ^
boost::hash_value(moduleScript) ^
boost::hash_value(contentId.toString());
}
private:
boost::shared_ptr<RBX::Script> script;
boost::shared_ptr<RBX::ModuleScript> moduleScript;
RBX::ContentId contentId;
explicit LuaSourceBuffer(boost::shared_ptr<RBX::ModuleScript> moduleScript);
explicit LuaSourceBuffer(boost::shared_ptr<RBX::Script> script);
explicit LuaSourceBuffer(const RBX::ContentId& contentId);
};
inline size_t hash_value(const LuaSourceBuffer& lsb)
{
return lsb.hash_value();
}
+30
View File
@@ -0,0 +1,30 @@
/**
* MachineConfig.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "MachineConfig.h"
// Roblox Headers
#include "MachineConfiguration.h"
// Roblox Studio Headers
#include "RobloxSettings.h"
MachineConfig* MachineConfig::machineConfig = NULL;
////////////////////////////////////////////////////////////////////////////////////
void MachineConfig::PostMachineConfiguration(bool authSuccess)
{
if(authSuccess)
{
disconnect(&AuthenticationHelper::Instance(), SIGNAL(authenticationDone(bool)), machineConfig, SLOT(PostMachineConfiguration(bool)));
RobloxSettings settings;
int lastGfxMode = settings.contains("lastGFXMode") ? settings.value("lastGFXMode").toInt() : 0;
RBX::postMachineConfiguration(RobloxSettings::getBaseURL().toLocal8Bit().constData(), lastGfxMode);
}
}
/////////////////////////////////////////////////////////////////////////////////
+35
View File
@@ -0,0 +1,35 @@
/**
* MachineConfig.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Roblox Studio Headers
#include "AuthenticationHelper.h"
class MachineConfig : public QObject
{
Q_OBJECT
public:
static MachineConfig* setupMachineConfig()
{
if(!machineConfig)
{
machineConfig = new MachineConfig();
connect(&AuthenticationHelper::Instance(), SIGNAL(authenticationDone(bool)), machineConfig, SLOT(PostMachineConfiguration(bool)));
}
return machineConfig;
}
private Q_SLOTS:
void PostMachineConfiguration(bool);
private:
MachineConfig() {}
MachineConfig(MachineConfig const&);
MachineConfig& operator=(MachineConfig const&);
static MachineConfig* machineConfig;
};
+86
View File
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ManageEmulationDevDialog</class>
<widget class="QDialog" name="ManageEmulationDevDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>246</width>
<height>204</height>
</rect>
</property>
<property name="windowTitle">
<string>Manage Devices</string>
</property>
<property name="windowIcon">
<iconset resource="RobloxStudio.qrc">
<normaloff>:/images/RobloxStudio.png</normaloff>:/images/RobloxStudio.png</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QListWidget" name="deviceListWidget">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="spacing">
<number>2</number>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="plusButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="RobloxStudio.qrc">
<normaloff>:/16x16/images/Studio 2.0 icons/16x16/plus_16.png</normaloff>:/16x16/images/Studio 2.0 icons/16x16/plus_16.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="minusButton">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="RobloxStudio.qrc">
<normaloff>:/16x16/images/Studio 2.0 icons/16x16/minus_16.png</normaloff>:/16x16/images/Studio 2.0 icons/16x16/minus_16.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources>
<include location="RobloxStudio.qrc"/>
</resources>
<connections/>
</ui>
@@ -0,0 +1,99 @@
/**
* AddEmulationDeviceDialog.cpp
* Copyright (c) 2014 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "ManageEmulationDeviceDialog.h"
// Qt Headers
#include <QApplication>
#include <QDesktopWidget>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QSettings>
// Roblox Studio Headers
#include "AddEmulationDeviceDialog.h"
#include "AuthoringSettings.h"
#include "RobloxApplicationManager.h"
#include "RobloxDocManager.h"
#include "RobloxIDEDoc.h"
#include "RobloxMainWindow.h"
#include "StudioDeviceEmulator.h"
#include "UpdateUIManager.h"
ManageEmulationDeviceDialog::ManageEmulationDeviceDialog(QWidget* Parent)
: QDialog(Parent,Qt::WindowTitleHint | Qt::WindowSystemMenuHint)
{
setAttribute(Qt::WA_DeleteOnClose,false);
m_UI.setupUi(this);
setFixedSize(size());
setWindowFlags(Qt::MSWindowsFixedSizeDialogHint);
rePopulateListWidget();
connect(m_UI.plusButton, SIGNAL(clicked()), this, SLOT(onPlusButtonPressed()));
connect(m_UI.minusButton, SIGNAL(clicked()), this, SLOT(onMinusButtonPressed()));
connect(m_UI.okButton, SIGNAL(clicked()), this, SLOT(onCancel()));
}
void ManageEmulationDeviceDialog::rePopulateListWidget()
{
m_UI.deviceListWidget->clear();
QList<StudioDeviceEmulator::EmulationDevice> devices = StudioDeviceEmulator::Instance().getDeviceList();
for (QList<StudioDeviceEmulator::EmulationDevice>::const_iterator iter = devices.begin(); iter != devices.end(); ++iter)
{
if (*iter == StudioDeviceEmulator::EmulationDevice("Default", 96, 0, 0, false))
continue;
QListWidgetItem* deviceItem = new QListWidgetItem(m_UI.deviceListWidget);
deviceItem->setText((*iter).name);
QString iconLocation = (*iter).mobile ? "EmulateMobile.png" : "EmulateComputer.png";
deviceItem->setIcon(QIcon(QString(":/RibbonBar/images/RibbonIcons/Test/%1").arg(iconLocation)));
QAction* action = StudioDeviceEmulator::Instance().getActionFromDevice(*iter);
deviceItem->setData(Qt::UserRole, (int)action);
m_UI.deviceListWidget->addItem(deviceItem);
}
}
void ManageEmulationDeviceDialog::onPlusButtonPressed()
{
AddEmulationDeviceDialog dialog(NULL);
dialog.exec();
rePopulateListWidget();
}
void ManageEmulationDeviceDialog::onMinusButtonPressed()
{
QList<QListWidgetItem*> selectedItems = m_UI.deviceListWidget->selectedItems();
for (QList<QListWidgetItem*>::const_iterator iter = selectedItems.begin(); iter != selectedItems.end(); ++iter)
{
QAction* action = (QAction*)((*iter)->data(Qt::UserRole).toInt());
StudioDeviceEmulator::Instance().removeDevice(action);
}
rePopulateListWidget();
}
void ManageEmulationDeviceDialog::closeEvent(QCloseEvent* event)
{
QDialog::closeEvent(event);
}
void ManageEmulationDeviceDialog::onCancel()
{
accept();
}
@@ -0,0 +1,39 @@
/**
* ManageEmulationDeviceDialog.h
* Copyright (c) 2014 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
#include <QDialog>
#include "ui_ManageEmulationDevDialog.h"
/**
* Dialog for asking the user what to do with auto-save recovery files
* that were not cleaned up properly.
*/
class ManageEmulationDeviceDialog : public QDialog
{
Q_OBJECT
public:
ManageEmulationDeviceDialog(QWidget* Parent);
public Q_SLOTS:
void onCancel();
void onPlusButtonPressed();
void onMinusButtonPressed();
protected:
void closeEvent(QCloseEvent* event);
private:
void rePopulateListWidget();
static QStringList getFileList();
Ui::ManageEmulationDevDialog m_UI;
};
+483
View File
@@ -0,0 +1,483 @@
/**
* MobileDevelopmentDeployer.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include <QNetworkInterface>
#include <QNetworkProxy>
#include <QHostInfo>
#include <QSignalMapper>
#include <qmath.h>
#include "StringConv.h"
#include "Script/script.h"
#include "MobileDevelopmentDeployer.h"
#include "AuthoringSettings.h"
#include "G3D/Random.h"
#define PAIR_CODE_SIZE 4
#define RBX_DEV_PORT 1313
MobileDevelopmentDeployer::MobileDevelopmentDeployer(QWidget* pParent)
: QWidget(pParent),
onPairBehavior(ON_PAIR_DO_NOTHING)
{
tcpServer = new QTcpServer(this);
udpSocket = new QUdpSocket(this);
timer = new QTimer();
}
MobileDevelopmentDeployer::~MobileDevelopmentDeployer()
{
disconnectAllClients();
if(tcpServer)
{
tcpServer->close();
tcpServer->deleteLater();
}
}
void MobileDevelopmentDeployer::disconnectAllClients()
{
if (tcpClients.size() > 0)
{
for(std::vector<QTcpSocket*>::iterator iter = tcpClients.begin(); iter != tcpClients.end(); ++iter)
{
if(*iter)
{
(*iter)->write("!exit");
}
}
tcpClients.clear();
}
}
void MobileDevelopmentDeployer::disconnectClientsAndShutDown()
{
disconnectAllClients();
if (tcpServer)
{
tcpServer->close();
}
if (udpSocket)
{
udpSocket->close();
}
if (timer)
{
timer->stop();
}
}
MobileDevelopmentDeployer& MobileDevelopmentDeployer::singleton()
{
static MobileDevelopmentDeployer* developmentDeployer = new MobileDevelopmentDeployer();
return *developmentDeployer;
}
int MobileDevelopmentDeployer::generateNewPairCode()
{
int newPairCode = 0;
// make current unix time the seed (should be random enough)
G3D::Random generator(std::time(NULL));
for ( int i = 0; i < forcedPairCodeSize(); ++i)
{
newPairCode += generator.integer(1, 9) * pow(10, i);
}
AuthoringSettings::singleton().setDeviceDeploymentPairingCode(newPairCode);
RBX::GlobalAdvancedSettings::singleton()->saveState();
return newPairCode;
}
int MobileDevelopmentDeployer::getSavedPairCode()
{
return AuthoringSettings::singleton().deploymentPairingCode;
}
int MobileDevelopmentDeployer::getNumOfDigits(int numToCheck)
{
if (numToCheck <= 0)
return 0;
return qFloor(log10(numToCheck)) + 1;
}
int MobileDevelopmentDeployer::forcedPairCodeSize()
{
return PAIR_CODE_SIZE;
}
int MobileDevelopmentDeployer::getPairCode()
{
int savedPairCode = getSavedPairCode();
if ( getNumOfDigits(savedPairCode) <= 0 )
{
savedPairCode = generateNewPairCode();
}
RBXASSERT(getNumOfDigits(savedPairCode) == forcedPairCodeSize());
return savedPairCode;
}
const QString MobileDevelopmentDeployer::getPairCodeString()
{
int pairCodeInt = getPairCode();
QString codeString = QString::number(pairCodeInt);
if (codeString.size() == 3)
{
codeString.prepend('0');
}
RBXASSERT(codeString.size() == forcedPairCodeSize());
RBXASSERT(pairCodeInt == codeString.toInt());
return codeString;
}
bool MobileDevelopmentDeployer::isPairCode(const QString& codeString)
{
if (codeString.isNull() || codeString.count() != forcedPairCodeSize())
return false;
int codeInt = codeString.toInt();
int pairCodeInt = getPairCode();
return ( codeInt == pairCodeInt );
}
QString MobileDevelopmentDeployer::getLocalIPAddress()
{
QTcpSocket socket;
socket.connectToHost("8.8.8.8", 53); // google DNS, or something else reliable
if (socket.waitForConnected())
{
return socket.localAddress().toString();
}
return QString("");
}
void MobileDevelopmentDeployer::broadcastReadyDatagram()
{
QString broadcastString("RbxDevPairServer readyToPair");
broadcastDatagram(broadcastString.toAscii());
}
void MobileDevelopmentDeployer::broadcastDatagram(QByteArray dataToSend)
{
sendToAllBroadcast(dataToSend);
}
void MobileDevelopmentDeployer::sendToAllBroadcast(QByteArray packet)
{
// Get network interfaces list
QList<QNetworkInterface> ifaces = QNetworkInterface::allInterfaces();
// Interfaces iteration
for (int i = 0; i < ifaces.size(); i++)
{
// Now get all IP addresses for the current interface
QList<QNetworkAddressEntry> addrs = ifaces[i].addressEntries();
// And for any IP address, if it is IPv4 and the interface is active, send the packet
for (int j = 0; j < addrs.size(); j++)
{
if ((addrs[j].ip().protocol() == QAbstractSocket::IPv4Protocol) && (addrs[j].broadcast().toString() != ""))
{
udpSocket->writeDatagram(packet.data(), packet.length(), addrs[j].broadcast(), RBX_DEV_PORT);
}
}
}
}
void MobileDevelopmentDeployer::pairWithRbxDevDevices()
{
onPairBehavior = ON_PAIR_DO_NOTHING;
listenForRbxDevDevices();
}
void MobileDevelopmentDeployer::startPlaySessionWithRbxDevDevices()
{
onPairBehavior = ON_PAIR_START_SERVER;
listenForRbxDevDevices();
}
void MobileDevelopmentDeployer::listenForRbxDevDevices()
{
if(udpSocket && !udpSocket->isOpen())
{
if (!udpSocket->bind(QHostAddress::Any,RBX_DEV_PORT,QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint))
RBX::StandardOut::singleton()->printf(RBX::MESSAGE_ERROR, "PairServer did not bind");
connect(udpSocket, SIGNAL(readyRead()),this, SLOT(on_receivedUdpBroadcast()));
timer->setInterval(500);
connect(timer, SIGNAL(timeout()), this, SLOT(broadcastReadyDatagram()));
timer->start();
}
}
bool MobileDevelopmentDeployer::startTcpServer()
{
if(tcpServer && !tcpServer->isListening())
{
if( tcpServer->listen(QHostAddress(getLocalIPAddress()),1315) )
{
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(on_newConnection()));
return true;
}
}
return false;
}
void MobileDevelopmentDeployer::on_newConnection()
{
QTcpSocket* tcpClient = tcpServer->nextPendingConnection();
if (!tcpClient)
{
return;
}
if (tcpClient->state() == QTcpSocket::ConnectedState)
{
connect( tcpClient, SIGNAL(disconnected()), this, SLOT(on_disconnected()) );
connect( tcpClient, SIGNAL(readyRead()), this, SLOT(on_readyRead()) );
tcpClients.push_back(tcpClient);
}
}
bool MobileDevelopmentDeployer::checkForClientPairCode(const QString& messageFromClient)
{
if (messageFromClient.contains(QString::fromStdString("RbxDevClient pairCode")))
{
QStringList list = messageFromClient.split(QRegExp("\\s+"));
if(list.count() == 3)
{
QString code = list.at(2);
if ( isPairCode(code) )
{
if (udpSocket)
{
QString broadcastString("RbxDevPairServer didPair true ip ");
broadcastString.append(getLocalIPAddress());
broadcastDatagram(broadcastString.toAscii());
}
}
else
{
if (udpSocket)
{
QString broadcastString("RbxDevPairServer didPair false ip ");
broadcastString.append("0.0.0.0");
broadcastDatagram(broadcastString.toAscii());
}
}
}
return true;
}
return false;
}
bool MobileDevelopmentDeployer::checkForClientPaired(const QString& messageFromClient, const QHostAddress& sender)
{
if (messageFromClient.contains(QString::fromStdString("RbxDevClient didPair")))
{
QStringList list = messageFromClient.split(QRegExp("\\s+"));
if (list.count() == 3)
{
QString didPair = list.at(2);
if (didPair == "true")
{
if (onPairBehavior == ON_PAIR_START_SERVER)
{
startTcpServer();
QString broadcastString("RbxDevPairServer didStartServer");
broadcastDatagram(broadcastString.toAscii());
}
QHostInfo info = QHostInfo::fromName(sender.toString());
Q_EMIT connectedToClient(info.hostName());
}
}
return true;
}
return false;
}
void MobileDevelopmentDeployer::on_receivedUdpBroadcast()
{
while (udpSocket && udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress sender;
quint16 senderPort;
udpSocket->readDatagram(datagram.data(), datagram.size(),
&sender, &senderPort);
QString message(datagram);
if ( checkForClientPairCode(message) )
return;
if ( checkForClientPaired(message,sender) )
return;
if (message.contains(QString::fromStdString("RbxDevClient didConnectToServer")))
{
// todo: maybe do something here? confirmation message or something?
}
}
}
static bool isHidden(const boost::filesystem::path &p)
{
boost::filesystem::path name = p.filename();
if(name != ".." &&
name != "." &&
name.c_str()[0] == '.')
{
return true;
}
return false;
}
void MobileDevelopmentDeployer::writeCoreScriptToFile(const std::string& filePath, const std::string& fileName, std::stringstream& stream)
{
std::ifstream in(RBX::utf8_decode(filePath).c_str(), std::ios::in | std::ios::binary);
if (in)
{
std::string relativeName = filePath.substr(RBX::BaseScript::adminScriptsPath.size() + 1);
relativeName = relativeName.substr(0,relativeName.size() - 4);
stream << RBX::format("RbxScriptName%sRbxEnd RbxScriptSource",relativeName.c_str());
stream << in.rdbuf() << "RbxEnd ";
}
}
bool MobileDevelopmentDeployer::writeCoreScriptsToStream(const boost::filesystem::path& dir_path, std::stringstream& stream)
{
if (!boost::filesystem::exists(dir_path))
{
return false;
}
boost::filesystem::directory_iterator end_itr;
for ( boost::filesystem::directory_iterator itr(dir_path); itr != end_itr; ++itr )
{
bool isHiddenDir = ::isHidden(itr->path());
if ( is_directory(itr->status()) && !isHiddenDir )
{
if ( writeCoreScriptsToStream( itr->path(), stream) )
return true;
}
else if ( !isHiddenDir && is_regular_file(itr->status()) )
{
const std::string fileName = itr->path().filename().string();
if (fileName.find(".lua") != std::string::npos)
{
const std::string filePath = itr->path().string();
writeCoreScriptToFile(filePath, fileName, stream);
}
}
}
return false;
}
void MobileDevelopmentDeployer::on_readyRead()
{
if (tcpClients.size() <= 0)
return;
QTcpSocket* tcpClient = qobject_cast<QTcpSocket*>(sender());
if (!tcpClient)
return;
QByteArray ba = tcpClient->readLine();
if(strcmp(ba.constData(), "!exit\n") == 0)
{
tcpClient->disconnectFromHost();
return;
}
if(strcmp(ba.constData(), "RbxReadyForPlay") == 0)
{
QString address = getLocalIPAddress();
tcpClient->write("RbxReadyForPlay 53640 " + address.toAscii());
return;
}
if(strcmp(ba.constData(), "RbxReadyForCoreScripts") == 0)
{
if (RBX::BaseScript::hasCoreScriptReplacements())
{
tcpClient->write("RbxReadyForCoreScripts true");
}
else
{
tcpClient->write("RbxReadyForCoreScripts false");
}
return;
}
if(strcmp(ba.constData(), "RbxSendCoreScripts") == 0)
{
if (RBX::BaseScript::hasCoreScriptReplacements())
{
tcpClient->write("RbxSendCoreScripts ");
std::stringstream ss;
boost::filesystem::path rootPath = boost::filesystem::path(RBX::BaseScript::adminScriptsPath);
writeCoreScriptsToStream(rootPath, ss);
ss << "RbxCoreScriptEnd";
std::string coreScriptsString = ss.str();
tcpClient->write(coreScriptsString.c_str(), coreScriptsString.size());
}
return;
}
QString attemptToPairCorrectResponse = QString("RbxAttemptToPair ").append(getPairCodeString());
if(strcmp(ba.constData(), attemptToPairCorrectResponse.toAscii()) == 0)
{
tcpClient->write("RbxAttemptToPair true");
}
}
void MobileDevelopmentDeployer::on_disconnected()
{
if (tcpClients.size() <= 0)
return;
QTcpSocket* tcpClient = qobject_cast<QTcpSocket*>(sender());
if (!tcpClient)
return;
QObject::disconnect(tcpClient, SIGNAL(disconnected()));
QObject::disconnect(tcpClient, SIGNAL(readyRead()));
if ( tcpClient )
{
std::vector<QTcpSocket*>::iterator iter = std::find(tcpClients.begin(), tcpClients.end(), tcpClient);
tcpClients.erase(iter);
tcpClient->disconnectFromHost();
tcpClient->deleteLater();
tcpClient = NULL;
}
}
+86
View File
@@ -0,0 +1,86 @@
/**
* MobileDevelopmentDeployer.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QObject>
#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QUdpSocket>
#include <QTimer>
#include <boost/filesystem.hpp>
class MobileDevelopmentDeployer : public QWidget
{
Q_OBJECT
public:
MobileDevelopmentDeployer(QWidget *pParent=0);
~MobileDevelopmentDeployer();
static MobileDevelopmentDeployer& singleton();
void pairWithRbxDevDevices();
void startPlaySessionWithRbxDevDevices();
void disconnectClientsAndShutDown();
const QString getPairCodeString();
static int getNumOfDigits(int numToCheck);
static int forcedPairCodeSize();
Q_SIGNALS:
void connectedToClient(const QString& clientName);
private:
QTcpServer *tcpServer;
std::vector<QTcpSocket*> tcpClients;
QUdpSocket* udpSocket; // used to establish connection with server
QTimer* timer;
QString getLocalIPAddress();
typedef enum
{
ON_PAIR_DO_NOTHING,
ON_PAIR_START_SERVER
} OnPairBehavior;
OnPairBehavior onPairBehavior;
void listenForRbxDevDevices();
int generateNewPairCode();
int getSavedPairCode();
int getPairCode();
void broadcastDatagram(QByteArray dataToSend);
void sendToAllBroadcast(QByteArray packet);
bool startTcpServer();
bool isPairCode(const QString& codeString);
bool checkForClientPairCode(const QString& messageFromClient);
bool checkForClientPaired(const QString& messageFromClient, const QHostAddress& sender);
void disconnectAllClients();
bool writeCoreScriptsToStream(const boost::filesystem::path& dir_path, std::stringstream& stream);
void writeCoreScriptToFile(const std::string& filePath, const std::string& fileName, std::stringstream& stream);
private Q_SLOTS:
void on_newConnection();
void on_readyRead();
void on_disconnected();
void on_receivedUdpBroadcast();
void broadcastReadyDatagram();
};
+51
View File
@@ -0,0 +1,51 @@
/**
* SimulationManager.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "NameValueStoreManager.h"
// Qt Headers
#include <QVariant>
void NameValueStoreManager::setValue(const QString& storeID, const QString& key, const QVariant &value)
{
Stores::iterator iter = store.find(storeID);
if (iter != store.end())
{
iter.value().insert(key, value);
}
else
{
Properties prop;
prop.insert(key, value);
store.insert(storeID, prop);
}
}
QVariant NameValueStoreManager::getValue(const QString&storeID, const QString& key, bool *isOK)
{
if (isOK) *isOK = false;
Stores::iterator iter = store.find(storeID);
if (iter != store.end())
{
Properties prop = iter.value();
Properties::iterator prop_iter = prop.find(key);
if (prop_iter != prop.end())
{
if (isOK) *isOK = true;
return prop_iter.value();
}
}
return QVariant();
}
bool NameValueStoreManager::clear(const QString& storeID)
{ return store.remove(storeID); }
void NameValueStoreManager::clearAll()
{ store.clear(); }
+32
View File
@@ -0,0 +1,32 @@
/**
* SimulationManager.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
#include <QMap>
class QVariant;
class NameValueStoreManager
{
public:
typedef QMap<QString, QVariant> Properties;
typedef QMap<QString, Properties> Stores;
static NameValueStoreManager& singleton()
{
static NameValueStoreManager manager;
return manager;
}
void setValue(const QString& storeID, const QString& key, const QVariant &value);
QVariant getValue(const QString&storeID, const QString& key, bool *isOK = NULL);
bool clear(const QString& storeID);
void clearAll();
private:
Stores store;
};
+163
View File
@@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PairRbxDevDialog</class>
<widget class="QDialog" name="PairRbxDevDialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>650</width>
<height>500</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>650</width>
<height>350</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>650</width>
<height>500</height>
</size>
</property>
<property name="windowTitle">
<string>Pair Roblox Developer Device</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>images/RobloxStudio.png</normaloff>images/RobloxStudio.png</iconset>
</property>
<property name="windowOpacity">
<double>3.000000000000000</double>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="font">
<font>
<family>Helvetica Neue</family>
<pointsize>31</pointsize>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Enter this code on your device's pair screen</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="statusLabel">
<property name="font">
<font>
<family>Helvetica Neue</family>
<pointsize>15</pointsize>
</font>
</property>
<property name="text">
<string>Waiting for device to pair....</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="codeLabel">
<property name="font">
<font>
<family>Helvetica Neue</family>
<pointsize>40</pointsize>
</font>
</property>
<property name="text">
<string>8974</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
<property name="leftMargin">
<number>100</number>
</property>
<property name="rightMargin">
<number>100</number>
</property>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>180</width>
<height>0</height>
</size>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Done</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+76
View File
@@ -0,0 +1,76 @@
/**
* PairRbxDeviceDialog.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "PairRbxDeviceDialog.h"
// Qt Headers
#include <QSettings>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
// Roblox Studio Headers
#include "AuthoringSettings.h"
#include "RobloxDocManager.h"
#include "UpdateUIManager.h"
#include "RobloxMainWindow.h"
#include "RobloxIDEDoc.h"
#include "RobloxApplicationManager.h"
#include "MobileDevelopmentDeployer.h"
PairRbxDeviceDialog::PairRbxDeviceDialog(QWidget* Parent)
: QDialog(Parent,Qt::WindowTitleHint | Qt::WindowSystemMenuHint)
{
setAttribute(Qt::WA_DeleteOnClose,false);
m_UI.setupUi(this);
updatePairCode();
connect(m_UI.cancelButton,SIGNAL(clicked()),this,SLOT(onCancel()));
MobileDevelopmentDeployer::singleton().pairWithRbxDevDevices();
connect(&MobileDevelopmentDeployer::singleton(), SIGNAL(connectedToClient(const QString&)), this, SLOT(clientPaired(const QString&)));
}
PairRbxDeviceDialog::~PairRbxDeviceDialog()
{
MobileDevelopmentDeployer::singleton().disconnectClientsAndShutDown();
}
void PairRbxDeviceDialog::updatePairCode()
{
m_UI.codeLabel->setText(MobileDevelopmentDeployer::singleton().getPairCodeString());
}
/**
* Callback for closing the dialog.
*
*/
void PairRbxDeviceDialog::closeEvent(QCloseEvent* event)
{
QDialog::closeEvent(event);
}
void PairRbxDeviceDialog::onCancel()
{
MobileDevelopmentDeployer::singleton().disconnectClientsAndShutDown();
accept();
}
void PairRbxDeviceDialog::clientPaired(const QString& clientName)
{
QString statusString("Successfully paired with ");
statusString.append(clientName).append("!");
m_UI.statusLabel->setText("You can now connect to servers started here from your mobile device.");
m_UI.codeLabel->setText("");
m_UI.label->setText(statusString);
m_UI.cancelButton->setText("Done");
MobileDevelopmentDeployer::singleton().disconnectClientsAndShutDown();
}
+39
View File
@@ -0,0 +1,39 @@
/**
* PairRbxDevDialog.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
#include <QDialog>
#include "ui_PairRbxDevDialog.h"
/**
* Dialog for asking the user what to do with auto-save recovery files
* that were not cleaned up properly.
*/
class PairRbxDeviceDialog : public QDialog
{
Q_OBJECT
public:
PairRbxDeviceDialog(QWidget* Parent);
virtual ~PairRbxDeviceDialog();
void updatePairCode();
public Q_SLOTS:
void onCancel();
void clientPaired(const QString& clientName);
protected:
void closeEvent(QCloseEvent* event);
private:
static QStringList getFileList();
Ui::PairRbxDevDialog m_UI;
};
+388
View File
@@ -0,0 +1,388 @@
#include "stdafx.h"
#include "PlayersDataManager.h"
#include <QPointer>
#include <QUrl>
#include <QPainter>
#include "V8DataModel/DataModel.h"
#include "Network/Players.h"
#include "Network/Player.h"
#include "V8Xml/WebParser.h"
#include "util/BrickColor.h"
#include "RobloxSettings.h"
#include "QtUtilities.h"
#include "RobloxGameExplorer.h"
FASTFLAGVARIABLE(TeamCreateReduceCopiesSelectionSet, true)
static const int kAvatarImageSize = 32;
static const int kMaxColors = 128;
static const QString kPlayersColorSettingKey = "Players Color";
static const QString kPlayerIdKey = "PlayerId";
static const QString kColorValueKey = "ColorVal";
//--------------------------------------------------------------------------------------------
// PlayersDataManager
//--------------------------------------------------------------------------------------------
PlayersDataManager::PlayersDataManager(QObject* parent, boost::shared_ptr<RBX::DataModel> dataModel)
: QObject(parent)
, m_pDataModel(dataModel)
, m_CurrentColorCount(0)
{
qRegisterMetaType<PlayersColorCollection >();
qRegisterMetaTypeStreamOperators<PlayersColorCollection >("PlayersColorCollection");
loadPlayersColorSettings();
RBXASSERT(m_pDataModel->currentThreadHasWriteLock());
if (RBX::Network::Players* players = m_pDataModel->find<RBX::Network::Players>())
{
for (auto& playerInstance : *(players->getPlayers()))
{
onPlayerAdded(playerInstance);
}
players->playerAddedSignal.connect(boost::bind(&PlayersDataManager::onPlayerAdded, this, _1));
players->playerRemovingSignal.connect(boost::bind(&PlayersDataManager::onPlayerRemoving, this, _1));
}
}
PlayersDataManager::~PlayersDataManager()
{
savePlayersColorSettings();
}
void PlayersDataManager::getPlayerAvatar(int playerId, QObject* resultListener, const char* callbackName)
{
QSharedPointer<AvatarLoader> avatarLoader = getAvatarLoader(playerId);
if (avatarLoader && avatarLoader->hasFinishedLoading())
{
QMetaObject::invokeMethod(resultListener, callbackName, Qt::AutoConnection, Q_ARG(QImage, avatarLoader->getAvatar()));
}
else
{
if (!avatarLoader)
{
avatarLoader = QSharedPointer<AvatarLoader>(new AvatarLoader(playerId, getPlayerColor(playerId)), &QObject::deleteLater);
m_AvatarLoaders.push_back(avatarLoader);
}
// HACK: convert callback name to SLOT
QString slot = QString("1%1(const QImage&)").arg(callbackName);
connect(avatarLoader.data(), SIGNAL(avatarLoaded(const QImage&)), resultListener, qPrintable(slot));
}
}
static void invokePlayerDataLoaded(QPointer<PlayersDataManager> playersDataManager, RBX::HttpFuture future)
{
if (playersDataManager.isNull())
return;
QMetaObject::invokeMethod(playersDataManager.data(), "onPlayerNameLoaded", Qt::QueuedConnection, Q_ARG(RBX::HttpFuture, future));
}
void PlayersDataManager::getPlayerName(int playerId, QObject* resultListener, const char* callbackName)
{
if (m_PlayersInfo.contains(playerId))
{
QMetaObject::invokeMethod(resultListener, callbackName, Qt::AutoConnection, Q_ARG(int, playerId), Q_ARG(QString, m_PlayersInfo.value(playerId)));
}
else
{
QString str = QString("%1/Users/%2").arg(RobloxSettings::getApiBaseURL()).arg(playerId);
RBX::HttpAsync::get(str.toStdString()).then(boost::bind(&invokePlayerDataLoaded, QPointer<PlayersDataManager>(this), _1));
// HACK: convert callback name to SLOT
QString slot = QString("1%1(int, const QString&)").arg(callbackName);
connect(this, SIGNAL(playerNameLoaded(int, const QString&)), resultListener, qPrintable(slot));
}
}
int PlayersDataManager::getPlayerColor(int playerId)
{
PlayersColorCollection::const_iterator iter = m_PlayersColor.find(playerId);
if (iter != m_PlayersColor.end())
return iter.value();
m_PlayersColor[playerId] = getNextUnassignedColor();
return m_PlayersColor[playerId];
}
shared_ptr<const RBX::Instances> PlayersDataManager::getLastKnownSelection(int playerId)
{
PlayerSelectionMap::iterator iter = m_lastKnownSelectionByPlayerId.find(playerId);
if (iter != m_lastKnownSelectionByPlayerId.end())
return iter->second;
// return empty value if the key doesn't exist
static shared_ptr<const RBX::Instances> emptyInstances(new RBX::Instances());
return emptyInstances;
}
bool PlayersDataManager::isLocalPlayer(boost::shared_ptr<RBX::Network::Player> player)
{
RBX::Network::Players* players = m_pDataModel->find<RBX::Network::Players>();
return (players && players->getLocalPlayer() == player.get());
}
boost::shared_ptr<RBX::Network::Player> PlayersDataManager::getPlayer(const QString& playerName, Qt::CaseSensitivity cs)
{
if (!playerName.isEmpty())
{
RBX::Network::Players* players = m_pDataModel->find<RBX::Network::Players>();
if (players)
{
boost::shared_ptr<const RBX::Instances> playersList = players->getPlayers();
for (RBX::Instances::const_iterator iter = playersList->begin(); iter != playersList->end(); ++iter)
{
RBX::Network::Player* player = boost::polymorphic_downcast<RBX::Network::Player*>(iter->get());
if (QString::compare(player->getName().c_str(), playerName, cs) == 0)
return shared_from(player);
}
}
}
return shared_ptr<RBX::Network::Player>();
}
QSharedPointer<AvatarLoader> PlayersDataManager::getAvatarLoader(int playerId)
{
QListIterator<QSharedPointer<AvatarLoader> > iter(m_AvatarLoaders);
for (int ii = 0; ii < m_AvatarLoaders.size(); ++ii)
{
if (m_AvatarLoaders.at(ii)->getPlayerId() == playerId)
return m_AvatarLoaders.at(ii);
}
return QSharedPointer<AvatarLoader>();
}
void PlayersDataManager::onPlayerAdded(shared_ptr<RBX::Instance> playerInstance)
{
if (shared_ptr<RBX::Network::Player> player = RBX::Instance::fastSharedDynamicCast<RBX::Network::Player>(playerInstance))
{
player->cloudEditSelectionChanged.connect(boost::bind(&PlayersDataManager::onCloudEditSelectionChanged,
this, player->getUserID(), _1));
}
}
void PlayersDataManager::onPlayerRemoving(shared_ptr<RBX::Instance> playerInstance)
{
if (shared_ptr<RBX::Network::Player> player = RBX::Instance::fastSharedDynamicCast<RBX::Network::Player>(playerInstance))
{
m_lastKnownSelectionByPlayerId.erase(player->getUserID());
// remove selections for the removing player
Q_EMIT cloudEditSelectionChanged(player->getUserID());
}
}
void PlayersDataManager::onCloudEditSelectionChanged(int playerId, shared_ptr<const RBX::Reflection::ValueArray>& newSelection)
{
shared_ptr<RBX::Instances> inst(new RBX::Instances());
inst->reserve(newSelection->size());
for (auto& var : *newSelection)
{
if (var.isType<shared_ptr<RBX::Instance> >())
{
if (shared_ptr<RBX::Instance> i = var.cast<shared_ptr<RBX::Instance> >())
{
inst->push_back(i);
}
}
}
m_lastKnownSelectionByPlayerId[playerId] = inst;
Q_EMIT cloudEditSelectionChanged(playerId);
}
/*
Players color data present in registry:
RobloxStudio
|__
Players Color
|__ gametest2.robloxlabs.com
| |_ PlayersColorCollection (will be having player id and color assigned)
|
|__ roblox.com
|_ PlayersColorCollection
*/
static QString getHostName()
{
QUrl url(RobloxSettings::getBaseURL());
return url.host().mid(url.host().indexOf(QString(".")) + 1);
}
void PlayersDataManager::loadPlayersColorSettings()
{
RobloxSettings settings;
settings.beginGroup(kPlayersColorSettingKey);
m_PlayersColor = settings.value(getHostName()).value<PlayersColorCollection >();
settings.endGroup();
}
void PlayersDataManager::savePlayersColorSettings()
{
RobloxSettings settings;
settings.beginGroup(kPlayersColorSettingKey);
settings.setValue(getHostName(), QVariant::fromValue<PlayersColorCollection >(m_PlayersColor));
settings.endGroup();
}
int PlayersDataManager::getNextUnassignedColor()
{
using namespace RBX;
static int colorsIndex[kMaxColors] = { BrickColor::brick_45, BrickColor::brick_331, BrickColor::roblox_1020, BrickColor::brick_334,
BrickColor::brick_335, BrickColor::brick_21, BrickColor::roblox_1006, BrickColor::roblox_1019,
BrickColor::brick_28, BrickColor::brick_337, BrickColor::roblox_1014, BrickColor::brick_307,
BrickColor::roblox_1030, BrickColor::roblox_1009, BrickColor::brick_352, BrickColor::roblox_1016,
BrickColor::roblox_1029, BrickColor::brick_119, BrickColor::brick_359, BrickColor::brick_361,
BrickColor::brick_194, BrickColor::brick_365, BrickColor::brick_328, BrickColor::roblox_1017,
BrickColor::roblox_1032, BrickColor::brick_11, BrickColor::brick_324, BrickColor::brick_322,
BrickColor::brick_321, BrickColor::brick_314, BrickColor::brick_318, BrickColor::brick_316,
BrickColor::brick_315, BrickColor::brick_332, BrickColor::brick_313, BrickColor::brick_23,
BrickColor::brick_340, BrickColor::roblox_1021, BrickColor::brick_306, BrickColor::roblox_1018,
BrickColor::roblox_1011, BrickColor::brick_26, BrickColor::brick_141, BrickColor::brick_329,
BrickColor::roblox_1004, BrickColor::brick_333, BrickColor::brick_226, BrickColor::brick_336,
BrickColor::brick_338, BrickColor::brick_133, BrickColor::brick_341, BrickColor::brick_9,
BrickColor::brick_344, BrickColor::brick_105, BrickColor::brick_348, BrickColor::brick_125,
BrickColor::brick_192, BrickColor::brick_353, BrickColor::brick_5, BrickColor::brick_355,
BrickColor::brick_357, BrickColor::brick_360, BrickColor::brick_362, BrickColor::brick_363,
BrickColor::roblox_1003, BrickColor::roblox_1028, BrickColor::brick_350, BrickColor::roblox_1015,
BrickColor::roblox_1026, BrickColor::brick_325, BrickColor::brick_153, BrickColor::roblox_1008,
BrickColor::roblox_1013, BrickColor::brick_319, BrickColor::brick_151, BrickColor::roblox_1023,
BrickColor::roblox_1027, BrickColor::brick_37, BrickColor::roblox_1010, BrickColor::brick_135,
BrickColor::brick_309, BrickColor::brick_347, BrickColor::brick_302, BrickColor::brick_304,
BrickColor::roblox_1012, BrickColor::brick_301, BrickColor::brick_330, BrickColor::brick_343,
BrickColor::brick_24, BrickColor::brick_217, BrickColor::brick_342, BrickColor::roblox_1007,
BrickColor::brick_106, BrickColor::roblox_1001, BrickColor::roblox_1025, BrickColor::brick_345,
BrickColor::brick_346, BrickColor::brick_349, BrickColor::brick_101, BrickColor::brick_351,
BrickColor::brick_354, BrickColor::brick_18, BrickColor::brick_356, BrickColor::brick_358,
BrickColor::brick_38, BrickColor::brick_1, BrickColor::brick_364, BrickColor::brick_208,
BrickColor::brick_29, BrickColor::brick_327, BrickColor::roblox_1002, BrickColor::brick_320,
BrickColor::brick_323, BrickColor::brick_339, BrickColor::brick_104, BrickColor::roblox_1024,
BrickColor::brick_317, BrickColor::roblox_1031, BrickColor::brick_311, BrickColor::roblox_1022,
BrickColor::brick_312, BrickColor::brick_102, BrickColor::brick_310, BrickColor::brick_308,
BrickColor::brick_305, BrickColor::brick_199, BrickColor::brick_303, BrickColor::brick_107 };
return colorsIndex[m_PlayersColor.size() % kMaxColors];
}
void PlayersDataManager::onPlayerNameLoaded(RBX::HttpFuture future)
{
EntityProperties playerProp;
playerProp.setFromJsonFuture(future);
boost::optional<std::string> playerName = playerProp.get<std::string>("Username");
boost::optional<int> playerId = playerProp.get<int>("Id");
if (playerName.is_initialized() && playerId.is_initialized() && !playerName.get().empty())
{
m_PlayersInfo.insert(playerId.get(), QString::fromStdString(playerName.get()));
Q_EMIT playerNameLoaded(playerId.get(), QString::fromStdString(playerName.get()));
}
}
//--------------------------------------------------------------------------------------------
// AvatarLoader
//--------------------------------------------------------------------------------------------
static void invokeHeadshotThumbnailDataLoaded(QPointer<AvatarLoader> avatarLoader, int playerId, RBX::HttpFuture future)
{
if (avatarLoader.isNull())
return;
QMetaObject::invokeMethod(avatarLoader.data(), "headshotThumbnailDataLoaded", Qt::QueuedConnection,
Q_ARG(int, playerId), Q_ARG(RBX::HttpFuture, future));
}
static void invokeHeadshotThumbnailLoaded(QPointer<AvatarLoader> avatarLoader, int playerId, RBX::HttpFuture future)
{
if (avatarLoader.isNull())
return;
QMetaObject::invokeMethod(avatarLoader.data(), "headshotThumbnailLoaded", Qt::QueuedConnection,
Q_ARG(int, playerId), Q_ARG(RBX::HttpFuture, future));
}
AvatarLoader::AvatarLoader(int playerId, int backgroundColor)
: m_PlayerId(playerId)
, m_BackgroundColor(backgroundColor)
, m_bFinishedLoading(false)
{
// request player item update
QString str = QString("%1/thumbnail/avatar-headshot?userid=%2").arg(RobloxSettings::getBaseURL()).arg(playerId);
RBX::HttpAsync::get(str.toStdString()).then(boost::bind(&invokeHeadshotThumbnailDataLoaded, QPointer<AvatarLoader>(this), playerId, _1));
}
void AvatarLoader::headshotThumbnailDataLoaded(int playerId, RBX::HttpFuture future)
{
boost::optional<std::string> thumnailUri;
try
{
std::string result = future.get();
boost::shared_ptr<const RBX::Reflection::ValueTable> table;
if (RBX::WebParser::parseJSONTable(future.get(), table) && table->size() > 0)
{
RBX::Reflection::ValueTable::const_iterator finalStrIter = table->find("Final");
if (finalStrIter != table->end() && finalStrIter->second.isType<bool>() && finalStrIter->second.get<bool>())
{
RBX::Reflection::ValueTable::const_iterator thumnailUrlIter = table->find("Url");
if (thumnailUrlIter != table->end() && thumnailUrlIter->second.isString())
thumnailUri = thumnailUrlIter->second.get<std::string>();
}
else
{
RBX::Reflection::ValueTable::const_iterator RetryUrlIter = table->find("RetryUrl");
if (RetryUrlIter != table->end() && RetryUrlIter->second.isString())
thumnailUri = RetryUrlIter->second.get<std::string>();
}
}
}
catch (const RBX::base_exception& )
{
}
// TODO: in case of error shall we again try to reload avatar?
if (thumnailUri.is_initialized() && !thumnailUri.get().empty())
{
RBX::HttpOptions options;
options.setExternal(true);
RBX::HttpAsync::get(thumnailUri.get(), options).then(boost::bind(&invokeHeadshotThumbnailLoaded, QPointer<AvatarLoader>(this), playerId, _1));
}
}
void AvatarLoader::headshotThumbnailLoaded(int playerId, RBX::HttpFuture future)
{
std::string imageContent;
try
{
imageContent = future.get();
}
catch (const RBX::base_exception&)
{
}
if (!imageContent.empty())
{
QImage image;
if (image.loadFromData((uchar*)imageContent.c_str(), imageContent.size()))
{
// set the image background here itself, so the same image can be used in both Creators panel and Chat widget
QImage resultingImage(QSize(kAvatarImageSize,kAvatarImageSize), image.format());
resultingImage.fill(QtUtilities::toQColor(RBX::BrickColor(m_BackgroundColor).color3()));
QPainter painter(&resultingImage);
painter.drawImage(0, 0, image.scaled(QSize(kAvatarImageSize,kAvatarImageSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
// we are done with image drawing, assign the resulting image
m_PlayerAvatar = resultingImage;
}
}
m_bFinishedLoading = true;
// let connected slots to update image
Q_EMIT avatarLoaded(m_PlayerAvatar);
// remove connection
disconnect(SIGNAL(avatarLoaded(const QImage&)));
}
+101
View File
@@ -0,0 +1,101 @@
#pragma once
//this will make sure that we can use qt with boost (else moc compiler errors)
#ifndef QT_NO_KEYWORDS
#define QT_NO_KEYWORDS
#endif
#include <QObject>
#include <QList>
#include <QSharedPointer>
#include <QImage>
#include <QMap>
#include <QMetaType>
#include "rbx/signal.h"
#include "V8Tree/Instance.h"
#include "Util/HttpAsync.h"
namespace RBX {
class DataModel;
class BrickColor;
namespace Network {
class Player;
}
}
typedef QMap<int, int> PlayersColorCollection;
typedef QMap<int, QString> PlayersInfoCollection;
typedef boost::unordered_map<int, shared_ptr<const RBX::Instances> > PlayerSelectionMap;
Q_DECLARE_METATYPE(PlayersColorCollection)
class AvatarLoader: public QObject
{
Q_OBJECT
public:
AvatarLoader(int playerId, int backgroundColor);
int getPlayerId() { return m_PlayerId; }
bool hasFinishedLoading() { return m_bFinishedLoading; }
QImage& getAvatar() { return m_PlayerAvatar; }
Q_SIGNALS:
void avatarLoaded(const QImage& image);
private:
Q_INVOKABLE void headshotThumbnailDataLoaded(int playerId, RBX::HttpFuture future);
Q_INVOKABLE void headshotThumbnailLoaded(int playerId, RBX::HttpFuture future);
QImage m_PlayerAvatar;
int m_PlayerId;
int m_BackgroundColor;
bool m_bFinishedLoading;
};
class PlayersDataManager: public QObject
{
Q_OBJECT
public:
PlayersDataManager(QObject* parent, boost::shared_ptr<RBX::DataModel> dataModel);
~PlayersDataManager();
void getPlayerAvatar(int playerId, QObject* resultListener, const char* callbackName);
void getPlayerName(int playerId, QObject* resultListener, const char* callbackName);
int getPlayerColor(int playerId);
// must be called with a datamodel lock
shared_ptr<const RBX::Instances> getLastKnownSelection(int playerId);
bool isLocalPlayer(boost::shared_ptr<RBX::Network::Player> player);
boost::shared_ptr<RBX::Network::Player> getPlayer(const QString& playerName, Qt::CaseSensitivity cs = Qt::CaseInsensitive);
Q_SIGNALS:
void cloudEditSelectionChanged(int playerId);
void playerNameLoaded(int playerId, const QString& playerName);
private:
QSharedPointer<AvatarLoader> getAvatarLoader(int playerId);
void onPlayerAdded(shared_ptr<RBX::Instance> playerInstance);
void onPlayerRemoving(shared_ptr<RBX::Instance> playerInstance);
void onCloudEditSelectionChanged(int playerId, shared_ptr<const RBX::Reflection::ValueArray>& newSelection);
Q_INVOKABLE void onPlayerNameLoaded(RBX::HttpFuture future);
void loadPlayersColorSettings();
void savePlayersColorSettings();
int getNextUnassignedColor();
PlayerSelectionMap m_lastKnownSelectionByPlayerId;
boost::shared_ptr<RBX::DataModel> m_pDataModel;
QList<QSharedPointer<AvatarLoader> > m_AvatarLoaders;
PlayersColorCollection m_PlayersColor;
PlayersInfoCollection m_PlayersInfo;
int m_CurrentColorCount;
};
+14
View File
@@ -0,0 +1,14 @@
/**
* PluginAction.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
#include <QAction>
class PluginAction : public QAction
{
public:
PluginAction(const QString& string, QObject* obj) : QAction(string, obj){}
};
File diff suppressed because it is too large Load Diff
+212
View File
@@ -0,0 +1,212 @@
/**
* PropertyItems.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QObject>
#include <QTreeWidget>
#include <QDoubleSpinBox>
#include <QListWidgetItem>
// Roblox Headers
#include "reflection/Type.h"
#include "RobloxCustomWidgets.h"
namespace RBX {
class Instance;
namespace Reflection
{
class ClassDescriptor;
class PropertyDescriptor;
class Variant;
}
}
class PropertyTreeWidget;
typedef std::list< RBX::Instance* > InstanceList;
typedef std::list<const RBX::Reflection::ClassDescriptor*> Classes;
class PropertyItem: public QTreeWidgetItem
{
public:
PropertyItem(const RBX::Reflection::PropertyDescriptor *pPropertyDescriptor);
PropertyItem(PropertyItem *pParentItem, const QString &name);
virtual ~PropertyItem();
//factory method
static PropertyItem* createItem(const RBX::Reflection::PropertyDescriptor* pPropertyDescriptor);
virtual Qt::TextElideMode getTextElideMode() const { return Qt::ElideRight; }
//for editing of values, will be called from "PropertyItemDelegate" (default implementation)
virtual QWidget* createEditor(QWidget*, const QStyleOptionViewItem&) { return NULL; }
virtual void setModelData(QWidget*) {}
virtual void setModelDataSafe(QWidget*) {}
virtual void setEditorData(QWidget*) {}
virtual bool editorEvent(QEvent*) { return false; }
//will set update values from selected instance(s) to the property widget
virtual bool update();
//search filter
virtual bool applyFilter(const QString &filterString);
//incase child update is required (this can be replaced with a signal)
virtual void syncChildren(PropertyItem*) {}
virtual void setTextValue(const QString &value);
virtual QString getTextValue();
virtual int compareInstanceValues(RBX::Instance *pPreviousInstance, RBX::Instance *pCurrentInstance);
virtual void pushInstanceValue(RBX::Instance *pInstance);
virtual void pullInstanceValue(RBX::Instance *pInstance);
virtual void setVariantValue(const RBX::Reflection::Variant &value);
virtual RBX::Reflection::Variant getVariantValue();
// returns weather the hook handled the edit launch
virtual bool customLaunchEditorHook(QMouseEvent* event) { return false; }
virtual void buttonClicked(const QString& buttonName = QString()) {}
//add functions with different arguments as and when required
virtual void onEvent(const QObject *pSender, const QString &arg) {}
// for hiding Properties dynamically
virtual bool currentlyHidden(const InstanceList &instances);
const RBX::Reflection::PropertyDescriptor* propertyDescriptor() const { return m_pPropertyDescriptor; }
PropertyTreeWidget* getTreeWidget();
bool hasError() const { return m_hasExceptionError; }
protected:
void addClassDescriptor(const RBX::Reflection::ClassDescriptor* pClassDescriptor);
void getSelectedInstances(InstanceList &instances);
bool canBeShown();
QPoint computePopupLocation(const QSize &pickerFrameSize);
void init(const QString &itemName);
void commitModification(bool requestWaypoint = true);
bool handleCollisionLocally(RBX::Instance* pInstance);
Classes m_Classes;
const RBX::Reflection::PropertyDescriptor *m_pPropertyDescriptor;
PropertyItem *m_pParentItem;
RBX::Reflection::Variant m_variantValue;
bool m_isMultiValued;
bool m_hasExceptionError;
};
class BrickColorPropertyItem: public QObject, public PropertyItem
{
Q_OBJECT
private:
PopupLaunchEditor * m_pPopupLaunchEditor;
public:
BrickColorPropertyItem(const RBX::Reflection::PropertyDescriptor *pPropertyDescriptor);
QString getTextValue();
void updateIcon();
bool update();
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option);
void updatePropertyValueQColor(QColor selectedColor);
bool customLaunchEditorHook(QMouseEvent* event);
void buttonClicked(const QString& buttonName = QString());
private Q_SLOTS:
void updatePropertyValue(int selectedColor);
};
class ColorPropertyItem: public QObject, public PropertyItem
{
Q_OBJECT
private:
PopupLaunchEditor *m_pPopupLaunchEditor;
QLineEdit *m_pProxyLineEdit;
public:
ColorPropertyItem(const RBX::Reflection::PropertyDescriptor *pPropertyDescriptor);
ColorPropertyItem(PropertyItem* parent, const QString& name);
QString getTextValue();
void updateIcon();
bool update();
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option);
void setEditorData(QWidget *editor);
bool customLaunchEditorHook(QMouseEvent* event);
void buttonClicked(const QString& buttonName = QString());
void setModelData(QWidget *editor);
void setModelDataSafe(QWidget *editor);
int popupLauncherButtonSize();
private Q_SLOTS:
void updatePropertyValue(QColor selectedColor);
};
class DoubleSpinBoxWidget : public QDoubleSpinBox
{
public:
DoubleSpinBoxWidget(QWidget* parent)
: QDoubleSpinBox(parent)
{}
virtual QString textFromValue(double value) const
{
std::stringstream stream;
stream << value;
return QString(stream.str().c_str());
}
};
class DoublePropertyItem: public QObject, public PropertyItem
{
Q_OBJECT
public:
DoublePropertyItem(const RBX::Reflection::PropertyDescriptor *pPropertyDescriptor);
DoublePropertyItem(PropertyItem* parent, const QString& name);
QString getTextValue();
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem &option);
void setEditorData(QWidget *editor);
void setModelData(QWidget *editor);
void updateSpinBox();
void updateSlider();
virtual void setVariantValue(const RBX::Reflection::Variant &value);
virtual bool eventFilter(QObject * obj, QEvent * evt);
private Q_SLOTS:
void onSpinBoxChanged();
void onSliderChanged(int value);
void onSliderReleased();
private:
QDoubleSpinBox* m_SpinBox;
QSlider* m_Slider;
QMutex m_updateMutex;
};
+103
View File
@@ -0,0 +1,103 @@
/**
* QDirBoundProp.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QDir>
// Roblox Headers
#include "reflection/Property.h"
namespace RBX
{
template<>
std::string StringConverter<QDir>::convertToString(const QDir& value)
{
return value.absolutePath().toStdString();
}
template<>
bool StringConverter<QDir>::convertToValue(const std::string& text,QDir& value)
{
value.setPath(text.c_str());
return true;
}
namespace Reflection
{
template<>
const Type& Type::getSingleton<QDir>()
{
static TType<QDir> type("QDir");
return type;
}
template<>
int TypedPropertyDescriptor<QDir>::getDataSize(const DescribedBase* instance) const
{
return sizeof(QDir) + getValue(instance).absolutePath().size();
}
template<>
bool TypedPropertyDescriptor<QDir>::hasStringValue() const
{
return true;
}
template<>
std::string TypedPropertyDescriptor<QDir>::getStringValue(const DescribedBase* instance) const
{
return StringConverter<QDir>::convertToString(getValue(instance));
}
template<>
bool TypedPropertyDescriptor<QDir>::setStringValue(DescribedBase* instance,const std::string& text) const
{
QDir value;
if ( StringConverter<QDir>::convertToValue(text,value) )
{
setValue(instance,value);
return true;
}
else
return false;
}
template<>
void TypedPropertyDescriptor<QDir>::readValue(
DescribedBase* instance,
const XmlElement* element,
IReferenceBinder& binder ) const
{
if (!element->isXsiNil() )
{
std::string text;
if ( element->getValue(text) )
{
QDir value;
value.setPath(text.c_str());
setValue(instance,value);
}
}
}
template<>
void TypedPropertyDescriptor<QDir>::writeValue(const DescribedBase* instance,XmlElement* element) const
{
QDir font = getValue(instance);
element->setValue(font.absolutePath().toStdString());
}
template<>
QDir& Variant::convert<QDir>(void)
{
return genericConvert<QDir>();
}
}
}
+102
View File
@@ -0,0 +1,102 @@
/**
* QFontPropDescriptor.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QFont>
// Roblox Headers
#include "reflection/Property.h"
namespace RBX
{
template<>
std::string StringConverter<QFont>::convertToString(const QFont& value)
{
return value.toString().toStdString();
}
template<>
bool StringConverter<QFont>::convertToValue(const std::string& text,QFont& value)
{
return value.fromString(text.c_str());
}
namespace Reflection
{
template<>
const Type& Type::getSingleton<QFont>()
{
static TType<QFont> type("QFont");
return type;
}
template<>
int TypedPropertyDescriptor<QFont>::getDataSize(const DescribedBase* instance) const
{
return sizeof(QDir) + getValue(instance).toString().size();
}
template<>
bool TypedPropertyDescriptor<QFont>::hasStringValue() const
{
return true;
}
template<>
std::string TypedPropertyDescriptor<QFont>::getStringValue(const DescribedBase* instance) const
{
return StringConverter<QFont>::convertToString(getValue(instance));
}
template<>
bool TypedPropertyDescriptor<QFont>::setStringValue(DescribedBase* instance,const std::string& text) const
{
QFont value;
if ( StringConverter<QFont>::convertToValue(text,value) )
{
setValue(instance,value);
return true;
}
else
return false;
}
template<>
void TypedPropertyDescriptor<QFont>::readValue(
DescribedBase* instance,
const XmlElement* element,
IReferenceBinder& binder ) const
{
if (!element->isXsiNil() )
{
std::string text;
if ( element->getValue(text) )
{
QFont value;
value.fromString(text.c_str());
setValue(instance,value);
}
}
}
template<>
void TypedPropertyDescriptor<QFont>::writeValue(const DescribedBase* instance,XmlElement* element) const
{
QFont font = getValue(instance);
element->setValue(font.toString().toStdString());
}
template<>
QFont& Variant::convert<QFont>(void)
{
return genericConvert<QFont>();
}
}
}
+55
View File
@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup
Condition="'$(RccBeforeTargets)' == '' and '$(RccAfterTargets)' == '' and '$(ConfigurationType)' != 'Makefile'">
<RccBeforeTargets>Midl</RccBeforeTargets>
<RccAfterTargets>CustomBuild</RccAfterTargets>
</PropertyGroup>
<PropertyGroup>
<RccDependsOn
Condition="'$(ConfigurationType)' != 'Makefile'">_SelectedFiles;$(RccDependsOn)</RccDependsOn>
</PropertyGroup>
<ItemDefinitionGroup>
<Rcc>
<CommandLineTemplate>"$(CONTRIB_PATH)\Qt\4.8.5\win_VS2012\bin\Rcc.exe" -name %(Filename) "%(FullPath)" -o "$(ProjectDir)qrc_%(Filename).cpp"</CommandLineTemplate>
<!--<Outputs>"$(ProjectDir)qrc_%(Filename).cpp"</Outputs>-->
<Outputs>$(ProjectDir)qrc_%(Filename).cpp</Outputs>
<ExecutionDescription>Rcc %(Filename)%(Extension)</ExecutionDescription>
</Rcc>
</ItemDefinitionGroup>
<PropertyGroup
Condition="'$(MocBeforeTargets)' == '' and '$(MocAfterTargets)' == '' and '$(ConfigurationType)' != 'Makefile'">
<MocBeforeTargets>Midl</MocBeforeTargets>
<MocAfterTargets>CustomBuild</MocAfterTargets>
</PropertyGroup>
<PropertyGroup>
<MocDependsOn
Condition="'$(ConfigurationType)' != 'Makefile'">_SelectedFiles;$(MocDependsOn)</MocDependsOn>
</PropertyGroup>
<ItemDefinitionGroup>
<Moc>
<CommandLineTemplate>"$(CONTRIB_PATH)\Qt\4.8.5\win_VS2012\bin\moc.exe" -fstdafx.h -f%(Filename).h -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_GUI_LIB -DQT_CORE_LIB -DQT_THREAD_SUPPORT -I"$(CONTRIB_PATH)\Qt\4.8.5\win_VS2012\include\QtCore" -I"$(CONTRIB_PATH)\Qt\4.8.5\win_VS2012\include\QtGui" -I"$(CONTRIB_PATH)\Qt\4.8.5\win_VS2012\include" -I"$(CONTRIB_PATH)\Qt\4.8.5\win_VS2012\include\ActiveQt" -I"$(CONTRIB_PATH)\Qt\4.8.5\win_VS2012\mkspecs\win32-msvc2008" -D_MSC_VER=1500 -DWIN32 "%(FullPath)" -o "$(ProjectDir)moc_%(Filename).cpp"</CommandLineTemplate>
<!--<Outputs>"$(ProjectDir)moc_%(Filename).cpp"</Outputs>-->
<Outputs>$(ProjectDir)moc_%(Filename).cpp</Outputs>
<ExecutionDescription>Moc %(Filename)%(Extension)</ExecutionDescription>
</Moc>
</ItemDefinitionGroup>
<PropertyGroup
Condition="'$(UicBeforeTargets)' == '' and '$(UicAfterTargets)' == '' and '$(ConfigurationType)' != 'Makefile'">
<UicBeforeTargets>Midl</UicBeforeTargets>
<UicAfterTargets>CustomBuild</UicAfterTargets>
</PropertyGroup>
<PropertyGroup>
<UicDependsOn
Condition="'$(ConfigurationType)' != 'Makefile'">_SelectedFiles;$(UicDependsOn)</UicDependsOn>
</PropertyGroup>
<ItemDefinitionGroup>
<Uic>
<CommandLineTemplate>"$(CONTRIB_PATH)\Qt\4.8.5\win_VS2012\bin\uic.exe" "%(FullPath)" -o "$(ProjectDir)ui_%(Filename).h"</CommandLineTemplate>
<!--<Outputs>"$(ProjectDir)ui_%(Filename).h"</Outputs>-->
<Outputs>$(ProjectDir)ui_%(Filename).h</Outputs>
<ExecutionDescription>Uic %(Filename)%(Extension)</ExecutionDescription>
</Uic>
</ItemDefinitionGroup>
</Project>
+41
View File
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<VisualStudioToolFile
Name="Qt Build Rules"
Version="8.00"
>
<Rules>
<CustomBuildRule
Name="Rcc"
DisplayName="Qt Rcc"
CommandLine="&quot;$(CONTRIB_PATH)\Qt\4.8.5\win\bin\Rcc.exe&quot; -name $(InputName) &quot;$(InputPath)&quot; -o &quot;$(ProjectDir)qrc_$(InputName).cpp&quot;"
Outputs="&quot;$(ProjectDir)qrc_$(InputName).cpp&quot;"
FileExtensions="*.qrc"
ExecutionDescription="Rcc $(InputFileName)"
>
<Properties>
</Properties>
</CustomBuildRule>
<CustomBuildRule
Name="Moc"
DisplayName="Qt Moc"
CommandLine="&quot;$(CONTRIB_PATH)\Qt\4.8.5\win\bin\moc.exe&quot; -fstdafx.h -f$(InputName).h -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_GUI_LIB -DQT_CORE_LIB -DQT_THREAD_SUPPORT -I&quot;$(CONTRIB_PATH)\Qt\4.8.5\win\include\QtCore&quot; -I&quot;$(CONTRIB_PATH)\Qt\4.8.5\win\include\QtGui&quot; -I&quot;$(CONTRIB_PATH)\Qt\4.8.5\win\include&quot; -I&quot;$(CONTRIB_PATH)\Qt\4.8.5\win\include\ActiveQt&quot; -I&quot;$(CONTRIB_PATH)\Qt\4.8.5\win\mkspecs\win32-msvc2008&quot; -D_MSC_VER=1500 -DWIN32 &quot;$(InputPath)&quot; -o &quot;$(ProjectDir)moc_$(InputName).cpp&quot;"
Outputs="&quot;$(ProjectDir)moc_$(InputName).cpp&quot;"
FileExtensions="*.dummy"
ExecutionDescription="Moc $(InputFileName)"
>
<Properties>
</Properties>
</CustomBuildRule>
<CustomBuildRule
Name="Uic"
DisplayName="Qt Uic"
CommandLine="&quot;$(CONTRIB_PATH)\Qt\4.8.5\win\bin\uic.exe&quot; &quot;$(InputPath)&quot; -o &quot;$(ProjectDir)ui_$(InputName).h&quot;"
Outputs="&quot;$(ProjectDir)ui_$(InputName).h&quot;"
FileExtensions="*.ui"
ExecutionDescription="Uic $(InputFileName)"
>
<Properties>
</Properties>
</CustomBuildRule>
</Rules>
</VisualStudioToolFile>
+229
View File
@@ -0,0 +1,229 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PropertyPageSchema
Include="$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml" />
<AvailableItemName
Include="Rcc">
<Targets>_Rcc</Targets>
</AvailableItemName>
<AvailableItemName
Include="Moc">
<Targets>_Moc</Targets>
</AvailableItemName>
<AvailableItemName
Include="Uic">
<Targets>_Uic</Targets>
</AvailableItemName>
</ItemGroup>
<UsingTask
TaskName="Rcc"
TaskFactory="XamlTaskFactory"
AssemblyName="Microsoft.Build.Tasks.v4.0">
<Task>$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml</Task>
</UsingTask>
<UsingTask
TaskName="Moc"
TaskFactory="XamlTaskFactory"
AssemblyName="Microsoft.Build.Tasks.v4.0">
<Task>$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml</Task>
</UsingTask>
<UsingTask
TaskName="Uic"
TaskFactory="XamlTaskFactory"
AssemblyName="Microsoft.Build.Tasks.v4.0">
<Task>$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml</Task>
</UsingTask>
<Target
Name="_Rcc"
BeforeTargets="$(RccBeforeTargets)"
AfterTargets="$(RccAfterTargets)"
Condition="'@(Rcc)' != ''"
DependsOnTargets="$(RccDependsOn);ComputeRccOutput"
Outputs="%(Rcc.Outputs)"
Inputs="%(Rcc.Identity);%(Rcc.AdditionalDependencies);$(MSBuildProjectFile)">
<ItemGroup
Condition="'@(SelectedFiles)' != ''">
<Rcc
Remove="@(Rcc)"
Condition="'%(Identity)' != '@(SelectedFiles)'" />
</ItemGroup>
<ItemGroup>
<Rcc_tlog
Include="%(Rcc.Outputs)"
Condition="'%(Rcc.Outputs)' != '' and '%(Rcc.ExcludedFromBuild)' != 'true'">
<Source>@(Rcc, '|')</Source>
</Rcc_tlog>
</ItemGroup>
<Message
Importance="High"
Text="%(Rcc.ExecutionDescription)" />
<WriteLinesToFile
Condition="'@(Rcc_tlog)' != '' and '%(Rcc_tlog.ExcludedFromBuild)' != 'true'"
File="$(TLogLocation)$(ProjectName).write.1.tlog"
Lines="^%(Rcc_tlog.Source);@(Rcc_tlog-&gt;'%(Fullpath)')" />
<Rcc
Condition="'@(Rcc)' != '' and '%(Rcc.ExcludedFromBuild)' != 'true'"
CommandLineTemplate="%(Rcc.CommandLineTemplate)"
AdditionalOptions="%(Rcc.AdditionalOptions)"
Inputs="%(Rcc.Identity)" />
</Target>
<PropertyGroup>
<ComputeLinkInputsTargets>
$(ComputeLinkInputsTargets);
ComputeRccOutput;
</ComputeLinkInputsTargets>
<ComputeLibInputsTargets>
$(ComputeLibInputsTargets);
ComputeRccOutput;
</ComputeLibInputsTargets>
</PropertyGroup>
<Target
Name="ComputeRccOutput"
Condition="'@(Rcc)' != ''">
<ItemGroup>
<RccDirsToMake
Condition="'@(Rcc)' != '' and '%(Rcc.ExcludedFromBuild)' != 'true'"
Include="%(Rcc.Outputs)" />
<Link
Include="%(RccDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<Lib
Include="%(RccDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<ImpLib
Include="%(RccDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
</ItemGroup>
<MakeDir
Directories="@(RccDirsToMake-&gt;'%(RootDir)%(Directory)')" />
</Target>
<Target
Name="_Moc"
BeforeTargets="$(MocBeforeTargets)"
AfterTargets="$(MocAfterTargets)"
Condition="'@(Moc)' != ''"
DependsOnTargets="$(MocDependsOn);ComputeMocOutput"
Outputs="%(Moc.Outputs)"
Inputs="%(Moc.Identity);%(Moc.AdditionalDependencies);$(MSBuildProjectFile)">
<ItemGroup
Condition="'@(SelectedFiles)' != ''">
<Moc
Remove="@(Moc)"
Condition="'%(Identity)' != '@(SelectedFiles)'" />
</ItemGroup>
<ItemGroup>
<Moc_tlog
Include="%(Moc.Outputs)"
Condition="'%(Moc.Outputs)' != '' and '%(Moc.ExcludedFromBuild)' != 'true'">
<Source>@(Moc, '|')</Source>
</Moc_tlog>
</ItemGroup>
<Message
Importance="High"
Text="%(Moc.ExecutionDescription)" />
<WriteLinesToFile
Condition="'@(Moc_tlog)' != '' and '%(Moc_tlog.ExcludedFromBuild)' != 'true'"
File="$(TLogLocation)$(ProjectName).write.1.tlog"
Lines="^%(Moc_tlog.Source);@(Moc_tlog-&gt;'%(Fullpath)')" />
<Moc
Condition="'@(Moc)' != '' and '%(Moc.ExcludedFromBuild)' != 'true'"
CommandLineTemplate="%(Moc.CommandLineTemplate)"
AdditionalOptions="%(Moc.AdditionalOptions)"
Inputs="%(Moc.Identity)" />
</Target>
<PropertyGroup>
<ComputeLinkInputsTargets>
$(ComputeLinkInputsTargets);
ComputeMocOutput;
</ComputeLinkInputsTargets>
<ComputeLibInputsTargets>
$(ComputeLibInputsTargets);
ComputeMocOutput;
</ComputeLibInputsTargets>
</PropertyGroup>
<Target
Name="ComputeMocOutput"
Condition="'@(Moc)' != ''">
<ItemGroup>
<MocDirsToMake
Condition="'@(Moc)' != '' and '%(Moc.ExcludedFromBuild)' != 'true'"
Include="%(Moc.Outputs)" />
<Link
Include="%(MocDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<Lib
Include="%(MocDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<ImpLib
Include="%(MocDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
</ItemGroup>
<MakeDir
Directories="@(MocDirsToMake-&gt;'%(RootDir)%(Directory)')" />
</Target>
<Target
Name="_Uic"
BeforeTargets="$(UicBeforeTargets)"
AfterTargets="$(UicAfterTargets)"
Condition="'@(Uic)' != ''"
DependsOnTargets="$(UicDependsOn);ComputeUicOutput"
Outputs="%(Uic.Outputs)"
Inputs="%(Uic.Identity);%(Uic.AdditionalDependencies);$(MSBuildProjectFile)">
<ItemGroup
Condition="'@(SelectedFiles)' != ''">
<Uic
Remove="@(Uic)"
Condition="'%(Identity)' != '@(SelectedFiles)'" />
</ItemGroup>
<ItemGroup>
<Uic_tlog
Include="%(Uic.Outputs)"
Condition="'%(Uic.Outputs)' != '' and '%(Uic.ExcludedFromBuild)' != 'true'">
<Source>@(Uic, '|')</Source>
</Uic_tlog>
</ItemGroup>
<Message
Importance="High"
Text="%(Uic.ExecutionDescription)" />
<WriteLinesToFile
Condition="'@(Uic_tlog)' != '' and '%(Uic_tlog.ExcludedFromBuild)' != 'true'"
File="$(TLogLocation)$(ProjectName).write.1.tlog"
Lines="^%(Uic_tlog.Source);@(Uic_tlog-&gt;'%(Fullpath)')" />
<Uic
Condition="'@(Uic)' != '' and '%(Uic.ExcludedFromBuild)' != 'true'"
CommandLineTemplate="%(Uic.CommandLineTemplate)"
AdditionalOptions="%(Uic.AdditionalOptions)"
Inputs="%(Uic.Identity)" />
</Target>
<PropertyGroup>
<ComputeLinkInputsTargets>
$(ComputeLinkInputsTargets);
ComputeUicOutput;
</ComputeLinkInputsTargets>
<ComputeLibInputsTargets>
$(ComputeLibInputsTargets);
ComputeUicOutput;
</ComputeLibInputsTargets>
</PropertyGroup>
<Target
Name="ComputeUicOutput"
Condition="'@(Uic)' != ''">
<ItemGroup>
<UicDirsToMake
Condition="'@(Uic)' != '' and '%(Uic.ExcludedFromBuild)' != 'true'"
Include="%(Uic.Outputs)" />
<Link
Include="%(UicDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<Lib
Include="%(UicDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
<ImpLib
Include="%(UicDirsToMake.Identity)"
Condition="'%(Extension)'=='.obj' or '%(Extension)'=='.res' or '%(Extension)'=='.rsc' or '%(Extension)'=='.lib'" />
</ItemGroup>
<MakeDir
Directories="@(UicDirsToMake-&gt;'%(RootDir)%(Directory)')" />
</Target>
</Project>
+375
View File
@@ -0,0 +1,375 @@
<?xml version="1.0" encoding="utf-8"?>
<ProjectSchemaDefinitions xmlns="clr-namespace:Microsoft.Build.Framework.XamlTypes;assembly=Microsoft.Build.Framework" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:transformCallback="Microsoft.Cpp.Dev10.ConvertPropertyCallback">
<Rule
Name="Rcc"
PageTemplate="tool"
DisplayName="Qt Rcc"
Order="200">
<Rule.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Rcc" />
</Rule.DataSource>
<Rule.Categories>
<Category
Name="General">
<Category.DisplayName>
<sys:String>General</sys:String>
</Category.DisplayName>
</Category>
<Category
Name="Command Line"
Subtype="CommandLine">
<Category.DisplayName>
<sys:String>Command Line</sys:String>
</Category.DisplayName>
</Category>
</Rule.Categories>
<StringListProperty
Name="Inputs"
Category="Command Line"
IsRequired="true"
Switch=" ">
<StringListProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Rcc"
SourceType="Item" />
</StringListProperty.DataSource>
</StringListProperty>
<StringProperty
Name="CommandLineTemplate"
DisplayName="Command Line"
Visible="False"
IncludeInCommandLine="False" />
<DynamicEnumProperty
Name="RccBeforeTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute Before</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run before.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^RccBeforeTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<DynamicEnumProperty
Name="RccAfterTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute After</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run after.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^RccAfterTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType=""
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<StringListProperty
Name="Outputs"
DisplayName="Outputs"
Visible="False"
IncludeInCommandLine="False" />
<StringProperty
Name="ExecutionDescription"
DisplayName="Execution Description"
Visible="False"
IncludeInCommandLine="False" />
<StringListProperty
Name="AdditionalDependencies"
DisplayName="Additional Dependencies"
IncludeInCommandLine="False"
Visible="false" />
<StringProperty
Subtype="AdditionalOptions"
Name="AdditionalOptions"
Category="Command Line">
<StringProperty.DisplayName>
<sys:String>Additional Options</sys:String>
</StringProperty.DisplayName>
<StringProperty.Description>
<sys:String>Additional Options</sys:String>
</StringProperty.Description>
</StringProperty>
</Rule>
<ItemType
Name="Rcc"
DisplayName="Qt Rcc" />
<FileExtension
Name="*.qrc"
ContentType="Rcc" />
<ContentType
Name="Rcc"
DisplayName="Qt Rcc"
ItemType="Rcc" />
<Rule
Name="Moc"
PageTemplate="tool"
DisplayName="Qt Moc"
Order="200">
<Rule.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Moc" />
</Rule.DataSource>
<Rule.Categories>
<Category
Name="General">
<Category.DisplayName>
<sys:String>General</sys:String>
</Category.DisplayName>
</Category>
<Category
Name="Command Line"
Subtype="CommandLine">
<Category.DisplayName>
<sys:String>Command Line</sys:String>
</Category.DisplayName>
</Category>
</Rule.Categories>
<StringListProperty
Name="Inputs"
Category="Command Line"
IsRequired="true"
Switch=" ">
<StringListProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Moc"
SourceType="Item" />
</StringListProperty.DataSource>
</StringListProperty>
<StringProperty
Name="CommandLineTemplate"
DisplayName="Command Line"
Visible="False"
IncludeInCommandLine="False" />
<DynamicEnumProperty
Name="MocBeforeTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute Before</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run before.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^MocBeforeTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<DynamicEnumProperty
Name="MocAfterTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute After</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run after.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^MocAfterTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType=""
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<StringListProperty
Name="Outputs"
DisplayName="Outputs"
Visible="False"
IncludeInCommandLine="False" />
<StringProperty
Name="ExecutionDescription"
DisplayName="Execution Description"
Visible="False"
IncludeInCommandLine="False" />
<StringListProperty
Name="AdditionalDependencies"
DisplayName="Additional Dependencies"
IncludeInCommandLine="False"
Visible="false" />
<StringProperty
Subtype="AdditionalOptions"
Name="AdditionalOptions"
Category="Command Line">
<StringProperty.DisplayName>
<sys:String>Additional Options</sys:String>
</StringProperty.DisplayName>
<StringProperty.Description>
<sys:String>Additional Options</sys:String>
</StringProperty.Description>
</StringProperty>
</Rule>
<ItemType
Name="Moc"
DisplayName="Qt Moc" />
<FileExtension
Name="*.dummy"
ContentType="Moc" />
<ContentType
Name="Moc"
DisplayName="Qt Moc"
ItemType="Moc" />
<Rule
Name="Uic"
PageTemplate="tool"
DisplayName="Qt Uic"
Order="200">
<Rule.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Uic" />
</Rule.DataSource>
<Rule.Categories>
<Category
Name="General">
<Category.DisplayName>
<sys:String>General</sys:String>
</Category.DisplayName>
</Category>
<Category
Name="Command Line"
Subtype="CommandLine">
<Category.DisplayName>
<sys:String>Command Line</sys:String>
</Category.DisplayName>
</Category>
</Rule.Categories>
<StringListProperty
Name="Inputs"
Category="Command Line"
IsRequired="true"
Switch=" ">
<StringListProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType="Uic"
SourceType="Item" />
</StringListProperty.DataSource>
</StringListProperty>
<StringProperty
Name="CommandLineTemplate"
DisplayName="Command Line"
Visible="False"
IncludeInCommandLine="False" />
<DynamicEnumProperty
Name="UicBeforeTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute Before</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run before.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^UicBeforeTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<DynamicEnumProperty
Name="UicAfterTargets"
Category="General"
EnumProvider="Targets"
IncludeInCommandLine="False">
<DynamicEnumProperty.DisplayName>
<sys:String>Execute After</sys:String>
</DynamicEnumProperty.DisplayName>
<DynamicEnumProperty.Description>
<sys:String>Specifies the targets for the build customization to run after.</sys:String>
</DynamicEnumProperty.Description>
<DynamicEnumProperty.ProviderSettings>
<NameValuePair
Name="Exclude"
Value="^UicAfterTargets|^Compute" />
</DynamicEnumProperty.ProviderSettings>
<DynamicEnumProperty.DataSource>
<DataSource
Persistence="ProjectFile"
ItemType=""
HasConfigurationCondition="true" />
</DynamicEnumProperty.DataSource>
</DynamicEnumProperty>
<StringListProperty
Name="Outputs"
DisplayName="Outputs"
Visible="False"
IncludeInCommandLine="False" />
<StringProperty
Name="ExecutionDescription"
DisplayName="Execution Description"
Visible="False"
IncludeInCommandLine="False" />
<StringListProperty
Name="AdditionalDependencies"
DisplayName="Additional Dependencies"
IncludeInCommandLine="False"
Visible="false" />
<StringProperty
Subtype="AdditionalOptions"
Name="AdditionalOptions"
Category="Command Line">
<StringProperty.DisplayName>
<sys:String>Additional Options</sys:String>
</StringProperty.DisplayName>
<StringProperty.Description>
<sys:String>Additional Options</sys:String>
</StringProperty.Description>
</StringProperty>
</Rule>
<ItemType
Name="Uic"
DisplayName="Qt Uic" />
<FileExtension
Name="*.ui"
ContentType="Uic" />
<ContentType
Name="Uic"
DisplayName="Qt Uic"
ItemType="Uic" />
</ProjectSchemaDefinitions>
+612
View File
@@ -0,0 +1,612 @@
/**
* QtUtilities.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "QtUtilities.h"
// Qt Headers
#include <QBitmap>
#include <QPainter>
#include <QLabel>
#include <QProgressBar>
#include <QCloseEvent>
#include <QPixmapCache>
#include <QResource>
#include <QString>
#include <QPushButton>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QAbstractButton>
#include <QPushButton>
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>
// Roblox Studio Headers
#include "RobloxMainWindow.h"
#include "UpdateUIManager.h"
namespace QtUtilities
{
/**
* Gets a pixmap from a sprite sheet.
*
* @param resourceName name of qrc resource for image
* @param index index of sprite
* @param iconSize width/height of sprite
* @param bMask true to return a mask of the sprite
*/
QPixmap getPixmap(const QString& resourceName, int index, int iconSize, bool bMask)
{
static QMap< QString, QList<QPixmap> > sPixmaps;
QList<QPixmap> pixmapList;
if(sPixmaps.contains(resourceName))
pixmapList = sPixmaps[resourceName];
if(pixmapList.isEmpty())
{
QPixmap completePixmap(resourceName);
for (int count=0; iconSize * count < completePixmap.width(); count++)
pixmapList.append(completePixmap.copy(iconSize*count, 0, iconSize, iconSize));
if(completePixmap.width())
sPixmaps[resourceName] = pixmapList;
}
QPixmap pixmap;
if(index >= 0 || index < pixmapList.size())
{
pixmap = pixmapList[index];
if(bMask)
{
QImage pixImg = pixmap.toImage();
QColor maskColor(pixImg.pixel(0,0));
pixmap.setMask(pixmap.createMaskFromColor(maskColor, Qt::MaskInColor));
}
}
return pixmap;
}
/**
* Gets a pixmap used by Qt to draw a tree control's branch indicator.
* Caches image so this function is very fast for multiple calls.
*/
QPixmap getBranchIndicator(QStyle::State iconState)
{
QPixmap pix;
if ( !QPixmapCache::find(QString::number(iconState),&pix) )
{
const int BranchIndicatorBorder = 2;
pix = QPixmap(BranchIndicatorSize,BranchIndicatorSize);
pix.fill(Qt::transparent);
QStyleOptionViewItemV4 branchOption;
branchOption.rect = QRect(
BranchIndicatorBorder,
BranchIndicatorBorder,
BranchIndicatorSize / 2 + BranchIndicatorBorder,
BranchIndicatorSize / 2 + BranchIndicatorBorder );
branchOption.state = iconState;
QPainter p(&pix);
QApplication::style()->drawPrimitive(QStyle::PE_IndicatorBranch,&branchOption,&p);
QPixmapCache::insert(QString::number(iconState),pix);
}
return pix;
}
QPixmap getBreakpointPixmap(int breakpointState, int size)
{
QPixmap pix;
if ( !QPixmapCache::find(QString("BreakpointState%1").arg(breakpointState),&pix) )
{
int scaledSize = QtUtilities::BranchIndicatorSize*6;
pix = QPixmap(scaledSize, scaledSize);
pix.fill(Qt::transparent);
QPoint center(scaledSize/2, scaledSize/2);
QPainter painter;
painter.begin(&pix);
QColor outerClr(186,46,73);
if (breakpointState == 1)
{
QColor innerClr(210,72,100);
QColor lightClr(230,153,168);
QPoint focalPt(0.4*scaledSize/2, 0.4*scaledSize/2);
QRadialGradient gradient(center.x(), center.y(), (scaledSize/2), focalPt.x(), focalPt.y());
gradient.setColorAt(0.4, lightClr);
gradient.setColorAt(0.8, innerClr);
gradient.setColorAt(1, outerClr);
painter.setBrush(gradient);
painter.setPen(Qt::NoPen);
}
else
{
QPen newPen(outerClr);
newPen.setWidth(6);
painter.setPen(newPen);
}
painter.drawEllipse(center, scaledSize/2, scaledSize/2);
painter.end();
pix = pix.scaled(size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
QPixmapCache::insert(QString("BreakpointState%1").arg(breakpointState), pix);
}
return pix;
}
QString getMacOSXVersion()
{
QString strMacVersion = "";
#ifdef Q_WS_MAC
QSysInfo::MacVersion macVersion = QSysInfo::MacintoshVersion;
switch (macVersion)
{
case QSysInfo::MV_LEOPARD:
strMacVersion = "Mac OS X 10.5";
break;
case QSysInfo::MV_SNOWLEOPARD:
strMacVersion = "Mac OS X 10.6";
break;
default:
// Roblox Studio is not allowed to Run on 10.4 or below
// So the OSX is probably 10.7 or higher
// We cannot query for Lion/Mountain Lion as Qt 4.7 did not support Lion/Mountain Lion
// We need to move upto Qt 4.8 to support Lion
// Qt 5 which is in Beta will officially supports Mountain Lion
strMacVersion = "Mac OS X 10.7 or Higher";
}
#endif
return strMacVersion;
}
/**
* Converts a G3D::Color3 to a QColor.
*/
QColor toQColor(const G3D::Color3& color)
{
return QColor(
color.r * 255,
color.g * 255,
color.b * 255 );
}
int toInt(Q_PID pId)
{
int processId = 0;
#ifdef Q_WS_WIN32
if (pId)
processId = pId->dwProcessId;
#else
processId = pId;
#endif
return processId;
}
QByteArray getResourceFileBytes(const QString& resourceName)
{
const QResource resource(resourceName);
RBXASSERT(resource.data());
RBXASSERT(resource.size() > 0);
QByteArray data((const char*)resource.data(),(int)resource.size());
// uncompress if necessary
if ( resource.isCompressed() )
data = qUncompress(data);
return data;
}
/**
* Given a resource file name, load the text of the resource.
* The resource data must be in text.
*
* @param resourceName name of resource in the form ":/path/file.ext"
* @return contents of resource file as a text string
*/
QString getResourceFileText(const QString& resourceName)
{
return getResourceFileBytes(resourceName);
}
/**
* Sets the shortcut keys and adds key sequences to tooltip.
*/
void setActionShortcuts(QAction& action,const QList<QKeySequence> shortcuts)
{
QString tooltip = action.toolTip();
// set status tip
if ( action.statusTip().isEmpty() )
action.setStatusTip(tooltip);
// do not set action shortcut in case of ribbon bar (it get's added by ribbon bar)
if (!UpdateUIManager::Instance().getMainWindow().isRibbonStyle())
{
// strip off old shortcut if found in tooltip
// some common commands have tooltips already set from assignAccelerators
int index = tooltip.lastIndexOf(" (");
if ( index != -1 )
tooltip = tooltip.left(index);
tooltip += " (";
for ( int i = 0 ; i < shortcuts.size() ; ++i )
{
if ( i > 0 )
tooltip += ", ";
tooltip += shortcuts[i].toString(QKeySequence::NativeText);
}
tooltip += ")";
action.setToolTip(tooltip);
}
action.setShortcuts(shortcuts);
}
/**
* Inserts newlines to attempt to wrap text to the given width.
*
* @param text input text
* @param width column width of new text
* @return text with newlines inserted to attempt to make no long wider than the width
*/
QString wrapText(const QString& text,int width)
{
QString s = text;
QString result;
QRegExp regExp("[\\s\\n,\\.]");
while ( !s.isEmpty() )
{
int index;
if ( s.length() > width )
{
// search from width and find the last break
index = s.lastIndexOf(regExp,width);
if ( index == -1 )
{
// otherwise try searching forward to the next break
index = s.indexOf(regExp,width);
if ( index == -1 )
{
// give up searching
index = s.length();
}
}
}
else
{
result += s;
break;
}
result += s.left(index + 1);
result += "\n";
s = s.right(s.length() - index - 1);
}
return result;
}
/*****************************************************************************/
// ProgressDialog
/*****************************************************************************/
ProgressDialog::ProgressDialog(QWidget *pParent, QWidget* lastFocusWidget)
: QDialog(pParent, Qt::WindowTitleHint)
, m_lastFocusWidget(lastFocusWidget)
, m_bExecutionComplete(false)
, m_eventLoop(NULL)
{
if(m_lastFocusWidget == NULL)
{
m_lastFocusWidget = QApplication::focusWidget();
if(m_lastFocusWidget && m_lastFocusWidget->focusProxy())
{
m_lastFocusWidget = m_lastFocusWidget->focusProxy();
}
}
updateWindowTitle(this);
setFixedSize(275, 100);
setWindowModality(Qt::WindowModal);
m_pLabel = new QLabel(this);
m_pLabel->setAlignment(Qt::AlignCenter);
m_pProgressBar = new QProgressBar(this);
m_pProgressBar->setRange(0,0);
m_pProgressBar->setTextVisible(false);
QGridLayout *pGridLayout = new QGridLayout(this);
pGridLayout->setObjectName(QString::fromUtf8("gridLayout"));
pGridLayout->setVerticalSpacing(4);
pGridLayout->setHorizontalSpacing(2);
pGridLayout->addWidget(m_pLabel, 0, 0, 1, 1);
pGridLayout->addWidget(m_pProgressBar, 1, 0, 1, 1);
#ifdef Q_WS_MAC
UpdateUIManager::Instance().setMenubarEnabled(false);
#endif
m_pForceTimer = new QTimer(this);
QObject::connect(m_pForceTimer, SIGNAL(timeout()), this, SLOT(showDialog()));
}
ProgressDialog::~ProgressDialog()
{
#ifdef Q_WS_MAC
UpdateUIManager::Instance().setMenubarEnabled(true);
#endif
if(m_lastFocusWidget)
m_lastFocusWidget->setFocus();
close();
}
void ProgressDialog::setLabel(const QString &text)
{ m_pLabel->setText(text); }
void ProgressDialog::executeInNewThread(boost::function<void()> userWorkFunc)
{
if ( m_eventLoop )
delete m_eventLoop;
m_eventLoop = new QEventLoop(this);
m_workerThread.reset(new boost::thread(boost::bind(&ProgressDialog::execute, this, userWorkFunc)));
m_pForceTimer->start(300);
m_progressMutex.lock();
bool execDone = m_bExecutionComplete;
m_progressMutex.unlock();
if (!execDone)
m_eventLoop->exec();
}
void ProgressDialog::execute(boost::function<void()> userWorkFunc)
{
try
{
userWorkFunc();
}
catch(...)
{
// TODO - silent failure??
//RBXCRASH();
}
m_progressMutex.lock();
m_bExecutionComplete = true;
m_progressMutex.unlock();
m_eventLoop->exit();
}
void ProgressDialog::showDialog()
{
m_pForceTimer->stop();
show();
}
void ProgressDialog::closeEvent(QCloseEvent *evt)
{ evt->ignore(); }
void ProgressDialog::keyPressEvent(QKeyEvent *evt)
{
if (evt->key() != Qt::Key_Escape)
QDialog::keyPressEvent(evt);
}
/*****************************************************************************/
// RBXMessageBox
/*****************************************************************************/
RBXMessageBox::RBXMessageBox(QWidget* parent)
:QMessageBox(parent ? parent : &UpdateUIManager::Instance().getMainWindow())
{
updateWindowTitle(this);
}
RBXMessageBox::RBXMessageBox(QMessageBox::Icon icon, const QString & title, const QString & text,
QMessageBox::StandardButtons buttons, QWidget *parent, Qt::WindowFlags f)
:QMessageBox(icon, title, text, buttons, parent, f)
{
if(!parentWidget())
{
setParent(&UpdateUIManager::Instance().getMainWindow());
}
if(title.isNull() || title.isEmpty())
updateWindowTitle(this);
}
/**
* Updates the window title of a widget based on the widget's parent or the main window.
*/
void updateWindowTitle(QWidget* pWidget)
{
if(!pWidget)
return;
QString title = pWidget->windowTitle();
if( title.isEmpty() )
{
QWidget* parent = pWidget->parentWidget();
RobloxMainWindow& main_window = UpdateUIManager::Instance().getMainWindow();
if ( parent && parent != &main_window )
title = parent->windowTitle();
if ( title.isEmpty() )
title = main_window.getDialogTitle();
pWidget->setWindowTitle(title);
}
}
/*****************************************************************************/
// RBXMessageBox
/*****************************************************************************/
RBXConfirmationMessageBox::RBXConfirmationMessageBox(QString question, QString yesButtonName, QString noButtonName, QWidget* parent)
:QDialog(parent ? parent : &UpdateUIManager::Instance().getMainWindow())
,clickedYes(false)
{
updateWindowTitle(this);
label = new QLabel(question);
dontAskCheckbox = new QCheckBox(tr("Don't ask me again"));
// if label contains a link then let label open it in an external browser
if (question.contains("href="))
label->setOpenExternalLinks(true);
QHBoxLayout *messageLine = new QHBoxLayout;
QHBoxLayout *checkboxLine = new QHBoxLayout;
QHBoxLayout *buttonLine = new QHBoxLayout;
QVBoxLayout *mainLayout = new QVBoxLayout;
label->setWordWrap(true);
buttonBox = new QDialogButtonBox;
yesButton = new QPushButton(yesButtonName);
noButton = new QPushButton(noButtonName);
buttonBox->addButton(yesButton, QDialogButtonBox::ActionRole);
buttonBox->addButton(noButton, QDialogButtonBox::ActionRole);
messageLine->addWidget(label);
checkboxLine->addWidget(dontAskCheckbox);
buttonLine->addWidget(buttonBox);
mainLayout->addLayout(messageLine);
mainLayout->addLayout(checkboxLine);
mainLayout->addLayout(buttonLine);
setLayout(mainLayout);
setModal(true);
connect(yesButton, SIGNAL(clicked()), this, SLOT(markYes()));
connect(noButton, SIGNAL(clicked()), this, SLOT(markNo()));
connect(buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(close()));
}
void RBXConfirmationMessageBox::markYes()
{
clickedYes = true;
}
void RBXConfirmationMessageBox::markNo()
{
clickedYes = false;
}
/*****************************************************************************/
// StackedWidgetAnimator
/*****************************************************************************/
StackedWidgetAnimator::StackedWidgetAnimator(QStackedWidget* pStackedWidget)
: QObject(pStackedWidget)
, m_pStackedWidget(pStackedWidget)
, m_FromIndex(-1)
, m_PendingRequestedIndex(-1)
, m_ToIndex(-1)
, m_bAnimInProgress(false)
{
}
void StackedWidgetAnimator::setCurrentIndex(int index)
{
if (m_bAnimInProgress)
{
m_PendingRequestedIndex = index;
return;
}
moveIndex(m_pStackedWidget->currentIndex(), index);
}
void StackedWidgetAnimator::moveIndex(int fromIndex, int toIndex)
{
if (fromIndex == toIndex)
return;
m_bAnimInProgress = true;
QWidget* fromWidget = m_pStackedWidget->widget(fromIndex);
QWidget* toWidget = m_pStackedWidget->widget(toIndex);
int offsetx = m_pStackedWidget->frameRect().width();
int offsety = m_pStackedWidget->frameRect().height();
toWidget->setGeometry (0, 0, offsetx, offsety);
if (fromIndex < toIndex)
{
offsetx = -offsetx;
offsety = 0;
}
else
{
offsety = 0;
}
QPoint fromWidgetPos= fromWidget->pos();
QPoint toWidgetPos= toWidget->pos();
m_fromWidgetPos = fromWidgetPos;
toWidget->move(toWidgetPos.x() - offsetx, toWidgetPos.y() - offsety);
toWidget->show();
toWidget->raise();
QParallelAnimationGroup *animationGroup = new QParallelAnimationGroup;
animationGroup->addAnimation(getAnimation(fromWidget, QPoint(fromWidgetPos.x(), fromWidgetPos.y()), QPoint(offsetx + fromWidgetPos.x(), offsety + fromWidgetPos.y())));
animationGroup->addAnimation(getAnimation(toWidget, QPoint(-offsetx + toWidgetPos.x(), offsety + toWidgetPos.y()), QPoint(toWidgetPos.x(), toWidgetPos.y())));
m_ToIndex = toIndex;
m_FromIndex = fromIndex;
connect(animationGroup, SIGNAL(finished()), this, SLOT(onAnimationFinished()));
animationGroup->start(QAbstractAnimation::DeleteWhenStopped);
}
void StackedWidgetAnimator::onAnimationFinished()
{
m_pStackedWidget->setCurrentIndex(m_ToIndex);
m_pStackedWidget->widget(m_FromIndex)->hide();
m_pStackedWidget->widget(m_FromIndex)->move(m_fromWidgetPos);
m_ToIndex = -1;
m_FromIndex = -1;
m_bAnimInProgress = false;
int pendingIndex = m_PendingRequestedIndex;
m_PendingRequestedIndex = -1;
if (pendingIndex > 0)
setCurrentIndex(pendingIndex);
}
QPropertyAnimation* StackedWidgetAnimator::getAnimation(QWidget* pWidget, const QPoint& startVal, const QPoint& endVal)
{
QPropertyAnimation *pPropertyAnimation = new QPropertyAnimation(pWidget, "pos");
pPropertyAnimation->setDuration(500);
pPropertyAnimation->setEasingCurve(QEasingCurve::Linear);
pPropertyAnimation->setStartValue(startVal);
pPropertyAnimation->setEndValue(endVal);
return pPropertyAnimation;
}
}
+190
View File
@@ -0,0 +1,190 @@
/**
* QtUtilities.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QStyle>
#include <QDialog>
#include <QMutex>
#include <QMessageBox>
#include <QPointer>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QProcess>
// 3rd Party Headers
#include "boost/function.hpp"
#include "boost/thread.hpp"
#include "boost/scoped_ptr.hpp"
// Roblox Headers
#include "g3d/Color3.h"
class QPixmap;
class QEventLoop;
class QProgressBar;
class QLabel;
class QDialogButtonBox;
class QPushButton;
class QStackedWidget;
class QPropertyAnimation;
namespace QtUtilities
{
#ifdef Q_WS_MAC
const int BranchIndicatorSize = 18;
#else
const int BranchIndicatorSize = 14;
#endif
QPixmap getBranchIndicator(QStyle::State iconState);
QPixmap getBreakpointPixmap(int breakpointState, int size);
QPixmap getPixmap(const QString& resourceName, int index, int iconSize = 16, bool bMask = false);
void updateWindowTitle(QWidget* pWidget);
QString getMacOSXVersion();
QColor toQColor(const G3D::Color3& color);
int toInt(Q_PID pId);
/**
* Given a resource file name, load the text of the resource.
* The resource data must be in text.
*
* @param resourceName name of resource in the form ":/path/file.ext"
* @return contents of resource file as a text string
*/
QString getResourceFileText(const QString& resourceName);
QByteArray getResourceFileBytes(const QString& resourceName);
/**
* Sets the shortcut keys and adds key sequences to tooltip.
*/
void setActionShortcuts(QAction& action,const QList<QKeySequence> shortcuts);
/**
* Inserts newlines to attempt to wrap text to the given width.
*
* @param text input text
* @param width column width of new text
* @return text with newlines inserted to attempt to make no long wider than the width
*/
QString wrapText(const QString& text,int width);
static void sleep(int ms)
{
#ifdef _WIN32
::Sleep(ms);
#else
::usleep(1000 * ms);
#endif
}
class ProgressDialog: public QDialog
{
Q_OBJECT
public:
ProgressDialog(QWidget *pParent, QWidget* lastFocusWidget = 0);
virtual ~ProgressDialog();
void setLabel(const QString &text);
void executeInNewThread(boost::function<void()> userWorkFunc);
private Q_SLOTS:
void showDialog();
private:
/*override*/ void closeEvent(QCloseEvent *evt);
/*override*/ void keyPressEvent(QKeyEvent *evt);
void execute(boost::function<void()> userWorkFunc);
boost::scoped_ptr<boost::thread> m_workerThread;
QEventLoop* m_eventLoop;
QProgressBar* m_pProgressBar;
QLabel* m_pLabel;
QPointer<QWidget> m_lastFocusWidget;
QTimer* m_pForceTimer;
QMutex m_progressMutex;
volatile bool m_bExecutionComplete;
};
class RBXMessageBox : public QMessageBox
{
public:
RBXMessageBox(QWidget* parent = 0);
RBXMessageBox(QMessageBox::Icon icon, const QString & title, const QString & text,
QMessageBox::StandardButtons buttons = NoButton, QWidget *parent = 0,
Qt::WindowFlags f = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint );
};
class RBXConfirmationMessageBox : public QDialog
{
Q_OBJECT
private:
QLabel *label;
QDialogButtonBox* buttonBox;
QCheckBox* dontAskCheckbox;
QPushButton* yesButton;
QPushButton* noButton;
bool clickedYes;
public:
RBXConfirmationMessageBox(QString question, QString yesButtonName = QObject::tr("Yes"), QString noButtonName = QObject::tr("No"), QWidget* parent = 0);
bool getClickedYes() { return clickedYes; }
bool getCheckBoxState() { return dontAskCheckbox->isChecked(); }
public Q_SLOTS:
void markYes();
void markNo();
};
inline QString qstringFromStdString( const std::string& s )
{
return QString::fromStdString(s);
}
inline QString qstringFromStdString( const std::wstring& str )
{
#ifdef _MSC_VER
return QString::fromUtf16((const ushort *)str.c_str());
#else
return QString::fromStdWString(str);
#endif
}
class StackedWidgetAnimator : public QObject
{
Q_OBJECT
public:
StackedWidgetAnimator(QStackedWidget* pStackedWidget);
void setCurrentIndex(int index);
private Q_SLOTS:
void onAnimationFinished();
private:
void moveIndex(int fromIndex, int toIndex);
QPropertyAnimation* getAnimation(QWidget* pWidget, const QPoint& startVal, const QPoint& endVal);
QStackedWidget* m_pStackedWidget;
QPoint m_fromWidgetPos;
int m_PendingRequestedIndex;
int m_FromIndex;
int m_ToIndex;
bool m_bAnimInProgress;
};
}
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
/**
* RbxContent.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "RbxContent.h"
// Roblox Headers
#include "util/Http.h"
#include "util/standardout.h"
#include "RobloxGameExplorer.h"
#include "UpdateUIManager.h"
FASTFLAG(StudioEnableGameAnimationsTab)
RbxContent::RbxContent( QObject *parent)
: QObject(parent)
{
}
// Publish Selection To Roblox
QString RbxContent::Upload(const QString &urlQStr)
{
QByteArray ba = urlQStr.toLocal8Bit();
const char *url = ba.data();
bool success = RBX::Http::trustCheck(url);
std::string response;
if(success)
{
try
{
RBX::Http http(url);
http.post(data, RBX::Http::kContentTypeApplicationXml, true, response);
}
catch (std::exception &e)
{
RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, e.what());
return QString("");
}
}
// if we are creating 'new' animation game asset then we will need to reload game explorer
if (FFlag::StudioEnableGameAnimationsTab && !response.empty() &&
urlQStr.contains("uploadnewanimation?assetTypeName=Animation") && urlQStr.contains("isGamesAsset=True"))
{
RobloxGameExplorer& gameExplorer = UpdateUIManager::Instance().getViewWidget<RobloxGameExplorer>(eDW_GAME_EXPLORER);
QMetaObject::invokeMethod(&gameExplorer, "reloadDataFromWeb", Qt::QueuedConnection);
}
return QString::fromStdString(response);
}
+27
View File
@@ -0,0 +1,27 @@
/**
* RbxContent.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Standard C/C++ Headers
#include <sstream>
// Qt Headers
#include <QObject>
// Required for Publish Selection to Roblox
class RbxContent : public QObject
{
Q_OBJECT
public:
RbxContent(QObject *parent);
std::stringstream data;
public Q_SLOTS:
QString Upload(const QString &url);
};
File diff suppressed because it is too large Load Diff
+148
View File
@@ -0,0 +1,148 @@
/**
* RbxWorkspace.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QObject>
// boost headers
#include <boost/enable_shared_from_this.hpp>
// Roblox Headers
#include "v8xml/XmlElement.h"
#include "v8tree/Instance.h"
// If you comment the ENABLE_EDIT_PLACE_IN_STUDIO then the Play Button will start working
// The reason is if the Webpage has a StartGame implementation then the NPRoblox Plugin does not get loaded & that prevents the Play button to invoke the Player
#define ENABLE_EDIT_PLACE_IN_STUDIO
#define ENABLE_DRAG_DROP_TOOLBOX
namespace RBX
{
class DataModel;
class Workspace;
}
class RbxContent;
class QMessageBox;
class QAbstractButton;
// This class is used as the JS Hookup from the WebView
class RbxWorkspace
: public QObject
, public boost::enable_shared_from_this<RbxWorkspace>
{
Q_OBJECT
// Edit Buton Show up only if we return true for the IsRobloxAppIDE property
Q_PROPERTY(bool IsRobloxAppIDE READ getIsRobloxAppIDE) // MFC Studio && QT Studio (All Studio)
Q_PROPERTY(bool IsRoblox2App READ getIsRoblox2App) // QT Studio only
Q_PROPERTY(bool IsRobloxABApp READ getIsRobloxABApp) // Admin Build -- Play button will call window.external.StartGame to play inside of Studio as opposed to launching the Roblox Player
public:
RbxWorkspace(QObject *parent, RBX::DataModel* dm);
~RbxWorkspace();
bool getIsRobloxAppIDE() const { return true; }
bool getIsRoblox2App() const { return true; }
bool getIsRobloxABApp() const
{
#ifdef STUDIO_ADMIN_BUILD
return true;
#else
return false;
#endif
}
private:
QString m_selectorForFileUpload;
QMessageBox* m_screenShotInProgressMsgBox;
RBX::DataModel *m_pDataModel;
RbxContent *m_pContent;
QObject *m_pParent;
std::auto_ptr<XmlElement> writeSelectionToXml();
void writeToStream(const XmlElement* root, std::ostream& stream);
RBX::Workspace* getWorkspace() const;
void insert(RBX::ContentId contentId, bool insertInto);
void insert(std::istream& stream, bool insertInto);
void insert(RBX::Instances& instances, bool insertInto);
void loadContent(RBX::ContentId contentId, RBX::Instances& instances, bool& hasError);
void Close() { Q_EMIT closeEvent(); }
void Hide() { Q_EMIT hideEvent(); }
void Show() { Q_EMIT showEvent(); }
static void pluginInstallCompleteHelper(weak_ptr<RbxWorkspace> weakWorkspace, bool succeeded, int assetId);
public:
static bool isScriptAssetUploadEnabled;
static bool isAnimationAssetUploadEnabled;
static bool isImageModelAssetUploadEnabled;
void onScreenshotFinished(const std::string &fileName);
Q_INVOKABLE void onScreenShotFinished_MT(QString fileName);
void setDataModel(RBX::DataModel *pDataModel) { m_pDataModel = pDataModel; }
Q_SIGNALS:
void thumbnailTaken(int width, int height);
void closeEvent();
void hideEvent();
void showEvent();
void PluginInstallComplete(bool succeeded, int assetId);
public Q_SLOTS:
// Publish To Roblox JS Callback
bool SaveUrl( const QString &saveUrl );
bool Save();
void handleTakeThumbnail(QAbstractButton*);
#ifdef ENABLE_EDIT_PLACE_IN_STUDIO
// Build & Edit Button Hookup for JS Callback to open the Place in Studio
bool StartGame(const QString &ticket, const QString &url, const QString &script);
#endif
// Opens a recent file -- called from the StartPage -- file name is looked up in recent files for safety (prevent arbitrary file opening)
bool OpenRecentFile(const QString &recentFileName);
// Publish Selection As Model to Roblox JS Callback, it returns another JS Callboack object RbxContent which does the actual publishing
QObject* WriteSelection();
// HTML Toolbox related JS Callback
bool Insert(const QString &url);
QString GetStudioSessionID();
bool TakeThumbnail(QString selector);
#ifdef ENABLE_DRAG_DROP_TOOLBOX
bool StartDrag(const QString& url);
#endif
// Opens picture folder
bool OpenPicFolder();
// Posts given image to Roblox site
void PostImage(bool doPost, int postSetting, const QString &fileName, const QString &seoStr);
void InstallPlugin(int assetId, int assetVersion);
QString GetInstalledPlugins();
void SetPluginEnabled(int assetId, bool enabled);
void UninstallPlugin(int assetId);
#ifdef Q_WS_WIN32 // must be Q_WS_WIN32 so moc picks it up
bool OpenVideoFolder();
void UploadVideo(const QString &token, bool doPost, int postSetting, const QString &title);
#endif
void OpenUniverse(int universeId);
void PublishAsNewUniverse();
void PublishAsNewGroupUniverse(int groupId);
void PublishToUniverse(int universeId);
void ImportAsset(int assetId);
QString GetGameAnimations(int universeId);
};
+73
View File
@@ -0,0 +1,73 @@
/**
* RenderStatsItem.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "RenderStatsItem.h"
#include "rbx/SystemUtil.h"
// Roblox Headers
#include "util/Profiling.h"
RenderStatsItem::RenderStatsItem(const RBX::RenderStats& renderStats)
: m_renderStats(renderStats)
{
}
boost::shared_ptr<RenderStatsItem> RenderStatsItem::create(const RBX::RenderStats& renderStats)
{
boost::shared_ptr<RenderStatsItem> pResult = RBX::Creatable<RBX::Instance>::create<RenderStatsItem, const RBX::RenderStats&>(renderStats);
RBX::Stats::Item* pItem3D = pResult->createBoundChildItem(*(renderStats.cpuRenderTotal));
pItem3D->createBoundChildItem(*(renderStats.culling));
pItem3D->createBoundChildItem(*(renderStats.flip));
pItem3D->createBoundChildItem(*(renderStats.renderObjects));
pItem3D->createBoundChildItem(*(renderStats.updateLighting));
pItem3D->createBoundChildItem(*(renderStats.adorn2D));
pItem3D->createBoundChildItem(*(renderStats.adorn3D));
pItem3D->createBoundChildItem(*(renderStats.visualEngineSceneUpdater));
pItem3D->createBoundChildItem(*(renderStats.finishRendering));
pItem3D->createBoundChildItem(*(renderStats.renderTargetUpdate));
pItem3D->createBoundChildItem(*(renderStats.updateDynamicParts));
pItem3D->createBoundChildItem(*(renderStats.frameRateManager));
RBX::Stats::Item* updateScene = pItem3D->createBoundChildItem(*(renderStats.updateSceneGraph));
updateScene->createBoundChildItem(*(renderStats.updateAllInvalidParts));
updateScene->createBoundChildItem(*(renderStats.updateDynamicsAndAggregateStatics));
RBX::Stats::Item* pMem = pResult->createChildItem("Memory");
pResult->m_videoMemory = pMem->createChildItem("Video");
RBX::Stats::Item* pStateChanges = pResult->createChildItem("State Changes");
pResult->m_majorStateChanges = pStateChanges->createChildItem("Major");
pResult->m_minorStateChanges = pStateChanges->createChildItem("Minor");
return pResult;
}
void RenderStatsItem::update()
{
RBX::Stats::Item::update();
m_videoMemory->formatMem(size_t(RBX::SystemUtil::getVideoMemory()));
}
void RenderStatsItem::addBucketToItem(RBX::Stats::Item* item, const RBX::Profiling::BucketProfile& bucketProfile)
{
const std::vector<int>& data = bucketProfile.getData();
const int* bucketLimits = bucketProfile.getLimits();
char temp[50];
sprintf_s(temp, 50, "Less than %u", bucketLimits[0]);
item->createBoundChildItem(temp, data[0]);
for(unsigned i = 1; i < data.size()-1; i++)
{
sprintf_s(temp, 50, "%u to %u", bucketLimits[i-1], bucketLimits[i]);
item->createBoundChildItem(temp, data[i]);
}
sprintf_s(temp, 50, "More than %u", bucketLimits[data.size()-2]);
item->createBoundChildItem(temp, data[data.size()-1]);
}
+44
View File
@@ -0,0 +1,44 @@
/*
* RenderStatsItem.h
* RobloxStudio
* Copied from old Roblox app directory
*
* Created by Vinod on 10/18/11.
* Copyright 2011 Roblox. All rights reserved.
*
*/
#pragma once
#include "V8Datamodel/Stats.h"
#include "GfxBase/RenderStats.h"
#include "GfxBase/FrameRateManager.h"
#include "boost/shared_ptr.hpp"
namespace RBX
{
namespace Stats
{
class Item;
class StatsService;
}
namespace Profiling
{
class BucketProfile;
}
}
class RenderStatsItem : public RBX::Stats::Item
{
public:
RenderStatsItem(const RBX::RenderStats& renderStats);
void update();
static boost::shared_ptr<RenderStatsItem> create(const RBX::RenderStats& renderStats);
static void addBucketToItem(RBX::Stats::Item* item, const RBX::Profiling::BucketProfile& bucketProfile);
private:
RBX::Stats::Item* m_videoMemory;
RBX::Stats::Item* m_majorStateChanges;
RBX::Stats::Item* m_minorStateChanges;
const RBX::RenderStats& m_renderStats;
};
+166
View File
@@ -0,0 +1,166 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>540</width>
<height>463</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>540</width>
<height>463</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>540</width>
<height>463</height>
</size>
</property>
<property name="windowTitle">
<string/>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>54</x>
<y>416</y>
<width>461</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>20</x>
<y>10</y>
<width>81</width>
<height>71</height>
</rect>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/images/RobloxStudio.png&quot; width=&quot;70&quot; height=&quot;70&quot; style=&quot;float: left;&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="margin">
<number>0</number>
</property>
<property name="indent">
<number>0</number>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>40</x>
<y>80</y>
<width>511</width>
<height>381</height>
</rect>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;We’re proud to present this improved ROBLOX Studio experience! In addition&lt;br/&gt;to large performance improvements and new features, you may notice a &lt;br/&gt;different look and feel to ROBLOX Studio. As always, we welcome your &lt;br/&gt;feedback as we continually strive to provide you with a great product. &lt;br/&gt;Please leave your feedback in our ROBLOX Studio forum. &lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;Here are just a few of the recent improvements to ROBLOX Studio: &lt;/span&gt;&lt;/p&gt;&lt;p&gt;
&lt;div style=&quot;margin-left: 20px;&quot;&gt;
&lt;img src=&quot;:/images/checkmark.png&quot;/&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;80% fewer crashes &lt;/span&gt;&lt;br/&gt;
&lt;img src=&quot;:/images/checkmark.png&quot;/&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;Dockable basic objects panel &lt;/span&gt;&lt;br/&gt;
&lt;img src=&quot;:/images/checkmark.png&quot;/&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;Interface settings and docks remember how you set them &lt;/span&gt;&lt;br/&gt;
&lt;img src=&quot;:/images/checkmark.png&quot;/&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;Performance improvements for selection and rendering &lt;/span&gt;&lt;br/&gt;
&lt;img src=&quot;:/images/checkmark.png&quot;/&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;Fewer crashes during file saves &lt;/span&gt;&lt;br/&gt;
&lt;img src=&quot;:/images/checkmark.png&quot;/&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;Collision free tool – Rotate, resize, and translate with no collisions &lt;/span&gt;&lt;br/&gt;
&lt;img src=&quot;:/images/checkmark.png&quot;/&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;Script syntax highlighting support for core libraries &lt;/span&gt;&lt;br/&gt;
&lt;img src=&quot;:/images/checkmark.png&quot;/&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;Additional shortcut keys &lt;/span&gt;&lt;br/&gt;
&lt;img src=&quot;:/images/checkmark.png&quot;/&gt;&amp;nbsp;&amp;nbsp;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;Auto-indentation for scripts &lt;/span&gt;
&lt;/div&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;span style=&quot; font-family:'arial'; font-size:14px;&quot;&gt;Sincerely,&lt;br/&gt;The ROBLOX Studio Team &lt;/span&gt;&lt;br/&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>105</x>
<y>21</y>
<width>421</width>
<height>51</height>
</rect>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/images/ROBLOX-Studio-2013.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<zorder>label</zorder>
<zorder>label_3</zorder>
<zorder>label_2</zorder>
<zorder>buttonBox</zorder>
</widget>
<resources>
<include location="RobloxStudio.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+309
View File
@@ -0,0 +1,309 @@
/**
* Roblox.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "Roblox.h"
// Qt Headers
#include <QDateTime>
#include <QTimer>
#include <QThread>
// 3rd Party Headers
#include "boost/function.hpp"
// Roblox Headers
#include "v8datamodel/GameBasicSettings.h"
#include "v8datamodel/DebugSettings.h"
#include "v8datamodel/Game.h"
#include "v8datamodel/MouseCommand.h"
#include "v8datamodel/TeleportService.h"
#include "v8datamodel/DataModel.h"
#include "v8datamodel/Light.h"
#include "v8datamodel/Lighting.h"
#include "v8datamodel/HttpService.h"
#include "rbx/TaskScheduler.h"
#include "rbx/ProcessPerfCounter.h"
#include "Util/standardout.h"
#include "Util/RobloxGoogleAnalytics.h"
#include "Util/Statistics.h"
#include "NetworkSettings.h"
#include "RenderSettingsItem.h"
#include "V8DataModel/Workspace.h"
#include "v8datamodel/CustomParticleEmitter.h"
#include "v8datamodel/ContentProvider.h"
// Roblox Studio Headers
#include "RobloxIDEDoc.h"
#include "RobloxMainWindow.h"
#include "UpdateUIManager.h"
#include "FunctionMarshaller.h"
#include "RobloxSettings.h"
#include "AuthoringSettings.h"
#include "ExternalHandlers.h"
#include "RobloxDocManager.h"
#include "StudioUtilities.h"
#include "MachineConfig.h"
#include "RobloxUser.h"
#include "rbx/Profiler.h"
FASTFLAG(SurfaceLightEnabled)
DYNAMIC_FASTFLAG(CustomEmitterInstanceEnabled)
FASTFLAG(UsePGSSolver)
FASTFLAGVARIABLE(StudioABTestEnabled, false)
#ifdef STUDIO_ADMIN_BUILD
#include "util/MD5Hasher.h"
#endif
bool Roblox::sInitialized = false;
rbx::signals::scoped_connection messageOutConnection;
#ifdef Q_WS_WIN
static boost::shared_ptr<CProcessPerfCounter> s_perfCounter;
#endif
#ifdef Q_WS_MAC
extern "C" {
void writeFastLogDumpHelper(const char* fileName, int numEntries)
{
FLog::WriteFastLogDump(fileName, numEntries);
}
};
#endif
//this function is being used in RBXGraphics.cpp also!
std::string macBundlePath()
{
#ifdef Q_WS_MAC
char path[1024];
CFBundleRef mainBundle = CFBundleGetMainBundle();
assert(mainBundle);
CFURLRef mainBundleURL = CFBundleCopyBundleURL(mainBundle);
assert(mainBundleURL);
CFStringRef cfStringRef = CFURLCopyFileSystemPath( mainBundleURL, kCFURLPOSIXPathStyle);
assert(cfStringRef);
CFStringGetCString(cfStringRef, path, 1024, kCFStringEncodingASCII);
CFRelease(mainBundleURL);
CFRelease(cfStringRef);
return std::string(path);
#endif
return "";
}
Roblox& Roblox::Instance()
{
static Roblox singleton;
return singleton;
}
Roblox::Roblox()
: m_pMarshaller(RBX::FunctionMarshaller::GetWindow())
, m_pTimer(new QTimer(this))
{
qRegisterMetaType<RBX::MessageType>("RBX::MessageType");
qRegisterMetaType<boost::shared_ptr<RBX::Instance> >("shared_ptr<RBX::Instance>");
qRegisterMetaType<const RBX::Reflection::PropertyDescriptor*>("const RBX::Reflection::PropertyDescriptor*");
connect(m_pTimer, SIGNAL(timeout()), this, SLOT(onTimeOut()));
}
Roblox::~Roblox()
{
RBX::FunctionMarshaller::ReleaseWindow(m_pMarshaller);
}
void Roblox::startTimer()
{
m_pTimer->start(1000);
}
void Roblox::stopTimer()
{
m_pTimer->stop();
}
void Roblox::onTimeOut()
{
#ifdef Q_WS_WIN
s_perfCounter->CollectData();
#endif
}
void Roblox::globalInit(const QString& urlArg, const QString& ticketArg, RBX::HttpFuture& settingsFuture)
{
if (sInitialized)
return;
#ifdef STUDIO_ADMIN_BUILD
RBX::DataModel::hash = RobloxSettings::getAdminKey().toStdString();
#endif
messageOutConnection = RBX::StandardOut::singleton()->messageOut.connect(&onMessageOut);
if (FFlag::StudioABTestEnabled)
{
AuthenticationHelper::Instance().waitForHttpAuthentication();
QSettings retentionData("Roblox", "Retention");
static const char* const kBrowserTrackerId = "BrowserTrackerId";
QString browserTrackerId = retentionData.value(kBrowserTrackerId, "").toString();
const std::string& apiUrl = RBX::ContentProvider::getUnsecureApiBaseUrl(GetBaseURL());
std::string abUrl = RBX::format("%s/users/get-studio-experiment-enrollments?firstStudioVisit=%s%s", apiUrl.c_str(), StudioUtilities::isFirstTimeOpeningStudio() ? "true" : "false",
browserTrackerId.isEmpty() ? "" : RBX::format("&browserTrackerId=%s", browserTrackerId.toStdString().c_str()).c_str());
RBX::HttpFuture abTestResult = FetchABTestDataAsync(abUrl);
try
{
std::string newBrowserTrackerId = LoadABTestFromString(abTestResult.get());
if(browserTrackerId.isEmpty() && !newBrowserTrackerId.empty())
{
retentionData.setValue(kBrowserTrackerId, QString(newBrowserTrackerId.c_str()));
}
}
catch(std::exception&)
{
FASTLOG(FLog::Error, "Failed to load AB test data");
}
}
RBX::Http::SetUseStatistics(true);
RobloxSettings::recoverAssets();
RobloxSettings::initWorkspaceSettings();
RBX::MouseCommand::enableAdvArrowTool(true);
#ifdef Q_WS_WIN
s_perfCounter = CProcessPerfCounter::getInstance();
#endif
// Force loading of settings classes
RBX::GameBasicSettings::singleton();
AuthoringSettings::singleton();
RBX::NetworkSettings::singleton();
CRenderSettingsItem::singleton();
RBX::DebugSettings::singleton();
RBX::GlobalAdvancedSettings::singleton()->loadState(std::string());
{
RBX::Security::Impersonator impersonate(RBX::Security::RobloxGameScript_);
RBX::GlobalBasicSettings::singleton()->loadState(std::string());
}
RBX::GameBasicSettings::singleton().setStudioMode(true);
// now that fast flags are loaded we can reconfigure our settings if necessary
AuthoringSettings::singleton().configureBasedOnFastFlags();
RBX::Profiler::onThreadCreate("Main");
// Initialize the TaskScheduler (after loading configs)
RBX::TaskScheduler::singleton().setThreadCount(RBX::TaskSchedulerSettings::singleton().getThreadPoolConfig());
Roblox::Instance().startTimer();
MachineConfig::setupMachineConfig();
RBX::TeleportService::SetCallback(&Roblox::Instance());
RBX::TeleportService::SetBaseUrl(RobloxSettings::getBaseURL().toStdString().c_str());
sInitialized = true;
if (!FFlag::SurfaceLightEnabled)
{
RBX::Reflection::ClassDescriptor* classDesc = &RBX::SurfaceLight::classDescriptor();
RBX::Reflection::Metadata::Class* pMetadataDynamic = RBX::Reflection::Metadata::Reflection::singleton()->get(*classDesc, false);
if (pMetadataDynamic)
{
RBX::Reflection::Metadata::Class::prop_deprecated.setValue(pMetadataDynamic, true);
}
}
if (!DFFlag::CustomEmitterInstanceEnabled)
{
RBX::Reflection::ClassDescriptor* classDesc = &RBX::CustomParticleEmitter::classDescriptor();
RBX::Reflection::Metadata::Class* pMetadataDynamic = RBX::Reflection::Metadata::Reflection::singleton()->get(*classDesc, false);
if (pMetadataDynamic)
{
RBX::Reflection::Metadata::Class::prop_deprecated.setValue(pMetadataDynamic, true);
}
}
if (!FFlag::UsePGSSolver)
{
RBX::Reflection::PropertyDescriptor* propDesc = RBX::Workspace::classDescriptor().findPropertyDescriptor("PGSPhysicsSolverEnabled");
if(propDesc) {
RBX::Reflection::Metadata::Member* pMetadataDynamic = RBX::Reflection::Metadata::Reflection::singleton()->get(*propDesc);
if(pMetadataDynamic)
RBX::Reflection::Metadata::Item::prop_deprecated.setValue(pMetadataDynamic, true);
}
}
}
void Roblox::globalShutdown()
{
RobloxSettings::saveAssets();
#ifdef Q_WS_WIN
s_perfCounter.reset();
#endif
Roblox::Instance().stopTimer();
RBX::GlobalBasicSettings::singleton()->saveState();
RBX::Game::globalExit();
messageOutConnection.disconnect();
}
// FunctionMarshaller help - posts worker thread message to UI thread and deals with it next time through, wait till the event is processed
void Roblox::sendAppEvent(void *pClosure)
{
RBX::CEvent *waitEvent = ((RBX::FunctionMarshaller::Closure *) pClosure)->waitEvent;
bool waitFlag = (waitEvent == NULL);
Q_EMIT Roblox::Instance().marshallAppEvent(pClosure, waitFlag);
if (waitEvent)
waitEvent->Wait();
}
// FunctionMarshaller help - posts worker thread message to UI thread and deals with it next time through, do not wait
void Roblox::postAppEvent(void *pClosure)
{
Q_EMIT Roblox::Instance().marshallAppEvent(pClosure, false);
}
bool Roblox::isTeleportEnabled() const
{
return true;
}
void Roblox::onMessageOut(const RBX::StandardOutMessage& message)
{
Q_EMIT Roblox::Instance().newOutputMessage(message.message.c_str(), message.type);
}
void Roblox::Teleport(const std::string& url, const std::string& ticket, const std::string& script)
{
// Teleporting in Studio is disabled.
RobloxIDEDoc* pPlayDoc = RobloxDocManager::Instance().getPlayDoc();
if(!pPlayDoc)
return;
pPlayDoc->teleportToURL(script.c_str(), true);
}
void Roblox::doTeleport(const std::string& url, const std::string& ticket, const std::string& script)
{
RBXASSERT(isTeleportEnabled());
boost::function<void()> callback = boost::bind(&Roblox::Teleport, this, url, ticket, script);
m_pMarshaller->Submit(callback);
}
+77
View File
@@ -0,0 +1,77 @@
/**
* Roblox.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QObject>
#include <QMetaType>
// Roblox Headers
#include "v8datamodel/TeleportCallback.h"
#include "v8datamodel/FastLogSettings.h"
namespace RBX
{
class Game;
class FunctionMarshaller;
struct StandardOutMessage;
}
class QTimer;
Q_DECLARE_METATYPE(RBX::MessageType);
class Roblox : public QObject, RBX::TeleportCallback
{
Q_OBJECT
Q_SIGNALS:
/**
* Emitted when a log entry is made from the engine that should be displayed to the user.
*
* @param message note that this is a copy, not a reference to handle multi-threading
* @param type type of log message
*/
void newOutputMessage(const QString message,RBX::MessageType type);
void marshallAppEvent(void* event,bool wait);
public:
static Roblox& Instance();
static void globalInit(const QString& urlArg, const QString& ticketArg, RBX::HttpFuture& settingsFuture);
static void globalShutdown();
static void sendAppEvent(void *pClosure);
static void postAppEvent(void *pClosure);
virtual bool isTeleportEnabled() const;
void doTeleport(const std::string& url, const std::string& ticket,
const std::string& script);
void Teleport(const std::string& url, const std::string& ticket,
const std::string& script);
void startTimer();
void stopTimer();
private Q_SLOTS:
void onTimeOut();
private:
Roblox(); // Private constructor
~Roblox();
Roblox(const Roblox&); // Prevent copy-construction
Roblox& operator=(const Roblox&); // Prevent assignment
static void onMessageOut(const RBX::StandardOutMessage& message);
RBX::FunctionMarshaller* m_pMarshaller;
QTimer* m_pTimer;
static bool sInitialized;
};
+312
View File
@@ -0,0 +1,312 @@
/**
* RobloxApplicationManager.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "RobloxApplicationManager.h"
// Qt Headers
#include <QStringList>
#include <QCoreApplication>
#include <QProcess>
#include <QLocalSocket>
#include <QLocalServer>
// Roblox Headers
#include "RobloxCookieJar.h"
#include "RobloxMainWindow.h"
#include "RobloxNetworkAccessManager.h"
#include "SharedLauncher.h"
#include "UpdateUIManager.h"
#include "QtUtilities.h"
#include "StudioDeviceEmulator.h"
#include "StudioUtilities.h"
const char* kChildProcessServerName = "rbxDeleteChildProcessesServer_%1";
const char* kDeleteChildProcessesMsg = "rbxDeleteChildProcesses";
const char* kChildProcessesDeletedMsg = "rbxChildProcessesDeleted";
RobloxApplicationManager::RobloxApplicationManager()
: childProcessHandler(NULL)
{
sharedMemory.setKey("QT_ROBLOX_STUDIO");
}
RobloxApplicationManager& RobloxApplicationManager::instance()
{
static RobloxApplicationManager instance;
return instance;
}
bool RobloxApplicationManager::createNewStudioInstance(
const QString& script,
const QString& fileLocation,
bool isTestMode,
bool isAvatarMode,
NewInstanceMode mode)
{
// Save the current layout before launching
UpdateUIManager::Instance().getMainWindow().saveApplicationStates();
// Persist our cookies (to make sure we have the most recent ones in the new app)
RobloxCookieJar* cookieJar = dynamic_cast<RobloxCookieJar*>(RobloxNetworkAccessManager::Instance().cookieJar());
if (cookieJar)
cookieJar->saveCookiesToDisk();
QStringList args;
args << SharedLauncher::IDEArgument;
composeArgList(args, script, fileLocation, isTestMode, isAvatarMode);
QString robloxApp = QCoreApplication::applicationFilePath();
if (mode == NewInstanceMode_Detached)
return QProcess::startDetached(robloxApp, args);
if (!childProcessHandler)
childProcessHandler = new ChildProcessHandler(&UpdateUIManager::Instance().getMainWindow());
return childProcessHandler->startChildProcess(robloxApp, args, mode);
}
int RobloxApplicationManager::getApplicationCount()
{
SharedData data;
if (!sharedMemory.lock())
return 0;
char* sharedData = (char*)sharedMemory.data();
if (!sharedData) // Odd scenario, should this ever happen?
{
sharedMemory.unlock();
return 0;
}
memcpy(&data, sharedData, sizeof(SharedData));
sharedMemory.unlock();
return data.numStudioApplications;
}
void RobloxApplicationManager::registerApplication()
{
sharedMemory.attach();
sharedMemory.create(sizeof(SharedData)); // Create it if it doesn't exist
modifyApplicationCount(1); // Increment our count
}
void RobloxApplicationManager::unregisterApplication()
{
modifyApplicationCount(-1); // Decrement our count
sharedMemory.detach();
}
void RobloxApplicationManager::modifyApplicationCount(int delta)
{
if (!sharedMemory.lock())
return;
SharedData data;
char* sharedData = (char*)sharedMemory.data();
if (sharedData)
{
memcpy(&data, sharedData, sizeof(SharedData));
}
else
{
// Should never be here?
data.numStudioApplications = 0;
}
data.numStudioApplications += delta;
memcpy(sharedData, &data, sizeof(SharedData));
sharedMemory.unlock();
}
void RobloxApplicationManager::composeArgList(
QStringList& argsContainer,
const QString& script,
const QString& fileLocation,
bool isTestMode,
bool isAvatarMode )
{
if ( !script.isEmpty() )
argsContainer << SharedLauncher::ScriptArgument << script;
if ( !fileLocation.isEmpty() )
argsContainer << SharedLauncher::FileLocationArgument << fileLocation;
if ( isTestMode )
argsContainer << SharedLauncher::TestModeArgument;
if ( isAvatarMode )
argsContainer << SharedLauncher::AvatarModeArgument;
if (StudioDeviceEmulator::Instance().isEmulatingTouch())
argsContainer << StudioUtilities::EmulateTouchArgument;
int studioWidth = StudioDeviceEmulator::Instance().getCurrentDeviceWidth();
int studioHeight = StudioDeviceEmulator::Instance().getCurrentDeviceHeight();
if (studioWidth)
argsContainer << StudioUtilities::StudioWidthArgument << QString("%1").arg(studioWidth);
if (studioHeight)
argsContainer << StudioUtilities::StudioHeightArgument << QString("%1").arg(studioHeight);
}
bool RobloxApplicationManager::hasChildProcesses()
{
if (childProcessHandler)
return childProcessHandler->hasChildProcesses();
return false;
}
void RobloxApplicationManager::cleanupChildProcesses(int timeout)
{
if (childProcessHandler)
childProcessHandler->cleanupChildProcesses(timeout);
}
void RobloxApplicationManager::startLocalServer()
{
if (!childProcessHandler)
childProcessHandler = new ChildProcessHandler(&UpdateUIManager::Instance().getMainWindow());
childProcessHandler->startLocalServer();
}
bool RobloxApplicationManager::hasLocalServer()
{
if (childProcessHandler)
return childProcessHandler->hasLocalServer();
return false;
}
ChildProcessHandler::ChildProcessHandler(QObject* parent)
: QObject(parent)
, m_pLocalServer(NULL)
{
}
bool ChildProcessHandler::startChildProcess(const QString& robloxApp, const QStringList &args, NewInstanceMode mode)
{
QProcess *newProcess = new QProcess();
newProcess->start(robloxApp, args);
if (!newProcess->waitForStarted())
return false;
if (mode == NewInstanceMode_Detached)
return true;
// child process can be managed if it's Server or Player
(mode == NewInstanceMode_Server) ? serverProcesses.append(newProcess) : playerProcesses.append(newProcess);
connect(newProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(onProcessFinished()));
return true;
}
void ChildProcessHandler::startLocalServer()
{
// if we already have a local server, do not do anything
if (m_pLocalServer)
return;
m_pLocalServer = new QLocalServer(this);
connect(m_pLocalServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
int processId = 0;
#ifdef Q_WS_WIN32
processId = ::GetCurrentProcessId();
#else
processId = getpid();
#endif
// set name for the local server (attach process id to make it unique)
m_pLocalServer->listen(QString(kChildProcessServerName).arg(processId));
}
bool ChildProcessHandler::hasLocalServer()
{ return m_pLocalServer != NULL; }
void ChildProcessHandler::cleanupChildProcesses(int timeout)
{
// first cleanup players launched from this instance of studio
QProcess* childProcess = NULL;
for (int ii = 0; ii < playerProcesses.size(); ++ii)
{
childProcess = playerProcesses.at(ii);
if (childProcess && childProcess->state() != QProcess::NotRunning)
{
childProcess->kill();
}
}
playerProcesses.clear();
// now time to remove server processes
for (int ii = 0; ii < serverProcesses.size(); ++ii)
{
childProcess = serverProcesses.at(ii);
if (childProcess && childProcess->state() != QProcess::NotRunning)
{
int processId = QtUtilities::toInt(childProcess->pid());
if (!processId)
continue;
// since server launches player (in Ribbon mode), we need to make sure those launched players also gets cleaned up
QLocalSocket localSocket;
localSocket.connectToServer(QString(kChildProcessServerName).arg(processId));
if (localSocket.waitForConnected(timeout))
{
QString message(kDeleteChildProcessesMsg);
localSocket.write(message.toUtf8());
localSocket.waitForBytesWritten(timeout);
localSocket.waitForReadyRead(timeout);
}
// now we are done with players cleanup, all set to kill the server
if (childProcess && childProcess->state() != QProcess::NotRunning)
childProcess->kill();
}
}
serverProcesses.clear();
}
bool ChildProcessHandler::hasChildProcesses()
{ return !serverProcesses.isEmpty() || !playerProcesses.isEmpty(); }
void ChildProcessHandler::onProcessFinished()
{
QProcess *pProcess = qobject_cast<QProcess *>(sender());
if (!pProcess)
return;
// check if we can remove process from list
bool removed = serverProcesses.removeOne(pProcess);
if (!removed)
removed = playerProcesses.removeOne(pProcess);
// if we do not have any child processes pending update toolbars
if (removed && serverProcesses.isEmpty() && playerProcesses.isEmpty())
UpdateUIManager::Instance().updateToolBars();
}
void ChildProcessHandler::onNewConnection()
{
if (!m_pLocalServer)
return;
QLocalSocket *localSocket = m_pLocalServer->nextPendingConnection();
if (localSocket && localSocket->waitForReadyRead(3000))
{
QString receivedMessage = QString::fromUtf8(localSocket->readAll().constData());
if (!receivedMessage.isEmpty() && (receivedMessage == kDeleteChildProcessesMsg))
{
// kill all child process
cleanupChildProcesses();
// update socket about the completion
localSocket->write(kChildProcessesDeletedMsg);
}
}
}
+98
View File
@@ -0,0 +1,98 @@
/**
* RobloxApplicationManager.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QSharedMemory>
#include <QProcess>
#include <QObject>
#include <QPointer>
class QString;
class QStringList;
class ChildProcessHandler;
class QLocalServer;
enum NewInstanceMode
{
NewInstanceMode_Detached = 0,
NewInstanceMode_Server,
NewInstanceMode_Player
};
class RobloxApplicationManager
{
public:
static RobloxApplicationManager& instance();
// launches a new process
bool createNewStudioInstance(
const QString& script = QString::null,
const QString& fileLocation = QString::null,
bool isTestMode = false,
bool isAvatarMode = false,
NewInstanceMode mode = NewInstanceMode_Detached);
// Increment/decrement counters for application types
void registerApplication();
void unregisterApplication();
int getApplicationCount();
// manages child process
bool hasChildProcesses();
void cleanupChildProcesses(int timeout = 3000);
void startLocalServer();
bool hasLocalServer();
private:
struct SharedData
{
int numStudioApplications;
int numPlayerApplications;
};
RobloxApplicationManager();
void composeArgList(
QStringList& argsContainer,
const QString& script,
const QString& fileLocation,
bool isTestMode,
bool isAvatarMode );
// Updates shared memory
void modifyApplicationCount(int delta);
QSharedMemory sharedMemory;
ChildProcessHandler* childProcessHandler;
};
class ChildProcessHandler : public QObject
{
Q_OBJECT
public:
ChildProcessHandler(QObject* parent);
bool startChildProcess(const QString& robloxApp, const QStringList &args, NewInstanceMode mode);
void cleanupChildProcesses(int timeout = 3000);
bool hasChildProcesses();
void startLocalServer();
bool hasLocalServer();
private Q_SLOTS:
void onProcessFinished();
void onNewConnection();
private:
QLocalServer* m_pLocalServer;
typedef QList<QPointer<QProcess> > ProcessCollection;
ProcessCollection serverProcesses;
ProcessCollection playerProcesses;
};
+65
View File
@@ -0,0 +1,65 @@
/**
* RobloxBasicDoc.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "RobloxBasicDoc.h"
// Qt Headers
#include <QString>
// Roblox Studio Headers
#include "IExternalHandler.h"
#include "UpdateUIManager.h"
#include "RobloxDocManager.h"
RobloxBasicDoc::RobloxBasicDoc()
: m_bActive(false)
{
}
RobloxBasicDoc::~RobloxBasicDoc()
{
}
void RobloxBasicDoc::addExternalHandler(IExternalHandler* extHandler)
{
if(extHandler == NULL)
return;
if(extHandler->handlerId().isEmpty())
return;
m_handlersMap[extHandler->handlerId()] = extHandler;
}
void RobloxBasicDoc::removeExternalHandler(const QString &handlerId)
{
if(handlerId.isEmpty())
return;
m_handlersMap.erase(handlerId);
}
IExternalHandler* RobloxBasicDoc::getExternalHandler(const QString &handlerId)
{
return m_handlersMap[handlerId];
}
bool RobloxBasicDoc::handleAction( const QString &actionID, bool isChecked /*= true*/ )
{
// Try to see if inherited class handles this action
return doHandleAction(actionID, isChecked);
}
/** do not override **/
bool RobloxBasicDoc::close()
{
if (doClose())
{
RobloxDocManager::Instance().updateWindowMenu();
return true;
}
return false;
}
+52
View File
@@ -0,0 +1,52 @@
/**
* RobloxBasicDoc.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
#include <QIcon>
// Standard C/C++ Headers
#include <map>
// Roblox Studio Headers
#include "IRobloxDoc.h"
typedef std::map<QString, IExternalHandler*> HandlersMap;
class RobloxBasicDoc: public IRobloxDoc
{
public:
virtual const QIcon& titleIcon() const { return m_emptyIcon; }
virtual const QString& titleTooltip() const { return m_titleToolTip; }
virtual QString windowTitle() const { return displayName(); }
virtual void addExternalHandler(IExternalHandler* extHandler);
virtual void removeExternalHandler(const QString &handlerId);
virtual IExternalHandler* getExternalHandler(const QString &handlerId);
virtual void updateUI(bool state) {}
virtual bool handleDrop(const QString &fileName) { return true; }
virtual bool doHandleAction(const QString& actionID, bool isChecked) { return false; }
#ifdef _WIN32
bool close() sealed; // do not override -- sealed is CLI, so at least we prevent it at compile time in Windows.
#else
bool close(); // do not override
#endif
protected:
RobloxBasicDoc();
virtual ~RobloxBasicDoc();
HandlersMap m_handlersMap;
bool m_bActive;
QIcon m_emptyIcon;
QString m_titleToolTip;
private:
virtual bool doClose() = 0;
bool handleAction(const QString &actionID, bool isChecked = true);
};
+183
View File
@@ -0,0 +1,183 @@
/**
* RobloxBrowser.cpp
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "RobloxBrowser.h"
#include "FastLog.h"
// Qt Headers
#include <QContextMenuEvent>
#include <QDialog>
#include <QFileInfo>
#include <QWebFrame>
// Roblox Studio Headers
#include "RobloxMainWindow.h"
#include "UpdateUIManager.h"
LOGVARIABLE(BrowserActivity, 0)
FASTFLAGVARIABLE(WebkitLocalStorageEnabled, false)
FASTFLAGVARIABLE(WebkitDeveloperToolsEnabled, false)
RobloxBrowser::RobloxBrowser(QWidget* parent)
: QWebView(parent)
, m_pPopup(NULL)
, m_pPopupDlg(NULL)
, m_loadingTimer(NULL)
, m_refreshIncr(0.0f)
{
connect(this, SIGNAL(loadStarted()), this, SLOT(loadStarted()));
connect(this, SIGNAL(loadFinished(bool)), this, SLOT(loadFinished(bool)));
setAcceptDrops(true);
}
RobloxBrowser::~RobloxBrowser()
{
}
void RobloxBrowser::dropEvent(QDropEvent *evt)
{
const QMimeData *pMimeData = evt->mimeData();
if (pMimeData && pMimeData->hasUrls())
{
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))
{
UpdateUIManager::Instance().getMainWindow().handleFileOpen(fileName, IRobloxDoc::IDE);
}
}
}
}
evt->acceptProposedAction();
}
bool RobloxBrowser::close()
{
if(m_pPopupDlg)
m_pPopupDlg->close();
return true;
}
void RobloxBrowser::loadStarted()
{
FASTLOGS(FLog::BrowserActivity, "URL: %s", url().toString().toStdString().c_str());
resetLoadingTimer();
}
void RobloxBrowser::loadFinished(bool)
{
if (m_loadingTimer)
{
m_loadingTimer->stop();
delete m_loadingTimer;
m_loadingTimer = NULL;
}
update();
}
void RobloxBrowser::resetLoadingTimer()
{
if (m_loadingTimer)
{
m_loadingTimer->stop();
delete m_loadingTimer;
m_loadingTimer = NULL;
}
m_loadingTimer = new QTimer(this);
connect(m_loadingTimer, SIGNAL(timeout()), this, SLOT(update()));
m_loadingTimer->start(1000/30);
m_refreshIncr = 0;
}
void RobloxBrowser::drawLoadingWatermark()
{
if (m_loadingTimer)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::HighQualityAntialiasing);
// Used to fade in the graphic at the beginning of the load timer.
float fadeIn = m_refreshIncr * 0.5f;
if (fadeIn > 1.0f)
fadeIn = 1.0f;
// The center of the graphic.
const float centerX = rect().left() + rect().width() * 0.5f;
const float centerY = rect().top() + rect().height() * 0.5f;
// Size of the rect around the loading fins.
const float rectSize = 40;
// Number of loading fins to draw.
const int numFins = 8;
QColor loadingFinColor(0, 0, 255, 128);
QColor rectPenColor(230, 230, 230, 208);
QColor rectFillColor(230, 230, 230, 180);
rectPenColor.setAlpha(rectPenColor.alpha() - ((1.0f - fadeIn) * rectPenColor.alpha()));
rectFillColor.setAlpha(rectFillColor.alpha() - ((1.0f - fadeIn) * rectFillColor.alpha()));
QPen rectPen(rectPenColor);
rectPen.setWidth(3);
rectPen.setCosmetic(true);
painter.setPen(rectPen);
painter.setBrush(rectFillColor);
QRect roundRect(centerX - rectSize * 0.5f,
centerY - rectSize * 0.5f,
rectSize,
rectSize);
painter.drawRoundRect(roundRect, 18);
QPen pen;
pen.setWidth(2);
pen.setCapStyle(Qt::RoundCap);
pen.setCosmetic(true);
painter.setBrush(Qt::NoBrush);
const float outerRadius = 10.0f;
const float innerRadius = 4.0f;
for (int i = 0; i < numFins; i++)
{
float pos = float(i) / float(numFins);
pos = pos * 2.0f - 1.0f;
float val = sin(m_refreshIncr * 0.5f + pos * M_PI) * 0.5f + 0.5f;
pen.setColor(QColor(loadingFinColor.red(),
loadingFinColor.green(),
loadingFinColor.blue(),
val * loadingFinColor.alpha() - ((1.0f - fadeIn) * (val * loadingFinColor.alpha()))));
painter.setPen(pen);
float sinpos = sin(m_refreshIncr + pos * M_PI);
float cospos = cos(m_refreshIncr + pos * M_PI);
painter.drawLine(floor(centerX + sinpos * innerRadius + 0.5f),
floor(centerY + cospos * innerRadius + 0.5f),
floor(centerX + sinpos * outerRadius + 0.5f),
floor(centerY + cospos * outerRadius + 0.5f));
}
m_refreshIncr += 0.08f;
}
}
void RobloxBrowser::paintEvent(QPaintEvent* event)
{
QWebView::paintEvent(event);
drawLoadingWatermark();
}
+43
View File
@@ -0,0 +1,43 @@
/**
* RobloxBrowser.h
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
// Qt Headers
#include <QWebView>
class RobloxBrowser : public QWebView
{
Q_OBJECT
public:
RobloxBrowser(QWidget* parent=0);
virtual ~RobloxBrowser();
public Q_SLOTS:
bool close();
void resetLoadingTimer();
protected:
virtual void paintEvent(QPaintEvent*);
virtual void dropEvent(QDropEvent* evt);
private Q_SLOTS:
void loadStarted();
void loadFinished(bool);
private:
void drawLoadingWatermark();
RobloxBrowser* m_pPopup;
QDialog* m_pPopupDlg;
QTimer* m_loadingTimer;
float m_refreshIncr;
};
+681
View File
@@ -0,0 +1,681 @@
/**
* RobloxChatWidget.cpp
* Copyright (c) 2015 ROBLOX Corp. All Rights Reserved.
*/
#include "stdafx.h"
#include "RobloxChatWidget.h"
#include <QFont>
#include <QPainter>
#include <QScrollBar>
#include "V8DataModel/DataModel.h"
#include "Network/Players.h"
#include "Network/Player.h"
#include "UpdateUIManager.h"
#include "RobloxMainWindow.h"
#include "PlayersDataManager.h"
static const int kPlayerIconSize = 32;
static const int kMessageBoxHMargin = 8;
static const int kMessageBoxVMargin = 6;
static const int kMessageBoxVOuterMargin = 3;
static const int kIconMessageBoxDistance = 4;
static const int kPointingTriSize = 5;
static const QString kLockImagePath(":/16x16/images/Studio 2.0 icons/16x16/lock.png");
static const QSize kMinChatWidgetSize(200, 150);
//--------------------------------------------------------------------------------------------
// ChatMessageItem
//--------------------------------------------------------------------------------------------
ChatMessageItem::ChatMessageItem(QObject* parent)
: QObject(parent)
{}
QSize ChatMessageItem::sizeHint(const QStyleOptionViewItem& option, const QSize& viewportSize) const
{
QFontMetrics fontMetrics(option.font);
QString headerText(data(Qt::DisplayRole).toString());
QString sublabelText(data(ChatMessageDelegate::SubLabelRole).toString());
bool pointingRight = data(Qt::TextAlignmentRole).toInt() == Qt::AlignRight;
int avatarRelatedWidth = (pointingRight || sublabelText.isEmpty()) ? 0 : (kPlayerIconSize + kIconMessageBoxDistance + kMessageBoxHMargin);
int availableWidth = viewportSize.width() - avatarRelatedWidth - kMessageBoxHMargin - 2*kMessageBoxHMargin - kPointingTriSize;
QRectF bRect = fontMetrics.boundingRect(kMessageBoxHMargin, 0, availableWidth, 0, Qt::AlignLeft | Qt::TextWordWrap, headerText);
return QSize(viewportSize.width(), bRect.height() + (sublabelText.isEmpty() ? 0 : fontMetrics.height()) + 2*kMessageBoxVOuterMargin + 2*kMessageBoxVMargin);
}
void ChatMessageItem::paint(QPainter* painter, const QStyleOptionViewItem& option)
{
// adjust rectangle for text drawing
QRect adjustedRect = option.rect.adjusted(kMessageBoxHMargin, kMessageBoxVOuterMargin, -kMessageBoxHMargin, -kMessageBoxVOuterMargin);
// draw player thumbnail (assuming we show icon only for the left aligned items)
QImage icon(qvariant_cast<QImage>(data(Qt::DecorationRole)));
if (!icon.isNull())
{
painter->save();
QPainterPath path;
path.addEllipse(adjustedRect.topLeft().x(), adjustedRect.topLeft().y(), kPlayerIconSize, kPlayerIconSize);
painter->setRenderHints(QPainter::Antialiasing);
painter->setClipPath(path);
painter->drawImage(QPoint(adjustedRect.topLeft().x(), adjustedRect.topLeft().y()), icon);
adjustedRect.setLeft(adjustedRect.left() + kPlayerIconSize + kIconMessageBoxDistance);
painter->restore();
}
painter->save();
// construct text rectangle
QString messageText(data(Qt::DisplayRole).toString());
QString sublabelText(data(ChatMessageDelegate::SubLabelRole).toString());
QString subLabelImagePath(data(ChatMessageDelegate::SubLabelDecorationPath).toString());
QFont subLabelFont = option.font;
subLabelFont.setPointSize(subLabelFont.pointSize() - 3);
subLabelFont.setBold(true);
int subLabelImageHeight = QFontMetrics(subLabelFont).height();
int messageTextWidth = QFontMetrics(option.font).width(messageText) + 2;
int subLabelTextWidth = (!sublabelText.isEmpty() ? QFontMetrics(subLabelFont).width(sublabelText) : 0) +
(!subLabelImagePath.isEmpty() ? (subLabelImageHeight + kMessageBoxHMargin) : 0);
int textRectWidth = qMax(messageTextWidth, subLabelTextWidth);
int adjustedWidth = textRectWidth + (2*kMessageBoxHMargin + kPointingTriSize);
bool pointingRight = data(Qt::TextAlignmentRole).toInt() == Qt::AlignRight;
if (pointingRight)
{
if (adjustedRect.width() > adjustedWidth)
adjustedRect.setLeft(adjustedRect.right() - textRectWidth - 2*kMessageBoxHMargin);
}
else
{
adjustedRect.setWidth(adjustedWidth > adjustedRect.width() ? adjustedRect.width() :
textRectWidth + 2*kMessageBoxHMargin + kPointingTriSize);
}
// draw shadow rect
QRect shadowRect(adjustedRect);
pointingRight ? shadowRect.setRight(shadowRect.right() - 6) : shadowRect.setLeft(shadowRect.left() + 5);
QColor gray(Qt::gray);
painter->fillRect(pointingRight ? shadowRect.translated(-1, 1) : shadowRect.translated(1, 1), QBrush(gray.lighter(120)));
// draw message polygon
QPainterPath path;
path.addPolygon(getMessageBoxPolygon(adjustedRect, pointingRight));
painter->fillPath(path, pointingRight ? QBrush(QColor("#99daff")) : QBrush(QColor("#e3e3e3")));
// draw message text
if (!sublabelText.isEmpty())
{
adjustedRect.adjust(kMessageBoxHMargin, kMessageBoxVMargin, -kMessageBoxHMargin, -kMessageBoxVMargin);
QRect subLabelRect(adjustedRect);
if (!subLabelImagePath.isEmpty())
{
// if image is not set already, load image
QImage image = qvariant_cast<QImage>(data(ChatMessageDelegate::SubLabelDecorationRole));
if (image.isNull())
{
image = QImage(subLabelImagePath).scaled(QSize(subLabelImageHeight, subLabelImageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
setData(ChatMessageDelegate::SubLabelDecorationRole, image);
}
if (pointingRight)
{
painter->drawImage(QPoint(subLabelRect.topRight().x() - subLabelTextWidth - 6, subLabelRect.topLeft().y()), image);
subLabelRect.setLeft(subLabelRect.topRight().x() - subLabelTextWidth + subLabelImageHeight);
}
else
{
painter->drawImage(QPoint(subLabelRect.topLeft().x(), subLabelRect.topLeft().y()), image);
subLabelRect.setLeft(subLabelRect.topLeft().x() + subLabelImageHeight + 6);
}
}
painter->setPen(QPen(QColor(Qt::black)));
painter->setFont(subLabelFont);
painter->drawText(subLabelRect, Qt::AlignLeft | Qt::TextWordWrap, sublabelText);
adjustedRect.adjust(0, 3*kMessageBoxVMargin, 0, 0);
// right align message text if sublabel message is longer than the message
if (pointingRight && (subLabelTextWidth > messageTextWidth))
adjustedRect.setLeft(subLabelRect.topRight().x() - messageTextWidth - kMessageBoxHMargin);
}
else
{
adjustedRect.adjust(kMessageBoxHMargin, kMessageBoxVMargin, -kMessageBoxHMargin, -kMessageBoxVMargin);
}
painter->setFont(option.font);
painter->setPen(QColor(Qt::black));
painter->drawText(adjustedRect, Qt::AlignLeft | Qt::TextWordWrap, messageText);
painter->restore();
}
void ChatMessageItem::updatePlayerAvatar(const QImage& playerImage)
{ setData(Qt::DecorationRole, playerImage); }
QPolygon ChatMessageItem::getMessageBoxPolygon(QRect& boundingRect, bool pointingRight)
{
QPolygon polygon;
const int pointingTriOffet = boundingRect.top() + 2*kMessageBoxVMargin;
if (pointingRight)
{
polygon << QPoint(boundingRect.right(), pointingTriOffet) << QPoint(boundingRect.right() - kPointingTriSize, pointingTriOffet + kPointingTriSize)
<< QPoint(boundingRect.right() - kPointingTriSize, boundingRect.bottom()) << boundingRect.bottomLeft() << boundingRect.topLeft()
<< QPoint(boundingRect.right() - kPointingTriSize, boundingRect.top()) << QPoint(boundingRect.right() - kPointingTriSize, pointingTriOffet - kPointingTriSize);
boundingRect.adjust(-kPointingTriSize, 0, 0, 0);
}
else
{
polygon << QPoint(boundingRect.left(), pointingTriOffet) << QPoint(boundingRect.left() + kPointingTriSize, pointingTriOffet + kPointingTriSize)
<< QPoint(boundingRect.left() + kPointingTriSize, boundingRect.bottom()) << boundingRect.bottomRight() << boundingRect.topRight()
<< QPoint(boundingRect.left() + kPointingTriSize, boundingRect.top()) << QPoint(boundingRect.left() + kPointingTriSize, pointingTriOffet - kPointingTriSize);
boundingRect.adjust(kPointingTriSize, 0, 0, 0);
}
return polygon;
}
//--------------------------------------------------------------------------------------------
// ChatMessageStatusItem
//--------------------------------------------------------------------------------------------
class ChatMessageStatusItem: public QListWidgetItem
{
public:
ChatMessageStatusItem(const QString& messageStatus)
{
setText(messageStatus);
}
QSize sizeHint(const QStyleOptionViewItem& option, const QSize& viewportSize) const
{
QFont updatedFont(option.font);
updatedFont.setPointSize(option.font.pointSize() - 2);
QFontMetrics fontMetrics(updatedFont);
return QSize(viewportSize.width(), fontMetrics.height() + 2*kMessageBoxVMargin);
}
void paint(QPainter* painter, const QStyleOptionViewItem& option)
{
painter->save();
QFont updatedFont(option.font);
updatedFont.setPointSize(option.font.pointSize() - 2);
painter->setFont(updatedFont);
QRect adjustedRect = option.rect.adjusted(kMessageBoxHMargin, kMessageBoxVOuterMargin, -kMessageBoxHMargin, -kMessageBoxVOuterMargin);
painter->drawText(adjustedRect, Qt::AlignRight | Qt::TextWordWrap, data(Qt::DisplayRole).toString());
painter->restore();
}
};
//--------------------------------------------------------------------------------------------
// ChatBroadcastItem
//--------------------------------------------------------------------------------------------
class ChatBroadcastItem: public QListWidgetItem
{
public:
ChatBroadcastItem(const QString& message, bool hasError = false)
: m_bHasError(hasError)
{
setText(message);
}
QSize sizeHint(const QStyleOptionViewItem& option, const QSize& viewportSize) const
{
QFont updatedFont(option.font);
updatedFont.setPointSize(option.font.pointSize() - 1);
QFontMetrics fontMetrics(updatedFont);
int availableWidth = viewportSize.width() - 2*kMessageBoxHMargin - 2*kMessageBoxHMargin - kPointingTriSize;
QRectF bRect = fontMetrics.boundingRect(kMessageBoxHMargin, 0, availableWidth, 0, Qt::AlignLeft | Qt::TextWordWrap, data(Qt::DisplayRole).toString());
return QSize(viewportSize.width(), bRect.height() + 2*kMessageBoxVMargin + 2*kMessageBoxVOuterMargin);
}
void paint(QPainter* painter, const QStyleOptionViewItem& option)
{
painter->save();
QRect adjustedRect = option.rect.adjusted(kMessageBoxHMargin, kMessageBoxVOuterMargin, -kMessageBoxHMargin, -kMessageBoxVOuterMargin);
// paint shadow rect
QColor gray(Qt::gray);
painter->fillRect(adjustedRect.translated(2, 1), QBrush(gray.lighter(120)));
// paint enclosing rect (TODO: check the colors)
painter->fillRect(adjustedRect, QBrush(m_bHasError ? QColor(255, 5, 100, 127) : QColor(85, 255, 127, 127)));
QFont updatedFont(option.font);
updatedFont.setPointSize(option.font.pointSize() - 1);
painter->setFont(updatedFont);
painter->setPen(QColor(Qt::black));
adjustedRect = adjustedRect.adjusted(kMessageBoxHMargin, kMessageBoxVMargin, -kMessageBoxHMargin, -kMessageBoxVMargin);
painter->drawText(adjustedRect, Qt::AlignLeft|Qt::TextWordWrap, data(Qt::DisplayRole).toString());
painter->restore();
}
private:
bool m_bHasError;
};
//--------------------------------------------------------------------------------------------
// ChatMessageDelegate
//--------------------------------------------------------------------------------------------
ChatMessageDelegate::ChatMessageDelegate(QObject* parent)
: QItemDelegate(parent)
{
}
QSize ChatMessageDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
ChatMessageListWidget* pListWidget(static_cast<ChatMessageListWidget*>(parent()));
QListWidgetItem* pItem = pListWidget->itemFromIndex(index);
if (ChatMessageItem* pMessageItem = dynamic_cast<ChatMessageItem*>(pItem))
return pMessageItem->sizeHint(option, pListWidget->viewport()->size());
if (ChatMessageStatusItem* pStatusItem = dynamic_cast<ChatMessageStatusItem*>(pItem))
return pStatusItem->sizeHint(option, pListWidget->viewport()->size());
if (ChatBroadcastItem* pBroadcastItem = dynamic_cast<ChatBroadcastItem*>(pItem))
return pBroadcastItem->sizeHint(option, pListWidget->viewport()->size());
return QItemDelegate::sizeHint(option, index);
}
void ChatMessageDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
ChatMessageListWidget* pListWidget(static_cast<ChatMessageListWidget*>(parent()));
QListWidgetItem* pItem = pListWidget->itemFromIndex(index);
if (ChatMessageItem* pMessageItem = dynamic_cast<ChatMessageItem*>(pItem))
{
pMessageItem->paint(painter, option);
}
else if (ChatMessageStatusItem* pStatusItem = dynamic_cast<ChatMessageStatusItem*>(pItem))
{
pStatusItem->paint(painter, option);
}
if (ChatBroadcastItem* pBroadcastItem = dynamic_cast<ChatBroadcastItem*>(pItem))
{
return pBroadcastItem->paint(painter, option);
}
}
//--------------------------------------------------------------------------------------------
// ChatMessageListWidget
//--------------------------------------------------------------------------------------------
ChatMessageListWidget::ChatMessageListWidget(QWidget* parent, boost::shared_ptr<PlayersDataManager> m_pPlayersDataManager)
: QListWidget(parent)
, m_pPlayersDataManager(m_pPlayersDataManager)
{
setUniformItemSizes(false);
setResizeMode(QListView::Adjust);
setWordWrap(true);
setItemDelegate(new ChatMessageDelegate(this));
}
QListWidgetItem* ChatMessageListWidget::addMessageItem(StudioChat::ChatMessageData messageData)
{
ChatMessageItem* pItem = new ChatMessageItem(this);
pItem->setText(messageData.message);
if (messageData.isLocalPlayer)
{
if (messageData.chatType == StudioChat::Whisper)
{
pItem->setData(ChatMessageDelegate::SubLabelRole, QString("Me %1 %2").arg(QChar(0x2192)).arg(messageData.playerName));
pItem->setData(ChatMessageDelegate::SubLabelDecorationPath, kLockImagePath);
}
pItem->setData(Qt::TextAlignmentRole, Qt::AlignRight);
}
else
{
if (messageData.chatType == StudioChat::Whisper)
{
pItem->setData(ChatMessageDelegate::SubLabelRole, QString("%1 %2 Me").arg(messageData.playerName).arg(QChar(0x2192)));
pItem->setData(ChatMessageDelegate::SubLabelDecorationPath, kLockImagePath);
}
else
{
pItem->setData(ChatMessageDelegate::SubLabelRole, messageData.playerName);
}
// update of avatar is required only for remote player i.e. not for local player
m_pPlayersDataManager->getPlayerAvatar(messageData.playerId, pItem, "updatePlayerAvatar");
}
addItem(pItem);
// scroll if local player or if user at the end of the widget
if (messageData.isLocalPlayer || (verticalScrollBar() && verticalScrollBar()->value() == verticalScrollBar()->maximum()))
scrollToItem(pItem);
return pItem;
}
QListWidgetItem* ChatMessageListWidget::addMessageStatusItem(const QString& statusMessage)
{
QListWidgetItem* pStatusItem = new ChatMessageStatusItem(statusMessage);
insertAndScrollToItem(pStatusItem);
return pStatusItem;
}
QListWidgetItem* ChatMessageListWidget::addBroadcastItem(const QString& message, bool hasError)
{
QListWidgetItem* pItem = new ChatBroadcastItem(message, hasError);
insertAndScrollToItem(pItem);
return pItem;
}
void ChatMessageListWidget::insertAndScrollToItem(QListWidgetItem* pItem)
{
addItem(pItem);
scrollToItem(pItem);
}
//--------------------------------------------------------------------------------------------
// ChatMessageLineEdit
//--------------------------------------------------------------------------------------------
ChatMessageLineEdit::ChatMessageLineEdit(QWidget* parent, boost::shared_ptr<PlayersDataManager> playersDataManager)
: QLineEdit(parent)
, m_pPlayersDataManager(playersDataManager)
, m_chatType(StudioChat::All)
{
connect(this, SIGNAL(textEdited(const QString&)), this, SLOT(onTextEdited(const QString&)));
connect(this, SIGNAL(returnPressed()), this, SLOT(onReturnPressed()));
}
QString ChatMessageLineEdit::getText()
{ return text().mid(m_prefixString.length()); }
void ChatMessageLineEdit::onTextEdited(const QString& message)
{
if (message.isEmpty() || (!m_prefixString.isEmpty() && (message.length() < m_prefixString.length())))
{
m_chatType = StudioChat::All;
m_prefixString = "";
setText("");
return;
}
QString messageText = message.mid(m_prefixString.length());
QString firstWord = messageText.left(messageText.indexOf(' ') + 1);
if (firstWord.startsWith("/a ", Qt::CaseInsensitive) || firstWord.startsWith("/all ", Qt::CaseInsensitive))
{
m_chatType = StudioChat::All;
m_prefixString = "";
setText(messageText.mid(firstWord.length()));
}
else if (messageText.startsWith("/w ", Qt::CaseInsensitive) || messageText.startsWith("/whisper ", Qt::CaseInsensitive))
{
messageText = messageText.mid(firstWord.length());
int index = messageText.indexOf(' ');
if (index < 0)
return;
QString playerName;
boost::shared_ptr<RBX::Network::Player> player = m_pPlayersDataManager->getPlayer(messageText.mid(0, index));
if (player)
playerName = player->getName().c_str();
if (!playerName.isEmpty())
{
m_chatType = StudioChat::Whisper;
m_prefixString = QString("[%1] ").arg(playerName);
setText(m_prefixString);
}
}
}
void ChatMessageLineEdit::onReturnPressed()
{
Q_EMIT returnPressed(m_chatType, getText(), m_prefixString.mid(1, m_prefixString.length() - 3));
setText(m_prefixString);
}
//--------------------------------------------------------------------------------------------
// RobloxChatWidget
//--------------------------------------------------------------------------------------------
RobloxChatWidget::RobloxChatWidget(QWidget* parent, boost::shared_ptr<RBX::DataModel> dataModel, boost::shared_ptr<PlayersDataManager> playerDataManager)
: QFrame(parent)
, m_pDataModel(dataModel)
, m_pPlayersDataManager(playerDataManager)
{
static bool registerMetaData(true);
if (registerMetaData)
{
qRegisterMetaType<StudioChat::ChatMessageData>("StudioChat::ChatMessageData");
registerMetaData = false;
}
m_pChatListWidget = new ChatMessageListWidget(this, m_pPlayersDataManager);
m_pMessageBox = new ChatMessageLineEdit(this, m_pPlayersDataManager);
m_pMessageBox->setPlaceholderText(tr("Send a message"));
QGridLayout* pLayout = new QGridLayout;
pLayout->setMargin(0);
pLayout->setSpacing(0);
pLayout->addWidget(m_pChatListWidget, 0, 0, 1, 1);
pLayout->addWidget(m_pMessageBox, 1, 0, 1, 1);
setLayout(pLayout);
setMinimumSize(kMinChatWidgetSize);
connect(m_pMessageBox, SIGNAL(returnPressed(int, const QString&, const QString&)), this, SLOT(onReturnPressed(int, const QString&, const QString&)));
// datamodel lock must be set in the function caller
RBX::Network::Players* players = m_pDataModel->find<RBX::Network::Players>();
RBXASSERT(players);
m_cPlayerChatMessage = players->chatMessageSignal.connect(boost::bind(&RobloxChatWidget::onPlayerChatMessage, this, _1));
// save pixmap for chat action
QDockWidget* chatDockWidget = UpdateUIManager::Instance().getMainWindow().findChild<QDockWidget*>("chatDockWidget");
RBXASSERT(chatDockWidget && chatDockWidget->toggleViewAction());
QIcon icon = chatDockWidget->toggleViewAction()->icon();
m_OriginalChatActionPixmap = icon.pixmap(icon.availableSizes()[0]);
}
void RobloxChatWidget::onPlayerChatMessage(const RBX::Network::ChatMessage& chatMessage)
{
if (chatMessage.message.empty())
return;
StudioChat::ChatType chatType;
switch (chatMessage.chatType)
{
case RBX::Network::ChatMessage::CHAT_TYPE_ALL:
chatType = StudioChat::All;
break;
case RBX::Network::ChatMessage::CHAT_TYPE_WHISPER:
chatType = StudioChat::Whisper;
break;
case RBX::Network::ChatMessage::CHAT_TYPE_GAME:
chatType = StudioChat::Game;
break;
default:
return;
}
StudioChat::ChatMessageData messageData(chatType, QString::fromStdString(chatMessage.message));
if (chatMessage.source)
{
messageData.isLocalPlayer = m_pPlayersDataManager->isLocalPlayer(chatMessage.source);
messageData.playerName = QString::fromStdString(chatMessage.source->getName());
messageData.playerId = chatMessage.source->getUserID();
}
// message must be added in main thread
QMetaObject::invokeMethod(this, "addMessage", Qt::QueuedConnection, Q_ARG(StudioChat::ChatMessageData, messageData));
}
void RobloxChatWidget::addMessage(StudioChat::ChatMessageData messageData)
{
// must be called from Main Thread
if (messageData.isLocalPlayer)
{
updateSentMessageStatus(messageData.message);
}
else
{
if (messageData.playerId > 0 && (messageData.chatType == StudioChat::All || messageData.chatType == StudioChat::Whisper))
{
m_pChatListWidget->addMessageItem(messageData);
// if widget is not visible then update action pixmap to convey new message received
if (!isVisible())
updateToggleChatActionPixmap();
}
else if (messageData.chatType == StudioChat::Game)
{
m_pChatListWidget->addBroadcastItem(messageData.message);
}
}
}
void RobloxChatWidget::onReturnPressed(int chatType, const QString& message, const QString& targetPlayerName)
{
// do not send empty message
if (message.isEmpty() || (chatType == (int)StudioChat::Whisper && targetPlayerName.isEmpty()))
return;
// check if help is required
if (message == "/?")
{
// TODO: check if we need to change color for the item?
QString helpMessage("Chat Commands:\n/w [PlayerName] or /whisper [PlayerName] - Whisper Chat\n/a or /all - All Chat");
m_pChatListWidget->addBroadcastItem(helpMessage);
return;
}
// if the player name is invalid then the chat type will be all
if ((chatType == (int)StudioChat::All) && (message.startsWith("/w ", Qt::CaseInsensitive) || message.startsWith("/whisper ", Qt::CaseInsensitive)))
{
QStringList words = message.split(' ');
QString playerName = words.count() > 1 ? words[1] : message.mid(words[0].length() + 1);
m_pChatListWidget->addBroadcastItem(QString("Cannot send message to Player '%1'").arg(playerName), true);
return;
}
// do not send invalid message
if (message.startsWith("/"))
{
m_pChatListWidget->addBroadcastItem("Please make sure message is in correct format. Type \'/?\' for help.", true);
return;
}
QString errorMessage;
StudioChat::ChatMessageData messageData((StudioChat::ChatType)chatType, message);
messageData.playerName = targetPlayerName;
messageData.isLocalPlayer = true;
// send message
sendMessage(messageData, errorMessage);
// if we do not have any errors, then directly add the message to listwidget or else add error
if (errorMessage.isEmpty())
{
QListWidgetItem* pMessageItem = m_pChatListWidget->addMessageItem(messageData);
QListWidgetItem* pStatusMessageItem = m_pChatListWidget->addMessageStatusItem("Sending...");
m_sentMessages.enqueue(qMakePair(pMessageItem, pStatusMessageItem));
}
else
{
m_pChatListWidget->addBroadcastItem(errorMessage, true);
}
}
void RobloxChatWidget::sendMessage(const StudioChat::ChatMessageData& messageData, QString& errorMessage)
{
RBX::DataModel::LegacyLock lock(m_pDataModel.get(), RBX::DataModelJob::Write);
RBX::Network::Players* players = m_pDataModel->find<RBX::Network::Players>();
if (players)
{
try
{
if (messageData.chatType == StudioChat::All)
{
players->chat(messageData.message.toStdString());
}
else if (messageData.chatType == StudioChat::Whisper)
{
if (boost::shared_ptr<RBX::Network::Player> player = m_pPlayersDataManager->getPlayer(messageData.playerName))
{
if (m_pPlayersDataManager->isLocalPlayer(player))
{
errorMessage = "You cannot send a whisper to yourself.";
}
else
{
players->whisperChat(messageData.message.toStdString(), player);
}
}
else
{
errorMessage = QString("Cannot send message to Player '%1'").arg(messageData.playerName);
}
}
}
catch (const RBX::base_exception& e)
{
errorMessage = e.what();
}
}
}
void RobloxChatWidget::updateSentMessageStatus(const QString& message)
{
if (m_sentMessages.isEmpty())
return;
QPair<QListWidgetItem*, QListWidgetItem*> sentMessagePair = m_sentMessages.dequeue();
RBXASSERT(sentMessagePair.first);
RBXASSERT(sentMessagePair.second);
if (message != sentMessagePair.first->text())
sentMessagePair.first->setText(message);
delete sentMessagePair.second;
}
void RobloxChatWidget::showEvent(QShowEvent* evt)
{
QWidget::showEvent(evt);
// reset the original pixmap
setToggleChatActionPixmap(m_OriginalChatActionPixmap);
}
void RobloxChatWidget::setToggleChatActionPixmap(const QPixmap& pixmap)
{
QDockWidget* chatDockWidget = UpdateUIManager::Instance().getMainWindow().findChild<QDockWidget*>("chatDockWidget");
RBXASSERT(chatDockWidget && chatDockWidget->toggleViewAction());
chatDockWidget->toggleViewAction()->setIcon(QIcon(pixmap));
}
void RobloxChatWidget::updateToggleChatActionPixmap()
{
QPixmap updatedPixmap(m_OriginalChatActionPixmap.rect().width(), m_OriginalChatActionPixmap.rect().height());
updatedPixmap.fill(Qt::transparent);
QPainter painter;
painter.begin(&updatedPixmap);
painter.drawPixmap(0, 0, m_OriginalChatActionPixmap);
painter.setRenderHints(QPainter::Antialiasing);
QPainterPath circlePath;
circlePath.addEllipse(QPoint(26, 6), 6, 6);
painter.fillPath(circlePath, QColor("#e31132"));
setToggleChatActionPixmap(updatedPixmap);
}
+162
View File
@@ -0,0 +1,162 @@
/**
* RobloxChatWidget.h
* Copyright (c) 2015 ROBLOX Corp. All Rights Reserved.
*/
#pragma once
//this will make sure that we can use qt with boost (else moc compiler errors)
#ifndef QT_NO_KEYWORDS
#define QT_NO_KEYWORDS
#endif
#include <QListWidget>
#include <QItemDelegate>
#include <QLineEdit>
#include <QPair>
#include <QQueue>
#include "rbx/signal.h"
#include "Util/HttpAsync.h"
namespace RBX {
class DataModel;
namespace Network {
class ChatMessage;
}
}
class RobloxChatWidget;
class PlayersDataManager;
namespace StudioChat {
enum ChatType
{
All,
Whisper,
Game
};
struct ChatMessageData
{
ChatType chatType;
QString message;
QString playerName;
int playerId;
bool isLocalPlayer;
ChatMessageData()
{;}
ChatMessageData(ChatType chatType_, QString message_)
: chatType(chatType_), message(message_), playerId(-1), isLocalPlayer(false)
{;}
};
}
class ChatMessageItem: public QObject, public QListWidgetItem
{
Q_OBJECT
public:
ChatMessageItem(QObject* parent);
QSize sizeHint(const QStyleOptionViewItem& option, const QSize& viewportSize) const;
void paint(QPainter* painter, const QStyleOptionViewItem& option);
private Q_SLOTS:
void updatePlayerAvatar(const QImage& image);
private:
static QPolygon getMessageBoxPolygon(QRect& boundingRect, bool pointingRight);
};
class ChatMessageDelegate: public QItemDelegate
{
public:
ChatMessageDelegate(QObject* parent);
enum Role
{
SubLabelRole = Qt::UserRole,
SubLabelDecorationPath,
SubLabelDecorationRole
};
private:
/*override*/ QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const;
/*override*/ void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
};
class ChatMessageListWidget: public QListWidget
{
public:
ChatMessageListWidget(QWidget* parent, boost::shared_ptr<PlayersDataManager> playerDataManager);
QListWidgetItem* addMessageItem(StudioChat::ChatMessageData messageData);
QListWidgetItem* addMessageStatusItem(const QString& statusMessage);
QListWidgetItem* addBroadcastItem(const QString& message, bool hasError = false);
using QListWidget::itemFromIndex;
private:
void insertAndScrollToItem(QListWidgetItem* pItem);
boost::shared_ptr<PlayersDataManager> m_pPlayersDataManager;
};
class ChatMessageLineEdit: public QLineEdit
{
Q_OBJECT
public:
ChatMessageLineEdit(QWidget* parent, boost::shared_ptr<PlayersDataManager> playersDataManager);
QString getText();
StudioChat::ChatType getChatType() { return m_chatType; }
Q_SIGNALS:
void returnPressed(int chatType, const QString& message, const QString& targetPlayerName);
private Q_SLOTS:
void onTextEdited(const QString& message);
void onReturnPressed();
private:
boost::shared_ptr<PlayersDataManager> m_pPlayersDataManager;
StudioChat::ChatType m_chatType;
QString m_prefixString;
};
class RobloxChatWidget: public QFrame
{
Q_OBJECT
public:
RobloxChatWidget(QWidget* parent, boost::shared_ptr<RBX::DataModel> dataModel, boost::shared_ptr<PlayersDataManager> playerDataManager);
private Q_SLOTS:
void onReturnPressed(int chatMode, const QString& message, const QString& targetPlayerName);
private:
Q_INVOKABLE void addMessage(StudioChat::ChatMessageData messageData);
void onPlayerChatMessage(const RBX::Network::ChatMessage& chatMessage);
void sendMessage(const StudioChat::ChatMessageData& messageData, QString& errorMessage);
void updateSentMessageStatus(const QString& message);
void showEvent(QShowEvent* evt);
void setToggleChatActionPixmap(const QPixmap& pixmap);
void updateToggleChatActionPixmap();
QQueue<QPair<QListWidgetItem*, QListWidgetItem*> > m_sentMessages;
ChatMessageListWidget* m_pChatListWidget;
ChatMessageLineEdit* m_pMessageBox;
boost::shared_ptr<RBX::DataModel> m_pDataModel;
boost::shared_ptr<PlayersDataManager> m_pPlayersDataManager;
rbx::signals::scoped_connection m_cPlayerChatMessage;
QPixmap m_OriginalChatActionPixmap;
};

Some files were not shown because too many files have changed in this diff Show More