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: public:
virtual void SerializeValue(void* pData, IArchive& Arc) const virtual void SerializeValue(void* pData, IArchive& Arc) const
{ {
Value(pData).Serialize(Arc); ValueRef(pData).Serialize(Arc);
} }
virtual const char* HashableTypeName() const virtual const char* HashableTypeName() const

View File

@ -25,7 +25,7 @@ public:
virtual void SerializeValue(void* pData, IArchive& Arc) const 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 "CTweakEditor.h"
#include "ui_CTweakEditor.h" #include "ui_CTweakEditor.h"
#include "Editor/Undo/IUndoCommand.h"
CTweakEditor::CTweakEditor(QWidget* pParent) CTweakEditor::CTweakEditor(QWidget* pParent)
: IEditor(pParent) : IEditor(pParent)
@ -9,9 +10,11 @@ CTweakEditor::CTweakEditor(QWidget* pParent)
{ {
mpUI->setupUi(this); mpUI->setupUi(this);
mpUI->TweakTabs->setExpanding(false); 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() 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) void CTweakEditor::OnProjectChanged(CGameProject* pNewProject)
{ {
// Close and clear tabs // Close and clear tabs
@ -78,8 +109,8 @@ void CTweakEditor::OnProjectChanged(CGameProject* pNewProject)
mpUI->TweakTabs->removeTab(0); mpUI->TweakTabs->removeTab(0);
} }
mpUI->TweakTabs->blockSignals(false);
mTweakAssets.clear(); mTweakAssets.clear();
UndoStack().clear();
// Create tweak list // Create tweak list
if (pNewProject != nullptr) if (pNewProject != nullptr)
@ -105,4 +136,6 @@ void CTweakEditor::OnProjectChanged(CGameProject* pNewProject)
SetActiveTweakIndex(0); SetActiveTweakIndex(0);
} }
mpUI->TweakTabs->blockSignals(false);
} }

View File

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

View File

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

View File

@ -1,5 +1,7 @@
#include "IEditor.h" #include "IEditor.h"
#include "Editor/Undo/IUndoCommand.h"
#include <QMenu> #include <QMenu>
#include <QMessageBox> #include <QMessageBox>
#include <QToolBar> #include <QToolBar>
@ -19,6 +21,8 @@ IEditor::IEditor(QWidget* pParent)
pRedoAction->setIcon(QIcon(":/icons/Redo.png")); pRedoAction->setIcon(QIcon(":/icons/Redo.png"));
mUndoActions.push_back(pUndoAction); mUndoActions.push_back(pUndoAction);
mUndoActions.push_back(pRedoAction); mUndoActions.push_back(pRedoAction);
connect(&mUndoStack, SIGNAL(indexChanged(int)), this, SLOT(OnUndoStackIndexChanged()));
} }
QUndoStack& IEditor::UndoStack() QUndoStack& IEditor::UndoStack()
@ -26,12 +30,12 @@ QUndoStack& IEditor::UndoStack()
return mUndoStack; return mUndoStack;
} }
void IEditor::AddUndoActions(QToolBar* pToolBar, QAction* pBefore) void IEditor::AddUndoActions(QToolBar* pToolBar, QAction* pBefore /*= 0*/)
{ {
pToolBar->insertActions(pBefore, mUndoActions); pToolBar->insertActions(pBefore, mUndoActions);
} }
void IEditor::AddUndoActions(QMenu* pMenu, QAction* pBefore) void IEditor::AddUndoActions(QMenu* pMenu, QAction* pBefore /*= 0*/)
{ {
pMenu->insertActions(pBefore, mUndoActions); pMenu->insertActions(pBefore, mUndoActions);
} }
@ -49,7 +53,10 @@ bool IEditor::CheckUnsavedChanges()
OkToClear = Save(); OkToClear = Save();
else if (Result == QMessageBox::No) else if (Result == QMessageBox::No)
{
mUndoStack.setIndex(0); // Revert all changes
OkToClear = true; OkToClear = true;
}
else if (Result == QMessageBox::Cancel) else if (Result == QMessageBox::Cancel)
OkToClear = false; OkToClear = false;
@ -83,3 +90,59 @@ bool IEditor::SaveAndRepack()
} }
else return false; 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: public:
IEditor(QWidget* pParent); IEditor(QWidget* pParent);
QUndoStack& UndoStack(); QUndoStack& UndoStack();
void AddUndoActions(QToolBar* pToolBar, QAction* pBefore); void AddUndoActions(QToolBar* pToolBar, QAction* pBefore = 0);
void AddUndoActions(QMenu* pMenu, QAction* pBefore); void AddUndoActions(QMenu* pMenu, QAction* pBefore = 0);
bool CheckUnsavedChanges(); bool CheckUnsavedChanges();
/** QMainWindow overrides */ /** QMainWindow overrides */
@ -39,12 +39,12 @@ public slots:
// Default implementation for editor windows that do not support resaving assets. // Default implementation for editor windows that do not support resaving assets.
// This should not be called. // This should not be called.
warnf("Base IEditor::Save() implementation called. Changes will not be saved."); warnf("Base IEditor::Save() implementation called. Changes will not be saved.");
ASSERT(false);
return true; return true;
} }
/** Non-virtual slots */ /** Non-virtual slots */
bool SaveAndRepack(); bool SaveAndRepack();
void OnUndoStackIndexChanged();
signals: signals:
void Closed(); void Closed();

