Changes made in the tweak editor are now correctly applied to the tweak data & are undo/redo supported

This commit is contained in:
Aruki 2018-12-30 03:55:50 -07:00
parent e8d3224088
commit 7b005d7ebd
25 changed files with 359 additions and 225 deletions

View File

@ -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

View File

@ -25,7 +25,7 @@ public:
virtual void SerializeValue(void* pData, IArchive& Arc) const
{
Value(pData).Serialize(Arc);
ValueRef(pData).Serialize(Arc);
}
};

View File

@ -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);
}

View File

@ -33,6 +33,7 @@ public:
public slots:
void SetActiveTweakData(CTweakData* pTweakData);
void SetActiveTweakIndex(int Index);
void OnTweakTabClicked(int Index);
void OnProjectChanged(CGameProject* pNewProject);
};

View File

@ -204,7 +204,8 @@ HEADERS += \
StringEditor/CStringListModel.h \
StringEditor/CStringDelegate.h \
CCustomDelegate.h \
CTweakEditor.h
CTweakEditor.h \
Undo/CEditIntrinsicPropertyCommand.h
# Source Files
SOURCES += \

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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"
@ -25,22 +26,23 @@
connect(pRelay, SIGNAL(WidgetEdited(QWidget*, const QModelIndex&)), this, SLOT(WidgetEdited(QWidget*, const QModelIndex&))); \
}
CPropertyDelegate::CPropertyDelegate(QObject *pParent /*= 0*/)
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)
void CPropertyDelegate::SetPropertyModel(CPropertyModel* pModel)
{
mpModel = pModel;
}
QWidget* CPropertyDelegate::createEditor(QWidget *pParent, const QStyleOptionViewItem& /*rkOption*/, const QModelIndex& rkIndex) const
QWidget* CPropertyDelegate::createEditor(QWidget* pParent, const QStyleOptionViewItem& /*rkOption*/, const QModelIndex& rkIndex) const
{
if (!mpModel) return nullptr;
IProperty *pProp = mpModel->PropertyForIndex(rkIndex, false);
@ -361,16 +363,28 @@ void CPropertyDelegate::setModelData(QWidget *pEditor, QAbstractItemModel* /*pMo
if (pProp)
{
EPropertyType Type = mpModel->GetEffectiveFieldType(pProp);
CScriptObject* pObject = mpModel->GetScriptObject();
QVector<CScriptObject*> Objects;
Objects << mpModel->GetScriptObject();
if (!pObject)
{
QVector<void*> DataPointers;
DataPointers << pData;
pCommand = new CEditIntrinsicPropertyCommand(pProp, DataPointers, mpModel, rkIndex);
}
else
{
QVector<CScriptObject*> Objects;
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)

View File

@ -9,28 +9,29 @@ class CPropertyDelegate : public QStyledItemDelegate
{
Q_OBJECT
CWorldEditor *mpEditor;
CPropertyModel *mpModel;
IEditor* mpEditor;
CPropertyModel* mpModel;
bool mInRelayWidgetEdit;
mutable bool mEditInProgress;
mutable bool mRelaysBlocked;
public:
CPropertyDelegate(QObject *pParent = 0);
void SetPropertyModel(CPropertyModel *pModel);
CPropertyDelegate(QObject* pParent = 0);
void SetEditor(IEditor* pEditor);
void SetPropertyModel(CPropertyModel* pModel);
virtual QWidget* createEditor(QWidget *pParent, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const;
virtual void setEditorData(QWidget *pEditor, const QModelIndex &rkIndex) const;
virtual void setModelData(QWidget *pEditor, QAbstractItemModel *pModel, const QModelIndex &rkIndex) const;
bool eventFilter(QObject *pObject, QEvent *pEvent);
virtual QWidget* createEditor(QWidget* pParent, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const;
virtual void setEditorData(QWidget* pEditor, const QModelIndex& rkIndex) const;
virtual void setModelData(QWidget* pEditor, QAbstractItemModel* pModel, const QModelIndex& rkIndex) const;
bool eventFilter(QObject* pObject, QEvent* pEvent);
QWidget* CreateCharacterEditor(QWidget *pParent, const QModelIndex& rkIndex) const;
void SetCharacterEditorData(QWidget *pEditor, const QModelIndex& rkIndex) const;
void SetCharacterModelData(QWidget *pEditor, const QModelIndex& rkIndex) const;
QWidget* CreateCharacterEditor(QWidget* pParent, const QModelIndex& rkIndex) const;
void SetCharacterEditorData(QWidget* pEditor, const QModelIndex& rkIndex) const;
void SetCharacterModelData(QWidget* pEditor, const QModelIndex& rkIndex) const;
EPropertyType DetermineCharacterPropType(EGame Game, const QModelIndex& rkIndex) const;
public slots:
void WidgetEdited(QWidget *pWidget, const QModelIndex& rkIndex);
void WidgetEdited(QWidget* pWidget, const QModelIndex& rkIndex);
protected:
void BlockRelays(bool Block) const { mRelaysBlocked = Block; }

View File

@ -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();
}

View File

@ -10,26 +10,25 @@ class CPropertyView : public QTreeView
{
Q_OBJECT
CWorldEditor *mpEditor;
CPropertyModel *mpModel;
CPropertyDelegate *mpDelegate;
CScriptObject *mpObject;
CPropertyModel* mpModel;
CPropertyDelegate* mpDelegate;
CScriptObject* mpObject;
IProperty *mpMenuProperty;
QAction *mpShowNameValidityAction;
QAction *mpEditTemplateAction;
QAction *mpGenNamesForPropertyAction;
QAction *mpGenNamesForSiblingsAction;
QAction *mpGenNamesForChildrenAction;
IProperty* mpMenuProperty;
QAction* mpShowNameValidityAction;
QAction* mpEditTemplateAction;
QAction* mpGenNamesForPropertyAction;
QAction* mpGenNamesForSiblingsAction;
QAction* mpGenNamesForChildrenAction;
public:
CPropertyView(QWidget *pParent = 0);
void setModel(QAbstractItemModel *pModel);
bool event(QEvent *pEvent);
CPropertyView(QWidget* pParent = 0);
void setModel(QAbstractItemModel* pModel);
bool event(QEvent* pEvent);
void InitColumnWidths(float NameColumnPercentage, float ValueColumnPercentage);
void ClearProperties();
void SetIntrinsicProperties(CStructRef InProperties);
void SetInstance(CScriptObject *pObj);
void SetInstance(CScriptObject* pObj);
void UpdateEditorProperties(const QModelIndex& rkParent);
inline CPropertyModel* PropertyModel() const { return mpModel; }
@ -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

View File

@ -42,10 +42,27 @@ namespace UICommon
{
// Utility
QWindow* FindWidgetWindowHandle(QWidget *pWidget);
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)
{

View File

@ -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

View File

@ -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();
}
};

View File

@ -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);

View File

@ -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

View File

@ -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();

View File

@ -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")
{

View File

@ -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);

View File

@ -541,42 +541,53 @@ 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)
{
pScript->PropertyModified(pProp);
CSceneNode* pNode = *It;
// If this is the name, update other parts of the UI to reflect the new value.
if ( pProp->Name() == "Name" )
if (pNode && pNode->NodeType() == ENodeType::Script)
{
UpdateStatusBar();
UpdateSelectionUI();
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.
if ( pProp->Name() == "Name" )
{
UpdateStatusBar();
UpdateSelectionUI();
}
else if (pProp->Name() == "Position" ||
pProp->Name() == "Rotation" ||
pProp->Name() == "Scale")
{
mpSelection->UpdateBounds();
}
// Emit signal so other widgets can react to the property change
emit PropertyModified(pProp, pScript->Instance());
}
else if (pProp->Name() == "Position" ||
pProp->Name() == "Rotation" ||
pProp->Name() == "Scale")
// If this is a model/character, then we'll treat this as a modified selection. This is to make sure the selection bounds updates.
if (pProp->Type() == EPropertyType::Asset)
{
mpSelection->UpdateBounds();
CAssetProperty *pAsset = TPropCast<CAssetProperty>(pProp);
const CResTypeFilter& rkFilter = pAsset->GetTypeFilter();
if (rkFilter.Accepts(EResourceType::Model) || rkFilter.Accepts(EResourceType::AnimSet) || rkFilter.Accepts(EResourceType::Character))
ShouldUpdateSelection = true;
}
else if (pProp->Type() == EPropertyType::AnimationSet)
ShouldUpdateSelection = true;
}
// If this is a model/character, then we'll treat this as a modified selection. This is to make sure the selection bounds updates.
if (pProp->Type() == EPropertyType::Asset)
if (ShouldUpdateSelection)
{
CAssetProperty *pAsset = TPropCast<CAssetProperty>(pProp);
const CResTypeFilter& rkFilter = pAsset->GetTypeFilter();
if (rkFilter.Accepts(EResourceType::Model) || rkFilter.Accepts(EResourceType::AnimSet) || rkFilter.Accepts(EResourceType::Character))
SelectionModified();
}
else if (pProp->Type() == EPropertyType::AnimationSet)
SelectionModified();
// Emit signal so other widgets can react to the property change
emit PropertyModified(pObject, pProp);
}
}
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);

View File

@ -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

View File

@ -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())
{

View File

@ -14,32 +14,32 @@
class WEditorProperties : public QWidget
{
Q_OBJECT
CWorldEditor *mpEditor;
CSceneNode *mpDisplayNode;
CWorldEditor* mpEditor;
CSceneNode* mpDisplayNode;
QVBoxLayout *mpMainLayout;
QVBoxLayout* mpMainLayout;
QLabel *mpInstanceInfoLabel;
QHBoxLayout *mpInstanceInfoLayout;
QLabel* mpInstanceInfoLabel;
QHBoxLayout* mpInstanceInfoLayout;
QCheckBox *mpActiveCheckBox;
QLineEdit *mpInstanceNameLineEdit;
QHBoxLayout *mpNameLayout;
QCheckBox* mpActiveCheckBox;
QLineEdit* mpInstanceNameLineEdit;
QHBoxLayout* mpNameLayout;
QLabel *mpLayersLabel;
QComboBox *mpLayersComboBox;
QHBoxLayout *mpLayersLayout;
QLabel* mpLayersLabel;
QComboBox* mpLayersComboBox;
QHBoxLayout* mpLayersLayout;
bool mHasEditedName;
public:
WEditorProperties(QWidget *pParent = 0);
void SyncToEditor(CWorldEditor *pEditor);
WEditorProperties(QWidget* pParent = 0);
void SyncToEditor(CWorldEditor* pEditor);
void SetLayerComboBox();
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();

View File

@ -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()
{

View File

@ -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();