SceneSettingsCard.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "SceneSettingsCard.h"
  9. #include <QDateTime>
  10. #include <QHeaderView>
  11. #include <QHBoxLayout>
  12. #include <QListWidget>
  13. #include <QMenu>
  14. #include <QPushButton>
  15. #include <QSvgWidget>
  16. #include <QSvgRenderer>
  17. #include <QTimer>
  18. #include <QWidgetAction>
  19. #include <AzFramework/StringFunc/StringFunc.h>
  20. #include <AzToolsFramework/Debug/TraceContextLogFormatter.h>
  21. #include <AzToolsFramework/Debug/TraceContextStack.h>
  22. #include <AzToolsFramework/UI/Logging/LogEntry.h>
  23. #include <AzQtComponents/Components/StyledDetailsTableView.h>
  24. #include <AzQtComponents/Components/Widgets/TableView.h>
  25. #include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/ProcessingHandler.h>
  26. SceneSettingsCardHeader::SceneSettingsCardHeader(QWidget* parent /* = nullptr */)
  27. : AzQtComponents::CardHeader(parent)
  28. {
  29. m_busySpinner = new QSvgWidget(":/stylesheet/img/loading.svg", this);
  30. m_busySpinner->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  31. m_busySpinner->setMinimumSize(20, 20);
  32. m_busySpinner->setMaximumSize(20, 20);
  33. m_busySpinner->setBaseSize(20, 20);
  34. m_backgroundLayout->insertWidget(1, m_busySpinner);
  35. m_busySpinner->setStyleSheet("background-color: rgba(0,0,0,0)");
  36. m_busySpinner->setToolTip(tr("There is an active processing event for this file. The window will update when the event completes."));
  37. m_closeButton = new QPushButton(this);
  38. m_closeButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
  39. m_closeButton->setMinimumSize(24, 24);
  40. m_closeButton->setMaximumSize(24, 24);
  41. m_closeButton->setBaseSize(24, 24);
  42. m_closeButton->setToolTip(tr("Removes this from the window. If you wish to see log details for this file again later, you can check the Asset Processor."));
  43. QIcon closeButtonIcon;
  44. closeButtonIcon.addPixmap(QPixmap(":/SceneUI/Common/CloseIcon.svg"));
  45. m_closeButton->setIcon(closeButtonIcon);
  46. m_closeButton->setFlat(true);
  47. m_backgroundLayout->addWidget(m_closeButton);
  48. connect(m_closeButton, &QPushButton::clicked, this, &SceneSettingsCardHeader::triggerCloseButton);
  49. }
  50. void SceneSettingsCardHeader::triggerCloseButton()
  51. {
  52. // A singleshot + delete on the parent is used instead of calling deleteLater,
  53. // because the deleteLater wasn't functioning in automated tests, but this logic does.
  54. QObject* card(parent());
  55. QTimer::singleShot(
  56. 0,
  57. [card]()
  58. {
  59. delete card;
  60. });
  61. }
  62. void SceneSettingsCardHeader::SetCanClose(bool canClose)
  63. {
  64. m_closeButton->setEnabled(canClose);
  65. // If this card can be closed, it's not busy
  66. m_busySpinner->setHidden(canClose);
  67. }
  68. SceneSettingsCard::SceneSettingsCard(AZ::Uuid traceTag, QString fileTracked, Layout layout, QWidget* parent /* = nullptr */)
  69. :
  70. AzQtComponents::Card(new SceneSettingsCardHeader(), parent),
  71. m_traceTag(traceTag),
  72. m_fileTracked(fileTracked)
  73. {
  74. m_settingsHeader = qobject_cast<SceneSettingsCardHeader*>(header());
  75. // This has to be set here, instead of in the customheader,
  76. // because the Card constructor forces the context menu to be visible.
  77. header()->setHasContextMenu(false);
  78. m_reportModel = new AzQtComponents::StyledDetailsTableModel(this);
  79. int statusColumn = m_reportModel->AddColumn("Status", AzQtComponents::StyledDetailsTableModel::StatusIcon);
  80. int platformColumn = -1;
  81. int windowColumn = -1;
  82. if (layout == Layout::Exporting)
  83. {
  84. platformColumn = m_reportModel->AddColumn("Platform");
  85. windowColumn = m_reportModel->AddColumn("Window");
  86. m_reportModel->AddColumnAlias("window", "Window");
  87. }
  88. int timeColumn = m_reportModel->AddColumn("Time");
  89. m_reportModel->AddColumn("Message");
  90. m_reportModel->AddColumnAlias("message", "Message");
  91. m_reportView = new AzQtComponents::TableView(this);
  92. m_reportView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
  93. m_reportView->setModel(m_reportModel);
  94. if (platformColumn > 0)
  95. {
  96. m_reportView->header()->setSectionResizeMode(platformColumn, QHeaderView::ResizeToContents);
  97. }
  98. if (windowColumn > 0)
  99. {
  100. m_reportView->header()->setSectionResizeMode(windowColumn, QHeaderView::ResizeToContents);
  101. }
  102. m_reportView->header()->setSectionResizeMode(statusColumn, QHeaderView::ResizeToContents);
  103. m_reportView->header()->setSectionResizeMode(timeColumn, QHeaderView::ResizeToContents);
  104. setContentWidget(m_reportView);
  105. AZ::Debug::TraceMessageBus::Handler::BusConnect();
  106. m_reportView->setContextMenuPolicy(Qt::CustomContextMenu);
  107. connect(m_reportView, &AzQtComponents::TableView::customContextMenuRequested, this, &SceneSettingsCard::ShowLogContextMenu);
  108. }
  109. SceneSettingsCard::~SceneSettingsCard()
  110. {
  111. AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
  112. }
  113. void SceneSettingsCard::SetAndStartProcessingHandler(const AZStd::shared_ptr<AZ::SceneAPI::SceneUI::ProcessingHandler>& handler)
  114. {
  115. AZ_Assert(handler, "Processing handler was null");
  116. AZ_Assert(!m_targetHandler, "A handler has already been assigned. Only one can be active per layer at any given time.");
  117. if (m_targetHandler)
  118. {
  119. return;
  120. }
  121. m_targetHandler = handler;
  122. connect(m_targetHandler.get(), &AZ::SceneAPI::SceneUI::ProcessingHandler::StatusMessageUpdated, this, &SceneSettingsCard::OnSetStatusMessage);
  123. connect(m_targetHandler.get(), &AZ::SceneAPI::SceneUI::ProcessingHandler::AddLogEntry, this, &SceneSettingsCard::AddLogEntry);
  124. connect(m_targetHandler.get(), &AZ::SceneAPI::SceneUI::ProcessingHandler::ProcessingComplete, this, &SceneSettingsCard::OnProcessingComplete);
  125. handler->BeginProcessing();
  126. }
  127. void SceneSettingsCard::AddLogEntry(const AzToolsFramework::Logging::LogEntry& logEntry)
  128. {
  129. if (logEntry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Message)
  130. {
  131. return;
  132. }
  133. AzQtComponents::StyledDetailsTableModel::TableEntry reportEntry;
  134. QVector<QPair<QString, QString>> detailsForLogLine;
  135. for (auto& field : logEntry.GetFields())
  136. {
  137. if (AzFramework::StringFunc::Equal("message", field.second.m_name.c_str()) ||
  138. AzFramework::StringFunc::Equal("window", field.second.m_name.c_str()))
  139. {
  140. // Add the message and window to the direct log
  141. reportEntry.Add(field.second.m_name.c_str(), field.second.m_value.c_str());
  142. }
  143. else
  144. {
  145. // All other fields, add to the additional details view.
  146. detailsForLogLine.push_back(QPair<QString, QString>(field.second.m_name.c_str(), field.second.m_value.c_str()));
  147. }
  148. }
  149. m_additionalLogDetails[m_reportModel->rowCount()] = detailsForLogLine;
  150. if (logEntry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Error)
  151. {
  152. reportEntry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
  153. UpdateCompletionState(CompletionState::Error);
  154. }
  155. else if (logEntry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Warning)
  156. {
  157. reportEntry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
  158. UpdateCompletionState(CompletionState::Warning);
  159. }
  160. reportEntry.Add("Time", GetTimeNowAsString());
  161. AddLogTableEntry(reportEntry);
  162. }
  163. void SceneSettingsCard::OnProcessingComplete()
  164. {
  165. AzQtComponents::StyledDetailsTableModel::TableEntry entry;
  166. entry.Add("Message", "Asset processing completed.");
  167. entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusSuccess);
  168. entry.Add("Time", GetTimeNowAsString());
  169. AddLogTableEntry(entry);
  170. SetState(SceneSettingsCard::State::Done);
  171. }
  172. void SceneSettingsCard::OnSetStatusMessage(const AZStd::string& message)
  173. {
  174. AzQtComponents::StyledDetailsTableModel::TableEntry entry;
  175. entry.Add("Message", message.c_str());
  176. entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusSuccess);
  177. entry.Add("Time", GetTimeNowAsString());
  178. AddLogTableEntry(entry);
  179. }
  180. bool SceneSettingsCard::OnPrintf(const char* window, const char* message)
  181. {
  182. if (!ShouldProcessMessage())
  183. {
  184. return false;
  185. }
  186. AzQtComponents::StyledDetailsTableModel::TableEntry entry;
  187. if (AzFramework::StringFunc::Find(window, "Success") != AZStd::string::npos)
  188. {
  189. entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusSuccess);
  190. }
  191. else if (AzFramework::StringFunc::Find(window, "Warning") != AZStd::string::npos)
  192. {
  193. entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
  194. UpdateCompletionState(CompletionState::Warning);
  195. }
  196. else if (AzFramework::StringFunc::Find(window, "Error") != AZStd::string::npos)
  197. {
  198. entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
  199. UpdateCompletionState(CompletionState::Error);
  200. }
  201. else
  202. {
  203. // To reduce noise in the report widget, only show success, warning and error messages.
  204. return false;
  205. }
  206. entry.Add("Message", message);
  207. entry.Add("Time", GetTimeNowAsString());
  208. AddLogTableEntry(entry);
  209. return false;
  210. }
  211. bool SceneSettingsCard::OnError(const char* /*window*/, const char* message)
  212. {
  213. if (!ShouldProcessMessage())
  214. {
  215. return false;
  216. }
  217. AzQtComponents::StyledDetailsTableModel::TableEntry entry;
  218. entry.Add("Message", message);
  219. entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
  220. entry.Add("Time", GetTimeNowAsString());
  221. AddLogTableEntry(entry);
  222. UpdateCompletionState(CompletionState::Error);
  223. return false;
  224. }
  225. bool SceneSettingsCard::OnWarning(const char* /*window*/, const char* message)
  226. {
  227. if (!ShouldProcessMessage())
  228. {
  229. return false;
  230. }
  231. AzQtComponents::StyledDetailsTableModel::TableEntry entry;
  232. entry.Add("Message", message);
  233. entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
  234. entry.Add("Time", GetTimeNowAsString());
  235. AddLogTableEntry(entry);
  236. UpdateCompletionState(CompletionState::Warning);
  237. return false;
  238. }
  239. bool SceneSettingsCard::OnAssert(const char* message)
  240. {
  241. if (!ShouldProcessMessage())
  242. {
  243. return false;
  244. }
  245. AzQtComponents::StyledDetailsTableModel::TableEntry entry;
  246. entry.Add("Message", message);
  247. entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
  248. entry.Add("Time", GetTimeNowAsString());
  249. AddLogTableEntry(entry);
  250. UpdateCompletionState(CompletionState::Error);
  251. return false;
  252. }
  253. void SceneSettingsCard::SetState(State newState)
  254. {
  255. switch (newState)
  256. {
  257. case State::Loading:
  258. {
  259. QString toolTip(tr("The scene settings for this file are being loaded. The window will update when the event completes."));
  260. setTitle(tr("Loading scene settings"));
  261. setTitleToolTip(toolTip);
  262. m_settingsHeader->m_busySpinner->setToolTip(toolTip);
  263. m_settingsHeader->SetCanClose(false);
  264. }
  265. break;
  266. case State::Processing:
  267. {
  268. QString toolTip(tr("The scene file is being processed. The window will update when the event completes."));
  269. setTitle(tr("Saving scene settings, and reprocessing scene file"));
  270. setTitleToolTip(toolTip);
  271. m_settingsHeader->m_busySpinner->setToolTip(toolTip);
  272. m_settingsHeader->SetCanClose(false);
  273. }
  274. break;
  275. case State::Done:
  276. {
  277. QString toolTip(tr("No errors or warnings were encountered with the scene file."));
  278. QString errorsAndWarningsString;
  279. if (m_warningCount > 0 || m_errorCount > 0)
  280. {
  281. errorsAndWarningsString = tr(" with %1 warning(s), %2 error(s)").arg(m_warningCount).arg(m_errorCount);
  282. toolTip = tr("Warnings and/or errors were encountered with the scene file. You can view the details by expanding this card "
  283. "and reading the log message.");
  284. }
  285. QString previousStateString;
  286. switch (m_sceneCardState)
  287. {
  288. case State::Loading:
  289. previousStateString = tr("Loading");
  290. break;
  291. case State::Processing:
  292. previousStateString = tr("Processing");
  293. toolTip = tr("%1 If you dismiss this card, you can view the processing logs again in the Asset Processor.").arg(toolTip);
  294. break;
  295. }
  296. setTitle(tr("%1 %2 completed at %3%4").arg(previousStateString).arg(m_fileTracked).arg(GetTimeNowAsString()).arg(errorsAndWarningsString));
  297. setTitleToolTip(toolTip);
  298. m_settingsHeader->SetCanClose(true);
  299. switch (m_completionState)
  300. {
  301. case CompletionState::Error:
  302. case CompletionState::Failure:
  303. m_settingsHeader->setIcon(QIcon(":/SceneUI/Common/ErrorIcon.svg"));
  304. m_settingsHeader->setUnderlineColor(QColor(226, 82, 67));
  305. break;
  306. case CompletionState::Warning:
  307. m_settingsHeader->setIcon(QIcon(":/SceneUI/Common/WarningIcon.svg"));
  308. m_settingsHeader->setUnderlineColor(QColor(240, 195, 45));
  309. break;
  310. default:
  311. m_settingsHeader->setIcon(QIcon(":/SceneUI/Common/SuccessIcon.svg"));
  312. m_settingsHeader->setUnderlineColor(QColor(88, 188, 97));
  313. break;
  314. }
  315. AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
  316. emit ProcessingCompleted();
  317. }
  318. break;
  319. }
  320. m_sceneCardState = newState;
  321. }
  322. bool SceneSettingsCard::ShouldProcessMessage()
  323. {
  324. AZStd::shared_ptr<const AzToolsFramework::Debug::TraceContextStack> stack = m_traceStackHandler.GetCurrentStack();
  325. if (stack)
  326. {
  327. for (size_t i = 0; i < stack->GetStackCount(); ++i)
  328. {
  329. if (stack->GetType(i) == AzToolsFramework::Debug::TraceContextStackInterface::ContentType::UuidType)
  330. {
  331. if (stack->GetUuidValue(i) == m_traceTag)
  332. {
  333. return true;
  334. }
  335. }
  336. }
  337. }
  338. return false;
  339. }
  340. void SceneSettingsCard::UpdateCompletionState(CompletionState newState)
  341. {
  342. // Use the highest encountered state
  343. m_completionState = AZStd::max(m_completionState, newState);
  344. switch (newState)
  345. {
  346. case CompletionState::Warning:
  347. ++m_warningCount;
  348. break;
  349. case CompletionState::Error:
  350. ++m_errorCount;
  351. break;
  352. }
  353. }
  354. void SceneSettingsCard::CopyTraceContext(AzQtComponents::StyledDetailsTableModel::TableEntry& entry) const
  355. {
  356. AZStd::shared_ptr<const AzToolsFramework::Debug::TraceContextStack> stack = m_traceStackHandler.GetCurrentStack();
  357. if (stack)
  358. {
  359. AZStd::string value;
  360. for (size_t i = 0; i < stack->GetStackCount(); ++i)
  361. {
  362. if (stack->GetType(i) != AzToolsFramework::Debug::TraceContextStackInterface::ContentType::UuidType)
  363. {
  364. const char* key = stack->GetKey(i);
  365. AzToolsFramework::Debug::TraceContextLogFormatter::PrintValue(value, *stack, i);
  366. entry.Add(key, value.c_str());
  367. value.clear();
  368. }
  369. }
  370. }
  371. }
  372. void SceneSettingsCard::AddLogTableEntry(AzQtComponents::StyledDetailsTableModel::TableEntry& entry)
  373. {
  374. CopyTraceContext(entry);
  375. m_reportModel->AddEntry(entry);
  376. // Set the height of the view, so the cards expand more vertically.
  377. // Clamp that max height at a point so it doesn't try to pull too much height from containing window.
  378. int rowCount = m_reportModel->rowCount();
  379. if(rowCount < 10)
  380. {
  381. m_reportView->setMinimumHeight(m_reportView->sizeHintForRow(0) * (rowCount + 1));
  382. }
  383. }
  384. QString SceneSettingsCard::GetTimeNowAsString()
  385. {
  386. return QDateTime::currentDateTime().toString(tr("hh:mm:ss ap"));
  387. }
  388. void SceneSettingsCard::ShowLogContextMenu(const QPoint& pos)
  389. {
  390. const QModelIndex selectedIndex = m_reportView->indexAt(pos);
  391. if (!selectedIndex.isValid())
  392. {
  393. return;
  394. }
  395. int logRow = selectedIndex.row();
  396. if (!m_additionalLogDetails.contains(logRow))
  397. {
  398. return;
  399. }
  400. int additionalLogCount = m_additionalLogDetails[logRow].count();
  401. if (additionalLogCount <= 0)
  402. {
  403. return;
  404. }
  405. // If the log message for the first row is empty, skip. This happens when there was no log details at all.
  406. if (additionalLogCount == 1 && m_additionalLogDetails[logRow][0].second.isEmpty())
  407. {
  408. return;
  409. }
  410. QMenu menu;
  411. menu.setToolTipsVisible(true);
  412. QAction* contextMenuTitleAction = menu.addAction("Additional log context");
  413. contextMenuTitleAction->setToolTip(tr("Additional log information for the selected line"));
  414. menu.addSeparator();
  415. QWidgetAction* logMenuListAction = new QWidgetAction(&menu);
  416. QListWidget* logDetailsWidget = new QListWidget(&menu);
  417. logDetailsWidget->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
  418. logDetailsWidget->setTextElideMode(Qt::ElideLeft);
  419. logDetailsWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
  420. logDetailsWidget->setSelectionMode(QAbstractItemView::NoSelection);
  421. logMenuListAction->setDefaultWidget(logDetailsWidget);
  422. for (const auto& logDetail : m_additionalLogDetails[logRow])
  423. {
  424. logDetailsWidget->addItem(tr("%1 - %2").arg(logDetail.first).arg(logDetail.second));
  425. }
  426. logDetailsWidget->setFixedHeight(additionalLogCount * logDetailsWidget->sizeHintForRow(0));
  427. logDetailsWidget->setFixedWidth(logDetailsWidget->sizeHintForColumn(0));
  428. menu.addAction(logMenuListAction);
  429. menu.exec(m_reportView->viewport()->mapToGlobal(pos));
  430. }