diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index b31d224ab..a25a7d8b1 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -41,6 +41,8 @@ add_library(common
null_audio_stream.cpp
null_audio_stream.h
rectangle.h
+ progress_callback.cpp
+ progress_callback.h
state_wrapper.cpp
state_wrapper.h
string.cpp
diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj
index 8a993f302..9e5559329 100644
--- a/src/common/common.vcxproj
+++ b/src/common/common.vcxproj
@@ -60,6 +60,7 @@
+
@@ -94,6 +95,7 @@
+
diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters
index 84877fa9d..450591a77 100644
--- a/src/common/common.vcxproj.filters
+++ b/src/common/common.vcxproj.filters
@@ -51,6 +51,7 @@
d3d11
+
@@ -98,6 +99,7 @@
d3d11
+
diff --git a/src/common/progress_callback.cpp b/src/common/progress_callback.cpp
new file mode 100644
index 000000000..55f334321
--- /dev/null
+++ b/src/common/progress_callback.cpp
@@ -0,0 +1,397 @@
+#include "progress_callback.h"
+#include "assert.h"
+#include "byte_stream.h"
+#include "log.h"
+#include
+#include
+#include
+Log_SetChannel(ProgressCallback);
+
+ProgressCallback::~ProgressCallback() {}
+
+void ProgressCallback::SetFormattedStatusText(const char* Format, ...)
+{
+ SmallString str;
+ va_list ap;
+
+ va_start(ap, Format);
+ str.FormatVA(Format, ap);
+ va_end(ap);
+
+ SetStatusText(str);
+}
+
+void ProgressCallback::DisplayFormattedError(const char* format, ...)
+{
+ SmallString str;
+ va_list ap;
+
+ va_start(ap, format);
+ str.FormatVA(format, ap);
+ va_end(ap);
+
+ DisplayError(str);
+}
+
+void ProgressCallback::DisplayFormattedWarning(const char* format, ...)
+{
+ SmallString str;
+ va_list ap;
+
+ va_start(ap, format);
+ str.FormatVA(format, ap);
+ va_end(ap);
+
+ DisplayWarning(str);
+}
+
+void ProgressCallback::DisplayFormattedInformation(const char* format, ...)
+{
+ SmallString str;
+ va_list ap;
+
+ va_start(ap, format);
+ str.FormatVA(format, ap);
+ va_end(ap);
+
+ DisplayInformation(str);
+}
+
+void ProgressCallback::DisplayFormattedDebugMessage(const char* format, ...)
+{
+ SmallString str;
+ va_list ap;
+
+ va_start(ap, format);
+ str.FormatVA(format, ap);
+ va_end(ap);
+
+ DisplayDebugMessage(str);
+}
+
+void ProgressCallback::DisplayFormattedModalError(const char* format, ...)
+{
+ SmallString str;
+ va_list ap;
+
+ va_start(ap, format);
+ str.FormatVA(format, ap);
+ va_end(ap);
+
+ ModalError(str);
+}
+
+bool ProgressCallback::DisplayFormattedModalConfirmation(const char* format, ...)
+{
+ SmallString str;
+ va_list ap;
+
+ va_start(ap, format);
+ str.FormatVA(format, ap);
+ va_end(ap);
+
+ return ModalConfirmation(str);
+}
+
+void ProgressCallback::UpdateProgressFromStream(ByteStream* pStream)
+{
+ u32 streamSize = (u32)pStream->GetSize();
+ u32 streamPosition = (u32)pStream->GetPosition();
+
+ SetProgressRange(streamSize);
+ SetProgressValue(streamPosition);
+}
+
+class NullProgressCallbacks final : public ProgressCallback
+{
+public:
+ void PushState() override {}
+ void PopState() override {}
+
+ bool IsCancelled() const override { return false; }
+ bool IsCancellable() const override { return false; }
+
+ void SetCancellable(bool cancellable) override {}
+ void SetStatusText(const char* statusText) override {}
+ void SetProgressRange(u32 range) override {}
+ void SetProgressValue(u32 value) override {}
+ void IncrementProgressValue() override {}
+
+ void DisplayError(const char* message) override { Log_ErrorPrint(message); }
+ void DisplayWarning(const char* message) override { Log_WarningPrint(message); }
+ void DisplayInformation(const char* message) override { Log_InfoPrint(message); }
+ void DisplayDebugMessage(const char* message) override { Log_DevPrint(message); }
+
+ void ModalError(const char* message) override { Log_ErrorPrint(message); }
+ bool ModalConfirmation(const char* message) override
+ {
+ Log_InfoPrint(message);
+ return false;
+ }
+ u32 ModalPrompt(const char* message, u32 nOptions, ...) override
+ {
+ DebugAssert(nOptions > 0);
+ Log_InfoPrint(message);
+ return 0;
+ }
+};
+
+static NullProgressCallbacks s_nullProgressCallbacks;
+ProgressCallback* ProgressCallback::NullProgressCallback = &s_nullProgressCallbacks;
+
+BaseProgressCallback::BaseProgressCallback()
+ : m_cancellable(false), m_cancelled(false), m_progress_range(1), m_progress_value(0), m_base_progress_value(0),
+ m_saved_state(NULL)
+{
+}
+
+BaseProgressCallback::~BaseProgressCallback()
+{
+ State* pNextState = m_saved_state;
+ while (pNextState != NULL)
+ {
+ State* pCurrentState = pNextState;
+ pNextState = pCurrentState->next_saved_state;
+ delete pCurrentState;
+ }
+}
+
+void BaseProgressCallback::PushState()
+{
+ State* pNewState = new State;
+ pNewState->cancellable = m_cancellable;
+ pNewState->status_text = m_status_text;
+ pNewState->progress_range = m_progress_range;
+ pNewState->progress_value = m_progress_value;
+ pNewState->base_progress_value = m_base_progress_value;
+ pNewState->next_saved_state = m_saved_state;
+ m_saved_state = pNewState;
+}
+
+void BaseProgressCallback::PopState()
+{
+ DebugAssert(m_saved_state);
+ State* state = m_saved_state;
+ m_saved_state = nullptr;
+
+ // impose the current position into the previous range
+ const u32 new_progress_value =
+ (m_progress_range != 0) ?
+ static_cast(((float)m_progress_value / (float)m_progress_range) * (float)state->progress_range) :
+ state->progress_value;
+
+ SetCancellable(state->cancellable);
+ SetStatusText(state->status_text);
+ SetProgressRange(state->progress_range);
+ SetProgressValue(new_progress_value);
+
+ m_base_progress_value = state->base_progress_value;
+ m_saved_state = state->next_saved_state;
+ delete state;
+}
+
+bool BaseProgressCallback::IsCancelled() const
+{
+ return m_cancelled;
+}
+
+bool BaseProgressCallback::IsCancellable() const
+{
+ return m_cancellable;
+}
+
+void BaseProgressCallback::SetCancellable(bool cancellable)
+{
+ m_cancellable = cancellable;
+}
+
+void BaseProgressCallback::SetStatusText(const char* text)
+{
+ m_status_text = text;
+}
+
+void BaseProgressCallback::SetProgressRange(u32 range)
+{
+ if (m_saved_state)
+ {
+ // impose the previous range on this range
+ m_progress_range = m_saved_state->progress_range * range;
+ m_base_progress_value = m_progress_value = m_saved_state->progress_value * range;
+ }
+ else
+ {
+ m_progress_range = range;
+ m_progress_value = 0;
+ m_base_progress_value = 0;
+ }
+}
+
+void BaseProgressCallback::SetProgressValue(u32 value)
+{
+ m_progress_value = m_base_progress_value + value;
+}
+
+void BaseProgressCallback::IncrementProgressValue()
+{
+ SetProgressValue((m_progress_value - m_base_progress_value) + 1);
+}
+
+ConsoleProgressCallback::ConsoleProgressCallback()
+ : BaseProgressCallback(), m_last_percent_complete(std::numeric_limits::infinity()),
+ m_last_bar_length(0xFFFFFFFF)
+{
+}
+
+ConsoleProgressCallback::~ConsoleProgressCallback()
+{
+ Clear();
+}
+
+void ConsoleProgressCallback::PushState()
+{
+ BaseProgressCallback::PushState();
+}
+
+void ConsoleProgressCallback::PopState()
+{
+ BaseProgressCallback::PopState();
+ Redraw(false);
+}
+
+void ConsoleProgressCallback::SetCancellable(bool cancellable)
+{
+ BaseProgressCallback::SetCancellable(cancellable);
+ Redraw(false);
+}
+
+void ConsoleProgressCallback::SetStatusText(const char* text)
+{
+ BaseProgressCallback::SetStatusText(text);
+ Redraw(false);
+}
+
+void ConsoleProgressCallback::SetProgressRange(u32 range)
+{
+ u32 last_range = m_progress_range;
+
+ BaseProgressCallback::SetProgressRange(range);
+
+ if (m_progress_range != last_range)
+ Redraw(false);
+}
+
+void ConsoleProgressCallback::SetProgressValue(u32 value)
+{
+ u32 lastValue = m_progress_value;
+
+ BaseProgressCallback::SetProgressValue(value);
+
+ if (m_progress_value != lastValue)
+ Redraw(true);
+}
+
+void ConsoleProgressCallback::Clear()
+{
+ SmallString message;
+ for (u32 i = 0; i < COLUMNS; i++)
+ message.AppendCharacter(' ');
+ message.AppendCharacter('\r');
+
+ std::fwrite(message.GetCharArray(), message.GetLength(), 1, stderr);
+ std::fflush(stderr);
+}
+
+void ConsoleProgressCallback::Redraw(bool update_value_only)
+{
+ float percent_complete = (m_progress_range > 0) ? ((float)m_progress_value / (float)m_progress_range) * 100.0f : 0.0f;
+ if (percent_complete > 100.0f)
+ percent_complete = 100.0f;
+
+ const u32 current_length = m_status_text.GetLength() + 14;
+ const u32 max_bar_length = (current_length < COLUMNS) ? COLUMNS - current_length : 0;
+ const u32 current_bar_length =
+ (max_bar_length > 0) ? (static_cast(percent_complete / 100.0f * (float)max_bar_length)) : 0;
+
+ if (update_value_only && (current_bar_length == m_last_bar_length) &&
+ std::abs(percent_complete - m_last_percent_complete) < 0.01f)
+ {
+ return;
+ }
+
+ m_last_bar_length = current_bar_length;
+ m_last_percent_complete = percent_complete;
+
+ SmallString message;
+ message.AppendString(m_status_text);
+ message.AppendFormattedString(" [%.2f%%]", percent_complete);
+
+ if (max_bar_length > 0)
+ {
+ message.AppendString(" |");
+
+ u32 i;
+ for (i = 0; i < current_bar_length; i++)
+ message.AppendCharacter('=');
+ for (; i < max_bar_length; i++)
+ message.AppendCharacter(' ');
+
+ message.AppendString("|");
+ }
+
+ message.AppendCharacter('\r');
+
+ std::fwrite(message.GetCharArray(), message.GetLength(), 1, stderr);
+ std::fflush(stderr);
+}
+
+void ConsoleProgressCallback::DisplayError(const char* message)
+{
+ Clear();
+ Log_ErrorPrint(message);
+ Redraw(false);
+}
+
+void ConsoleProgressCallback::DisplayWarning(const char* message)
+{
+ Clear();
+ Log_WarningPrint(message);
+ Redraw(false);
+}
+
+void ConsoleProgressCallback::DisplayInformation(const char* message)
+{
+ Clear();
+ Log_InfoPrint(message);
+ Redraw(false);
+}
+
+void ConsoleProgressCallback::DisplayDebugMessage(const char* message)
+{
+ Clear();
+ Log_DevPrint(message);
+ Redraw(false);
+}
+
+void ConsoleProgressCallback::ModalError(const char* message)
+{
+ Clear();
+ Log_ErrorPrint(message);
+ Redraw(false);
+}
+
+bool ConsoleProgressCallback::ModalConfirmation(const char* message)
+{
+ Clear();
+ Log_InfoPrint(message);
+ Redraw(false);
+ return false;
+}
+
+u32 ConsoleProgressCallback::ModalPrompt(const char* message, u32 num_options, ...)
+{
+ Clear();
+ DebugAssert(num_options > 0);
+ Log_InfoPrint(message);
+ Redraw(false);
+ return 0;
+}
diff --git a/src/common/progress_callback.h b/src/common/progress_callback.h
new file mode 100644
index 000000000..a6436e47d
--- /dev/null
+++ b/src/common/progress_callback.h
@@ -0,0 +1,121 @@
+#pragma once
+#include "string.h"
+#include "types.h"
+
+class ByteStream;
+
+class ProgressCallback
+{
+public:
+ virtual ~ProgressCallback();
+
+ virtual void PushState() = 0;
+ virtual void PopState() = 0;
+
+ virtual bool IsCancelled() const = 0;
+ virtual bool IsCancellable() const = 0;
+
+ virtual void SetCancellable(bool cancellable) = 0;
+
+ virtual void SetStatusText(const char* text) = 0;
+ virtual void SetProgressRange(u32 range) = 0;
+ virtual void SetProgressValue(u32 value) = 0;
+ virtual void IncrementProgressValue() = 0;
+
+ void SetFormattedStatusText(const char* Format, ...);
+
+ virtual void DisplayError(const char* message) = 0;
+ virtual void DisplayWarning(const char* message) = 0;
+ virtual void DisplayInformation(const char* message) = 0;
+ virtual void DisplayDebugMessage(const char* message) = 0;
+
+ virtual void ModalError(const char* message) = 0;
+ virtual bool ModalConfirmation(const char* message) = 0;
+ virtual u32 ModalPrompt(const char* message, u32 num_options, ...) = 0;
+
+ void DisplayFormattedError(const char* format, ...);
+ void DisplayFormattedWarning(const char* format, ...);
+ void DisplayFormattedInformation(const char* format, ...);
+ void DisplayFormattedDebugMessage(const char* format, ...);
+ void DisplayFormattedModalError(const char* format, ...);
+ bool DisplayFormattedModalConfirmation(const char* format, ...);
+
+ void UpdateProgressFromStream(ByteStream* stream);
+
+public:
+ static ProgressCallback* NullProgressCallback;
+};
+
+class BaseProgressCallback : public ProgressCallback
+{
+public:
+ BaseProgressCallback();
+ virtual ~BaseProgressCallback();
+
+ virtual void PushState() override;
+ virtual void PopState() override;
+
+ virtual bool IsCancelled() const override;
+ virtual bool IsCancellable() const override;
+
+ virtual void SetCancellable(bool cancellable) override;
+ virtual void SetStatusText(const char* text) override;
+ virtual void SetProgressRange(u32 range) override;
+ virtual void SetProgressValue(u32 value) override;
+ virtual void IncrementProgressValue() override;
+
+protected:
+ struct State
+ {
+ State* next_saved_state;
+ String status_text;
+ u32 progress_range;
+ u32 progress_value;
+ u32 base_progress_value;
+ bool cancellable;
+ };
+
+ bool m_cancellable;
+ bool m_cancelled;
+ String m_status_text;
+ u32 m_progress_range;
+ u32 m_progress_value;
+
+ u32 m_base_progress_value;
+
+ State* m_saved_state;
+};
+
+class ConsoleProgressCallback : public BaseProgressCallback
+{
+public:
+ static const u32 COLUMNS = 78;
+
+public:
+ ConsoleProgressCallback();
+ ~ConsoleProgressCallback();
+
+ virtual void PushState() override;
+ virtual void PopState() override;
+
+ virtual void SetCancellable(bool cancellable) override;
+ virtual void SetStatusText(const char* text) override;
+ virtual void SetProgressRange(u32 range) override;
+ virtual void SetProgressValue(u32 value) override;
+
+ virtual void DisplayError(const char* message) override;
+ virtual void DisplayWarning(const char* message) override;
+ virtual void DisplayInformation(const char* message) override;
+ virtual void DisplayDebugMessage(const char* message) override;
+
+ virtual void ModalError(const char* message) override;
+ virtual bool ModalConfirmation(const char* message) override;
+ virtual u32 ModalPrompt(const char* message, u32 num_options, ...) override;
+
+private:
+ void Clear();
+ void Redraw(bool update_value_only);
+
+ float m_last_percent_complete;
+ u32 m_last_bar_length;
+};