/** * QtUtilities.cpp * Copyright (c) 2013 ROBLOX Corp. All Rights Reserved. */ #include "stdafx.h" #include "QtUtilities.h" // Qt Headers #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // 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 > sPixmaps; QList 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 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 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 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; } }