!XB (Sandbox) (DEV-8348) Embedded Asset Browser must show a filtered list of assets...
[CRYENGINE.git] / Code / Sandbox / Plugins / EditorCommon / AssetSystem / Browser / AssetBrowser.cpp
blobf0bbd6e64eb7c42b769c391583bd270b43ca5425
1 // Copyright 2001-2018 Crytek GmbH / Crytek Group. All rights reserved.
3 #include "StdAfx.h"
4 #include "AssetBrowser.h"
6 #include "AssetDropHandler.h"
7 #include "AssetFolderFilterModel.h"
8 #include "AssetFoldersModel.h"
9 #include "AssetFoldersView.h"
10 #include "AssetModel.h"
11 #include "AssetReverseDependenciesDialog.h"
12 #include "AssetThumbnailsGenerator.h"
13 #include "AssetThumbnailsLoader.h"
14 #include "AssetTooltip.h"
15 #include "DependenciesAttribute.h"
16 #include "FilteredFolders.h"
17 #include "LineEditDelegate.h"
18 #include "ManageWorkFilesDialog.h"
19 #include "NewAssetModel.h"
20 #include "SortFilterProxyModel.h"
22 #include "AssetSystem/Asset.h"
23 #include "AssetSystem/AssetEditor.h"
24 #include "AssetSystem/AssetImporter.h"
25 #include "AssetSystem/AssetManager.h"
26 #include "AssetSystem/AssetManagerHelpers.h"
27 #include "AssetSystem/EditableAsset.h"
29 #include "Commands/QCommandAction.h"
30 #include "Controls/BreadcrumbsBar.h"
31 #include "Controls/QuestionDialog.h"
32 #include "DragDrop.h"
33 #include "EditorFramework/Events.h"
34 #include "FileDialogs/SystemFileDialog.h"
35 #include "FileUtils.h"
36 #include "Menu/MenuWidgetBuilders.h"
37 #include "Notifications/NotificationCenter.h"
38 #include "PathUtils.h"
39 #include "ProxyModels/AttributeFilterProxyModel.h"
40 #include "QAdvancedItemDelegate.h"
41 #include "QAdvancedTreeView.h"
42 #include "QControls.h"
43 #include "QFilteringPanel.h"
44 #include "QSearchBox.h"
45 #include "QThumbnailView.h"
46 #include "QtUtil.h"
47 #include "QtViewPane.h"
48 #include "ThreadingUtils.h"
50 #include "VersionControl/VersionControlEventHandler.h"
51 #include <IEditor.h>
53 #include <QButtonGroup>
54 #include <QDirIterator>
55 #include <QDragEnterEvent>
56 #include <QGridLayout>
57 #include <QHBoxLayout>
58 #include <QHeaderView>
59 #include <QItemSelectionModel>
60 #include <QLineEdit>
61 #include <QListView>
62 #include <QSplitter>
63 #include <QToolButton>
64 #include <QVariant>
65 #include <QClipboard>
67 REGISTER_VIEWPANE_FACTORY(CAssetBrowser, "Asset Browser", "Tools", false);
69 CCrySignal<void(CAbstractMenu&, const std::shared_ptr<IUIContext>&)> CAssetBrowser::s_signalMenuCreated;
70 CCrySignal<void(CAbstractMenu&, const std::vector<CAsset*>&, const std::vector<string>& folders, const std::shared_ptr<IUIContext>&)> CAssetBrowser::s_signalContextMenuRequested;
72 namespace Private_AssetBrowser
75 //! returns EAssetModelRowType
76 static int GetType(const QModelIndex& index)
78 return (EAssetModelRowType)index.data((int)CAssetModel::Roles::TypeCheckRole).toUInt();
81 static bool IsFolder(const QModelIndex& index)
83 bool ok = false;
84 return index.data((int)CAssetModel::Roles::TypeCheckRole).toUInt(&ok) == eAssetModelRow_Folder && ok;
87 static QString ToFolderPath(const QModelIndex& index)
89 return index.data((int)CAssetFoldersModel::Roles::FolderPathRole).toString();
92 static bool UserConfirmsRenaming(CAsset& asset, QWidget* pParent)
94 const CAssetManager* const pAssetManager = CAssetManager::GetInstance();
95 const QString question = QObject::tr("There is a possibility that %1 has undetected dependencies which can be violated after the operation.\n"
96 "\n"
97 "Do you really want to rename the asset?").arg(QtUtil::ToQString(asset.GetName()));
98 const QString title = QObject::tr("Rename Asset");
100 if (pAssetManager->HasAnyReverseDependencies({ &asset }))
102 CAssetReverseDependenciesDialog dialog({ &asset },
103 QObject::tr("Asset to be renamed"),
104 QObject::tr("Dependent Assets"),
105 QObject::tr("The following assets probably will not behave correctly after performing the operation."),
106 question, pParent);
107 dialog.setWindowTitle(title);
109 if (!dialog.Execute())
111 return false;
114 else if (CQuestionDialog::SQuestion(title, question) != QDialogButtonBox::Yes)
116 return false;
119 return true;
122 // A private helper class that allows to drop items into the root folder of details and thumbnails views.
123 // This is a workaround for CAssetFolderFilterModel that does not support hierarchy so the views can not use QAbstractItemView::SetRootIndex()
124 template<typename TView>
125 class CDraggingIntoRootOf : public TView
127 public:
128 template<typename ... Arg>
129 CDraggingIntoRootOf(Arg&& ... arg)
130 : TView(std::forward<Arg>(arg) ...)
134 // The root folder is the parent folder to the view's top level items.
135 // folder.IsEmpty is a valid string and stands for the assets root folder, while folder.isNull means the root folder is not assigned.
136 void SetRootFolder(const QString& folder)
138 m_root = folder;
141 template<typename TEvent>
142 bool Processed(const TEvent* pEvent)
144 return pEvent->isAccepted() || m_root.isNull() || indexAt(pEvent->pos()).isValid();
147 template<typename TEvent>
148 void dragEnterMoveRoot(TEvent* pEvent) const
150 const CAssetFoldersModel* const pModel = CAssetFoldersModel::GetInstance();
151 const QModelIndex root = pModel->FindIndexForFolder(m_root);
152 if (pModel->canDropMimeData(pEvent->mimeData(), pEvent->dropAction(), root.row(), root.column(), root.parent()))
154 pEvent->accept();
158 virtual void dragEnterEvent(QDragEnterEvent* pEvent) override
160 CDragDropData::ShowDragText(qApp->widgetAt(QCursor::pos()), tr("Invalid operation"));
162 TView::dragEnterEvent(pEvent);
164 if (!Processed(pEvent))
166 dragEnterMoveRoot(pEvent);
170 virtual void dragMoveEvent(QDragMoveEvent* pEvent) override
172 TView::dragMoveEvent(pEvent);
174 if (!Processed(pEvent))
176 dragEnterMoveRoot(pEvent);
180 // For the QListView (Thumbnail view) we want to use QListView::Movement::Static but this will disable drag&drop.
181 // Calling here the QAbstractItemView implementation directly, we disable the items movement for the QListView.
182 template<typename T = TView, typename std::enable_if<std::is_same<T, QListView>::value, int>::type = 0>
183 void BaseDropEvent(QDropEvent* pEvent)
185 QAbstractItemView::dropEvent(pEvent);
188 template<typename T = TView, typename std::enable_if<!std::is_same<T, QListView>::value, int>::type = 0>
189 void BaseDropEvent(QDropEvent* pEvent)
191 T::dropEvent(pEvent);
194 virtual void dropEvent(QDropEvent* pEvent) override
196 BaseDropEvent(pEvent);
198 if (!Processed(pEvent))
200 CAssetFoldersModel* const pModel = CAssetFoldersModel::GetInstance();
201 const QModelIndex root = pModel->FindIndexForFolder(m_root);
202 if (pModel->dropMimeData(pEvent->mimeData(), pEvent->dropAction(), root.row(), root.column(), root.parent()))
204 pEvent->accept();
209 virtual void mouseReleaseEvent(QMouseEvent *event) override
211 // Qt documentation says that it is possible for the user to deselect
212 // the selected item with QAbstractItemView::SingleSelection.
213 // but it does not work this way.
214 // Remove the following workaround when the Qt bug fixed: https://bugreports.qt.io/browse/QTBUG-75898
215 auto temp = TView::selectionMode();
216 TView::setSelectionMode(QAbstractItemView::ExtendedSelection);
217 TView::mouseReleaseEvent(event);
218 TView::setSelectionMode(temp);
221 private:
222 QString m_root;
225 class QAssetDetailsView : public CDraggingIntoRootOf<QAdvancedTreeView>
227 public:
228 QAssetDetailsView(QWidget* parent = nullptr)
229 : CDraggingIntoRootOf(QAdvancedTreeView::UseItemModelAttribute, parent)
232 protected:
233 virtual bool edit(const QModelIndex& index, EditTrigger trigger, QEvent* pEvent) override
235 if ((editTriggers() & trigger) && index.isValid() && CAssetModel::IsAsset(index))
237 CAsset* pAsset = CAssetModel::ToAsset(index);
238 if (pAsset && !UserConfirmsRenaming(*pAsset, this))
240 if (pEvent)
242 pEvent->accept();
244 // If return false, Qt can ignore() and propagate the event - it is not what we want.
245 return true;
248 return QAdvancedTreeView::edit(index, trigger, pEvent);
252 class CThumbnailsInternalView : public CDraggingIntoRootOf<QListView>
254 public:
255 CThumbnailsInternalView(QWidget* pParent = nullptr)
256 : CDraggingIntoRootOf(pParent)
260 protected:
261 virtual void startDrag(Qt::DropActions supportedActions) override
263 if (model() && selectionModel())
265 QMimeData* const pMimeData = model()->mimeData(selectionModel()->selectedIndexes());
266 CDragDropData::StartDrag(this, supportedActions, pMimeData);
270 virtual bool edit(const QModelIndex& index, EditTrigger trigger, QEvent* pEvent) override
272 if ((editTriggers() & trigger) && index.isValid() && CAssetModel::IsAsset(index))
274 CAsset* pAsset = CAssetModel::ToAsset(index);
275 if (pAsset && !UserConfirmsRenaming(*pAsset, this))
277 if (pEvent)
279 pEvent->accept();
281 return true;
284 return QListView::edit(index, trigger, pEvent);
287 virtual void scrollContentsBy(int dx, int dy) override
289 QListView::scrollContentsBy(dx, dy);
290 TouchVisibleAssets();
293 private:
294 void TouchVisibleAssetsBatched(int firstBatchRow)
296 if (!model() || !model()->rowCount())
298 return;
301 const int batchSize = 1 << 3;
302 const int lastRow = model()->rowCount() - 1;
303 const int lastBatchRow = std::min(lastRow, firstBatchRow + batchSize);
304 for (int i = lastBatchRow; i >= firstBatchRow; --i)
306 QModelIndex index = model()->index(i, (int)eAssetColumns_Thumbnail);
307 if (index.isValid())
309 CAsset* const pAsset = (CAsset*)(index.data((int)CAssetModel::Roles::InternalPointerRole).value<intptr_t>());
310 if (pAsset && pAsset->GetType()->HasThumbnail())
312 const QRect r = visualRect(index);
313 if (r.y() > 0 && r.y() < rect().height() && r.size().width() * r.size().height() > 1)
315 CAssetThumbnailsLoader::GetInstance().TouchAsset(pAsset);
321 if (lastBatchRow < lastRow)
323 QPointer<CThumbnailsInternalView> pView(this);
324 QTimer::singleShot(0, [pView, lastBatchRow]()
326 if (!pView)
328 return;
330 pView->TouchVisibleAssetsBatched(lastBatchRow + 1);
335 void TouchVisibleAssets()
337 TouchVisibleAssetsBatched(0);
341 class CWorkFileOperator : public Attributes::IAttributeFilterOperator
343 public:
344 virtual QString GetName() override { return QWidget::tr("Work File"); }
346 virtual bool Match(const QVariant& value, const QVariant& filterValue) override
348 if (!filterValue.isValid())
350 return true;
353 const CAsset* const pAsset = value.isValid() ? reinterpret_cast<CAsset*>(value.value<intptr_t>()) : nullptr;
354 if (!pAsset)
356 return false;
359 const string workFileSubstr = QtUtil::ToString(filterValue.toString());
360 for (const string& workFile : pAsset->GetWorkFiles())
362 if (workFile.find(workFileSubstr) != std::string::npos)
364 return true;
368 return false;
371 QWidget* CreateEditWidget(std::shared_ptr<CAttributeFilter> pFilter, const QStringList* pAttributeValues) override
373 auto pWidget = new QLineEdit();
374 auto currentValue = pFilter->GetFilterValue();
376 if (currentValue.type() == QVariant::String)
378 pWidget->setText(currentValue.toString());
381 QWidget::connect(pWidget, &QLineEdit::editingFinished, [pFilter, pWidget]()
383 pFilter->SetFilterValue(pWidget->text());
386 return pWidget;
389 void UpdateWidget(QWidget* widget, const QVariant& value) override
391 QLineEdit* pEdit = qobject_cast<QLineEdit*>(widget);
392 if (pEdit)
394 pEdit->setText(value.toString());
399 static CAttributeType<QString> s_workFileAttributeType({ new CWorkFileOperator() });
401 class CWorkFileAttribute : public CItemModelAttribute
403 public:
404 CWorkFileAttribute()
405 : CItemModelAttribute("Work File", &s_workFileAttributeType, CItemModelAttribute::AlwaysHidden, true, QVariant(), (int)CAssetModel::Roles::InternalPointerRole)
407 static CAssetModel::CAutoRegisterColumn column(this, [](const CAsset* pAsset, const CItemModelAttribute* /*pAttribute*/, int role)
409 return QVariant();
414 static CWorkFileAttribute g_workFilesAttribute;
417 void GetExtensionFilter(ExtensionFilterVector& extFilter)
419 CRY_ASSERT(CAssetManager::GetInstance()->GetAssetImporters().size() > 0);
421 extFilter.resize(1); // Reserve slot for "All supported types".
423 std::vector<string> exts;
424 for (CAssetImporter* pAssetImporter : CAssetManager::GetInstance()->GetAssetImporters())
426 for (const string& ext : pAssetImporter->GetFileExtensions())
428 stl::binary_insert_unique(exts, ext);
432 QStringList allExts;
433 for (const string& ext : exts)
435 extFilter << CExtensionFilter(QObject::tr(".%1 files").arg(ext.c_str()), ext.c_str());
436 allExts << QtUtil::ToQString(ext);
439 extFilter << CExtensionFilter(QObject::tr("All Files (*.*)"), "*");
441 const QString allExtsShort = QtUtil::ToQString(ShortenStringWithEllipsis(QtUtil::ToString(allExts.join(", "))));
442 extFilter[0] = CExtensionFilter(QObject::tr("All importable files (%1)").arg(allExtsShort), allExts);
445 std::vector<CAsset*> GetAssets(const CDragDropData& data)
447 QVector<quintptr> tmp;
448 QByteArray byteArray = data.GetCustomData("Assets");
449 QDataStream stream(byteArray);
450 stream >> tmp;
452 std::vector<CAsset*> assets;
453 std::transform(tmp.begin(), tmp.end(), std::back_inserter(assets), [](quintptr p)
455 return reinterpret_cast<CAsset*>(p);
457 return assets;
460 bool IsMovePossible(const std::vector<CAsset*>& assets, const string& destinationFolder)
462 // Do not allow moving to aliases, like %engine%
463 if (destinationFolder.empty() || destinationFolder.front() == '%')
465 return false;
468 // Make sure none of assets belong to the destination folder.
469 const string path(PathUtil::AddSlash(destinationFolder));
470 return std::none_of(assets.begin(), assets.end(), [&path](CAsset* pAsset)
472 return strcmp(path.c_str(), pAsset->GetFolder()) == 0;
476 // Implements QueryNewAsset for the AssetBrowser context menu.
477 class CContextMenuContext : public IUIContext
479 public:
480 CContextMenuContext(CAssetBrowser* pBrowser)
481 : m_pBrowser(pBrowser)
483 m_connection = QObject::connect(pBrowser, &CAssetBrowser::destroyed, [this](auto)
485 m_pBrowser = nullptr;
488 ~CContextMenuContext()
490 QObject::disconnect(m_connection);
493 virtual CAsset* QueryNewAsset(const CAssetType& type, const CAssetType::SCreateParams* pCreateParams) override
495 return m_pBrowser ? m_pBrowser->QueryNewAsset(type, pCreateParams) : nullptr;
497 private:
498 CAssetBrowser* m_pBrowser;
499 QMetaObject::Connection m_connection;
502 QToolButton* CreateToolButtonForAction(QAction* pAction)
504 CRY_ASSERT(pAction);
506 QToolButton* const pButton = new QToolButton;
507 pButton->setDefaultAction(pAction);
508 pButton->setAutoRaise(true);
509 return pButton;
512 // Copy/Paste implementation
513 static std::vector<string> g_clipboard;
517 CAssetBrowser::CAssetBrowser(bool bHideEngineFolder /*= false*/, QWidget* pParent /*= nullptr*/)
518 : CDockableEditor(pParent)
519 , m_knownAssetTypes(CAssetManager::GetInstance()->GetAssetTypes())
521 setObjectName("Asset Browser");
523 InitActions();
524 InitMenus();
526 SetModel(new CAssetFolderFilterModel(false, true, this));
527 InitViews(bHideEngineFolder);
529 // Create thumbnail size menu
530 CAbstractMenu* const pMenuView = GetMenu(CEditor::MenuItems::ViewMenu);
531 int section = pMenuView->GetNextEmptySection();
532 m_pThumbnailView->AppendPreviewSizeActions(*pMenuView->CreateMenu("Thumbnail Sizes", section));
534 m_pAssetDropHandler.reset(new CAssetDropHandler());
535 setAcceptDrops(true);
536 InstallReleaseMouseFilter(this);
537 UpdateSelectionDependantActions();
539 WaitUntilAssetsAreReady();
542 CAssetBrowser::CAssetBrowser(const std::vector<CAssetType*>& assetTypes, bool bHideEngineFolder /*= false*/, QWidget* pParent /*= nullptr*/)
543 : CDockableEditor(pParent)
544 , m_knownAssetTypes(assetTypes)
546 setObjectName("Asset Browser");
548 InitActions();
549 InitMenus();
551 SetModel(new CAssetFolderFilterModel(assetTypes, false, true, this));
552 const QStringList assetTypeNames = AssetManagerHelpers::GetUiNamesFromAssetTypes(assetTypes);
553 InitAssetTypeFilter(assetTypeNames);
554 InitViews(bHideEngineFolder);
555 m_pFilterPanel->OverrideAttributeEnumEntries(AssetModelAttributes::s_AssetTypeAttribute.GetName(), assetTypeNames);
557 // Create thumbnail size menu
558 CAbstractMenu* const pMenuView = GetMenu(CEditor::MenuItems::ViewMenu);
559 int section = pMenuView->GetNextEmptySection();
560 m_pThumbnailView->AppendPreviewSizeActions(*pMenuView->CreateMenu("Thumbnail Sizes", section));
562 m_pAssetDropHandler.reset(new CAssetDropHandler());
563 setAcceptDrops(true);
564 InstallReleaseMouseFilter(this);
565 UpdateSelectionDependantActions();
567 WaitUntilAssetsAreReady();
570 void CAssetBrowser::SetModel(CAssetFolderFilterModel* pModel)
572 m_pFolderFilterModel.reset(pModel);
574 m_pAttributeFilterProxyModel.reset(new CSortFilterProxyModel(this));
575 m_pAttributeFilterProxyModel->setSourceModel(m_pFolderFilterModel.get());
576 m_pAttributeFilterProxyModel->setFilterKeyColumn(eAssetColumns_FilterString);
578 m_pFilteredFolders.reset(new CFilteredFolders(m_pFolderFilterModel.get(), m_pAttributeFilterProxyModel.get()));
579 m_pAttributeFilterProxyModel->SetFilteredFolders(m_pFilteredFolders.get());
582 void CAssetBrowser::InitAssetTypeFilter(const QStringList assetTypeNames)
584 MAKE_SURE(!assetTypeNames.empty(), return)
586 AttributeFilterSharedPtr pAssetTypeFilter = std::make_shared<CAttributeFilter>(&AssetModelAttributes::s_AssetTypeAttribute);
587 pAssetTypeFilter->SetFilterValue(assetTypeNames);
588 m_pAttributeFilterProxyModel->AddFilter(pAssetTypeFilter);
589 m_pActionHideIrrelevantFolders->setChecked(true);
590 UpdateNonEmptyFolderList();
593 //"Loading" feature while scanning for assets
594 void CAssetBrowser::WaitUntilAssetsAreReady()
596 if (!CAssetManager::GetInstance()->IsScanning())
598 return;
601 //swap layout for a loading layout
602 //swapping layout using the temporary widget trick
603 auto tempWidget = new QWidget();
604 tempWidget->setLayout(layout());
606 QGridLayout* loadingLayout = new QGridLayout();
607 loadingLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding), 0, 0, 3);
608 loadingLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding), 1, 0);
609 loadingLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding), 1, 2);
610 loadingLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding), 2, 0);
611 loadingLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding), 2, 2);
612 loadingLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding), 3, 0, 3);
613 loadingLayout->addWidget(new QLoading(), 1, 1, 1, 1, Qt::AlignHCenter | Qt::AlignBottom);
614 loadingLayout->addWidget(new QLabel(tr("Loading Assets...")), 2, 1, 1, 1, Qt::AlignHCenter | Qt::AlignTop);
615 setLayout(loadingLayout);
618 CAssetManager::GetInstance()->signalScanningCompleted.Connect([loadingLayout, tempWidget, this]()
620 auto tempWidget2 = new QWidget();
621 tempWidget2->setLayout(layout());
622 setLayout(tempWidget->layout());
623 tempWidget->deleteLater();
624 tempWidget2->deleteLater();
625 CAssetManager::GetInstance()->signalScanningCompleted.DisconnectById((uintptr_t)this);
626 UpdateNonEmptyFolderList();
627 }, (uintptr_t)this);
630 CAssetBrowser::~CAssetBrowser()
632 m_pFoldersView->SetFilteredFolders(nullptr);
633 m_pAttributeFilterProxyModel->SetFilteredFolders(nullptr);
634 CAssetManager::GetInstance()->signalScanningCompleted.DisconnectById((uintptr_t)this);
637 bool DiscardChanges(const QString& what)
639 return CQuestionDialog::SQuestion("Discard changes?", what) == QDialogButtonBox::Yes;
642 void CAssetBrowser::mouseReleaseEvent(QMouseEvent* pEvent)
644 switch (pEvent->button())
646 case Qt::MouseButton::BackButton:
648 if (m_pBackButton->isEnabled())
650 OnNavBack();
651 pEvent->accept();
653 break;
655 case Qt::MouseButton::ForwardButton:
657 if (m_pForwardButton->isEnabled())
659 OnNavForward();
660 pEvent->accept();
662 break;
664 default:
665 break;
669 // Create and set item delegates for naming a new asset.
670 void CAssetBrowser::InitNewNameDelegates()
672 auto onEnd = std::function<void(const QModelIndex&)>([this](const QModelIndex&)
674 EndCreateAsset();
677 m_pDetailsViewNewNameDelegate.reset(new CLineEditDelegate(m_pDetailsView));
678 m_pDetailsViewNewNameDelegate->signalEditingAborted.Connect(onEnd);
679 m_pDetailsViewNewNameDelegate->signalEditingFinished.Connect(onEnd);
680 m_pDetailsView->setItemDelegate(m_pDetailsViewNewNameDelegate.get());
682 QAbstractItemView* const pThumbnailView = m_pThumbnailView->GetInternalView();
683 m_pThumbnailViewNewNameDelegate.reset(new CLineEditDelegate(pThumbnailView));
684 m_pThumbnailViewNewNameDelegate->signalEditingAborted.Connect(onEnd);
685 m_pThumbnailViewNewNameDelegate->signalEditingFinished.Connect(onEnd);
686 pThumbnailView->setItemDelegate(m_pThumbnailViewNewNameDelegate.get());
689 void CAssetBrowser::InitViews(bool bHideEngineFolder)
691 using namespace Private_AssetBrowser;
693 //folders view
694 m_pFoldersView = new CAssetFoldersView(bHideEngineFolder);
695 m_pFoldersView->SetFilteredFolders(m_pFilteredFolders.get());
696 m_pFoldersView->signalSelectionChanged.Connect(this, &CAssetBrowser::OnFolderSelectionChanged);
697 connect(m_pFoldersView->m_treeView, &QTreeView::customContextMenuRequested, this, &CAssetBrowser::OnFolderViewContextMenu);
699 // TODO: Consider extracting the AssetsView stuff to a new CAssetsView class to encapsulate all the detail/thumbnail related states.
700 InitAssetsView();
702 QWidget* pAssetsView = CreateAssetsViewSelector();
704 //filter panel
705 m_pFilterPanel = new QFilteringPanel("AssetBrowser", m_pAttributeFilterProxyModel.get());
706 //Searching in AB is likely to be quite expensive so timer prevents too many searches.
707 //TODO : This should be an adaptive threshold depending on assets count!
708 m_pFilterPanel->EnableDelayedSearch(true, 500.f);
709 m_pFilterPanel->SetContent(pAssetsView);
710 m_pFilterPanel->GetSearchBox()->setPlaceholderText(tr("Search Assets"));
711 m_pFilterPanel->GetSearchBox()->signalOnSearch.Connect(this, &CAssetBrowser::OnSearch);
712 m_pFilterPanel->signalOnFiltered.Connect(this, &CAssetBrowser::UpdateNonEmptyFolderList);
714 m_pFoldersSplitter = new QSplitter();
715 m_pFoldersSplitter->setOrientation(Qt::Horizontal);
716 m_pFoldersSplitter->addWidget(m_pFoldersView);
717 m_pFoldersSplitter->addWidget(m_pFilterPanel);
718 m_pFoldersSplitter->setStretchFactor(0, 1);
719 m_pFoldersSplitter->setStretchFactor(1, 3);
721 #if ASSET_BROWSER_USE_PREVIEW_WIDGET
722 //preview widget, temporary solution
723 m_previewWidget = new QContainer();
724 m_pFoldersSplitter->addWidget(m_previewWidget);
725 m_pFoldersSplitter->setStretchFactor(2, 3);
726 m_previewWidget->setVisible(false);
727 #endif
729 m_pFoldersSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
731 //address bar
733 //TODO : prev/next shortcuts unified with other places where we have it, use a generic command and unify with object create tool
734 //TODO : hold on the buttons should "show" the history in a drop down, much like web browsers
735 m_pBackButton = new QToolButton();
736 m_pBackButton->setIcon(CryIcon("icons:General/Arrow_Left.ico"));
737 m_pBackButton->setToolTip(tr("Back"));
738 m_pBackButton->setEnabled(false);
739 connect(m_pBackButton, &QToolButton::clicked, this, &CAssetBrowser::OnNavBack);
741 m_pForwardButton = new QToolButton();
742 m_pForwardButton->setIcon(CryIcon("icons:General/Arrow_Right.ico"));
743 m_pForwardButton->setToolTip(tr("Forward"));
744 m_pForwardButton->setEnabled(false);
745 connect(m_pForwardButton, &QToolButton::clicked, this, &CAssetBrowser::OnNavForward);
747 m_pBreadcrumbs = new CBreadcrumbsBar();
748 m_pBreadcrumbs->signalBreadcrumbClicked.Connect(this, &CAssetBrowser::OnBreadcrumbClick);
749 m_pBreadcrumbs->signalTextChanged.Connect(this, &CAssetBrowser::OnBreadcrumbsTextChanged);
750 m_pBreadcrumbs->SetValidator(std::function<bool(const QString)>([this](const QString path)
752 return this->ValidatePath(path);
753 }));
755 m_pMultipleFoldersLabel = new QLabel(tr("Multiple Folders Selected"));
756 m_pMultipleFoldersLabel->hide();
758 auto addressBar = new QHBoxLayout();
759 addressBar->setMargin(0);
760 addressBar->setSpacing(0);
761 addressBar->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
762 addressBar->addWidget(m_pBackButton);
763 addressBar->addWidget(m_pForwardButton);
764 addressBar->addWidget(m_pBreadcrumbs);
765 addressBar->addWidget(m_pMultipleFoldersLabel);
767 UpdateBreadcrumbsBar(CAssetFoldersModel::GetInstance()->GetProjectAssetsFolderName());
768 UpdateNavigation(false);
770 //top level layout
771 auto topLayout = new QVBoxLayout();
772 topLayout->setMargin(0);
773 topLayout->setSpacing(0);
774 topLayout->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
775 topLayout->addLayout(addressBar);
776 topLayout->addWidget(m_pFoldersSplitter);
778 //Layout
779 SetContent(topLayout);
781 SetViewMode(Thumbnails);//by default let's use thumbnails
783 OnFolderSelectionChanged(m_pFoldersView->GetSelectedFolders());
786 void CAssetBrowser::OnCopyName()
788 string clipBoardText;
789 std::vector<CAsset*> assets;
790 std::vector<string> folders;
791 GetSelection(assets, folders);
793 for (const string& folder : folders)
795 clipBoardText += PathUtil::GetFileName(folder) + "\n";;
798 for (const CAsset* pAsset : assets)
800 clipBoardText += pAsset->GetName() + "\n";
803 QApplication::clipboard()->setText(clipBoardText.c_str());
806 void CAssetBrowser::OnCopyPath()
808 string clipBoardText;
809 std::vector<CAsset*> assets;
810 std::vector<string> folders;
811 GetSelection(assets, folders);
813 for (const string& folder : folders)
815 clipBoardText += folder + "\n";;
818 for (const CAsset* pAsset : assets)
820 clipBoardText += pAsset->GetMetadataFile() + "\n";
823 QApplication::clipboard()->setText(clipBoardText.c_str());
826 void CAssetBrowser::InitActions()
828 m_pActionDelete = RegisterAction("general.delete", &CAssetBrowser::OnDelete);
829 RegisterAction("general.open", &CAssetBrowser::OnOpen);
830 m_pActionCopy = RegisterAction("general.copy", &CAssetBrowser::OnCopy);
831 m_pActionPaste = RegisterAction("general.paste", &CAssetBrowser::OnPaste);
832 m_pActionDuplicate = RegisterAction("general.duplicate", &CAssetBrowser::OnDuplicate);
833 RegisterAction("general.import", &CAssetBrowser::OnImport);
834 RegisterAction("general.new_folder", &CAssetBrowser::OnNewFolder);
835 m_pActionRename = RegisterAction("general.rename", &CAssetBrowser::OnRename);
836 m_pActionSave = RegisterAction("general.save", &CAssetBrowser::OnSave);
838 m_pActionShowInFileExplorer = RegisterAction("path_utils.show_in_file_explorer", &CAssetBrowser::OnShowInFileExplorer);
839 m_pActionGenerateThumbnails = RegisterAction("asset.generate_thumbnails", &CAssetBrowser::OnGenerateThumbmails);
840 m_pActionSaveAll = RegisterAction("asset.save_all", &CAssetBrowser::OnSaveAll);
841 m_pActionShowDetails = RegisterAction("asset.view_details", &CAssetBrowser::OnDetailsView);
842 m_pActionShowThumbnails = RegisterAction("asset.view_thumbnails", &CAssetBrowser::OnThumbnailsView);
843 m_pActionShowSplitHorizontally = RegisterAction("asset.view_split_horizontally", &CAssetBrowser::OnSplitHorizontalView);
844 m_pActionShowSplitVertically = RegisterAction("asset.view_split_vertically", &CAssetBrowser::OnSplitVerticalView);
845 m_pActionShowFoldersView = RegisterAction("asset.view_folder_tree", &CAssetBrowser::OnFolderTreeView);
846 m_pActionRecursiveView = RegisterAction("asset.view_recursive_view", &CAssetBrowser::OnRecursiveView);
847 m_pActionHideIrrelevantFolders = RegisterAction("asset.view_hide_irrelevant_folders", &CAssetBrowser::OnHideIrrelevantFolders);
848 m_pActionDiscardChanges = RegisterAction("asset.discard_changes", &CAssetBrowser::OnDiscardChanges);
849 m_pActionManageWorkFiles = RegisterAction("asset.manage_work_files", &CAssetBrowser::OnManageWorkFiles);
850 m_pActionGenerateRepairMetaData = RegisterAction("asset.generate_and_repair_all_metadata", &CAssetBrowser::OnGenerateRepairAllMetadata);
851 m_pActionReimport = RegisterAction("general.reimport", &CAssetBrowser::OnReimport);
853 m_pActionShowFoldersView->setChecked(true);
854 m_pActionRecursiveView->setChecked(false);
855 m_pActionHideIrrelevantFolders->setChecked(false);
857 m_pActionCopyName = RegisterAction("path_utils.copy_name", &CAssetBrowser::OnCopyName);
858 m_pActionCopyPath = RegisterAction("path_utils.copy_path", &CAssetBrowser::OnCopyPath);
860 #if ASSET_BROWSER_USE_PREVIEW_WIDGET
861 m_pActionShowPreview = RegisterAction("asset.show_preview", &CAssetBrowser::OnShowPreview);
862 #endif
865 void CAssetBrowser::InitMenus()
867 // File menu
868 AddToMenu({ CEditor::MenuItems::FileMenu, CEditor::MenuItems::Save });
870 CAbstractMenu* const pMenuFile = GetMenu(CEditor::MenuItems::FileMenu);
871 pMenuFile->signalAboutToShow.Connect([pMenuFile, this]()
873 auto folderSelection = m_pFoldersView->GetSelectedFolders();
874 pMenuFile->Clear();
875 pMenuFile->AddCommandAction(GetAction("general.new_folder"));
876 CAbstractMenu* subMenu = pMenuFile->CreateMenu(tr("New Asset"));
877 FillCreateAssetMenu(subMenu, folderSelection.size() == 1 && !CAssetFoldersModel::GetInstance()->IsReadOnlyFolder(folderSelection[0]));
879 int section = pMenuFile->GetNextEmptySection();
880 pMenuFile->AddCommandAction(GetAction("general.import"), section);
882 section = pMenuFile->GetNextEmptySection();
883 pMenuFile->AddCommandAction(m_pActionSave, section);
884 pMenuFile->AddCommandAction(m_pActionDiscardChanges, section);
885 section = pMenuFile->GetNextEmptySection();
886 pMenuFile->AddCommandAction(m_pActionSaveAll, section);
888 const bool isModified = HasSelectedModifiedAsset();
889 m_pActionDiscardChanges->setEnabled(isModified);
890 m_pActionSave->setEnabled(isModified);
892 section = pMenuFile->GetNextEmptySection();
893 pMenuFile->AddCommandAction(m_pActionGenerateThumbnails, section);
895 pMenuFile->AddCommandAction(m_pActionGenerateRepairMetaData, section);
896 m_pActionGenerateRepairMetaData->setEnabled(!CAssetManager::GetInstance()->IsScanning());
899 // Edit menu
900 AddToMenu({ MenuItems::EditMenu, MenuItems::Copy, MenuItems::Paste, MenuItems::Duplicate, MenuItems::Rename, MenuItems::Delete });
902 CAbstractMenu* const pMenuEdit = GetMenu(CEditor::MenuItems::EditMenu);
904 int section = pMenuEdit->GetNextEmptySection();
905 pMenuEdit->AddCommandAction(m_pActionReimport, section);
907 section = pMenuEdit->GetNextEmptySection();
908 m_pActionShowInFileExplorer = pMenuEdit->CreateCommandAction("path_utils.show_in_file_explorer", section);
909 pMenuEdit->AddCommandAction(m_pActionCopyName, section);
910 pMenuEdit->AddCommandAction(m_pActionCopyPath, section);
912 AddWorkFilesMenu(*pMenuEdit);
914 section = pMenuEdit->GetNextEmptySection();
915 pMenuEdit->AddCommandAction(m_pActionShowInFileExplorer, section);
917 pMenuFile->signalAboutToShow.Connect([this]()
919 UpdatePasteActionState();
922 //View menu
923 AddToMenu(CEditor::MenuItems::ViewMenu);
925 CAbstractMenu* const menuView = GetMenu(CEditor::MenuItems::ViewMenu);
927 section = menuView->GetNextEmptySection();
928 menuView->AddCommandAction(m_pActionShowDetails, section);
929 menuView->AddCommandAction(m_pActionShowThumbnails, section);
930 menuView->AddCommandAction(m_pActionShowSplitHorizontally, section);
931 menuView->AddCommandAction(m_pActionShowSplitVertically, section);
933 section = menuView->GetNextEmptySection();
934 menuView->AddCommandAction(m_pActionShowFoldersView, section);
936 #if ASSET_BROWSER_USE_PREVIEW_WIDGET
937 menuView->AddCommandAction(m_pActionShowPreview);
938 m_pActionShowPreview->setChecked(m_previewWidget->isVisible());
939 #endif
941 section = menuView->GetNextEmptySection();
943 menuView->AddCommandAction(m_pActionRecursiveView, section);
945 if (m_pFilterPanel)
947 m_pFilterPanel->CreateMenu(menuView);
950 s_signalMenuCreated(*GetMenu(MenuItems::ViewMenu), std::make_shared<Private_AssetBrowser::CContextMenuContext>(this));
953 void CAssetBrowser::InitAssetsView()
955 //selection model must be shared with all the views
956 m_pSelection = new QItemSelectionModel(m_pAttributeFilterProxyModel.get(), this);
957 connect(m_pSelection, &QItemSelectionModel::currentChanged, this, &CAssetBrowser::OnCurrentChanged);
958 connect(m_pSelection, &QItemSelectionModel::selectionChanged, [this](auto, auto)
960 OnSelectionChanged();
963 InitDetailsView();
964 InitThumbnailsView();
966 // Set up double-clicking.
968 typedef void (CAssetBrowser::* ResolveOverload)(const QModelIndex&);
969 connect(m_pDetailsView, &QAdvancedTreeView::activated, this, (ResolveOverload) &CAssetBrowser::OnActivated);
970 connect(m_pThumbnailView->GetInternalView(), &QAbstractItemView::activated, this, (ResolveOverload) &CAssetBrowser::OnActivated);
973 InitNewNameDelegates();
976 void CAssetBrowser::InitDetailsView()
978 using namespace Private_AssetBrowser;
980 m_pDetailsView = new QAssetDetailsView();
981 m_pDetailsView->setModel(m_pAttributeFilterProxyModel.get());
982 m_pDetailsView->setSelectionMode(QAbstractItemView::ExtendedSelection);
983 m_pDetailsView->setSelectionBehavior(QAbstractItemView::SelectRows);
984 m_pDetailsView->setSelectionModel(m_pSelection);
985 m_pDetailsView->setUniformRowHeights(true);
986 m_pDetailsView->setDragEnabled(true);
987 m_pDetailsView->setDragDropMode(QAbstractItemView::DragDrop);
988 m_pDetailsView->sortByColumn((int)eAssetColumns_Name, Qt::AscendingOrder);
989 m_pDetailsView->setContextMenuPolicy(Qt::CustomContextMenu);
990 m_pDetailsView->header()->setStretchLastSection(false);
991 m_pDetailsView->header()->resizeSection((int)eAssetColumns_Name, fontMetrics().width(QStringLiteral("wwwwwwwwwwwwwwwwwwwwwwwwww")));
992 m_pDetailsView->header()->resizeSection((int)eAssetColumns_Type, fontMetrics().width(QStringLiteral("wwwwwwwwwwwwwww")));
993 m_pDetailsView->setTreePosition((int)eAssetColumns_Name);
994 m_pDetailsView->setItemsExpandable(false);
995 m_pDetailsView->setRootIsDecorated(false);
996 m_pDetailsView->installEventFilter(this);
997 m_pDetailsView->setEditTriggers(m_pDetailsView->editTriggers() & ~QAbstractItemView::DoubleClicked);
999 connect(m_pDetailsView, &QTreeView::customContextMenuRequested, [this]() { OnContextMenu(); });
1001 FavoritesHelper::SetupView(m_pDetailsView, m_pDetailsView->GetAdvancedDelegate(), eAssetColumns_Favorite);
1004 void CAssetBrowser::InitThumbnailsView()
1006 using namespace Private_AssetBrowser;
1008 m_pThumbnailView = new QThumbnailsView(new CThumbnailsInternalView(), false, this);
1009 m_pThumbnailView->SetModel(m_pAttributeFilterProxyModel.get());
1010 m_pThumbnailView->SetRootIndex(QModelIndex());
1011 m_pThumbnailView->signalShowContextMenu.Connect(this, &CAssetBrowser::OnContextMenu);
1012 m_pThumbnailView->installEventFilter(this);
1013 QAbstractItemView* const pView = m_pThumbnailView->GetInternalView();
1014 pView->setSelectionMode(QAbstractItemView::ExtendedSelection);
1015 pView->setSelectionBehavior(QAbstractItemView::SelectRows);
1016 pView->setSelectionModel(m_pSelection);
1017 pView->setDragDropMode(QAbstractItemView::DragDrop);
1018 pView->setEditTriggers(pView->editTriggers() & ~QAbstractItemView::DoubleClicked);
1021 QWidget* CAssetBrowser::CreateAssetsViewSelector()
1023 using namespace Private_AssetBrowser;
1025 QWidget* const pAssetsView = new QWidget();
1027 m_pMainViewSplitter = new QSplitter();
1028 m_pMainViewSplitter->setOrientation(Qt::Horizontal);
1029 m_pMainViewSplitter->addWidget(m_pDetailsView);
1030 m_pMainViewSplitter->addWidget(m_pThumbnailView);
1032 m_pAssetsViewLayout = new QBoxLayout(QBoxLayout::LeftToRight);
1033 m_pAssetsViewLayout->setSpacing(0);
1034 m_pAssetsViewLayout->setMargin(0);
1035 m_pAssetsViewLayout->addWidget(m_pMainViewSplitter);
1036 pAssetsView->setLayout(m_pAssetsViewLayout);
1038 return pAssetsView;
1041 void CAssetBrowser::SelectAsset(const char* szPath) const
1043 CAsset* pAsset = CAssetManager::GetInstance()->FindAssetForFile(szPath);
1044 if (pAsset)
1046 SelectAsset(*pAsset);
1048 else if (strchr(szPath, '.')) // try to select folder by the file path
1050 m_pFoldersView->SelectFolder(QtUtil::ToQString(PathUtil::GetDirectory(szPath)));
1052 else
1054 m_pFoldersView->SelectFolder(QtUtil::ToQString(szPath));
1058 void CAssetBrowser::SelectAsset(const CAsset& asset) const
1060 m_pFoldersView->SelectFolder(asset.GetFolder().c_str());
1061 QModelIndex idx = CAssetModel::GetInstance()->ToIndex(asset);
1062 QModelIndex result;
1063 QAbstractItemView* const pActiveView = m_viewMode == Thumbnails ? m_pThumbnailView->GetInternalView() : m_pDetailsView;
1064 QtUtil::MapFromSourceIndirect(pActiveView, idx, result);
1065 m_pSelection->setCurrentIndex(result, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1068 // TODO: Only add menu entries for asset types that support creating new assets, i.e., implement CAssetType::Create().
1069 void CAssetBrowser::FillCreateAssetMenu(CAbstractMenu* menu, bool enable)
1071 for (CAssetType* pAssetType : CAssetManager::GetInstance()->GetAssetTypes())
1073 if (!pAssetType->CanBeCreated())
1075 continue;
1078 QAction* const pAction = menu->CreateAction(pAssetType->GetUiTypeName());
1079 connect(pAction, &QAction::triggered, [this, pAssetType]() { BeginCreateAsset(*pAssetType, nullptr); });
1080 const bool knownAssetType = std::find(m_knownAssetTypes.cbegin(), m_knownAssetTypes.cend(), pAssetType) != m_knownAssetTypes.cend();
1081 pAction->setEnabled(enable && knownAssetType);
1085 void CAssetBrowser::EditNewAsset()
1087 QAbstractItemView* pView = GetFocusedView();
1088 if (!pView)
1090 if (m_viewMode == Thumbnails)
1092 pView = m_pThumbnailView->GetInternalView();
1094 else
1096 pView = m_pDetailsView;
1100 const int col = pView == m_pThumbnailView->GetInternalView() ? eAssetColumns_Thumbnail : eAssetColumns_Name;
1102 const QModelIndex sourceIndex = CNewAssetModel::GetInstance()->index(0, col, QModelIndex());
1103 QModelIndex filteredIndex;
1104 if (!QtUtil::MapFromSourceIndirect(m_pAttributeFilterProxyModel.get(), sourceIndex, filteredIndex))
1106 return;
1109 if (filteredIndex.isValid())
1111 pView->edit(filteredIndex);
1112 pView->scrollTo(filteredIndex);
1113 pView->selectionModel()->select(filteredIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
1115 else
1117 CNewAssetModel::GetInstance()->setData(sourceIndex, "Untitled");
1118 EndCreateAsset();
1122 bool CAssetBrowser::OnDiscardChanges()
1124 const QString title(tr("Discard Changes"));
1125 const QString text(tr("Are you sure you want to discard the changes in the selected assets?"));
1127 const auto button = CQuestionDialog::SQuestion(title, text, QDialogButtonBox::Discard | QDialogButtonBox::Cancel, QDialogButtonBox::Cancel);
1128 if (QDialogButtonBox::Discard == button)
1130 for (CAsset* pAsset : GetSelectedAssets())
1132 pAsset->Reload();
1135 return true;
1138 bool CAssetBrowser::OnShowInFileExplorer()
1140 std::vector<CAsset*> assets;
1141 std::vector<string> folders;
1142 GetSelection(assets, folders);
1144 if (assets.empty() && folders.empty())
1146 folders = GetSelectedFolders();
1148 for (CAsset* pAsset : assets)
1150 const string path = PathUtil::Make(PathUtil::GetGameProjectAssetsPath(), pAsset->GetMetadataFile());
1151 QtUtil::OpenInExplorer(path);
1153 for (const string& folder : folders)
1155 OnOpenInExplorer(QtUtil::ToQString(folder));
1157 return true;
1160 bool CAssetBrowser::OnGenerateThumbmails()
1162 std::vector<CAsset*> assets;
1163 std::vector<string> folders;
1164 GetSelection(assets, folders);
1165 for (const string& folder : folders)
1167 GenerateThumbnailsAsync(folder);
1169 if (!folders.empty())
1171 return true;
1173 std::vector<string> selectedFolders = GetSelectedFolders();
1174 for (const string& folder : selectedFolders)
1176 GenerateThumbnailsAsync(folder);
1178 return true;
1181 bool CAssetBrowser::OnSaveAll()
1183 GetIEditor()->FindDockable("PropertyTree");
1184 CProgressNotification notification(tr("Saving modified assets"), QString(), true);
1185 auto progress = [&notification](float value) { notification.SetProgress(value); };
1186 CAssetManager::GetInstance()->SaveAll(progress);
1187 return true;
1190 bool CAssetBrowser::OnRecursiveView()
1192 SetRecursiveView(IsRecursiveView());
1193 return true;
1196 bool CAssetBrowser::OnFolderTreeView()
1198 m_pFoldersView->setVisible(IsFoldersViewVisible());
1199 return true;
1202 bool CAssetBrowser::OnManageWorkFiles()
1204 auto assets = GetSelectedAssets();
1205 if (assets.size() > 0)
1207 CManageWorkFilesDialog::ShowWindow(assets.back());
1208 return true;
1210 return false;
1213 bool CAssetBrowser::OnDetailsView()
1215 SetViewMode(Details);
1216 return true;
1219 bool CAssetBrowser::OnThumbnailsView()
1221 SetViewMode(Thumbnails);
1222 return true;
1225 bool CAssetBrowser::OnSplitHorizontalView()
1227 SetViewMode(HSplit);
1228 return true;
1231 bool CAssetBrowser::OnSplitVerticalView()
1233 SetViewMode(VSplit);
1234 return true;
1237 bool CAssetBrowser::OnGenerateRepairAllMetadata()
1239 auto pNotification = std::make_shared<CProgressNotification>(tr("Generating/Repairing Metadata"), QString(), false);
1240 CAssetManager::GetInstance()->GenerateCryassetsAsync([pNotification]() {});
1241 return true;
1244 #if ASSET_BROWSER_USE_PREVIEW_WIDGET
1245 bool CAssetBrowser::OnShowPreview()
1247 m_previewWidget->setVisible(!m_previewWidget->isVisible());
1248 UpdatePreview(m_pSelection->currentIndex());
1250 #endif
1252 void CAssetBrowser::BeginCreateAsset(const CAssetType& type, const CAssetType::SCreateParams* pCreateParams)
1254 auto folderSelection = m_pFoldersView->GetSelectedFolders();
1255 string folder = QtUtil::ToString(folderSelection.front());
1256 if (folderSelection.size() != 1)
1258 return; // More than one folder selected, so target folder is ambiguous.
1261 CNewAssetModel::GetInstance()->BeginCreateAsset(folder, "Untitled", type, pCreateParams);
1263 EditNewAsset();
1266 void CAssetBrowser::EndCreateAsset()
1268 CNewAssetModel* const pModel = CNewAssetModel::GetInstance();
1269 pModel->EndCreateAsset();
1270 CAsset* pAsset = pModel->GetNewAsset();
1271 if (pAsset)
1273 SelectAsset(*pAsset);
1277 CAsset* CAssetBrowser::QueryNewAsset(const CAssetType& type, const CAssetType::SCreateParams* pCreateParams)
1279 BeginCreateAsset(type, pCreateParams);
1281 CNewAssetModel* const pModel = CNewAssetModel::GetInstance();
1282 while (CNewAssetModel::GetInstance()->IsEditing())
1284 qApp->processEvents();
1286 return pModel->GetNewAsset();
1289 void CAssetBrowser::SetLayout(const QVariantMap& state)
1291 CDockableEditor::SetLayout(state);
1293 QVariant mainViewSplitter = state.value("mainViewSplitter");
1294 if (mainViewSplitter.isValid())
1296 m_pMainViewSplitter->restoreState(QByteArray::fromBase64(mainViewSplitter.toByteArray()));
1299 QVariant foldersSplitter = state.value("foldersSplitter");
1300 if (foldersSplitter.isValid())
1302 m_pFoldersSplitter->restoreState(QByteArray::fromBase64(foldersSplitter.toByteArray()));
1305 QVariant viewModeVar = state.value("viewMode");
1306 if (viewModeVar.isValid())
1308 SetViewMode((ViewMode)viewModeVar.toInt());
1311 QVariant recursiveViewVar = state.value("recursiveView");
1312 if (recursiveViewVar.isValid())
1314 SetRecursiveView(recursiveViewVar.toBool());
1317 QVariant showFoldersVar = state.value("showFolders");
1318 if (showFoldersVar.isValid())
1320 SetFoldersViewVisible(showFoldersVar.toBool());
1324 #if ASSET_BROWSER_USE_PREVIEW_WIDGET
1325 QVariant showPreviewVar = state.value("showPreview");
1326 if (showPreviewVar.isValid())
1328 m_previewWidget->setVisible(showPreviewVar.toBool());
1330 #endif
1332 QVariant filtersStateVar = state.value("filters");
1333 if (filtersStateVar.isValid() && filtersStateVar.type() == QVariant::Map)
1335 m_pFilterPanel->SetState(filtersStateVar.value<QVariantMap>());
1338 QVariant detailsViewVar = state.value("detailsView");
1339 if (detailsViewVar.isValid() && detailsViewVar.type() == QVariant::Map)
1341 m_pDetailsView->SetState(detailsViewVar.value<QVariantMap>());
1344 QVariant thumbnailViewVar = state.value("thumbnailView");
1345 if (thumbnailViewVar.isValid() && thumbnailViewVar.type() == QVariant::Map)
1347 m_pThumbnailView->SetState(thumbnailViewVar.value<QVariantMap>());
1350 QVariant foldersViewVar = state.value("foldersView");
1351 if (foldersViewVar.isValid() && foldersViewVar.type() == QVariant::Map)
1353 m_pFoldersView->SetState(foldersViewVar.value<QVariantMap>());
1356 UpdateNavigation(true);
1359 void CAssetBrowser::SetFoldersViewVisible(const bool isVisible)
1361 m_pActionShowFoldersView->setChecked(isVisible);
1362 m_pFoldersView->setVisible(isVisible);
1365 QVariantMap CAssetBrowser::GetLayout() const
1367 QVariantMap state = CDockableEditor::GetLayout();
1369 state.insert("mainViewSplitter", m_pMainViewSplitter->saveState().toBase64());
1370 state.insert("foldersSplitter", m_pFoldersSplitter->saveState().toBase64());
1371 state.insert("viewMode", (int)m_viewMode);
1372 state.insert("recursiveView", IsRecursiveView());
1373 state.insert("showFolders", IsFoldersViewVisible());
1374 #if ASSET_BROWSER_USE_PREVIEW_WIDGET
1375 state.insert("showPreview", m_previewWidget->isVisibleTo(this));
1376 #endif
1377 state.insert("filters", m_pFilterPanel->GetState());
1378 state.insert("detailsView", m_pDetailsView->GetState());
1379 state.insert("thumbnailView", m_pThumbnailView->GetState());
1380 state.insert("foldersView", m_pFoldersView->GetState());
1382 return state;
1385 QFilteringPanel* CAssetBrowser::GetFilterPanel()
1387 return m_pFilterPanel;
1390 std::vector<CAsset*> CAssetBrowser::GetSelectedAssets() const
1392 std::vector<CAsset*> assets;
1393 std::vector<string> folders;
1394 GetSelection(assets, folders);
1395 return assets;
1398 std::vector<string> CAssetBrowser::GetSelectedFolders() const
1400 std::vector<string> folders;
1401 folders.reserve(folders.size());
1402 for (const QString& f : m_pFoldersView->GetSelectedFolders())
1404 folders.push_back(QtUtil::ToString(f));
1406 return folders;
1409 CAsset* CAssetBrowser::GetLastSelectedAsset() const
1411 using namespace Private_AssetBrowser;
1413 auto index = m_pSelection->currentIndex();
1414 if (index.isValid() && CAssetModel::IsAsset(index))
1415 return CAssetModel::ToAsset(index);
1416 else
1417 return nullptr;
1420 bool CAssetBrowser::IsRecursiveView() const
1422 return m_pActionRecursiveView->isChecked();
1425 bool CAssetBrowser::IsFoldersViewVisible() const
1427 return m_pActionShowFoldersView->isChecked();
1430 bool CAssetBrowser::AreIrrelevantFoldersHidden() const
1432 return m_pActionHideIrrelevantFolders->isChecked();
1435 void CAssetBrowser::HideIrrelevantFolders(bool isHidden)
1437 m_pActionHideIrrelevantFolders->setChecked(isHidden);
1438 UpdateNonEmptyFolderList();
1441 void CAssetBrowser::SetViewMode(ViewMode viewMode)
1443 if (m_viewMode != viewMode)
1445 switch (viewMode)
1447 case Details:
1448 m_pThumbnailView->setVisible(false);
1449 m_pDetailsView->setVisible(true);
1450 break;
1451 case Thumbnails:
1452 m_pThumbnailView->setVisible(true);
1453 m_pDetailsView->setVisible(false);
1454 break;
1455 case HSplit:
1456 case VSplit:
1457 m_pThumbnailView->setVisible(true);
1458 m_pDetailsView->setVisible(true);
1459 m_pMainViewSplitter->setOrientation(viewMode == VSplit ? Qt::Vertical : Qt::Horizontal);
1460 break;
1461 default:
1462 CRY_ASSERT(0);
1463 break;
1466 m_viewMode = viewMode;
1469 m_pActionShowDetails->setChecked(viewMode == Details ? true : false);
1470 m_pActionShowThumbnails->setChecked(viewMode == Thumbnails ? true : false);
1471 m_pActionShowSplitHorizontally->setChecked(viewMode == HSplit ? true : false);
1472 m_pActionShowSplitVertically->setChecked(viewMode == VSplit ? true : false);
1475 void CAssetBrowser::SetRecursiveView(bool recursiveView)
1477 m_pActionRecursiveView->setChecked(recursiveView);
1478 m_pFolderFilterModel->SetRecursive(recursiveView);
1479 m_pFolderFilterModel->SetShowFolders(!recursiveView);
1480 m_recursiveSearch = false;
1483 void CAssetBrowser::OnSearch(bool isNewSearch)
1485 // If recursive view is disabled:
1486 // - Enable recursive view as soon as the first character is entered.
1487 // - Disable recursive view when the last character is removed from the query.
1489 const bool searching = !m_pFilterPanel->GetSearchBox()->IsEmpty();
1490 if (searching && !m_recursiveSearch && isNewSearch && !IsRecursiveView())
1492 SetRecursiveView(true);
1493 m_recursiveSearch = true;
1495 else if (!searching && m_recursiveSearch)
1497 m_recursiveSearch = false;
1498 SetRecursiveView(false);
1501 UpdateNonEmptyFolderList();
1504 QAbstractItemView* CAssetBrowser::GetFocusedView() const
1506 QWidget* w = QApplication::focusWidget();
1507 if (w == m_pThumbnailView->GetInternalView())
1508 return m_pThumbnailView->GetInternalView();
1509 else if (w == m_pDetailsView)
1510 return m_pDetailsView;
1511 return nullptr;
1514 bool CAssetBrowser::eventFilter(QObject* object, QEvent* event)
1516 using namespace Private_AssetBrowser;
1518 if (event->type() == QEvent::ToolTip)
1520 if (object == m_pDetailsView)
1522 auto index = m_pDetailsView->indexAt(m_pDetailsView->viewport()->mapFromGlobal(QCursor::pos()));
1523 auto asset = CAssetModel::ToAsset(index);
1524 if (asset)
1525 CAssetTooltip::ShowTrackingTooltip(asset);
1526 else
1527 CAssetTooltip::HideTooltip();
1529 event->accept();
1530 return true;
1533 if (object == m_pThumbnailView)
1535 auto index = m_pThumbnailView->GetInternalView()->indexAt(m_pThumbnailView->GetInternalView()->viewport()->mapFromGlobal(QCursor::pos()));
1536 auto asset = CAssetModel::ToAsset(index);
1537 if (asset)
1538 CAssetTooltip::ShowTrackingTooltip(asset);
1539 else
1540 CAssetTooltip::HideTooltip();
1542 event->accept();
1543 return true;
1546 else if (event->type() == QEvent::MouseButtonRelease)
1548 event->ignore();
1549 mouseReleaseEvent((QMouseEvent*)event);
1550 if (event->isAccepted())
1551 return true;
1554 return false;
1557 void CAssetBrowser::OnAdaptiveLayoutChanged()
1559 CEditor::OnAdaptiveLayoutChanged();
1560 m_pFoldersSplitter->setOrientation(GetOrientation());
1561 m_pAssetsViewLayout->setDirection(GetOrientation() == Qt::Horizontal ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom);
1564 void CAssetBrowser::GetSelection(std::vector<CAsset*>& assets, std::vector<string>& folders) const
1566 using namespace Private_AssetBrowser;
1568 auto indexList = m_pSelection->selectedRows(eAssetColumns_Name);
1569 assets.reserve(indexList.size());
1570 folders.reserve(indexList.size());
1571 for (auto& index : indexList)
1573 const int type = GetType(index);
1574 switch (type)
1576 case eAssetModelRow_Asset:
1578 // The asset can be nullptr if we are in the process of creating a new asset. See CAssetBrowser::EditNewAsset()
1579 CAsset* const pAsset = CAssetModel::ToAsset(index);
1580 if (pAsset)
1582 assets.push_back(pAsset);
1585 break;
1586 case eAssetModelRow_Folder:
1587 folders.push_back(QtUtil::ToString(ToFolderPath(index)));
1588 break;
1589 default:
1590 CRY_ASSERT(0);
1591 break;
1596 void CAssetBrowser::OnSelectionChanged()
1598 UpdateSelectionDependantActions();
1599 SelectionChanged();
1602 void CAssetBrowser::UpdateSelectionDependantActions()
1604 std::vector<CAsset*> assets;
1605 std::vector<string> folders;
1606 GetSelection(assets, folders);
1608 if (assets.empty() && folders.empty())
1610 folders = GetSelectedFolders();
1613 const QString selectedFolder = folders.size() == 1 ? QtUtil::ToQString(folders[0]) : QString();
1615 const bool hasAssetsSelected = !assets.empty();
1616 const bool hasWritableFolderSelected = !selectedFolder.isNull() && !CAssetFoldersModel::GetInstance()->IsReadOnlyFolder(selectedFolder);
1617 const bool hasEmptyFolderSelected = hasWritableFolderSelected && CAssetFoldersModel::GetInstance()->IsEmptyFolder(selectedFolder);
1619 m_pActionManageWorkFiles->setEnabled(hasAssetsSelected);
1620 m_pActionDelete->setEnabled(hasAssetsSelected ^ hasEmptyFolderSelected);
1621 m_pActionRename->setEnabled(hasAssetsSelected ^ hasEmptyFolderSelected);
1622 m_pActionCopy->setEnabled(hasAssetsSelected);
1623 m_pActionDuplicate->setEnabled(hasAssetsSelected);
1624 m_pActionSave->setEnabled(hasAssetsSelected);
1625 m_pActionReimport->setEnabled(hasAssetsSelected);
1627 GetAction("general.new_folder")->setEnabled(hasWritableFolderSelected);
1628 m_pActionShowInFileExplorer->setEnabled(hasWritableFolderSelected);
1629 m_pActionGenerateThumbnails->setEnabled(hasWritableFolderSelected);
1631 UpdatePasteActionState();
1634 void CAssetBrowser::UpdatePasteActionState()
1636 m_pActionPaste->setEnabled(!Private_AssetBrowser::g_clipboard.empty() && !m_pFolderFilterModel->IsRecursive());
1639 void CAssetBrowser::OnFolderViewContextMenu()
1641 CreateContextMenu(true);
1644 void CAssetBrowser::OnContextMenu()
1646 CreateContextMenu(false);
1649 void CAssetBrowser::CreateContextMenu(bool isFolderView /*= false*/)
1651 //TODO : This could be unified more with the folders view's context menu
1653 CAbstractMenu abstractMenu;
1655 std::vector<CAsset*> assets;
1656 std::vector<string> folders;
1657 GetSelection(assets, folders);
1659 if (!assets.empty())
1661 BuildContextMenuForAssets(assets, folders, abstractMenu);
1663 else if (!folders.empty() || isFolderView)
1665 if (isFolderView)
1667 folders = GetSelectedFolders();
1669 BuildContextMenuForFolders(folders, abstractMenu);
1671 else if (assets.empty() && folders.empty())
1673 BuildContextMenuForEmptiness(abstractMenu);
1676 QMenu menu;
1677 abstractMenu.Build(MenuWidgetBuilders::CMenuBuilder(&menu));
1679 if (menu.actions().count() > 0)
1681 menu.exec(QCursor::pos());
1685 void CAssetBrowser::BuildContextMenuForEmptiness(CAbstractMenu& abstractMenu)
1687 const std::vector<string> selectedFolders = GetSelectedFolders();
1688 CAssetFoldersModel* const pModel = CAssetFoldersModel::GetInstance();
1690 int foldersSection = abstractMenu.GetNextEmptySection();
1691 abstractMenu.SetSectionName(foldersSection, "Folders");
1693 auto folder = QtUtil::ToQString(selectedFolders[0]);
1694 abstractMenu.AddCommandAction(GetAction("general.new_folder"));
1696 CAbstractMenu* const pCreateAssetMenu = abstractMenu.CreateMenu(tr("New Asset"));
1697 FillCreateAssetMenu(pCreateAssetMenu, selectedFolders.size() == 1 && !pModel->IsReadOnlyFolder(folder));
1699 abstractMenu.AddCommandAction(GetAction("general.paste"), foldersSection);
1700 abstractMenu.AddCommandAction(GetAction("general.import"), foldersSection);
1701 abstractMenu.AddCommandAction(m_pActionShowInFileExplorer, foldersSection);
1702 abstractMenu.AddCommandAction(m_pActionGenerateThumbnails, foldersSection);
1704 int section = abstractMenu.GetNextEmptySection();
1705 abstractMenu.AddCommandAction(m_pActionRecursiveView, section);
1706 abstractMenu.AddCommandAction(m_pActionShowDetails, section);
1707 abstractMenu.AddCommandAction(m_pActionShowThumbnails, section);
1708 abstractMenu.AddCommandAction(m_pActionShowSplitHorizontally, section);
1709 abstractMenu.AddCommandAction(m_pActionShowSplitVertically, section);
1711 section = abstractMenu.GetNextEmptySection();
1712 abstractMenu.AddCommandAction(m_pActionShowFoldersView, section);
1714 UpdateSelectionDependantActions();
1716 NotifyContextMenuCreation(abstractMenu, {}, selectedFolders);
1719 void CAssetBrowser::BuildContextMenuForFolders(const std::vector<string>& folders, CAbstractMenu& abstractMenu)
1721 if (folders.size() > 1)
1723 return;
1726 //Do not show folder actions if we are not showing folder
1727 auto folder = QtUtil::ToQString(folders[0]);
1728 if (CAssetFoldersModel::GetInstance()->IsReadOnlyFolder(folder))
1729 return;
1731 //TODO : move this, just only add the separator if we add more things later
1732 int foldersSection = abstractMenu.GetNextEmptySection();
1733 abstractMenu.SetSectionName(foldersSection, "Folders");
1734 abstractMenu.AddCommandAction(GetAction("general.new_folder"));
1736 abstractMenu.AddCommandAction(m_pActionDelete);
1737 abstractMenu.AddCommandAction(m_pActionRename);
1738 abstractMenu.AddCommandAction(m_pActionShowInFileExplorer);
1739 abstractMenu.AddCommandAction(m_pActionGenerateThumbnails);
1741 UpdateSelectionDependantActions();
1743 NotifyContextMenuCreation(abstractMenu, {}, folders);
1746 void CAssetBrowser::BuildContextMenuForAssets(const std::vector<CAsset*>& assets, const std::vector<string>& folders, CAbstractMenu& abstractMenu)
1748 bool canReimport = false;
1749 bool canCopy = false;
1750 bool isImmutable = false;
1751 bool isModified = false;
1752 QMap<const CAssetType*, std::vector<CAsset*>> assetsByType;
1754 for (CAsset* asset : assets)
1756 if (asset->GetType()->IsImported() && !asset->IsImmutable() && asset->HasSourceFile())
1758 canReimport = true;
1761 if (asset->IsImmutable() || FileUtils::Pak::IsFileInPakOnly(assets.front()->GetFile(0)))
1763 isImmutable = true;
1766 canCopy = canCopy || asset->GetType()->CanBeCopied();
1768 isModified = isModified || asset->IsModified();
1770 assetsByType[asset->GetType()].push_back(asset);
1773 abstractMenu.FindSectionByName("Assets");
1775 abstractMenu.AddCommandAction(m_pActionCopy);
1776 m_pActionCopy->setEnabled(canCopy);
1778 abstractMenu.AddCommandAction(m_pActionDuplicate);
1779 m_pActionDuplicate->setEnabled(canCopy);
1781 abstractMenu.AddCommandAction(m_pActionReimport);
1782 m_pActionReimport->setEnabled(canReimport);
1784 abstractMenu.AddCommandAction(m_pActionDelete);
1785 m_pActionDelete->setEnabled(!isImmutable);
1787 abstractMenu.AddCommandAction(m_pActionSave);
1788 m_pActionSave->setEnabled(isModified);
1790 abstractMenu.AddCommandAction(m_pActionDiscardChanges);
1791 m_pActionDiscardChanges->setEnabled(isModified);
1793 int section = abstractMenu.GetNextEmptySection();
1794 abstractMenu.AddCommandAction(m_pActionSaveAll, section);
1796 //TODO : source control
1797 auto it = assetsByType.begin();
1798 for (; it != assetsByType.end(); ++it)
1800 if (it->size() != 0)
1802 const CAssetType* pType = it.key();
1803 string s = pType->GetTypeName();
1804 std::vector<CAsset*> assets = it.value();
1805 pType->AppendContextMenuActions(assets, &abstractMenu);
1809 if (assets.size() == 1)
1811 CAsset* pAsset = assets.front();
1813 AddWorkFilesMenu(abstractMenu);
1815 const bool canBeRenamed = !isImmutable && pAsset->IsWritable(true);
1817 abstractMenu.AddCommandAction(m_pActionRename);
1818 m_pActionRename->setEnabled(canBeRenamed);
1820 abstractMenu.AddCommandAction(m_pActionShowInFileExplorer);
1821 m_pActionShowInFileExplorer->setEnabled(!isImmutable);
1823 AppendFilterDependenciesActions(&abstractMenu, assets.front());
1826 NotifyContextMenuCreation(abstractMenu, assets, folders);
1829 void CAssetBrowser::AddWorkFilesMenu(CAbstractMenu& abstractMenu)
1831 const int section = abstractMenu.GetNextEmptySection();
1832 auto pWorkFilesMenu = abstractMenu.CreateMenu(tr("Work Files"), section);
1833 pWorkFilesMenu->signalAboutToShow.Connect([pWorkFilesMenu, this]()
1835 pWorkFilesMenu->Clear();
1836 const auto assets = GetSelectedAssets();
1837 if (assets.size() == 1 && !assets.front()->GetWorkFiles().empty())
1839 int workFilesListSection = pWorkFilesMenu->GetNextEmptySection();
1840 for (const string& workFile : assets.front()->GetWorkFiles())
1842 auto pWorkFileMenu = pWorkFilesMenu->CreateMenu(QtUtil::ToQString(PathUtil::GetFile(workFile)), workFilesListSection);
1843 auto action = pWorkFileMenu->CreateAction(tr("Open..."));
1844 connect(action, &QAction::triggered, [workFile]()
1846 const string path = PathUtil::Make(PathUtil::GetGameProjectAssetsPath(), workFile);
1847 QtUtil::OpenFileForEdit(path);
1850 action = pWorkFileMenu->CreateAction(tr("Copy Path"));
1851 connect(action, &QAction::triggered, [workFile]()
1853 const string path = PathUtil::MatchAbsolutePathToCaseOnFileSystem(PathUtil::Make(PathUtil::GetGameProjectAssetsPath(), workFile));
1854 QApplication::clipboard()->setText(QtUtil::ToQString(path));
1857 action = pWorkFileMenu->CreateAction(tr("Show in File Explorer"));
1858 connect(action, &QAction::triggered, [workFile]()
1860 QtUtil::OpenInExplorer(PathUtil::Make(PathUtil::GetGameProjectAssetsPath(), workFile));
1864 pWorkFilesMenu->AddCommandAction(m_pActionManageWorkFiles);
1868 bool CAssetBrowser::HasSelectedModifiedAsset() const
1870 const auto assets = GetSelectedAssets();
1871 for (CAsset* asset : assets)
1873 if (asset->IsModified())
1875 return true;
1878 return false;
1881 void CAssetBrowser::NotifyContextMenuCreation(CAbstractMenu& menu, const std::vector<CAsset*>& assets, const std::vector<string>& folders)
1883 if (menu.FindSectionByName("Assets") == CAbstractMenu::eSections_Default)
1885 int section = menu.GetNextEmptySection();
1886 menu.SetSectionName(section, "Assets");
1889 s_signalContextMenuRequested(menu, assets, folders, std::make_shared<Private_AssetBrowser::CContextMenuContext>(this));
1892 void CAssetBrowser::AppendFilterDependenciesActions(CAbstractMenu* pAbstractMenu, const CAsset* pAsset)
1894 using namespace Private_AssetBrowser;
1896 const auto dependencyOperators = Attributes::s_dependenciesAttribute.GetType()->GetOperators();
1897 for (Attributes::IAttributeFilterOperator* pOperator : dependencyOperators)
1899 QAction* pAction = pAbstractMenu->CreateAction(QString("%1 %2 '%3'").arg(tr("Show Assets"), pOperator->GetName(), QtUtil::ToQString(pAsset->GetName())));
1900 connect(pAction, &QAction::triggered, [pOperator, pAsset]()
1902 CAssetBrowser* const pAssetBrowser = static_cast<CAssetBrowser*>(GetIEditor()->CreateDockable("Asset Browser"));
1903 if (pAssetBrowser)
1905 pAssetBrowser->GetFilterPanel()->AddFilter(Attributes::s_dependenciesAttribute.GetName(), pOperator->GetName(), QtUtil::ToQString(pAsset->GetFile(0)));
1906 pAssetBrowser->GetFilterPanel()->SetExpanded(true);
1907 pAssetBrowser->SetRecursiveView(true);
1913 void CAssetBrowser::OnActivated(const QModelIndex& index)
1915 using namespace Private_AssetBrowser;
1917 const int type = GetType(index);
1918 switch (type)
1920 case eAssetModelRow_Asset:
1922 CAsset* pAsset = CAssetModel::ToAsset(index);
1923 if (pAsset)
1925 OnActivated(pAsset);
1927 break;
1929 case eAssetModelRow_Folder:
1931 OnActivated(ToFolderPath(index));
1932 break;
1934 default:
1935 CRY_ASSERT(0);
1936 break;
1940 void CAssetBrowser::OnActivated(CAsset* pAsset)
1942 pAsset->Edit();
1945 void CAssetBrowser::OnActivated(const QString& folder)
1947 m_pFoldersView->SelectFolder(folder);
1950 void CAssetBrowser::OnCurrentChanged(const QModelIndex& current, const QModelIndex& previous)
1952 if (current.isValid())
1954 //selections are in sync but views and scrolling is not always
1955 m_pThumbnailView->ScrollToRow(current);
1956 m_pDetailsView->scrollTo(current);
1957 UpdatePreview(current);
1961 void CAssetBrowser::UpdatePreview(const QModelIndex& currentIndex)
1963 #if ASSET_BROWSER_USE_PREVIEW_WIDGET
1964 using namespace Private_AssetBrowser;
1966 if (m_previewWidget->isVisible())
1968 if (CAssetModel::IsAsset(currentIndex))
1970 CAsset* asset = CAssetModel::ToAsset(currentIndex);
1971 if (asset)
1973 QWidget* w = asset->GetType()->CreatePreviewWidget(asset);
1974 if (w)
1976 m_previewWidget->SetChild(w);
1977 return;
1982 m_previewWidget->SetChild(nullptr);
1984 #endif
1987 bool CAssetBrowser::OnImport()
1989 using namespace Private_AssetBrowser;
1991 // If there are no imports, there are no supported extensions, so we cannot show the file dialog.
1992 if (!CAssetManager::GetInstance()->GetAssetImporters().size())
1994 const QString what = tr(
1995 "No importers available. This might be because you are missing editor plugins. "
1996 "If you build Sandbox locally, check if all plugins have been built successfully. "
1997 "If not, make sure that all required dependencies and SDKs are available.");
1998 CQuestionDialog::SWarning(tr("No importers registered"), what);
1999 return false;
2002 static const char* const szRecentImportPathProperty = "RecentImportPath";
2004 std::vector<string> filePaths;
2006 CSystemFileDialog::RunParams runParams;
2007 GetExtensionFilter(runParams.extensionFilters);
2009 const QString recentImportPath = GetProjectProperty(szRecentImportPathProperty).toString();
2010 if (!recentImportPath.isEmpty())
2012 runParams.initialDir = recentImportPath;
2015 std::vector<QString> v = CSystemFileDialog::RunImportMultipleFiles(runParams, nullptr);
2016 filePaths.reserve(v.size());
2017 std::transform(v.begin(), v.end(), std::back_inserter(filePaths), QtUtil::ToString);
2020 if (filePaths.empty())
2022 return false;
2025 SetProjectProperty(szRecentImportPathProperty, PathUtil::GetPathWithoutFilename(filePaths[0]).c_str());
2027 CAssetDropHandler dropHandler;
2028 if (dropHandler.CanImportAny(filePaths))
2030 CAssetDropHandler::SImportParams importParams;
2031 auto folderSelection = m_pFoldersView->GetSelectedFolders();
2032 if (folderSelection.size() == 1)
2034 importParams.outputDirectory = QtUtil::ToString(folderSelection.front());
2036 ThreadingUtils::AsyncFinalize([dropHandler, filePaths, importParams]
2038 return dropHandler.Import(filePaths, importParams);
2040 [](std::vector<CAsset*>&& assets)
2042 GetIEditor()->GetAssetManager()->MergeAssets(assets);
2045 else
2047 if (filePaths.size() > 1)
2049 CryWarning(VALIDATOR_MODULE_ASSETS, VALIDATOR_ERROR, "Cannot import files.");
2051 else
2053 CryWarning(VALIDATOR_MODULE_ASSETS, VALIDATOR_ERROR, "Cannot import file '%s'.", filePaths.front().c_str());
2055 return false;
2058 return true;
2061 bool CAssetBrowser::OnNewFolder()
2063 QString newFolderPath = CAssetFoldersModel::GetInstance()->CreateFolder(QtUtil::ToQString(GetSelectedFolders()[0]));
2064 OnRenameFolder(newFolderPath);
2065 return true;
2068 bool CAssetBrowser::OnRename()
2070 std::vector<CAsset*> assets;
2071 std::vector<string> folders;
2072 GetSelection(assets, folders);
2073 if (!assets.empty())
2075 OnRenameAsset(*assets.back());
2076 return true;
2079 if (folders.empty())
2081 folders = GetSelectedFolders();
2084 if (!folders.empty())
2086 const QString& qFolder = QtUtil::ToQString(folders.back());
2087 if (CAssetFoldersModel::GetInstance()->IsEmptyFolder(qFolder))
2089 OnRenameFolder(qFolder);
2090 return true;
2093 return false;
2096 bool CAssetBrowser::OnSave()
2098 for (CAsset* pAsset : GetSelectedAssets())
2100 pAsset->Save();
2102 return true;
2105 bool CAssetBrowser::OnReimport()
2107 const auto assets = GetSelectedAssets();
2108 for (CAsset* pAsset : assets)
2110 if (pAsset->GetType()->IsImported() && !pAsset->IsImmutable())
2112 pAsset->Reimport();
2115 return true;
2118 bool CAssetBrowser::OnHideIrrelevantFolders()
2120 UpdateNonEmptyFolderList();
2121 return true;
2124 void CAssetBrowser::Delete(const std::vector<CAsset*>& assets)
2126 CRY_ASSERT(std::none_of(assets.begin(), assets.end(), [](CAsset* pAsset) { return !pAsset; }));
2128 std::vector<CAsset*> assetsToDelete(assets);
2129 CAssetManager* const pAssetManager = CAssetManager::GetInstance();
2131 const QString question = tr("There is a possibility of undetected dependencies which can be violated after performing the operation.\n"
2132 "\n"
2133 "Do you really want to delete %n asset(s)?", "", assets.size());
2135 if (pAssetManager->HasAnyReverseDependencies(assetsToDelete))
2137 CAssetReverseDependenciesDialog assetDeleteDialog(
2138 assets,
2139 tr("Assets to be deleted"),
2140 tr("Dependent Assets"),
2141 tr("The following assets depend on the asset(s) to be deleted. Therefore they probably will not behave correctly after performing the delete operation."),
2142 question,
2143 this);
2144 assetDeleteDialog.setWindowTitle(tr("Delete Assets"));
2146 if (!assetDeleteDialog.Execute())
2148 return;
2151 else if (CQuestionDialog::SQuestion(tr("Delete Assets"), question) != QDialogButtonBox::Yes)
2153 return;
2156 pAssetManager->DeleteAssetsWithFiles(assetsToDelete);
2159 bool CAssetBrowser::OnOpen()
2161 const std::vector<CAsset*> assets = GetSelectedAssets();
2162 if (assets.empty())
2164 return false;
2167 for (CAsset* pAsset : assets)
2169 OnActivated(pAsset);
2171 return true;
2174 bool CAssetBrowser::OnCopy()
2176 using namespace Private_AssetBrowser;
2178 const std::vector<CAsset*> assets = GetSelectedAssets();
2179 if (assets.empty())
2181 return true;
2184 g_clipboard.clear();
2185 g_clipboard.reserve(assets.size());
2186 std::unordered_set<const CAssetType*> excludedTypes(CAssetManager::GetInstance()->GetAssetTypes().size());
2187 for (CAsset* pAsset : assets)
2189 if (!pAsset->GetType()->CanBeCopied())
2191 excludedTypes.insert(pAsset->GetType());
2192 continue;
2194 g_clipboard.push_back(pAsset->GetMetadataFile());
2197 for (const CAssetType* pType : excludedTypes)
2199 CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, "%s asset does not support Copy/Paste.", pType->GetUiTypeName());
2201 return true;
2204 bool CAssetBrowser::OnPaste()
2206 Paste(false);
2207 return true;
2210 bool CAssetBrowser::OnDuplicate()
2212 OnCopy();
2213 Paste(true);
2214 return true;
2217 void CAssetBrowser::Paste(bool pasteNextToOriginal)
2219 using namespace Private_AssetBrowser;
2221 if (g_clipboard.empty())
2223 return;
2226 string selectedFolder;
2227 if (!pasteNextToOriginal)
2229 if (m_recursiveSearch)
2231 CRY_ASSERT(!m_pFilterPanel->GetSearchBox()->IsEmpty());
2233 const char* szMsg = QT_TR_NOOP("The target folder is ambiguous when the search result is displayed. Please cancel the search by clearing the search bar.");
2234 CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, szMsg);
2235 return;
2238 if (IsRecursiveView())
2240 const char* szMsg = QT_TR_NOOP("Target folder is ambiguous.Please turn off the recursive view");
2241 CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, szMsg);
2242 return;
2245 auto folderSelection = m_pFoldersView->GetSelectedFolders();
2246 if (folderSelection.size() != 1)
2248 CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, "Target folder is ambiguous. Please select a single folder in the Asset Browser to paste");
2249 return;
2251 selectedFolder = PathUtil::AddSlash(QtUtil::ToString(folderSelection.front()));
2254 CAssetManager* const pAssetManager = CAssetManager::GetInstance();
2255 for (const string& asset : g_clipboard)
2257 CAsset* const pAsset = pAssetManager->FindAssetForMetadata(asset);
2258 if (!pAsset)
2260 continue;
2263 std::vector<string> exclusions;
2264 exclusions.reserve(100);
2265 const string& folder = pasteNextToOriginal ? pAsset->GetFolder() : selectedFolder;
2266 pAssetManager->ForeachAssetOfType(pAsset->GetType(), [folder, &exclusions](const CAsset* pExistingAsset)
2268 if (pExistingAsset->GetFolder().CompareNoCase(folder) == 0)
2270 exclusions.push_back(pExistingAsset->GetName());
2273 const string name = PathUtil::GetUniqueName(pAsset->GetName(), exclusions);
2275 const auto filename = PathUtil::Make(folder, pAsset->GetType()->MakeMetadataFilename(name));
2276 CAssetType::SCreateParams params;
2277 params.pSourceAsset = pAsset;
2278 pAsset->GetType()->Create(filename, &params);
2282 void CAssetBrowser::OnRenameFolder(const QString& folder)
2284 const QModelIndex sourceIndex = CAssetFoldersModel::GetInstance()->FindIndexForFolder(folder);
2285 if (!sourceIndex.isValid())
2287 return;
2290 // renaming in assets views.
2292 QAbstractItemView* const pView = GetFocusedView();
2293 if (pView)
2295 const auto column = pView == m_pThumbnailView->GetInternalView() ? EAssetColumns::eAssetColumns_Thumbnail : EAssetColumns::eAssetColumns_Name;
2296 QModelIndex index;
2297 if (QtUtil::MapFromSourceIndirect(pView, sourceIndex.sibling(sourceIndex.row(), column), index))
2299 m_pSelection->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
2300 pView->edit(index);
2302 return;
2305 // folders view.
2307 QWidget* pWidget = QApplication::focusWidget();
2308 if (pWidget != m_pFoldersView->GetInternalView())
2310 return;
2312 m_pFoldersView->RenameFolder(sourceIndex);
2315 void CAssetBrowser::OnOpenInExplorer(const QString& folder)
2317 CAssetFoldersModel::GetInstance()->OpenFolderWithShell(folder);
2320 void CAssetBrowser::OnRenameAsset(CAsset& asset)
2322 auto view = GetFocusedView();
2323 if (!view)
2325 return;
2328 auto column = view == m_pDetailsView ? EAssetColumns::eAssetColumns_Name : EAssetColumns::eAssetColumns_Thumbnail;
2329 QModelIndex sourceIndex = CAssetModel::GetInstance()->ToIndex(asset, column);
2330 QModelIndex index;
2331 if (QtUtil::MapFromSourceIndirect(view, sourceIndex, index))
2333 m_pSelection->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
2334 view->edit(index);
2338 void CAssetBrowser::GenerateThumbnailsAsync(const string& folder, const std::function<void()>& finalize /*= std::function<void()>()*/)
2340 AsseThumbnailsGenerator::GenerateThumbnailsAsync(folder, finalize);
2343 void CAssetBrowser::OnNavBack()
2345 m_dontPushNavHistory = true;
2347 if (m_navigationIndex >= 0)
2349 m_navigationIndex--;
2352 if (m_navigationIndex == -1)
2353 m_pFoldersView->ClearSelection();
2354 else
2355 m_pFoldersView->SelectFolders(m_navigationHistory[m_navigationIndex]);
2357 m_dontPushNavHistory = false;
2360 void CAssetBrowser::OnNavForward()
2362 m_dontPushNavHistory = true;
2364 m_navigationIndex++;
2365 m_pFoldersView->SelectFolders(m_navigationHistory[m_navigationIndex]);
2367 m_dontPushNavHistory = false;
2370 void CAssetBrowser::OnFolderSelectionChanged(const QStringList& selectedFolders)
2372 using namespace Private_AssetBrowser;
2374 CThumbnailsInternalView* pThumbnailsView = static_cast<CThumbnailsInternalView*>(m_pThumbnailView->GetInternalView());
2375 QAssetDetailsView* pDetailsView = static_cast<QAssetDetailsView*>(m_pDetailsView);
2377 const int numFolders = selectedFolders.size();
2378 if (numFolders > 1)
2380 m_pBreadcrumbs->hide();
2381 m_pMultipleFoldersLabel->show();
2383 pThumbnailsView->SetRootFolder(QString());
2384 pDetailsView->SetRootFolder(QString());
2386 else
2388 m_pBreadcrumbs->show();
2389 m_pMultipleFoldersLabel->hide();
2391 UpdateBreadcrumbsBar(CAssetFoldersModel::GetInstance()->GetPrettyPath(selectedFolders.first()));
2393 pThumbnailsView->SetRootFolder(selectedFolders.first());
2394 pDetailsView->SetRootFolder(selectedFolders.first());
2397 m_pFolderFilterModel->SetAcceptedFolders(selectedFolders);
2399 if (!m_dontPushNavHistory)
2401 if (m_navigationIndex < m_navigationHistory.count() - 1)
2402 m_navigationHistory.resize(m_navigationIndex + 1);
2404 m_navigationHistory.append(selectedFolders);
2405 m_navigationIndex++;
2408 UpdateNavigation(false);
2410 SelectionChanged();
2413 void CAssetBrowser::UpdateNavigation(bool clearHistory)
2415 if (clearHistory)
2417 m_navigationHistory.clear();
2418 m_navigationIndex = -1;
2421 m_pBackButton->setEnabled(m_navigationHistory.count() > 0 && m_navigationIndex > -1);
2422 m_pForwardButton->setEnabled(m_navigationHistory.count() && m_navigationIndex < m_navigationHistory.count() - 1);
2425 void CAssetBrowser::UpdateBreadcrumbsBar(const QString& path)
2427 m_pBreadcrumbs->Clear();
2429 int curIndex = 0;
2430 int slashIndex = -1;
2434 slashIndex = path.indexOf('/', curIndex);
2435 QString crumbText = path.mid(curIndex, slashIndex == -1 ? -1 : slashIndex - curIndex);
2436 m_pBreadcrumbs->AddBreadcrumb(crumbText, path.mid(0, slashIndex));
2437 curIndex = slashIndex + 1;
2439 while (slashIndex != -1);
2442 void CAssetBrowser::UpdateNonEmptyFolderList()
2444 m_pFilteredFolders->Update(AreIrrelevantFoldersHidden());
2447 void CAssetBrowser::OnBreadcrumbClick(const QString& text, const QVariant& data)
2449 auto index = CAssetFoldersModel::GetInstance()->FindIndexForFolder(data.toString(), CAssetFoldersModel::Roles::DisplayFolderPathRole);
2450 if (index.isValid())
2452 m_pFoldersView->SelectFolder(index);
2456 void CAssetBrowser::OnBreadcrumbsTextChanged(const QString& text)
2458 auto index = CAssetFoldersModel::GetInstance()->FindIndexForFolder(text, CAssetFoldersModel::Roles::DisplayFolderPathRole);
2459 if (index.isValid())
2461 m_pFoldersView->SelectFolder(index);
2463 else
2465 // Check if the user entered the absolute path and delete up to the asset folder
2466 // fromNativeSeparators ensures same separators are used
2467 QString assetsPaths = QDir::fromNativeSeparators(QDir::currentPath());
2468 QString breadCrumbsPath = QDir::fromNativeSeparators(text);
2469 if (breadCrumbsPath.contains(assetsPaths))
2471 breadCrumbsPath.remove(assetsPaths);
2473 auto index = CAssetFoldersModel::GetInstance()->FindIndexForFolder(breadCrumbsPath, CAssetFoldersModel::Roles::DisplayFolderPathRole);
2474 if (index.isValid())
2476 m_pFoldersView->SelectFolder(index);
2481 bool CAssetBrowser::OnFind()
2483 m_pFilterPanel->GetSearchBox()->setFocus();
2484 return true;
2487 bool CAssetBrowser::ValidatePath(const QString path)
2489 auto index = CAssetFoldersModel::GetInstance()->FindIndexForFolder(path, CAssetFoldersModel::Roles::DisplayFolderPathRole);
2490 if (index.isValid())
2492 return true;
2494 else
2496 // Check if the user entered the absolute path and delete up to the asset folder
2497 // fromNativeSeparators ensures same seperators are used
2498 QString assetsPaths = QDir::fromNativeSeparators(QDir::currentPath());
2499 QString breadCrumbsPath = QDir::fromNativeSeparators(path);
2500 if (breadCrumbsPath.contains(assetsPaths))
2502 breadCrumbsPath.remove(assetsPaths);
2504 auto index = CAssetFoldersModel::GetInstance()->FindIndexForFolder(breadCrumbsPath, CAssetFoldersModel::Roles::DisplayFolderPathRole);
2505 if (index.isValid())
2507 return true;
2510 return false;
2513 bool CAssetBrowser::OnDelete()
2515 std::vector<CAsset*> assets;
2516 std::vector<string> folders;
2517 GetSelection(assets, folders);
2518 bool isHandled = false;
2519 if (!assets.empty())
2521 Delete(assets);
2522 isHandled = true;
2524 else
2526 if (folders.empty())
2528 folders = GetSelectedFolders();
2531 for (const string& folder : folders)
2533 const QString& qFolder = QtUtil::ToQString(folder);
2534 if (CAssetFoldersModel::GetInstance()->IsEmptyFolder(qFolder))
2536 CAssetFoldersModel::GetInstance()->DeleteFolder(qFolder);
2537 isHandled = true;
2541 return isHandled;
2544 QAttributeFilterProxyModel* CAssetBrowser::GetAttributeFilterProxyModel()
2546 return m_pAttributeFilterProxyModel.get();
2549 QItemSelectionModel* CAssetBrowser::GetItemSelectionModel()
2551 return m_pSelection;
2554 const QItemSelectionModel* CAssetBrowser::GetItemSelectionModel() const
2556 return m_pSelection;
2559 QAdvancedTreeView* CAssetBrowser::GetDetailsView()
2561 return m_pDetailsView;
2564 QThumbnailsView* CAssetBrowser::GetThumbnailsView()
2566 return m_pThumbnailView;
2569 void CAssetBrowser::ScrollToSelected()
2571 const QModelIndex index = GetItemSelectionModel()->currentIndex();
2572 if (index.isValid())
2574 m_pDetailsView->scrollTo(index, QAbstractItemView::EnsureVisible);
2575 m_pThumbnailView->ScrollToRow(index, QAbstractItemView::EnsureVisible);