You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1795 lines
56 KiB
C++
1795 lines
56 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
// Original file Copyright Crytek GMBH or its affiliates, used under license.
|
|
|
|
#include "EditorDefs.h"
|
|
|
|
#include "MaterialBrowser.h"
|
|
|
|
// Qt
|
|
#include <QMessageBox>
|
|
|
|
// AzQtComponents
|
|
#include <AzQtComponents/Utilities/DesktopUtilities.h>
|
|
|
|
// AzToolsFramework
|
|
#include <AzToolsFramework/AssetBrowser/AssetBrowserModel.h>
|
|
|
|
// Editor
|
|
#include "MaterialManager.h"
|
|
#include "Clipboard.h"
|
|
#include "MaterialImageListCtrl.h"
|
|
#include "StringDlg.h"
|
|
|
|
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
|
|
#include <Material/ui_MaterialBrowser.h>
|
|
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
|
|
|
|
|
|
enum
|
|
{
|
|
MENU_UNDEFINED = CMaterialImageListCtrl::MaterialBrowserWidgetActionsStart,
|
|
MENU_CUT,
|
|
MENU_COPY,
|
|
MENU_COPY_NAME,
|
|
MENU_PASTE,
|
|
MENU_EXPLORE,
|
|
MENU_DUPLICATE,
|
|
MENU_EXTRACT,
|
|
MENU_RENAME,
|
|
MENU_DELETE,
|
|
MENU_RESET,
|
|
MENU_ASSIGNTOSELECTION,
|
|
MENU_SELECTASSIGNEDOBJECTS,
|
|
MENU_NUM_SUBMTL,
|
|
MENU_ADDNEW,
|
|
MENU_ADDNEW_MULTI,
|
|
MENU_CONVERT_TO_MULTI,
|
|
MENU_SUBMTL_MAKE,
|
|
MENU_SUBMTL_CLEAR,
|
|
MENU_SAVE_TO_FILE,
|
|
MENU_SAVE_TO_FILE_MULTI,
|
|
MENU_MERGE,
|
|
|
|
MENU_SCM_ADD,
|
|
MENU_SCM_CHECK_OUT,
|
|
MENU_SCM_UNDO_CHECK_OUT,
|
|
MENU_SCM_GET_LATEST,
|
|
MENU_SCM_GET_LATEST_TEXTURES,
|
|
};
|
|
|
|
static QAction* CreateTreeViewAction(const char* text, const QKeySequence& shortcut, QWidget* shortcutContext, MaterialBrowserWidget* widget, void (MaterialBrowserWidget::*slot)())
|
|
{
|
|
QAction* action = new QAction(text, shortcutContext);
|
|
action->setShortcut(shortcut);
|
|
QObject::connect(action, &QAction::triggered, widget, slot);
|
|
widget->addAction(action);
|
|
return action;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
MaterialBrowserWidget::MaterialBrowserWidget(QWidget* parent)
|
|
: QWidget(parent)
|
|
, m_ui(new Ui::MaterialBrowser)
|
|
, m_filterModel(new MaterialBrowserFilterModel(this))
|
|
{
|
|
using namespace AzToolsFramework::AssetBrowser;
|
|
|
|
m_ui->setupUi(this);
|
|
|
|
// create some permanent (for the life of this widget) actions for shortcut handling
|
|
m_cutAction = CreateTreeViewAction("Cut", QKeySequence::Cut, m_ui->treeView, this, &MaterialBrowserWidget::OnCut);
|
|
m_copyAction = CreateTreeViewAction("Copy", QKeySequence::Copy, m_ui->treeView, this, &MaterialBrowserWidget::OnCopy);
|
|
m_pasteAction = CreateTreeViewAction("Paste", QKeySequence::Paste, m_ui->treeView, this, &MaterialBrowserWidget::OnPaste);
|
|
m_duplicateAction = CreateTreeViewAction("Duplicate", QKeySequence(Qt::CTRL + Qt::Key_D), m_ui->treeView, this, &MaterialBrowserWidget::OnDuplicate);
|
|
m_deleteAction = CreateTreeViewAction("Delete", QKeySequence::Delete, m_ui->treeView, this, &MaterialBrowserWidget::DeleteItem);
|
|
m_renameItemAction = CreateTreeViewAction("Rename", Qt::Key_F2, m_ui->treeView, this, &MaterialBrowserWidget::OnRenameItem);
|
|
m_addNewMaterialAction = CreateTreeViewAction("Add New Material", Qt::Key_Insert, m_ui->treeView, this, &MaterialBrowserWidget::OnAddNewMaterial);
|
|
|
|
MaterialBrowserWidgetBus::Handler::BusConnect();
|
|
|
|
// Get the asset browser model
|
|
AssetBrowserComponentRequestBus::BroadcastResult(m_assetBrowserModel, &AssetBrowserComponentRequests::GetAssetBrowserModel);
|
|
AZ_Assert(m_assetBrowserModel, "Failed to get filebrowser model");
|
|
|
|
// Set up the filter model
|
|
m_filterModel->setSourceModel(m_assetBrowserModel);
|
|
m_ui->treeView->setModel(m_filterModel.data());
|
|
m_ui->treeView->SetThumbnailContext("MaterialBrowser");
|
|
m_ui->treeView->SetShowSourceControlIcons(true);
|
|
|
|
m_ui->m_searchWidget->Setup(true, false);
|
|
m_filterModel->SetSearchFilter(m_ui->m_searchWidget);
|
|
connect(m_ui->m_searchWidget->GetFilter().data(), &AssetBrowserEntryFilter::updatedSignal, m_filterModel.data(), &MaterialBrowserFilterModel::SearchFilterUpdated);
|
|
|
|
// Call LoadState to initialize the AssetBrowserTreeView's QTreeViewStateSaver
|
|
// This must be done BEFORE StartRecordUpdateJobs(). A race condition from the update jobs was causing a 5-10% crash/hang when opening the Material Editor.
|
|
m_ui->treeView->SetName("MaterialBrowserTreeView");
|
|
|
|
// Override the AssetBrowserTreeView's custom context menu
|
|
disconnect(m_ui->treeView, &QWidget::customContextMenuRequested, 0, 0);
|
|
connect(m_ui->treeView, &QWidget::customContextMenuRequested, this, [=](const QPoint& point)
|
|
{
|
|
CMaterialBrowserRecord record;
|
|
bool found = TryGetSelectedRecord(record);
|
|
ShowContextMenu(record, point);
|
|
});
|
|
|
|
connect(m_ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MaterialBrowserWidget::OnSelectionChanged);
|
|
//Wait for the signal emitted on record update jobs finished, then we can restore the selection for the previous selected item
|
|
connect(this, SIGNAL(refreshSelection()), this, SLOT(OnRefreshSelection()));
|
|
|
|
connect(this, SIGNAL(materialAdded()), this, SLOT(OnMaterialAdded()));
|
|
|
|
m_bIgnoreSelectionChange = false;
|
|
m_bItemsValid = true;
|
|
|
|
m_pMatMan = GetIEditor()->GetMaterialManager();
|
|
m_pMatMan->AddListener(this);
|
|
m_pListener = NULL;
|
|
|
|
m_viewType = VIEW_LEVEL;
|
|
m_pMaterialImageListCtrl = NULL;
|
|
m_pLastActiveMultiMaterial = 0;
|
|
|
|
m_bNeedReload = false;
|
|
|
|
m_bHighlightMaterial = false;
|
|
m_timeOfHighlight = 0;
|
|
|
|
m_bShowOnlyCheckedOut = false;
|
|
|
|
GetIEditor()->RegisterNotifyListener(this);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
MaterialBrowserWidget::~MaterialBrowserWidget()
|
|
{
|
|
m_filterModel->CancelRecordUpdateJobs();
|
|
m_filterModel->deleteLater();
|
|
m_ui->treeView->SaveState();
|
|
GetIEditor()->UnregisterNotifyListener(this);
|
|
|
|
m_pMaterialImageListCtrl = NULL;
|
|
m_pMatMan->RemoveListener(this);
|
|
ClearItems();
|
|
|
|
if (m_bHighlightMaterial)
|
|
{
|
|
m_pMatMan->SetHighlightedMaterial(0);
|
|
}
|
|
|
|
MaterialBrowserWidgetBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::ClearItems()
|
|
{
|
|
m_bIgnoreSelectionChange = true;
|
|
|
|
if (m_pMaterialImageListCtrl)
|
|
{
|
|
QMaterialImageListModel* materialModel =
|
|
qobject_cast<QMaterialImageListModel*>(m_pMaterialImageListCtrl->model());
|
|
Q_ASSERT(materialModel);
|
|
materialModel->DeleteAllItems();
|
|
}
|
|
|
|
m_pLastActiveMultiMaterial = nullptr;
|
|
m_delayedSelection = nullptr;
|
|
|
|
m_bIgnoreSelectionChange = false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::TryLoadRecordMaterial(CMaterialBrowserRecord& record)
|
|
{
|
|
// If material already loaded ignore.
|
|
if (record.m_material)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_bIgnoreSelectionChange = true;
|
|
// Try to load material for it.
|
|
record.m_material = m_pMatMan->LoadMaterial(record.GetRelativeFilePath().c_str(), false);
|
|
|
|
m_filterModel->SetRecord(record);
|
|
m_bIgnoreSelectionChange = false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::TickRefreshMaterials()
|
|
{
|
|
if (m_bHighlightMaterial)
|
|
{
|
|
uint32 t = GetTickCount();
|
|
if ((t - m_timeOfHighlight) > 300)
|
|
{
|
|
m_bHighlightMaterial = false;
|
|
m_pMatMan->SetHighlightedMaterial(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
|
|
{
|
|
switch (event)
|
|
{
|
|
case eNotify_OnIdleUpdate:
|
|
{
|
|
TickRefreshMaterials();
|
|
}
|
|
break;
|
|
case eNotify_OnBeginLoad:
|
|
{
|
|
//Need to make sure the selection is cleared before clearing the record map
|
|
SetSelectedItem(nullptr, nullptr, true);
|
|
m_filterModel->ClearRecordMap();
|
|
}
|
|
break;
|
|
case eNotify_OnCloseScene:
|
|
{
|
|
m_filterModel->ShowOnlyLevelMaterials(false, true);
|
|
ClearItems();
|
|
m_ui->treeView->SaveState();
|
|
//Need to make sure the selection is cleared before clearing the record map
|
|
SetSelectedItem(nullptr, nullptr, true);
|
|
m_filterModel->ClearRecordMap();
|
|
}
|
|
break;
|
|
case eNotify_OnEndNewScene:
|
|
case eNotify_OnEndSceneOpen:
|
|
{
|
|
m_filterModel->ShowOnlyLevelMaterials(false, true);
|
|
m_filterModel->StartRecordUpdateJobs();
|
|
if (m_ui->treeView->IsTreeViewSavingReady())
|
|
{
|
|
m_ui->treeView->ApplyTreeViewSnapshot();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::SetSelectedItem(_smart_ptr<CMaterial> material, const TMaterialBrowserRecords* pMarkedRecords, bool selectInTreeView)
|
|
{
|
|
if (m_bIgnoreSelectionChange)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_bIgnoreSelectionChange = true;
|
|
if (pMarkedRecords)
|
|
{
|
|
m_markedRecords = *pMarkedRecords;
|
|
}
|
|
else
|
|
{
|
|
m_markedRecords.clear();
|
|
}
|
|
|
|
m_pMatMan->SetCurrentFolder("");
|
|
|
|
_smart_ptr<CMaterial> selectedMaterial = material;
|
|
if (material && material->IsPureChild())
|
|
{
|
|
selectedMaterial = material->GetParent();
|
|
}
|
|
|
|
// Clear the delayed selection whenever a new material is selected
|
|
m_delayedSelection = nullptr;
|
|
|
|
// In some cases, such as when SetSelectedItem is called from the material picker or the rollup bar,
|
|
// the material has not yet been selected in the tree-view, so that item must be selected here
|
|
bool validSelection = false;
|
|
if (selectInTreeView)
|
|
{
|
|
if (!selectedMaterial)
|
|
{
|
|
// Clear the current selection so that the upcoming call to RefreshSelected()
|
|
// doesn't try to refresh the previously selected material which may be invalid
|
|
m_ui->treeView->clearSelection();
|
|
m_ui->treeView->setCurrentIndex(QModelIndex());
|
|
}
|
|
else if (m_markedRecords.size() > 0)
|
|
{
|
|
QItemSelection selection;
|
|
for (CMaterialBrowserRecord record : m_markedRecords)
|
|
{
|
|
QModelIndex currentIndex = m_filterModel->GetIndexFromMaterial(record.m_material);
|
|
if (currentIndex.isValid())
|
|
{
|
|
selection.push_back(QItemSelectionRange(currentIndex));
|
|
validSelection = true;
|
|
}
|
|
}
|
|
m_ui->treeView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect);
|
|
}
|
|
else
|
|
{
|
|
QModelIndex currentIndex = m_filterModel->GetIndexFromMaterial(selectedMaterial);
|
|
if (currentIndex.isValid())
|
|
{
|
|
m_ui->treeView->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::ClearAndSelect);
|
|
validSelection = true;
|
|
|
|
// Now that the parent material is selected in the browser,
|
|
// select the appropriate sub-material in the material preview window
|
|
if (selectedMaterial == material->GetParent())
|
|
{
|
|
for (int i = 0; i < selectedMaterial->GetSubMaterialCount(); ++i)
|
|
{
|
|
if (material == selectedMaterial->GetSubMaterial(i))
|
|
{
|
|
m_selectedSubMaterialIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Hold on to a pointer to this material, listen for the MaterialFinishedProcessing event,
|
|
// and attempt to re-select this material if it finishes processing in the background
|
|
m_delayedSelection = selectedMaterial;
|
|
}
|
|
}
|
|
}
|
|
|
|
RefreshSelected();
|
|
|
|
if (!selectedMaterial)
|
|
{
|
|
QModelIndex current = m_ui->treeView->currentIndex();
|
|
QString path;
|
|
while (current.isValid())
|
|
{
|
|
path = '/' + current.data().toString() + path;
|
|
current = current.parent();
|
|
}
|
|
m_pMatMan->SetCurrentFolder((Path::GetEditingGameDataFolder() + path.toUtf8().data()).c_str());
|
|
}
|
|
else
|
|
{
|
|
if (selectedMaterial->IsMultiSubMaterial() && m_selectedSubMaterialIndex >= 0)
|
|
{
|
|
selectedMaterial = selectedMaterial->GetSubMaterial(m_selectedSubMaterialIndex);
|
|
}
|
|
}
|
|
|
|
if (m_pListener)
|
|
{
|
|
// Don't call the listener event if we attempted to select an item in the tree view and failed
|
|
// to prevent the material parameter editor from switching to a new material if it wasn't actually selected
|
|
if (!selectInTreeView || validSelection)
|
|
{
|
|
m_pListener->OnBrowserSelectItem(selectedMaterial, false);
|
|
}
|
|
//Update the selected item if no material is selected in tree view
|
|
else if (!selectedMaterial && selectInTreeView)
|
|
{
|
|
m_pListener->OnBrowserSelectItem(nullptr, false);
|
|
}
|
|
}
|
|
|
|
m_timeOfHighlight = GetTickCount();
|
|
m_pMatMan->SetHighlightedMaterial(selectedMaterial);
|
|
if (selectedMaterial)
|
|
{
|
|
m_bHighlightMaterial = true;
|
|
}
|
|
|
|
std::vector<_smart_ptr<CMaterial> > markedMaterials;
|
|
if (pMarkedRecords)
|
|
{
|
|
for (size_t i = 0; i < pMarkedRecords->size(); ++i)
|
|
{
|
|
markedMaterials.push_back((*pMarkedRecords)[i].m_material);
|
|
}
|
|
}
|
|
m_pMatMan->SetMarkedMaterials(markedMaterials);
|
|
|
|
m_bIgnoreSelectionChange = false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::SelectItem(IDataBaseItem* pItem, [[maybe_unused]] IDataBaseItem* pParentItem)
|
|
{
|
|
if (m_bIgnoreSelectionChange)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!pItem)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bFound = false;
|
|
|
|
_smart_ptr<CMaterial> material = static_cast<CMaterial*>(pItem);
|
|
SetSelectedItem(material, 0, true);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnDuplicate()
|
|
{
|
|
GetIEditor()->GetMaterialManager()->Command_Merge();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnCut()
|
|
{
|
|
CMaterialBrowserRecord record;
|
|
bool found = TryGetSelectedRecord(record);
|
|
if (found)
|
|
{
|
|
OnCopy();
|
|
DeleteItem(record);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnCopyName()
|
|
{
|
|
_smart_ptr<CMaterial> pMtl = GetCurrentMaterial();
|
|
if (pMtl)
|
|
{
|
|
CClipboard clipboard(this);
|
|
clipboard.PutString(pMtl->GetName(), "Material Name");
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::ShowOnlyLevelMaterials(bool levelOnly)
|
|
{
|
|
m_filterModel->ShowOnlyLevelMaterials(levelOnly);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnCopy()
|
|
{
|
|
_smart_ptr<CMaterial> pMtl = GetCurrentMaterial();
|
|
if (pMtl)
|
|
{
|
|
CClipboard clipboard(this);
|
|
XmlNodeRef node = XmlHelpers::CreateXmlNode("Material");
|
|
node->setAttr("Name", pMtl->GetName().toUtf8().data());
|
|
CBaseLibraryItem::SerializeContext ctx(node, false);
|
|
ctx.bCopyPaste = true;
|
|
pMtl->Serialize(ctx);
|
|
clipboard.Put(node);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool MaterialBrowserWidget::CanPaste() const
|
|
{
|
|
CClipboard clipboard(nullptr);
|
|
if (clipboard.IsEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
XmlNodeRef node = clipboard.Get();
|
|
if (!node)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (strcmp(node->getTag(), "Material") == 0)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnPaste()
|
|
{
|
|
CClipboard clipboard(this);
|
|
if (clipboard.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
XmlNodeRef node = clipboard.Get();
|
|
if (!node)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (strcmp(node->getTag(), "Material") == 0)
|
|
{
|
|
if (!m_pMatMan->GetCurrentMaterial())
|
|
{
|
|
GetIEditor()->GetMaterialManager()->Command_Create();
|
|
}
|
|
|
|
_smart_ptr<CMaterial> pMtl = m_pMatMan->GetCurrentMaterial();
|
|
if (pMtl)
|
|
{
|
|
// This is material node.
|
|
CBaseLibraryItem::SerializeContext serCtx(node, true);
|
|
serCtx.bCopyPaste = true;
|
|
serCtx.bUniqName = true;
|
|
pMtl->Serialize(serCtx);
|
|
|
|
SelectItem(pMtl, NULL);
|
|
pMtl->Save();
|
|
pMtl->Reload();
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnAddNewMaterial()
|
|
{
|
|
GetIEditor()->GetMaterialManager()->Command_Create();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnAddNewMultiMaterial()
|
|
{
|
|
GetIEditor()->GetMaterialManager()->Command_CreateMulti();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnMergeMaterials()
|
|
{
|
|
GetIEditor()->GetMaterialManager()->Command_Merge();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnConvertToMulti()
|
|
{
|
|
GetIEditor()->GetMaterialManager()->Command_ConvertToMulti();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::DeleteItem()
|
|
{
|
|
CMaterialBrowserRecord record;
|
|
bool found = TryGetSelectedRecord(record);
|
|
if (found)
|
|
{
|
|
DeleteItem(record);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnResetItem()
|
|
{
|
|
if (QMessageBox::question(this, tr("Reset Material"), tr("Reset Material to default?")) == QMessageBox::Yes)
|
|
{
|
|
_smart_ptr<CMaterial> pMtl = GetCurrentMaterial();
|
|
int index;
|
|
|
|
pMtl->GetSubMaterialCount() > 0 ? index = pMtl->GetSubMaterialCount() : index = 1;
|
|
|
|
if (pMtl)
|
|
{
|
|
for (int i = 0; i < index; i++)
|
|
{
|
|
pMtl->GetSubMaterialCount() > 0 ? pMtl->GetSubMaterial(i)->Reload() : pMtl->Reload();
|
|
TickRefreshMaterials();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::DeleteItem(const CMaterialBrowserRecord& record)
|
|
{
|
|
_smart_ptr<CMaterial> pMtl = record.m_material;
|
|
if (pMtl)
|
|
{
|
|
if (m_selectedSubMaterialIndex >= 0)
|
|
{
|
|
pMtl = pMtl->GetSubMaterial(m_selectedSubMaterialIndex);
|
|
OnClearSubMtlSlot(pMtl);
|
|
}
|
|
else
|
|
{
|
|
GetIEditor()->GetMaterialManager()->Command_Delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnRenameItem()
|
|
{
|
|
_smart_ptr<CMaterial> pMtl = GetCurrentMaterial();
|
|
if (!pMtl)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (pMtl->IsPureChild())
|
|
{
|
|
StringDlg dlg(tr("Enter New Sub-Material Name"), this);
|
|
dlg.SetString(pMtl->GetName());
|
|
dlg.SetCheckCallback([this](QString name) -> bool
|
|
{
|
|
static const int MAX_REASONABLE_MATERIAL_NAME = 128;
|
|
if (name.length() >= MAX_REASONABLE_MATERIAL_NAME)
|
|
{
|
|
QMessageBox::warning(this, tr("Name too long"), tr("Please enter a name less than %1 characters").arg(MAX_REASONABLE_MATERIAL_NAME));
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
if (dlg.exec() == QDialog::Accepted)
|
|
{
|
|
pMtl->SetName(dlg.GetString());
|
|
pMtl->Save();
|
|
pMtl->Reload();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((pMtl->GetFileAttributes() & SCC_FILE_ATTRIBUTE_MANAGED) &&
|
|
!(pMtl->GetFileAttributes() & SCC_FILE_ATTRIBUTE_CHECKEDOUT))
|
|
{
|
|
if (QMessageBox::Cancel == QMessageBox::information(this, tr("Confirm"), tr("Only checked-out files can be renamed. Check out and mark for delete before rename it?"), QMessageBox::Ok | QMessageBox::Cancel))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
QFileInfo info(pMtl->GetFilename());
|
|
QString filename = info.fileName();
|
|
if (!CFileUtil::SelectSaveFile("Material Files (*.mtl)", "mtl", info.path(), filename))
|
|
{
|
|
return;
|
|
}
|
|
|
|
QString itemName = m_pMatMan->FilenameToMaterial(Path::GetRelativePath(filename));
|
|
if (itemName.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_pMatMan->FindItemByName(itemName))
|
|
{
|
|
Warning("Material with name %s already exist", itemName.toUtf8().data());
|
|
return;
|
|
}
|
|
|
|
if (pMtl->GetFileAttributes() & SCC_FILE_ATTRIBUTE_MANAGED)
|
|
{
|
|
if (pMtl->GetFileAttributes() & SCC_FILE_ATTRIBUTE_CHECKEDOUT)
|
|
{
|
|
if (QMessageBox::Cancel == QMessageBox::information(this, tr("Confirm"), tr("The original file will be marked for delete and the new named file will be marked for integration."), QMessageBox::Ok | QMessageBox::Cancel))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CFileUtil::CheckoutFile(pMtl->GetFilename().toUtf8().data(), this);
|
|
}
|
|
|
|
if (!CFileUtil::RenameFile(pMtl->GetFilename().toUtf8().data(), filename.toUtf8().data()))
|
|
{
|
|
QMessageBox::critical(this, tr("Error"), tr("Could not rename file in Source Control."));
|
|
}
|
|
}
|
|
|
|
|
|
// Delete file on disk.
|
|
if (!pMtl->GetFilename().isEmpty())
|
|
{
|
|
QFile::remove(pMtl->GetFilename());
|
|
}
|
|
pMtl->SetName(itemName);
|
|
pMtl->Save();
|
|
SetSelectedItem(pMtl, 0, true);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnSetSubMtlCount(const CMaterialBrowserRecord& record)
|
|
{
|
|
_smart_ptr<CMaterial> pMtl = record.m_material;
|
|
if (!pMtl)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!pMtl->IsMultiSubMaterial())
|
|
{
|
|
return;
|
|
}
|
|
|
|
int num = pMtl->GetSubMaterialCount();
|
|
bool ok = false;
|
|
num = QInputDialog::getInt(this, tr("Number of Sub Materials"), QStringLiteral(""), num, 0, MAX_SUB_MATERIALS, 1, &ok);
|
|
if (ok)
|
|
{
|
|
if (num != pMtl->GetSubMaterialCount())
|
|
{
|
|
if (m_selectedSubMaterialIndex >= num)
|
|
{
|
|
m_selectedSubMaterialIndex = num - 1;
|
|
}
|
|
|
|
CUndo undo("Set SubMtl Count");
|
|
pMtl->SetSubMaterialCount(num);
|
|
|
|
for (int i = 0; i < num; i++)
|
|
{
|
|
if (pMtl->GetSubMaterial(i) == 0)
|
|
{
|
|
// Allocate pure childs for all empty slots.
|
|
QString name;
|
|
name = QStringLiteral("SubMtl%1").arg(i + 1);
|
|
_smart_ptr<CMaterial> pChild = new CMaterial(name, MTL_FLAG_PURE_CHILD);
|
|
pMtl->SetSubMaterial(i, pChild);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::DoSourceControlOp(CMaterialBrowserRecord& record, ESourceControlOp scmOp)
|
|
{
|
|
if (!GetIEditor()->IsSourceControlAvailable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
_smart_ptr<CMaterial> pMtl = record.m_material;
|
|
|
|
if (pMtl && pMtl->IsPureChild())
|
|
{
|
|
pMtl = pMtl->GetParent();
|
|
}
|
|
|
|
if (scmOp == ESCM_IMPORT) // don't save unless you're doing an operation which writes.
|
|
{
|
|
if (pMtl && pMtl->IsModified())
|
|
{
|
|
pMtl->Save();
|
|
}
|
|
}
|
|
|
|
QString path = pMtl ? pMtl->GetFilename() : QString();
|
|
|
|
if (path.isEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bRes = true;
|
|
switch (scmOp)
|
|
{
|
|
case ESCM_IMPORT:
|
|
if (pMtl)
|
|
{
|
|
QStringList filenames;
|
|
int nTextures = pMtl->GetTextureFilenames(filenames);
|
|
for (int i = 0; i < nTextures; ++i)
|
|
{
|
|
CFileUtil::CheckoutFile(filenames[i].toUtf8().data(), this);
|
|
}
|
|
|
|
bRes = CFileUtil::CheckoutFile(path.toUtf8().data(), this);
|
|
}
|
|
break;
|
|
case ESCM_CHECKOUT:
|
|
{
|
|
if (pMtl && (pMtl->GetFileAttributes() & SCC_FILE_ATTRIBUTE_BYANOTHER))
|
|
{
|
|
AZStd::string otherUser("another user");
|
|
AzToolsFramework::SourceControlFileInfo fileInfo;
|
|
if (CFileUtil::GetFileInfoFromSourceControl(pMtl->GetFilename().toUtf8().data(), fileInfo, this))
|
|
{
|
|
// Sanity check the source control api reports the file is checked out by another
|
|
AZ_Assert(fileInfo.HasFlag(AzToolsFramework::SCF_OtherOpen), "File attributes reporting incorrectly");
|
|
otherUser = fileInfo.m_StatusUser;
|
|
}
|
|
|
|
if (QMessageBox::question(this, QString(), tr("This file is checked out by %1. Try to continue?").arg(otherUser.c_str())) != QMessageBox::Yes)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
bRes = CFileUtil::GetLatestFromSourceControl(path.toUtf8().data(), this);
|
|
if (bRes)
|
|
{
|
|
bRes = CFileUtil::CheckoutFile(path.toUtf8().data(), this);
|
|
}
|
|
}
|
|
break;
|
|
case ESCM_UNDO_CHECKOUT:
|
|
bRes = CFileUtil::RevertFile(path.toUtf8().data(), this);
|
|
break;
|
|
case ESCM_GETLATEST:
|
|
bRes = CFileUtil::GetLatestFromSourceControl(path.toUtf8().data(), this);
|
|
break;
|
|
case ESCM_GETLATESTTEXTURES:
|
|
if (pMtl)
|
|
{
|
|
QString message;
|
|
QStringList filenames;
|
|
int nTextures = pMtl->GetTextureFilenames(filenames);
|
|
for (int i = 0; i < nTextures; ++i)
|
|
{
|
|
bRes = CFileUtil::GetLatestFromSourceControl(filenames[i].toUtf8().data(), this);
|
|
message += Path::GetRelativePath(filenames[i], true) + (bRes ? " [OK]" : " [Fail]") + "\n";
|
|
}
|
|
QMessageBox::information(this, QString(), message.isEmpty() ? tr("No files are affected") : message);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!bRes)
|
|
{
|
|
QMessageBox::critical(this, tr("Error"), tr("Source Control Operation Failed.\r\nCheck if Source Control Provider correctly setup and working directory is correct."));
|
|
return;
|
|
}
|
|
|
|
// force the cache to be rebuilt next time we repaint
|
|
record.InitializeSourceControlAttributes();
|
|
m_filterModel->SetRecord(record);
|
|
|
|
if (pMtl)
|
|
{
|
|
pMtl->Reload();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnMakeSubMtlSlot(const CMaterialBrowserRecord& record)
|
|
{
|
|
if (m_selectedSubMaterialIndex >= 0)
|
|
{
|
|
_smart_ptr<CMaterial> parentMaterial = record.m_material;
|
|
if (parentMaterial && parentMaterial->IsMultiSubMaterial())
|
|
{
|
|
const QString str = tr("Making new material will override material currently assigned to the slot %1 of %2\r\nMake new sub material?")
|
|
.arg(m_selectedSubMaterialIndex).arg(parentMaterial->GetName());
|
|
if (QMessageBox::question(this, tr("Confirm Override"), str) == QMessageBox::Yes)
|
|
{
|
|
QString name = tr("SubMtl%1")
|
|
.arg(m_selectedSubMaterialIndex + 1);
|
|
_smart_ptr<CMaterial> pMtl = new CMaterial(name, MTL_FLAG_PURE_CHILD);
|
|
parentMaterial->SetSubMaterial(m_selectedSubMaterialIndex, pMtl);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnClearSubMtlSlot(_smart_ptr<CMaterial> subMaterial)
|
|
{
|
|
if (subMaterial && m_selectedSubMaterialIndex >= 0)
|
|
{
|
|
_smart_ptr<CMaterial> parentMaterial = subMaterial->GetParent();
|
|
if (parentMaterial && parentMaterial->IsMultiSubMaterial())
|
|
{
|
|
const QString str = tr("Clear Sub-Material Slot %1 of %2?").arg(m_selectedSubMaterialIndex).arg(parentMaterial->GetName());
|
|
if (QMessageBox::question(this, tr("Clear Sub-Material"), str) == QMessageBox::Yes)
|
|
{
|
|
CUndo undo("Material Change");
|
|
SetSubMaterial(parentMaterial, m_selectedSubMaterialIndex, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::SetSubMaterial(_smart_ptr<CMaterial> parentMaterial, int slot, _smart_ptr<CMaterial> subMaterial)
|
|
{
|
|
if (parentMaterial && parentMaterial->IsMultiSubMaterial())
|
|
{
|
|
// If the last sub-material is being removed, select the 2nd to last sub-material
|
|
if (!subMaterial && slot == parentMaterial->GetSubMaterialCount() - 1)
|
|
{
|
|
m_selectedSubMaterialIndex = slot - 1;
|
|
}
|
|
parentMaterial->SetSubMaterial(slot, subMaterial);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnDataBaseItemEvent(IDataBaseItem* pItem, EDataBaseItemEvent event)
|
|
{
|
|
if (m_bIgnoreSelectionChange)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!pItem)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
switch (event)
|
|
{
|
|
case EDB_ITEM_EVENT_ADD:
|
|
break;
|
|
case EDB_ITEM_EVENT_DELETE:
|
|
{
|
|
if (pItem)
|
|
{
|
|
//If the deleted item is selected, remove selection
|
|
CMaterial* pMtl = static_cast<CMaterial*>(pItem);
|
|
CMaterial* selectedMaterial = GetCurrentMaterial();
|
|
if (selectedMaterial && selectedMaterial->IsPureChild())
|
|
{
|
|
selectedMaterial = selectedMaterial->GetParent();
|
|
}
|
|
if (pMtl == selectedMaterial)
|
|
{
|
|
SetSelectedItem(nullptr, nullptr, true);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case EDB_ITEM_EVENT_CHANGED:
|
|
{
|
|
CMaterial* pMtl = static_cast<CMaterial*>(pItem);
|
|
CMaterial* selectedMaterial = GetCurrentMaterial();
|
|
if (selectedMaterial && selectedMaterial->IsPureChild())
|
|
{
|
|
selectedMaterial = selectedMaterial->GetParent();
|
|
}
|
|
// If this is a sub material, refresh parent
|
|
if (pMtl->IsPureChild())
|
|
{
|
|
pMtl = pMtl->GetParent();
|
|
}
|
|
|
|
if (pMtl == selectedMaterial)
|
|
{
|
|
if (pMtl->IsMultiSubMaterial())
|
|
{
|
|
m_pLastActiveMultiMaterial = NULL;
|
|
}
|
|
RefreshSelected();
|
|
}
|
|
m_bItemsValid = false;
|
|
}
|
|
break;
|
|
case EDB_ITEM_EVENT_SELECTED:
|
|
{
|
|
SelectItem(pItem, NULL);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::SetImageListCtrl(CMaterialImageListCtrl* pCtrl)
|
|
{
|
|
m_pMaterialImageListCtrl = pCtrl;
|
|
if (m_pMaterialImageListCtrl)
|
|
{
|
|
connect(m_pMaterialImageListCtrl->selectionModel(), &QItemSelectionModel::currentChanged,
|
|
this, &MaterialBrowserWidget::OnSubMaterialSelectedInPreviewPane);
|
|
|
|
connect(m_pMaterialImageListCtrl, &QAbstractItemView::clicked,
|
|
this, &MaterialBrowserWidget::OnSubMaterialSelectedInPreviewPane);
|
|
|
|
m_pMaterialImageListCtrl->SetMaterialBrowserWidget(this);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnSaveToFile(bool bMulti)
|
|
{
|
|
_smart_ptr<CMaterial> pCurrentMaterial = GetCurrentMaterial();
|
|
if (pCurrentMaterial)
|
|
{
|
|
QString startPath = GetIEditor()->GetSearchPath(EDITOR_PATH_MATERIALS);
|
|
QString filename;
|
|
if (!CFileUtil::SelectSaveFile("Material Files (*.mtl)", "mtl", startPath, filename))
|
|
{
|
|
return;
|
|
}
|
|
QFileInfo info(filename);
|
|
QString itemName = info.baseName();
|
|
itemName = Path::MakeGamePath(itemName);
|
|
|
|
if (m_pMatMan->FindItemByName(itemName))
|
|
{
|
|
Warning("Material with name %s already exist", itemName.toUtf8().data());
|
|
return;
|
|
}
|
|
int flags = pCurrentMaterial->GetFlags();
|
|
if (bMulti)
|
|
{
|
|
flags |= MTL_FLAG_MULTI_SUBMTL;
|
|
}
|
|
|
|
pCurrentMaterial->SetFlags(flags);
|
|
|
|
if (pCurrentMaterial->IsDummy())
|
|
{
|
|
pCurrentMaterial->ClearMatInfo();
|
|
pCurrentMaterial->SetDummy(false);
|
|
}
|
|
pCurrentMaterial->SetModified(true);
|
|
pCurrentMaterial->Save();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::RefreshSelected()
|
|
{
|
|
CMaterialBrowserRecord record;
|
|
bool found = TryGetSelectedRecord(record);
|
|
_smart_ptr<CMaterial> pMtl = nullptr;
|
|
if (!found)
|
|
{
|
|
ClearImageListControlSelection();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
pMtl = record.m_material;
|
|
}
|
|
|
|
if (m_pMaterialImageListCtrl)
|
|
{
|
|
QMaterialImageListModel* materialModel =
|
|
qobject_cast<QMaterialImageListModel*>(m_pMaterialImageListCtrl->model());
|
|
Q_ASSERT(materialModel);
|
|
|
|
materialModel->InvalidateMaterial(pMtl);
|
|
if (pMtl)
|
|
{
|
|
_smart_ptr<CMaterial> pMultiMtl = nullptr;
|
|
if (pMtl->IsMultiSubMaterial())
|
|
{
|
|
// It's possible that the currently selected sub-material index is beyond the range of the current multi-material if, for example,
|
|
// the last sub-material was selected but then the source .mtl file was changed to have fewer total sub-materials
|
|
if (m_selectedSubMaterialIndex >= pMtl->GetSubMaterialCount())
|
|
{
|
|
// If that's the case, select the last sub-material
|
|
// If the sub-material count has dropped to 0, setting m_selectedSubMaterialIndex to -1 will cause the parent material to be selected
|
|
m_selectedSubMaterialIndex = pMtl->GetSubMaterialCount() - 1;
|
|
}
|
|
|
|
pMultiMtl = pMtl;
|
|
if (m_selectedSubMaterialIndex >= 0)
|
|
{
|
|
pMtl = pMtl->GetSubMaterial(m_selectedSubMaterialIndex);
|
|
}
|
|
}
|
|
|
|
if (m_pLastActiveMultiMaterial != pMultiMtl || pMultiMtl == nullptr)
|
|
{
|
|
// A new material was selected, so the previewer needs to be cleared
|
|
materialModel->DeleteAllItems();
|
|
// If the new material was not a multi-material, add it to the previewer
|
|
if (pMultiMtl == nullptr)
|
|
{
|
|
materialModel->AddMaterial(pMtl);
|
|
}
|
|
}
|
|
|
|
// If a new multi-material was selected
|
|
if (m_pLastActiveMultiMaterial != pMultiMtl && pMultiMtl != nullptr)
|
|
{
|
|
// Add all of its submaterials to the previewer
|
|
for (size_t i = 0; i < pMultiMtl->GetSubMaterialCount(); i++)
|
|
{
|
|
if (pMultiMtl->GetSubMaterial(i))
|
|
{
|
|
materialModel->AddMaterial(pMultiMtl->GetSubMaterial(i), reinterpret_cast<void*>(i));
|
|
}
|
|
}
|
|
m_pMaterialImageListCtrl->selectionModel()->clear();
|
|
|
|
// If a sub-material was selected in the material browser, select it in the previewer
|
|
QModelIndex modelIndex;
|
|
if (m_selectedSubMaterialIndex >= 0)
|
|
{
|
|
modelIndex = materialModel->index(m_selectedSubMaterialIndex, 0);
|
|
}
|
|
m_pMaterialImageListCtrl->selectionModel()->select(modelIndex, QItemSelectionModel::SelectCurrent);
|
|
}
|
|
|
|
m_pMaterialImageListCtrl->SelectMaterial(pMtl);
|
|
m_pLastActiveMultiMaterial = pMultiMtl;
|
|
}
|
|
else
|
|
{
|
|
ClearSelection(materialModel);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::ClearImageListControlSelection()
|
|
{
|
|
QMaterialImageListModel* materialModel =
|
|
qobject_cast<QMaterialImageListModel*>(m_pMaterialImageListCtrl->model());
|
|
Q_ASSERT(materialModel);
|
|
|
|
ClearSelection(materialModel);
|
|
}
|
|
|
|
void MaterialBrowserWidget::ClearSelection(QMaterialImageListModel* materialModel)
|
|
{
|
|
materialModel->DeleteAllItems();
|
|
m_pLastActiveMultiMaterial = nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
void MaterialBrowserWidget::AddContextMenuActionsMultiSelect(QMenu& menu) const
|
|
{
|
|
int numMaterialsSelected = 0;
|
|
for (int i = 0; i < m_markedRecords.size(); ++i)
|
|
{
|
|
if (m_markedRecords[i].m_material)
|
|
{
|
|
++numMaterialsSelected;
|
|
}
|
|
}
|
|
const QString itemsSelected = tr(" (%1 Materials Selected)").arg(numMaterialsSelected);
|
|
menu.addAction(itemsSelected)->setEnabled(false);
|
|
menu.addSeparator();
|
|
|
|
if (numMaterialsSelected >= 2) // ... and at least two materials
|
|
{
|
|
menu.addAction(tr("Merge"))->setData(MENU_MERGE);
|
|
}
|
|
else
|
|
{
|
|
menu.addAction(tr("Merge (Select two or more)"))->setEnabled(false);
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::AddContextMenuActionsNoSelection(QMenu& menu) const
|
|
{
|
|
QAction* action = menu.addAction(tr("Paste"));
|
|
action->setShortcut(QKeySequence::Paste);
|
|
action->setData(MENU_PASTE);
|
|
action->setEnabled(CanPaste());
|
|
|
|
menu.addSeparator();
|
|
menu.addAction(tr("Add New Material"))->setData(MENU_ADDNEW);
|
|
menu.addAction(tr("Add New Multi Material"))->setData(MENU_ADDNEW_MULTI);
|
|
if (GetIEditor()->IsSourceControlAvailable())
|
|
{
|
|
menu.addSeparator();
|
|
menu.addAction(tr("Source Control"))->setEnabled(false);
|
|
menu.addAction(tr("Check Out"))->setData(MENU_SCM_CHECK_OUT);
|
|
menu.addAction(tr("Undo Check Out"))->setData(MENU_SCM_UNDO_CHECK_OUT);
|
|
menu.addAction(tr("Get Latest Version"))->setData(MENU_SCM_GET_LATEST);
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::AddContextMenuActionsSingleSelection(QMenu& menu, _smart_ptr<CMaterial> material) const
|
|
{
|
|
if (material)
|
|
{
|
|
if (material->IsMultiSubMaterial())
|
|
{
|
|
if (m_selectedSubMaterialIndex >= 0)
|
|
{
|
|
AddContextMenuActionsSubMaterial(menu, material, material->GetSubMaterial(m_selectedSubMaterialIndex));
|
|
}
|
|
else
|
|
{
|
|
AddContextMenuActionsMultiMaterial(menu);
|
|
AddContextMenuActionsCommon(menu, material);
|
|
}
|
|
}
|
|
else if (material->IsPureChild())
|
|
{
|
|
AddContextMenuActionsSubMaterial(menu, material->GetParent(), material);
|
|
}
|
|
else
|
|
{
|
|
AddContextMenuActionsSingleMaterial(menu);
|
|
AddContextMenuActionsCommon(menu, material);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::AddContextMenuActionsSubMaterial(QMenu& menu, _smart_ptr<CMaterial> parentMaterial, _smart_ptr<CMaterial> subMaterial) const
|
|
{
|
|
bool enabled = true;
|
|
|
|
if (parentMaterial)
|
|
{
|
|
uint32 nFileAttr = parentMaterial->GetFileAttributes();
|
|
if (nFileAttr & SCC_FILE_ATTRIBUTE_READONLY)
|
|
{
|
|
enabled = false;
|
|
}
|
|
}
|
|
|
|
QAction* action = menu.addAction(tr("Reset sub-material to default"));
|
|
action->setData(MENU_SUBMTL_MAKE);
|
|
action->setEnabled(enabled);
|
|
|
|
menu.addSeparator();
|
|
|
|
action = menu.addAction(tr("Cut"));
|
|
action->setShortcut(QKeySequence::Cut);
|
|
action->setData(MENU_CUT);
|
|
action->setEnabled(enabled);
|
|
|
|
action = menu.addAction(tr("Copy"));
|
|
action->setShortcut(QKeySequence::Copy);
|
|
action->setData(MENU_COPY);
|
|
|
|
action = menu.addAction(tr("Paste"));
|
|
action->setShortcut(QKeySequence::Paste);
|
|
action->setData(MENU_PASTE);
|
|
action->setEnabled(CanPaste() && enabled);
|
|
|
|
action = menu.addAction(tr("Rename\tF2"));
|
|
action->setData(MENU_RENAME);
|
|
action->setEnabled(enabled);
|
|
|
|
action = menu.addAction(tr("Delete"));
|
|
action->setData(MENU_SUBMTL_CLEAR);
|
|
action->setEnabled(enabled);
|
|
}
|
|
|
|
void MaterialBrowserWidget::AddContextMenuActionsMultiMaterial(QMenu& menu) const
|
|
{
|
|
menu.addAction(tr("Set Number of Sub-Materials"))->setData(MENU_NUM_SUBMTL);
|
|
menu.addSeparator();
|
|
}
|
|
|
|
void MaterialBrowserWidget::AddContextMenuActionsSingleMaterial(QMenu& menu) const
|
|
{
|
|
menu.addAction(tr("Convert To Multi Material"))->setData(MENU_CONVERT_TO_MULTI);
|
|
menu.addSeparator();
|
|
}
|
|
|
|
void MaterialBrowserWidget::AddContextMenuActionsCommon(QMenu& menu, _smart_ptr<CMaterial> material) const
|
|
{
|
|
uint32 fileAttributes = material->GetFileAttributes();
|
|
|
|
bool modificationsEnabled = true;
|
|
if (fileAttributes & SCC_FILE_ATTRIBUTE_READONLY)
|
|
{
|
|
modificationsEnabled = false;
|
|
}
|
|
|
|
QAction* action = menu.addAction(tr("Cut"));
|
|
action->setShortcut(QKeySequence::Cut);
|
|
action->setData(MENU_CUT);
|
|
action = menu.addAction(tr("Copy"));
|
|
action->setShortcut(QKeySequence::Copy);
|
|
action->setData(MENU_COPY);
|
|
action = menu.addAction(tr("Paste"));
|
|
action->setShortcut(QKeySequence::Paste);
|
|
action->setData(MENU_PASTE);
|
|
action->setEnabled(CanPaste() && modificationsEnabled);
|
|
menu.addAction(tr("Copy Path to Clipboard"))->setData(MENU_COPY_NAME);
|
|
if (fileAttributes & SCC_FILE_ATTRIBUTE_INPAK)
|
|
{
|
|
menu.addAction(tr("Extract"))->setData(MENU_EXTRACT);
|
|
}
|
|
else
|
|
{
|
|
menu.addAction(tr("Explore"))->setData(MENU_EXPLORE);
|
|
}
|
|
menu.addSeparator();
|
|
action = menu.addAction(tr("Duplicate"));
|
|
action->setShortcut(tr("Ctrl+D"));
|
|
action->setData(MENU_DUPLICATE);
|
|
menu.addAction(tr("Rename\tF2"))->setData(MENU_RENAME);
|
|
action = menu.addAction(tr("Delete"));
|
|
action->setShortcut(QKeySequence::Delete);
|
|
action->setData(MENU_DELETE);
|
|
menu.addSeparator();
|
|
menu.addAction(tr("Assign to Selected Objects"))->setData(MENU_ASSIGNTOSELECTION);
|
|
menu.addAction(tr("Select Assigned Objects"))->setData(MENU_SELECTASSIGNEDOBJECTS);
|
|
menu.addSeparator();
|
|
|
|
menu.addAction(tr("Add New Material"))->setData(MENU_ADDNEW);
|
|
menu.addAction(tr("Add New Multi Material"))->setData(MENU_ADDNEW_MULTI);
|
|
menu.addAction(tr("Merge (Select two or more)"))->setEnabled(false);
|
|
|
|
AddContextMenuActionsSourceControl(menu, material, fileAttributes);
|
|
}
|
|
|
|
void MaterialBrowserWidget::AddContextMenuActionsSourceControl(QMenu& menu, _smart_ptr<CMaterial> material, uint32 fileAttributes) const
|
|
{
|
|
if (GetIEditor()->IsSourceControlAvailable())
|
|
{
|
|
menu.addSeparator();
|
|
|
|
if (fileAttributes & SCC_FILE_ATTRIBUTE_INPAK)
|
|
{
|
|
menu.addAction(tr(" Material In Pak (Read Only)"))->setEnabled(false);
|
|
}
|
|
else
|
|
{
|
|
menu.addAction(tr(" Source Control"))->setEnabled(false);
|
|
if (!(fileAttributes & SCC_FILE_ATTRIBUTE_MANAGED))
|
|
{
|
|
menu.addAction(tr("Add To Source Control"))->setData(MENU_SCM_ADD);
|
|
}
|
|
}
|
|
QAction* action = nullptr;
|
|
if (fileAttributes & SCC_FILE_ATTRIBUTE_MANAGED)
|
|
{
|
|
action = menu.addAction(tr("Check Out"));
|
|
action->setData(MENU_SCM_CHECK_OUT);
|
|
action->setEnabled(fileAttributes & SCC_FILE_ATTRIBUTE_READONLY || fileAttributes & SCC_FILE_ATTRIBUTE_INPAK);
|
|
action = menu.addAction(tr("Undo Check Out"));
|
|
action->setData(MENU_SCM_UNDO_CHECK_OUT);
|
|
action->setEnabled(fileAttributes & SCC_FILE_ATTRIBUTE_CHECKEDOUT);
|
|
menu.addAction(tr("Get Latest Version"))->setData(MENU_SCM_GET_LATEST);
|
|
}
|
|
|
|
if (material)
|
|
{
|
|
QStringList filenames;
|
|
int nTextures = material->GetTextureFilenames(filenames);
|
|
action = menu.addAction(tr("Get Textures"));
|
|
action->setData(MENU_SCM_GET_LATEST_TEXTURES);
|
|
action->setEnabled(nTextures > 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::ShowContextMenu(const CMaterialBrowserRecord& record, const QPoint& point)
|
|
{
|
|
_smart_ptr<CMaterial> material = record.m_material;
|
|
|
|
// Create pop up menu.
|
|
QMenu menu;
|
|
if (m_markedRecords.size() >= 2) // it makes sense when we have at least two items selected
|
|
{
|
|
AddContextMenuActionsMultiSelect(menu);
|
|
}
|
|
else if (!material) // click on root, background or folder
|
|
{
|
|
AddContextMenuActionsNoSelection(menu);
|
|
}
|
|
else
|
|
{
|
|
// When right-clicking a single item in the material browser, select the parent material that was clicked, rather than the currently selected sub-material
|
|
// The context menu for sub-materials will be handled by the image list control rather than the material browser widget
|
|
m_selectedSubMaterialIndex = -1;
|
|
SetSelectedItem(material, nullptr, true);
|
|
AddContextMenuActionsSingleSelection(menu, material);
|
|
}
|
|
QAction* action = menu.exec(m_ui->treeView->mapToGlobal(point));
|
|
const int cmd = action ? action->data().toInt() : 0;
|
|
|
|
OnContextMenuAction(cmd, material);
|
|
}
|
|
|
|
void MaterialBrowserWidget::OnContextMenuAction(int command, _smart_ptr<CMaterial> material)
|
|
{
|
|
CMaterialBrowserRecord record;
|
|
TryGetSelectedRecord(record);
|
|
switch (command)
|
|
{
|
|
case MENU_UNDEFINED:
|
|
return; // do nothing
|
|
case MENU_CUT:
|
|
OnCut();
|
|
break;
|
|
case MENU_COPY:
|
|
OnCopy();
|
|
break;
|
|
case MENU_COPY_NAME:
|
|
OnCopyName();
|
|
break;
|
|
case MENU_PASTE:
|
|
OnPaste();
|
|
break;
|
|
case MENU_EXPLORE:
|
|
{
|
|
if (material && material->IsPureChild())
|
|
{
|
|
material = material->GetParent();
|
|
}
|
|
|
|
if (material)
|
|
{
|
|
QString fullPath = material->GetFilename();
|
|
AzQtComponents::ShowFileOnDesktop(fullPath);
|
|
}
|
|
}
|
|
break;
|
|
case MENU_EXTRACT:
|
|
{
|
|
if (material && material->IsPureChild())
|
|
{
|
|
material = material->GetParent();
|
|
}
|
|
|
|
if (material)
|
|
{
|
|
QString fullPath = material->GetFilename();
|
|
if (CFileUtil::ExtractFile(fullPath, true, fullPath.toUtf8().data()))
|
|
{
|
|
AzQtComponents::ShowFileOnDesktop(fullPath);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case MENU_DUPLICATE:
|
|
OnDuplicate();
|
|
break;
|
|
case MENU_RENAME:
|
|
OnRenameItem();
|
|
break;
|
|
case MENU_DELETE:
|
|
DeleteItem(record);
|
|
break;
|
|
case MENU_RESET:
|
|
OnResetItem();
|
|
break;
|
|
case MENU_ASSIGNTOSELECTION:
|
|
{
|
|
CUndo undo("Assign Material To Selection");
|
|
GetIEditor()->GetMaterialManager()->Command_AssignToSelection();
|
|
break;
|
|
}
|
|
case MENU_SELECTASSIGNEDOBJECTS:
|
|
{
|
|
CUndo undo("Select Objects With Current Material");
|
|
GetIEditor()->GetMaterialManager()->Command_SelectAssignedObjects();
|
|
break;
|
|
}
|
|
case MENU_NUM_SUBMTL:
|
|
OnSetSubMtlCount(record);
|
|
break;
|
|
case MENU_ADDNEW:
|
|
OnAddNewMaterial();
|
|
break;
|
|
case MENU_ADDNEW_MULTI:
|
|
OnAddNewMultiMaterial();
|
|
break;
|
|
case MENU_CONVERT_TO_MULTI:
|
|
OnConvertToMulti();
|
|
break;
|
|
case MENU_MERGE:
|
|
OnMergeMaterials();
|
|
break;
|
|
|
|
case MENU_SUBMTL_MAKE:
|
|
OnMakeSubMtlSlot(record);
|
|
break;
|
|
case MENU_SUBMTL_CLEAR:
|
|
OnClearSubMtlSlot(material);
|
|
break;
|
|
|
|
case MENU_SCM_ADD:
|
|
DoSourceControlOp(record, ESCM_IMPORT);
|
|
break;
|
|
case MENU_SCM_CHECK_OUT:
|
|
DoSourceControlOp(record, ESCM_CHECKOUT);
|
|
break;
|
|
case MENU_SCM_UNDO_CHECK_OUT:
|
|
DoSourceControlOp(record, ESCM_UNDO_CHECKOUT);
|
|
break;
|
|
case MENU_SCM_GET_LATEST:
|
|
DoSourceControlOp(record, ESCM_GETLATEST);
|
|
break;
|
|
case MENU_SCM_GET_LATEST_TEXTURES:
|
|
DoSourceControlOp(record, ESCM_GETLATESTTEXTURES);
|
|
break;
|
|
|
|
case MENU_SAVE_TO_FILE:
|
|
OnSaveToFile(false);
|
|
break;
|
|
case MENU_SAVE_TO_FILE_MULTI:
|
|
OnSaveToFile(true);
|
|
break;
|
|
//case MENU_MAKE_SUBMTL: OnAddNewMultiMaterial(); break;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::PopulateItems()
|
|
{
|
|
if (m_bIgnoreSelectionChange)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_bItemsValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_bItemsValid = true;
|
|
m_bIgnoreSelectionChange = true;
|
|
|
|
IDataBaseItem* pSelection = m_pMatMan->GetSelectedItem();
|
|
IDataBaseItem* pSelectionParent = m_pMatMan->GetSelectedParentItem();
|
|
|
|
m_bIgnoreSelectionChange = false;
|
|
|
|
if (pSelection)
|
|
{
|
|
SelectItem(pSelection, pSelectionParent);
|
|
if (m_bHighlightMaterial)
|
|
{
|
|
m_bHighlightMaterial = false;
|
|
m_pMatMan->SetHighlightedMaterial(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::StartRecordUpdateJobs()
|
|
{
|
|
m_filterModel->StartRecordUpdateJobs();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
uint32 MaterialBrowserWidget::MaterialNameToCrc32(const QString& str)
|
|
{
|
|
return CCrc32::ComputeLowercase(str.toUtf8());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool MaterialBrowserWidget::TryGetSelectedRecord(CMaterialBrowserRecord& record)
|
|
{
|
|
QVariant variant = m_filterModel->data(m_ui->treeView->currentIndex(), Qt::UserRole);
|
|
if (variant.isValid())
|
|
{
|
|
record = variant.value<CMaterialBrowserRecord>();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
_smart_ptr<CMaterial> MaterialBrowserWidget::GetCurrentMaterial()
|
|
{
|
|
CMaterialBrowserRecord record;
|
|
bool found = TryGetSelectedRecord(record);
|
|
if (found && record.m_material)
|
|
{
|
|
if (record.m_material->IsMultiSubMaterial() && m_selectedSubMaterialIndex >= 0)
|
|
{
|
|
return record.m_material->GetSubMaterial(m_selectedSubMaterialIndex);
|
|
}
|
|
else
|
|
{
|
|
return record.m_material;
|
|
}
|
|
}
|
|
|
|
return m_pMatMan->GetCurrentMaterial();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
AZStd::string MaterialBrowserWidget::GetSelectedMaterialID()
|
|
{
|
|
CMaterialBrowserRecord record;
|
|
bool found = TryGetSelectedRecord(record);
|
|
if (found)
|
|
{
|
|
return record.GetRelativeFilePath();
|
|
}
|
|
return AZStd::string();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void MaterialBrowserWidget::OnSelectionChanged()
|
|
{
|
|
m_selectedSubMaterialIndex = -1;
|
|
TMaterialBrowserRecords markedRecords;
|
|
|
|
CMaterialBrowserRecord record;
|
|
bool found = TryGetSelectedRecord(record);
|
|
for (const QModelIndex& row : m_ui->treeView->selectionModel()->selectedRows())
|
|
{
|
|
QVariant userRole = row.data(Qt::UserRole);
|
|
if (userRole.isValid())
|
|
{
|
|
CMaterialBrowserRecord currentRecord = userRole.value<CMaterialBrowserRecord>();
|
|
markedRecords.push_back(currentRecord);
|
|
}
|
|
}
|
|
|
|
if (!found && !markedRecords.empty())
|
|
{
|
|
record = markedRecords.front();
|
|
found = true;
|
|
}
|
|
if (found)
|
|
{
|
|
// Since the call to SetSelectedItem is coming from an OnSelectionChanged event, the appropriate
|
|
// element of the tree view has already been selected. Pass in false for the selectInTreeView argument
|
|
// to prevent SetSelectedItem from trying to re-select the material in the browser
|
|
SetSelectedItem(record.m_material, &markedRecords, false);
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::OnRefreshSelection()
|
|
{
|
|
// force RefreshSelected to repopulate by setting m_pLastActiveMultiMaterial to null
|
|
// so it thinks we selected a new material.
|
|
m_pLastActiveMultiMaterial = nullptr;
|
|
|
|
//If no material is selected, clear preview.
|
|
if (!GetCurrentMaterial())
|
|
{
|
|
if (m_pMaterialImageListCtrl)
|
|
{
|
|
QMaterialImageListModel* materialModel =
|
|
qobject_cast<QMaterialImageListModel*>(m_pMaterialImageListCtrl->model());
|
|
Q_ASSERT(materialModel);
|
|
materialModel->DeleteAllItems();
|
|
}
|
|
}
|
|
|
|
RefreshSelected();
|
|
|
|
// Force update the material dialog
|
|
if (m_pListener)
|
|
{
|
|
m_pListener->OnBrowserSelectItem(GetCurrentMaterial(), true);
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::OnMaterialAdded()
|
|
{
|
|
if (m_delayedSelection)
|
|
{
|
|
SetSelectedItem(m_delayedSelection, nullptr, true);
|
|
|
|
// Force update the material dialog
|
|
if (m_pListener)
|
|
{
|
|
m_pListener->OnBrowserSelectItem(GetCurrentMaterial(), true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::OnSubMaterialSelectedInPreviewPane(const QModelIndex& current)
|
|
{
|
|
if (!m_pMaterialImageListCtrl)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QMaterialImageListModel* materialModel =
|
|
qobject_cast<QMaterialImageListModel*>(m_pMaterialImageListCtrl->model());
|
|
Q_ASSERT(materialModel);
|
|
|
|
int nSlot = (INT_PTR)materialModel->UserDataFromIndex(current);
|
|
if (nSlot < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CMaterialBrowserRecord record;
|
|
bool found = TryGetSelectedRecord(record);
|
|
if (!found || !record.m_material)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!record.m_material->IsMultiSubMaterial())
|
|
{
|
|
return; // must be multi sub material.
|
|
}
|
|
if (nSlot >= record.m_material->GetSubMaterialCount())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (nSlot == m_selectedSubMaterialIndex)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_selectedSubMaterialIndex = nSlot;
|
|
|
|
SetSelectedItem(record.m_material, nullptr, false);
|
|
}
|
|
|
|
void MaterialBrowserWidget::SaveCurrentMaterial()
|
|
{
|
|
// Saving might open a modal dialog asking to overwrite, therefore don't run this from DTOR, might crash
|
|
|
|
_smart_ptr<CMaterial> pCurrentMtl = GetCurrentMaterial();
|
|
if (pCurrentMtl && pCurrentMtl->IsModified())
|
|
{
|
|
pCurrentMtl->Save();
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::expandAllNotMatchingIndexes(const QModelIndex& parent)
|
|
{
|
|
if (!parent.isValid())
|
|
{
|
|
m_ui->treeView->collapseAll();
|
|
}
|
|
|
|
const int rowCount = m_ui->treeView->model()->rowCount(parent);
|
|
for (int row = 0; row < rowCount; ++row)
|
|
{
|
|
const QModelIndex index = m_ui->treeView->model()->index(row, 0, parent);
|
|
const QString text = index.data().toString();
|
|
bool contains = true;
|
|
m_ui->treeView->setExpanded(index, !contains);
|
|
if (!contains)
|
|
{
|
|
expandAllNotMatchingIndexes(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::MaterialAddFinished()
|
|
{
|
|
emit materialAdded();
|
|
}
|
|
|
|
void MaterialBrowserWidget::MaterialFinishedProcessing(_smart_ptr<CMaterial> material, const QPersistentModelIndex& filterModelIndex)
|
|
{
|
|
// If the currently selected material finished processing
|
|
if (filterModelIndex.isValid() && filterModelIndex == m_ui->treeView->currentIndex())
|
|
{
|
|
// Stash the delayed selection so that it is not cleared just because the currently selected material finished processing
|
|
_smart_ptr<CMaterial> tempDelayedSelection = m_delayedSelection;
|
|
|
|
// Re-select the material to update the material dialogue
|
|
// but ignore the tree-view selection since the current index is already the correct one for this material
|
|
SetSelectedItem(material, nullptr, false);
|
|
|
|
// Force update the material dialog
|
|
if (m_pListener)
|
|
{
|
|
m_pListener->OnBrowserSelectItem(GetCurrentMaterial(), true);
|
|
}
|
|
|
|
if (material != m_delayedSelection)
|
|
{
|
|
// If the current selection that just finished processing was not the delayed selection, restore the delayed selection
|
|
m_delayedSelection = tempDelayedSelection;
|
|
}
|
|
}
|
|
// If there was a failed attempt to select the material before it finished processing
|
|
else if (m_delayedSelection && m_delayedSelection == material)
|
|
{
|
|
// Re-select the material to update the material dialogue
|
|
// and also select the material in the tree-view
|
|
SetSelectedItem(material, nullptr, true);
|
|
|
|
// Force update the material dialog
|
|
if (m_pListener)
|
|
{
|
|
m_pListener->OnBrowserSelectItem(GetCurrentMaterial(), true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialBrowserWidget::MaterialRecordUpdateFinished()
|
|
{
|
|
//The event is sent by a AZJob working thread, need to emit a signal here for qt to catch later.
|
|
//It can make sure all the qt functions need to be called at this point are only executed in the qt thread.
|
|
emit refreshSelection();
|
|
}
|
|
|
|
|
|
#include <Material/moc_MaterialBrowser.cpp>
|