/** * RobloxCustomWidgets.cpp * Copyright (c) 2013 ROBLOX Corp. All Rights Reserved. */ #include "stdafx.h" #include "RobloxCustomWidgets.h" // Qt Headers #include #include #include #include #include #include #include #include #include #include #include #include #include // Roblox Headers #include "util/BrickColor.h" #include "V8DataModel/ToolsPart.h" #include "V8DataModel/Commands.h" #include "V8DataModel/DataModel.h" #include "V8DataModel/ContentProvider.h" #include "appdraw/Draw.h" // Roblox Studio Headers #include "PropertyItems.h" #include "RobloxGameExplorer.h" #include "RobloxPropertyWidget.h" #include "UpdateUIManager.h" #include "RobloxDocManager.h" #include "RobloxIDEDoc.h" #include "RobloxTreeWidget.h" #include "RobloxMainWindow.h" #define COLOR_COLS 8 #define MATERIAL_COLS 4 #define MARGIN_Y 4 #define MARGIN_X 4 #define COLOR_SPACE 6 #define MATERIAL_SPACE 6 #define COLOR_SIZE 30 #define MAT_IMAGE_SIZE 26 #define COLOR_POLY_SIDE 14 #define COLOR_POLY_DIFF 2 const QRectF ColorPickerFrame::m_selectionRectangleAdjustment = QRectF(-2, -2, 3, 3); const QRectF ColorPickerFrame::m_secondaryRectangleAdjustment = QRectF(-1, -1, 0, 0); ColorPickerFrame::ColorPickerFrame(QWidget *parent) : PickerFrame(parent) , m_selectedColorIndex(12) , m_hoveredColorIndex(12) , m_closeOnMouseRelease(false) { int maxPolygonsInARow = getMaxPolygonsInARow(); int numRows(maxPolygonsInARow), numCols(2*maxPolygonsInARow - 1), numColorsUsed(0); QPolygonF polygon; m_colorsInfo.reserve(RBX::BrickColor::colorPalette().size()); for (int row = 0; row < numRows; row++) for (int col = 0; col < numCols; col++) { if((qAbs(numRows/2 - row) + qAbs(numCols/2 - col) < maxPolygonsInARow) && (row % 2 == col % 2)) { polygon = getPolygon(PolygonSize_Normal); polygon.translate(qSqrt(3)/2 * (COLOR_POLY_SIDE + COLOR_POLY_DIFF) * col + 18, (COLOR_POLY_SIDE + COLOR_POLY_DIFF) * 1.5 * row + 24); RBX::BrickColor bColor = RBX::BrickColor::colorPalette().operator[](numColorsUsed++); m_colorsInfo.push_back(ColorKeyInfo(QColor(bColor.color3uint8().r, bColor.color3uint8().g, bColor.color3uint8().b), polygon, bColor.name().c_str(), bColor.asInt())); } } static RBX::BrickColor::Number lowerLineColorIDs[12] = {RBX::BrickColor::brick_199, RBX::BrickColor::brick_194, RBX::BrickColor::roblox_1002, RBX::BrickColor::brick_325, RBX::BrickColor::brick_348, RBX::BrickColor::brick_26, RBX::BrickColor::brick_302, RBX::BrickColor::brick_311, RBX::BrickColor::brick_320, RBX::BrickColor::brick_335, RBX::BrickColor::roblox_1001, RBX::BrickColor::brick_1}; int colorCount = RBX::BrickColor::colorPalette().size(), lowerLineColorCount = 0; for (int ii = 0; ii < numCols - 1; ++ii) { if (!(ii % 2)) continue; polygon = getPolygon(PolygonSize_Normal); polygon.translate(qSqrt(3)/2 * (COLOR_POLY_SIDE + COLOR_POLY_DIFF) * ii + 18, (COLOR_POLY_SIDE + COLOR_POLY_DIFF) * 1.5 * numRows + 24 + 4); RBX::BrickColor bColor; if (numColorsUsed < colorCount) bColor = RBX::BrickColor::colorPalette().operator[](numColorsUsed++); else bColor = RBX::BrickColor(lowerLineColorIDs[lowerLineColorCount++]); m_colorsInfo.push_back(ColorKeyInfo(QColor(bColor.color3uint8().r, bColor.color3uint8().g, bColor.color3uint8().b), polygon, bColor.name().c_str(), bColor.asInt())); } // check if we want to compare color names instead of using index m_selectedColorIndex = 123; m_hoveredColorIndex = 123; int width = maxPolygonsInARow * 2 * (qSqrt(3)/2 * (COLOR_POLY_SIDE + COLOR_POLY_DIFF)) + 8; int height = (maxPolygonsInARow + 1) * (COLOR_POLY_SIDE + COLOR_POLY_DIFF) * 1.5 + MARGIN_Y + 20; resize(width, height); } void ColorPickerFrame::setSelectedItemQString(QString selectedItem) { m_selectedColorIndex = -1; for(int index = 0; index < (int)m_colorsInfo.size(); index++) { if (m_colorsInfo[index].colorName == selectedItem) { m_selectedColorIndex = index; m_hoveredColorIndex = index; break; } } } QString ColorPickerFrame::getSelectedItemQString() { return m_selectedColorIndex < 0 ? QString("") : m_colorsInfo[m_selectedColorIndex].colorName; } void ColorPickerFrame::setSelectedItem(int selectedItem) { m_selectedColorIndex = -1; for(size_t index = 0; index < m_colorsInfo.size(); index++) { if (m_colorsInfo[index].colorValue == selectedItem) { m_selectedColorIndex = index; m_hoveredColorIndex = index; break; } } } int ColorPickerFrame::getSelectedItem() { return m_selectedColorIndex < 0 ? -1 : m_colorsInfo[m_selectedColorIndex].colorValue; } void ColorPickerFrame::setSelectedIndex(int index) { m_selectedColorIndex = -1; if (index >=0 && index < (int)m_colorsInfo.size()) m_selectedColorIndex = index; } int ColorPickerFrame::getSelectedIndex() { if (m_selectedColorIndex >=0 && m_selectedColorIndex < (int)m_colorsInfo.size()) return m_selectedColorIndex; return -1; } void ColorPickerFrame::paintEvent(QPaintEvent *) { QPainter painter( this ); painter.save(); painter.setPen(QPen(Qt::black)); painter.drawRect(1, 1, width() - 1, height() - 1); painter.fillRect( QRect(1, 1, width() - 1, height() - 1), QApplication::palette().window()); paintPolygonGrid(painter); painter.restore(); } void ColorPickerFrame::mousePressEvent(QMouseEvent* evt) { int colorIndex = getColorIndex(evt->pos()); if (colorIndex >= 0) { m_selectedColorIndex = colorIndex; Q_EMIT currentBrickColorChanged(m_colorsInfo[colorIndex].colorValue); } // Ideally, we would like to close the picker here. We can't, because then // MouseRelease event goes to the window below it and messes up the logic there. // Instead, we raise a flag that the picker should close on MouseRelease and // then close it there. m_closeOnMouseRelease = true; } void ColorPickerFrame::mouseReleaseEvent(QMouseEvent *event) { if (m_closeOnMouseRelease) { this->close(); m_closeOnMouseRelease = false; } } bool ColorPickerFrame::event(QEvent *evt) { if (evt->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(evt); int colorIndex = getColorIndex(helpEvent->pos()); if (colorIndex >= 0) { RBX::BrickColor color(m_colorsInfo[colorIndex].colorValue); QToolTip::showText(helpEvent->globalPos(), color.name().c_str()); } else { QToolTip::hideText(); } return true; } return QFrame::event(evt); } int ColorPickerFrame::getColorIndex(const QPoint& pos) { for(int index = 0; index < (int)m_colorsInfo.size(); index++) { if(m_colorsInfo[index].polygon.containsPoint(pos, Qt::OddEvenFill)) return index; } return -1; } void ColorPickerFrame::paintPolygonGrid(QPainter& painter) { painter.setRenderHint(QPainter::Antialiasing, true); painter.setRenderHint(QPainter::HighQualityAntialiasing, true); bool itemHovered = false; for(size_t colorIndex = 0; colorIndex < m_colorsInfo.size(); colorIndex++) { painter.setPen(QPen(Qt::black, 2)); painter.drawPolyline(m_colorsInfo[colorIndex].polygon); QPainterPath path; path.addPolygon(m_colorsInfo[colorIndex].polygon); QColor color(m_colorsInfo.at(colorIndex).color); painter.fillPath(path, QBrush(color)); if (colorIndex == m_selectedColorIndex) { painter.setPen(QPen(Qt::red, 2)); painter.drawPolyline(m_colorsInfo[colorIndex].polygon); QPolygonF smaller(getPolygon(PolygonSize_Small)); smaller.translate((m_colorsInfo[colorIndex].polygon[4] + m_colorsInfo[colorIndex].polygon[1])/2); painter.setPen(QPen(Qt::yellow, 2)); painter.drawPolyline(smaller); } if (m_colorsInfo[colorIndex].polygon.containsPoint(mapFromGlobal(QCursor::pos()), Qt::OddEvenFill)) { itemHovered = true; painter.setPen(QPen(Qt::blue, 2)); painter.drawPolyline(m_colorsInfo[colorIndex].polygon); QPolygonF smaller(getPolygon(PolygonSize_Small)); smaller.translate((m_colorsInfo[colorIndex].polygon[4] + m_colorsInfo[colorIndex].polygon[1])/2); painter.setPen(QPen(Qt::white, 2)); painter.drawPolyline(smaller); if (colorIndex != m_hoveredColorIndex) { m_hoveredColorIndex = colorIndex; Q_EMIT currentBrickColorChanged(m_colorsInfo[m_hoveredColorIndex].colorValue); } } } if (!itemHovered) { m_hoveredColorIndex = -1; Q_EMIT currentBrickColorChanged(-1); } } int ColorPickerFrame::getMaxPolygonsInARow() { int colorCount = RBX::BrickColor::colorPalette().size(); int temp = 3, result = 0; while (true) { if ((temp*temp - temp/2*(temp/2+1)) > colorCount) break; result = temp; temp += 2; } return result; } static QPolygonF getPolygonFromSide(int side) { QPolygonF polygon; qreal dx = qSqrt(3)/2 * side; polygon << QPointF(dx, -side/2) << QPointF(0, -side) << QPointF(-dx, -side/2) << QPointF(-dx, side/2) << QPointF(0, side) << QPointF(dx, side/2) << QPointF(dx, -side/2); return polygon; } QPolygonF ColorPickerFrame::getPolygon(PolygonSize polySize) { if (polySize == PolygonSize_Small) { static QPolygonF smallPolygon(getPolygonFromSide(COLOR_POLY_SIDE - COLOR_POLY_DIFF)); return smallPolygon; } RBXASSERT(polySize == PolygonSize_Normal); static QPolygonF normalPolygon(getPolygonFromSide(COLOR_POLY_SIDE)); return normalPolygon; } MaterialPickerFrame::MaterialPickerFrame( QWidget *parent ) : PickerFrame(parent) , m_selectedMaterial(0) { m_materialValues.append(RBX::PLASTIC_MATERIAL); m_materialValues.append(RBX::WOOD_MATERIAL); m_materialValues.append(RBX::WOODPLANKS_MATERIAL); m_materialValues.append(RBX::SLATE_MATERIAL); m_materialValues.append(RBX::CONCRETE_MATERIAL); m_materialValues.append(RBX::METAL_MATERIAL); m_materialValues.append(RBX::RUST_MATERIAL); m_materialValues.append(RBX::DIAMONDPLATE_MATERIAL); m_materialValues.append(RBX::ALUMINUM_MATERIAL); m_materialValues.append(RBX::GRASS_MATERIAL); m_materialValues.append(RBX::ICE_MATERIAL); m_materialValues.append(RBX::BRICK_MATERIAL); m_materialValues.append(RBX::SAND_MATERIAL); m_materialValues.append(RBX::FABRIC_MATERIAL); m_materialValues.append(RBX::GRANITE_MATERIAL); m_materialValues.append(RBX::MARBLE_MATERIAL); m_materialValues.append(RBX::PEBBLE_MATERIAL); m_materialValues.append(RBX::COBBLESTONE_MATERIAL); m_materialValues.append(RBX::SMOOTH_PLASTIC_MATERIAL); //create individual images QImage materialsImage(":/images/MaterialPalette.PNG"); int materialWidth = materialsImage.width(); for (int count=0; count < qFloor(materialWidth / MAT_IMAGE_SIZE); count++) m_materialImages.append(materialsImage.copy(MAT_IMAGE_SIZE*count, 0, MAT_IMAGE_SIZE, MAT_IMAGE_SIZE)); //resize frame with the created image size taking margins into consideration int width = (qMin(MATERIAL_COLS, m_materialImages.size()) * (MATERIAL_SPACE + MAT_IMAGE_SIZE)) + (MARGIN_X * 4) - MATERIAL_SPACE; int height = (qCeil((float)m_materialImages.size() / (float)MATERIAL_COLS) * (MATERIAL_SPACE + MAT_IMAGE_SIZE)) + (MARGIN_Y * 4) - MATERIAL_SPACE; resize(width, height); } void MaterialPickerFrame::setSelectedItemQString(const QString& selectedItem) { m_selectedMaterial = -1; for(int index = 0; index < m_materialNames.count(); index++) { if(m_materialNames[index] == selectedItem) { m_selectedMaterial = index; break; } } } QString MaterialPickerFrame::getSelectedItemQString() { return m_selectedMaterial < 0 ? QString("") : m_materialNames[m_selectedMaterial]; } void MaterialPickerFrame::setSelectedItem(int selectedItem) { m_selectedMaterial = -1; for(int index = 0; index < m_materialValues.count(); index++) { if(m_materialValues[index] == selectedItem) { m_selectedMaterial = index; break; } } } int MaterialPickerFrame::getSelectedItem() { return m_selectedMaterial < 0 ? -1 : m_materialValues[m_selectedMaterial]; } void MaterialPickerFrame::paintEvent(QPaintEvent*) { QPainter painter(this); painter.save(); painter.fillRect(QRect(1, 1, width() - 1, height() - 1), QApplication::palette().window()); QPointF mousePos = this->mapFromGlobal(QCursor::pos()); for (int materialIndex = 0; materialIndex < m_materialImages.size(); materialIndex++) { int xOffset = (2 * MARGIN_X) + (materialIndex % MATERIAL_COLS) * (MAT_IMAGE_SIZE + MATERIAL_SPACE); int yOffset = (2 * MARGIN_Y) + (materialIndex / MATERIAL_COLS) * (MAT_IMAGE_SIZE + MATERIAL_SPACE); painter.setPen(QPen(Qt::black, 1)); painter.drawRect(xOffset, yOffset, 26, 26); painter.drawImage(xOffset, yOffset, m_materialImages.at(materialIndex)); QRectF currentMaterialRect(xOffset, yOffset, MAT_IMAGE_SIZE, MAT_IMAGE_SIZE); if (materialIndex == m_selectedMaterial) { painter.setPen(QPen(Qt::red, 1)); painter.drawRect(currentMaterialRect.adjusted(-MATERIAL_SPACE / 2, -MATERIAL_SPACE / 2, MATERIAL_SPACE / 2, MATERIAL_SPACE / 2)); } if(currentMaterialRect.contains(mousePos)) { painter.setPen(QPen(Qt::blue, 1)); painter.drawRect(currentMaterialRect.adjusted(-MATERIAL_SPACE / 2, -MATERIAL_SPACE / 2, MATERIAL_SPACE / 2, MATERIAL_SPACE / 2)); } } painter.restore(); } void MaterialPickerFrame::mousePressEvent(QMouseEvent *evt) { int materialIndex = getMaterialIndex(evt->pos()); if (materialIndex >= 0) { m_selectedMaterial = materialIndex; Q_EMIT currentMaterialChanged(m_materialValues[materialIndex]); } this->close(); } bool MaterialPickerFrame::event(QEvent *evt) { if (evt->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(evt); int materialIndex = getMaterialIndex(helpEvent->pos()); if (materialIndex >= 0) { std::string materialName = RBX::Reflection::EnumDesc::singleton().convertToString((RBX::PartMaterial)m_materialValues[materialIndex]); QToolTip::showText(helpEvent->globalPos(), materialName.c_str()); } else QToolTip::hideText(); return true; } return QFrame::event(evt); } int MaterialPickerFrame::getMaterialIndex(const QPoint& pos) { if(rect().contains(pos)) { int materialIndex = ((pos.x() - MARGIN_X)/(MAT_IMAGE_SIZE + MATERIAL_SPACE)) + MATERIAL_COLS * ((pos.y() - MARGIN_Y)/(MAT_IMAGE_SIZE + MATERIAL_SPACE)); if (materialIndex >= 0 && materialIndex < m_materialValues.size()) return materialIndex; } return -1; } CustomToolButton::CustomToolButton(QWidget *parent) : QToolButton(parent) , m_pPickerFrame(NULL) , m_bMenuButtonPressed(false) , m_toolButtonStyle(Qt::ToolButtonIconOnly) { setPopupMode(QToolButton::MenuButtonPopup); } CustomToolButton::~CustomToolButton() { } void CustomToolButton::paintEvent (QPaintEvent *evt) { if (!m_bMenuButtonPressed) { QToolButton::paintEvent(evt); return; } //initialize options to have push on menu behavior QStyleOptionToolButton styleOptions; initStyleOptions(&styleOptions); //draw tool button appropriately QStylePainter pPainter(this); pPainter.drawComplexControl(QStyle::CC_ToolButton, styleOptions); } void CustomToolButton::mousePressEvent ( QMouseEvent *e ) { if (e->button() == Qt::LeftButton) { QStyleOptionToolButton styleOption; initStyleOption(&styleOption); QRect popupRect = style()->subControlRect(QStyle::CC_ToolButton, &styleOption, QStyle::SC_ToolButtonMenu, this); if (popupRect.isValid() && popupRect.contains(e->pos())) { showPopupFrame(); return; } } QToolButton::mousePressEvent(e); } void CustomToolButton::initStyleOptions(QStyleOptionToolButton *pOption) { if (!pOption) return; pOption->initFrom(this); pOption->icon = icon(); pOption->iconSize = iconSize(); pOption->pos = pos(); pOption->font = font(); pOption->text = text(); pOption->features = QStyleOptionToolButton::HasMenu|QStyleOptionToolButton::MenuButtonPopup; pOption->subControls = QStyle::SC_ToolButtonMenu; pOption->activeSubControls = QStyle::SC_ToolButtonMenu; pOption->state |= QStyle::State_Sunken; pOption->toolButtonStyle = toolButtonStyle(); } void CustomToolButton::showPopupFrame() { PickerFrame *pPickerFrame = getPickerFrame(); if (!pPickerFrame) return; m_bMenuButtonPressed = true; QEventLoop eventLoop; pPickerFrame->setEventLoop(&eventLoop); //TODO: Calculate correct position pPickerFrame->move(getPopupLocation(pPickerFrame->size())); pPickerFrame->show(); repaint(); Q_EMIT frameShown(); //start event loop eventLoop.exec(); Q_EMIT frameHidden(); m_bMenuButtonPressed = false; repaint(); } QPoint CustomToolButton::getPopupLocation(const QSize &pickerFrameSize) { QPoint location; QRect screen = QApplication::desktop()->availableGeometry(this); QRect rect = this->rect(); if (mapToGlobal(QPoint(0, rect.bottom())).y() + pickerFrameSize.height() <= screen.height()) location = mapToGlobal(rect.bottomLeft()); else location = mapToGlobal(rect.topLeft() - QPoint(0, pickerFrameSize.height())); location.rx() = qMax(screen.left(), qMin(location.x(), screen.right() - pickerFrameSize.width())); location.ry() += 1; return location; } //delayed creation of picker frames PickerFrame* MaterialPickerToolButton::getPickerFrame() { if (!m_pPickerFrame) { m_pPickerFrame = new MaterialPickerFrame(this); connect(m_pPickerFrame, SIGNAL(changed(const QString &)), this, SIGNAL(changed(const QString &))); } return m_pPickerFrame; } PickerFrame* ColorPickerToolButton::getPickerFrame() { if (!m_pPickerFrame) { m_pPickerFrame = new ColorPickerFrame(this); connect(m_pPickerFrame, SIGNAL(changed(const QString &)), this, SIGNAL(changed(const QString &))); } //set the current color m_pPickerFrame->setSelectedItem(RBX::ColorVerb::getCurrentColor().asInt()); return m_pPickerFrame; } PickerFrame* FillColorPickerToolButton::getPickerFrame() { if (!m_pPickerFrame) { m_pPickerFrame = new ColorPickerFrame(this); connect(m_pPickerFrame, SIGNAL(changed(const QString &)), this, SLOT(onPickerFrameChanged(const QString &))); // just in case picker frame gets deleted in between! connect(m_pPickerFrame, SIGNAL(destroyed()), this, SLOT(onPickerFrameDestroyed())); } //set the current color if (m_selectedIndex >= 0) { m_pPickerFrame->setSelectedIndex(m_selectedIndex); } else { m_pPickerFrame->setSelectedItem(RBX::FillTool::color.get().asInt()); } return m_pPickerFrame; } void FillColorPickerToolButton::onPickerFrameChanged(const QString& selectedColor) { if (m_pPickerFrame) m_selectedIndex = m_pPickerFrame->getSelectedIndex(); Q_EMIT changed(selectedColor); } void FillColorPickerToolButton::onPickerFrameDestroyed() { m_pPickerFrame = NULL; } PopupLaunchEditor::PopupLaunchEditor(PropertyItem *pParentItem, QWidget *pParent, const QIcon &itemIcon, const QString &labelText, int buttonWidth, QWidget *proxyWidget) : QWidget(pParent) , m_pParentItem(pParentItem) { QHBoxLayout *pLayout = new QHBoxLayout(this); QPixmap pix = itemIcon.pixmap(QSize(16, 16)); QToolButton *pButton = new QToolButton(this); pButton->setText(tr("...")); pButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); pButton->setFixedWidth(buttonWidth); if (!pix.isNull()) pButton->setIcon(QIcon(pix)); pLayout->addWidget(pButton); pLayout->setMargin(0); pLayout->setSpacing(4); if (proxyWidget) { pLayout->addWidget(proxyWidget); } else { QLabel *pTextLabel = new QLabel(labelText, this); pTextLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored)); pLayout->addWidget(pTextLabel); } setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); setFocusProxy(proxyWidget ? proxyWidget : pButton); connect(pButton, SIGNAL(clicked()), this, SLOT(onButtonClicked())); connect(proxyWidget, SIGNAL(editingFinished()), this, SLOT(onEditFinished())); } void PopupLaunchEditor::onEditFinished() { if (m_pParentItem) { m_pParentItem->setModelDataSafe(this); m_pParentItem->getTreeWidget()->closeEditor(this, QAbstractItemDelegate::NoHint); } } void PopupLaunchEditor::onButtonClicked() { if (m_pParentItem) { m_pParentItem->buttonClicked(); m_pParentItem->getTreeWidget()->closeEditor(this, QAbstractItemDelegate::NoHint); } } SignalConnector::SignalConnector(PropertyItem *pPropertyItem) : m_pPropertyItem(pPropertyItem) { } bool SignalConnector::connectSignalArg1(QObject *pSender, const char * signal) { return connect(pSender, signal, this, SLOT(genericSlotArg1(const QString &))); } void SignalConnector::genericSlotArg1(const QString &arg) { m_pPropertyItem->onEvent(sender(), arg); } ImagePickerWidget::ImagePickerWidget(QWidget* parent) : PickerFrame(parent) , lineEdit(new QLineEdit(this)) { } QString ImagePickerWidget::getImageUrl(const QString& lastValue, const QPoint& location, boost::shared_ptr dm) { RBX::ContentProvider* cp = dm->find(); resultString = lastValue; std::vector imageNames; UpdateUIManager::Instance().getViewWidget(eDW_GAME_EXPLORER) .getListOfImages(&imageNames); QGridLayout* topLayout = new QGridLayout(this); QLabel* otherLabel = new QLabel(this); otherLabel->setText("URL:"); topLayout->addWidget(otherLabel, 0, 0); lineEdit->setText(lastValue); topLayout->addWidget(lineEdit, 0, 1); QObject::connect(lineEdit, SIGNAL(returnPressed()), this, SLOT (acceptLineEditValue())); QListWidget* scrollList = new QListWidget(this); for (size_t i = 0; i < imageNames.size(); ++i) { QListWidgetItem* item = new QListWidgetItem(); item->setSizeHint(QSize(-1, 64)); QFrame* widget = new QFrame(); widget->setLayout(new QHBoxLayout()); QString url = QString("rbxgameasset://%1").arg(imageNames[i]); QLabel* image = new QLabel(); if (shared_ptr data = cp->requestContentString(RBX::ContentId(url.toStdString()), RBX::ContentProvider::PRIORITY_MFC)) { QPixmap p; p.loadFromData((uchar*)data->c_str(), data->size()); p = p.scaled(QSize(64, 64), Qt::KeepAspectRatio); image->setPixmap(p); } else { image->setText("(Loading...)"); } widget->layout()->addWidget(image); // Trim leading "Images/" in name widget->layout()->addWidget(new QLabel(imageNames[i].right(imageNames[i].length() - 7))); item->setData(Qt::UserRole + 1, url); scrollList->addItem(item); scrollList->setItemWidget(item, widget); } // "Add" button QListWidgetItem* addItem = new QListWidgetItem("Add Image..."); addItem->setSizeHint(QSize(-1, 64)); scrollList->addItem(addItem); QObject::connect(scrollList, SIGNAL(itemClicked (QListWidgetItem*)), this, SLOT (handleListItemClicked(QListWidgetItem*))); topLayout->addWidget(scrollList, 1, 0, 1, 2); setLayout(topLayout); // We need to know the actual size of this widget in order to fit it into the monitor, // so do a fake show()/hide() it so that all of the geometry calculations happen and size() // gives a correct value. setAttribute(Qt::WA_DontShowOnScreen, true); show(); hide(); setAttribute(Qt::WA_DontShowOnScreen, false); move(moveLocationIntoMonitor(location)); show(); lineEdit->setFocus(); lineEdit->selectAll(); QEventLoop* eventLoop = new QEventLoop(this); setEventLoop(eventLoop); eventLoop->exec(); return resultString; } QPoint ImagePickerWidget::moveLocationIntoMonitor(const QPoint& location) { QRect monitorGeometry = QApplication::desktop()->screenGeometry(location); QSize frameSize = size(); QPoint minLocation = monitorGeometry.topLeft(); QPoint maxLocation = monitorGeometry.bottomRight() - QPoint(frameSize.width(), frameSize.height()); return QPoint(std::max(minLocation.x(), std::min(location.x(), maxLocation.x())), std::max(minLocation.y(), std::min(location.y(), maxLocation.y()))); } void ImagePickerWidget::acceptLineEditValue() { resultString = lineEdit->text(); close(); } void ImagePickerWidget::handleListItemClicked(QListWidgetItem* item) { QVariant url = item->data(Qt::UserRole + 1); bool hasUrl = url.isValid(); if (!hasUrl) { // "Add" button clicked bool created = false; QString newName; AddImageDialog addImageDialog; addImageDialog.runModal(item->listWidget(), &created, &newName); if (created) { hasUrl = true; url = QString("rbxgameasset://%1").arg(newName); } } if (hasUrl) { resultString = url.toString(); close(); } } ////////////////////////////////////////////////////////////////////////// NumberSequencePropertyWidget::NumberSequencePropertyWidget( QWidget* parent, Callbacks* c ): QWidget(parent), cb(c) { textEditor = new QLineEdit( "" ); editButton = new QPushButton( "..." ); editButton->setFixedWidth(30); QGridLayout* layout = new QGridLayout(); // [Text] | [...] layout->addWidget(textEditor, 0, 0); layout->addWidget(editButton, 0, 1); layout->setMargin(0); layout->setSpacing(0); layout->setContentsMargins(0,0,0,0); this->setContentsMargins(0,0,0,0); this->setLayout(layout); this->setFocusProxy(textEditor); connect( textEditor, SIGNAL(editingFinished()), this, SLOT(onText()) ); connect( editButton, SIGNAL(clicked(bool)), this, SLOT(onButton(bool)) ); } void NumberSequencePropertyWidget::onText() { cb->onTextAccepted(); } void NumberSequencePropertyWidget::onButton(bool) { cb->onCurveClicked(); } //-------------------------------------------------------------------------------------------- // PartSelectionTool //-------------------------------------------------------------------------------------------- const char* const sPartSelectionTool = "PartSelectionTool"; PartSelectionTool::PartSelectionTool(RBX::Workspace* workspace) : RBX::Named(workspace) , m_CursorName("ArrowFarCursor") , m_bIsValidPartSelected(false) { } void PartSelectionTool::onMouseHover(const shared_ptr& inputObject) { bool isValid = false; RBX::PartInstance* pPartHoveredOver = getPart(workspace, inputObject); partHoveredOverSignal(pPartHoveredOver, isValid); setHighlightPart( isValid ? shared_from(pPartHoveredOver) : boost::shared_ptr()); } shared_ptr PartSelectionTool::onMouseDown(const shared_ptr& inputObject) { // first emit signal partSelectedSignal(getPart(workspace, inputObject), m_bIsValidPartSelected); // check if we've a valid selection if (!m_bIsValidPartSelected) return shared_from(this); return shared_ptr(); } shared_ptr PartSelectionTool::onMouseUp(const shared_ptr& inputObject) { if (!m_bIsValidPartSelected) return shared_from(this); return shared_ptr(); } void PartSelectionTool::render3dAdorn(RBX::Adorn* adorn) { RBX::MouseCommand::render3dAdorn(adorn); if (m_pHighlightPart) { RBX::Draw::selectionBox(m_pHighlightPart->getPart(), adorn, RBX::ModelInstance::primaryPartHoverOverColor(), RBX::ModelInstance::primaryPartLineThickness()); } } //-------------------------------------------------------------------------------------------- // PrimaryPartSelectionEditor //-------------------------------------------------------------------------------------------- PrimaryPartSelectionEditor::PrimaryPartSelectionEditor(QWidget* parent, PropertyItem* pPropertyItem, const QString& labelText, boost::shared_ptr instance) : QWidget(parent) , m_pPrimaryPartPropertyItem(pPropertyItem) , m_pModelInstance(instance) { setStatusTip(tr("Select Primary Part")); QHBoxLayout *pLayout = new QHBoxLayout(this); QLabel *pTextLabel = new QLabel(labelText, this); pTextLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored)); pLayout->addWidget(pTextLabel); QToolButton *pRemoveButton = new QToolButton(this); pRemoveButton->setObjectName("removeButton"); pRemoveButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); pRemoveButton->setFixedWidth(20); pRemoveButton->setIcon(QIcon(":/16x16/images/Studio 2.0 icons/16x16/delete_x_16_h.png")); pRemoveButton->setToolTip(tr("Remove Primary Part")); pLayout->addWidget(pRemoveButton); pLayout->setMargin(0); pLayout->setSpacing(4); setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); setFocusProxy(pRemoveButton); connect(pRemoveButton, SIGNAL(clicked()), this, SLOT(onButtonClicked())); RBX::Workspace* workspace = RobloxDocManager::Instance().getPlayDoc()->getDataModel()->getWorkspace(); m_pSelectionTool = RBX::Creatable::create(workspace); workspace->setMouseCommand(m_pSelectionTool); m_cPartSelectedConnection = m_pSelectionTool->partSelectedSignal.connect( boost::bind(&PrimaryPartSelectionEditor::onPartSelected, this, _1) ); m_cPartHoveredOverConnection = m_pSelectionTool->partHoveredOverSignal.connect( boost::bind(&PrimaryPartSelectionEditor::onPartHoveredOver, this, _1, _2) ); RobloxTreeWidget* pTreeWidget = UpdateUIManager::Instance().getViewWidget(eDW_OBJECT_EXPLORER).getTreeWidget(); if (pTreeWidget) pTreeWidget->setInstanceSelectionHandler(this); updateCursor("textures/ArrowFarCursor.png"); qApp->installEventFilter(this); } PrimaryPartSelectionEditor::~PrimaryPartSelectionEditor() { QApplication::restoreOverrideCursor(); if (RobloxDocManager::Instance().getPlayDoc()) { RBX::Workspace* workspace = RobloxDocManager::Instance().getPlayDoc()->getDataModel()->getWorkspace(); if (workspace->getCurrentMouseCommand() == m_pSelectionTool.get()) { workspace->setMouseCommand(boost::shared_ptr()); UpdateUIManager::Instance().updateToolBars(); } RobloxTreeWidget* pTreeWidget = UpdateUIManager::Instance().getViewWidget(eDW_OBJECT_EXPLORER).getTreeWidget(); if (pTreeWidget) { pTreeWidget->selectionModel()->clearSelection(); pTreeWidget->setInstanceSelectionHandler(NULL); } } } bool PrimaryPartSelectionEditor::eventFilter(QObject* watched,QEvent* evt) { if ((evt->type() == QEvent::ActionChanged) && (RobloxDocManager::Instance().getPlayDoc())) { // if there's an action change and any mouse command related action is activated then close editing operation static QString mouseCommandActions("advArrowToolAction actionFillColor actionMaterial anchorAction glueSurfaceAction " "smoothSurfaceAction weldSurfaceAction studsAction inletAction universalsAction hingeAction smoothNoOutlinesAction " "motorRightAction dropperAction playSoloAction lockAction advTranslateAction advRotateAction resizeAction"); if (!watched->objectName().isEmpty() && mouseCommandActions.contains(watched->objectName())) { QAction* pAction = qobject_cast(watched); if (pAction && pAction->isChecked()) deleteLater(); } } else if ((evt->type() == QEvent::KeyRelease) && (static_cast(evt)->key() == Qt::Key_Escape)) { deleteLater(); } else if (evt->type() == PRIMARY_PART_SELECTED) { if (m_pTempPrimaryPart.lock()) setPrimaryPart(m_pTempPrimaryPart.lock().get()); } return false; } void PrimaryPartSelectionEditor::onPartHoveredOver(RBX::PartInstance* pPart, bool& isValid) { if (pPart && pPart->isDescendantOf(m_pModelInstance.get())) { isValid = true; updateCursor("textures/ArrowCursor.png"); } else { isValid = false; updateCursor("textures/ArrowFarCursor.png"); } } void PrimaryPartSelectionEditor::onInstanceSelected(boost::shared_ptr instance) { setPrimaryPart(RBX::Instance::fastDynamicCast(instance.get())); } void PrimaryPartSelectionEditor::onInstanceHovered(boost::shared_ptr instance) { bool isValid = false; onPartHoveredOver(RBX::Instance::fastDynamicCast(instance.get()), isValid); } void PrimaryPartSelectionEditor::onPartSelected(RBX::PartInstance* pPart) { m_pTempPrimaryPart = shared_from(pPart); QApplication::postEvent(this, new RobloxCustomEvent(PRIMARY_PART_SELECTED)); } void PrimaryPartSelectionEditor::onButtonClicked() { m_pPrimaryPartPropertyItem->buttonClicked(sender()->objectName()); } bool PrimaryPartSelectionEditor::setPrimaryPart(RBX::PartInstance* pPart) { if (pPart && pPart->isDescendantOf(m_pModelInstance.get())) { if (pPart == m_pModelInstance->getPrimaryPartSetByUser()) { RBX::StandardOut::singleton()->print(RBX::MESSAGE_ERROR, "Selected part is already set as primary part."); return false; } RBXASSERT(RobloxDocManager::Instance().getPlayDoc() && RobloxDocManager::Instance().getPlayDoc()->getDataModel()); RBX::DataModel::LegacyLock lock(RobloxDocManager::Instance().getPlayDoc()->getDataModel(), RBX::DataModelJob::Write); // set primary part m_pModelInstance->setPrimaryPartSetByUser(pPart); // update property item m_pPrimaryPartPropertyItem->setModelData(NULL); return true; } return false; } void PrimaryPartSelectionEditor::updateCursor(const char* cursorImage) { QApplication::restoreOverrideCursor(); QPixmap pixmap(RBX::ContentProvider::getAssetFile(cursorImage).c_str()); if (!pixmap.isNull()) { QPainter painter(&pixmap); QPixmap toAppend(":/images/cursor_PrimaryPart.png"); painter.drawPixmap(40, 20, toAppend); QApplication::setOverrideCursor(pixmap); } }