Added support for deleting resources + minor fixes

This commit is contained in:
Aruki 2019-02-03 02:22:36 -07:00
parent 56843e214d
commit 96c1aae27f
26 changed files with 524 additions and 125 deletions

2
externals/LibCommon vendored

@ -1 +1 @@
Subproject commit 5c3bfbe57f6ef8a300933afdc053a445cabd5c7c
Subproject commit 3c6a40742551d7afd0737d1293d036df69f34ec6

View File

@ -297,6 +297,15 @@ CGameProject* CGameProject::LoadProject(const TString& rkProjPath, IProgressNoti
gpResourceStore = pOldStore;
}
// Create hidden files directory, if needed
TString HiddenDir = pProj->HiddenFilesDir();
if (!FileUtil::Exists(HiddenDir))
{
FileUtil::MakeDirectory(HiddenDir);
FileUtil::MarkHidden(HiddenDir, true);
}
pProj->mpAudioManager->LoadAssets();
pProj->mpTweakManager->LoadTweaks();
return pProj;

View File

@ -84,6 +84,7 @@ public:
// Directory Handling
inline TString ProjectRoot() const { return mProjectRoot; }
inline TString ProjectPath() const { return mProjectRoot + FileUtil::SanitizeName(mProjectName, false) + ".prj"; }
inline TString HiddenFilesDir() const { return mProjectRoot + ".project/"; }
inline TString DiscDir(bool Relative) const { return Relative ? "Disc/" : mProjectRoot + "Disc/"; }
inline TString PackagesDir(bool Relative) const { return Relative ? "Packages/" : mProjectRoot + "Packages/"; }
inline TString ResourcesDir(bool Relative) const { return Relative ? "Resources/" : mProjectRoot + "Resources/"; }

View File

