Added support for script instance copy/paste in the World Editor

This commit is contained in:
parax0 2016-03-20 06:31:23 -06:00
parent d961545309
commit 5009c08c87
21 changed files with 463 additions and 46 deletions

View File

@ -64,5 +64,6 @@ SRayIntersection CRayCollisionTester::TestNodes(const SViewInfo& ViewInfo)
} }
} }
if (Result.Hit) Result.HitPoint = mRay.PointOnRay(Result.Distance);
return Result; return Result;
} }

View File

@ -127,6 +127,22 @@ CScriptObject* CGameArea::InstanceByID(u32 InstanceID)
else return nullptr; 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, CScriptObject* CGameArea::SpawnInstance(CScriptTemplate *pTemplate,
CScriptLayer *pLayer, CScriptLayer *pLayer,
@ -167,17 +183,7 @@ CScriptObject* CGameArea::SpawnInstance(CScriptTemplate *pTemplate,
} }
// Look for a valid instance ID // Look for a valid instance ID
InstanceID = (LayerIndex << 26) | (mWorldIndex << 16) | 1; InstanceID = FindUnusedInstanceID(pLayer);
while (true)
{
auto it = mObjectMap.find(InstanceID);
if (it == mObjectMap.end())
break;
else
InstanceID++;
}
} }
// Spawn instance // Spawn instance

View File

@ -69,6 +69,7 @@ public:
void ClearScriptLayers(); void ClearScriptLayers();
u32 TotalInstanceCount() const; u32 TotalInstanceCount() const;
CScriptObject* InstanceByID(u32 InstanceID); CScriptObject* InstanceByID(u32 InstanceID);
u32 FindUnusedInstanceID(CScriptLayer *pLayer) const;
CScriptObject* SpawnInstance(CScriptTemplate *pTemplate, CScriptLayer *pLayer, CScriptObject* SpawnInstance(CScriptTemplate *pTemplate, CScriptLayer *pLayer,
const CVector3f& rkPosition = CVector3f::skZero, const CVector3f& rkPosition = CVector3f::skZero,
const CQuaternion& rkRotation = CQuaternion::skIdentity, const CQuaternion& rkRotation = CQuaternion::skIdentity,

View File

@ -215,6 +215,7 @@ CScriptObject* CScriptLoader::LoadObjectMP1(IInputStream& rSCLY)
} }
u32 InstanceID = rSCLY.ReadLong(); u32 InstanceID = rSCLY.ReadLong();
if (InstanceID == -1) InstanceID = mpArea->FindUnusedInstanceID(mpLayer);
mpObj = new CScriptObject(InstanceID, mpArea, mpLayer, pTemp); mpObj = new CScriptObject(InstanceID, mpArea, mpLayer, pTemp);
// Load connections // Load connections
@ -328,6 +329,7 @@ CScriptObject* CScriptLoader::LoadObjectMP2(IInputStream& rSCLY)
} }
u32 InstanceID = rSCLY.ReadLong(); u32 InstanceID = rSCLY.ReadLong();
if (InstanceID == -1) InstanceID = mpArea->FindUnusedInstanceID(mpLayer);
mpObj = new CScriptObject(InstanceID, mpArea, mpLayer, pTemplate); mpObj = new CScriptObject(InstanceID, mpArea, mpLayer, pTemplate);
// Load connections // Load connections

View File

@ -56,7 +56,7 @@ public:
CScriptObject *pNewSender = mpArea->InstanceByID(NewSenderID); CScriptObject *pNewSender = mpArea->InstanceByID(NewSenderID);
mSenderID = NewSenderID; mSenderID = NewSenderID;
pOldSender->RemoveLink(eOutgoing, this); if (pOldSender) pOldSender->RemoveLink(eOutgoing, this);
pNewSender->AddLink(eOutgoing, this, Index); pNewSender->AddLink(eOutgoing, this, Index);
} }
@ -67,7 +67,7 @@ public:
CScriptObject *pNewReceiver = mpArea->InstanceByID(NewReceiverID); CScriptObject *pNewReceiver = mpArea->InstanceByID(NewReceiverID);
mReceiverID = NewReceiverID; mReceiverID = NewReceiverID;
pOldReceiver->RemoveLink(eIncoming, this); if (pOldReceiver) pOldReceiver->RemoveLink(eIncoming, this);
pNewReceiver->AddLink(eIncoming, this, Index); pNewReceiver->AddLink(eIncoming, this, Index);
} }

