mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
613 lines
17 KiB
C++
613 lines
17 KiB
C++
/**
|
|
* 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;
|
|
}
|
|
}
|
|
|