@ -113,6 +113,9 @@ bool CResourceEntry::LoadMetadata()
bool CResourceEntry::SaveMetadata(bool ForceSave /*= false*/)
{
// Make sure we aren't saving a deleted resource
ASSERT( !HasFlag(EResEntryFlag::MarkedForDeletion) );
if (mMetadataDirty || ForceSave)
{
TString Path = MetadataFilePath();
@ -491,6 +494,9 @@ bool CResourceEntry::CanMoveTo(const TString& rkDir, const TString& rkName)
bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName, bool IsAutoGenDir /*= false*/, bool IsAutoGenName /*= false*/)
{
// Make sure we are not moving a deleted resource.
ASSERT( !IsMarkedForDeletion() );
if (!CanMoveTo(rkDir, rkName)) return false;
// Store old paths
@ -501,7 +507,15 @@ bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName,
TString OldMetaPath = MetadataFilePath();
// Set new directory and name
CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, true);
bool DirAlreadyExisted = true;
CVirtualDirectory *pNewDir = mpStore->GetVirtualDirectory(rkDir, false);
if (!pNewDir)
{
pNewDir = mpStore->GetVirtualDirectory(rkDir, true);
DirAlreadyExisted = false;
}
if (pNewDir == mpDirectory && rkName == mName) return false;
// Check if we can legally move to this spot
@ -580,7 +594,7 @@ bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName,
// If we succeeded, finish the move
if (FSMoveSuccess)
{
if (mpDirectory != pOldDir)
if (mpDirectory != pOldDir && pOldDir != nullptr)
{
FSMoveSuccess = pOldDir->RemoveChildResource(this);
ASSERT(FSMoveSuccess == true); // this shouldn't be able to fail
@ -605,7 +619,11 @@ bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName,
errorf("MOVE FAILED: %s", *MoveFailReason);
mpDirectory = pOldDir;
mName = OldName;
mpStore->ConditionalDeleteDirectory(pNewDir, false);
if (!DirAlreadyExisted)
{
mpStore->ConditionalDeleteDirectory(pNewDir, true);
}
if (FileUtil::Exists(NewRawPath))
FileUtil::MoveFile(NewRawPath, OldRawPath);
@ -627,6 +645,96 @@ bool CResourceEntry::Rename(const TString& rkName, bool IsAutoGenName /*= false*
return MoveAndRename(mpDirectory->FullPath(), rkName, false, IsAutoGenName);
}
void CResourceEntry::MarkDeleted(bool InDeleted)
{
// Flags resource for future deletion. "Deleted" resources remain in memory (which
// allows them to easily be un-deleted) but cannot be looked up in the resource
// store and will not save back out to the resource database. Their file data is
// stored in a temporary directory, which allows them to be moved back if the user
// un-does the deletion.
if (IsMarkedForDeletion() != InDeleted)
{
SetFlagEnabled(EResEntryFlag::MarkedForDeletion, InDeleted);
// Restore old name/directory if un-deleting
if (!InDeleted)
{
// Our directory path is stored in the Name field - see below for explanation
int NameEnd = mName.IndexOf('|');
ASSERT( NameEnd != -1 );
TString DirPath = mName.ChopFront(NameEnd + 1);
mName = mName.ChopBack( mName.Size() - NameEnd);
mpDirectory = mpStore->GetVirtualDirectory( DirPath, true );
ASSERT( mpDirectory != nullptr );
mpDirectory->AddChild("", this);
}
TString CookedPath = CookedAssetPath();
TString RawPath = RawAssetPath();
TString MetaPath = MetadataFilePath();
TString PathBase = mpStore->DeletedResourcePath() + mID.ToString() + ".";
TString DelCookedPath = PathBase + CookedExtension().ToString();
TString DelRawPath = DelCookedPath + ".rsraw";
TString DelMetaPath = DelCookedPath + ".rsmeta";
// If we are deleting...
if (InDeleted)
{
// Temporarily store our directory path in the name string.
// This is a hack, but we can't store the directory pointer because it may have been
// deleted and remade by the user by the time the resource is un-deleted, which
// means it is not safe to access later. Separating the name and the path with
// the '|' character is safe because this character is not allowed in filenames
// (which is enforced in FileUtil::IsValidName()).
mName = mName + "|" + mpDirectory->FullPath();
// Remove from parent directory.
mpDirectory->RemoveChildResource(this);
mpDirectory = nullptr;
// Move any resource files out of the project into a temporary folder.
FileUtil::MakeDirectory(DelMetaPath.GetFileDirectory());
if (FileUtil::Exists(MetaPath))
{
FileUtil::MoveFile(MetaPath, DelMetaPath);
}
if (FileUtil::Exists(RawPath))
{
FileUtil::MoveFile(RawPath, DelRawPath);
}
if (FileUtil::Exists(CookedPath))
{
FileUtil::MoveFile(CookedPath, DelCookedPath);
}
}
// If we are un-deleting...
else
{
// Move any resource files out of the temporary folder back into the project.
FileUtil::MakeDirectory(MetaPath.GetFileDirectory());
if (FileUtil::Exists(DelMetaPath))
{
FileUtil::MoveFile(DelMetaPath, MetaPath);
}
if (FileUtil::Exists(DelRawPath))
{
FileUtil::MoveFile(DelRawPath, RawPath);
}
if (FileUtil::Exists(DelCookedPath))
{
FileUtil::MoveFile(DelCookedPath, CookedPath);
}
}
mpStore->SetCacheDirty();
debugf("%s FOR DELETION: [%s] %s", InDeleted ? "MARKED" : "UNMARKED", *ID().ToString(), *CookedPath.GetFileName());
}
}
CGameProject* CResourceEntry::Project() const
{
return mpStore ? mpStore->Project() : nullptr;

View File

@ -21,6 +21,7 @@ enum class EResEntryFlag
HasBeenModified = 0x00000008, // Resource has been modified and resaved by the user
AutoResName = 0x00000010, // Resource name is auto-generated
AutoResDir = 0x00000020, // Resource directory name is auto-generated
MarkedForDeletion = 0x00000040, // Resource has been marked for deletion by the user
};
DECLARE_FLAGS(EResEntryFlag, FResEntryFlags)
@ -76,6 +77,7 @@ public:
bool MoveAndRename(const TString& rkDir, const TString& rkName, bool IsAutoGenDir = false, bool IsAutoGenName = false);
bool Move(const TString& rkDir, bool IsAutoGenDir = false);
bool Rename(const TString& rkName, bool IsAutoGenName = false);
void MarkDeleted(bool InDeleted);
CGameProject* Project() const;
EGame Game() const;
@ -90,6 +92,7 @@ public:
inline void SetHidden(bool Hidden) { SetFlagEnabled(EResEntryFlag::Hidden, Hidden); }
inline bool HasFlag(EResEntryFlag Flag) const { return mFlags.HasFlag(Flag); }
inline bool IsHidden() const { return HasFlag(EResEntryFlag::Hidden); }
inline bool IsMarkedForDeletion() const { return HasFlag(EResEntryFlag::MarkedForDeletion); }
inline bool IsLoaded() const { return mpResource != nullptr; }
inline bool IsCategorized() const { return mpDirectory && !mpDirectory->FullPath().CaseInsensitiveCompare( mpStore->DefaultResourceDirPath() ); }

View File

@ -22,12 +22,16 @@ public:
virtual CResourceEntry* Next()
{
if (mIter != mpkStore->mResourceEntries.end())
do
{
mpCurEntry = mIter->second;
mIter++;
if (mIter != mpkStore->mResourceEntries.end())
{
mpCurEntry = mIter->second;
mIter++;
}
else mpCurEntry = nullptr;
}
else mpCurEntry = nullptr;
while (mpCurEntry && mpCurEntry->IsMarkedForDeletion());
return mpCurEntry;
}

View File

@ -66,6 +66,22 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc)
{
// Serialize resources
uint32 ResourceCount = mResourceEntries.size();
if (rArc.IsWriter())
{
// Make sure deleted resources aren't included in the count.
// We can't use CResourceIterator because it skips MarkedForDeletion resources.
for (auto Iter = mResourceEntries.begin(); Iter != mResourceEntries.end(); Iter++)
{
CResourceEntry* pEntry = Iter->second;
if (pEntry->IsMarkedForDeletion())
{
ResourceCount--;
}
}
}
rArc << SerialParameter("ResourceCount", ResourceCount);
if (rArc.IsReader())
@ -85,10 +101,13 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc)
{
for (CResourceIterator It(this); It; ++It)
{
if (rArc.ParamBegin("Resource", 0))
if (!It->IsMarkedForDeletion())
{
It->SerializeEntryInfo(rArc, false);
rArc.ParamEnd();
if (rArc.ParamBegin("Resource", 0))
{
It->SerializeEntryInfo(rArc, false);
rArc.ParamEnd();
}
}
}
}
@ -140,18 +159,22 @@ bool CResourceStore::LoadDatabaseCache()
}
else
{
// Database is succesfully loaded at this point
// Database is successfully loaded at this point
if (mpProj)
{
ASSERT(mpProj->Game() == Reader.Game());
}
mGame = Reader.Game();
}
mGame = Reader.Game();
return true;
}
bool CResourceStore::SaveDatabaseCache()
{
TString Path = DatabasePath();
debugf("Saving database cache...");
CBasicBinaryWriter Writer(Path, FOURCC('CACH'), 0, mGame);
@ -182,6 +205,14 @@ void CResourceStore::SetProject(CGameProject *pProj)
mDatabasePath = mpProj->ProjectRoot();
mpDatabaseRoot = new CVirtualDirectory(this);
mGame = mpProj->Game();
// Clear deleted files from previous runs
TString DeletedPath = DeletedResourcePath();
if (FileUtil::Exists(DeletedPath))
{
FileUtil::ClearDirectory(DeletedPath);
}
}
}
@ -215,6 +246,14 @@ void CResourceStore::CloseProject()
It = mResourceEntries.erase(It);
}
// Clear deleted files from previous runs
TString DeletedPath = DeletedResourcePath();
if (FileUtil::Exists(DeletedPath))
{
FileUtil::ClearDirectory(DeletedPath);
}
delete mpDatabaseRoot;
mpDatabaseRoot = nullptr;
mpProj = nullptr;
@ -256,12 +295,27 @@ TString CResourceStore::DefaultResourceDirPath() const
return StaticDefaultResourceDirPath( mGame );
}
TString CResourceStore::DeletedResourcePath() const
{
return mpProj->HiddenFilesDir() / "delete/";
}
CResourceEntry* CResourceStore::FindEntry(const CAssetID& rkID) const
{
if (!rkID.IsValid()) return nullptr;
auto Found = mResourceEntries.find(rkID);
if (Found == mResourceEntries.end()) return nullptr;
else return Found->second;
if (rkID.IsValid())
{
auto Found = mResourceEntries.find(rkID);
if (Found != mResourceEntries.end())
{
CResourceEntry* pEntry = Found->second;
if (!pEntry->IsMarkedForDeletion())
return pEntry;
}
}
return nullptr;
}
CResourceEntry* CResourceStore::FindEntry(const TString& rkPath) const
@ -411,6 +465,8 @@ CResourceEntry* CResourceStore::CreateNewResource(const CAssetID& rkID, EResourc
{
TrackLoadedResource(pEntry);
}
debugf("CREATED NEW RESOURCE: [%s] %s", *rkID.ToString(), *pEntry->CookedAssetPath());
}
else

View File

@ -52,6 +52,7 @@ public:
void CreateVirtualDirectory(const TString& rkPath);
void ConditionalDeleteDirectory(CVirtualDirectory *pDir, bool Recurse);
TString DefaultResourceDirPath() const;
TString DeletedResourcePath() const;
bool IsResourceRegistered(const CAssetID& rkID) const;
CResourceEntry* CreateNewResource(const CAssetID& rkID, EResourceType Type, const TString& rkDir, const TString& rkName);

View File

@ -46,6 +46,28 @@ bool CVirtualDirectory::IsDescendantOf(CVirtualDirectory *pDir) const
return (this == pDir) || (mpParent && pDir && (mpParent == pDir || mpParent->IsDescendantOf(pDir)));
}
bool CVirtualDirectory::IsSafeToDelete() const
{
// Return false if we contain any referenced assets.
for (CResourceEntry* pEntry : mResources)
{
if (pEntry->IsLoaded() && pEntry->Resource()->IsReferenced())
{
return false;
}
}
for (CVirtualDirectory* pSubdir : mSubdirectories)
{
if (!pSubdir->IsSafeToDelete())
{
return false;
}
}
return true;
}
TString CVirtualDirectory::FullPath() const
{
if (IsRoot())

View File

@ -26,6 +26,7 @@ public:
bool IsEmpty(bool CheckFilesystem) const;
bool IsDescendantOf(CVirtualDirectory *pDir) const;
bool IsSafeToDelete() const;
TString FullPath() const;
TString AbsolutePath() const;
CVirtualDirectory* GetRoot();

View File

@ -64,19 +64,6 @@ bool CTweakEditor::Save()
}
}
void CTweakEditor::showEvent(QShowEvent* pEvent)
{
// Perform first-time UI initialization
// Property view cannot initialize correctly until first show due to window width not being configured
if (!mHasBeenShown)
{
mpUI->PropertyView->InitColumnWidths(0.6f, 0.3f);
mHasBeenShown = true;
}
IEditor::showEvent(pEvent);
}
void CTweakEditor::SetActiveTweakData(CTweakData* pTweakData)
{
for( int TweakIdx = 0; TweakIdx < mTweakAssets.size(); TweakIdx++ )

View File

@ -29,7 +29,6 @@ public:
bool HasTweaks();
virtual bool Save() override;
virtual void showEvent(QShowEvent* pEvent) override;
public slots:
void SetActiveTweakData(CTweakData* pTweakData);

View File

@ -203,7 +203,9 @@ HEADERS += \
Undo/CEditIntrinsicPropertyCommand.h \
Undo/TSerializeUndoCommand.h \
StringEditor/CStringMimeData.h \
ScanEditor/CScanEditor.h
ScanEditor/CScanEditor.h \
Undo/ICreateDeleteResourceCommand.h \
Undo/CSaveStoreCommand.h
# Source Files
SOURCES += \

View File

@ -79,8 +79,20 @@ bool CPropertyView::event(QEvent *pEvent)
pEvent->ignore();
return true;
}
else if (pEvent->type() == QEvent::Resize && !isVisible())
{
resizeColumnToContents(0);
}
else return QTreeView::event(pEvent);
return QTreeView::event(pEvent);
}
int CPropertyView::sizeHintForColumn(int Column) const
{
if (Column == 0)
return width() * 0.6f;
else
return width() * 0.4f;
}
void CPropertyView::SetEditor(IEditor* pEditor)
@ -88,13 +100,6 @@ void CPropertyView::SetEditor(IEditor* pEditor)
mpDelegate->SetEditor(pEditor);
}
void CPropertyView::InitColumnWidths(float NameColumnPercentage, float ValueColumnPercentage)
{
header()->resizeSection(0, width() * NameColumnPercentage);
header()->resizeSection(1, width() * ValueColumnPercentage);
header()->setSectionResizeMode(1, QHeaderView::Fixed);
}
void CPropertyView::ClearProperties()
{
mpObject = nullptr;

View File

@ -25,8 +25,9 @@ public:
CPropertyView(QWidget* pParent = 0);
void setModel(QAbstractItemModel* pModel);
bool event(QEvent* pEvent);
int sizeHintForColumn(int Column) const;
void SetEditor(IEditor* pEditor);
void InitColumnWidths(float NameColumnPercentage, float ValueColumnPercentage);
void ClearProperties();
void SetIntrinsicProperties(CStructRef InProperties);
void SetInstance(CScriptObject* pObj);

View File

@ -8,7 +8,9 @@
#include "Editor/Undo/CMoveResourceCommand.h"
#include "Editor/Undo/CRenameDirectoryCommand.h"
#include "Editor/Undo/CRenameResourceCommand.h"
#include "Editor/Undo/CSaveStoreCommand.h"
#include "Editor/Undo/ICreateDeleteDirectoryCommand.h"
#include "Editor/Undo/ICreateDeleteResourceCommand.h"
#include <Core/GameProject/AssetNameGeneration.h>
#include <Core/GameProject/CAssetNameMap.h>
@ -346,7 +348,11 @@ bool CResourceBrowser::RenameResource(CResourceEntry *pEntry, const TString& rkN
}
// Everything seems to be valid; proceed with the rename
mUndoStack.beginMacro("Rename Resource");
mUndoStack.push( new CSaveStoreCommand(mpStore) );
mUndoStack.push( new CRenameResourceCommand(pEntry, rkNewName) );
mUndoStack.push( new CSaveStoreCommand(mpStore) );
mUndoStack.endMacro();
return true;
}
@ -369,7 +375,11 @@ bool CResourceBrowser::RenameDirectory(CVirtualDirectory *pDir, const TString& r
}
// No conflicts, proceed with the rename
mUndoStack.beginMacro("Rename Directory");
mUndoStack.push( new CSaveStoreCommand(mpStore) );
mUndoStack.push( new CRenameDirectoryCommand(pDir, rkNewName) );
mUndoStack.push( new CSaveStoreCommand(mpStore) );
mUndoStack.endMacro();
return true;
}
@ -431,6 +441,7 @@ bool CResourceBrowser::MoveResources(const QList<CResourceEntry*>& rkResources,
if (!ValidResources.isEmpty() || !ValidDirs.isEmpty())
{
mUndoStack.beginMacro("Move Resources");
mUndoStack.push( new CSaveStoreCommand(mpStore) );
foreach (CVirtualDirectory *pDir, ValidDirs)
mUndoStack.push( new CMoveDirectoryCommand(mpStore, pDir, pNewDir) );
@ -438,43 +449,59 @@ bool CResourceBrowser::MoveResources(const QList<CResourceEntry*>& rkResources,
foreach (CResourceEntry *pEntry, ValidResources)
mUndoStack.push( new CMoveResourceCommand(pEntry, pNewDir) );
mUndoStack.push( new CSaveStoreCommand(mpStore) );
mUndoStack.endMacro();
}
return true;
}
CResourceEntry* CResourceBrowser::CreateNewResource(EResourceType Type)
CResourceEntry* CResourceBrowser::CreateNewResource(EResourceType Type,
TString Name /*= ""*/,
CVirtualDirectory* pDir /*= nullptr*/,
CAssetID ID /*= CAssetID()*/)
{
// Create new asset ID. Sanity check to make sure the ID is unused.
CAssetID NewAssetID;
while (!NewAssetID.IsValid() || mpStore->FindEntry(NewAssetID) != nullptr)
if (!pDir)
{
NewAssetID = CAssetID::RandomID( mpStore->Game() );
pDir = mpSelectedDir;
}
// Create new asset ID. Sanity check to make sure the ID is unused.
while (!ID.IsValid() || mpStore->FindEntry(ID) != nullptr)
{
ID = CAssetID::RandomID( mpStore->Game() );
}
// Boring generic default name - user will immediately be prompted to change this
TString BaseName = TString::Format(
TString BaseName = Name;
if (BaseName.IsEmpty())
{
BaseName = TString::Format(
"New %s", *CResTypeInfo::FindTypeInfo(Type)->TypeName()
);
}
TString Name = BaseName;
Name = BaseName;
int Num = 0;
while (mpSelectedDir->FindChildResource(Name, Type) != nullptr)
while (pDir->FindChildResource(Name, Type) != nullptr)
{
Num++;
Name = TString::Format("%s (%d)", *BaseName, Num);
}
emit ResourceAboutToBeCreated(mpSelectedDir);
// Create the actual resource
CResourceEntry* pEntry = mpStore->CreateNewResource(NewAssetID, Type, mpSelectedDir->FullPath(), Name);
pEntry->Save();
CResourceEntry* pEntry = mpStore->CreateNewResource(ID, Type, pDir->FullPath(), Name);
emit ResourceCreated(pEntry);
// Push undo command
mUndoStack.beginMacro("Create Resource");
mUndoStack.push( new CSaveStoreCommand(mpStore) );
mUndoStack.push( new CCreateResourceCommand(pEntry) );
mUndoStack.push( new CSaveStoreCommand(mpStore) );
mUndoStack.endMacro();
pEntry->Save();
// Select new resource so user can enter a name
QModelIndex Index = mpModel->GetIndexForEntry(pEntry);
@ -617,8 +644,12 @@ bool CResourceBrowser::CreateDirectory()
}
// Push create command to actually create the directory
mUndoStack.beginMacro("Create Directory");
mUndoStack.push( new CSaveStoreCommand(mpStore) );
CCreateDirectoryCommand *pCmd = new CCreateDirectoryCommand(mpStore, mpSelectedDir->FullPath(), DirName);
mUndoStack.push(pCmd);
mUndoStack.push( new CSaveStoreCommand(mpStore) );
mUndoStack.endMacro();
// Now fetch the new directory and start editing it so the user can enter a name
CVirtualDirectory *pNewDir = mpSelectedDir->FindChildDirectory(DirName, false);
@ -641,28 +672,101 @@ bool CResourceBrowser::CreateDirectory()
return false;
}
bool CResourceBrowser::DeleteDirectories(const QList<CVirtualDirectory*>& rkDirs)
bool CResourceBrowser::Delete(QVector<CResourceEntry*> Resources, QVector<CVirtualDirectory*> Directories)
{
QList<CVirtualDirectory*> DeletableDirs;
// Don't delete any resources/directories that are still referenced.
// This is kind of a hack but there's no good way to clear out these references right now.
QString ErrorPaths;
foreach (CVirtualDirectory *pDir, rkDirs)
for (int DirIdx = 0; DirIdx < Directories.size(); DirIdx++)
{
if (pDir && pDir->IsEmpty(true))
DeletableDirs << pDir;
if (!Directories[DirIdx]->IsSafeToDelete())
{
ErrorPaths += TO_QSTRING( Directories[DirIdx]->FullPath() ) + '\n';
Directories.removeAt(DirIdx);
DirIdx--;
}
}
if (DeletableDirs.size() > 0)
for (int ResIdx = 0; ResIdx < Resources.size(); ResIdx++)
{
mUndoStack.beginMacro("Delete Directories");
if (Resources[ResIdx]->IsLoaded() && Resources[ResIdx]->Resource()->IsReferenced())
{
ErrorPaths += TO_QSTRING( Resources[ResIdx]->CookedAssetPath(true) ) + '\n';
Resources.removeAt(ResIdx);
ResIdx--;
}
}
foreach (CVirtualDirectory *pDir, DeletableDirs)
if (!ErrorPaths.isEmpty())
{
// Remove trailing newline
ErrorPaths.chop(1);
UICommon::ErrorMsg(this, QString("The following resources/directories are still referenced and cannot be deleted:\n\n%1")
.arg(ErrorPaths));
}
// Gather a complete list of resources in subdirectories
for (int DirIdx = 0; DirIdx < Directories.size(); DirIdx++)
{
CVirtualDirectory* pDir = Directories[DirIdx];
Resources.reserve( Resources.size() + pDir->NumResources() );
Directories.reserve( Directories.size() + pDir->NumSubdirectories() );
for (uint ResourceIdx = 0; ResourceIdx < pDir->NumResources(); ResourceIdx++)
Resources << pDir->ResourceByIndex(ResourceIdx);
for (uint SubdirIdx = 0; SubdirIdx < pDir->NumSubdirectories(); SubdirIdx++)
Directories << pDir->SubdirectoryByIndex(SubdirIdx);
}
// Exit if we have nothing to do.
if (Resources.isEmpty() && Directories.isEmpty())
return false;
// Allow the user to confirm before proceeding.
QString ConfirmMsg = QString("Are you sure you want to permanently delete ");
if (Resources.size() > 0)
{
ConfirmMsg += QString("%1 resource%2").arg(Resources.size()).arg(Resources.size() == 1 ? "" : "s");
if (Directories.size() > 0)
{
ConfirmMsg += " and ";
}
}
if (Directories.size() > 0)
{
ConfirmMsg += QString("%1 %2").arg(Directories.size()).arg(Directories.size() == 1 ? "directory" : "directories");
}
ConfirmMsg += "?";
if (UICommon::YesNoQuestion(this, "Warning", ConfirmMsg))
{
// Note that the undo stack will undo actions in the reverse order they are pushed
// So we need to push commands last that we want to be undone first
// We want to delete subdirectories first, then parent directories, then resources
mUndoStack.beginMacro("Delete");
mUndoStack.push( new CSaveStoreCommand(mpStore) );
// Delete resources first.
foreach (CResourceEntry* pEntry, Resources)
mUndoStack.push( new CDeleteResourceCommand(pEntry) );
// Now delete directories in reverse order (so subdirectories delete first)
for (int DirIdx = Directories.size()-1; DirIdx >= 0; DirIdx--)
{
CVirtualDirectory* pDir = Directories[DirIdx];
mUndoStack.push( new CDeleteDirectoryCommand(mpStore, pDir->Parent()->FullPath(), pDir->Name()) );
}
mUndoStack.push( new CSaveStoreCommand(mpStore) );
mUndoStack.endMacro();
return true;
}
else return false;
else
return false;
}
void CResourceBrowser::OnSearchStringChanged(QString SearchString)

View File

@ -72,7 +72,10 @@ public:
bool RenameDirectory(CVirtualDirectory *pDir, const TString& rkNewName);
bool MoveResources(const QList<CResourceEntry*>& rkResources, const QList<CVirtualDirectory*>& rkDirectories, CVirtualDirectory *pNewDir);
CResourceEntry* CreateNewResource(EResourceType Type);
CResourceEntry* CreateNewResource(EResourceType Type,
TString Name = "",
CVirtualDirectory* pDir = nullptr,
CAssetID ID = CAssetID());
// Interface
bool eventFilter(QObject *pWatched, QEvent *pEvent);
@ -94,7 +97,7 @@ public slots:
void OnSortModeChanged(int Index);
void OnCreateAssetAction();
bool CreateDirectory();
bool DeleteDirectories(const QList<CVirtualDirectory*>& rkDirs);
bool Delete(QVector<CResourceEntry*> Resources, QVector<CVirtualDirectory*> Directories);
void OnSearchStringChanged(QString SearchString);
void OnDirectorySelectionChanged(const QModelIndex& rkNewIndex);
void OnDoubleClickTable(QModelIndex Index);

View File

@ -1,6 +1,9 @@
#include "CResourceTableContextMenu.h"
#include "CResourceBrowser.h"
#include "Editor/CEditorApplication.h"
#include <Core/Resource/Scan/CScan.h>
#include <QClipboard>
CResourceTableContextMenu::CResourceTableContextMenu(CResourceBrowser *pBrowser, QTableView *pView, CResourceTableModel *pModel, CResourceProxyModel *pProxy)
@ -70,6 +73,17 @@ void CResourceTableContextMenu::InitMenu()
QMenu* pCreate = addMenu("Create...");
mpBrowser->AddCreateAssetMenuActions(pCreate);
// Asset-specific
if (mpClickedEntry)
{
switch (mpClickedEntry->ResourceType())
{
case EResourceType::StringTable:
addAction("Create Scan", this, SLOT(CreateSCAN()));
break;
}
}
}
void CResourceTableContextMenu::ShowMenu(const QPoint& rkPos)
@ -198,43 +212,18 @@ void CResourceTableContextMenu::ShowDependencies()
void CResourceTableContextMenu::Delete()
{
// Create confirmation message
uint NumResources = 0, NumDirectories = 0;
QVector<CResourceEntry*> Resources;
QVector<CVirtualDirectory*> Directories;
foreach (const QModelIndex& kIndex, mSelectedIndexes)
{
if (mpModel->IsIndexDirectory(kIndex))
NumDirectories++;
Directories << mpModel->IndexDirectory(kIndex);
else
NumResources++;
Resources << mpModel->IndexEntry(kIndex);
}
if (NumResources == 0 && NumDirectories == 0)
return;
QString ConfirmMsg = QString("Are you sure you want to permanently delete ");
if (NumResources > 0)
{
ConfirmMsg += QString("%d resource%s").arg(NumResources).arg(NumResources == 1 ? "" : "s");
if (NumDirectories > 0)
{
ConfirmMsg += " and ";
}
}
if (NumDirectories > 0)
{
ConfirmMsg += QString("%d %s").arg(NumDirectories).arg(NumDirectories == 1 ? "directory" : "directories");
}
// Allow the user to confirm the action before performing it
if (UICommon::YesNoQuestion(mpBrowser, "Warning", ConfirmMsg))
{
//@todo this is wrong lol
QList<CVirtualDirectory*> List;
List << mpClickedDirectory;
mpBrowser->DeleteDirectories(List);
}
mpBrowser->Delete(Resources, Directories);
}
void CResourceTableContextMenu::CopyName()
@ -258,3 +247,22 @@ void CResourceTableContextMenu::CopyID()
ASSERT(mpClickedEntry);
gpEdApp->clipboard()->setText( TO_QSTRING(mpClickedEntry->ID().ToString()) );
}
// Asset Specific
void CResourceTableContextMenu::CreateSCAN()
{
// Create a SCAN asset to go along with a selected STRG asset
ASSERT( mpClickedEntry && mpClickedEntry->ResourceType() == EResourceType::StringTable );
CResourceEntry* pNewEntry = mpBrowser->CreateNewResource(EResourceType::Scan,
mpClickedEntry->Name(),
mpClickedEntry->Directory());
if (pNewEntry)
{
CScan* pScan = (CScan*) pNewEntry->Load();
pScan->ScanStringPropertyRef().Set( mpClickedEntry->ID() );
pNewEntry->Save();
}
}

View File

@ -41,6 +41,9 @@ public slots:
void CopyName();
void CopyPath();
void CopyID();
// Asset Specific
void CreateSCAN();
};
#endif // CRESOURCETABLECONTEXTMENU_H

View File

@ -8,6 +8,7 @@ CResourceTableModel::CResourceTableModel(CResourceBrowser *pBrowser, QObject *pP
, mIsDisplayingUserEntryList(false)
{
connect(pBrowser, SIGNAL(ResourceCreated(CResourceEntry*)), this, SLOT(CheckAddResource(CResourceEntry*)));
connect(pBrowser, SIGNAL(ResourceAboutToBeDeleted(CResourceEntry*)), this, SLOT(CheckRemoveResource(CResourceEntry*)));
connect(pBrowser, SIGNAL(DirectoryCreated(CVirtualDirectory*)), this, SLOT(CheckAddDirectory(CVirtualDirectory*)));
connect(pBrowser, SIGNAL(DirectoryAboutToBeDeleted(CVirtualDirectory*)), this, SLOT(CheckRemoveDirectory(CVirtualDirectory*)));
connect(pBrowser, SIGNAL(ResourceMoved(CResourceEntry*,CVirtualDirectory*,TString)), this, SLOT(OnResourceMoved(CResourceEntry*,CVirtualDirectory*,TString)));
@ -309,8 +310,8 @@ void CResourceTableModel::CheckRemoveResource(CResourceEntry *pEntry)
if (Index != -1)
{
Index += mDirectories.size();
beginRemoveRows(QModelIndex(), Index, Index);
int RowIndex = Index + mDirectories.size();
beginRemoveRows(QModelIndex(), RowIndex, RowIndex);
mEntries.removeAt(Index);
endRemoveRows();
}

View File

@ -44,34 +44,20 @@ void CResourceTableView::DeleteSelected()
// Figure out which indices can actually be deleted
CResourceProxyModel *pProxy = static_cast<CResourceProxyModel*>(model());
CResourceTableModel *pModel = static_cast<CResourceTableModel*>(pProxy->sourceModel());
QList<CVirtualDirectory*> DirsToDelete;
bool HasNonEmptyDirSelected = false;
QVector<CResourceEntry*> ResourcesToDelete;
QVector<CVirtualDirectory*> DirsToDelete;
foreach (QModelIndex Index, List)
{
QModelIndex SourceIndex = pProxy->mapToSource(Index);
if (pModel->IsIndexDirectory(SourceIndex))
{
CVirtualDirectory *pDir = pModel->IndexDirectory(SourceIndex);
DirsToDelete << pModel->IndexDirectory(SourceIndex);
else
ResourcesToDelete << pModel->IndexEntry(SourceIndex);
if (pDir)
{
if (pDir->IsEmpty(true))
DirsToDelete << pDir;
else
HasNonEmptyDirSelected = true;
}
}
}
// Let the user know if all selected directories are non empty
if (HasNonEmptyDirSelected && DirsToDelete.isEmpty())
{
UICommon::ErrorMsg(parentWidget(), "Unable to delete; one or more of the selected directories is non-empty.");
return;
}
// Delete
gpEdApp->ResourceBrowser()->DeleteDirectories(DirsToDelete);
gpEdApp->ResourceBrowser()->Delete(ResourcesToDelete, DirsToDelete);
}

View File

@ -0,0 +1,26 @@
#ifndef CSAVESTORECOMMAND_H
#define CSAVESTORECOMMAND_H
#include "IUndoCommand.h"
#include <Core/GameProject/CResourceStore.h>
/** Command that calls ConditionalSaveStore on a resource store.
* This is meant to be added to undo macros that modify the resource store
* in order to trigger the store to resave when the macro is complete.
*/
class CSaveStoreCommand : public IUndoCommand
{
CResourceStore* mpStore;
public:
CSaveStoreCommand(CResourceStore* pInStore)
: IUndoCommand("Save Store")
, mpStore(pInStore)
{}
virtual void undo() override { mpStore->ConditionalSaveStore(); }
virtual void redo() override { mpStore->ConditionalSaveStore(); }
virtual bool AffectsCleanState() const override { return false; }
};
#endif // CSAVESTORECOMMAND_H

View File

@ -16,8 +16,8 @@ protected:
CVirtualDirectory *mpDir;
public:
ICreateDeleteDirectoryCommand(CResourceStore *pStore, TString ParentPath, TString DirName)
: IUndoCommand("Create Directory")
ICreateDeleteDirectoryCommand(const QString& rkText, CResourceStore *pStore, TString ParentPath, TString DirName)
: IUndoCommand(rkText)
, mpStore(pStore)
, mParentPath(ParentPath)
, mDirName(DirName)
@ -64,7 +64,7 @@ class CCreateDirectoryCommand : public ICreateDeleteDirectoryCommand
{
public:
CCreateDirectoryCommand(CResourceStore *pStore, TString ParentPath, TString DirName)
: ICreateDeleteDirectoryCommand(pStore, ParentPath, DirName)
: ICreateDeleteDirectoryCommand("Create Directory", pStore, ParentPath, DirName)
{}
void undo() { DoDelete(); }
@ -75,7 +75,7 @@ class CDeleteDirectoryCommand : public ICreateDeleteDirectoryCommand
{
public:
CDeleteDirectoryCommand(CResourceStore *pStore, TString ParentPath, TString DirName)
: ICreateDeleteDirectoryCommand(pStore, ParentPath, DirName)
: ICreateDeleteDirectoryCommand("Delete Directory", pStore, ParentPath, DirName)
{
mpDir = pStore->GetVirtualDirectory(ParentPath + DirName, false);
ASSERT(mpDir);

View File

@ -0,0 +1,70 @@
#ifndef ICREATEDELETERESOURCECOMMAND_H
#define ICREATEDELETERESOURCECOMMAND_H
#include "IUndoCommand.h"
#include "Editor/CEditorApplication.h"
#include "Editor/ResourceBrowser/CResourceBrowser.h"
#include <Core/GameProject/CResourceEntry.h>
#include <Core/GameProject/CResourceStore.h>
class ICreateDeleteResourceCommand : public IUndoCommand
{
protected:
CResourceEntry* mpEntry;
TString mDirPath;
public:
ICreateDeleteResourceCommand(const QString& kText, CResourceEntry* pEntry)
: IUndoCommand(kText)
, mpEntry(pEntry)
{
mDirPath = mpEntry->Directory()->FullPath();
}
void DoCreate()
{
CVirtualDirectory* pDir = mpEntry->ResourceStore()->GetVirtualDirectory(mDirPath, true);
gpEdApp->ResourceBrowser()->ResourceAboutToBeCreated(pDir);
// restore directory and undelete
mpEntry->MarkDeleted(false);
gpEdApp->ResourceBrowser()->ResourceCreated(mpEntry);
}
void DoDelete()
{
gpEdApp->ResourceBrowser()->ResourceAboutToBeDeleted(mpEntry);
// save directory and delete
mpEntry->MarkDeleted(true);
gpEdApp->ResourceBrowser()->ResourceDeleted();
}
bool AffectsCleanState() const { return false; }
};
class CCreateResourceCommand : public ICreateDeleteResourceCommand
{
public:
CCreateResourceCommand(CResourceEntry* pEntry)
: ICreateDeleteResourceCommand("Create Resource", pEntry)
{}
void undo() { DoDelete(); }
void redo() { DoCreate(); }
};
class CDeleteResourceCommand : public ICreateDeleteResourceCommand
{
public:
CDeleteResourceCommand(CResourceEntry* pEntry)
: ICreateDeleteResourceCommand("Delete Resource", pEntry)
{}
void undo() { DoCreate(); }
void redo() { DoDelete(); }
};
#endif // ICREATEDELETERESOURCECOMMAND_H

View File

@ -16,7 +16,6 @@ WModifyTab::WModifyTab(CWorldEditor *pEditor, QWidget *pParent)
, mIsPicking(false)
{
ui->setupUi(this);
ui->PropertyView->InitColumnWidths(0.3f, 0.3f);
ui->PropertyView->SetEditor(pEditor);
mpWorldEditor = pEditor;

View File

@ -36795,7 +36795,7 @@
</Element>
<Element>
<Key ID="0xC915686F" Type="float"/>
<Value Name="Unknown"/>
<Value Name="DeathBallDamageDelay"/>
</Element>
<Element>
<Key ID="0xC91B0946" Type="int"/>