Files
video-v1/vav2/Vav2Player/Vav2Player/LogMessagePage.xaml.cpp

254 lines
8.5 KiB
C++
Raw Normal View History

2025-09-26 01:32:24 +09:00
#include "pch.h"
#include "LogMessagePage.xaml.h"
#if __has_include("LogMessagePage.g.cpp")
#include "LogMessagePage.g.cpp"
#endif
using namespace winrt;
using namespace winrt::Microsoft::UI::Xaml;
using namespace winrt::Microsoft::UI::Xaml::Controls;
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
using namespace std::chrono;
namespace winrt::Vav2Player::implementation
{
// Static members
std::weak_ptr<LogMessagePage> LogMessagePage::s_instance;
std::mutex LogMessagePage::s_instanceMutex;
LogMessagePage::LogMessagePage()
{
InitializeComponent();
m_logCollection = winrt::single_threaded_observable_vector<IInspectable>();
// Set ItemsSource after InitializeComponent to ensure controls are ready
if (LogItemsControl()) {
LogItemsControl().ItemsSource(m_logCollection);
}
// Initialize Observer pattern with LogManager
InitializeLogObserver();
}
LogMessagePage::~LogMessagePage()
{
// Remove observer callback when LogMessagePage is destroyed
if (m_observerInitialized) {
::Vav2Player::LogManager::GetInstance().RegisterLogAddedCallback(nullptr);
}
}
void LogMessagePage::ClearLogButton_Click(IInspectable const&, RoutedEventArgs const&)
{
// Clear logs in LogManager (single source of truth)
::Vav2Player::LogManager::GetInstance().ClearLogs();
// Clear UI collection
{
std::lock_guard<std::mutex> lock(m_uiUpdateMutex);
m_logCollection.Clear();
LogCountText().Text(L"0 messages");
} // Release mutex before logging to avoid deadlock
// Log the clear action (called after releasing mutex to prevent deadlock)
::Vav2Player::LogManager::GetInstance().LogInfo(L"Log cleared by user", L"LogMessagePage");
}
void LogMessagePage::AutoScrollCheckBox_CheckedChanged(IInspectable const& sender, RoutedEventArgs const&)
{
if (auto checkbox = sender.try_as<CheckBox>())
{
m_autoScroll = checkbox.IsChecked().GetBoolean();
}
}
void LogMessagePage::LogLevelFilterComboBox_SelectionChanged(IInspectable const& sender, SelectionChangedEventArgs const&)
{
// Prevent crashes during XAML initialization
if (!m_observerInitialized || !m_logCollection) {
return;
}
if (auto comboBox = sender.try_as<ComboBox>())
{
if (auto selectedItem = comboBox.SelectedItem().try_as<ComboBoxItem>())
{
auto tag = selectedItem.Tag().try_as<hstring>();
if (tag == L"DEBUG") m_currentFilter = LogLevel::Debug;
else if (tag == L"INFO") m_currentFilter = LogLevel::Info;
else if (tag == L"WARNING") m_currentFilter = LogLevel::Warning;
else if (tag == L"ERROR") m_currentFilter = LogLevel::Error;
else m_currentFilter = LogLevel::Debug; // ALL
UpdateLogDisplay();
}
}
}
void LogMessagePage::InitializeLogObserver()
{
if (m_observerInitialized) {
return;
}
// Register as observer with LogManager using Observer pattern
auto& logManager = ::Vav2Player::LogManager::GetInstance();
// Set up callback to receive log messages from LogManager
logManager.RegisterLogAddedCallback([this](const ::Vav2Player::LogMessage& message) {
OnLogAdded(message);
});
// Load existing messages from LogManager
const auto& existingMessages = logManager.GetLogMessages();
for (const auto& message : existingMessages) {
OnLogAdded(*message);
}
m_observerInitialized = true;
}
void LogMessagePage::OnLogAdded(const ::Vav2Player::LogMessage& message)
{
// Dispatch to UI thread if needed
if (!DispatcherQueue().HasThreadAccess())
{
DispatcherQueue().TryEnqueue([this, message]()
{
OnLogAdded(message);
});
return;
}
// Prevent crashes during initialization
if (!m_logCollection || !LogCountText()) {
return;
}
std::lock_guard<std::mutex> lock(m_uiUpdateMutex);
// Update UI if message should be shown according to current filter
if (ShouldShowMessage(message.level))
{
// Format message for display using LogManager's data
std::wstring formattedMessage = FormatLogMessageForUI(message);
// Add the formatted message to the UI collection
m_logCollection.Append(winrt::box_value(formattedMessage));
// Limit UI collection size (LogManager handles its own size limits)
static constexpr uint32_t UI_MAX_MESSAGES = 500; // Smaller than LogManager limit
while (m_logCollection.Size() > UI_MAX_MESSAGES)
{
m_logCollection.RemoveAt(0);
}
}
// Update count from LogManager (safe now that callback is called after mutex release)
const auto& allMessages = ::Vav2Player::LogManager::GetInstance().GetLogMessages();
LogCountText().Text(std::to_wstring(allMessages.size()) + L" messages");
// Auto-scroll if enabled
if (m_autoScroll)
{
ScrollToBottom();
}
}
// Removed AddLogMessage methods - LogMessagePage now observes LogManager instead of managing logs directly
std::shared_ptr<LogMessagePage> LogMessagePage::GetInstance()
{
std::lock_guard<std::mutex> lock(s_instanceMutex);
return s_instance.lock();
}
void LogMessagePage::SetInstance(std::shared_ptr<LogMessagePage> instance)
{
std::lock_guard<std::mutex> lock(s_instanceMutex);
s_instance = instance;
}
void LogMessagePage::UpdateLogDisplay()
{
// Prevent crashes during initialization
if (!m_logCollection || !LogCountText()) {
return;
}
std::lock_guard<std::mutex> lock(m_uiUpdateMutex);
// Clear and repopulate UI from LogManager (single source of truth)
m_logCollection.Clear();
const auto& allMessages = ::Vav2Player::LogManager::GetInstance().GetLogMessages();
for (const auto& logMessage : allMessages)
{
if (ShouldShowMessage(logMessage->level))
{
std::wstring formattedMessage = FormatLogMessageForUI(*logMessage);
m_logCollection.Append(winrt::box_value(formattedMessage));
}
}
// Update count
LogCountText().Text(std::to_wstring(allMessages.size()) + L" messages");
if (m_autoScroll)
{
ScrollToBottom();
}
}
void LogMessagePage::ScrollToBottom()
{
// Prevent crashes during initialization
if (!m_logCollection || !LogScrollViewer()) {
return;
}
// Schedule scroll to bottom on next UI update
DispatcherQueue().TryEnqueue([this]()
{
if (m_logCollection && m_logCollection.Size() > 0 && LogScrollViewer())
{
LogScrollViewer().ChangeView(nullptr, LogScrollViewer().ScrollableHeight(), nullptr);
}
});
}
std::wstring LogMessagePage::FormatLogMessageForUI(const ::Vav2Player::LogMessage& message) const
{
std::wstring formattedMessage = L"[" + message.timestamp + L"] " +
GetLogLevelString(message.level) + L": " +
message.message;
if (!message.source.empty()) {
formattedMessage += L" (" + message.source + L")";
}
return formattedMessage;
}
bool LogMessagePage::ShouldShowMessage(LogLevel level) const
{
// If filter is "ALL" (Debug), show everything
if (m_currentFilter == LogLevel::Debug)
return true;
// Otherwise, show messages at current filter level or higher
return static_cast<int>(level) >= static_cast<int>(m_currentFilter);
}
std::wstring LogMessagePage::GetLogLevelString(LogLevel level) const
{
switch (level)
{
case LogLevel::Debug: return L"DEBUG";
case LogLevel::Info: return L"INFO";
case LogLevel::Warning: return L"WARN";
case LogLevel::Error: return L"ERROR";
default: return L"UNKNOWN";
}
}
}