View File

@ -0,0 +1,109 @@
#ifndef CNODECOPYMIMEDATA
#define CNODECOPYMIMEDATA
#include <Common/TString.h>
#include <Math/CVector3f.h>
#include <Core/Resource/Cooker/CScriptCooker.h>
#include <Core/Resource/Factory/CScriptLoader.h>
#include <Core/Scene/CSceneNode.h>
#include "Editor/CSelectionIterator.h"
#include "Editor/WorldEditor/CWorldEditor.h"
#include <QMimeData>
class CNodeCopyMimeData : public QMimeData
{
Q_OBJECT
public:
struct SCopiedNode
{
ENodeType Type;
TString Name;
CVector3f Position;
CQuaternion Rotation;
CVector3f Scale;
u32 OriginalInstanceID;
std::vector<char> InstanceData;
};
private:
CWorldEditor *mpEditor;
CUniqueID mAreaID;
QVector<SCopiedNode> 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<CScriptNode*>(*It)->Object();
rNode.OriginalInstanceID = pInst->InstanceID();
CVectorOutStream Out(&rNode.InstanceData, IOUtil::eBigEndian);
CScriptCooker::CookInstance(eReturns, static_cast<CScriptNode*>(*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<SCopiedNode>& CopiedNodes() const { return mCopiedNodes; }
};
#endif // CNODECOPYMIMEDATA

View File

@ -112,31 +112,33 @@ void CSceneViewport::CheckGizmoInput(const CRay& ray)
else mGizmoHovering = false; else mGizmoHovering = false;
} }
void CSceneViewport::SceneRayCast(const CRay& rkRay) SRayIntersection CSceneViewport::SceneRayCast(const CRay& rkRay)
{ {
if (mpEditor->Gizmo()->IsTransforming()) if (mpEditor->Gizmo()->IsTransforming())
{ {
ResetHover(); ResetHover();
return; return SRayIntersection();
} }
mRayIntersection = mpScene->SceneRayCast(rkRay, mViewInfo); SRayIntersection Intersect = mpScene->SceneRayCast(rkRay, mViewInfo);
if (mRayIntersection.Hit) if (Intersect.Hit)
{ {
if (mpHoverNode) if (mpHoverNode)
mpHoverNode->SetMouseHovering(false); mpHoverNode->SetMouseHovering(false);
mpHoverNode = mRayIntersection.pNode; mpHoverNode = Intersect.pNode;
mpHoverNode->SetMouseHovering(true); mpHoverNode->SetMouseHovering(true);
mHoverPoint = rkRay.PointOnRay(mRayIntersection.Distance); mHoverPoint = rkRay.PointOnRay(Intersect.Distance);
} }
else else
{ {
mHoverPoint = rkRay.PointOnRay(5.f); mHoverPoint = rkRay.PointOnRay(10.f);
ResetHover(); ResetHover();
} }
return Intersect;
} }
void CSceneViewport::ResetHover() void CSceneViewport::ResetHover()
@ -242,6 +244,8 @@ QMouseEvent CSceneViewport::CreateMouseEvent()
void CSceneViewport::FindConnectedObjects(u32 InstanceID, bool SearchOutgoing, bool SearchIncoming, QList<u32>& rIDList) void CSceneViewport::FindConnectedObjects(u32 InstanceID, bool SearchOutgoing, bool SearchIncoming, QList<u32>& rIDList)
{ {
CScriptNode *pScript = mpScene->NodeForInstanceID(InstanceID); CScriptNode *pScript = mpScene->NodeForInstanceID(InstanceID);
if (!pScript) return;
CScriptObject *pInst = pScript->Object(); CScriptObject *pInst = pScript->Object();
rIDList << InstanceID; rIDList << InstanceID;
@ -287,7 +291,7 @@ void CSceneViewport::CheckUserInput()
CheckGizmoInput(Ray); CheckGizmoInput(Ray);
if (!mpEditor->Gizmo()->IsTransforming()) if (!mpEditor->Gizmo()->IsTransforming())
SceneRayCast(Ray); mRayIntersection = SceneRayCast(Ray);
} }
else else
@ -341,7 +345,7 @@ void CSceneViewport::Paint()
void CSceneViewport::ContextMenu(QContextMenuEvent* pEvent) void CSceneViewport::ContextMenu(QContextMenuEvent* pEvent)
{ {
// mpHoverNode is cleared during mouse input, so this call is necessary. todo: better way? // mpHoverNode is cleared during mouse input, so this call is necessary. todo: better way?
SceneRayCast(CastRay()); mRayIntersection = SceneRayCast(CastRay());
// Set up actions // Set up actions
TString NodeName; TString NodeName;

View File

@ -55,7 +55,7 @@ public:
CSceneNode* HoverNode(); CSceneNode* HoverNode();
CVector3f HoverPoint(); CVector3f HoverPoint();
void CheckGizmoInput(const CRay& ray); void CheckGizmoInput(const CRay& ray);
void SceneRayCast(const CRay& ray); SRayIntersection SceneRayCast(const CRay& ray);
void ResetHover(); void ResetHover();
bool IsHoveringGizmo(); bool IsHoveringGizmo();

View File

@ -155,7 +155,9 @@ HEADERS += \
WorldEditor/CTemplateListView.h \ WorldEditor/CTemplateListView.h \
CSelectionIterator.h \ CSelectionIterator.h \
Undo/ObjReferences.h \ Undo/ObjReferences.h \
Undo/CCloneSelectionCommand.h Undo/CCloneSelectionCommand.h \
CNodeCopyMimeData.h \
Undo/CPasteNodesCommand.h
# Source Files # Source Files
SOURCES += \ SOURCES += \
@ -213,7 +215,8 @@ SOURCES += \
Undo/CEditLinkCommand.cpp \ Undo/CEditLinkCommand.cpp \
Undo/CDeleteSelectionCommand.cpp \ Undo/CDeleteSelectionCommand.cpp \
Undo/CCreateInstanceCommand.cpp \ Undo/CCreateInstanceCommand.cpp \
Undo/CCloneSelectionCommand.cpp Undo/CCloneSelectionCommand.cpp \
Undo/CPasteNodesCommand.cpp
# UI Files # UI Files
FORMS += \ FORMS += \

View File

@ -40,6 +40,7 @@ void CCloneSelectionCommand::redo()
QList<u32> ToCloneInstanceIDs; QList<u32> ToCloneInstanceIDs;
QList<u32> ClonedInstanceIDs; QList<u32> ClonedInstanceIDs;
// Clone nodes
foreach (CSceneNode *pNode, ToClone) foreach (CSceneNode *pNode, ToClone)
{ {
mpEditor->NotifyNodeAboutToBeSpawned(); mpEditor->NotifyNodeAboutToBeSpawned();
@ -55,7 +56,6 @@ void CCloneSelectionCommand::redo()
pCloneNode->SetPosition(pScript->LocalPosition()); pCloneNode->SetPosition(pScript->LocalPosition());
pCloneNode->SetRotation(pScript->LocalRotation()); pCloneNode->SetRotation(pScript->LocalRotation());
pCloneNode->SetScale(pScript->LocalScale()); pCloneNode->SetScale(pScript->LocalScale());
pCloneNode->OnLoadFinished();
ToCloneInstanceIDs << pInstance->InstanceID(); ToCloneInstanceIDs << pInstance->InstanceID();
ClonedInstanceIDs << pCloneInst->InstanceID(); ClonedInstanceIDs << pCloneInst->InstanceID();
@ -92,6 +92,10 @@ void CCloneSelectionCommand::redo()
} }
} }
// Call LoadFinished
foreach (CSceneNode *pNode, ClonedNodes)
pNode->OnLoadFinished();
mpEditor->OnLinksModified(LinkedInstances); mpEditor->OnLinksModified(LinkedInstances);
mpEditor->Selection()->SetSelectedNodes(mClonedNodes.DereferenceList()); mpEditor->Selection()->SetSelectedNodes(mClonedNodes.DereferenceList());
} }

