Files
watrbx-game-engine/RobloxStudio/CommonInsertWidget.cpp
T
2025-09-18 17:55:52 -04:00

919 lines
32 KiB
C++

/**
* 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;
}