Added support for script instance copy/paste in the World Editor
This commit is contained in:
parent
d961545309
commit
5009c08c87
|
@ -64,5 +64,6 @@ SRayIntersection CRayCollisionTester::TestNodes(const SViewInfo& ViewInfo)
|
|||
}
|
||||
}
|
||||
|
||||
if (Result.Hit) Result.HitPoint = mRay.PointOnRay(Result.Distance);
|
||||
return Result;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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<u32>& 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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 += \
|
||||
|
|
|
@ -40,6 +40,7 @@ void CCloneSelectionCommand::redo()
|
|||
QList<u32> ToCloneInstanceIDs;
|
||||
QList<u32> 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());
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
#include <Core/Resource/Cooker/CScriptCooker.h>
|
||||
#include <Core/Resource/Factory/CScriptLoader.h>
|
||||
|
||||
CDeleteSelectionCommand::CDeleteSelectionCommand(CWorldEditor *pEditor)
|
||||
: IUndoCommand("Delete")
|
||||
CDeleteSelectionCommand::CDeleteSelectionCommand(CWorldEditor *pEditor, const QString& rkCommandName /*= "Delete"*/)
|
||||
: IUndoCommand(rkCommandName)
|
||||
, mpEditor(pEditor)
|
||||
{
|
||||
QSet<CLink*> Links;
|
||||
|
|
|
@ -47,7 +47,7 @@ class CDeleteSelectionCommand : public IUndoCommand
|
|||
QVector<SDeletedLink> mDeletedLinks;
|
||||
|
||||
public:
|
||||
CDeleteSelectionCommand(CWorldEditor *pEditor);
|
||||
CDeleteSelectionCommand(CWorldEditor *pEditor, const QString& rkCommandName = "Delete");
|
||||
void undo();
|
||||
void redo();
|
||||
bool AffectsCleanState() const { return true; }
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "CCreateInstanceCommand.h"
|
||||
#include "CCloneSelectionCommand.h"
|
||||
#include "CPasteNodesCommand.h"
|
||||
|
||||
#include "CTranslateNodeCommand.h"
|
||||
#include "CRotateNodeCommand.h"
|
||||
|
|
|
@ -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 <Common/Log.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <QClipboard>
|
||||
#include <QComboBox>
|
||||
#include <QFontMetrics>
|
||||
#include <QMessageBox>
|
||||
|
@ -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<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()
|
||||
{
|
||||
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<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)
|
||||
{
|
||||
if (Enabled)
|
||||
|
|
|
@ -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<CScriptObject*>& 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();
|
||||
|
|
|
@ -417,6 +417,11 @@
|
|||
<property name="title">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
<addaction name="ActionCut"/>
|
||||
<addaction name="ActionCopy"/>
|
||||
<addaction name="ActionPaste"/>
|
||||
<addaction name="ActionDelete"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="ActionSelectAll"/>
|
||||
<addaction name="ActionInvertSelection"/>
|
||||
</widget>
|
||||
|
@ -753,6 +758,9 @@
|
|||
</property>
|
||||
</action>
|
||||
<action name="ActionDelete">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
|
@ -760,6 +768,30 @@
|
|||
<string>Del</string>
|
||||
</property>
|
||||
</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>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<char*>(pData);
|
||||
mpDataStart = static_cast<const char*>(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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue