Added support for dragging/dropping resources; you can use drag/drop to rearrange resources/folders in the resource browser now, and you can drag/drop resources onto resource selector widgets

This commit is contained in:
Aruki 2017-07-14 18:41:56 -06:00
parent fe9a074029
commit dbe8b7922c
25 changed files with 897 additions and 198 deletions

View File

@ -106,10 +106,15 @@ bool MoveFile(const TString& rkOldPath, const TString& rkNewPath)
return false;
}
if (CopyFile(rkOldPath, rkNewPath))
return DeleteFile(rkOldPath);
else
if (Exists(rkNewPath))
{
Log::Error("Unable to move file because there is an existing file at the destination path: " + rkNewPath);
return false;
}
// todo: check return value? Docs don't say what the return value actually is
rename(*rkOldPath, *rkNewPath);
return true;
}
bool MoveDirectory(const TString& rkOldPath, const TString& rkNewPath)
@ -120,10 +125,15 @@ bool MoveDirectory(const TString& rkOldPath, const TString& rkNewPath)
return false;
}
if (CopyDirectory(rkOldPath, rkNewPath))
return DeleteDirectory(rkOldPath, false);
else
if (Exists(rkNewPath))
{
Log::Error("Unable to move directory because there is an existing directory at the destination path: " + rkNewPath);
return false;
}
// todo: check return value? Docs don't say what the return value actually is
rename(*rkOldPath, *rkNewPath);
return true;
}
bool DeleteFile(const TString& rkFilePath)

View File

@ -79,7 +79,7 @@ void ApplyGeneratedName(CResourceEntry *pEntry, const TString& rkDir, const TStr
if (pEntry->Directory() == pNewDir && pEntry->Name() == NewName) return;
// Perform the move
bool Success = pEntry->Move(pNewDir->FullPath(), NewName, true, true);
bool Success = pEntry->MoveAndRename(pNewDir->FullPath(), NewName, true, true);
ASSERT(Success);
}
@ -100,7 +100,7 @@ void GenerateAssetNames(CGameProject *pProj)
TString NewDir = (HasCustomDir ? It->DirectoryPath() : pStore->DefaultResourceDirPath());
TString NewName = (HasCustomName ? It->Name() : It->ID().ToString());
It->Move(NewDir, NewName, true, true);
It->MoveAndRename(NewDir, NewName, true, true);
}
#endif
@ -656,7 +656,7 @@ void GenerateAssetNames(CGameProject *pProj)
}
#endif
pStore->RootDirectory()->RemoveEmptySubdirectories();
pStore->RootDirectory()->DeleteEmptySubdirectories();
pStore->ConditionalSaveStore();
Log::Write("*** Asset Name Generation FINISHED ***");
}

View File

@ -476,7 +476,7 @@ bool CResourceEntry::CanMoveTo(const TString& rkDir, const TString& rkName)
return true;
}
bool CResourceEntry::Move(const TString& rkDir, const TString& rkName, bool IsAutoGenDir /*= false*/, bool IsAutoGenName /*= false*/)
bool CResourceEntry::MoveAndRename(const TString& rkDir, const TString& rkName, bool IsAutoGenDir /*= false*/, bool IsAutoGenName /*= false*/)
{
if (!CanMoveTo(rkDir, rkName)) return false;
@ -516,7 +516,7 @@ bool CResourceEntry::Move(const TString& rkDir, const TString& rkName, bool IsAu
// Move raw file to new location
if (FileUtil::Exists(OldRawPath))
{
FSMoveSuccess = FileUtil::CopyFile(OldRawPath, NewRawPath);
FSMoveSuccess = FileUtil::MoveFile(OldRawPath, NewRawPath);
if (!FSMoveSuccess)
MoveFailReason = TString::Format("Failed to move raw file to new destination (%s --> %s)", *OldRawPath, *NewRawPath);
@ -525,11 +525,10 @@ bool CResourceEntry::Move(const TString& rkDir, const TString& rkName, bool IsAu
// Move cooked file to new location
if (FSMoveSuccess && FileUtil::Exists(OldCookedPath))
{
FSMoveSuccess = FileUtil::CopyFile(OldCookedPath, NewCookedPath);
FSMoveSuccess = FileUtil::MoveFile(OldCookedPath, NewCookedPath);
if (!FSMoveSuccess)
{
FileUtil::DeleteFile(NewRawPath);
MoveFailReason = TString::Format("Failed to move cooked file to new destination (%s --> %s)", *OldCookedPath, *NewCookedPath);
}
}
@ -539,12 +538,10 @@ bool CResourceEntry::Move(const TString& rkDir, const TString& rkName, bool IsAu
{
if (FileUtil::Exists(OldMetaPath))
{
FSMoveSuccess = FileUtil::CopyFile(OldMetaPath, NewMetaPath);
FSMoveSuccess = FileUtil::MoveFile(OldMetaPath, NewMetaPath);
if (!FSMoveSuccess)
{
FileUtil::DeleteFile(NewRawPath);
FileUtil::DeleteFile(NewCookedPath);
MoveFailReason = TString::Format("Failed to move metadata file to new destination (%s --> %s)", *OldMetaPath, *NewMetaPath);
}
}
@ -582,10 +579,6 @@ bool CResourceEntry::Move(const TString& rkDir, const TString& rkName, bool IsAu
mpStore->SetCacheDirty();
mCachedUppercaseName = rkName.ToUpper();
FileUtil::DeleteFile(OldRawPath);
FileUtil::DeleteFile(OldCookedPath);
FileUtil::DeleteFile(OldMetaPath);
SaveMetadata();
return true;
}
@ -597,10 +590,27 @@ bool CResourceEntry::Move(const TString& rkDir, const TString& rkName, bool IsAu
mpDirectory = pOldDir;
mName = OldName;
mpStore->ConditionalDeleteDirectory(pNewDir, false);
if (FileUtil::Exists(NewRawPath))
FileUtil::MoveFile(NewRawPath, OldRawPath);
if (FileUtil::Exists(NewCookedPath))
FileUtil::MoveFile(NewCookedPath, OldCookedPath);
return false;
}
}
bool CResourceEntry::Move(const TString& rkDir, bool IsAutoGenDir /*= false*/)
{
return MoveAndRename(rkDir, mName, IsAutoGenDir, false);
}
bool CResourceEntry::Rename(const TString& rkName, bool IsAutoGenName /*= false*/)
{
return MoveAndRename(mpDirectory->FullPath(), rkName, false, IsAutoGenName);
}
CGameProject* CResourceEntry::Project() const
{
return mpStore ? mpStore->Project() : nullptr;

View File

@ -74,7 +74,10 @@ public:
CResource* LoadCooked(IInputStream& rInput);
bool Unload();
bool CanMoveTo(const TString& rkDir, const TString& rkName);
bool Move(const TString& rkDir, const TString& rkName, bool IsAutoGenDir = false, bool IsAutoGenName = false);
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);
CGameProject* Project() const;
EGame Game() const;

View File

@ -48,7 +48,7 @@ CResourceStore::~CResourceStore()
void RecursiveGetListOfEmptyDirectories(CVirtualDirectory *pDir, TStringList& rOutList)
{
// Helper function for SerializeResourceDatabase
if (pDir->IsEmpty())
if (pDir->IsEmpty(false))
{
rOutList.push_back(pDir->FullPath());
}
@ -233,7 +233,7 @@ void CResourceStore::CreateVirtualDirectory(const TString& rkPath)
void CResourceStore::ConditionalDeleteDirectory(CVirtualDirectory *pDir, bool Recurse)
{
if (pDir->IsEmpty() && !pDir->IsRoot())
if (pDir->IsEmpty(true) && !pDir->IsRoot())
{
CVirtualDirectory *pParent = pDir->Parent();
pParent->RemoveChildDirectory(pDir);
@ -366,8 +366,9 @@ bool CResourceStore::BuildFromDirectory(bool ShouldGenerateCacheFile)
void CResourceStore::RebuildFromDirectory()
{
ASSERT(mpProj != nullptr);
mpProj->AudioManager()->ClearAssets();
if (mpProj)
mpProj->AudioManager()->ClearAssets();
ClearDatabase();
BuildFromDirectory(true);
}
@ -596,7 +597,7 @@ void CResourceStore::ImportNamesFromPakContentsTxt(const TString& rkTxtPath, boo
TString Name = Path.GetFileName(false);
if (Dir.IsEmpty()) Dir = pEntry->DirectoryPath();
pEntry->Move(Dir, Name);
pEntry->MoveAndRename(Dir, Name);
}
// Save

View File

@ -26,22 +26,37 @@ CVirtualDirectory::~CVirtualDirectory()
delete mSubdirectories[iSub];
}
bool CVirtualDirectory::IsEmpty() const
bool CVirtualDirectory::IsEmpty(bool CheckFilesystem) const
{
if (!mResources.empty()) return false;
if (!mResources.empty())
return false;
for (u32 iSub = 0; iSub < mSubdirectories.size(); iSub++)
if (!mSubdirectories[iSub]->IsEmpty()) return false;
if (!mSubdirectories[iSub]->IsEmpty(CheckFilesystem))
return false;
if (CheckFilesystem && !FileUtil::IsEmpty( AbsolutePath() ))
return false;
return true;
}
bool CVirtualDirectory::IsDescendantOf(CVirtualDirectory *pDir) const
{
return mpParent && (mpParent == pDir || mpParent->IsDescendantOf(pDir));
}
TString CVirtualDirectory::FullPath() const
{
if (IsRoot())
return "";
else
return (mpParent && !mpParent->IsRoot() ? mpParent->FullPath() + mName + '/' : mName + '/');
return (mpParent ? mpParent->FullPath() + mName : mName) + '/';
}
TString CVirtualDirectory::AbsolutePath() const
{
return mpStore->ResourcesDir() + FullPath();
}
CVirtualDirectory* CVirtualDirectory::GetRoot()
@ -191,24 +206,26 @@ bool CVirtualDirectory::AddChild(const TString &rkPath, CResourceEntry *pEntry)
return false;
}
bool CVirtualDirectory::AddChild(CVirtualDirectory *pDir)
{
if (pDir->Parent() != this) return false;
if (FindChildDirectory(pDir->Name(), false) != nullptr) return false;
mSubdirectories.push_back(pDir);
std::sort(mSubdirectories.begin(), mSubdirectories.end(), [](CVirtualDirectory *pLeft, CVirtualDirectory *pRight) -> bool {
return (pLeft->Name().ToUpper() < pRight->Name().ToUpper());
});
return true;
}
bool CVirtualDirectory::RemoveChildDirectory(CVirtualDirectory *pSubdir)
{
ASSERT(pSubdir->IsEmpty());
for (auto It = mSubdirectories.begin(); It != mSubdirectories.end(); It++)
{
if (*It == pSubdir)
{
mSubdirectories.erase(It);
// If this is part of the resource store, delete the corresponding filesystem directory
if (mpStore && pSubdir->GetRoot() == mpStore->RootDirectory())
{
TString AbsPath = mpStore->ResourcesDir() + pSubdir->FullPath();
FileUtil::DeleteDirectory(AbsPath, true);
}
delete pSubdir;
return true;
}
}
@ -230,19 +247,73 @@ bool CVirtualDirectory::RemoveChildResource(CResourceEntry *pEntry)
return false;
}
void CVirtualDirectory::RemoveEmptySubdirectories()
bool CVirtualDirectory::Delete()
{
ASSERT(IsEmpty(true) && !IsRoot());
if (IsEmpty(true) && !IsRoot())
{
if (FileUtil::DeleteDirectory(AbsolutePath(), true))
{
if (!mpParent || mpParent->RemoveChildDirectory(this))
{
delete this;
return true;
}
}
}
return false;
}
void CVirtualDirectory::DeleteEmptySubdirectories()
{
for (u32 SubdirIdx = 0; SubdirIdx < mSubdirectories.size(); SubdirIdx++)
{
CVirtualDirectory *pDir = mSubdirectories[SubdirIdx];
if (pDir->IsEmpty())
if (pDir->IsEmpty(true))
{
RemoveChildDirectory(pDir);
pDir->Delete();
SubdirIdx--;
}
else
pDir->RemoveEmptySubdirectories();
pDir->DeleteEmptySubdirectories();
}
}
bool CVirtualDirectory::SetParent(CVirtualDirectory *pParent)
{
ASSERT(!pParent->IsDescendantOf(this));
if (mpParent == pParent) return true;
Log::Write("MOVING DIRECTORY: " + FullPath() + " -> " + pParent->FullPath() + mName + '/');
// Check for a conflict
CVirtualDirectory *pConflictDir = pParent->FindChildDirectory(mName, false);
if (pConflictDir)
{
Log::Error("DIRECTORY MOVE FAILED: Conflicting directory exists at the destination path!");
return false;
}
// Move filesystem contents to new path
TString AbsOldPath = mpStore->ResourcesDir() + FullPath();
TString AbsNewPath = mpStore->ResourcesDir() + pParent->FullPath() + mName + '/';
if (mpParent->RemoveChildDirectory(this) && FileUtil::MoveDirectory(AbsOldPath, AbsNewPath))
{
mpParent = pParent;
mpParent->AddChild(this);
mpStore->SetCacheDirty();
return true;
}
else
{
Log::Error("DIRECTORY MOVE FAILED: Filesystem move operation failed!");
mpParent->AddChild(this);
return false;
}
}

View File

@ -24,16 +24,21 @@ public:
CVirtualDirectory(CVirtualDirectory *pParent, const TString& rkName, CResourceStore *pStore);
~CVirtualDirectory();
bool IsEmpty() const;
bool IsEmpty(bool CheckFilesystem) const;
bool IsDescendantOf(CVirtualDirectory *pDir) const;
TString FullPath() const;
TString AbsolutePath() const;
CVirtualDirectory* GetRoot();
CVirtualDirectory* FindChildDirectory(const TString& rkName, bool AllowCreate);
CResourceEntry* FindChildResource(const TString& rkPath);
CResourceEntry* FindChildResource(const TString& rkName, EResType Type);
bool AddChild(const TString& rkPath, CResourceEntry *pEntry);
bool AddChild(CVirtualDirectory *pDir);
bool RemoveChildDirectory(CVirtualDirectory *pSubdir);
bool RemoveChildResource(CResourceEntry *pEntry);
void RemoveEmptySubdirectories();
bool Delete();
void DeleteEmptySubdirectories();
bool SetParent(CVirtualDirectory *pParent);
static bool IsValidDirectoryName(const TString& rkName);
static bool IsValidDirectoryPath(TString Path);

View File

@ -287,7 +287,10 @@ void CEditorApplication::OnEditorClose()
mEditorWindows.removeOne(pEditor);
delete pEditor;
mpActiveProject->ResourceStore()->DestroyUnreferencedResources();
if (mpActiveProject)
{
mpActiveProject->ResourceStore()->DestroyUnreferencedResources();
}
}
}

View File

@ -64,6 +64,8 @@ signals:
void ActiveProjectChanged(CGameProject *pNewProj);
void AssetsModified();
void PackagesCooked();
void ResourceRenamed(CResourceEntry *pEntry);
void DirectoryRenamed(CVirtualDirectory *pDir);
};
#define gpEdApp static_cast<CEditorApplication*>(qApp)

View File

@ -187,7 +187,11 @@ HEADERS += \
Widgets/CSelectResourcePanel.h \
Widgets/CFilteredResourceModel.h \
ResourceBrowser/CResourceDelegate.h \
ResourceBrowser/CResourceTableContextMenu.h
ResourceBrowser/CResourceTableContextMenu.h \
ResourceBrowser/CResourceMimeData.h \
ResourceBrowser/CResourceTableView.h \
Undo/CMoveResourceCommand.h \
Undo/CMoveDirectoryCommand.h
# Source Files
SOURCES += \
@ -257,7 +261,9 @@ SOURCES += \
CProgressDialog.cpp \
Widgets/CSelectResourcePanel.cpp \
ResourceBrowser/CResourceDelegate.cpp \
ResourceBrowser/CResourceTableContextMenu.cpp
ResourceBrowser/CResourceTableContextMenu.cpp \
ResourceBrowser/CResourceTableModel.cpp \
ResourceBrowser/CResourceTableView.cpp
# UI Files
FORMS += \

View File

@ -4,6 +4,8 @@
#include "CResourceDelegate.h"
#include "CResourceTableContextMenu.h"
#include "Editor/CEditorApplication.h"
#include "Editor/Undo/CMoveDirectoryCommand.h"
#include "Editor/Undo/CMoveResourceCommand.h"
#include <Core/GameProject/AssetNameGeneration.h>
#include <Core/GameProject/CAssetNameMap.h>
@ -28,6 +30,19 @@ CResourceBrowser::CResourceBrowser(QWidget *pParent)
// Hide sorting combo box for now. The size isn't displayed on the UI so this isn't really useful for the end user.
mpUI->SortComboBox->hide();
// Create undo/redo actions
mpUndoAction = new QAction("Undo", this);
mpRedoAction = new QAction("Redo", this);
mpUndoAction->setShortcut( QKeySequence::Undo );
mpRedoAction->setShortcut( QKeySequence::Redo );
addAction(mpUndoAction);
addAction(mpRedoAction);
connect(mpUndoAction, SIGNAL(triggered(bool)), this, SLOT(Undo()));
connect(mpRedoAction, SIGNAL(triggered(bool)), this, SLOT(Redo()));
connect(&mUndoStack, SIGNAL(canUndoChanged(bool)), this, SLOT(UpdateUndoActionStates()));
connect(&mUndoStack, SIGNAL(canRedoChanged(bool)), this, SLOT(UpdateUndoActionStates()));
// Configure display mode buttons
QButtonGroup *pModeGroup = new QButtonGroup(this);
pModeGroup->addButton(mpUI->ResourceTreeButton);
@ -45,6 +60,7 @@ CResourceBrowser::CResourceBrowser(QWidget *pParent)
mpDelegate = new CResourceBrowserDelegate(this);
mpUI->ResourceTableView->setItemDelegate(mpDelegate);
mpUI->ResourceTableView->installEventFilter(this);
// Set up directory tree model
mpDirectoryModel = new CVirtualDirectoryModel(this);
@ -203,6 +219,70 @@ void CResourceBrowser::CreateFilterCheckboxes()
mpFilterBoxesLayout->addSpacerItem(pSpacer);
}
bool CResourceBrowser::MoveResources(const QList<CResourceEntry*>& rkResources, const QList<CVirtualDirectory*>& rkDirectories, CVirtualDirectory *pNewDir)
{
// Check for any conflicts
QList<CResourceEntry*> ConflictingResources;
foreach (CResourceEntry *pEntry, rkResources)
{
if (pNewDir->FindChildResource(pEntry->Name(), pEntry->ResourceType()) != nullptr)
ConflictingResources << pEntry;
}
QList<CVirtualDirectory*> ConflictingDirs;
foreach (CVirtualDirectory *pDir, rkDirectories)
{
if (pNewDir->FindChildDirectory(pDir->Name(), false) != nullptr)
ConflictingDirs << pDir;
}
// If there were conflicts, notify the user of them
if (!ConflictingResources.isEmpty() || !ConflictingDirs.isEmpty())
{
QString ErrorMsg = "Unable to move; the destination directory has conflicting files.\n\n";
foreach (CVirtualDirectory *pDir, ConflictingDirs)
{
ErrorMsg += QString("* %1").arg( TO_QSTRING(pDir->Name()) );
}
foreach (CResourceEntry *pEntry, ConflictingResources)
{
ErrorMsg += QString("* %1.%2\n").arg( TO_QSTRING(pEntry->Name()) ).arg( TO_QSTRING(pEntry->CookedExtension().ToString()) );
}
UICommon::ErrorMsg(this, ErrorMsg);
return false;
}
// Create undo actions to actually perform the moves
mUndoStack.beginMacro("Move Resources");
foreach (CVirtualDirectory *pDir, rkDirectories)
mUndoStack.push( new CMoveDirectoryCommand(mpStore, pDir, pNewDir) );
foreach (CResourceEntry *pEntry, rkResources)
mUndoStack.push( new CMoveResourceCommand(pEntry, pNewDir) );
mUndoStack.endMacro();
return true;
}
bool CResourceBrowser::eventFilter(QObject *pWatched, QEvent *pEvent)
{
if (pWatched == mpUI->ResourceTableView)
{
if (pEvent->type() == QEvent::FocusIn || pEvent->type() == QEvent::FocusOut)
{
UpdateUndoActionStates();
}
}
return false;
}
void CResourceBrowser::RefreshResources()
{
// Fill resource table
@ -421,7 +501,7 @@ void CResourceBrowser::ImportAssetNameMap()
bool AutoDir, AutoName;
if (Map.GetNameInfo(It->ID(), Dir, Name, AutoDir, AutoName))
It->Move(Dir, Name, AutoDir, AutoName);
It->MoveAndRename(Dir, Name, AutoDir, AutoName);
}
mpStore->ConditionalSaveStore();
@ -520,3 +600,24 @@ void CResourceBrowser::OnFilterTypeBoxTicked(bool Checked)
mpProxyModel->invalidate();
ReentrantGuard = false;
}
void CResourceBrowser::UpdateUndoActionStates()
{
// Make sure that the undo actions are only enabled when the table view has focus.
// This is to prevent them from conflicting with world editor undo/redo actions.
bool HasFocus = (mpUI->ResourceTableView->hasFocus());
mpUndoAction->setEnabled( HasFocus && mUndoStack.canUndo() );
mpRedoAction->setEnabled( HasFocus && mUndoStack.canRedo() );
}
void CResourceBrowser::Undo()
{
mUndoStack.undo();
UpdateUndoActionStates();
}
void CResourceBrowser::Redo()
{
mUndoStack.redo();
UpdateUndoActionStates();
}

View File

@ -7,6 +7,7 @@
#include "CVirtualDirectoryModel.h"
#include <QCheckBox>
#include <QTimer>
#include <QUndoStack>
#include <QVBoxLayout>
namespace Ui {
@ -41,6 +42,12 @@ class CResourceBrowser : public QWidget
};
QList<SResourceType> mTypeList;
// Undo/Redo
QUndoStack mUndoStack;
QAction *mpUndoAction;
QAction *mpRedoAction;
QWidget *mpActionContainerWidget;
public:
explicit CResourceBrowser(QWidget *pParent = 0);
~CResourceBrowser();
@ -49,6 +56,11 @@ public:
void SelectDirectory(CVirtualDirectory *pDir);
void CreateFilterCheckboxes();
bool MoveResources(const QList<CResourceEntry*>& rkResources, const QList<CVirtualDirectory*>& rkDirectories, CVirtualDirectory *pNewDir);
// Interface
bool eventFilter(QObject *pWatched, QEvent *pEvent);
// Accessors
inline CResourceStore* CurrentStore() const { return mpStore; }
inline CResourceEntry* SelectedEntry() const { return mpSelectedEntry; }
@ -80,6 +92,10 @@ public slots:
void ResetTypeFilter();
void OnFilterTypeBoxTicked(bool Checked);
void UpdateUndoActionStates();
void Undo();
void Redo();
signals:
void SelectedResourceChanged(CResourceEntry *pNewRes);
};

View File

@ -257,7 +257,7 @@
</item>
</layout>
</widget>
<widget class="QTableView" name="ResourceTableView">
<widget class="CResourceTableView" name="ResourceTableView">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -275,6 +275,15 @@
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragDrop</enum>
</property>
<property name="defaultDropAction">
<enum>Qt::MoveAction</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
@ -310,6 +319,11 @@
<extends>QLineEdit</extends>
<header>Editor/Widgets/CTimedLineEdit.h</header>
</customwidget>
<customwidget>
<class>CResourceTableView</class>
<extends>QTableView</extends>
<header>Editor/ResourceBrowser/CResourceTableView.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../Icons.qrc"/>

View File

@ -0,0 +1,31 @@
#ifndef CRESOURCEMIMEDATA_H
#define CRESOURCEMIMEDATA_H
#include <Core/GameProject/CResourceEntry.h>
#include <Core/GameProject/CVirtualDirectory.h>
#include <QMimeData>
class CResourceMimeData : public QMimeData
{
Q_OBJECT
QList<CResourceEntry*> mEntries;
QList<CVirtualDirectory*> mDirectories;
public:
CResourceMimeData(const QList<CResourceEntry*>& rkEntries, const QList<CVirtualDirectory*>& rkDirectories)
: QMimeData()
, mEntries(rkEntries)
, mDirectories(rkDirectories)
{}
CResourceMimeData(CResourceEntry *pEntry)
: QMimeData()
{
mEntries << pEntry;
}
const QList<CResourceEntry*>& Resources() const { return mEntries; }
const QList<CVirtualDirectory*>& Directories() const { return mDirectories; }
};
#endif // CRESOURCEMIMEDATA_H