View File

@ -3,6 +3,7 @@
#include "Editor/UICommon.h" #include "Editor/UICommon.h"
#include "Editor/Undo/CEditScriptPropertyCommand.h" #include "Editor/Undo/CEditScriptPropertyCommand.h"
#include "Editor/Undo/CEditIntrinsicPropertyCommand.h"
#include "Editor/Undo/CResizeScriptArrayCommand.h" #include "Editor/Undo/CResizeScriptArrayCommand.h"
#include "Editor/Widgets/CResourceSelector.h" #include "Editor/Widgets/CResourceSelector.h"
#include "Editor/Widgets/WColorPicker.h" #include "Editor/Widgets/WColorPicker.h"
@ -27,12 +28,13 @@
CPropertyDelegate::CPropertyDelegate(QObject* pParent /*= 0*/) CPropertyDelegate::CPropertyDelegate(QObject* pParent /*= 0*/)
: QStyledItemDelegate(pParent) : QStyledItemDelegate(pParent)
, mpEditor(nullptr)
, mpModel(nullptr) , mpModel(nullptr)
, mInRelayWidgetEdit(false) , mInRelayWidgetEdit(false)
, mEditInProgress(false) , mEditInProgress(false)
, mRelaysBlocked(false) , mRelaysBlocked(false)
{ {
mpEditor = gpEdApp->WorldEditor(); mpEditor = UICommon::FindAncestor<IEditor>(this);
} }
void CPropertyDelegate::SetPropertyModel(CPropertyModel* pModel) void CPropertyDelegate::SetPropertyModel(CPropertyModel* pModel)
@ -361,16 +363,28 @@ void CPropertyDelegate::setModelData(QWidget *pEditor, QAbstractItemModel* /*pMo
if (pProp) if (pProp)
{ {
EPropertyType Type = mpModel->GetEffectiveFieldType(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; 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) 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 // Handle sub-properties of flags and animation sets
if (rkIndex.internalId() & 0x80000000) if (rkIndex.internalId() & 0x80000000)
{ {
@ -482,9 +496,6 @@ void CPropertyDelegate::setModelData(QWidget *pEditor, QAbstractItemModel* /*pMo
// Array // Array
else else
{ {
pCommand = new CResizeScriptArrayCommand(pProp, mpEditor, Objects, mpModel, rkIndex);
pCommand->SaveOldData();
WIntegralSpinBox* pSpinBox = static_cast<WIntegralSpinBox*>(pEditor); WIntegralSpinBox* pSpinBox = static_cast<WIntegralSpinBox*>(pEditor);
CArrayProperty* pArray = static_cast<CArrayProperty*>(pProp); CArrayProperty* pArray = static_cast<CArrayProperty*>(pProp);
int OldCount = pArray->ArrayCount(pData); int OldCount = pArray->ArrayCount(pData);
@ -560,9 +571,9 @@ QWidget* CPropertyDelegate::CreateCharacterEditor(QWidget *pParent, const QModel
pSelector->SetFrameVisible(false); pSelector->SetFrameVisible(false);
if (Params.Version() <= EGame::Echoes) if (Params.Version() <= EGame::Echoes)
pSelector->SetTypeFilter(mpEditor->CurrentGame(), "ANCS"); pSelector->SetTypeFilter(gpEdApp->CurrentGame(), "ANCS");
else else
pSelector->SetTypeFilter(mpEditor->CurrentGame(), "CHAR"); pSelector->SetTypeFilter(gpEdApp->CurrentGame(), "CHAR");
CONNECT_RELAY(pSelector, rkIndex, ResourceChanged(CResourceEntry*)); CONNECT_RELAY(pSelector, rkIndex, ResourceChanged(CResourceEntry*));
return pSelector; return pSelector;
@ -626,7 +637,7 @@ void CPropertyDelegate::SetCharacterModelData(QWidget *pEditor, const QModelInde
if (Type == EPropertyType::Asset) if (Type == EPropertyType::Asset)
{ {
CResourceEntry *pEntry = static_cast<CResourceSelector*>(pEditor)->Entry(); 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) else if (Type == EPropertyType::Enum || Type == EPropertyType::Choice)

View File

@ -9,7 +9,7 @@ class CPropertyDelegate : public QStyledItemDelegate
{ {
Q_OBJECT Q_OBJECT
CWorldEditor *mpEditor; IEditor* mpEditor;
CPropertyModel* mpModel; CPropertyModel* mpModel;
bool mInRelayWidgetEdit; bool mInRelayWidgetEdit;
mutable bool mEditInProgress; mutable bool mEditInProgress;
@ -17,6 +17,7 @@ class CPropertyDelegate : public QStyledItemDelegate
public: public:
CPropertyDelegate(QObject* pParent = 0); CPropertyDelegate(QObject* pParent = 0);
void SetEditor(IEditor* pEditor);
void SetPropertyModel(CPropertyModel* pModel); void SetPropertyModel(CPropertyModel* pModel);
virtual QWidget* createEditor(QWidget* pParent, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const; virtual QWidget* createEditor(QWidget* pParent, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const;

View File

@ -39,9 +39,6 @@ CPropertyView::CPropertyView(QWidget *pParent)
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(CreateContextMenu(QPoint))); connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(CreateContextMenu(QPoint)));
connect(mpModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(SetPersistentEditors(QModelIndex))); connect(mpModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(SetPersistentEditors(QModelIndex)));
connect(mpModel, SIGNAL(PropertyModified(const QModelIndex&)), this, SLOT(OnPropertyModified(const 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) void CPropertyView::setModel(QAbstractItemModel *pModel)
@ -110,7 +107,7 @@ void CPropertyView::SetIntrinsicProperties(CStructRef InProperties)
void CPropertyView::SetInstance(CScriptObject *pObj) void CPropertyView::SetInstance(CScriptObject *pObj)
{ {
mpObject = pObj; mpObject = pObj;
mpModel->SetBoldModifiedProperties(mpEditor ? (mpEditor->CurrentGame() > EGame::Prime) : true); mpModel->SetBoldModifiedProperties(gpEdApp->CurrentGame() > EGame::Prime);
if (pObj) if (pObj)
mpModel->ConfigureScript(pObj->Area()->Entry()->Project(), pObj->Template()->Properties(), 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) void CPropertyView::UpdateEditorProperties(const QModelIndex& rkParent)
{ {
// Check what game this is // 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. // Iterate over all properties and update if they're an editor property.
for (int iRow = 0; iRow < mpModel->rowCount(rkParent); iRow++) for (int iRow = 0; iRow < mpModel->rowCount(rkParent); iRow++)
@ -245,6 +242,10 @@ void CPropertyView::OnPropertyModified(const QModelIndex& rkIndex)
ClosePersistentEditors(rkIndex); ClosePersistentEditors(rkIndex);
SetPersistentEditors(rkIndex); SetPersistentEditors(rkIndex);
} }
scrollTo(rkIndex);
emit PropertyModified(rkIndex);
emit PropertyModified(pProperty);
} }
void CPropertyView::RefreshView() void CPropertyView::RefreshView()
@ -268,7 +269,7 @@ void CPropertyView::CreateContextMenu(const QPoint& rkPos)
Menu.addAction(mpEditTemplateAction); Menu.addAction(mpEditTemplateAction);
} }
if (mpEditor->CurrentGame() >= EGame::EchoesDemo) if (gpEdApp->CurrentGame() >= EGame::EchoesDemo)
{ {
Menu.addAction(mpShowNameValidityAction); Menu.addAction(mpShowNameValidityAction);
} }
@ -305,7 +306,8 @@ void CPropertyView::ToggleShowNameValidity(bool ShouldShow)
void CPropertyView::EditPropertyTemplate() void CPropertyView::EditPropertyTemplate()
{ {
CTemplateEditDialog Dialog(mpMenuProperty, mpEditor); QMainWindow* pParentWindow = UICommon::FindAncestor<QMainWindow>(this);
CTemplateEditDialog Dialog(mpMenuProperty, pParentWindow);
connect(&Dialog, SIGNAL(PerformedTypeConversion()), this, SLOT(RefreshView())); connect(&Dialog, SIGNAL(PerformedTypeConversion()), this, SLOT(RefreshView()));
Dialog.exec(); Dialog.exec();
} }
@ -313,21 +315,21 @@ void CPropertyView::EditPropertyTemplate()
void CPropertyView::GenerateNamesForProperty() void CPropertyView::GenerateNamesForProperty()
{ {
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog(); CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
pDialog->AddToIDPool(mpMenuProperty); pDialog->AddToIDPool(mpMenuProperty);
pDialog->show(); pDialog->show();
} }
void CPropertyView::GenerateNamesForSiblings() void CPropertyView::GenerateNamesForSiblings()
{ {
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog(); CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
pDialog->AddChildrenToIDPool(mpMenuProperty->Parent(), false); pDialog->AddChildrenToIDPool(mpMenuProperty->Parent(), false);
pDialog->show(); pDialog->show();
} }
void CPropertyView::GenerateNamesForChildren() void CPropertyView::GenerateNamesForChildren()
{ {
CGeneratePropertyNamesDialog* pDialog = mpEditor->NameGeneratorDialog(); CGeneratePropertyNamesDialog* pDialog = gpEdApp->WorldEditor()->NameGeneratorDialog();
pDialog->AddChildrenToIDPool(mpMenuProperty, false); pDialog->AddChildrenToIDPool(mpMenuProperty, false);
pDialog->show(); pDialog->show();
} }

View File

@ -10,7 +10,6 @@ class CPropertyView : public QTreeView
{ {
Q_OBJECT Q_OBJECT
CWorldEditor *mpEditor;
CPropertyModel* mpModel; CPropertyModel* mpModel;
CPropertyDelegate* mpDelegate; CPropertyDelegate* mpDelegate;
CScriptObject* mpObject; CScriptObject* mpObject;
@ -47,6 +46,10 @@ public slots:
void GenerateNamesForProperty(); void GenerateNamesForProperty();
void GenerateNamesForSiblings(); void GenerateNamesForSiblings();
void GenerateNamesForChildren(); void GenerateNamesForChildren();
signals:
void PropertyModified(const QModelIndex& kIndex);
void PropertyModified(IProperty* pProperty);
}; };
#endif // CPROPERTYVIEW_H #endif // CPROPERTYVIEW_H

View File

@ -46,6 +46,23 @@ QWindow* FindWidgetWindowHandle(QWidget *pWidget);
void OpenContainingFolder(const QString& rkPath); void OpenContainingFolder(const QString& rkPath);
bool OpenInExternalApplication(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 // TString/TWideString <-> QString
inline QString ToQString(const TString& rkStr) 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: protected:
QVector<CInstancePtr> mInstances; QVector<CInstancePtr> mInstances;
CWorldEditor* mpEditor;
QModelIndex mIndex; QModelIndex mIndex;
public: public:
CEditScriptPropertyCommand(IProperty* pProperty, CEditScriptPropertyCommand(IProperty* pProperty,
CWorldEditor* pEditor, const QVector<CScriptObject*>& kInstances,
const QVector<CScriptObject*>& rkInstances, CPropertyModel* pModel,
QModelIndex Index = QModelIndex(), QModelIndex Index = QModelIndex(),
const QString& rkCommandName = "Edit Property") const QString& kCommandName = "Edit Property")
: IEditPropertyCommand(pProperty, rkCommandName) : IEditPropertyCommand(pProperty, pModel, Index, kCommandName)
, mpEditor(pEditor)
, mIndex(Index) , 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 // Convert CScriptObject pointers to CInstancePtrs
mInstances.reserve( rkInstances.size() ); mInstances.reserve( kInstances.size() );
for (int i = 0; i < rkInstances.size(); i++) for (int i = 0; i < kInstances.size(); i++)
mInstances.push_back( CInstancePtr(rkInstances[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 // todo: support multiple objects being edited at once on the property view
if (mIndex.isValid()) if (mIndex.isValid())
{ {
ASSERT(mInstances.size() == 1); ASSERT(mInstances.size() == 1);
rOutPointers << mInstances[0]->PropertyData(); OutPointers << mInstances[0]->PropertyData();
return; return;
} }
// grab instance pointers // grab instance pointers
ASSERT(!mpProperty->IsArrayArchetype()); ASSERT(!mpProperty->IsArrayArchetype());
rOutPointers.resize(mInstances.size()); OutPointers.resize(mInstances.size());
for (int i = 0; i < mInstances.size(); i++) for (int i = 0; i < mInstances.size(); i++)
rOutPointers[i] = mInstances[i]->PropertyData(); OutPointers[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);
} }
}; };

View File

@ -5,31 +5,21 @@
class CResizeScriptArrayCommand : public CEditScriptPropertyCommand 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 */ /** Old/new model row counts; we store this here to support editing arrays on multiple instances at once */
int mOldRowCount; int mOldRowCount;
int mNewRowCount; int mNewRowCount;
public: public:
CResizeScriptArrayCommand(IProperty* pProperty, CResizeScriptArrayCommand(IProperty* pProperty,
CWorldEditor* pEditor,
const QVector<CScriptObject*>& rkInstances, const QVector<CScriptObject*>& rkInstances,
CPropertyModel* pModel = nullptr, CPropertyModel* pModel,
QModelIndex Index = QModelIndex(), QModelIndex Index = QModelIndex(),
const QString& rkCommandName = "Resize Array" const QString& rkCommandName = "Resize Array"
) )
: CEditScriptPropertyCommand(pProperty, pEditor, rkInstances, Index, rkCommandName) : CEditScriptPropertyCommand(pProperty, rkInstances, pModel, Index, rkCommandName)
, mpModel(nullptr)
, mOldRowCount(-1) , mOldRowCount(-1)
, mNewRowCount(-1) , mNewRowCount(-1)
{ {
if (Index.isValid())
{
ASSERT(pModel != nullptr);
mpModel = pModel;
}
} }
bool mergeWith(const QUndoCommand *pkOther) 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 // 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() void undo()
{ {
//@todo verify, do we need to fully override undo()?
if (mpModel) if (mpModel)
{ {
mpModel->ArrayAboutToBeResized(mIndex, mOldRowCount); mpModel->ArrayAboutToBeResized(mIndex, mOldRowCount);

View File

@ -1,4 +1,6 @@
#include "IEditPropertyCommand.h" #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 */ /** Save the current state of the object properties to the given data buffer */
void IEditPropertyCommand::SaveObjectStateToArray(std::vector<char>& rVector) void IEditPropertyCommand::SaveObjectStateToArray(std::vector<char>& rVector)
@ -31,14 +33,36 @@ void IEditPropertyCommand::RestoreObjectStateFromArray(std::vector<char>& rArray
IEditPropertyCommand::IEditPropertyCommand( IEditPropertyCommand::IEditPropertyCommand(
IProperty* pProperty, IProperty* pProperty,
const QString& rkCommandName /*= "Edit Property"*/ CPropertyModel* pModel,
const QModelIndex& kIndex,
const QString& kCommandName /*= "Edit Property"*/
) )
: IUndoCommand(rkCommandName) : IUndoCommand(kCommandName)
, mpProperty(pProperty) , mpProperty(pProperty)
, mpModel(pModel)
, mIndex(kIndex)
, mSavedOldData(false) , mSavedOldData(false)
, mSavedNewData(false) , mSavedNewData(false)
{ {
ASSERT(mpProperty); 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() void IEditPropertyCommand::SaveOldData()
@ -108,12 +132,30 @@ void IEditPropertyCommand::undo()
ASSERT(mSavedOldData && mSavedNewData); ASSERT(mSavedOldData && mSavedNewData);
RestoreObjectStateFromArray(mOldData); RestoreObjectStateFromArray(mOldData);
mCommandEnded = true; mCommandEnded = true;
if (mpModel && mIndex.isValid())
{
mpModel->NotifyPropertyModified(mIndex);
}
else
{
gpEdApp->WorldEditor()->OnPropertyModified(mpProperty);
}
} }
void IEditPropertyCommand::redo() void IEditPropertyCommand::redo()
{ {
ASSERT(mSavedOldData && mSavedNewData); ASSERT(mSavedOldData && mSavedNewData);
RestoreObjectStateFromArray(mNewData); RestoreObjectStateFromArray(mNewData);
if (mpModel && mIndex.isValid())
{
mpModel->NotifyPropertyModified(mIndex);
}
else
{
gpEdApp->WorldEditor()->OnPropertyModified(mpProperty);
}
} }
bool IEditPropertyCommand::AffectsCleanState() const bool IEditPropertyCommand::AffectsCleanState() const

View File

@ -13,6 +13,8 @@ protected:
std::vector<char> mNewData; std::vector<char> mNewData;
IProperty* mpProperty; IProperty* mpProperty;
CPropertyModel* mpModel;
QModelIndex mIndex;
bool mCommandEnded; bool mCommandEnded;
bool mSavedOldData; bool mSavedOldData;
bool mSavedNewData; bool mSavedNewData;
@ -26,9 +28,13 @@ protected:
public: public:
IEditPropertyCommand( IEditPropertyCommand(
IProperty* pProperty, IProperty* pProperty,
const QString& rkCommandName = "Edit Property" CPropertyModel* pModel,
const QModelIndex& kIndex,
const QString& kCommandName = "Edit Property"
); );
virtual ~IEditPropertyCommand() {}
virtual void SaveOldData(); virtual void SaveOldData();
virtual void SaveNewData(); 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(NodeSpawned(CSceneNode*)), this, SLOT(NodeCreated(CSceneNode*)));
connect(mpEditor, SIGNAL(NodeAboutToBeDeleted(CSceneNode*)), this, SLOT(NodeAboutToBeDeleted(CSceneNode*))); connect(mpEditor, SIGNAL(NodeAboutToBeDeleted(CSceneNode*)), this, SLOT(NodeAboutToBeDeleted(CSceneNode*)));
connect(mpEditor, SIGNAL(NodeDeleted()), this, SLOT(NodeDeleted())); 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(InstancesLayerAboutToChange()), this, SLOT(InstancesLayerPreChange()));
connect(mpEditor, SIGNAL(InstancesLayerChanged(QList<CScriptNode*>)), this, SLOT(InstancesLayerPostChange(QList<CScriptNode*>))); 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") if (pProp->Name() == "Name")
{ {

View File

@ -60,7 +60,7 @@ public slots:
void NodeAboutToBeDeleted(CSceneNode *pNode); void NodeAboutToBeDeleted(CSceneNode *pNode);
void NodeDeleted(); void NodeDeleted();
void PropertyModified(CScriptObject *pInst, IProperty *pProp); void PropertyModified(IProperty *pProp, CScriptObject *pInst);
void InstancesLayerPreChange(); void InstancesLayerPreChange();
void InstancesLayerPostChange(const QList<CScriptNode*>& rkInstanceList); void InstancesLayerPostChange(const QList<CScriptNode*>& rkInstanceList);

View File

@ -541,12 +541,17 @@ void CWorldEditor::OnLinksModified(const QList<CScriptObject*>& rkInstances)
emit InstanceLinksModified(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); pScript->PropertyModified(pProp);
// If this is the name, update other parts of the UI to reflect the new value. // 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(); 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. // 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(); const CResTypeFilter& rkFilter = pAsset->GetTypeFilter();
if (rkFilter.Accepts(EResourceType::Model) || rkFilter.Accepts(EResourceType::AnimSet) || rkFilter.Accepts(EResourceType::Character)) if (rkFilter.Accepts(EResourceType::Model) || rkFilter.Accepts(EResourceType::AnimSet) || rkFilter.Accepts(EResourceType::Character))
SelectionModified(); ShouldUpdateSelection = true;
} }
else if (pProp->Type() == EPropertyType::AnimationSet) else if (pProp->Type() == EPropertyType::AnimationSet)
SelectionModified(); ShouldUpdateSelection = true;
}
// Emit signal so other widgets can react to the property change if (ShouldUpdateSelection)
emit PropertyModified(pObject, pProp); {
SelectionModified();
}
} }
void CWorldEditor::SetSelectionActive(bool Active) void CWorldEditor::SetSelectionActive(bool Active)
@ -616,10 +627,14 @@ void CWorldEditor::SetSelectionActive(bool Active)
if (pActiveProperty) if (pActiveProperty)
{ {
CPropertyModel* pModel = qobject_cast<CPropertyModel*>(
mpScriptSidebar->ModifyTab()->PropertyView()->model()
);
CEditScriptPropertyCommand* pCommand = new CEditScriptPropertyCommand( CEditScriptPropertyCommand* pCommand = new CEditScriptPropertyCommand(
pActiveProperty, pActiveProperty,
this, CommandObjects,
CommandObjects pModel
); );
pCommand->SaveOldData(); 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) void CWorldEditor::OnPickModeEnter(QCursor Cursor)
{ {
ui->MainViewport->SetCursorState(Cursor); ui->MainViewport->SetCursorState(Cursor);

View File

@ -30,7 +30,6 @@
#include <QList> #include <QList>
#include <QMainWindow> #include <QMainWindow>
#include <QTimer> #include <QTimer>
#include <QUndoStack>
namespace Ui { namespace Ui {
class CWorldEditor; class CWorldEditor;
@ -115,7 +114,7 @@ public slots:
void OnActiveProjectChanged(CGameProject *pProj); void OnActiveProjectChanged(CGameProject *pProj);
void OnLinksModified(const QList<CScriptObject*>& rkInstances); void OnLinksModified(const QList<CScriptObject*>& rkInstances);
void OnPropertyModified(CScriptObject* pObject, IProperty *pProp); void OnPropertyModified(IProperty *pProp);
void SetSelectionActive(bool Active); void SetSelectionActive(bool Active);
void SetSelectionInstanceNames(const QString& rkNewName, bool IsDone); void SetSelectionInstanceNames(const QString& rkNewName, bool IsDone);
void SetSelectionLayer(CScriptLayer *pLayer); void SetSelectionLayer(CScriptLayer *pLayer);
@ -143,7 +142,6 @@ private slots:
void OnLinkEnd(); void OnLinkEnd();
void OnUnlinkClicked(); void OnUnlinkClicked();
void OnUndoStackIndexChanged();
void OnPickModeEnter(QCursor Cursor); void OnPickModeEnter(QCursor Cursor);
void OnPickModeExit(); void OnPickModeExit();
void UpdateCameraOrbit(); void UpdateCameraOrbit();
@ -178,7 +176,7 @@ signals:
void InstancesLayerAboutToChange(); void InstancesLayerAboutToChange();
void InstancesLayerChanged(const QList<CScriptNode*>& rkInstanceList); void InstancesLayerChanged(const QList<CScriptNode*>& rkInstanceList);
void InstanceLinksModified(const QList<CScriptObject*>& rkInstances); void InstanceLinksModified(const QList<CScriptObject*>& rkInstances);
void PropertyModified(CScriptObject *pInst, IProperty *pProp); void PropertyModified(IProperty *pProp, CScriptObject* pObject);
}; };
#endif // CWORLDEDITOR_H #endif // CWORLDEDITOR_H

View File

@ -65,7 +65,7 @@ void WEditorProperties::SyncToEditor(CWorldEditor *pEditor)
connect(mpEditor, SIGNAL(SelectionModified()), this, SLOT(OnSelectionModified())); connect(mpEditor, SIGNAL(SelectionModified()), this, SLOT(OnSelectionModified()));
connect(mpEditor, SIGNAL(LayersModified()), this, SLOT(OnLayersModified())); connect(mpEditor, SIGNAL(LayersModified()), this, SLOT(OnLayersModified()));
connect(mpEditor, SIGNAL(InstancesLayerChanged(QList<CScriptNode*>)), this, SLOT(OnInstancesLayerChanged(QList<CScriptNode*>))); 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(); OnLayersModified();
} }
@ -139,7 +139,7 @@ void WEditorProperties::OnSelectionModified()
SetLayerComboBox(); SetLayerComboBox();
} }
void WEditorProperties::OnPropertyModified(CScriptObject *pInstance, IProperty *pProp) void WEditorProperties::OnPropertyModified(IProperty* pProp, CScriptObject* pInstance)
{ {
if (!mpInstanceNameLineEdit->hasFocus()) if (!mpInstanceNameLineEdit->hasFocus())
{ {

View File

@ -39,7 +39,7 @@ public:
public slots: public slots:
void OnSelectionModified(); void OnSelectionModified();
void OnPropertyModified(CScriptObject *pInst, IProperty *pProp); void OnPropertyModified(IProperty* pProp, CScriptObject* pInstance);
void OnInstancesLayerChanged(const QList<CScriptNode*>& rkNodeList); void OnInstancesLayerChanged(const QList<CScriptNode*>& rkNodeList);
void OnLayersModified(); void OnLayersModified();
void UpdatePropertyValues(); void UpdatePropertyValues();

View File

@ -45,6 +45,7 @@ WModifyTab::WModifyTab(CWorldEditor *pEditor, QWidget *pParent)
connect(ui->DeleteIncomingConnectionButton, SIGNAL(clicked()), this, SLOT(OnDeleteLinksClicked())); connect(ui->DeleteIncomingConnectionButton, SIGNAL(clicked()), this, SLOT(OnDeleteLinksClicked()));
connect(ui->EditOutgoingConnectionButton, SIGNAL(clicked()), this, SLOT(OnEditLinkClicked())); connect(ui->EditOutgoingConnectionButton, SIGNAL(clicked()), this, SLOT(OnEditLinkClicked()));
connect(ui->EditIncomingConnectionButton, 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(MapChanged(CWorld*,CGameArea*)), this, SLOT(OnMapChanged()));
connect(mpWorldEditor, SIGNAL(SelectionTransformed()), this, SLOT(OnWorldSelectionTransformed())); connect(mpWorldEditor, SIGNAL(SelectionTransformed()), this, SLOT(OnWorldSelectionTransformed()));
connect(mpWorldEditor, SIGNAL(InstanceLinksModified(const QList<CScriptObject*>&)), this, SLOT(OnInstanceLinksModified(const QList<CScriptObject*>&))); connect(mpWorldEditor, SIGNAL(InstanceLinksModified(const QList<CScriptObject*>&)), this, SLOT(OnInstanceLinksModified(const QList<CScriptObject*>&)));
@ -65,6 +66,11 @@ void WModifyTab::ClearUI()
mpSelectedNode = nullptr; mpSelectedNode = nullptr;
} }
CPropertyView* WModifyTab::PropertyView() const
{
return ui->PropertyView;
}
// ************ PUBLIC SLOTS ************ // ************ PUBLIC SLOTS ************
void WModifyTab::GenerateUI() void WModifyTab::GenerateUI()
{ {

View File

@ -4,6 +4,7 @@
#include "CLinkDialog.h" #include "CLinkDialog.h"
#include "CLinkModel.h" #include "CLinkModel.h"
#include "Editor/CNodeSelection.h" #include "Editor/CNodeSelection.h"
#include "Editor/PropertyEdit/CPropertyView.h"
#include <Core/Scene/CSceneNode.h> #include <Core/Scene/CSceneNode.h>
#include <Core/Scene/CScriptNode.h> #include <Core/Scene/CScriptNode.h>
@ -37,6 +38,7 @@ public:
explicit WModifyTab(CWorldEditor *pEditor, QWidget *pParent = 0); explicit WModifyTab(CWorldEditor *pEditor, QWidget *pParent = 0);
~WModifyTab(); ~WModifyTab();
void ClearUI(); void ClearUI();
CPropertyView* PropertyView() const;
public slots: public slots:
void GenerateUI(); void GenerateUI();