Changes made in the tweak editor are now correctly applied to the tweak data & are undo/redo supported
This commit is contained in:
parent
e8d3224088
commit
7b005d7ebd
|
@ -17,7 +17,7 @@ protected:
|
|||
public:
|
||||
virtual void SerializeValue(void* pData, IArchive& Arc) const
|
||||
{
|
||||
Value(pData).Serialize(Arc);
|
||||
ValueRef(pData).Serialize(Arc);
|
||||
}
|
||||
|
||||
virtual const char* HashableTypeName() const
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
|
||||
virtual void SerializeValue(void* pData, IArchive& Arc) const
|
||||
{
|
||||
Value(pData).Serialize(Arc);
|
||||
ValueRef(pData).Serialize(Arc);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "CTweakEditor.h"
|
||||
#include "ui_CTweakEditor.h"
|
||||
#include "Editor/Undo/IUndoCommand.h"
|
||||
|
||||
CTweakEditor::CTweakEditor(QWidget* pParent)
|
||||
: IEditor(pParent)
|
||||
|
@ -9,9 +10,11 @@ CTweakEditor::CTweakEditor(QWidget* pParent)
|
|||
{
|
||||
mpUI->setupUi(this);
|
||||
mpUI->TweakTabs->setExpanding(false);
|
||||
SET_WINDOWTITLE_APPVARS("%APP_FULL_NAME% - Tweak Editor");
|
||||
mpUI->ToolBar->addSeparator();
|
||||
AddUndoActions(mpUI->ToolBar);
|
||||
SET_WINDOWTITLE_APPVARS("%APP_FULL_NAME% - Tweak Editor[*]");
|
||||
|
||||
connect(mpUI->TweakTabs, SIGNAL(currentChanged(int)), this, SLOT(SetActiveTweakIndex(int)));
|
||||
connect(mpUI->TweakTabs, SIGNAL(currentChanged(int)), this, SLOT(OnTweakTabClicked(int)));
|
||||
}
|
||||
|
||||
CTweakEditor::~CTweakEditor()
|
||||
|
@ -64,6 +67,34 @@ void CTweakEditor::SetActiveTweakIndex(int Index)
|
|||
}
|
||||
}
|
||||
|
||||
void CTweakEditor::OnTweakTabClicked(int Index)
|
||||
{
|
||||
/** Internal undo command for changing tabs */
|
||||
class CSetTweakIndexCommand : public IUndoCommand
|
||||
{
|
||||
CTweakEditor* mpEditor;
|
||||
int mOldIndex, mNewIndex;
|
||||
|
||||
public:
|
||||
CSetTweakIndexCommand(CTweakEditor* pEditor, int OldIndex, int NewIndex)
|
||||
: IUndoCommand("Change Tab")
|
||||
, mpEditor(pEditor)
|
||||
, mOldIndex(OldIndex)
|
||||
, mNewIndex(NewIndex)
|
||||
{}
|
||||
|
||||
virtual void undo() override { mpEditor->SetActiveTweakIndex(mOldIndex); }
|
||||
virtual void redo() override { mpEditor->SetActiveTweakIndex(mNewIndex); }
|
||||
virtual bool AffectsCleanState() const { return false; }
|
||||
};
|
||||
|
||||
if (Index != mCurrentTweakIndex)
|
||||
{
|
||||
CSetTweakIndexCommand* pCommand = new CSetTweakIndexCommand(this, mCurrentTweakIndex, Index);
|
||||
UndoStack().push(pCommand);
|
||||
}
|
||||
}
|
||||
|
||||
void CTweakEditor::OnProjectChanged(CGameProject* pNewProject)
|
||||
{
|
||||
// Close and clear tabs
|
||||
|
@ -78,8 +109,8 @@ void CTweakEditor::OnProjectChanged(CGameProject* pNewProject)
|
|||
mpUI->TweakTabs->removeTab(0);
|
||||
}
|
||||
|
||||
mpUI->TweakTabs->blockSignals(false);
|
||||
mTweakAssets.clear();
|
||||
UndoStack().clear();
|
||||
|
||||
// Create tweak list
|
||||
if (pNewProject != nullptr)
|
||||
|
@ -105,4 +136,6 @@ void CTweakEditor::OnProjectChanged(CGameProject* pNewProject)
|
|||
|
||||
SetActiveTweakIndex(0);
|
||||
}
|
||||
|
||||
mpUI->TweakTabs->blockSignals(false);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ public:
|
|||
public slots:
|
||||
void SetActiveTweakData(CTweakData* pTweakData);
|
||||
void SetActiveTweakIndex(int Index);
|
||||
void OnTweakTabClicked(int Index);
|
||||
void OnProjectChanged(CGameProject* pNewProject);
|
||||
};
|
||||
|
||||
|
|
|
@ -204,7 +204,8 @@ HEADERS += \
|
|||
StringEditor/CStringListModel.h \
|
||||
StringEditor/CStringDelegate.h \
|
||||
CCustomDelegate.h \
|
||||
CTweakEditor.h
|
||||
CTweakEditor.h \
|
||||
Undo/CEditIntrinsicPropertyCommand.h
|
||||
|
||||
# Source Files
|
||||
SOURCES += \
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#include "IEditor.h"
|
||||
|
||||
#include "Editor/Undo/IUndoCommand.h"
|
||||
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QToolBar>
|
||||
|
@ -19,6 +21,8 @@ IEditor::IEditor(QWidget* pParent)
|
|||
pRedoAction->setIcon(QIcon(":/icons/Redo.png"));
|
||||
mUndoActions.push_back(pUndoAction);
|
||||
mUndoActions.push_back(pRedoAction);
|
||||
|
||||
connect(&mUndoStack, SIGNAL(indexChanged(int)), this, SLOT(OnUndoStackIndexChanged()));
|
||||
}
|
||||
|
||||
QUndoStack& IEditor::UndoStack()
|
||||
|
@ -26,12 +30,12 @@ QUndoStack& IEditor::UndoStack()
|
|||
return mUndoStack;
|
||||
}
|
||||
|
||||
void IEditor::AddUndoActions(QToolBar* pToolBar, QAction* pBefore)
|
||||
void IEditor::AddUndoActions(QToolBar* pToolBar, QAction* pBefore /*= 0*/)
|
||||
{
|
||||
pToolBar->insertActions(pBefore, mUndoActions);
|
||||
}
|
||||
|
||||
void IEditor::AddUndoActions(QMenu* pMenu, QAction* pBefore)
|
||||
void IEditor::AddUndoActions(QMenu* pMenu, QAction* pBefore /*= 0*/)
|
||||
{
|
||||
pMenu->insertActions(pBefore, mUndoActions);
|
||||
}
|
||||
|
@ -49,7 +53,10 @@ bool IEditor::CheckUnsavedChanges()
|
|||
OkToClear = Save();
|
||||
|
||||
else if (Result == QMessageBox::No)
|
||||
{
|
||||
mUndoStack.setIndex(0); // Revert all changes
|
||||
OkToClear = true;
|
||||
}
|
||||
|
||||
else if (Result == QMessageBox::Cancel)
|
||||
OkToClear = false;
|
||||
|
@ -83,3 +90,59 @@ bool IEditor::SaveAndRepack()
|
|||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
|
||||
void IEditor::OnUndoStackIndexChanged()
|
||||
{
|
||||
// Check the commands that have been executed on the undo stack and find out whether any of them affect the clean state.
|
||||
// This is to prevent commands like select/deselect from altering the clean state.
|
||||
int CurrentIndex = mUndoStack.index();
|
||||
int CleanIndex = mUndoStack.cleanIndex();
|
||||
|
||||
if (CleanIndex == -1)
|
||||
{
|
||||
if (!isWindowModified())
|
||||
mUndoStack.setClean();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (CurrentIndex == CleanIndex)
|
||||
setWindowModified(false);
|
||||
|
||||
else
|
||||
{
|
||||
bool IsClean = true;
|
||||
int LowIndex = (CurrentIndex > CleanIndex ? CleanIndex : CurrentIndex);
|
||||
int HighIndex = (CurrentIndex > CleanIndex ? CurrentIndex - 1 : CleanIndex - 1);
|
||||
|
||||
for (int i = LowIndex; i <= HighIndex; i++)
|
||||
{
|
||||
const QUndoCommand *pkQCmd = mUndoStack.command(i);
|
||||
|
||||
if (const IUndoCommand* pkCmd = dynamic_cast<const IUndoCommand*>(pkQCmd))
|
||||
{
|
||||
if (pkCmd->AffectsCleanState())
|
||||
IsClean = false;
|
||||
}
|
||||
|
||||
else if (pkQCmd->childCount() > 0)
|
||||
{
|
||||
for (int ChildIdx = 0; ChildIdx < pkQCmd->childCount(); ChildIdx++)
|
||||
{
|
||||
const IUndoCommand *pkCmd = static_cast<const IUndoCommand*>(pkQCmd->child(ChildIdx));
|
||||
|
||||
if (pkCmd->AffectsCleanState())
|
||||
{
|
||||
IsClean = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsClean) break;
|
||||
}
|
||||
|
||||
setWindowModified(!IsClean);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ protected:
|
|||
public:
|
||||
IEditor(QWidget* pParent);
|
||||
QUndoStack& UndoStack();
|
||||
void AddUndoActions(QToolBar* pToolBar, QAction* pBefore);
|
||||
void AddUndoActions(QMenu* pMenu, QAction* pBefore);
|
||||
void AddUndoActions(QToolBar* pToolBar, QAction* pBefore = 0);
|
||||
void AddUndoActions(QMenu* pMenu, QAction* pBefore = 0);
|
||||
bool CheckUnsavedChanges();
|
||||
|
||||
/** QMainWindow overrides */
|
||||
|
@ -39,12 +39,12 @@ public slots:
|
|||
// Default implementation for editor windows that do not support resaving assets.
|
||||
// This should not be called.
|
||||
warnf("Base IEditor::Save() implementation called. Changes will not be saved.");
|
||||
ASSERT(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Non-virtual slots */
|
||||
bool SaveAndRepack();
|
||||
void OnUndoStackIndexChanged();
|
||||
|
||||
signals:
|
||||
void Closed();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "Editor/UICommon.h"
|
||||
#include "Editor/Undo/CEditScriptPropertyCommand.h"
|
||||
#include "Editor/Undo/CEditIntrinsicPropertyCommand.h"
|
||||
#include "Editor/Undo/CResizeScriptArrayCommand.h"
|
||||
#include "Editor/Widgets/CResourceSelector.h"
|
||||
#include "Editor/Widgets/WColorPicker.h"
|
||||
|
@ -27,12 +28,13 @@
|
|||
|
||||
CPropertyDelegate::CPropertyDelegate(QObject* pParent /*= 0*/)
|
||||
: QStyledItemDelegate(pParent)
|
||||
, mpEditor(nullptr)
|
||||
, mpModel(nullptr)
|
||||
, mInRelayWidgetEdit(false)
|
||||
, mEditInProgress(false)
|
||||
, mRelaysBlocked(false)
|
||||
{
|
||||
mpEditor = gpEdApp->WorldEditor();
|
||||
mpEditor = UICommon::FindAncestor<IEditor>(this);
|
||||
}
|
||||
|
||||
void CPropertyDelegate::SetPropertyModel(CPropertyModel* pModel)
|
||||
|
@ -361,16 +363,28 @@ void CPropertyDelegate::setModelData(QWidget *pEditor, QAbstractItemModel* /*pMo
|
|||
if (pProp)
|
||||
{
|
||||
EPropertyType Type = mpModel->GetEffectiveFieldType(pProp);
|
||||
CScriptObject* pObject = mpModel->GetScriptObject();
|
||||
|
||||
if (!pObject)
|
||||
{
|
||||
QVector<void*> DataPointers;
|
||||
DataPointers << pData;
|
||||
pCommand = new CEditIntrinsicPropertyCommand(pProp, DataPointers, mpModel, rkIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
QVector<CScriptObject*> Objects;
|
||||
Objects << mpModel->GetScriptObject();
|
||||
Objects << pObject;
|
||||
|
||||
pCommand = (Type != EPropertyType::Array) ?
|
||||
new CEditScriptPropertyCommand(pProp, Objects, mpModel, rkIndex) :
|
||||
new CResizeScriptArrayCommand (pProp, Objects, mpModel, rkIndex);
|
||||
}
|
||||
|
||||
pCommand->SaveOldData();
|
||||
|
||||
if (Type != EPropertyType::Array)
|
||||
{
|
||||
// TODO: support this for non script object properties
|
||||
pCommand = new CEditScriptPropertyCommand(pProp, mpEditor, Objects, rkIndex);
|
||||
pCommand->SaveOldData();
|
||||
|
||||
// Handle sub-properties of flags and animation sets
|
||||
if (rkIndex.internalId() & 0x80000000)
|
||||
{
|
||||
|
@ -482,9 +496,6 @@ void CPropertyDelegate::setModelData(QWidget *pEditor, QAbstractItemModel* /*pMo
|
|||
// Array
|
||||
else
|
||||
{
|
||||
pCommand = new CResizeScriptArrayCommand(pProp, mpEditor, Objects, mpModel, rkIndex);
|
||||
pCommand->SaveOldData();
|
||||
|
||||
WIntegralSpinBox* pSpinBox = static_cast<WIntegralSpinBox*>(pEditor);
|
||||
CArrayProperty* pArray = static_cast<CArrayProperty*>(pProp);
|
||||
int OldCount = pArray->ArrayCount(pData);
|
||||
|
@ -560,9 +571,9 @@ QWidget* CPropertyDelegate::CreateCharacterEditor(QWidget *pParent, const QModel
|
|||
pSelector->SetFrameVisible(false);
|
||||
|
||||
if (Params.Version() <= EGame::Echoes)
|
||||
pSelector->SetTypeFilter(mpEditor->CurrentGame(), "ANCS");
|
||||
pSelector->SetTypeFilter(gpEdApp->CurrentGame(), "ANCS");
|
||||
else
|
||||
pSelector->SetTypeFilter(mpEditor->CurrentGame(), "CHAR");
|
||||
pSelector->SetTypeFilter(gpEdApp->CurrentGame(), "CHAR");
|
||||
|
||||
CONNECT_RELAY(pSelector, rkIndex, ResourceChanged(CResourceEntry*));
|
||||
return pSelector;
|
||||
|
@ -626,7 +637,7 @@ void CPropertyDelegate::SetCharacterModelData(QWidget *pEditor, const QModelInde
|
|||
if (Type == EPropertyType::Asset)
|
||||
{
|
||||
CResourceEntry *pEntry = static_cast<CResourceSelector*>(pEditor)->Entry();
|
||||
Params.SetResource( pEntry ? pEntry->ID() : CAssetID::InvalidID(mpEditor->CurrentGame()) );
|
||||
Params.SetResource( pEntry ? pEntry->ID() : CAssetID::InvalidID(gpEdApp->CurrentGame()) );
|
||||
}
|
||||
|
||||
else if (Type == EPropertyType::Enum || Type == EPropertyType::Choice)
|
||||
|
|
|
@ -9,7 +9,7 @@ class CPropertyDelegate : public QStyledItemDelegate
|
|||
{
|
||||
Q_OBJECT
|
||||
|
||||
CWorldEditor *mpEditor;
|
||||
IEditor* mpEditor;
|
||||
CPropertyModel* mpModel;
|
||||
bool mInRelayWidgetEdit;
|
||||
mutable bool mEditInProgress;
|
||||
|
@ -17,6 +17,7 @@ class CPropertyDelegate : public QStyledItemDelegate
|
|||
|
||||
public:
|
||||
CPropertyDelegate(QObject* pParent = 0);
|
||||
void SetEditor(IEditor* pEditor);
|
||||
void SetPropertyModel(CPropertyModel* pModel);
|
||||
|
||||
virtual QWidget* createEditor(QWidget* pParent, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const;
|
||||
|
|
|
@ -39,9 +39,6 @@ CPropertyView::CPropertyView(QWidget *pParent)
|
|||
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(CreateContextMenu(QPoint)));
|
||||
connect(mpModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(SetPersistentEditors(QModelIndex)));
|
||||
connect(mpModel, SIGNAL(PropertyModified(const QModelIndex&)), this, SLOT(OnPropertyModified(const QModelIndex&)));
|
||||
|
||||
mpEditor = gpEdApp->WorldEditor();
|
||||
connect(mpEditor, SIGNAL(PropertyModified(CScriptObject*,IProperty*)), mpModel, SLOT(NotifyPropertyModified(CScriptObject*,IProperty*)));
|
||||
}
|
||||
|
||||
void CPropertyView::setModel(QAbstractItemModel *pModel)
|
||||
|
@ -110,7 +107,7 @@ void CPropertyView::SetIntrinsicProperties(CStructRef InProperties)
|
|||
void CPropertyView::SetInstance(CScriptObject *pObj)
|
||||
{
|
||||
mpObject = pObj;
|
||||
mpModel->SetBoldModifiedProperties(mpEditor ? (mpEditor->CurrentGame() > EGame::Prime) : true);
|
||||
mpModel->SetBoldModifiedProperties(gpEdApp->CurrentGame() > EGame::Prime);
|
||||
|
||||
if (pObj)
|
||||
mpModel->ConfigureScript(pObj->Area()->Entry()->Project(), pObj->Template()->Properties(), pObj);
|
||||
|
@ -129,7 +126,7 @@ void CPropertyView::SetInstance(CScriptObject *pObj)
|
|||
void CPropertyView::UpdateEditorProperties(const QModelIndex& rkParent)
|
||||
{
|
||||
// Check what game this is
|
||||
EGame Game = mpEditor->CurrentGame();
|
||||
EGame Game = gpEdApp->CurrentGame();
|
||||
|
||||
// Iterate over all properties and update if they're an editor property.
|
||||
for (int iRow = 0; iRow < mpModel->rowCount(rkParent); iRow++)
|
||||
|
@ -245,6 +242,10 @@ void CPropertyView::OnPropertyModified(const QModelIndex& rkIndex)
|
|||
ClosePersistentEditors(rkIndex);
|
||||
SetPersistentEditors(rkIndex);
|
||||
}
|
||||
|
||||
scrollTo(rkIndex);
|
||||
emit PropertyModified(rkIndex);
|
||||
emit PropertyModified(pProperty);
|
||||
}
|
||||
|
||||
void CPropertyView::RefreshView()
|
||||
|
@ -268,7 +269,7 @@ void CPropertyView::CreateContextMenu(const QPoint& rkPos)
|
|||
Menu.addAction(mpEditTemplateAction);
|
||||
}
|
||||
|
||||
if (mpEditor->CurrentGame() >= EGame::EchoesDemo)
|
||||
if (gpEdApp->CurrentGame() >= EGame::EchoesDemo)
|
||||
{
|
||||
Menu.addAction(mpShowNameValidityAction);
|
||||
}
|
||||
|
@ -305,7 +306,8 @@ void CPropertyView::ToggleShowNameValidity(bool ShouldShow)
|
|||
|
||||
void CPropertyView::EditPropertyTemplate()
|
||||
{
|
||||
CTemplateEditDialog Dialog(mpMenuProperty, mpEditor);
|
||||
QMainWindow* pParentWindow = UICommon::FindAncestor<QMainWindow>(this);
|
||||
CTemplateEditDialog Dialog(mpMenuProperty, pParentWindow);
|
||||
connect(&Dialog, SIGNAL(PerformedTypeConversion()), this, SLOT(RefreshView()));
|
||||
Dialog.exec();
|
||||
}
|
||||
|
@ -313,21 +315,21 @@ void CPropertyView::EditPropertyTemplate()
|
|||
|
||||
void CPropertyView::GenerateNamesForProperty()
|
||||
{
|
||||
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog();
|
||||
CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
|
||||
pDialog->AddToIDPool(mpMenuProperty);
|
||||
pDialog->show();
|
||||
}
|
||||
|
||||
void CPropertyView::GenerateNamesForSiblings()
|
||||
{
|
||||
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog();
|
||||
CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
|
||||
pDialog->AddChildrenToIDPool(mpMenuProperty->Parent(), false);
|
||||
pDialog->show();
|
||||
}
|
||||
|
||||
void CPropertyView::GenerateNamesForChildren()
|
||||
{
|
||||
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog();
|
||||
CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
|
||||
pDialog->AddChildrenToIDPool(mpMenuProperty, false);
|
||||
pDialog->show();
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ class CPropertyView : public QTreeView
|
|||
{
|
||||
Q_OBJECT
|
||||
|
||||
CWorldEditor *mpEditor;
|
||||
CPropertyModel* mpModel;
|
||||
CPropertyDelegate* mpDelegate;
|
||||
CScriptObject* mpObject;
|
||||
|
@ -47,6 +46,10 @@ public slots:
|
|||
void GenerateNamesForProperty();
|
||||
void GenerateNamesForSiblings();
|
||||
void GenerateNamesForChildren();
|
||||
|
||||
signals:
|
||||
void PropertyModified(const QModelIndex& kIndex);
|
||||
void PropertyModified(IProperty* pProperty);
|
||||
};
|
||||
|
||||
#endif // CPROPERTYVIEW_H
|
||||
|
|
|
@ -46,6 +46,23 @@ QWindow* FindWidgetWindowHandle(QWidget *pWidget);
|
|||
void OpenContainingFolder(const QString& rkPath);
|
||||
bool OpenInExternalApplication(const QString& rkPath);
|
||||
|
||||
// Searches the widget's ancestry tree to find an ancestor of type ObjectT.
|
||||
// ObjectT must be a QObject subclass.
|
||||
template<typename ObjectT>
|
||||
ObjectT* FindAncestor(QObject* pObject)
|
||||
{
|
||||
for (QObject* pParent = pObject->parent(); pParent; pParent = pParent->parent())
|
||||
{
|
||||
ObjectT* pCasted = qobject_cast<ObjectT*>(pParent);
|
||||
|
||||
if (pCasted)
|
||||
{
|
||||
return pCasted;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// TString/TWideString <-> QString
|
||||
inline QString ToQString(const TString& rkStr)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef CEDITINTRINSICPROPERTYCOMMAND_H
|
||||
#define CEDITINTRINSICPROPERTYCOMMAND_H
|
||||
|
||||
#include "IEditPropertyCommand.h"
|
||||
|
||||
class CEditIntrinsicPropertyCommand : public IEditPropertyCommand
|
||||
{
|
||||
protected:
|
||||
QVector<void*> mDataPointers;
|
||||
|
||||
public:
|
||||
CEditIntrinsicPropertyCommand(IProperty* pProperty,
|
||||
const QVector<void*>& kDataPointers,
|
||||
CPropertyModel* pModel,
|
||||
QModelIndex Index = QModelIndex(),
|
||||
const QString& kCommandName = "Edit Property")
|
||||
: IEditPropertyCommand(pProperty, pModel, Index, kCommandName)
|
||||
, mDataPointers(kDataPointers)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void GetObjectDataPointers(QVector<void*>& rOutPointers) const override
|
||||
{
|
||||
rOutPointers = mDataPointers;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CEDITINTRINSICPROPERTYCOMMAND_H
|
|
@ -9,72 +9,41 @@ class CEditScriptPropertyCommand : public IEditPropertyCommand
|
|||
{
|
||||
protected:
|
||||
QVector<CInstancePtr> mInstances;
|
||||
CWorldEditor* mpEditor;
|
||||
QModelIndex mIndex;
|
||||
|
||||
public:
|
||||
CEditScriptPropertyCommand(IProperty* pProperty,
|
||||
CWorldEditor* pEditor,
|
||||
const QVector<CScriptObject*>& rkInstances,
|
||||
const QVector<CScriptObject*>& kInstances,
|
||||
CPropertyModel* pModel,
|
||||
QModelIndex Index = QModelIndex(),
|
||||
const QString& rkCommandName = "Edit Property")
|
||||
: IEditPropertyCommand(pProperty, rkCommandName)
|
||||
, mpEditor(pEditor)
|
||||
const QString& kCommandName = "Edit Property")
|
||||
: IEditPropertyCommand(pProperty, pModel, Index, kCommandName)
|
||||
, mIndex(Index)
|
||||
{
|
||||
// If the property being passed in is part of an array archetype, then we MUST have a QModelIndex.
|
||||
// Without the index, there's no way to identify the correct child being edited.
|
||||
if (!Index.isValid() && pProperty && pProperty->IsArrayArchetype())
|
||||
{
|
||||
while (pProperty && pProperty->IsArrayArchetype())
|
||||
{
|
||||
pProperty = pProperty->Parent();
|
||||
}
|
||||
ASSERT(pProperty && !pProperty->IsArrayArchetype());
|
||||
}
|
||||
|
||||
// Convert CScriptObject pointers to CInstancePtrs
|
||||
mInstances.reserve( rkInstances.size() );
|
||||
mInstances.reserve( kInstances.size() );
|
||||
|
||||
for (int i = 0; i < rkInstances.size(); i++)
|
||||
mInstances.push_back( CInstancePtr(rkInstances[i]) );
|
||||
for (int i = 0; i < kInstances.size(); i++)
|
||||
mInstances.push_back( CInstancePtr(kInstances[i]) );
|
||||
}
|
||||
|
||||
virtual void GetObjectDataPointers(QVector<void*>& rOutPointers) const override
|
||||
virtual void GetObjectDataPointers(QVector<void*>& OutPointers) const override
|
||||
{
|
||||
// todo: support multiple objects being edited at once on the property view
|
||||
if (mIndex.isValid())
|
||||
{
|
||||
ASSERT(mInstances.size() == 1);
|
||||
rOutPointers << mInstances[0]->PropertyData();
|
||||
OutPointers << mInstances[0]->PropertyData();
|
||||
return;
|
||||
}
|
||||
|
||||
// grab instance pointers
|
||||
ASSERT(!mpProperty->IsArrayArchetype());
|
||||
|
||||
rOutPointers.resize(mInstances.size());
|
||||
OutPointers.resize(mInstances.size());
|
||||
|
||||
for (int i = 0; i < mInstances.size(); i++)
|
||||
rOutPointers[i] = mInstances[i]->PropertyData();
|
||||
}
|
||||
|
||||
virtual void undo() override
|
||||
{
|
||||
IEditPropertyCommand::undo();
|
||||
NotifyWorldEditor();
|
||||
}
|
||||
|
||||
virtual void redo() override
|
||||
{
|
||||
IEditPropertyCommand::redo();
|
||||
NotifyWorldEditor();
|
||||
}
|
||||
|
||||
void NotifyWorldEditor()
|
||||
{
|
||||
for (int InstanceIdx = 0; InstanceIdx < mInstances.size(); InstanceIdx++)
|
||||
mpEditor->OnPropertyModified(*mInstances[InstanceIdx], mpProperty);
|
||||
OutPointers[i] = mInstances[i]->PropertyData();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -5,31 +5,21 @@
|
|||
|
||||
class CResizeScriptArrayCommand : public CEditScriptPropertyCommand
|
||||
{
|
||||
/** Property model the edit was performed on */
|
||||
CPropertyModel* mpModel;
|
||||
|
||||
/** Old/new model row counts; we store this here to support editing arrays on multiple instances at once */
|
||||
int mOldRowCount;
|
||||
int mNewRowCount;
|
||||
|
||||
public:
|
||||
CResizeScriptArrayCommand(IProperty* pProperty,
|
||||
CWorldEditor* pEditor,
|
||||
const QVector<CScriptObject*>& rkInstances,
|
||||
CPropertyModel* pModel = nullptr,
|
||||
CPropertyModel* pModel,
|
||||
QModelIndex Index = QModelIndex(),
|
||||
const QString& rkCommandName = "Resize Array"
|
||||
)
|
||||
: CEditScriptPropertyCommand(pProperty, pEditor, rkInstances, Index, rkCommandName)
|
||||
, mpModel(nullptr)
|
||||
: CEditScriptPropertyCommand(pProperty, rkInstances, pModel, Index, rkCommandName)
|
||||
, mOldRowCount(-1)
|
||||
, mNewRowCount(-1)
|
||||
{
|
||||
if (Index.isValid())
|
||||
{
|
||||
ASSERT(pModel != nullptr);
|
||||
mpModel = pModel;
|
||||
}
|
||||
}
|
||||
|
||||
bool mergeWith(const QUndoCommand *pkOther)
|
||||
|
@ -61,6 +51,7 @@ public:
|
|||
// This is why we need to check the array's actual current size instead of assuming it will match one of the arrays
|
||||
void undo()
|
||||
{
|
||||
//@todo verify, do we need to fully override undo()?
|
||||
if (mpModel)
|
||||
{
|
||||
mpModel->ArrayAboutToBeResized(mIndex, mOldRowCount);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#include "IEditPropertyCommand.h"
|
||||
#include "Editor/CEditorApplication.h"
|
||||
#include "Editor/WorldEditor/CWorldEditor.h"
|
||||
|
||||
/** Save the current state of the object properties to the given data buffer */
|
||||
void IEditPropertyCommand::SaveObjectStateToArray(std::vector<char>& rVector)
|
||||
|
@ -31,14 +33,36 @@ void IEditPropertyCommand::RestoreObjectStateFromArray(std::vector<char>& rArray
|
|||
|
||||
IEditPropertyCommand::IEditPropertyCommand(
|
||||
IProperty* pProperty,
|
||||
const QString& rkCommandName /*= "Edit Property"*/
|
||||
CPropertyModel* pModel,
|
||||
const QModelIndex& kIndex,
|
||||
const QString& kCommandName /*= "Edit Property"*/
|
||||
)
|
||||
: IUndoCommand(rkCommandName)
|
||||
: IUndoCommand(kCommandName)
|
||||
, mpProperty(pProperty)
|
||||
, mpModel(pModel)
|
||||
, mIndex(kIndex)
|
||||
, mSavedOldData(false)
|
||||
, mSavedNewData(false)
|
||||
{
|
||||
ASSERT(mpProperty);
|
||||
|
||||
if (!mIndex.isValid())
|
||||
{
|
||||
// If the property being passed in is part of an array archetype, then we MUST have a QModelIndex.
|
||||
// Without the index, there's no way to identify the correct child being edited.
|
||||
// So if we don't have an index, we need to serialize the entire array property.
|
||||
if (mpProperty->IsArrayArchetype())
|
||||
{
|
||||
while (mpProperty && mpProperty->IsArrayArchetype())
|
||||
{
|
||||
mpProperty = mpProperty->Parent();
|
||||
}
|
||||
ASSERT(mpProperty && !mpProperty->IsArrayArchetype());
|
||||
}
|
||||
|
||||
// Now we can fetch the index from the model
|
||||
mIndex = mpModel->IndexForProperty(mpProperty);
|
||||
}
|
||||
}
|
||||
|
||||
void IEditPropertyCommand::SaveOldData()
|
||||
|
@ -108,12 +132,30 @@ void IEditPropertyCommand::undo()
|
|||
ASSERT(mSavedOldData && mSavedNewData);
|
||||
RestoreObjectStateFromArray(mOldData);
|
||||
mCommandEnded = true;
|
||||
|
||||
if (mpModel && mIndex.isValid())
|
||||
{
|
||||
mpModel->NotifyPropertyModified(mIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
gpEdApp->WorldEditor()->OnPropertyModified(mpProperty);
|
||||
}
|
||||
}
|
||||
|
||||
void IEditPropertyCommand::redo()
|
||||
{
|
||||
ASSERT(mSavedOldData && mSavedNewData);
|
||||
RestoreObjectStateFromArray(mNewData);
|
||||
|
||||
if (mpModel && mIndex.isValid())
|
||||
{
|
||||
mpModel->NotifyPropertyModified(mIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
gpEdApp->WorldEditor()->OnPropertyModified(mpProperty);
|
||||
}
|
||||
}
|
||||
|
||||
bool IEditPropertyCommand::AffectsCleanState() const
|
||||
|
|
|
@ -13,6 +13,8 @@ protected:
|
|||
std::vector<char> mNewData;
|
||||
|
||||
IProperty* mpProperty;
|
||||
CPropertyModel* mpModel;
|
||||
QModelIndex mIndex;
|
||||
bool mCommandEnded;
|
||||
bool mSavedOldData;
|
||||
bool mSavedNewData;
|
||||
|
@ -26,9 +28,13 @@ protected:
|
|||
public:
|
||||
IEditPropertyCommand(
|
||||
IProperty* pProperty,
|
||||
const QString& rkCommandName = "Edit Property"
|
||||
CPropertyModel* pModel,
|
||||
const QModelIndex& kIndex,
|
||||
const QString& kCommandName = "Edit Property"
|
||||
);
|
||||
|
||||
virtual ~IEditPropertyCommand() {}
|
||||
|
||||
virtual void SaveOldData();
|
||||
virtual void SaveNewData();
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ CInstancesModel::CInstancesModel(CWorldEditor *pEditor, QObject *pParent)
|
|||
connect(mpEditor, SIGNAL(NodeSpawned(CSceneNode*)), this, SLOT(NodeCreated(CSceneNode*)));
|
||||
connect(mpEditor, SIGNAL(NodeAboutToBeDeleted(CSceneNode*)), this, SLOT(NodeAboutToBeDeleted(CSceneNode*)));
|
||||
connect(mpEditor, SIGNAL(NodeDeleted()), this, SLOT(NodeDeleted()));
|
||||
connect(mpEditor, SIGNAL(PropertyModified(CScriptObject*,IProperty*)), this, SLOT(PropertyModified(CScriptObject*,IProperty*)));
|
||||
connect(mpEditor, SIGNAL(PropertyModified(IProperty*,CScriptObject*)), this, SLOT(PropertyModified(IProperty*,CScriptObject*)));
|
||||
connect(mpEditor, SIGNAL(InstancesLayerAboutToChange()), this, SLOT(InstancesLayerPreChange()));
|
||||
connect(mpEditor, SIGNAL(InstancesLayerChanged(QList<CScriptNode*>)), this, SLOT(InstancesLayerPostChange(QList<CScriptNode*>)));
|
||||
}
|
||||
|
@ -472,7 +472,7 @@ void CInstancesModel::NodeDeleted()
|
|||
}
|
||||
}
|
||||
|
||||
void CInstancesModel::PropertyModified(CScriptObject *pInst, IProperty *pProp)
|
||||
void CInstancesModel::PropertyModified(IProperty *pProp, CScriptObject *pInst)
|
||||
{
|
||||
if (pProp->Name() == "Name")
|
||||
{
|
||||
|
|
|
@ -60,7 +60,7 @@ public slots:
|
|||
void NodeAboutToBeDeleted(CSceneNode *pNode);
|
||||
void NodeDeleted();
|
||||
|
||||
void PropertyModified(CScriptObject *pInst, IProperty *pProp);
|
||||
void PropertyModified(IProperty *pProp, CScriptObject *pInst);
|
||||
void InstancesLayerPreChange();
|
||||
void InstancesLayerPostChange(const QList<CScriptNode*>& rkInstanceList);
|
||||
|
||||
|
|
|
@ -541,12 +541,17 @@ void CWorldEditor::OnLinksModified(const QList<CScriptObject*>& rkInstances)
|
|||
emit InstanceLinksModified(rkInstances);
|
||||
}
|
||||
|
||||
void CWorldEditor::OnPropertyModified(CScriptObject* pObject, IProperty *pProp)
|
||||
void CWorldEditor::OnPropertyModified(IProperty *pProp)
|
||||
{
|
||||
CScriptNode *pScript = mScene.NodeForInstance(pObject);
|
||||
bool ShouldUpdateSelection = false;
|
||||
|
||||
if (pScript)
|
||||
for (CSelectionIterator It(mpSelection); It; ++It)
|
||||
{
|
||||
CSceneNode* pNode = *It;
|
||||
|
||||
if (pNode && pNode->NodeType() == ENodeType::Script)
|
||||
{
|
||||
CScriptNode* pScript = static_cast<CScriptNode*>(pNode);
|
||||
pScript->PropertyModified(pProp);
|
||||
|
||||
// If this is the name, update other parts of the UI to reflect the new value.
|
||||
|
@ -561,6 +566,9 @@ void CWorldEditor::OnPropertyModified(CScriptObject* pObject, IProperty *pProp)
|
|||
{
|
||||
mpSelection->UpdateBounds();
|
||||
}
|
||||
|
||||
// Emit signal so other widgets can react to the property change
|
||||
emit PropertyModified(pProp, pScript->Instance());
|
||||
}
|
||||
|
||||
// If this is a model/character, then we'll treat this as a modified selection. This is to make sure the selection bounds updates.
|
||||
|
@ -570,13 +578,16 @@ void CWorldEditor::OnPropertyModified(CScriptObject* pObject, IProperty *pProp)
|
|||
const CResTypeFilter& rkFilter = pAsset->GetTypeFilter();
|
||||
|
||||
if (rkFilter.Accepts(EResourceType::Model) || rkFilter.Accepts(EResourceType::AnimSet) || rkFilter.Accepts(EResourceType::Character))
|
||||
SelectionModified();
|
||||
ShouldUpdateSelection = true;
|
||||
}
|
||||
else if (pProp->Type() == EPropertyType::AnimationSet)
|
||||
SelectionModified();
|
||||
ShouldUpdateSelection = true;
|
||||
}
|
||||
|
||||
// Emit signal so other widgets can react to the property change
|
||||
emit PropertyModified(pObject, pProp);
|
||||
if (ShouldUpdateSelection)
|
||||
{
|
||||
SelectionModified();
|
||||
}
|
||||
}
|
||||
|
||||
void CWorldEditor::SetSelectionActive(bool Active)
|
||||
|
@ -616,10 +627,14 @@ void CWorldEditor::SetSelectionActive(bool Active)
|
|||
|
||||
if (pActiveProperty)
|
||||
{
|
||||
CPropertyModel* pModel = qobject_cast<CPropertyModel*>(
|
||||
mpScriptSidebar->ModifyTab()->PropertyView()->model()
|
||||
);
|
||||
|
||||
CEditScriptPropertyCommand* pCommand = new CEditScriptPropertyCommand(
|
||||
pActiveProperty,
|
||||
this,
|
||||
CommandObjects
|
||||
CommandObjects,
|
||||
pModel
|
||||
);
|
||||
|
||||
pCommand->SaveOldData();
|
||||
|
@ -1073,61 +1088,6 @@ void CWorldEditor::OnUnlinkClicked()
|
|||
}
|
||||
}
|
||||
|
||||
void CWorldEditor::OnUndoStackIndexChanged()
|
||||
{
|
||||
// Check the commands that have been executed on the undo stack and find out whether any of them affect the clean state.
|
||||
// This is to prevent commands like select/deselect from altering the clean state.
|
||||
int CurrentIndex = UndoStack().index();
|
||||
int CleanIndex = UndoStack().cleanIndex();
|
||||
|
||||
if (CleanIndex == -1)
|
||||
{
|
||||
if (!isWindowModified())
|
||||
UndoStack().setClean();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (CurrentIndex == CleanIndex)
|
||||
setWindowModified(false);
|
||||
|
||||
else
|
||||
{
|
||||
bool IsClean = true;
|
||||
int LowIndex = (CurrentIndex > CleanIndex ? CleanIndex : CurrentIndex);
|
||||
int HighIndex = (CurrentIndex > CleanIndex ? CurrentIndex - 1 : CleanIndex - 1);
|
||||
|
||||
for (int iIdx = LowIndex; iIdx <= HighIndex; iIdx++)
|
||||
{
|
||||
const QUndoCommand *pkQCmd = UndoStack().command(iIdx);
|
||||
|
||||
if (const IUndoCommand *pkCmd = dynamic_cast<const IUndoCommand*>(pkQCmd))
|
||||
{
|
||||
if (pkCmd->AffectsCleanState())
|
||||
IsClean = false;
|
||||
}
|
||||
|
||||
else if (pkQCmd->childCount() > 0)
|
||||
{
|
||||
for (int iChild = 0; iChild < pkQCmd->childCount(); iChild++)
|
||||
{
|
||||
const IUndoCommand *pkCmd = static_cast<const IUndoCommand*>(pkQCmd->child(iChild));
|
||||
|
||||
if (pkCmd->AffectsCleanState())
|
||||
{
|
||||
IsClean = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsClean) break;
|
||||
}
|
||||
|
||||
setWindowModified(!IsClean);
|
||||
}
|
||||
}
|
||||
|
||||
void CWorldEditor::OnPickModeEnter(QCursor Cursor)
|
||||
{
|
||||
ui->MainViewport->SetCursorState(Cursor);
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
#include <QList>
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
#include <QUndoStack>
|
||||
|
||||
namespace Ui {
|
||||
class CWorldEditor;
|
||||
|
@ -115,7 +114,7 @@ public slots:
|
|||
|
||||
void OnActiveProjectChanged(CGameProject *pProj);
|
||||
void OnLinksModified(const QList<CScriptObject*>& rkInstances);
|
||||
void OnPropertyModified(CScriptObject* pObject, IProperty *pProp);
|
||||
void OnPropertyModified(IProperty *pProp);
|
||||
void SetSelectionActive(bool Active);
|
||||
void SetSelectionInstanceNames(const QString& rkNewName, bool IsDone);
|
||||
void SetSelectionLayer(CScriptLayer *pLayer);
|
||||
|
@ -143,7 +142,6 @@ private slots:
|
|||
void OnLinkEnd();
|
||||
void OnUnlinkClicked();
|
||||
|
||||
void OnUndoStackIndexChanged();
|
||||
void OnPickModeEnter(QCursor Cursor);
|
||||
void OnPickModeExit();
|
||||
void UpdateCameraOrbit();
|
||||
|
@ -178,7 +176,7 @@ signals:
|
|||
void InstancesLayerAboutToChange();
|
||||
void InstancesLayerChanged(const QList<CScriptNode*>& rkInstanceList);
|
||||
void InstanceLinksModified(const QList<CScriptObject*>& rkInstances);
|
||||
void PropertyModified(CScriptObject *pInst, IProperty *pProp);
|
||||
void PropertyModified(IProperty *pProp, CScriptObject* pObject);
|
||||
};
|
||||
|
||||
#endif // CWORLDEDITOR_H
|
||||
|
|
|
@ -65,7 +65,7 @@ void WEditorProperties::SyncToEditor(CWorldEditor *pEditor)
|
|||
connect(mpEditor, SIGNAL(SelectionModified()), this, SLOT(OnSelectionModified()));
|
||||
connect(mpEditor, SIGNAL(LayersModified()), this, SLOT(OnLayersModified()));
|
||||
connect(mpEditor, SIGNAL(InstancesLayerChanged(QList<CScriptNode*>)), this, SLOT(OnInstancesLayerChanged(QList<CScriptNode*>)));
|
||||
connect(mpEditor, SIGNAL(PropertyModified(CScriptObject*,IProperty*)), this, SLOT(OnPropertyModified(CScriptObject*,IProperty*)));
|
||||
connect(mpEditor, SIGNAL(PropertyModified(IProperty*,CScriptObject*)), this, SLOT(OnPropertyModified(IProperty*,CScriptObject*)));
|
||||
|
||||
OnLayersModified();
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ void WEditorProperties::OnSelectionModified()
|
|||
SetLayerComboBox();
|
||||
}
|
||||
|
||||
void WEditorProperties::OnPropertyModified(CScriptObject *pInstance, IProperty *pProp)
|
||||
void WEditorProperties::OnPropertyModified(IProperty* pProp, CScriptObject* pInstance)
|
||||
{
|
||||
if (!mpInstanceNameLineEdit->hasFocus())
|
||||
{
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
|
||||
public slots:
|
||||
void OnSelectionModified();
|
||||
void OnPropertyModified(CScriptObject *pInst, IProperty *pProp);
|
||||
void OnPropertyModified(IProperty* pProp, CScriptObject* pInstance);
|
||||
void OnInstancesLayerChanged(const QList<CScriptNode*>& rkNodeList);
|
||||
void OnLayersModified();
|
||||
void UpdatePropertyValues();
|
||||
|
|
|
@ -45,6 +45,7 @@ WModifyTab::WModifyTab(CWorldEditor *pEditor, QWidget *pParent)
|
|||
connect(ui->DeleteIncomingConnectionButton, SIGNAL(clicked()), this, SLOT(OnDeleteLinksClicked()));
|
||||
connect(ui->EditOutgoingConnectionButton, SIGNAL(clicked()), this, SLOT(OnEditLinkClicked()));
|
||||
connect(ui->EditIncomingConnectionButton, SIGNAL(clicked()), this, SLOT(OnEditLinkClicked()));
|
||||
connect(ui->PropertyView, SIGNAL(PropertyModified(IProperty*)), mpWorldEditor, SLOT(OnPropertyModified(IProperty*)));
|
||||
connect(mpWorldEditor, SIGNAL(MapChanged(CWorld*,CGameArea*)), this, SLOT(OnMapChanged()));
|
||||
connect(mpWorldEditor, SIGNAL(SelectionTransformed()), this, SLOT(OnWorldSelectionTransformed()));
|
||||
connect(mpWorldEditor, SIGNAL(InstanceLinksModified(const QList<CScriptObject*>&)), this, SLOT(OnInstanceLinksModified(const QList<CScriptObject*>&)));
|
||||
|
@ -65,6 +66,11 @@ void WModifyTab::ClearUI()
|
|||
mpSelectedNode = nullptr;
|
||||
}
|
||||
|
||||
CPropertyView* WModifyTab::PropertyView() const
|
||||
{
|
||||
return ui->PropertyView;
|
||||
}
|
||||
|
||||
// ************ PUBLIC SLOTS ************
|
||||
void WModifyTab::GenerateUI()
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "CLinkDialog.h"
|
||||
#include "CLinkModel.h"
|
||||
#include "Editor/CNodeSelection.h"
|
||||
#include "Editor/PropertyEdit/CPropertyView.h"
|
||||
#include <Core/Scene/CSceneNode.h>
|
||||
#include <Core/Scene/CScriptNode.h>
|
||||
|
||||
|
@ -37,6 +38,7 @@ public:
|
|||
explicit WModifyTab(CWorldEditor *pEditor, QWidget *pParent = 0);
|
||||
~WModifyTab();
|
||||
void ClearUI();
|
||||
CPropertyView* PropertyView() const;
|
||||
|
||||
public slots:
|
||||
void GenerateUI();
|
||||
|
|
Loading…
Reference in New Issue