View File

@ -29,18 +29,22 @@ void CResourceTableContextMenu::ShowMenu(const QPoint& rkPos)
{
// Fetch the entry/directory
QModelIndex ProxyIndex = mpTable->indexAt(rkPos);
mIndex = mpProxy->mapToSource(ProxyIndex);
mpEntry = mpModel->IndexEntry(mIndex);
mpDirectory = mpModel->IndexDirectory(mIndex);
// Show/hide menu options
bool IsRes = (mpEntry != nullptr);
mpOpenInExternalAppAction->setVisible(IsRes);
mpCopyIDAction->setVisible(IsRes);
if (ProxyIndex.isValid())
{
mIndex = mpProxy->mapToSource(ProxyIndex);
mpEntry = mpModel->IndexEntry(mIndex);
mpDirectory = mpModel->IndexDirectory(mIndex);
// Exec menu
QPoint GlobalPos = mpTable->viewport()->mapToGlobal(rkPos);
exec(GlobalPos);
// Show/hide menu options
bool IsRes = (mpEntry != nullptr);
mpOpenInExternalAppAction->setVisible(IsRes);
mpCopyIDAction->setVisible(IsRes);
// Exec menu
QPoint GlobalPos = mpTable->viewport()->mapToGlobal(rkPos);
exec(GlobalPos);
}
}
// Menu Options

