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.
429 lines
17 KiB
C++
429 lines
17 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <ctime>
|
|
#include <AzFramework/StringFunc/StringFunc.h>
|
|
#include <AzQtComponents/Components/StyledBusyLabel.h>
|
|
#include <AzQtComponents/Components/StyledDetailsTableView.h>
|
|
#include <AzToolsFramework/Debug/TraceContextLogFormatter.h>
|
|
#include <AzToolsFramework/UI/Logging/LogEntry.h>
|
|
#include <CommonWidgets/ui_ProcessingOverlayWidget.h>
|
|
#include <SceneAPI/SceneUI/CommonWidgets/ProcessingOverlayWidget.h>
|
|
#include <SceneAPI/SceneUI/CommonWidgets/OverlayWidget.h>
|
|
#include <SceneAPI/SceneUI/Handlers/ProcessingHandlers/ProcessingHandler.h>
|
|
#include <SceneAPI/SceneUI/CommonWidgets/OverlayWidget.h>
|
|
#include <QCloseEvent>
|
|
#include <QDateTime>
|
|
#include <QLabel>
|
|
#include <QTimer>
|
|
#include <QHeaderView>
|
|
|
|
namespace AZ
|
|
{
|
|
namespace SceneAPI
|
|
{
|
|
namespace SceneUI
|
|
{
|
|
namespace Internal
|
|
{
|
|
QtWebEngineMessageFilter::QtWebEngineMessageFilter(QObject* parent)
|
|
: QSortFilterProxyModel(parent)
|
|
{
|
|
}
|
|
|
|
QtWebEngineMessageFilter::~QtWebEngineMessageFilter()
|
|
{
|
|
}
|
|
|
|
bool QtWebEngineMessageFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
|
{
|
|
auto tableModel = qobject_cast<AzQtComponents::StyledDetailsTableModel*>(sourceModel());
|
|
if (!tableModel)
|
|
{
|
|
return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
|
|
}
|
|
|
|
const int sourceColumn = tableModel->GetColumnIndex(QStringLiteral("message"));
|
|
const QModelIndex index = tableModel->index(sourceRow, sourceColumn, sourceParent);
|
|
const QVariant data = tableModel->data(index);
|
|
|
|
static const QString filteredMessage = QStringLiteral("Qt WebEngine seems to be initialized from a plugin. Please set Qt::AA_ShareOpenGLContexts using QCoreApplication::setAttribute before constructing QGuiApplication.");
|
|
if (data.toString() == filteredMessage)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent);
|
|
}
|
|
}
|
|
|
|
ProcessingOverlayWidget::ProcessingOverlayWidget(UI::OverlayWidget* overlay, Layout layout, Uuid traceTag)
|
|
: QWidget(nullptr, Qt::Tool | Qt::WindowStaysOnTopHint)
|
|
, m_traceTag(traceTag)
|
|
, ui(new Ui::ProcessingOverlayWidget())
|
|
, m_overlay(overlay)
|
|
, m_progressLabel(nullptr)
|
|
, m_layerId(UI::OverlayWidget::s_invalidOverlayIndex)
|
|
, m_isProcessingComplete(false)
|
|
, m_isClosingBlocked(false)
|
|
, m_autoCloseOnSuccess(false)
|
|
, m_encounteredIssues(false)
|
|
, m_resizeTimer(new QTimer(this))
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
m_busyLabel = new AzQtComponents::StyledBusyLabel();
|
|
m_busyLabel->SetIsBusy(true);
|
|
m_busyLabel->SetBusyIconSize(14);
|
|
ui->m_header->addWidget(m_busyLabel);
|
|
|
|
m_reportModel = new AzQtComponents::StyledDetailsTableModel();
|
|
m_reportModel->AddColumn("Status", AzQtComponents::StyledDetailsTableModel::StatusIcon);
|
|
if (layout == Layout::Exporting)
|
|
{
|
|
m_reportModel->AddColumn("Platform");
|
|
}
|
|
m_reportModel->AddColumn("Message");
|
|
m_reportModel->AddColumnAlias("message", "Message");
|
|
|
|
auto messageFilterModel = new Internal::QtWebEngineMessageFilter(this);
|
|
messageFilterModel->setSourceModel(m_reportModel);
|
|
|
|
m_reportView = new AzQtComponents::StyledDetailsTableView();
|
|
m_reportView->setModel(messageFilterModel);
|
|
ui->m_reportArea->addWidget(m_reportView);
|
|
|
|
UpdateColumnSizes();
|
|
|
|
connect(m_overlay, &UI::OverlayWidget::LayerRemoved, this, &ProcessingOverlayWidget::OnLayerRemoved);
|
|
|
|
BusConnect();
|
|
|
|
m_resizeTimer->setSingleShot(true);
|
|
m_resizeTimer->setInterval(0);
|
|
connect(m_resizeTimer, &QTimer::timeout, this, &ProcessingOverlayWidget::UpdateColumnSizes);
|
|
}
|
|
|
|
ProcessingOverlayWidget::~ProcessingOverlayWidget()
|
|
{
|
|
BusDisconnect();
|
|
}
|
|
|
|
bool ProcessingOverlayWidget::OnPrintf(const char* window, const char* message)
|
|
{
|
|
if (ShouldProcessMessage())
|
|
{
|
|
AzQtComponents::StyledDetailsTableModel::TableEntry entry;
|
|
if (AzFramework::StringFunc::Find(window, "Success") != AZStd::string::npos)
|
|
{
|
|
entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusSuccess);
|
|
}
|
|
else if (AzFramework::StringFunc::Find(window, "Warning") != AZStd::string::npos)
|
|
{
|
|
entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
|
|
m_encounteredIssues = true;
|
|
}
|
|
else if (AzFramework::StringFunc::Find(window, "Error") != AZStd::string::npos)
|
|
{
|
|
entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
|
|
m_encounteredIssues = true;
|
|
}
|
|
else
|
|
{
|
|
// To reduce noise in the report widget, only show success, warning and error messages.
|
|
return false;
|
|
}
|
|
|
|
entry.Add("Message", message);
|
|
CopyTraceContext(entry);
|
|
m_reportModel->AddEntry(entry);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ProcessingOverlayWidget::OnError(const char* /*window*/, const char* message)
|
|
{
|
|
if (ShouldProcessMessage())
|
|
{
|
|
AzQtComponents::StyledDetailsTableModel::TableEntry entry;
|
|
entry.Add("Message", message);
|
|
entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
|
|
CopyTraceContext(entry);
|
|
m_reportModel->AddEntry(entry);
|
|
m_encounteredIssues = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ProcessingOverlayWidget::OnWarning(const char* /*window*/, const char* message)
|
|
{
|
|
if (ShouldProcessMessage())
|
|
{
|
|
AzQtComponents::StyledDetailsTableModel::TableEntry entry;
|
|
entry.Add("Message", message);
|
|
entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
|
|
CopyTraceContext(entry);
|
|
m_reportModel->AddEntry(entry);
|
|
m_encounteredIssues = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ProcessingOverlayWidget::OnAssert(const char* message)
|
|
{
|
|
if (ShouldProcessMessage())
|
|
{
|
|
AzQtComponents::StyledDetailsTableModel::TableEntry entry;
|
|
entry.Add("Message", message);
|
|
entry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
|
|
CopyTraceContext(entry);
|
|
m_reportModel->AddEntry(entry);
|
|
m_encounteredIssues = true;
|
|
// Don't return true here as assert should pop a window.
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ProcessingOverlayWidget::OnLayerRemoved(int layerId)
|
|
{
|
|
if (layerId == m_layerId)
|
|
{
|
|
delete m_progressLabel;
|
|
m_progressLabel = nullptr;
|
|
|
|
layerId = UI::OverlayWidget::s_invalidOverlayIndex;
|
|
emit Closing();
|
|
}
|
|
}
|
|
|
|
int ProcessingOverlayWidget::PushToOverlay()
|
|
{
|
|
AZ_Assert(m_layerId == UI::OverlayWidget::s_invalidOverlayIndex, "Processing overlay widget already pushed.");
|
|
if (m_layerId != UI::OverlayWidget::s_invalidOverlayIndex)
|
|
{
|
|
return m_layerId;
|
|
}
|
|
|
|
UI::OverlayWidgetButtonList buttons;
|
|
|
|
UI::OverlayWidgetButton button;
|
|
button.m_text = "Ok";
|
|
button.m_triggersPop = true;
|
|
button.m_isCloseButton = true;
|
|
button.m_enabledCheck = [this]() -> bool
|
|
{
|
|
return CanClose();
|
|
};
|
|
buttons.push_back(&button);
|
|
|
|
m_progressLabel = new QLabel("Processing...");
|
|
m_progressLabel->setAlignment(Qt::AlignCenter);
|
|
m_layerId = m_overlay->PushLayer(m_progressLabel, this, "File progress", buttons);
|
|
return m_layerId;
|
|
}
|
|
|
|
bool ProcessingOverlayWidget::GetAutoCloseOnSuccess() const
|
|
{
|
|
return m_autoCloseOnSuccess;
|
|
}
|
|
|
|
void ProcessingOverlayWidget::SetAutoCloseOnSuccess(bool closeOnComplete)
|
|
{
|
|
m_autoCloseOnSuccess = closeOnComplete;
|
|
}
|
|
|
|
bool ProcessingOverlayWidget::HasProcessingCompleted() const
|
|
{
|
|
return m_isProcessingComplete;
|
|
}
|
|
|
|
void ProcessingOverlayWidget::SetAndStartProcessingHandler(const AZStd::shared_ptr<ProcessingHandler>& handler)
|
|
{
|
|
AZ_Assert(handler, "Processing handler was null");
|
|
AZ_Assert(!m_targetHandler, "A handler has already been assigned. Only one can be active per layer at any given time.");
|
|
if (m_targetHandler)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_targetHandler = handler;
|
|
|
|
connect(m_targetHandler.get(), &ProcessingHandler::StatusMessageUpdated, this, &ProcessingOverlayWidget::OnSetStatusMessage);
|
|
connect(m_targetHandler.get(), &ProcessingHandler::AddLogEntry, this, &ProcessingOverlayWidget::AddLogEntry);
|
|
connect(m_targetHandler.get(), &ProcessingHandler::ProcessingComplete, this, &ProcessingOverlayWidget::OnProcessingComplete);
|
|
|
|
handler->BeginProcessing();
|
|
}
|
|
|
|
AZStd::shared_ptr<ProcessingHandler> ProcessingOverlayWidget::GetProcessingHandler() const
|
|
{
|
|
return m_targetHandler;
|
|
}
|
|
|
|
void ProcessingOverlayWidget::BlockClosing()
|
|
{
|
|
m_isClosingBlocked = true;
|
|
}
|
|
|
|
void ProcessingOverlayWidget::UnblockClosing()
|
|
{
|
|
m_isClosingBlocked = false;
|
|
SetUIToCompleteState();
|
|
}
|
|
|
|
void ProcessingOverlayWidget::AddLogEntry(const AzToolsFramework::Logging::LogEntry& entry)
|
|
{
|
|
if (entry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Message)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_encounteredIssues = true;
|
|
|
|
bool hasStatus = false;
|
|
AzQtComponents::StyledDetailsTableModel::TableEntry reportEntry;
|
|
for (auto& field : entry.GetFields())
|
|
{
|
|
size_t offset = 0;
|
|
hasStatus = hasStatus || AzFramework::StringFunc::Equal("status", field.second.m_name.c_str());
|
|
if (AzFramework::StringFunc::Equal("message", field.second.m_name.c_str()))
|
|
{
|
|
if (field.second.m_value.length() > 2)
|
|
{
|
|
// Removing the prefixes such as "W: " and "E: ".
|
|
if (field.second.m_value[1] == ':' && field.second.m_value[2] == ' ')
|
|
{
|
|
offset = 3;
|
|
}
|
|
}
|
|
}
|
|
reportEntry.Add(field.second.m_name.c_str(), field.second.m_value.c_str() + offset);
|
|
}
|
|
|
|
if (!hasStatus)
|
|
{
|
|
if (entry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Error)
|
|
{
|
|
reportEntry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusError);
|
|
}
|
|
else if (entry.GetSeverity() == AzToolsFramework::Logging::LogEntry::Severity::Warning)
|
|
{
|
|
reportEntry.Add("Status", AzQtComponents::StyledDetailsTableModel::StatusWarning);
|
|
}
|
|
}
|
|
|
|
time_t time = QDateTime::fromMSecsSinceEpoch(entry.GetRecordedTime()).toTime_t();
|
|
struct tm timeInfo;
|
|
#if defined(AZ_PLATFORM_WINDOWS)
|
|
localtime_s(&timeInfo, &time);
|
|
#else
|
|
localtime_r(&time, &timeInfo);
|
|
#endif
|
|
|
|
char buffer[128];
|
|
std::strftime(buffer, sizeof(buffer), "%H:%M:%S", &timeInfo);
|
|
reportEntry.Add("Time", buffer);
|
|
std::strftime(buffer, sizeof(buffer), "%A, %B %d, %Y", &timeInfo);
|
|
reportEntry.Add("Date", buffer);
|
|
|
|
m_reportModel->AddEntry(reportEntry);
|
|
|
|
m_resizeTimer->start();
|
|
}
|
|
|
|
void ProcessingOverlayWidget::OnProcessingComplete()
|
|
{
|
|
m_isProcessingComplete = true;
|
|
SetUIToCompleteState();
|
|
|
|
if (!m_encounteredIssues && m_autoCloseOnSuccess)
|
|
{
|
|
close();
|
|
}
|
|
else if (m_progressLabel != nullptr)
|
|
{
|
|
m_progressLabel->setText("Close the processing report to continue editing settings.");
|
|
}
|
|
}
|
|
|
|
void ProcessingOverlayWidget::OnSetStatusMessage(const AZStd::string& message)
|
|
{
|
|
m_busyLabel->SetText(message.c_str());
|
|
}
|
|
|
|
void ProcessingOverlayWidget::SetUIToCompleteState()
|
|
{
|
|
if (CanClose())
|
|
{
|
|
if (m_overlay && m_layerId != UI::OverlayWidget::s_invalidOverlayIndex)
|
|
{
|
|
m_overlay->RefreshLayer(m_layerId);
|
|
}
|
|
m_busyLabel->SetIsBusy(false);
|
|
}
|
|
}
|
|
|
|
bool ProcessingOverlayWidget::CanClose() const
|
|
{
|
|
return !m_isClosingBlocked && m_isProcessingComplete;
|
|
}
|
|
|
|
bool ProcessingOverlayWidget::ShouldProcessMessage() const
|
|
{
|
|
AZStd::shared_ptr<const AzToolsFramework::Debug::TraceContextStack> stack = m_traceStackHandler.GetCurrentStack();
|
|
if (stack)
|
|
{
|
|
for (size_t i = 0; i < stack->GetStackCount(); ++i)
|
|
{
|
|
if (stack->GetType(i) == AzToolsFramework::Debug::TraceContextStackInterface::ContentType::UuidType)
|
|
{
|
|
if (stack->GetUuidValue(i) == m_traceTag)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ProcessingOverlayWidget::CopyTraceContext(AzQtComponents::StyledDetailsTableModel::TableEntry& entry) const
|
|
{
|
|
AZStd::shared_ptr<const AzToolsFramework::Debug::TraceContextStack> stack = m_traceStackHandler.GetCurrentStack();
|
|
if (stack)
|
|
{
|
|
AZStd::string value;
|
|
for (size_t i = 0; i < stack->GetStackCount(); ++i)
|
|
{
|
|
if (stack->GetType(i) != AzToolsFramework::Debug::TraceContextStackInterface::ContentType::UuidType)
|
|
{
|
|
const char* key = stack->GetKey(i);
|
|
AzToolsFramework::Debug::TraceContextLogFormatter::PrintValue(value, *stack, i);
|
|
entry.Add(key, value.c_str());
|
|
value.clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProcessingOverlayWidget::UpdateColumnSizes()
|
|
{
|
|
const int headerPadding = 5;
|
|
m_reportView->resizeColumnsToContents();
|
|
m_reportView->horizontalHeader()->resizeSection(0,
|
|
fontMetrics().horizontalAdvance("Status")
|
|
+ style()->pixelMetric(QStyle::PM_HeaderMarkSize) + headerPadding);
|
|
}
|
|
} // SceneUI
|
|
} // SceneAPI
|
|
} // AZ
|
|
|
|
#include <CommonWidgets/moc_ProcessingOverlayWidget.cpp>
|