diff --git a/src/Core/CRayCollisionTester.cpp b/src/Core/CRayCollisionTester.cpp index 84e3940e..1d392c37 100644 --- a/src/Core/CRayCollisionTester.cpp +++ b/src/Core/CRayCollisionTester.cpp @@ -64,5 +64,6 @@ SRayIntersection CRayCollisionTester::TestNodes(const SViewInfo& ViewInfo) } } + if (Result.Hit) Result.HitPoint = mRay.PointOnRay(Result.Distance); return Result; } diff --git a/src/Core/Resource/CGameArea.cpp b/src/Core/Resource/CGameArea.cpp index f2c004fb..bd84fc56 100644 --- a/src/Core/Resource/CGameArea.cpp +++ b/src/Core/Resource/CGameArea.cpp @@ -127,6 +127,22 @@ CScriptObject* CGameArea::InstanceByID(u32 InstanceID) else return nullptr; } +u32 CGameArea::FindUnusedInstanceID(CScriptLayer *pLayer) const +{ + u32 InstanceID = (pLayer->AreaIndex() << 26) | (mWorldIndex << 16) | 1; + + while (true) + { + auto it = mObjectMap.find(InstanceID); + + if (it == mObjectMap.end()) + break; + else + InstanceID++; + } + + return InstanceID; +} CScriptObject* CGameArea::SpawnInstance(CScriptTemplate *pTemplate, CScriptLayer *pLayer, @@ -167,17 +183,7 @@ CScriptObject* CGameArea::SpawnInstance(CScriptTemplate *pTemplate, } // Look for a valid instance ID - InstanceID = (LayerIndex << 26) | (mWorldIndex << 16) | 1; - - while (true) - { - auto it = mObjectMap.find(InstanceID); - - if (it == mObjectMap.end()) - break; - else - InstanceID++; - } + InstanceID = FindUnusedInstanceID(pLayer); } // Spawn instance diff --git a/src/Core/Resource/CGameArea.h b/src/Core/Resource/CGameArea.h index ce96a38b..e0909643 100644 --- a/src/Core/Resource/CGameArea.h +++ b/src/Core/Resource/CGameArea.h @@ -69,6 +69,7 @@ public: void ClearScriptLayers(); u32 TotalInstanceCount() const; CScriptObject* InstanceByID(u32 InstanceID); + u32 FindUnusedInstanceID(CScriptLayer *pLayer) const; CScriptObject* SpawnInstance(CScriptTemplate *pTemplate, CScriptLayer *pLayer, const CVector3f& rkPosition = CVector3f::skZero, const CQuaternion& rkRotation = CQuaternion::skIdentity, diff --git a/src/Core/Resource/Factory/CScriptLoader.cpp b/src/Core/Resource/Factory/CScriptLoader.cpp index 40b75436..3955af97 100644 --- a/src/Core/Resource/Factory/CScriptLoader.cpp +++ b/src/Core/Resource/Factory/CScriptLoader.cpp @@ -215,6 +215,7 @@ CScriptObject* CScriptLoader::LoadObjectMP1(IInputStream& rSCLY) } u32 InstanceID = rSCLY.ReadLong(); + if (InstanceID == -1) InstanceID = mpArea->FindUnusedInstanceID(mpLayer); mpObj = new CScriptObject(InstanceID, mpArea, mpLayer, pTemp); // Load connections @@ -328,6 +329,7 @@ CScriptObject* CScriptLoader::LoadObjectMP2(IInputStream& rSCLY) } u32 InstanceID = rSCLY.ReadLong(); + if (InstanceID == -1) InstanceID = mpArea->FindUnusedInstanceID(mpLayer); mpObj = new CScriptObject(InstanceID, mpArea, mpLayer, pTemplate); // Load connections diff --git a/src/Core/Resource/Script/CLink.h b/src/Core/Resource/Script/CLink.h index 93088645..f133dfb9 100644 --- a/src/Core/Resource/Script/CLink.h +++ b/src/Core/Resource/Script/CLink.h @@ -56,7 +56,7 @@ public: CScriptObject *pNewSender = mpArea->InstanceByID(NewSenderID); mSenderID = NewSenderID; - pOldSender->RemoveLink(eOutgoing, this); + if (pOldSender) pOldSender->RemoveLink(eOutgoing, this); pNewSender->AddLink(eOutgoing, this, Index); } @@ -67,7 +67,7 @@ public: CScriptObject *pNewReceiver = mpArea->InstanceByID(NewReceiverID); mReceiverID = NewReceiverID; - pOldReceiver->RemoveLink(eIncoming, this); + if (pOldReceiver) pOldReceiver->RemoveLink(eIncoming, this); pNewReceiver->AddLink(eIncoming, this, Index); } diff --git a/src/Editor/CNodeCopyMimeData.h b/src/Editor/CNodeCopyMimeData.h new file mode 100644 index 00000000..8e493e40 --- /dev/null +++ b/src/Editor/CNodeCopyMimeData.h @@ -0,0 +1,109 @@ +#ifndef CNODECOPYMIMEDATA +#define CNODECOPYMIMEDATA + +#include +#include +#include +#include +#include +#include "Editor/CSelectionIterator.h" +#include "Editor/WorldEditor/CWorldEditor.h" + +#include + +class CNodeCopyMimeData : public QMimeData +{ + Q_OBJECT + +public: + struct SCopiedNode + { + ENodeType Type; + TString Name; + CVector3f Position; + CQuaternion Rotation; + CVector3f Scale; + + u32 OriginalInstanceID; + std::vector InstanceData; + }; + +private: + CWorldEditor *mpEditor; + CUniqueID mAreaID; + QVector mCopiedNodes; + EGame mGame; + +public: + CNodeCopyMimeData(const CNodeCopyMimeData& rkSrc) + : mpEditor(rkSrc.mpEditor) + , mAreaID(rkSrc.mAreaID) + , mCopiedNodes(rkSrc.mCopiedNodes) + , mGame(rkSrc.mGame) + { + } + + CNodeCopyMimeData(CWorldEditor *pEditor) + : mpEditor(pEditor) + , mAreaID(pEditor->ActiveArea()->ResID()) + , mGame(pEditor->CurrentGame()) + { + CNodeSelection *pSelection = pEditor->Selection(); + mCopiedNodes.resize(pSelection->Size()); + + u32 NodeIndex = 0; + CVector3f FirstNodePos; + bool SetFirstNodePos = false; + + for (CSelectionIterator It(pEditor->Selection()); It; ++It) + { + SCopiedNode& rNode = mCopiedNodes[NodeIndex]; + rNode.Type = It->NodeType(); + rNode.Name = It->Name(); + rNode.Position = It->LocalPosition(); + rNode.Rotation = It->LocalRotation(); + rNode.Scale = It->LocalScale(); + + if (rNode.Type == eScriptNode) + { + CScriptObject *pInst = static_cast(*It)->Object(); + rNode.OriginalInstanceID = pInst->InstanceID(); + + CVectorOutStream Out(&rNode.InstanceData, IOUtil::eBigEndian); + CScriptCooker::CookInstance(eReturns, static_cast(*It)->Object(), Out); + + // Replace instance ID with 0xFFFFFFFF to force it to generate a new one. + Out.Seek(0x6, SEEK_SET); + Out.WriteLong(0xFFFFFFFF); + + if (!SetFirstNodePos) + { + FirstNodePos = rNode.Position; + SetFirstNodePos = true; + } + + rNode.Position -= FirstNodePos; + } + + NodeIndex++; + } + } + + int IndexOfInstanceID(u32 InstanceID) const + { + for (int iNode = 0; iNode < mCopiedNodes.size(); iNode++) + { + if (mCopiedNodes[iNode].OriginalInstanceID == InstanceID) + return iNode; + } + + return -1; + } + + CUniqueID AreaID() const { return mAreaID; } + EGame Game() const { return mGame; } + const QVector& CopiedNodes() const { return mCopiedNodes; } +}; + +#endif // CNODECOPYMIMEDATA + diff --git a/src/Editor/CSceneViewport.cpp b/src/Editor/CSceneViewport.cpp index 4799de11..9176d21b 100644 --- a/src/Editor/CSceneViewport.cpp +++ b/src/Editor/CSceneViewport.cpp @@ -112,31 +112,33 @@ void CSceneViewport::CheckGizmoInput(const CRay& ray) else mGizmoHovering = false; } -void CSceneViewport::SceneRayCast(const CRay& rkRay) +SRayIntersection CSceneViewport::SceneRayCast(const CRay& rkRay) { if (mpEditor->Gizmo()->IsTransforming()) { ResetHover(); - return; + return SRayIntersection(); } - mRayIntersection = mpScene->SceneRayCast(rkRay, mViewInfo); + SRayIntersection Intersect = mpScene->SceneRayCast(rkRay, mViewInfo); - if (mRayIntersection.Hit) + if (Intersect.Hit) { if (mpHoverNode) mpHoverNode->SetMouseHovering(false); - mpHoverNode = mRayIntersection.pNode; + mpHoverNode = Intersect.pNode; mpHoverNode->SetMouseHovering(true); - mHoverPoint = rkRay.PointOnRay(mRayIntersection.Distance); + mHoverPoint = rkRay.PointOnRay(Intersect.Distance); } else { - mHoverPoint = rkRay.PointOnRay(5.f); + mHoverPoint = rkRay.PointOnRay(10.f); ResetHover(); } + + return Intersect; } void CSceneViewport::ResetHover() @@ -242,6 +244,8 @@ QMouseEvent CSceneViewport::CreateMouseEvent() void CSceneViewport::FindConnectedObjects(u32 InstanceID, bool SearchOutgoing, bool SearchIncoming, QList& rIDList) { CScriptNode *pScript = mpScene->NodeForInstanceID(InstanceID); + if (!pScript) return; + CScriptObject *pInst = pScript->Object(); rIDList << InstanceID; @@ -287,7 +291,7 @@ void CSceneViewport::CheckUserInput() CheckGizmoInput(Ray); if (!mpEditor->Gizmo()->IsTransforming()) - SceneRayCast(Ray); + mRayIntersection = SceneRayCast(Ray); } else @@ -341,7 +345,7 @@ void CSceneViewport::Paint() void CSceneViewport::ContextMenu(QContextMenuEvent* pEvent) { // mpHoverNode is cleared during mouse input, so this call is necessary. todo: better way? - SceneRayCast(CastRay()); + mRayIntersection = SceneRayCast(CastRay()); // Set up actions TString NodeName; diff --git a/src/Editor/CSceneViewport.h b/src/Editor/CSceneViewport.h index b906c38d..e5993071 100644 --- a/src/Editor/CSceneViewport.h +++ b/src/Editor/CSceneViewport.h @@ -55,7 +55,7 @@ public: CSceneNode* HoverNode(); CVector3f HoverPoint(); void CheckGizmoInput(const CRay& ray); - void SceneRayCast(const CRay& ray); + SRayIntersection SceneRayCast(const CRay& ray); void ResetHover(); bool IsHoveringGizmo(); diff --git a/src/Editor/Editor.pro b/src/Editor/Editor.pro index eac9a516..f37da5c6 100644 --- a/src/Editor/Editor.pro +++ b/src/Editor/Editor.pro @@ -155,7 +155,9 @@ HEADERS += \ WorldEditor/CTemplateListView.h \ CSelectionIterator.h \ Undo/ObjReferences.h \ - Undo/CCloneSelectionCommand.h + Undo/CCloneSelectionCommand.h \ + CNodeCopyMimeData.h \ + Undo/CPasteNodesCommand.h # Source Files SOURCES += \ @@ -213,7 +215,8 @@ SOURCES += \ Undo/CEditLinkCommand.cpp \ Undo/CDeleteSelectionCommand.cpp \ Undo/CCreateInstanceCommand.cpp \ - Undo/CCloneSelectionCommand.cpp + Undo/CCloneSelectionCommand.cpp \ + Undo/CPasteNodesCommand.cpp # UI Files FORMS += \ diff --git a/src/Editor/Undo/CCloneSelectionCommand.cpp b/src/Editor/Undo/CCloneSelectionCommand.cpp index 384eaffd..bae0a1b1 100644 --- a/src/Editor/Undo/CCloneSelectionCommand.cpp +++ b/src/Editor/Undo/CCloneSelectionCommand.cpp @@ -40,6 +40,7 @@ void CCloneSelectionCommand::redo() QList ToCloneInstanceIDs; QList ClonedInstanceIDs; + // Clone nodes foreach (CSceneNode *pNode, ToClone) { mpEditor->NotifyNodeAboutToBeSpawned(); @@ -55,7 +56,6 @@ void CCloneSelectionCommand::redo() pCloneNode->SetPosition(pScript->LocalPosition()); pCloneNode->SetRotation(pScript->LocalRotation()); pCloneNode->SetScale(pScript->LocalScale()); - pCloneNode->OnLoadFinished(); ToCloneInstanceIDs << pInstance->InstanceID(); ClonedInstanceIDs << pCloneInst->InstanceID(); @@ -92,6 +92,10 @@ void CCloneSelectionCommand::redo() } } + // Call LoadFinished + foreach (CSceneNode *pNode, ClonedNodes) + pNode->OnLoadFinished(); + mpEditor->OnLinksModified(LinkedInstances); mpEditor->Selection()->SetSelectedNodes(mClonedNodes.DereferenceList()); } diff --git a/src/Editor/Undo/CDeleteSelectionCommand.cpp b/src/Editor/Undo/CDeleteSelectionCommand.cpp index 7aabdff5..23350f83 100644 --- a/src/Editor/Undo/CDeleteSelectionCommand.cpp +++ b/src/Editor/Undo/CDeleteSelectionCommand.cpp @@ -4,8 +4,8 @@ #include #include -CDeleteSelectionCommand::CDeleteSelectionCommand(CWorldEditor *pEditor) - : IUndoCommand("Delete") +CDeleteSelectionCommand::CDeleteSelectionCommand(CWorldEditor *pEditor, const QString& rkCommandName /*= "Delete"*/) + : IUndoCommand(rkCommandName) , mpEditor(pEditor) { QSet Links; diff --git a/src/Editor/Undo/CDeleteSelectionCommand.h b/src/Editor/Undo/CDeleteSelectionCommand.h index bd9b9be0..7ab3d5ad 100644 --- a/src/Editor/Undo/CDeleteSelectionCommand.h +++ b/src/Editor/Undo/CDeleteSelectionCommand.h @@ -47,7 +47,7 @@ class CDeleteSelectionCommand : public IUndoCommand QVector mDeletedLinks; public: - CDeleteSelectionCommand(CWorldEditor *pEditor); + CDeleteSelectionCommand(CWorldEditor *pEditor, const QString& rkCommandName = "Delete"); void undo(); void redo(); bool AffectsCleanState() const { return true; } diff --git a/src/Editor/Undo/CPasteNodesCommand.cpp b/src/Editor/Undo/CPasteNodesCommand.cpp new file mode 100644 index 00000000..eff9102d --- /dev/null +++ b/src/Editor/Undo/CPasteNodesCommand.cpp @@ -0,0 +1,124 @@ +#include "CPasteNodesCommand.h" + +CPasteNodesCommand::CPasteNodesCommand(CWorldEditor *pEditor, CScriptLayer *pLayer, CVector3f PastePoint) + : IUndoCommand("Paste") + , mpEditor(pEditor) + , mpLayer(pLayer) + , mPastePoint(PastePoint) + , mOriginalSelection(pEditor->Selection()->SelectedNodeList()) +{ + const CNodeCopyMimeData *pkMimeData = qobject_cast(qApp->clipboard()->mimeData()); + + if (pkMimeData) + mpMimeData = new CNodeCopyMimeData(*pkMimeData); + else + mpMimeData = nullptr; +} + +CPasteNodesCommand::~CPasteNodesCommand() +{ + if (mpMimeData) delete mpMimeData; +} + +void CPasteNodesCommand::undo() +{ + mpEditor->Selection()->SetSelectedNodes(mOriginalSelection.DereferenceList()); + QList PastedNodes = mPastedNodes.DereferenceList(); + + foreach (CSceneNode *pNode, PastedNodes) + { + CScriptObject *pInst = (pNode->NodeType() == eScriptNode ? static_cast(pNode)->Object() : nullptr); + mpEditor->NotifyNodeAboutToBeDeleted(pNode); + mpEditor->Scene()->DeleteNode(pNode); + if (pInst) mpEditor->ActiveArea()->DeleteInstance(pInst); + mpEditor->NotifyNodeDeleted(); + } + + mPastedNodes.clear(); +} + +void CPasteNodesCommand::redo() +{ + if (!mpMimeData) return; + + const QVector& rkNodes = mpMimeData->CopiedNodes(); + CScene *pScene = mpEditor->Scene(); + CGameArea *pArea = mpEditor->ActiveArea(); + QList PastedNodes; + + foreach (const CNodeCopyMimeData::SCopiedNode& rkNode, rkNodes) + { + CSceneNode *pNewNode = nullptr; + + if (rkNode.Type == eScriptNode) + { + CMemoryInStream In(rkNode.InstanceData.data(), rkNode.InstanceData.size(), IOUtil::eBigEndian); + CScriptObject *pInstance = CScriptLoader::LoadInstance(In, pArea, mpLayer, pArea->Version(), true); + pArea->AddInstanceToArea(pInstance); + + pInstance->SetPosition(rkNode.Position + mPastePoint); + pInstance->SetRotation(rkNode.Rotation.ToEuler()); + pInstance->SetScale(rkNode.Scale); + + mpEditor->NotifyNodeAboutToBeSpawned(); + pNewNode = pScene->CreateScriptNode(pInstance); + } + + if (pNewNode) + { + pNewNode->SetName(rkNode.Name); + pNewNode->SetPosition(rkNode.Position + mPastePoint); + pNewNode->SetRotation(rkNode.Rotation); + pNewNode->SetScale(rkNode.Scale); + + PastedNodes << pNewNode; + mpEditor->NotifyNodeSpawned(pNewNode); + } + + // If we didn't paste a valid node, add a null node so that the indices still match up with the indices from the mime data. + else + PastedNodes << nullptr; + } + + // Fix links. This is how fixes are prioritized: + // 1. If the link receiver has also been copied then redirect to the copied version. + // 2. If we're pasting into the same area that this data was copied from and the receiver still exists, connect to original receiver. + // 3. If neither of those things is true, then delete the link. + foreach (CSceneNode *pNode, PastedNodes) + { + if (pNode && pNode->NodeType() == eScriptNode) + { + CScriptObject *pInstance = static_cast(pNode)->Object(); + + for (u32 iLink = 0; iLink < pInstance->NumLinks(eOutgoing); iLink++) + { + CLink *pLink = pInstance->Link(eOutgoing, iLink); + int Index = mpMimeData->IndexOfInstanceID(pLink->ReceiverID()); + + if (Index != -1) + { + CScriptObject *pNewTarget = static_cast(PastedNodes[Index])->Object(); + pLink->SetReceiver(pNewTarget->InstanceID()); + } + + else if (mpMimeData->AreaID() != pArea->ResID() || pArea->InstanceByID(pLink->ReceiverID()) == nullptr) + { + CScriptObject *pSender = pLink->Sender(); + CScriptObject *pReceiver = pLink->Receiver(); + if (pSender) pSender->RemoveLink(eOutgoing, pLink); + if (pReceiver) pReceiver->RemoveLink(eIncoming, pLink); + delete pLink; + } + } + } + } + + // Call PostLoad on all new nodes and select them + PastedNodes.removeAll(nullptr); + + foreach (CSceneNode *pNode, PastedNodes) + pNode->OnLoadFinished(); + + mpEditor->Selection()->SetSelectedNodes(PastedNodes); + mPastedNodes = PastedNodes; +} diff --git a/src/Editor/Undo/CPasteNodesCommand.h b/src/Editor/Undo/CPasteNodesCommand.h new file mode 100644 index 00000000..027066e1 --- /dev/null +++ b/src/Editor/Undo/CPasteNodesCommand.h @@ -0,0 +1,29 @@ +#ifndef CPASTENODESCOMMAND +#define CPASTENODESCOMMAND + +#include "IUndoCommand.h" +#include "ObjReferences.h" +#include "Editor/CNodeCopyMimeData.h" +#include "Editor/WorldEditor/CWorldEditor.h" +#include + +class CPasteNodesCommand : public IUndoCommand +{ + CWorldEditor *mpEditor; + CScriptLayer *mpLayer; + CVector3f mPastePoint; + CNodeCopyMimeData *mpMimeData; + CNodePtrList mPastedNodes; + CNodePtrList mOriginalSelection; + +public: + CPasteNodesCommand(CWorldEditor *pEditor, CScriptLayer *pLayer, CVector3f PastePoint); + ~CPasteNodesCommand(); + void undo(); + void redo(); + + bool AffectsCleanState() const { return true; } +}; + +#endif // CPASTENODESCOMMAND + diff --git a/src/Editor/Undo/UndoCommands.h b/src/Editor/Undo/UndoCommands.h index 3b5e8c62..8c3e71c7 100644 --- a/src/Editor/Undo/UndoCommands.h +++ b/src/Editor/Undo/UndoCommands.h @@ -3,6 +3,7 @@ #include "CCreateInstanceCommand.h" #include "CCloneSelectionCommand.h" +#include "CPasteNodesCommand.h" #include "CTranslateNodeCommand.h" #include "CRotateNodeCommand.h" diff --git a/src/Editor/WorldEditor/CWorldEditor.cpp b/src/Editor/WorldEditor/CWorldEditor.cpp index 1af81406..7c38f6a2 100644 --- a/src/Editor/WorldEditor/CWorldEditor.cpp +++ b/src/Editor/WorldEditor/CWorldEditor.cpp @@ -7,6 +7,7 @@ #include "WInstancesTab.h" #include "Editor/CBasicViewport.h" +#include "Editor/CNodeCopyMimeData.h" #include "Editor/CSelectionIterator.h" #include "Editor/UICommon.h" #include "Editor/PropertyEdit/CPropertyView.h" @@ -20,6 +21,7 @@ #include #include +#include #include #include #include @@ -62,13 +64,12 @@ CWorldEditor::CWorldEditor(QWidget *parent) mpTransformCombo->setMinimumWidth(75); ui->MainToolBar->addActions(mGizmoActions); ui->MainToolBar->addWidget(mpTransformCombo); - ui->menuEdit->insertActions(ui->ActionSelectAll, mUndoActions); - ui->menuEdit->insertSeparator(ui->ActionSelectAll); + ui->menuEdit->insertActions(ui->ActionCut, mUndoActions); + ui->menuEdit->insertSeparator(ui->ActionCut); // Initialize actions addAction(ui->ActionIncrementGizmo); addAction(ui->ActionDecrementGizmo); - addAction(ui->ActionDelete); QAction *pToolBarUndo = mUndoStack.createUndoAction(this); pToolBarUndo->setIcon(QIcon(":/icons/Undo.png")); @@ -79,6 +80,15 @@ CWorldEditor::CWorldEditor(QWidget *parent) ui->MainToolBar->insertAction(ui->ActionLink, pToolBarRedo); ui->MainToolBar->insertSeparator(ui->ActionLink); + ui->ActionCut->setAutoRepeat(false); + ui->ActionCut->setShortcut(QKeySequence::Cut); + ui->ActionCopy->setAutoRepeat(false); + ui->ActionCopy->setShortcut(QKeySequence::Copy); + ui->ActionPaste->setAutoRepeat(false); + ui->ActionPaste->setShortcut(QKeySequence::Paste); + ui->ActionDelete->setAutoRepeat(false); + ui->ActionDelete->setShortcut(QKeySequence::Delete); + // Connect signals and slots connect(ui->MainViewport, SIGNAL(ViewportClick(SRayIntersection,QMouseEvent*)), this, SLOT(OnViewportClick(SRayIntersection,QMouseEvent*))); connect(ui->MainViewport, SIGNAL(InputProcessed(SRayIntersection,QMouseEvent*)), this, SLOT(OnViewportInputProcessed(SRayIntersection,QMouseEvent*))); @@ -87,7 +97,7 @@ CWorldEditor::CWorldEditor(QWidget *parent) connect(ui->MainViewport, SIGNAL(InputProcessed(SRayIntersection,QMouseEvent*)), this, SLOT(UpdateCursor()) ); connect(ui->MainViewport, SIGNAL(GizmoMoved()), this, SLOT(OnGizmoMoved())); connect(ui->MainViewport, SIGNAL(CameraOrbit()), this, SLOT(UpdateCameraOrbit())); - connect(this, SIGNAL(SelectionModified()), this, SLOT(UpdateCameraOrbit())); + connect(this, SIGNAL(SelectionModified()), this, SLOT(OnSelectionModified())); connect(this, SIGNAL(SelectionTransformed()), this, SLOT(UpdateCameraOrbit())); connect(this, SIGNAL(PickModeEntered(QCursor)), this, SLOT(OnPickModeEnter(QCursor))); connect(this, SIGNAL(PickModeExited()), this, SLOT(OnPickModeExit())); @@ -97,6 +107,10 @@ CWorldEditor::CWorldEditor(QWidget *parent) connect(ui->ActionLink, SIGNAL(toggled(bool)), this, SLOT(OnLinkButtonToggled(bool))); connect(ui->ActionUnlink, SIGNAL(triggered()), this, SLOT(OnUnlinkClicked())); connect(ui->ActionDelete, SIGNAL(triggered()), this, SLOT(DeleteSelection())); + connect(ui->ActionCut, SIGNAL(triggered()), this, SLOT(Cut())); + connect(ui->ActionCopy, SIGNAL(triggered()), this, SLOT(Copy())); + connect(ui->ActionPaste, SIGNAL(triggered()), this, SLOT(Paste())); + connect(qApp->clipboard(), SIGNAL(dataChanged()), this, SLOT(OnClipboardDataModified())); connect(&mUndoStack, SIGNAL(indexChanged(int)), this, SLOT(OnUndoStackIndexChanged())); connect(ui->ActionSave, SIGNAL(triggered()), this, SLOT(Save())); @@ -239,6 +253,17 @@ bool CWorldEditor::CheckUnsavedChanges() return OkToClear; } +bool CWorldEditor::HasAnyScriptNodesSelected() const +{ + for (CSelectionIterator It(mpSelection); It; ++It) + { + if (It->NodeType() == eScriptNode) + return true; + } + + return false; +} + CSceneViewport* CWorldEditor::Viewport() const { return ui->MainViewport; @@ -253,6 +278,53 @@ void CWorldEditor::NotifyNodeAboutToBeDeleted(CSceneNode *pNode) ui->MainViewport->ResetHover(); } +void CWorldEditor::Cut() +{ + if (!mpSelection->IsEmpty()) + { + Copy(); + mUndoStack.push(new CDeleteSelectionCommand(this, "Cut")); + } +} + +void CWorldEditor::Copy() +{ + if (!mpSelection->IsEmpty()) + { + CNodeCopyMimeData *pMimeData = new CNodeCopyMimeData(this); + qApp->clipboard()->setMimeData(pMimeData); + } +} + +void CWorldEditor::Paste() +{ + if (const CNodeCopyMimeData *pkMimeData = + qobject_cast(qApp->clipboard()->mimeData())) + { + if (pkMimeData->Game() == CurrentGame()) + { + CVector3f PastePoint; + + if (ui->MainViewport->underMouse() && !ui->MainViewport->IsMouseInputActive()) + PastePoint = ui->MainViewport->HoverPoint(); + + else + { + CRay Ray = ui->MainViewport->Camera().CastRay(CVector2f(0.f, 0.f)); + SRayIntersection Intersect = ui->MainViewport->SceneRayCast(Ray); + + if (Intersect.Hit) + PastePoint = Intersect.HitPoint; + else + PastePoint = Ray.PointOnRay(10.f); + } + + CPasteNodesCommand *pCmd = new CPasteNodesCommand(this, ui->CreateTabContents->SpawnLayer(), PastePoint); + mUndoStack.push(pCmd); + } + } +} + bool CWorldEditor::Save() { TString Out = mpArea->FullSource(); @@ -390,9 +462,11 @@ void CWorldEditor::SetSelectionLayer(CScriptLayer *pLayer) void CWorldEditor::DeleteSelection() { - // note: make it only happen if there is a script node selected - CDeleteSelectionCommand *pCmd = new CDeleteSelectionCommand(this); - mUndoStack.push(pCmd); + if (HasAnyScriptNodesSelected()) + { + CDeleteSelectionCommand *pCmd = new CDeleteSelectionCommand(this); + mUndoStack.push(pCmd); + } } void CWorldEditor::UpdateStatusBar() @@ -574,6 +648,23 @@ void CWorldEditor::GizmoModeChanged(CGizmo::EGizmoMode mode) } // ************ PRIVATE SLOTS ************ +void CWorldEditor::OnClipboardDataModified() +{ + const QMimeData *pkMimeData = qApp->clipboard()->mimeData(); + bool ValidMimeData = (qobject_cast(pkMimeData) != nullptr); + ui->ActionPaste->setEnabled(ValidMimeData); +} + +void CWorldEditor::OnSelectionModified() +{ + bool HasScriptNode = HasAnyScriptNodesSelected(); + ui->ActionCut->setEnabled(HasScriptNode); + ui->ActionCopy->setEnabled(HasScriptNode); + ui->ActionDelete->setEnabled(HasScriptNode); + + UpdateCameraOrbit(); +} + void CWorldEditor::OnLinkButtonToggled(bool Enabled) { if (Enabled) diff --git a/src/Editor/WorldEditor/CWorldEditor.h b/src/Editor/WorldEditor/CWorldEditor.h index 5e2f3e1f..2ecb43d3 100644 --- a/src/Editor/WorldEditor/CWorldEditor.h +++ b/src/Editor/WorldEditor/CWorldEditor.h @@ -50,6 +50,7 @@ public: void closeEvent(QCloseEvent *pEvent); void SetArea(CWorld *pWorld, CGameArea *pArea); bool CheckUnsavedChanges(); + bool HasAnyScriptNodesSelected() const; inline CGameArea* ActiveArea() const { return mpArea; } inline EGame CurrentGame() const { return mpArea ? mpArea->Version() : eUnknownVersion; } @@ -59,6 +60,9 @@ public: public slots: virtual void NotifyNodeAboutToBeDeleted(CSceneNode *pNode); + void Cut(); + void Copy(); + void Paste(); bool Save(); void OnLinksModified(const QList& rkInstances); void OnPropertyModified(IProperty *pProp); @@ -77,6 +81,9 @@ protected: void GizmoModeChanged(CGizmo::EGizmoMode mode); private slots: + void OnClipboardDataModified(); + void OnSelectionModified(); + void OnLinkButtonToggled(bool Enabled); void OnLinkClick(const SRayIntersection& rkIntersect); void OnLinkEnd(); diff --git a/src/Editor/WorldEditor/CWorldEditor.ui b/src/Editor/WorldEditor/CWorldEditor.ui index 84238fdb..ac50daff 100644 --- a/src/Editor/WorldEditor/CWorldEditor.ui +++ b/src/Editor/WorldEditor/CWorldEditor.ui @@ -417,6 +417,11 @@ Edit + + + + + @@ -753,6 +758,9 @@ + + false + Delete @@ -760,6 +768,30 @@ Del + + + false + + + Cut + + + + + false + + + Copy + + + + + false + + + Paste + + diff --git a/src/Editor/WorldEditor/WCreateTab.h b/src/Editor/WorldEditor/WCreateTab.h index 25a73f5b..49f91331 100644 --- a/src/Editor/WorldEditor/WCreateTab.h +++ b/src/Editor/WorldEditor/WCreateTab.h @@ -22,6 +22,9 @@ public: void SetEditor(CWorldEditor *pEditor); void SetMaster(CMasterTemplate *pMaster); + // Accessors + inline CScriptLayer* SpawnLayer() const { return mpSpawnLayer; } + public slots: void OnLayersChanged(); void OnSpawnLayerChanged(int LayerIndex); diff --git a/src/FileIO/CMemoryInStream.cpp b/src/FileIO/CMemoryInStream.cpp index 52a01feb..0a2a2362 100644 --- a/src/FileIO/CMemoryInStream.cpp +++ b/src/FileIO/CMemoryInStream.cpp @@ -6,7 +6,7 @@ CMemoryInStream::CMemoryInStream() mDataSize = 0; mPos = 0; } -CMemoryInStream::CMemoryInStream(void *pData, unsigned long Size, IOUtil::EEndianness DataEndianness) +CMemoryInStream::CMemoryInStream(const void *pData, unsigned long Size, IOUtil::EEndianness DataEndianness) { SetData(pData, Size, DataEndianness); } @@ -15,9 +15,9 @@ CMemoryInStream::~CMemoryInStream() { } -void CMemoryInStream::SetData(void *pData, unsigned long Size, IOUtil::EEndianness DataEndianness) +void CMemoryInStream::SetData(const void *pData, unsigned long Size, IOUtil::EEndianness DataEndianness) { - mpDataStart = static_cast(pData); + mpDataStart = static_cast(pData); mDataSize = Size; mPos = 0; mDataEndianness = DataEndianness; @@ -91,12 +91,12 @@ void CMemoryInStream::SetSize(unsigned long Size) mPos = mDataSize; } -void* CMemoryInStream::Data() const +const void* CMemoryInStream::Data() const { return mpDataStart; } -void* CMemoryInStream::DataAtPosition() const +const void* CMemoryInStream::DataAtPosition() const { return mpDataStart + mPos; } diff --git a/src/FileIO/CMemoryInStream.h b/src/FileIO/CMemoryInStream.h index e6142009..f0491725 100644 --- a/src/FileIO/CMemoryInStream.h +++ b/src/FileIO/CMemoryInStream.h @@ -6,15 +6,15 @@ class CMemoryInStream : public IInputStream { - char *mpDataStart; + const char *mpDataStart; long mDataSize; long mPos; public: CMemoryInStream(); - CMemoryInStream(void *pData, unsigned long Size, IOUtil::EEndianness dataEndianness); + CMemoryInStream(const void *pData, unsigned long Size, IOUtil::EEndianness dataEndianness); ~CMemoryInStream(); - void SetData(void *pData, unsigned long Size, IOUtil::EEndianness dataEndianness); + void SetData(const void *pData, unsigned long Size, IOUtil::EEndianness dataEndianness); void ReadBytes(void *pDst, unsigned long Count); bool Seek(long offset, long Origin); @@ -23,8 +23,8 @@ public: bool IsValid() const; long Size() const; void SetSize(unsigned long Size); - void* Data() const; - void* DataAtPosition() const; + const void* Data() const; + const void* DataAtPosition() const; }; #endif // CMEMORYINSTREAM_H