View File

@ -0,0 +1,253 @@
#include "CResourceTableModel.h"
#include "CResourceBrowser.h"
#include "CResourceMimeData.h"
CResourceTableModel::CResourceTableModel(QObject *pParent /*= 0*/)
: QAbstractTableModel(pParent)
, mpCurrentDir(nullptr)
{
connect(gpEdApp, SIGNAL(ResourceRenamed(CResourceEntry*)), this, SLOT(OnResourceRenamed(CResourceEntry*)));
connect(gpEdApp, SIGNAL(DirectoryRenamed(CVirtualDirectory*)), this, SLOT(OnDirectoryRenamed(CVirtualDirectory*)));
}
// ************ INTERFACE ************
int CResourceTableModel::rowCount(const QModelIndex&) const
{
return mDirectories.size() + mEntries.size();
}
int CResourceTableModel::columnCount(const QModelIndex&) const
{
return 1;
}
QVariant CResourceTableModel::data(const QModelIndex& rkIndex, int Role) const
{
if (rkIndex.column() != 0)
return QVariant::Invalid;
// Directory
if (IsIndexDirectory(rkIndex))
{
CVirtualDirectory *pDir = IndexDirectory(rkIndex);
if (Role == Qt::DisplayRole || Role == Qt::ToolTipRole)
return (mHasParent && rkIndex.row() == 0 ? ".." : TO_QSTRING(pDir->Name()));
else if (Role == Qt::DecorationRole)
return QIcon(":/icons/Open_24px.png");
else
return QVariant::Invalid;
}
// Resource
CResourceEntry *pEntry = IndexEntry(rkIndex);
if (Role == Qt::DisplayRole)
return TO_QSTRING(pEntry->Name());
else if (Role == Qt::ToolTipRole)
return TO_QSTRING(pEntry->CookedAssetPath(true));
else if (Role == Qt::DecorationRole)
return QIcon(":/icons/Sphere Preview.png");
return QVariant::Invalid;
}
Qt::ItemFlags CResourceTableModel::flags(const QModelIndex& rkIndex) const
{
Qt::ItemFlags Out = Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled;
if (IsIndexDirectory(rkIndex))
Out |= Qt::ItemIsDropEnabled;
return Out;
}
bool CResourceTableModel::canDropMimeData(const QMimeData *pkData, Qt::DropAction, int Row, int Column, const QModelIndex& rkParent) const
{
const CResourceMimeData *pkMimeData = qobject_cast<const CResourceMimeData*>(pkData);
if (pkMimeData)
{
// Make sure we're dropping onto a directory
QModelIndex Index = (rkParent.isValid() ? rkParent : index(Row, Column, rkParent));
if (Index.isValid())
{
CVirtualDirectory *pDir = IndexDirectory(Index);
if (pDir)
{
// Make sure this directory isn't part of the mime data, or a subdirectory of a directory in the mime data
foreach (CVirtualDirectory *pMimeDir, pkMimeData->Directories())
{
if (pDir == pMimeDir || pDir->IsDescendantOf(pMimeDir))
return false;
}
// Valid directory
return true;
}
}
else return false;
}
return false;
}
bool CResourceTableModel::dropMimeData(const QMimeData *pkData, Qt::DropAction, int Row, int Column, const QModelIndex& rkParent)
{
const CResourceMimeData *pkMimeData = qobject_cast<const CResourceMimeData*>(pkData);
QModelIndex Index = (rkParent.isValid() ? rkParent : index(Row, Column, rkParent));
CVirtualDirectory *pDir = IndexDirectory(Index);
ASSERT(pDir);
gpEdApp->ResourceBrowser()->MoveResources( pkMimeData->Resources(), pkMimeData->Directories(), pDir );
return true;
}
QMimeData* CResourceTableModel::mimeData(const QModelIndexList& rkIndexes) const
{
if (rkIndexes.isEmpty())
return nullptr;
QList<CResourceEntry*> Resources;
QList<CVirtualDirectory*> Dirs;
foreach(QModelIndex Index, rkIndexes)
{
CResourceEntry *pEntry = IndexEntry(Index);
CVirtualDirectory *pDir = IndexDirectory(Index);
if (pEntry) Resources << pEntry;
else Dirs << pDir;
}
return new CResourceMimeData(Resources, Dirs);
}
Qt::DropActions CResourceTableModel::supportedDragActions() const
{
return Qt::MoveAction | Qt::CopyAction;
}
Qt::DropActions CResourceTableModel::supportedDropActions() const
{
return Qt::MoveAction;
}
// ************ FUNCTIONALITY ************
QModelIndex CResourceTableModel::GetIndexForEntry(CResourceEntry *pEntry) const
{
if (mEntryIndexMap.contains(pEntry))
return index(mEntryIndexMap[pEntry] + mDirectories.size(), 0, QModelIndex());
else
return QModelIndex();
}
CResourceEntry* CResourceTableModel::IndexEntry(const QModelIndex& rkIndex) const
{
int Index = rkIndex.row() - mDirectories.size();
return (Index >= 0 ? mEntries[Index] : nullptr);
}
CVirtualDirectory* CResourceTableModel::IndexDirectory(const QModelIndex& rkIndex) const
{
return (IsIndexDirectory(rkIndex) ? mDirectories[rkIndex.row()] : nullptr);
}
bool CResourceTableModel::IsIndexDirectory(const QModelIndex& rkIndex) const
{
return rkIndex.row() >= 0 && rkIndex.row() < mDirectories.size();
}
void CResourceTableModel::FillEntryList(CVirtualDirectory *pDir, bool AssetListMode)
{
beginResetModel();
mEntries.clear();
mDirectories.clear();
mEntryIndexMap.clear();
mHasParent = false;
if (pDir)
{
// In filesystem mode, show only subdirectories and assets in the current directory.
if (!AssetListMode)
{
if (!pDir->IsRoot())
{
mDirectories << pDir->Parent();
mHasParent = true;
}
for (u32 iDir = 0; iDir < pDir->NumSubdirectories(); iDir++)
mDirectories << pDir->SubdirectoryByIndex(iDir);
for (u32 iRes = 0; iRes < pDir->NumResources(); iRes++)
{
CResourceEntry *pEntry = pDir->ResourceByIndex(iRes);
if (pEntry->TypeInfo()->IsVisibleInBrowser() && !pEntry->IsHidden())
{
mEntryIndexMap[pEntry] = mEntries.size();
mEntries << pEntry;
}
}
}
// In asset list mode, do not show subdirectories and show all assets in current directory + all subdirectories.
else
RecursiveAddDirectoryContents(pDir);
}
endResetModel();
}
void CResourceTableModel::RecursiveAddDirectoryContents(CVirtualDirectory *pDir)
{
for (u32 iRes = 0; iRes < pDir->NumResources(); iRes++)
{
CResourceEntry *pEntry = pDir->ResourceByIndex(iRes);
if (pEntry->TypeInfo()->IsVisibleInBrowser() && !pEntry->IsHidden())
{
mEntryIndexMap[pEntry] = mEntries.size();
mEntries << pEntry;
}
}
for (u32 iDir = 0; iDir < pDir->NumSubdirectories(); iDir++)
RecursiveAddDirectoryContents(pDir->SubdirectoryByIndex(iDir));
}
void CResourceTableModel::OnResourceRenamed(CResourceEntry *pEntry)
{
if (mEntryIndexMap.contains(pEntry))
{
int Index = mEntries.indexOf(pEntry);
int Row = Index + mDirectories.size();
beginRemoveRows(QModelIndex(), Row, Row);
mEntries.removeAt(Index);
mEntryIndexMap.remove(pEntry);
endRemoveRows();
}
}
void CResourceTableModel::OnDirectoryRenamed(CVirtualDirectory *pDir)
{
for (int DirIdx = 0; DirIdx < mDirectories.size(); DirIdx++)
{
if (mDirectories[DirIdx] == pDir)
{
beginRemoveRows(QModelIndex(), DirIdx, DirIdx);
mDirectories.removeAt(DirIdx);
endRemoveRows();
}
}
}

