diff --git a/src/Common/FileUtil.cpp b/src/Common/FileUtil.cpp index 42082df4..3bb1ee1f 100644 --- a/src/Common/FileUtil.cpp +++ b/src/Common/FileUtil.cpp @@ -112,9 +112,8 @@ bool MoveFile(const TString& rkOldPath, const TString& rkNewPath) return false; } - // todo: check return value? Docs don't say what the return value actually is - rename(*rkOldPath, *rkNewPath); - return true; + int Result = rename(*rkOldPath, *rkNewPath); + return (Result == 0); } bool MoveDirectory(const TString& rkOldPath, const TString& rkNewPath) @@ -131,9 +130,8 @@ bool MoveDirectory(const TString& rkOldPath, const TString& rkNewPath) return false; } - // todo: check return value? Docs don't say what the return value actually is - rename(*rkOldPath, *rkNewPath); - return true; + int Result = rename(*rkOldPath, *rkNewPath); + return (Result == 0); } bool DeleteFile(const TString& rkFilePath) @@ -305,8 +303,13 @@ TString SimplifyRelativePath(const TString& rkPath) return Out; } +u32 MaxFileNameLength() +{ + return 255; +} + static const char gskIllegalNameChars[] = { - '<', '>', '\"', '/', '\\', '|', '?', '*' + '<', '>', '\"', '/', '\\', '|', '?', '*', ':' }; TString SanitizeName(TString Name, bool Directory, bool RootDir /*= false*/) @@ -316,8 +319,6 @@ TString SanitizeName(TString Name, bool Directory, bool RootDir /*= false*/) return Name; // Remove illegal characters from path - u32 NumIllegalChars = sizeof(gskIllegalNameChars) / sizeof(char); - for (u32 iChr = 0; iChr < Name.Size(); iChr++) { char Chr = Name[iChr]; @@ -326,21 +327,11 @@ TString SanitizeName(TString Name, bool Directory, bool RootDir /*= false*/) if (Chr >= 0 && Chr <= 31) Remove = true; - // For root, allow colon only as the last character of the name - else if (Chr == ':' && (!RootDir || iChr != Name.Size() - 1)) - Remove = true; + // Allow colon only as the last character of root + bool IsLegalColon = (Chr == ':' && RootDir && iChr == Name.Size() - 1); - else - { - for (u32 iBan = 0; iBan < NumIllegalChars; iBan++) - { - if (Chr == gskIllegalNameChars[iBan]) - { - Remove = true; - break; - } - } - } + if (!IsLegalColon && !IsValidFileNameCharacter(Chr)) + Remove = true; if (Remove) { @@ -376,9 +367,9 @@ TString SanitizeName(TString Name, bool Directory, bool RootDir /*= false*/) Name = Name.ChopFront(NumLeadingSpaces); // Ensure the name is below the character limit - if (Name.Size() > 255) + if (Name.Size() > MaxFileNameLength()) { - u32 ChopNum = Name.Size() - 255; + u32 ChopNum = Name.Size() - MaxFileNameLength(); Name = Name.ChopBack(ChopNum); } @@ -406,6 +397,22 @@ TString SanitizePath(TString Path, bool Directory) return Path; } +bool IsValidFileNameCharacter(char Chr) +{ + static const u32 skNumIllegalChars = sizeof(gskIllegalNameChars) / sizeof(char); + + if (Chr >= 0 && Chr <= 31) + return false; + + for (u32 BanIdx = 0; BanIdx < skNumIllegalChars; BanIdx++) + { + if (Chr == gskIllegalNameChars[BanIdx]) + return false; + } + + return true; +} + bool IsValidName(const TString& rkName, bool Directory, bool RootDir /*= false*/) { // Only accounting for Windows limitations right now. However, this function should @@ -413,11 +420,9 @@ bool IsValidName(const TString& rkName, bool Directory, bool RootDir /*= false*/ if (rkName.IsEmpty()) return false; - if (rkName.Size() > 255) + if (rkName.Size() > MaxFileNameLength()) return false; - u32 NumIllegalChars = sizeof(gskIllegalNameChars) / sizeof(char); - if (Directory && (rkName == "." || rkName == "..")) return true; @@ -426,18 +431,11 @@ bool IsValidName(const TString& rkName, bool Directory, bool RootDir /*= false*/ { char Chr = rkName[iChr]; - if (Chr >= 0 && Chr <= 31) - return false; + // Allow colon only as the last character of root + bool IsLegalColon = (Chr == ':' && RootDir && iChr == rkName.Size() - 1); - // Allow colon only on last character of root - if (Chr == ':' && (!RootDir || iChr != rkName.Size() - 1)) + if (!IsLegalColon && !IsValidFileNameCharacter(Chr)) return false; - - for (u32 iBan = 0; iBan < NumIllegalChars; iBan++) - { - if (Chr == gskIllegalNameChars[iBan]) - return false; - } } if (Directory && (rkName.Back() == ' ' || rkName.Back() == '.')) diff --git a/src/Common/FileUtil.h b/src/Common/FileUtil.h index d6e4097e..e0c13a2f 100644 --- a/src/Common/FileUtil.h +++ b/src/Common/FileUtil.h @@ -28,8 +28,10 @@ TString WorkingDirectory(); TString MakeAbsolute(TString Path); TString MakeRelative(const TString& rkPath, const TString& rkRelativeTo = WorkingDirectory()); TString SimplifyRelativePath(const TString& rkPath); +u32 MaxFileNameLength(); TString SanitizeName(TString Name, bool Directory, bool RootDir = false); TString SanitizePath(TString Path, bool Directory); +bool IsValidFileNameCharacter(char Chr); bool IsValidName(const TString& rkName, bool Directory, bool RootDir = false); bool IsValidPath(const TString& rkPath, bool Directory); void GetDirectoryContents(TString DirPath, TStringList& rOut, bool Recursive = true, bool IncludeFiles = true, bool IncludeDirs = true); diff --git a/src/Core/GameProject/CResourceStore.cpp b/src/Core/GameProject/CResourceStore.cpp index d025ebcb..f30f34cc 100644 --- a/src/Core/GameProject/CResourceStore.cpp +++ b/src/Core/GameProject/CResourceStore.cpp @@ -106,7 +106,13 @@ bool CResourceStore::SerializeDatabaseCache(IArchive& rArc) if (rArc.IsReader()) { for (auto Iter = EmptyDirectories.begin(); Iter != EmptyDirectories.end(); Iter++) - CreateVirtualDirectory(*Iter); + { + // Don't create empty virtual directories that don't actually exist in the filesystem + TString AbsPath = ResourcesDir() + *Iter; + + if (FileUtil::Exists(AbsPath)) + CreateVirtualDirectory(*Iter); + } } return true; diff --git a/src/Core/GameProject/CVirtualDirectory.cpp b/src/Core/GameProject/CVirtualDirectory.cpp index 81880782..d90e24b8 100644 --- a/src/Core/GameProject/CVirtualDirectory.cpp +++ b/src/Core/GameProject/CVirtualDirectory.cpp @@ -247,6 +247,30 @@ bool CVirtualDirectory::RemoveChildResource(CResourceEntry *pEntry) return false; } +bool CVirtualDirectory::Rename(const TString& rkNewName) +{ + Log::Write("MOVING DIRECTORY: " + FullPath() + " -> " + mpParent->FullPath() + rkNewName + '/'); + + if (!IsRoot()) + { + if (!mpParent->FindChildDirectory(rkNewName, false)) + { + TString AbsPath = AbsolutePath(); + TString NewPath = mpParent->AbsolutePath() + rkNewName + "/"; + + if (FileUtil::MoveDirectory(AbsPath, NewPath)) + { + mName = rkNewName; + mpStore->SetCacheDirty(); + return true; + } + } + } + + Log::Error("DIRECTORY MOVE FAILED"); + return false; +} + bool CVirtualDirectory::Delete() { ASSERT(IsEmpty(true) && !IsRoot()); @@ -257,6 +281,7 @@ bool CVirtualDirectory::Delete() { if (!mpParent || mpParent->RemoveChildDirectory(this)) { + mpStore->SetCacheDirty(); delete this; return true; } diff --git a/src/Core/GameProject/CVirtualDirectory.h b/src/Core/GameProject/CVirtualDirectory.h index d0cf8446..52bd2ed7 100644 --- a/src/Core/GameProject/CVirtualDirectory.h +++ b/src/Core/GameProject/CVirtualDirectory.h @@ -36,6 +36,7 @@ public: bool AddChild(CVirtualDirectory *pDir); bool RemoveChildDirectory(CVirtualDirectory *pSubdir); bool RemoveChildResource(CResourceEntry *pEntry); + bool Rename(const TString& rkNewName); bool Delete(); void DeleteEmptySubdirectories(); bool SetParent(CVirtualDirectory *pParent); diff --git a/src/Editor/CEditorApplication.cpp b/src/Editor/CEditorApplication.cpp index cfea825a..64ed0c69 100644 --- a/src/Editor/CEditorApplication.cpp +++ b/src/Editor/CEditorApplication.cpp @@ -240,7 +240,10 @@ void CEditorApplication::TickEditors() mLastUpdate = CTimer::GlobalTime(); double DeltaTime = mLastUpdate - LastUpdate; - // Make sure the resource store cache is up-to-date + // Make sure the resource store caches are up-to-date + if (gpEditorStore) + gpEditorStore->ConditionalSaveStore(); + if (gpResourceStore) gpResourceStore->ConditionalSaveStore(); diff --git a/src/Editor/CFileNameValidator.h b/src/Editor/CFileNameValidator.h new file mode 100644 index 00000000..68556dc2 --- /dev/null +++ b/src/Editor/CFileNameValidator.h @@ -0,0 +1,51 @@ +#ifndef CFILENAMEVALIDATOR_H +#define CFILENAMEVALIDATOR_H + +#include + +#include "UICommon.h" +#include + +class CFileNameValidator : public QValidator +{ + bool mIsDirectory; + +public: + CFileNameValidator(bool IsDirectory, QObject *pParent = 0) + : QValidator(pParent) + , mIsDirectory(IsDirectory) + {} + + QValidator::State validate(QString& rInput, int&) const + { + QValidator::State Out = QValidator::Acceptable; + + if (!FileUtil::IsValidName( TO_TSTRING(rInput), mIsDirectory )) + { + // Uh oh, the input is invalid. Only invalid characters will be considered entirely + // invalid; other errors will be considered intermediate. + Out = QValidator::Intermediate; + + for (int ChrIdx = 0; ChrIdx < rInput.size(); ChrIdx++) + { + char Chr = rInput.at(ChrIdx).toLatin1(); + + if (!FileUtil::IsValidFileNameCharacter(Chr)) + { + Out = QValidator::Invalid; + break; + } + } + } + + return Out; + } + + void fixup(QString& rInput) const + { + TString Sanitized = FileUtil::SanitizeName( TO_TSTRING(rInput), mIsDirectory ); + rInput = TO_QSTRING(Sanitized); + } +}; + +#endif // CFILENAMEVALIDATOR_H diff --git a/src/Editor/Editor.pro b/src/Editor/Editor.pro index 0a8b3a74..3d643f95 100644 --- a/src/Editor/Editor.pro +++ b/src/Editor/Editor.pro @@ -191,7 +191,10 @@ HEADERS += \ ResourceBrowser/CResourceMimeData.h \ ResourceBrowser/CResourceTableView.h \ Undo/CMoveResourceCommand.h \ - Undo/CMoveDirectoryCommand.h + Undo/CMoveDirectoryCommand.h \ + Undo/CRenameResourceCommand.h \ + Undo/CRenameDirectoryCommand.h \ + CFileNameValidator.h # Source Files SOURCES += \ diff --git a/src/Editor/ResourceBrowser/CResourceBrowser.cpp b/src/Editor/ResourceBrowser/CResourceBrowser.cpp index 82558db8..bc4418da 100644 --- a/src/Editor/ResourceBrowser/CResourceBrowser.cpp +++ b/src/Editor/ResourceBrowser/CResourceBrowser.cpp @@ -6,6 +6,8 @@ #include "Editor/CEditorApplication.h" #include "Editor/Undo/CMoveDirectoryCommand.h" #include "Editor/Undo/CMoveResourceCommand.h" +#include "Editor/Undo/CRenameDirectoryCommand.h" +#include "Editor/Undo/CRenameResourceCommand.h" #include #include @@ -220,6 +222,54 @@ void CResourceBrowser::CreateFilterCheckboxes() mpFilterBoxesLayout->addSpacerItem(pSpacer); } +bool CResourceBrowser::RenameResource(CResourceEntry *pEntry, const TString& rkNewName) +{ + if (pEntry->Name() == rkNewName) + return false; + + // Check if the move is valid + if (!pEntry->CanMoveTo(pEntry->DirectoryPath(), rkNewName)) + { + if (pEntry->Directory()->FindChildResource(rkNewName, pEntry->ResourceType()) != nullptr) + { + UICommon::ErrorMsg(this, "Failed to rename; the destination directory has conflicting files!"); + return false; + } + else + { + UICommon::ErrorMsg(this, "Failed to rename; filename is invalid!"); + return false; + } + } + + // Everything seems to be valid; proceed with the rename + mUndoStack.push( new CRenameResourceCommand(pEntry, rkNewName) ); + return true; +} + +bool CResourceBrowser::RenameDirectory(CVirtualDirectory *pDir, const TString& rkNewName) +{ + if (pDir->Name() == rkNewName) + return false; + + if (!CVirtualDirectory::IsValidDirectoryName(rkNewName)) + { + UICommon::ErrorMsg(this, "Failed to rename; directory name is invalid!"); + return false; + } + + // Check for conflicts + if (pDir->Parent()->FindChildDirectory(rkNewName, false) != nullptr) + { + UICommon::ErrorMsg(this, "Failed to rename; the destination directory has a conflicting directory!"); + return false; + } + + // No conflicts, proceed with the rename + mUndoStack.push( new CRenameDirectoryCommand(pDir, rkNewName) ); + return true; +} + bool CResourceBrowser::MoveResources(const QList& rkResources, const QList& rkDirectories, CVirtualDirectory *pNewDir) { // Check for any conflicts @@ -242,7 +292,7 @@ bool CResourceBrowser::MoveResources(const QList& rkResources, // 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"; + QString ErrorMsg = "Failed to move; the destination directory has conflicting files.\n\n"; foreach (CVirtualDirectory *pDir, ConflictingDirs) { diff --git a/src/Editor/ResourceBrowser/CResourceBrowser.h b/src/Editor/ResourceBrowser/CResourceBrowser.h index 2f6ea9db..0396b60b 100644 --- a/src/Editor/ResourceBrowser/CResourceBrowser.h +++ b/src/Editor/ResourceBrowser/CResourceBrowser.h @@ -56,6 +56,8 @@ public: void SelectDirectory(CVirtualDirectory *pDir); void CreateFilterCheckboxes(); + bool RenameResource(CResourceEntry *pEntry, const TString& rkNewName); + bool RenameDirectory(CVirtualDirectory *pDir, const TString& rkNewName); bool MoveResources(const QList& rkResources, const QList& rkDirectories, CVirtualDirectory *pNewDir); // Interface diff --git a/src/Editor/ResourceBrowser/CResourceDelegate.cpp b/src/Editor/ResourceBrowser/CResourceDelegate.cpp index 64fe53c8..eb6f693b 100644 --- a/src/Editor/ResourceBrowser/CResourceDelegate.cpp +++ b/src/Editor/ResourceBrowser/CResourceDelegate.cpp @@ -1,11 +1,16 @@ #include "CResourceDelegate.h" +#include "CResourceBrowser.h" #include "CResourceProxyModel.h" #include "CResourceTableModel.h" +#include "Editor/CFileNameValidator.h" +#include "Editor/UICommon.h" #include +#include #include -struct SResDelegateInfo +// Font Info +struct SResDelegateFontInfo { QFont NameFont; QFont InfoFont; @@ -16,12 +21,12 @@ struct SResDelegateInfo int Margin; int Spacing; - SResDelegateInfo() + SResDelegateFontInfo() : NameFontMetrics(NameFont), InfoFontMetrics(InfoFont) {} }; -SResDelegateInfo GetDelegateInfo(const QStyleOptionViewItem& rkOption) +SResDelegateFontInfo GetFontInfo(const QStyleOptionViewItem& rkOption) { - SResDelegateInfo Info; + SResDelegateFontInfo Info; Info.NameFont = rkOption.font; Info.NameFont.setPointSize( rkOption.font.pointSize() + 1 ); @@ -42,10 +47,61 @@ SResDelegateInfo GetDelegateInfo(const QStyleOptionViewItem& rkOption) return Info; } +// Geometry Info +struct SResDelegateGeometryInfo +{ + QRect InnerRect; + QRect IconRect; + QRect NameStringRect; + QRect InfoStringRect; +}; +SResDelegateGeometryInfo GetGeometryInfo(const SResDelegateFontInfo& rkFontInfo, const QStyleOptionViewItem& rkOption, bool IsDirectory) +{ + SResDelegateGeometryInfo Info; + + // Calculate inner rect + int Margin = rkFontInfo.Margin; + Info.InnerRect = rkOption.rect.adjusted(Margin, Margin, -Margin, -Margin); + + // Calculate icon + int IdealIconSize = CResourceBrowserDelegate::skIconSize; + int IconSize = Math::Min(IdealIconSize, Info.InnerRect.height()); + int IconX = Info.InnerRect.left() + ((IdealIconSize - IconSize) / 2); + int IconY = Info.InnerRect.top() + ((Info.InnerRect.height() - IconSize) / 2); + Info.IconRect = QRect(IconX, IconY, IconSize, IconSize); + + // Calculate name string + int NameX = Info.InnerRect.left() + IdealIconSize + (rkFontInfo.Spacing * 2); + int NameY = Info.InnerRect.top(); + int NameSizeX = Info.InnerRect.right() - NameX; + int NameSizeY = rkFontInfo.NameFontMetrics.height(); + + // Adjust Y for directories to center it in the rect + if (IsDirectory) + { + NameY += (Info.InnerRect.height() - NameSizeY) / 2; + } + + Info.NameStringRect = QRect(NameX, NameY, NameSizeX, NameSizeY); + + // Calculate info string + if (!IsDirectory) + { + int InfoX = NameX; + int InfoY = NameY + NameSizeY + rkFontInfo.Spacing; + int InfoSizeX = NameSizeX; + int InfoSizeY = rkFontInfo.InfoFontMetrics.height(); + Info.InfoStringRect = QRect(InfoX, InfoY, InfoSizeX, InfoSizeY); + } + + return Info; +} + +// Delegate implementation QSize CResourceBrowserDelegate::sizeHint(const QStyleOptionViewItem& rkOption, const QModelIndex&) const { // Get string info - SResDelegateInfo Info = GetDelegateInfo(rkOption); + SResDelegateFontInfo Info = GetFontInfo(rkOption); // Calculate height int Height = (Info.Margin * 2) + Info.NameFontMetrics.height() + Info.Spacing + Info.InfoFontMetrics.height(); @@ -54,20 +110,12 @@ QSize CResourceBrowserDelegate::sizeHint(const QStyleOptionViewItem& rkOption, c void CResourceBrowserDelegate::paint(QPainter *pPainter, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const { - const CResourceProxyModel *pkProxy = qobject_cast(rkIndex.model()); - ASSERT(pkProxy != nullptr); - - const CResourceTableModel *pkModel = qobject_cast(pkProxy->sourceModel()); - ASSERT(pkModel != nullptr); - // Get resource entry - QModelIndex SourceIndex = pkProxy->mapToSource(rkIndex); - CResourceEntry *pEntry = pkModel->IndexEntry(SourceIndex); + CResourceEntry *pEntry = GetIndexEntry(rkIndex); // Initialize - SResDelegateInfo Info = GetDelegateInfo(rkOption); - QRect InnerRect = rkOption.rect.adjusted(Info.Margin, Info.Margin, -Info.Margin, -Info.Margin); - QPoint PainterPos = InnerRect.topLeft(); + SResDelegateFontInfo FontInfo = GetFontInfo(rkOption); + SResDelegateGeometryInfo GeomInfo = GetGeometryInfo(FontInfo, rkOption, pEntry == nullptr); // Draw icon QVariant IconVariant = rkIndex.model()->data(rkIndex, Qt::DecorationRole); @@ -75,43 +123,16 @@ void CResourceBrowserDelegate::paint(QPainter *pPainter, const QStyleOptionViewI if (IconVariant != QVariant::Invalid) { QIcon Icon = IconVariant.value(); - - // Determine icon size. Ideally 24x24 if we have space, but downscale if we don't - int IdealIconSize = 24; - int IconSize = Math::Min(InnerRect.height(), IdealIconSize); - - // Adjust icon position so it's centered in the ideal rect - QPoint IconPos = PainterPos; - IconPos.rx() += (IdealIconSize - IconSize) / 2; - IconPos.ry() += (InnerRect.height() - IconSize) / 2; - - // Paint the icon - QRect IconRect(IconPos, QSize(IconSize, IconSize)); - Icon.paint(pPainter, IconRect); - - PainterPos.rx() += IdealIconSize + Info.Spacing + Info.Spacing; + Icon.paint(pPainter, GeomInfo.IconRect); } - // Calculate rects - if (!pEntry) - PainterPos.ry() += (InnerRect.height() - Info.NameFontMetrics.height()) / 2; - - int ResNameWidth = InnerRect.width() - (PainterPos.x() - InnerRect.left()); - QSize ResNameSize(ResNameWidth, Info.NameFontMetrics.height()); - QRect ResNameRect = QRect(PainterPos, ResNameSize); - PainterPos.ry() += ResNameRect.height() + Info.Spacing; - - int ResInfoWidth = ResNameWidth; - QSize ResInfoSize(ResInfoWidth, Info.InfoFontMetrics.height()); - QRect ResInfoRect = QRect(PainterPos, ResInfoSize); - // Draw resource name - QString ResName = pkModel->data(SourceIndex, Qt::DisplayRole).toString(); - QString ElidedName = Info.NameFontMetrics.elidedText(ResName, Qt::ElideRight, ResNameWidth); + QString ResName = rkIndex.model()->data(rkIndex, Qt::DisplayRole).toString(); + QString ElidedName = FontInfo.NameFontMetrics.elidedText(ResName, Qt::ElideRight, GeomInfo.NameStringRect.width()); - pPainter->setFont(Info.NameFont); - pPainter->setPen(Info.NamePen); - pPainter->drawText(ResNameRect, ElidedName); + pPainter->setFont(FontInfo.NameFont); + pPainter->setPen(FontInfo.NamePen); + pPainter->drawText(GeomInfo.NameStringRect, ElidedName); // Draw resource info string if (pEntry) @@ -121,10 +142,92 @@ void CResourceBrowserDelegate::paint(QPainter *pPainter, const QStyleOptionViewI if (mDisplayAssetIDs) ResInfo.prepend( TO_QSTRING(pEntry->ID().ToString()) + " | " ); - QString ElidedResInfo = Info.InfoFontMetrics.elidedText(ResInfo, Qt::ElideRight, ResInfoWidth); + QString ElidedResInfo = FontInfo.InfoFontMetrics.elidedText(ResInfo, Qt::ElideRight, GeomInfo.InfoStringRect.width()); - pPainter->setFont(Info.InfoFont); - pPainter->setPen(Info.InfoPen); - pPainter->drawText(ResInfoRect, ElidedResInfo); + pPainter->setFont(FontInfo.InfoFont); + pPainter->setPen(FontInfo.InfoPen); + pPainter->drawText(GeomInfo.InfoStringRect, ElidedResInfo); } } + +// Editor +QWidget* CResourceBrowserDelegate::createEditor(QWidget *pParent, const QStyleOptionViewItem&, const QModelIndex& rkIndex) const +{ + bool IsDirectory = (GetIndexDirectory(rkIndex) != nullptr); + + QLineEdit *pLineEdit = new QLineEdit(pParent); + pLineEdit->setValidator( new CFileNameValidator(IsDirectory, pLineEdit) ); + + // Set the max length to 150. Limit should be 255 but FileUtil::MoveFile doesn't + // seem to want to work with filenames that long. Not sure why. + u32 MaxLength = 150; + pLineEdit->setMaxLength(MaxLength); + + return pLineEdit; +} + +void CResourceBrowserDelegate::setEditorData(QWidget *pEditor, const QModelIndex& rkIndex) const +{ + QLineEdit *pLineEdit = static_cast(pEditor); + CResourceEntry *pEntry = GetIndexEntry(rkIndex); + + if (pEntry) + pLineEdit->setText( TO_QSTRING(pEntry->Name()) ); + else + pLineEdit->setText( TO_QSTRING(GetIndexDirectory(rkIndex)->Name()) ); +} + +void CResourceBrowserDelegate::setModelData(QWidget *pEditor, QAbstractItemModel *, const QModelIndex& rkIndex) const +{ + QLineEdit *pLineEdit = static_cast(pEditor); + QString NewName = pLineEdit->text(); + pLineEdit->validator()->fixup(NewName); + + if (!NewName.isEmpty()) + { + CResourceEntry *pEntry = GetIndexEntry(rkIndex); + + if (pEntry) + gpEdApp->ResourceBrowser()->RenameResource(pEntry, TO_TSTRING(NewName)); + else + gpEdApp->ResourceBrowser()->RenameDirectory( GetIndexDirectory(rkIndex), TO_TSTRING(NewName) ); + } +} + +void CResourceBrowserDelegate::updateEditorGeometry(QWidget *pEditor, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const +{ + // Check if this is a directory + bool IsDir = GetIndexEntry(rkIndex) == nullptr; + + // Get rect + SResDelegateFontInfo FontInfo = GetFontInfo(rkOption); + SResDelegateGeometryInfo GeomInfo = GetGeometryInfo(FontInfo, rkOption, IsDir); + + // Set geometry; make it a little bit better than the name string rect to give the user more space + QRect WidgetRect = GeomInfo.NameStringRect.adjusted(-3, -3, 3, 3); + pEditor->setGeometry(WidgetRect); +} + +CResourceEntry* CResourceBrowserDelegate::GetIndexEntry(const QModelIndex& rkIndex) const +{ + const CResourceProxyModel *pkProxy = qobject_cast(rkIndex.model()); + ASSERT(pkProxy != nullptr); + + const CResourceTableModel *pkModel = qobject_cast(pkProxy->sourceModel()); + ASSERT(pkModel != nullptr); + + QModelIndex SourceIndex = pkProxy->mapToSource(rkIndex); + return pkModel->IndexEntry(SourceIndex); +} + +CVirtualDirectory* CResourceBrowserDelegate::GetIndexDirectory(const QModelIndex& rkIndex) const +{ + const CResourceProxyModel *pkProxy = qobject_cast(rkIndex.model()); + ASSERT(pkProxy != nullptr); + + const CResourceTableModel *pkModel = qobject_cast(pkProxy->sourceModel()); + ASSERT(pkModel != nullptr); + + QModelIndex SourceIndex = pkProxy->mapToSource(rkIndex); + return pkModel->IndexDirectory(SourceIndex); +} diff --git a/src/Editor/ResourceBrowser/CResourceDelegate.h b/src/Editor/ResourceBrowser/CResourceDelegate.h index 13cc3152..f0d6a7c7 100644 --- a/src/Editor/ResourceBrowser/CResourceDelegate.h +++ b/src/Editor/ResourceBrowser/CResourceDelegate.h @@ -3,8 +3,13 @@ #include + class CResourceBrowserDelegate : public QStyledItemDelegate { +public: + static const int skIconSize = 32; + +private: bool mDisplayAssetIDs; public: @@ -16,7 +21,16 @@ public: QSize sizeHint(const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const; void paint(QPainter *pPainter, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const; + QWidget* createEditor(QWidget *pParent, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const; + void setEditorData(QWidget *pEditor, const QModelIndex& rkIndex) const; + void setModelData(QWidget *pEditor, QAbstractItemModel *pModel, const QModelIndex& rkIndex) const; + void updateEditorGeometry(QWidget *pEditor, const QStyleOptionViewItem& rkOption, const QModelIndex& rkIndex) const; + inline void SetDisplayAssetIDs(bool Display) { mDisplayAssetIDs = Display; } + +protected: + class CResourceEntry* GetIndexEntry(const QModelIndex& rkIndex) const; + class CVirtualDirectory* GetIndexDirectory(const QModelIndex& rkIndex) const; }; #endif // CRESOURCEBROWSERDELEGATE_H diff --git a/src/Editor/ResourceBrowser/CResourceProxyModel.h b/src/Editor/ResourceBrowser/CResourceProxyModel.h index 503f0c9b..e5d4867a 100644 --- a/src/Editor/ResourceBrowser/CResourceProxyModel.h +++ b/src/Editor/ResourceBrowser/CResourceProxyModel.h @@ -51,7 +51,7 @@ public: return false; else if (pLeftDir && pRightDir) - return rkLeft.row() < rkRight.row(); // leave original directory order intact + return pLeftDir->Name().ToUpper() < pRightDir->Name().ToUpper(); else if (mSortMode == eSortByName) return pLeftRes->UppercaseName() < pRightRes->UppercaseName(); diff --git a/src/Editor/ResourceBrowser/CResourceTableContextMenu.cpp b/src/Editor/ResourceBrowser/CResourceTableContextMenu.cpp index 39715ebd..31041c18 100644 --- a/src/Editor/ResourceBrowser/CResourceTableContextMenu.cpp +++ b/src/Editor/ResourceBrowser/CResourceTableContextMenu.cpp @@ -20,6 +20,8 @@ CResourceTableContextMenu::CResourceTableContextMenu(CResourceBrowser *pBrowser, mpOpenInExternalAppAction = addAction("Open in external application", this, SLOT(OpenInExternalApp())); mpOpenContainingFolderAction = addAction("Open containing folder", this, SLOT(OpenContainingFolder())); addSeparator(); + mpRenameAction = addAction("Rename", this, SLOT(Rename())); + addSeparator(); mpCopyNameAction = addAction("Copy name", this, SLOT(CopyName())); mpCopyPathAction = addAction("Copy path", this, SLOT(CopyPath())); mpCopyIDAction = addAction("Copy asset ID", this, SLOT(CopyID())); @@ -28,11 +30,11 @@ CResourceTableContextMenu::CResourceTableContextMenu(CResourceBrowser *pBrowser, void CResourceTableContextMenu::ShowMenu(const QPoint& rkPos) { // Fetch the entry/directory - QModelIndex ProxyIndex = mpTable->indexAt(rkPos); + mProxyIndex = mpTable->indexAt(rkPos); - if (ProxyIndex.isValid()) + if (mProxyIndex.isValid()) { - mIndex = mpProxy->mapToSource(ProxyIndex); + mIndex = mpProxy->mapToSource(mProxyIndex); mpEntry = mpModel->IndexEntry(mIndex); mpDirectory = mpModel->IndexDirectory(mIndex); @@ -77,6 +79,11 @@ void CResourceTableContextMenu::OpenContainingFolder() } } +void CResourceTableContextMenu::Rename() +{ + mpTable->edit(mProxyIndex); +} + void CResourceTableContextMenu::CopyName() { if (mpEntry) diff --git a/src/Editor/ResourceBrowser/CResourceTableContextMenu.h b/src/Editor/ResourceBrowser/CResourceTableContextMenu.h index 1b7b8738..5a13acc1 100644 --- a/src/Editor/ResourceBrowser/CResourceTableContextMenu.h +++ b/src/Editor/ResourceBrowser/CResourceTableContextMenu.h @@ -16,6 +16,7 @@ class CResourceTableContextMenu : public QMenu CResourceTableModel *mpModel; CResourceProxyModel *mpProxy; + QModelIndex mProxyIndex; QModelIndex mIndex; CResourceEntry *mpEntry; CVirtualDirectory *mpDirectory; @@ -25,6 +26,8 @@ class CResourceTableContextMenu : public QMenu QAction *mpOpenInExternalAppAction; QAction *mpOpenContainingFolderAction; + QAction *mpRenameAction; + QAction *mpCopyNameAction; QAction *mpCopyPathAction; QAction *mpCopyIDAction; @@ -39,6 +42,7 @@ public slots: void Open(); void OpenInExternalApp(); void OpenContainingFolder(); + void Rename(); void CopyName(); void CopyPath(); void CopyID(); diff --git a/src/Editor/ResourceBrowser/CResourceTableModel.cpp b/src/Editor/ResourceBrowser/CResourceTableModel.cpp index b1128096..31419407 100644 --- a/src/Editor/ResourceBrowser/CResourceTableModel.cpp +++ b/src/Editor/ResourceBrowser/CResourceTableModel.cpp @@ -59,7 +59,7 @@ QVariant CResourceTableModel::data(const QModelIndex& rkIndex, int Role) const Qt::ItemFlags CResourceTableModel::flags(const QModelIndex& rkIndex) const { - Qt::ItemFlags Out = Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; + Qt::ItemFlags Out = Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsEditable; if (IsIndexDirectory(rkIndex)) Out |= Qt::ItemIsDropEnabled; @@ -99,16 +99,20 @@ bool CResourceTableModel::canDropMimeData(const QMimeData *pkData, Qt::DropActio return false; } -bool CResourceTableModel::dropMimeData(const QMimeData *pkData, Qt::DropAction, int Row, int Column, const QModelIndex& rkParent) +bool CResourceTableModel::dropMimeData(const QMimeData *pkData, Qt::DropAction Action, int Row, int Column, const QModelIndex& rkParent) { const CResourceMimeData *pkMimeData = qobject_cast(pkData); - QModelIndex Index = (rkParent.isValid() ? rkParent : index(Row, Column, rkParent)); - CVirtualDirectory *pDir = IndexDirectory(Index); - ASSERT(pDir); + if (canDropMimeData(pkData, Action, Row, Column, rkParent)) + { + 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; + gpEdApp->ResourceBrowser()->MoveResources( pkMimeData->Resources(), pkMimeData->Directories(), pDir ); + return true; + } + else return false; } QMimeData* CResourceTableModel::mimeData(const QModelIndexList& rkIndexes) const @@ -292,6 +296,13 @@ void CResourceTableModel::OnDirectoryMoved(CVirtualDirectory *pDir, CVirtualDire bool WasInModel = !mIsAssetListMode && pOldDir == mpCurrentDir; bool IsInModel = !mIsAssetListMode && pNewDir == mpCurrentDir; + // Handle parent link + if (pDir == mpCurrentDir) + { + ASSERT(mDirectories.front() == pOldDir); + mDirectories[0] = pNewDir; + } + // Handle rename if (WasInModel && IsInModel && pDir->Name() != OldName) { @@ -312,7 +323,7 @@ void CResourceTableModel::OnDirectoryMoved(CVirtualDirectory *pDir, CVirtualDire } // Add - else if (!WasInModel && !IsInModel) + else if (!WasInModel && IsInModel) { // Just append to the end, let the proxy handle sorting beginInsertRows(QModelIndex(), mDirectories.size(), mDirectories.size()); diff --git a/src/Editor/ResourceBrowser/CResourceTableView.cpp b/src/Editor/ResourceBrowser/CResourceTableView.cpp index ef7ec288..0fc018e0 100644 --- a/src/Editor/ResourceBrowser/CResourceTableView.cpp +++ b/src/Editor/ResourceBrowser/CResourceTableView.cpp @@ -1,9 +1,17 @@ #include "CResourceTableView.h" +#include #include CResourceTableView::CResourceTableView(QWidget *pParent /*= 0*/) : QTableView(pParent) -{} +{ + // Create "rename" key shortcut + // todo - there's no QKeySequence::Rename. Is there another standard "rename" shortcut on other platforms? + mpRenameAction = new QAction(this); + mpRenameAction->setShortcut( QKeySequence(Qt::Key_F2) ); + connect(mpRenameAction, SIGNAL(triggered(bool)), this, SLOT(RenameSelected())); + addAction(mpRenameAction); +} void CResourceTableView::dragEnterEvent(QDragEnterEvent *pEvent) { @@ -18,3 +26,24 @@ void CResourceTableView::dragEnterEvent(QDragEnterEvent *pEvent) setState(QAbstractItemView::DraggingState); } } + +void CResourceTableView::focusInEvent(QFocusEvent*) +{ + mpRenameAction->setEnabled(true); +} + +void CResourceTableView::focusOutEvent(QFocusEvent*) +{ + mpRenameAction->setEnabled(false); +} + +// ************ SLOTS ************ +void CResourceTableView::RenameSelected() +{ + QModelIndexList List = selectionModel()->selectedIndexes(); + + if (List.size() == 1) + { + edit(List.front()); + } +} diff --git a/src/Editor/ResourceBrowser/CResourceTableView.h b/src/Editor/ResourceBrowser/CResourceTableView.h index d34ca18f..dc14f7ad 100644 --- a/src/Editor/ResourceBrowser/CResourceTableView.h +++ b/src/Editor/ResourceBrowser/CResourceTableView.h @@ -6,10 +6,16 @@ class CResourceTableView : public QTableView { Q_OBJECT + QAction *mpRenameAction; public: explicit CResourceTableView(QWidget *pParent = 0); void dragEnterEvent(QDragEnterEvent *pEvent); + void focusInEvent(QFocusEvent*); + void focusOutEvent(QFocusEvent*); + +public slots: + void RenameSelected(); }; #endif // CRESOURCETABLEVIEW_H diff --git a/src/Editor/Undo/CMoveResourceCommand.h b/src/Editor/Undo/CMoveResourceCommand.h index 870b1110..f21d7d67 100644 --- a/src/Editor/Undo/CMoveResourceCommand.h +++ b/src/Editor/Undo/CMoveResourceCommand.h @@ -34,11 +34,10 @@ protected: void DoMove(const TString& rkPath, bool IsAutoDir) { CVirtualDirectory *pOldDir = mpEntry->Directory(); - TString OldName = mpEntry->Name(); bool Success = mpEntry->Move(rkPath, IsAutoDir); ASSERT(Success); // todo better error handling - gpEdApp->ResourceBrowser()->ResourceMoved(mpEntry, pOldDir, OldName); + gpEdApp->ResourceBrowser()->ResourceMoved(mpEntry, pOldDir, mpEntry->Name()); } }; diff --git a/src/Editor/Undo/CRenameDirectoryCommand.h b/src/Editor/Undo/CRenameDirectoryCommand.h new file mode 100644 index 00000000..79feacba --- /dev/null +++ b/src/Editor/Undo/CRenameDirectoryCommand.h @@ -0,0 +1,38 @@ +#ifndef CRENAMEDIRECTORYCOMMAND_H +#define CRENAMEDIRECTORYCOMMAND_H + +#include "IUndoCommand.h" +#include "Editor/CEditorApplication.h" +#include "Editor/ResourceBrowser/CResourceBrowser.h" +#include + +class CRenameDirectoryCommand : public IUndoCommand +{ + CVirtualDirectory *mpDir; + TString mOldName; + TString mNewName; + +public: + CRenameDirectoryCommand(CVirtualDirectory *pDir, const TString& rkNewName) + : IUndoCommand("Rename Directory") + , mpDir(pDir) + , mOldName(pDir->Name()) + , mNewName(rkNewName) + {} + + void undo() { DoMove(mOldName); } + void redo() { DoMove(mNewName); } + bool AffectsCleanState() const { return false; } + +protected: + void DoMove(const TString& rkName) + { + TString OldName = mpDir->Name(); + bool Success = mpDir->Rename(rkName); + ASSERT(Success); + + gpEdApp->ResourceBrowser()->DirectoryMoved(mpDir, mpDir->Parent(), OldName); + } +}; + +#endif // CRENAMEDIRECTORYCOMMAND_H diff --git a/src/Editor/Undo/CRenameResourceCommand.h b/src/Editor/Undo/CRenameResourceCommand.h new file mode 100644 index 00000000..2d78e1cd --- /dev/null +++ b/src/Editor/Undo/CRenameResourceCommand.h @@ -0,0 +1,40 @@ +#ifndef CRENAMERESOURCECOMMAND_H +#define CRENAMERESOURCECOMMAND_H + +#include "IUndoCommand.h" +#include "Editor/CEditorApplication.h" +#include "Editor/ResourceBrowser/CResourceBrowser.h" +#include + +class CRenameResourceCommand : public IUndoCommand +{ + CResourceEntry *mpEntry; + TString mOldName; + TString mNewName; + bool mOldNameAutoGenerated; + +public: + CRenameResourceCommand(CResourceEntry *pEntry, const TString& rkNewName) + : IUndoCommand("Rename Resource") + , mpEntry(pEntry) + , mOldName(pEntry->Name()) + , mNewName(rkNewName) + , mOldNameAutoGenerated(pEntry->HasFlag(eREF_AutoResName)) + {} + + void undo() { DoMove(mOldName, mOldNameAutoGenerated); } + void redo() { DoMove(mNewName, false); } + bool AffectsCleanState() const { return false; } + +protected: + void DoMove(const TString& rkName, bool IsAutoName) + { + TString OldName = mpEntry->Name(); + bool Success = mpEntry->Rename(rkName, IsAutoName); + ASSERT(Success); + + gpEdApp->ResourceBrowser()->ResourceMoved(mpEntry, mpEntry->Directory(), OldName); + } +}; + +#endif // CRENAMERESOURCECOMMAND_H diff --git a/templates/mp2/Script/Midi.xml b/templates/mp2/Script/Midi.xml index eba1611c..7f3cb23c 100644 --- a/templates/mp2/Script/Midi.xml +++ b/templates/mp2/Script/Midi.xml @@ -3,7 +3,7 @@ Midi - + 0.0