diff --git a/tools/k4aviewer/CMakeLists.txt b/tools/k4aviewer/CMakeLists.txt index fba2c3dd..9c5f6809 100644 --- a/tools/k4aviewer/CMakeLists.txt +++ b/tools/k4aviewer/CMakeLists.txt @@ -14,6 +14,7 @@ set(SOURCE_FILES k4aimudatagraph.cpp k4aimusamplesource.cpp k4aimuwindow.cpp + k4alogdockcontrol.cpp k4amicrophone.cpp k4amicrophonelistener.cpp k4apointcloudrenderer.cpp @@ -26,6 +27,7 @@ set(SOURCE_FILES k4avideowindow.cpp k4aviewer.cpp k4aviewerimage.cpp + k4aviewerlogmanager.cpp k4aviewererrormanager.cpp k4aviewersettingsmanager.cpp k4awindowmanager.cpp diff --git a/tools/k4aviewer/k4alogdockcontrol.cpp b/tools/k4aviewer/k4alogdockcontrol.cpp new file mode 100644 index 00000000..4289f718 --- /dev/null +++ b/tools/k4aviewer/k4alogdockcontrol.cpp @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Associated header +// +#include "k4alogdockcontrol.h" + +// System headers +// +#include + +// Library headers +// + +// Project headers +// +#include "k4aimguiextensions.h" + +using namespace k4aviewer; + +namespace +{ +// Maximum number of log entries to keep in memory +// +constexpr static size_t MaxLines = 10000; + +// clang-format off + +// Get a string representation of a log level suitable for printing +// in the log box (fields are fixed-width) +// +const char *LogLevelToString(k4a_log_level_t logLevel) +{ + switch(logLevel) + { + case K4A_LOG_LEVEL_CRITICAL: return "critical"; + case K4A_LOG_LEVEL_ERROR: return "error "; + case K4A_LOG_LEVEL_WARNING: return "warning "; + case K4A_LOG_LEVEL_INFO: return "info "; + case K4A_LOG_LEVEL_TRACE: return "trace "; + + default: return "[unknown]"; + } +} + +const ImVec4 &LogLevelToColor(k4a_log_level_t logLevel) +{ + static const ImVec4 critical = ImVec4(1.f, 0.f, 0.f, 1.f); + static const ImVec4 error = ImVec4(1.f, .3f, 0.f, 1.f); + static const ImVec4 warning = ImVec4(1.f, 1.f, 0.f, 1.f); + static const ImVec4 info = ImVec4(1.f, 1.f, 1.f, 1.f); + static const ImVec4 trace = ImVec4(.5f, .5f, .5f, 1.f); + + switch(logLevel) + { + case K4A_LOG_LEVEL_CRITICAL: return critical; + case K4A_LOG_LEVEL_ERROR: return error; + case K4A_LOG_LEVEL_WARNING: return warning; + case K4A_LOG_LEVEL_INFO: return info; + case K4A_LOG_LEVEL_TRACE: return trace; + + default: return warning; + } +} + +// String mappings used for the combo box used to select error levels +// +const std::vector> LogLevelLabels = { + {K4A_LOG_LEVEL_CRITICAL, "Critical"}, + {K4A_LOG_LEVEL_ERROR, "Error"}, + {K4A_LOG_LEVEL_WARNING, "Warning"}, + {K4A_LOG_LEVEL_INFO, "Info"}, + {K4A_LOG_LEVEL_TRACE, "Trace"} +}; + +// clang-format on +} // namespace + +K4ALogDockControl::K4ALogDockControl() : m_logListener(std::make_shared()) +{ + K4AViewerLogManager::Instance().RegisterListener(m_logListener); +} + +void K4ALogDockControl::LogListener::Log(k4a_log_level_t severity, const char *file, int line, const char *msg) +{ + if (severity > m_minSeverity) + { + return; + } + + std::lock_guard lock(m_mutex); + + m_entries.emplace_back(severity, file, line, msg); + if (m_entries.size() > MaxLines) + { + m_entries.pop_front(); + } + + m_updated = true; +} + +K4ADockControlStatus K4ALogDockControl::Show() +{ + ImGui::BeginGroup(); + if (ImGui::Button("Clear Log")) + { + std::lock_guard lock(m_logListener->m_mutex); + m_logListener->m_entries.clear(); + } + ImGui::SameLine(); + const bool copy = ImGui::Button("Copy Log to Clipboard"); + + m_logListener->m_updated |= ImGuiExtensions::K4AComboBox("Severity", + "", + ImGuiComboFlags_None, + LogLevelLabels, + &m_logListener->m_minSeverity); + m_logListener->m_updated |= ImGui::InputText("Search", &m_filterString[0], m_filterString.size()); + m_logListener->m_updated |= ImGui::Checkbox("Show line info", &m_showLineInfo); + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginChild("LogTextScrollArea", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); + + if (copy) + { + ImGui::LogToClipboard(); + } + + bool updated = false; + { + std::lock_guard lock(m_logListener->m_mutex); + for (const LogEntry &entry : m_logListener->m_entries) + { + std::stringstream lineBuilder; + lineBuilder << "[ " << LogLevelToString(entry.Severity) << " ] "; + + if (m_showLineInfo) + { + lineBuilder << "( " << entry.File << ":" << entry.Line << " ) "; + } + + lineBuilder << ": " << entry.Msg.c_str(); + std::string lineStr = lineBuilder.str(); + if (m_filterString[0] != '\0' && lineStr.find(&m_filterString[0]) == std::string::npos) + { + continue; + } + ImGui::TextColored(LogLevelToColor(entry.Severity), "%s", lineStr.c_str()); + } + + updated = m_logListener->m_updated; + m_logListener->m_updated = false; + } + + if (copy) + { + ImGui::LogFinish(); + } + + if (updated) + { + ImGui::SetScrollHere(1.0f); + } + + ImGui::EndChild(); + + return K4ADockControlStatus::Ok; +} diff --git a/tools/k4aviewer/k4alogdockcontrol.h b/tools/k4aviewer/k4alogdockcontrol.h new file mode 100644 index 00000000..7a8b9706 --- /dev/null +++ b/tools/k4aviewer/k4alogdockcontrol.h @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef K4ALOGDOCKCONTROL_H +#define K4ALOGDOCKCONTROL_H + +// System headers +// +#include +#include +#include +#include + +// Library headers +// + +// Project headers +// +#include "ik4adockcontrol.h" +#include "k4aviewerlogmanager.h" + +namespace k4aviewer +{ + +class K4ALogDockControl : public IK4ADockControl +{ +public: + K4ALogDockControl(); + ~K4ALogDockControl() override = default; + + K4ALogDockControl(K4ALogDockControl &) = delete; + K4ALogDockControl(K4ALogDockControl &&) = delete; + K4ALogDockControl operator=(K4ALogDockControl &) = delete; + K4ALogDockControl operator=(K4ALogDockControl &&) = delete; + + K4ADockControlStatus Show() override; + +private: + struct LogEntry + { + LogEntry() = default; + LogEntry(k4a_log_level_t severity, const char *file, int line, const char *msg) : + Severity(severity), + File(file), + Line(line), + Msg(msg) + { + } + + k4a_log_level_t Severity; + std::string File; + int Line; + std::string Msg; + }; + + struct LogListener : public IK4AViewerLogListener + { + void Log(k4a_log_level_t severity, const char *file, int line, const char *msg) override; + ~LogListener() override = default; + + std::list m_entries; + bool m_updated = false; + std::mutex m_mutex; + k4a_log_level_t m_minSeverity = K4A_LOG_LEVEL_WARNING; + }; + + std::shared_ptr m_logListener; + std::array m_filterString = { { '\0' } }; + bool m_showLineInfo = false; +}; + +} // namespace k4aviewer + +#endif diff --git a/tools/k4aviewer/k4aviewer.cpp b/tools/k4aviewer/k4aviewer.cpp index a8697fb7..b12baf83 100644 --- a/tools/k4aviewer/k4aviewer.cpp +++ b/tools/k4aviewer/k4aviewer.cpp @@ -17,6 +17,7 @@ // Project headers // #include "k4aaudiomanager.h" +#include "k4alogdockcontrol.h" #include "k4asourceselectiondockcontrol.h" #include "k4aviewererrormanager.h" #include "k4aviewerutil.h" @@ -145,6 +146,7 @@ K4AViewer::K4AViewer(const K4AViewerOptions &args) } K4AWindowManager::Instance().PushLeftDockControl(std14::make_unique()); + K4AWindowManager::Instance().PushBottomDockControl(std14::make_unique()); } K4AViewer::~K4AViewer() diff --git a/tools/k4aviewer/k4aviewerlogmanager.cpp b/tools/k4aviewer/k4aviewerlogmanager.cpp new file mode 100644 index 00000000..7da6fde3 --- /dev/null +++ b/tools/k4aviewer/k4aviewerlogmanager.cpp @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Associated header +// +#include "k4aviewerlogmanager.h" + +// System headers +// +#include +#include + +// Library headers +// + +// Project headers +// + +using namespace k4aviewer; + +K4AViewerLogManager &K4AViewerLogManager::Instance() +{ + static K4AViewerLogManager instance; + return instance; +} + +K4AViewerLogManager::K4AViewerLogManager() +{ + if (k4a_set_debug_message_handler(&LoggerCallback, this, K4A_LOG_LEVEL_TRACE) != K4A_RESULT_SUCCEEDED) + { + Log(K4A_LOG_LEVEL_ERROR, __FILE__, __LINE__, "Failed to initialize K4A logging!"); + } +} + +K4AViewerLogManager::~K4AViewerLogManager() +{ + (void)k4a_set_debug_message_handler(nullptr, nullptr, K4A_LOG_LEVEL_TRACE); +} + +void K4AViewerLogManager::Log(k4a_log_level_t severity, const char *file, int line, const char *msg) +{ + std::lock_guard lock(m_mutex); + for (auto wpListener = m_listeners.begin(); wpListener != m_listeners.end();) + { + std::shared_ptr spListener = wpListener->lock(); + if (spListener) + { + spListener->Log(severity, file, line, msg); + ++wpListener; + } + else + { + auto toDelete = wpListener; + ++wpListener; + m_listeners.erase(toDelete); + } + } +} + +void K4AViewerLogManager::RegisterListener(std::shared_ptr listener) +{ + std::lock_guard lock(m_mutex); + m_listeners.emplace_back(std::move(listener)); +} + +void K4AViewerLogManager::LoggerCallback(void *context, + k4a_log_level_t level, + const char *file, + int line, + const char *msg) +{ + K4AViewerLogManager *instance = reinterpret_cast(context); + instance->Log(level, file, line, msg); +} diff --git a/tools/k4aviewer/k4aviewerlogmanager.h b/tools/k4aviewer/k4aviewerlogmanager.h new file mode 100644 index 00000000..445f120e --- /dev/null +++ b/tools/k4aviewer/k4aviewerlogmanager.h @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#ifndef K4AVIEWERLOGMANAGER_H +#define K4AVIEWERLOGMANAGER_H + +// System headers +// +#include +#include +#include +#include + +// Library headers +// +#include + +// Project headers +// + +namespace k4aviewer +{ +class IK4AViewerLogListener +{ +public: + virtual void Log(k4a_log_level_t severity, const char *file, int line, const char *msg) = 0; + virtual ~IK4AViewerLogListener() = default; +}; + +class K4AViewerLogManager +{ +public: + static K4AViewerLogManager &Instance(); + + void Log(k4a_log_level_t severity, const char *file, int line, const char *msg); + + void RegisterListener(std::shared_ptr listener); + +private: + K4AViewerLogManager(); + ~K4AViewerLogManager(); + + static void LoggerCallback(void *context, k4a_log_level_t level, const char *file, int line, const char *msg); + + std::mutex m_mutex; + std::list> m_listeners; +}; + +} // namespace k4aviewer + +#endif diff --git a/tools/k4aviewer/k4awindowdock.h b/tools/k4aviewer/k4awindowdock.h index 2d3cce2e..9827b9a1 100644 --- a/tools/k4aviewer/k4awindowdock.h +++ b/tools/k4aviewer/k4awindowdock.h @@ -61,7 +61,7 @@ private: ImVec2 m_regionSize = ImVec2(0.f, 0.f); // The actual size/location of the dock window, in absolute window coordinates. - // Must be within by m_viewRegion* + // Must be within by m_region* // ImVec2 m_size = ImVec2(0.f, 0.f);