View File

@ -13,150 +13,45 @@ class CResourceTableModel : public QAbstractTableModel
{
Q_OBJECT
CVirtualDirectory *mpCurrentDir;
QList<CVirtualDirectory*> mDirectories;
QList<CResourceEntry*> mEntries;
QMap<CResourceEntry*, int> mEntryIndexMap;
bool mHasParent;
public:
CResourceTableModel(QObject *pParent = 0)
: QAbstractTableModel(pParent)
{}
CResourceTableModel(QObject *pParent = 0);
int rowCount(const QModelIndex& /*rkParent*/) const
{
return mDirectories.size() + mEntries.size();
}
// Interface
int rowCount(const QModelIndex& /*rkParent*/) const;
int columnCount(const QModelIndex& /*rkParent*/) const;
QVariant data(const QModelIndex& rkIndex, int Role) const;
Qt::ItemFlags flags(const QModelIndex& rkIndex) const;
int columnCount(const QModelIndex& /*rkParent*/) const
{
return 1;
}
QVariant data(const QModelIndex& rkIndex, int Role) const
{
if (rkIndex.column() != 0)
return QVariant::Invalid;
// Directory
if (IsIndexDirectory(rkIndex))
{
CVirtualDirectory *pDir = IndexDirectory(rkIndex);
if (Role == Qt::DisplayRole || Role == Qt::ToolTipRole)
return (mHasParent && rkIndex.row() == 0 ? ".." : TO_QSTRING(pDir->Name()));
else if (Role == Qt::DecorationRole)
return QIcon(":/icons/Open_24px.png");
else
return QVariant::Invalid;
}
// Resource
CResourceEntry *pEntry = IndexEntry(rkIndex);
if (Role == Qt::DisplayRole)
return TO_QSTRING(pEntry->Name());
else if (Role == Qt::ToolTipRole)
return TO_QSTRING(pEntry->CookedAssetPath(true));
else if (Role == Qt::DecorationRole)
return QIcon(":/icons/Sphere Preview.png");
return QVariant::Invalid;
}
QModelIndex GetIndexForEntry(CResourceEntry *pEntry) const
{
if (mEntryIndexMap.contains(pEntry))
return index(mEntryIndexMap[pEntry] + mDirectories.size(), 0, QModelIndex());
else
return QModelIndex();
}
CResourceEntry* IndexEntry(const QModelIndex& rkIndex) const
{
int Index = rkIndex.row() - mDirectories.size();
return (Index >= 0 ? mEntries[Index] : nullptr);
}
CVirtualDirectory* IndexDirectory(const QModelIndex& rkIndex) const
{
return (rkIndex.row() < mDirectories.size() ? mDirectories[rkIndex.row()] : nullptr);
}
bool IsIndexDirectory(const QModelIndex& rkIndex) const
{
return rkIndex.row() < mDirectories.size();
}
void FillEntryList(CVirtualDirectory *pDir, bool AssetListMode)
{
beginResetModel();
mEntries.clear();
mDirectories.clear();
mEntryIndexMap.clear();
mHasParent = false;
if (pDir)
{
// In filesystem mode, show only subdirectories and assets in the current directory.
if (!AssetListMode)
{
if (!pDir->IsRoot())
{
mDirectories << pDir->Parent();
mHasParent = true;
}
for (u32 iDir = 0; iDir < pDir->NumSubdirectories(); iDir++)
mDirectories << pDir->SubdirectoryByIndex(iDir);
for (u32 iRes = 0; iRes < pDir->NumResources(); iRes++)
{
CResourceEntry *pEntry = pDir->ResourceByIndex(iRes);
if (pEntry->TypeInfo()->IsVisibleInBrowser() && !pEntry->IsHidden())
{
mEntryIndexMap[pEntry] = mEntries.size();
mEntries << pEntry;
}
}
}
// In asset list mode, do not show subdirectories and show all assets in current directory + all subdirectories.
else
RecursiveAddDirectoryContents(pDir);
}
endResetModel();
}
bool canDropMimeData(const QMimeData *pkData, Qt::DropAction Action, int Row, int Column, const QModelIndex& rkParent) const;
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
QMimeData* mimeData(const QModelIndexList& rkIndexes) const;
Qt::DropActions supportedDragActions() const;
Qt::DropActions supportedDropActions() const;
// Functionality
QModelIndex GetIndexForEntry(CResourceEntry *pEntry) const;
QModelIndex GetIndexForDirectory(CVirtualDirectory *pDir) const;
CResourceEntry* IndexEntry(const QModelIndex& rkIndex) const;
CVirtualDirectory* IndexDirectory(const QModelIndex& rkIndex) const;
bool IsIndexDirectory(const QModelIndex& rkIndex) const;
void FillEntryList(CVirtualDirectory *pDir, bool AssetListMode);
protected:
void RecursiveAddDirectoryContents(CVirtualDirectory *pDir)
{
for (u32 iRes = 0; iRes < pDir->NumResources(); iRes++)
{
CResourceEntry *pEntry = pDir->ResourceByIndex(iRes);
if (pEntry->TypeInfo()->IsVisibleInBrowser() && !pEntry->IsHidden())
{
mEntryIndexMap[pEntry] = mEntries.size();
mEntries << pEntry;
}
}
for (u32 iDir = 0; iDir < pDir->NumSubdirectories(); iDir++)
RecursiveAddDirectoryContents(pDir->SubdirectoryByIndex(iDir));
}
void RecursiveAddDirectoryContents(CVirtualDirectory *pDir);
public:
// Accessors
inline u32 NumDirectories() const { return mDirectories.size(); }
inline u32 NumResources() const { return mEntries.size(); }
public slots:
void OnResourceRenamed(CResourceEntry *pEntry);
void OnDirectoryRenamed(CVirtualDirectory *pDir);
};
#endif // CRESOURCELISTMODEL