View File

@ -4,8 +4,8 @@
#include <Core/Resource/Cooker/CScriptCooker.h> #include <Core/Resource/Cooker/CScriptCooker.h>
#include <Core/Resource/Factory/CScriptLoader.h> #include <Core/Resource/Factory/CScriptLoader.h>
CDeleteSelectionCommand::CDeleteSelectionCommand(CWorldEditor *pEditor) CDeleteSelectionCommand::CDeleteSelectionCommand(CWorldEditor *pEditor, const QString& rkCommandName /*= "Delete"*/)
: IUndoCommand("Delete") : IUndoCommand(rkCommandName)
, mpEditor(pEditor) , mpEditor(pEditor)
{ {
QSet<CLink*> Links; QSet<CLink*> Links;

View File

@ -47,7 +47,7 @@ class CDeleteSelectionCommand : public IUndoCommand
QVector<SDeletedLink> mDeletedLinks; QVector<SDeletedLink> mDeletedLinks;
public: public:
CDeleteSelectionCommand(CWorldEditor *pEditor); CDeleteSelectionCommand(CWorldEditor *pEditor, const QString& rkCommandName = "Delete");
void undo(); void undo();
void redo(); void redo();
bool AffectsCleanState() const { return true; } bool AffectsCleanState() const { return true; }

View File

@ -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<const CNodeCopyMimeData*>(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<CSceneNode*> PastedNodes = mPastedNodes.DereferenceList();
foreach (CSceneNode *pNode, PastedNodes)
{
CScriptObject *pInst = (pNode->NodeType() == eScriptNode ? static_cast<CScriptNode*>(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<CNodeCopyMimeData::SCopiedNode>& rkNodes = mpMimeData->CopiedNodes();
CScene *pScene = mpEditor->Scene();
CGameArea *pArea = mpEditor->ActiveArea();
QList<CSceneNode*> 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<CScriptNode*>(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<CScriptNode*>(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;
}

View File

@ -0,0 +1,29 @@
#ifndef CPASTENODESCOMMAND
#define CPASTENODESCOMMAND
#include "IUndoCommand.h"
#include "ObjReferences.h"
#include "Editor/CNodeCopyMimeData.h"
#include "Editor/WorldEditor/CWorldEditor.h"
#include <QClipboard>
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

View File

@ -3,6 +3,7 @@
#include "CCreateInstanceCommand.h" #include "CCreateInstanceCommand.h"
#include "CCloneSelectionCommand.h" #include "CCloneSelectionCommand.h"
#include "CPasteNodesCommand.h"
#include "CTranslateNodeCommand.h" #include "CTranslateNodeCommand.h"
#include "CRotateNodeCommand.h" #include "CRotateNodeCommand.h"

View File

@ -7,6 +7,7 @@
#include "WInstancesTab.h" #include "WInstancesTab.h"
#include "Editor/CBasicViewport.h" #include "Editor/CBasicViewport.h"
#include "Editor/CNodeCopyMimeData.h"
#include "Editor/CSelectionIterator.h" #include "Editor/CSelectionIterator.h"
#include "Editor/UICommon.h" #include "Editor/UICommon.h"
#include "Editor/PropertyEdit/CPropertyView.h" #include "Editor/PropertyEdit/CPropertyView.h"
@ -20,6 +21,7 @@
#include <Common/Log.h> #include <Common/Log.h>
#include <iostream> #include <iostream>
#include <QClipboard>
#include <QComboBox> #include <QComboBox>
#include <QFontMetrics> #include <QFontMetrics>
#include <QMessageBox> #include <QMessageBox>
@ -62,13 +64,12 @@ CWorldEditor::CWorldEditor(QWidget *parent)
mpTransformCombo->setMinimumWidth(75); mpTransformCombo->setMinimumWidth(75);
ui->MainToolBar->addActions(mGizmoActions); ui->MainToolBar->addActions(mGizmoActions);
ui->MainToolBar->addWidget(mpTransformCombo); ui->MainToolBar->addWidget(mpTransformCombo);
ui->menuEdit->insertActions(ui->ActionSelectAll, mUndoActions); ui->menuEdit->insertActions(ui->ActionCut, mUndoActions);
ui->menuEdit->insertSeparator(ui->ActionSelectAll); ui->menuEdit->insertSeparator(ui->ActionCut);
// Initialize actions // Initialize actions
addAction(ui->ActionIncrementGizmo); addAction(ui->ActionIncrementGizmo);
addAction(ui->ActionDecrementGizmo); addAction(ui->ActionDecrementGizmo);
addAction(ui->ActionDelete);
QAction *pToolBarUndo = mUndoStack.createUndoAction(this); QAction *pToolBarUndo = mUndoStack.createUndoAction(this);
pToolBarUndo->setIcon(QIcon(":/icons/Undo.png")); pToolBarUndo->setIcon(QIcon(":/icons/Undo.png"));
@ -79,6 +80,15 @@ CWorldEditor::CWorldEditor(QWidget *parent)
ui->MainToolBar->insertAction(ui->ActionLink, pToolBarRedo); ui->MainToolBar->insertAction(ui->ActionLink, pToolBarRedo);
ui->MainToolBar->insertSeparator(ui->ActionLink); 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 signals and slots
connect(ui->MainViewport, SIGNAL(ViewportClick(SRayIntersection,QMouseEvent*)), this, SLOT(OnViewportClick(SRayIntersection,QMouseEvent*))); connect(ui->MainViewport, SIGNAL(ViewportClick(SRayIntersection,QMouseEvent*)), this, SLOT(OnViewportClick(SRayIntersection,QMouseEvent*)));
connect(ui->MainViewport, SIGNAL(InputProcessed(SRayIntersection,QMouseEvent*)), this, SLOT(OnViewportInputProcessed(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(InputProcessed(SRayIntersection,QMouseEvent*)), this, SLOT(UpdateCursor()) );
connect(ui->MainViewport, SIGNAL(GizmoMoved()), this, SLOT(OnGizmoMoved())); connect(ui->MainViewport, SIGNAL(GizmoMoved()), this, SLOT(OnGizmoMoved()));
connect(ui->MainViewport, SIGNAL(CameraOrbit()), this, SLOT(UpdateCameraOrbit())); 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(SelectionTransformed()), this, SLOT(UpdateCameraOrbit()));
connect(this, SIGNAL(PickModeEntered(QCursor)), this, SLOT(OnPickModeEnter(QCursor))); connect(this, SIGNAL(PickModeEntered(QCursor)), this, SLOT(OnPickModeEnter(QCursor)));
connect(this, SIGNAL(PickModeExited()), this, SLOT(OnPickModeExit())); 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->ActionLink, SIGNAL(toggled(bool)), this, SLOT(OnLinkButtonToggled(bool)));
connect(ui->ActionUnlink, SIGNAL(triggered()), this, SLOT(OnUnlinkClicked())); connect(ui->ActionUnlink, SIGNAL(triggered()), this, SLOT(OnUnlinkClicked()));
connect(ui->ActionDelete, SIGNAL(triggered()), this, SLOT(DeleteSelection())); 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(&mUndoStack, SIGNAL(indexChanged(int)), this, SLOT(OnUndoStackIndexChanged()));
connect(ui->ActionSave, SIGNAL(triggered()), this, SLOT(Save())); connect(ui->ActionSave, SIGNAL(triggered()), this, SLOT(Save()));
@ -239,6 +253,17 @@ bool CWorldEditor::CheckUnsavedChanges()
return OkToClear; return OkToClear;
} }
bool CWorldEditor::HasAnyScriptNodesSelected() const
{
for (CSelectionIterator It(mpSelection); It; ++It)
{
if (It->NodeType() == eScriptNode)
return true;
}
return false;
}
CSceneViewport* CWorldEditor::Viewport() const CSceneViewport* CWorldEditor::Viewport() const
{ {
return ui->MainViewport; return ui->MainViewport;
@ -253,6 +278,53 @@ void CWorldEditor::NotifyNodeAboutToBeDeleted(CSceneNode *pNode)
ui->MainViewport->ResetHover(); 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<const CNodeCopyMimeData*>(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() bool CWorldEditor::Save()
{ {
TString Out = mpArea->FullSource(); TString Out = mpArea->FullSource();
@ -390,9 +462,11 @@ void CWorldEditor::SetSelectionLayer(CScriptLayer *pLayer)
void CWorldEditor::DeleteSelection() void CWorldEditor::DeleteSelection()
{ {
// note: make it only happen if there is a script node selected if (HasAnyScriptNodesSelected())
CDeleteSelectionCommand *pCmd = new CDeleteSelectionCommand(this); {
mUndoStack.push(pCmd); CDeleteSelectionCommand *pCmd = new CDeleteSelectionCommand(this);
mUndoStack.push(pCmd);
}
} }
void CWorldEditor::UpdateStatusBar() void CWorldEditor::UpdateStatusBar()
@ -574,6 +648,23 @@ void CWorldEditor::GizmoModeChanged(CGizmo::EGizmoMode mode)
} }
// ************ PRIVATE SLOTS ************ // ************ PRIVATE SLOTS ************
void CWorldEditor::OnClipboardDataModified()
{
const QMimeData *pkMimeData = qApp->clipboard()->mimeData();
bool ValidMimeData = (qobject_cast<const CNodeCopyMimeData*>(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) void CWorldEditor::OnLinkButtonToggled(bool Enabled)
{ {
if (Enabled) if (Enabled)

View File

@ -50,6 +50,7 @@ public:
void closeEvent(QCloseEvent *pEvent); void closeEvent(QCloseEvent *pEvent);
void SetArea(CWorld *pWorld, CGameArea *pArea); void SetArea(CWorld *pWorld, CGameArea *pArea);
bool CheckUnsavedChanges(); bool CheckUnsavedChanges();
bool HasAnyScriptNodesSelected() const;
inline CGameArea* ActiveArea() const { return mpArea; } inline CGameArea* ActiveArea() const { return mpArea; }
inline EGame CurrentGame() const { return mpArea ? mpArea->Version() : eUnknownVersion; } inline EGame CurrentGame() const { return mpArea ? mpArea->Version() : eUnknownVersion; }
@ -59,6 +60,9 @@ public:
public slots: public slots:
virtual void NotifyNodeAboutToBeDeleted(CSceneNode *pNode); virtual void NotifyNodeAboutToBeDeleted(CSceneNode *pNode);
void Cut();
void Copy();
void Paste();
bool Save(); bool Save();
void OnLinksModified(const QList<CScriptObject*>& rkInstances); void OnLinksModified(const QList<CScriptObject*>& rkInstances);
void OnPropertyModified(IProperty *pProp); void OnPropertyModified(IProperty *pProp);
@ -77,6 +81,9 @@ protected:
void GizmoModeChanged(CGizmo::EGizmoMode mode); void GizmoModeChanged(CGizmo::EGizmoMode mode);
private slots: private slots:
void OnClipboardDataModified();
void OnSelectionModified();
void OnLinkButtonToggled(bool Enabled); void OnLinkButtonToggled(bool Enabled);
void OnLinkClick(const SRayIntersection& rkIntersect); void OnLinkClick(const SRayIntersection& rkIntersect);
void OnLinkEnd(); void OnLinkEnd();

View File

@ -417,6 +417,11 @@
<property name="title"> <property name="title">
<string>Edit</string> <string>Edit</string>
</property> </property>
<addaction name="ActionCut"/>
<addaction name="ActionCopy"/>
<addaction name="ActionPaste"/>
<addaction name="ActionDelete"/>
<addaction name="separator"/>
<addaction name="ActionSelectAll"/> <addaction name="ActionSelectAll"/>
<addaction name="ActionInvertSelection"/> <addaction name="ActionInvertSelection"/>
</widget> </widget>
@ -753,6 +758,9 @@
</property> </property>
</action> </action>
<action name="ActionDelete"> <action name="ActionDelete">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text"> <property name="text">
<string>Delete</string> <string>Delete</string>
</property> </property>
@ -760,6 +768,30 @@
<string>Del</string> <string>Del</string>
</property> </property>
</action> </action>
<action name="ActionCut">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Cut</string>
</property>
</action>
<action name="ActionCopy">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Copy</string>
</property>
</action>
<action name="ActionPaste">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Paste</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

View File

@ -22,6 +22,9 @@ public:
void SetEditor(CWorldEditor *pEditor); void SetEditor(CWorldEditor *pEditor);
void SetMaster(CMasterTemplate *pMaster); void SetMaster(CMasterTemplate *pMaster);
// Accessors
inline CScriptLayer* SpawnLayer() const { return mpSpawnLayer; }
public slots: public slots:
void OnLayersChanged(); void OnLayersChanged();
void OnSpawnLayerChanged(int LayerIndex); void OnSpawnLayerChanged(int LayerIndex);

View File

@ -6,7 +6,7 @@ CMemoryInStream::CMemoryInStream()
mDataSize = 0; mDataSize = 0;
mPos = 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); 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<char*>(pData); mpDataStart = static_cast<const char*>(pData);
mDataSize = Size; mDataSize = Size;
mPos = 0; mPos = 0;
mDataEndianness = DataEndianness; mDataEndianness = DataEndianness;
@ -91,12 +91,12 @@ void CMemoryInStream::SetSize(unsigned long Size)
mPos = mDataSize; mPos = mDataSize;
} }
void* CMemoryInStream::Data() const const void* CMemoryInStream::Data() const
{ {
return mpDataStart; return mpDataStart;
} }
void* CMemoryInStream::DataAtPosition() const const void* CMemoryInStream::DataAtPosition() const
{ {
return mpDataStart + mPos; return mpDataStart + mPos;
} }

View File

@ -6,15 +6,15 @@
class CMemoryInStream : public IInputStream class CMemoryInStream : public IInputStream
{ {
char *mpDataStart; const char *mpDataStart;
long mDataSize; long mDataSize;
long mPos; long mPos;
public: public:
CMemoryInStream(); CMemoryInStream();
CMemoryInStream(void *pData, unsigned long Size, IOUtil::EEndianness dataEndianness); CMemoryInStream(const void *pData, unsigned long Size, IOUtil::EEndianness dataEndianness);
~CMemoryInStream(); ~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); void ReadBytes(void *pDst, unsigned long Count);
bool Seek(long offset, long Origin); bool Seek(long offset, long Origin);
@ -23,8 +23,8 @@ public:
bool IsValid() const; bool IsValid() const;
long Size() const; long Size() const;
void SetSize(unsigned long Size); void SetSize(unsigned long Size);
void* Data() const; const void* Data() const;
void* DataAtPosition() const; const void* DataAtPosition() const;
}; };
#endif // CMEMORYINSTREAM_H #endif // CMEMORYINSTREAM_H