View File

@ -0,0 +1,20 @@
#include "CResourceTableView.h"
#include <QDragEnterEvent>
CResourceTableView::CResourceTableView(QWidget *pParent /*= 0*/)
: QTableView(pParent)
{}
void CResourceTableView::dragEnterEvent(QDragEnterEvent *pEvent)
{
// need to reimplement this to fix a bug in QAbstractItemView
if (dragDropMode() == QAbstractItemView::InternalMove &&
(pEvent->source() != this || ((pEvent->possibleActions() & Qt::MoveAction) == 0)) )
return;
if (pEvent->possibleActions() & model()->supportedDropActions())
{
pEvent->accept();
setState(QAbstractItemView::DraggingState);
}
}

View File

@ -0,0 +1,15 @@
#ifndef CRESOURCETABLEVIEW_H
#define CRESOURCETABLEVIEW_H
#include <QTableView>
class CResourceTableView : public QTableView
{
Q_OBJECT
public:
explicit CResourceTableView(QWidget *pParent = 0);
void dragEnterEvent(QDragEnterEvent *pEvent);
};
#endif // CRESOURCETABLEVIEW_H

View File

@ -0,0 +1,52 @@
#ifndef CMOVEDIRECTORYCOMMAND_H
#define CMOVEDIRECTORYCOMMAND_H
#include "IUndoCommand.h"
#include "Editor/CEditorApplication.h"
#include <Core/GameProject/CResourceStore.h>
#include <Core/GameProject/CVirtualDirectory.h>
class CMoveDirectoryCommand : public IUndoCommand
{
CResourceStore *mpStore;
TString mTargetDir;
TString mOldParent;
TString mNewParent;
public:
CMoveDirectoryCommand(CResourceStore *pStore, CVirtualDirectory *pDir, CVirtualDirectory *pNewParent)
: IUndoCommand("Move Directory")
, mpStore(pStore)
, mTargetDir(pDir->FullPath())
, mOldParent(pDir->Parent()->FullPath())
, mNewParent(pNewParent->FullPath())
{}
void undo()
{
CVirtualDirectory *pDir = mpStore->GetVirtualDirectory(mTargetDir, false);
CVirtualDirectory *pParent = mpStore->GetVirtualDirectory(mOldParent, false);
ASSERT(pDir && pParent);
pDir->SetParent(pParent);
mTargetDir = pDir->FullPath();
gpEdApp->DirectoryRenamed(pDir);
}
void redo()
{
CVirtualDirectory *pDir = mpStore->GetVirtualDirectory(mTargetDir, false);
CVirtualDirectory *pParent = mpStore->GetVirtualDirectory(mNewParent, false);
ASSERT(pDir && pParent);
pDir->SetParent(pParent);
mTargetDir = pDir->FullPath();
gpEdApp->DirectoryRenamed(pDir);
}
bool AffectsCleanState() const { return false; }
};
#endif // CMOVEDIRECTORYCOMMAND_H

View File

@ -0,0 +1,45 @@
#ifndef CMOVERESOURCECOMMAND_H
#define CMOVERESOURCECOMMAND_H
#include "IUndoCommand.h"
#include "Editor/CEditorApplication.h"
#include <Core/GameProject/CResourceEntry.h>
class CMoveResourceCommand : public IUndoCommand
{
CResourceEntry *mpEntry;
TString mOldDirPath;
TString mNewDirPath;
bool mOldDirAutoGenerated;
public:
CMoveResourceCommand(CResourceEntry *pEntry, CVirtualDirectory *pNewDir)
: IUndoCommand("Move Resource")
, mpEntry(pEntry)
, mOldDirPath(pEntry->DirectoryPath())
, mNewDirPath(pNewDir->FullPath())
, mOldDirAutoGenerated(pEntry->HasFlag(eREF_AutoResDir))
{}
void undo()
{
bool Success = mpEntry->Move(mOldDirPath, mOldDirAutoGenerated);
ASSERT(Success); // todo better error handling
gpEdApp->ResourceRenamed(mpEntry);
}
void redo()
{
// note: it doesn't matter if the new directory was auto-generated, since the
// purpose of tracking that flag is to detect which resources have been auto-categorized.
// if this is being called, even if the new directory is auto-generated, it means the
// user is intentionally placing it here, so it should be treated as a custom directory
bool Success = mpEntry->Move(mNewDirPath);
ASSERT(Success); // todo better error handling
gpEdApp->ResourceRenamed(mpEntry);
}
bool AffectsCleanState() const { return false; }
};
#endif // CMOVERESOURCECOMMAND_H

View File

@ -3,17 +3,26 @@
#include "Editor/CEditorApplication.h"
#include "Editor/UICommon.h"
#include "Editor/ResourceBrowser/CResourceBrowser.h"
#include "Editor/ResourceBrowser/CResourceMimeData.h"
#include <Core/GameProject/CResourceStore.h>
#include <Core/Resource/CResource.h>
#include <QAction>
#include <QClipboard>
#include <QDrag>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QDropEvent>
#include <QMenu>
CResourceSelector::CResourceSelector(QWidget *pParent /*= 0*/)
: QWidget(pParent)
, mpResEntry(nullptr)
, mIsEditable(true)
, mIsDragging(false)
{
setAcceptDrops(true);
setContextMenuPolicy(Qt::CustomContextMenu);
// Set up UI
@ -49,6 +58,9 @@ CResourceSelector::CResourceSelector(QWidget *pParent /*= 0*/)
mpLayout->setContentsMargins(0, 0, 0, 0);
setLayout(mpLayout);
// Set up event filter
mpResNameButton->installEventFilter(this);
// UI Connections
connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(CreateContextMenu(QPoint)));
connect(mpResNameButton, SIGNAL(clicked()), this, SLOT(Find()));
@ -110,22 +122,123 @@ void CResourceSelector::SetTypeFilter(EGame Game, const TString& rkTypeList)
void CResourceSelector::SetResource(const CAssetID& rkID)
{
mpResEntry = gpResourceStore->FindEntry(rkID);
OnResourceChanged();
CResourceEntry *pNewEntry = gpResourceStore->FindEntry(rkID);
if (mpResEntry != pNewEntry)
{
mpResEntry = pNewEntry;
OnResourceChanged();
}
}
void CResourceSelector::SetResource(CResourceEntry *pEntry)
{
mpResEntry = pEntry;
OnResourceChanged();
if (mpResEntry != pEntry)
{
mpResEntry = pEntry;
OnResourceChanged();
}
}
void CResourceSelector::SetResource(CResource *pRes)
{
mpResEntry = (pRes ? pRes->Entry() : nullptr);
OnResourceChanged();
CResourceEntry *pNewEntry = (pRes ? pRes->Entry() : nullptr);
if (mpResEntry != pNewEntry)
{
mpResEntry = pNewEntry;
OnResourceChanged();
}
}
// ************ INTERFACE ************
bool CResourceSelector::eventFilter(QObject *pWatched, QEvent *pEvent)
{
if (pWatched == mpResNameButton)
{
if (pEvent->type() == QEvent::MouseButtonPress)
{
QMouseEvent *pMouseEvent = static_cast<QMouseEvent*>(pEvent);
mousePressEvent(pMouseEvent);
return false;
}
else if (pEvent->type() == QEvent::MouseButtonDblClick)
{
QMouseEvent *pMouseEvent = static_cast<QMouseEvent*>(pEvent);
if (pMouseEvent->button() == Qt::LeftButton)
{
if (mpResEntry)
gpEdApp->EditResource(mpResEntry);
return true;
}
}
}
return false;
}
// ************ DRAG ************
void CResourceSelector::mousePressEvent(QMouseEvent *pEvent)
{
if (mpResNameButton->rect().contains(pEvent->pos()) && pEvent->button() == Qt::LeftButton)
{
mDragStartPosition = pEvent->pos();
mIsDragging = true;
}
}
void CResourceSelector::mouseMoveEvent(QMouseEvent *pEvent)
{
if (mIsDragging)
{
if ( (pEvent->pos() - mDragStartPosition).manhattanLength() >= gpEdApp->startDragDistance() )
{
QDrag *pDrag = new QDrag(this);
CResourceMimeData *pMimeData = new CResourceMimeData(mpResEntry);
pDrag->setMimeData(pMimeData);
pDrag->exec(Qt::CopyAction);
}
}
}
void CResourceSelector::mouseReleaseEvent(QMouseEvent *pEvent)
{
if (pEvent->button() == Qt::LeftButton)
{
mIsDragging = false;
}
}
// ************ DROP *************
void CResourceSelector::dragEnterEvent(QDragEnterEvent *pEvent)
{
// Check whether the mime data is a valid format
if (mIsEditable && (pEvent->possibleActions() & Qt::CopyAction))
{
const CResourceMimeData *pkData = qobject_cast<const CResourceMimeData*>(pEvent->mimeData());
if (pkData && pkData->Directories().isEmpty() && pkData->Resources().size() == 1)
{
CResourceEntry *pEntry = pkData->Resources().front();
if (!pEntry || mTypeFilter.Accepts(pEntry))
pEvent->acceptProposedAction();
}
}
}
void CResourceSelector::dropEvent(QDropEvent *pEvent)
{
// Set the new resource
const CResourceMimeData *pkMimeData = qobject_cast<const CResourceMimeData*>(pEvent->mimeData());
CResourceEntry *pEntry = pkMimeData->Resources().front();
SetResource(pEntry);
}
// ************ SLOTS ************
void CResourceSelector::CreateContextMenu(const QPoint& rkPoint)
{
QMenu Menu;

View File

@ -29,6 +29,10 @@ class CResourceSelector : public QWidget
QAction *mpCopyNameAction;
QAction *mpCopyPathAction;
// Drag and Drop
bool mIsDragging;
QPoint mDragStartPosition;
public:
explicit CResourceSelector(QWidget *pParent = 0);
void SetFrameVisible(bool Visible);
@ -39,6 +43,18 @@ public:
void SetResource(CResourceEntry *pEntry);
void SetResource(CResource *pRes);
// Interface
bool eventFilter(QObject *pWatched, QEvent *pEvent);
// Drag
void mousePressEvent(QMouseEvent *pEvent);
void mouseMoveEvent(QMouseEvent *pEvent);
void mouseReleaseEvent(QMouseEvent *pEvent);
// Drop
void dragEnterEvent(QDragEnterEvent *pEvent);
void dropEvent(QDropEvent *pEvent);
// Accessors
inline CResourceEntry* Entry() const { return mpResEntry; }
inline const CResTypeFilter& TypeFilter() const { return mTypeFilter; }

View File

@ -62,6 +62,13 @@ int main(int argc, char *argv[])
// Create editor resource store
gpEditorStore = new CResourceStore("../resources/");
if (!gpEditorStore->AreAllEntriesValid())
{
Log::Write("Editor store has invalid entries. Rebuilding database...");
gpEditorStore->RebuildFromDirectory();
gpEditorStore->ConditionalSaveStore();
}
// Load templates
CTemplateLoader::LoadGameList();

View File

@ -9,7 +9,7 @@
<struct ID="0x04" name="PatternedInfo" template="Structs/PatternedInfo.xml"/>
<struct ID="0x05" name="ActorParameters" template="Structs/ActorParameters.xml"/>
<property ID="0x06" name="WPSC" type="asset" extensions="WPSC"/>
<property ID="0x07" name="Model" type="asset" extensions="CMDL"/>
<property ID="0x07" name="Bomb Model" type="asset" extensions="CMDL"/>
<struct ID="0x08" name="DamageInfo" template="Structs/DamageInfo.xml"/>
<property ID="0x09" name="Unknown 1" type="float"/>
<property ID="0x0A" name="Unknown 2" type="float"/>
@ -34,6 +34,12 @@
<model source="property">0x05:0x04</model>
<model source="property">0x07</model>
</assets>
<attachments>
<attachment propertyID="0x07" locator="bomb1_LCTR"/>
<attachment propertyID="0x07" locator="bomb2_LCTR"/>
<attachment propertyID="0x07" locator="bomb3_LCTR"/>
<attachment propertyID="0x07" locator="bomb4_LCTR"/>
</attachments>
<rotation_type>enabled</rotation_type>
<scale_type>enabled</scale_type>
</editor>