Redesign DialogProgress

Add agi::ProgressSink and agi::BackgroundRunner interfaces to libaegisub
which represent a generic progress sink and a thing which calls funtions
that need progress sinks. Make DialogProgress implement
agi::BackgroundRunner, invoking the passed function on a worker thread
and giving it a progress sink to update the dialog with.

Rewrite Automation4::ProgressSink, LuaThreadedCall and all related
classes to be based on agi::ProgressSink. Automation now simply uses
DialogProgress (although that's merely an implementation detail) and
adds a single method to route dialog opening from the worker thread to
the GUI thread.

Originally committed to SVN as r5634.
This commit is contained in:
Thomas Goyne 2011-09-28 19:47:40 +00:00
parent 5439c6dae6
commit 53b6765dd8
20 changed files with 636 additions and 960 deletions

View File

@ -397,6 +397,10 @@
RelativePath="..\..\libaegisub\include\libaegisub\access.h"
>
</File>
<File
RelativePath="..\..\libaegisub\include\libaegisub\background_runner.h"
>
</File>
<File
RelativePath="..\..\libaegisub\include\libaegisub\charset.h"
>

View File

@ -0,0 +1,87 @@
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// $Id$
/// @file background_runner.h
/// @brief Background runner and progress sink interfaces
/// @ingroup libaegisub
#pragma once
#ifndef LAGI_PRE
#include <string>
#ifdef _WIN32
#include <functional>
#else
#include <tr1/functional>
#endif
#endif
namespace agi {
/// @class ProgressSink
/// @brief A receiver for progress updates sent from a worker function
///
/// Note that ProgressSinks are not required to be thread-safe. The only
/// guarantee provided is that they can be used on the thread they are
/// spawned on (which may or may not be the GUI thread).
class ProgressSink {
public:
/// Virtual destructor so that things can safely inherit from this
virtual ~ProgressSink() { }
/// Set the progress to indeterminate
virtual void SetIndeterminate()=0;
/// Set the title of the running task
virtual void SetTitle(std::string const& title)=0;
/// Set an additional message associated with the task
virtual void SetMessage(std::string const& msg)=0;
/// Set the current task progress
virtual void SetProgress(int cur, int max)=0;
/// @brief Log a message
///
/// If any messages are logged then the dialog will not automatically close
/// when the task finishes so that the user has the chance to read them.
virtual void Log(std::string const& str)=0;
/// Has the user asked the task to cancel?
virtual bool IsCancelled()=0;
};
/// @class BackgroundRunner
/// @brief A class which runs a function, providing it with a progress sink
///
/// Normally implementations of this interface will spawn a new thread to
/// run the task on, but there are sensible implementations that may not.
/// For example, an implementation which has no UI and simply writes the
/// log output to a file would simply run on the main thread.
class BackgroundRunner {
public:
/// Virtual destructor so that things can safely inherit from this
virtual ~BackgroundRunner() { }
/// @brief Run a function on a background thread
/// @param task Function to run
/// @param priority Thread priority or -1 for default
/// @throws agi::UserCancelException on cancel
///
/// Blocks the calling thread until the task completes or is canceled.
/// Progress updates sent to the progress sink passed to the task should
/// be displayed to the user in some way, along with some way for the
/// user to cancel the task.
virtual void Run(std::tr1::function<void(ProgressSink *)> task, int priority=-1)=0;
};
}

View File

@ -49,9 +49,9 @@
#include "standard_paths.h"
#include "utils.h"
/// @brief Constructor
/// @param source
///
HDAudioProvider::HDAudioProvider(AudioProvider *src) {
std::auto_ptr<AudioProvider> source(src);
// Copy parameters
@ -76,44 +76,34 @@ HDAudioProvider::HDAudioProvider(AudioProvider *src) {
file_cache.Open(diskCacheFilename,wxFile::read_write);
if (!file_cache.IsOpened()) throw AudioOpenError("Unable to write to audio disk cache.");
// Start progress
volatile bool canceled = false;
DialogProgress *progress = new DialogProgress(AegisubApp::Get()->frame,"Load audio",&canceled,"Reading to Hard Disk cache",0,num_samples);
progress->Show();
// Write to disk
int block = 4096;
data = new char[block * channels * bytes_per_sample];
for (int64_t i=0;i<num_samples && !canceled; i+=block) {
if (block+i > num_samples) block = num_samples - i;
source->GetAudio(data,i,block);
file_cache.Write(data,block * channels * bytes_per_sample);
progress->SetProgress(i,num_samples);
}
file_cache.Seek(0);
// Finish
if (canceled) {
file_cache.Close();
delete[] data;
throw agi::UserCancelException("Audio loading cancelled by user");
}
progress->Destroy();
DialogProgress progress(AegisubApp::Get()->frame, "Load audio", "Reading to Hard Disk cache");
progress.Run(bind(&HDAudioProvider::FillCache, this, src, std::tr1::placeholders::_1));
}
/// @brief Destructor
///
HDAudioProvider::~HDAudioProvider() {
file_cache.Close();
wxRemoveFile(diskCacheFilename);
delete[] data;
}
/// @brief Get audio
/// @param buf
/// @param start
/// @param count
///
void HDAudioProvider::FillCache(AudioProvider *src, agi::ProgressSink *ps) {
int64_t block = 4096;
data = new char[block * channels * bytes_per_sample];
for (int64_t i = 0; i < num_samples; i += block) {
block = std::min(block, num_samples - i);
src->GetAudio(data, i, block);
file_cache.Write(data, block * channels * bytes_per_sample);
ps->SetProgress(i, num_samples);
if (ps->IsCancelled()) {
file_cache.Close();
wxRemoveFile(diskCacheFilename);
delete[] data;
}
}
file_cache.Seek(0);
}
void HDAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
// Requested beyond the length of audio
if (start+count > num_samples) {

View File

@ -41,6 +41,8 @@
#include "include/aegisub/audio_provider.h"
namespace agi { class ProgressSink; }
/// DOCME
/// @class HDAudioProvider
/// @brief DOCME
@ -65,6 +67,8 @@ class HDAudioProvider : public AudioProvider {
static wxString DiskCachePath();
static wxString DiskCacheName();
void FillCache(AudioProvider *src, agi::ProgressSink *ps);
public:
HDAudioProvider(AudioProvider *source);
~HDAudioProvider();

View File

@ -42,38 +42,28 @@
#include "main.h"
#include "utils.h"
/// DOCME
#define CacheBits ((22))
/// DOCME
#define CacheBlockSize ((1 << CacheBits))
/// @brief Constructor
/// @param source
///
RAMAudioProvider::RAMAudioProvider(AudioProvider *src) {
std::auto_ptr<AudioProvider> source(src);
// Init
blockcache = NULL;
blockcount = 0;
samples_native_endian = source->AreSamplesNativeEndian();
// Allocate cache
int64_t ssize = source->GetNumSamples() * source->GetBytesPerSample();
blockcount = (ssize + CacheBlockSize - 1) >> CacheBits;
blockcache = new char*[blockcount];
for (int i = 0; i < blockcount; i++) {
blockcache[i] = NULL;
}
memset(blockcache, blockcount * sizeof(char*), 0);
// Allocate cache blocks
try {
for (int i = 0; i < blockcount; i++) {
blockcache[i] = new char[std::min<size_t>(CacheBlockSize,ssize-i*CacheBlockSize)];
blockcache[i] = new char[std::min<size_t>(CacheBlockSize, ssize - i * CacheBlockSize)];
}
}
catch (...) {
catch (std::bad_alloc const&) {
Clear();
throw AudioOpenError("Couldn't open audio, not enough ram available.");
}
@ -85,35 +75,27 @@ RAMAudioProvider::RAMAudioProvider(AudioProvider *src) {
sample_rate = source->GetSampleRate();
filename = source->GetFilename();
// Start progress
volatile bool canceled = false;
DialogProgress *progress = new DialogProgress(AegisubApp::Get()->frame,_("Load audio"),&canceled,_("Reading into RAM"),0,source->GetNumSamples());
progress->Show();
progress->SetProgress(0,1);
// Read cache
int readsize = CacheBlockSize / source->GetBytesPerSample();
for (int i=0;i<blockcount && !canceled; i++) {
source->GetAudio((char*)blockcache[i],i*readsize, i == blockcount-1 ? (source->GetNumSamples() - i*readsize) : readsize);
progress->SetProgress(i,blockcount-1);
}
// Clean up progress
if (canceled) {
Clear();
throw agi::UserCancelException("Audio loading cancelled by user");
}
progress->Destroy();
DialogProgress progress(AegisubApp::Get()->frame, _("Load audio"), _("Reading into RAM"));
progress.Run(std::tr1::bind(&RAMAudioProvider::FillCache, this, src, std::tr1::placeholders::_1));
}
/// @brief Destructor
///
RAMAudioProvider::~RAMAudioProvider() {
Clear();
}
/// @brief Clear
///
void RAMAudioProvider::FillCache(AudioProvider *source, agi::ProgressSink *ps) {
int64_t readsize = CacheBlockSize / source->GetBytesPerSample();
for (int i = 0; i < blockcount; i++) {
source->GetAudio((char*)blockcache[i], i * readsize, std::min(readsize, num_samples - i * readsize));
ps->SetProgress(i, (blockcount - 1));
if (ps->IsCancelled()) {
Clear();
return;
}
}
}
void RAMAudioProvider::Clear() {
// Free ram cache
if (blockcache) {
@ -122,15 +104,8 @@ void RAMAudioProvider::Clear() {
}
delete [] blockcache;
}
blockcache = NULL;
blockcount = 0;
}
/// @brief Get audio
/// @param buf
/// @param start
/// @param count
///
void RAMAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
// Requested beyond the length of audio
if (start+count > num_samples) {

View File

@ -36,6 +36,8 @@
#include "include/aegisub/audio_provider.h"
namespace agi { class ProgressSink; }
/// DOCME
/// @class RAMAudioProvider
/// @brief DOCME
@ -52,6 +54,7 @@ class RAMAudioProvider : public AudioProvider {
bool samples_native_endian;
void Clear();
void FillCache(AudioProvider *source, agi::ProgressSink *ps);
public:
RAMAudioProvider(AudioProvider *source);

View File

@ -62,6 +62,7 @@
#include "ass_style.h"
#include "auto4_base.h"
#include "compat.h"
#include "dialog_progress.h"
#include "include/aegisub/context.h"
#include "main.h"
#include "standard_paths.h"
@ -292,7 +293,7 @@ namespace Automation4 {
FeatureFilter::FeatureFilter(const wxString &_name, const wxString &_description, int _priority)
: Feature(SCRIPTFEATURE_FILTER, _name)
, AssExportFilter(_name, _description, _priority)
, config_dialog(0)
, config_dialog(0)
{
AssExportFilterChain::Register(this);
}
@ -311,7 +312,7 @@ namespace Automation4 {
///
wxString FeatureFilter::GetScriptSettingsIdentifier()
{
return inline_string_encode(wxString::Format("Automation Settings %s", GetName()));
return inline_string_encode(wxString::Format("Automation Settings %s", AssExportFilter::GetName()));
}
@ -395,14 +396,6 @@ namespace Automation4 {
return !filename.Right(extension.Length()).CmpNoCase(extension);
}
// ShowConfigDialogEvent
/// DOCME
const wxEventType EVT_SHOW_CONFIG_DIALOG_t = wxNewEventType();
// ScriptConfigDialog
@ -436,220 +429,72 @@ namespace Automation4 {
// ProgressSink
wxDEFINE_EVENT(EVT_SHOW_CONFIG_DIALOG, wxThreadEvent);
/// @brief DOCME
/// @param parent
///
ProgressSink::ProgressSink(wxWindow *parent)
: wxDialog(parent, -1, "Automation", wxDefaultPosition, wxDefaultSize, wxBORDER_RAISED)
, debug_visible(false)
, data_updated(false)
, cancelled(false)
, has_inited(false)
, script_finished(false)
ProgressSink::ProgressSink(agi::ProgressSink *impl, BackgroundScriptRunner *bsr)
: impl(impl)
, bsr(bsr)
, trace_level(OPT_GET("Automation/Trace Level")->GetInt())
{
// make the controls
progress_display = new wxGauge(this, -1, 1000, wxDefaultPosition, wxSize(300, 20));
title_display = new wxStaticText(this, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE|wxST_NO_AUTORESIZE);
task_display = new wxStaticText(this, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE|wxST_NO_AUTORESIZE);
cancel_button = new wxButton(this, wxID_CANCEL);
debug_output = new wxTextCtrl(this, -1, "", wxDefaultPosition, wxSize(300, 120), wxTE_MULTILINE|wxTE_READONLY);
// put it in a sizer
sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(title_display, 0, wxEXPAND | wxALL, 5);
sizer->Add(progress_display, 0, wxALL&~wxTOP, 5);
sizer->Add(task_display, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
sizer->Add(cancel_button, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT | wxBOTTOM, 5);
sizer->Add(debug_output, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
sizer->Show(debug_output, false);
// make the title a slightly larger font
wxFont title_font = title_display->GetFont();
int fontsize = title_font.GetPointSize();
title_font.SetPointSize(fontsize + fontsize/4 + fontsize/8);
title_font.SetWeight(wxFONTWEIGHT_BOLD);
title_display->SetFont(title_font);
// Set up a timer to regularly update the status
// It doesn't need an event handler attached, as just a the timer in itself
// will ensure that the idle event is fired
update_timer = new wxTimer();
update_timer->Start(50, false);
sizer->SetSizeHints(this);
SetSizer(sizer);
Center();
// Init trace level
trace_level = OPT_GET("Automation/Trace Level")->GetInt();
}
/// @brief DOCME
///
ProgressSink::~ProgressSink()
void ProgressSink::ShowConfigDialog(ScriptConfigDialog *config_dialog)
{
delete update_timer;
wxSemaphore sema(0, 1);
wxThreadEvent *evt = new wxThreadEvent(EVT_SHOW_CONFIG_DIALOG);
evt->SetPayload(std::make_pair(config_dialog, &sema));
bsr->QueueEvent(evt);
sema.Wait();
}
/// @brief DOCME
/// @param evt
///
void ProgressSink::OnIdle(wxIdleEvent &evt)
BackgroundScriptRunner::BackgroundScriptRunner(wxWindow *parent, wxString const& title)
: impl(new DialogProgress(parent, title))
{
// The big glossy "update display" event
DoUpdateDisplay();
if (script_finished) {
if (!debug_visible) {
EndModal(0);
} else {
cancel_button->Enable(true);
cancel_button->SetLabel(_("Close"));
SetProgress(100.0);
SetTask(_("Script completed"));
}
}
impl->Bind(EVT_SHOW_CONFIG_DIALOG, &BackgroundScriptRunner::OnConfigDialog, this);
}
/// @brief DOCME
/// @return
///
void ProgressSink::DoUpdateDisplay()
BackgroundScriptRunner::~BackgroundScriptRunner()
{
// If debug output isn't handled before the test for script_finished later,
// there might actually be some debug output but the debug_visible flag won't
// be set before the dialog closes itself.
wxMutexLocker lock(data_mutex);
if (!data_updated) return;
if (!pending_debug_output.IsEmpty()) {
if (!debug_visible) {
sizer->Show(debug_output, true);
Layout();
sizer->Fit(this);
debug_visible = true;
}
*debug_output << pending_debug_output;
debug_output->SetInsertionPointEnd();
pending_debug_output = "";
}
progress_display->SetValue((int)(progress*10));
task_display->SetLabel(task);
title_display->SetLabel(title);
data_updated = false;
}
/// @brief DOCME
/// @param _progress
///
void ProgressSink::SetProgress(float _progress)
void BackgroundScriptRunner::OnConfigDialog(wxThreadEvent &evt)
{
wxMutexLocker lock(data_mutex);
progress = _progress;
data_updated = true;
std::pair<ScriptConfigDialog*, wxSemaphore*> payload = evt.GetPayload<std::pair<ScriptConfigDialog*, wxSemaphore*> >();
wxDialog w(impl.get(), -1, impl->GetTitle()); // container dialog box
wxBoxSizer *s = new wxBoxSizer(wxHORIZONTAL); // sizer for putting contents in
wxWindow *ww = payload.first->GetWindow(&w); // get/generate actual dialog contents
s->Add(ww, 0, wxALL, 5); // add contents to dialog
w.SetSizerAndFit(s);
w.CenterOnParent();
w.ShowModal();
payload.first->ReadBack();
payload.first->DeleteWindow();
payload.second->Post();
}
/// @brief DOCME
/// @param _task
///
void ProgressSink::SetTask(const wxString &_task)
{
wxMutexLocker lock(data_mutex);
task = _task;
data_updated = true;
void BackgroundScriptRunner::QueueEvent(wxEvent *evt) {
wxQueueEvent(impl.get(), evt);
}
/// @brief DOCME
/// @param _title
///
void ProgressSink::SetTitle(const wxString &_title)
// Convert a function taking an Automation4::ProgressSink to one taking an
// agi::ProgressSink so that we can pass it to an agi::BackgroundWorker
static void progress_sink_wrapper(std::tr1::function<void (ProgressSink*)> task, agi::ProgressSink *ps, BackgroundScriptRunner *bsr)
{
wxMutexLocker lock(data_mutex);
title = _title;
data_updated = true;
ProgressSink aps(ps, bsr);
task(&aps);
}
/// @brief DOCME
/// @param msg
///
void ProgressSink::AddDebugOutput(const wxString &msg)
void BackgroundScriptRunner::Run(std::tr1::function<void (ProgressSink*)> task)
{
wxMutexLocker lock(data_mutex);
pending_debug_output << msg;
data_updated = true;
}
int prio = OPT_GET("Automation/Thread Priority")->GetInt();
if (prio == 0) prio = 50; // normal
else if (prio == 1) prio = 30; // below normal
else if (prio == 2) prio = 10; // lowest
else prio = 50; // fallback normal
BEGIN_EVENT_TABLE(ProgressSink, wxWindow)
EVT_INIT_DIALOG(ProgressSink::OnInit)
EVT_BUTTON(wxID_CANCEL, ProgressSink::OnCancel)
EVT_IDLE(ProgressSink::OnIdle)
EVT_SHOW_CONFIG_DIALOG(ProgressSink::OnConfigDialog)
END_EVENT_TABLE()
/// @brief DOCME
/// @param evt
///
void ProgressSink::OnInit(wxInitDialogEvent &evt)
{
has_inited = true;
}
/// @brief DOCME
/// @param evt
///
void ProgressSink::OnCancel(wxCommandEvent &evt)
{
if (!script_finished) {
cancelled = true;
cancel_button->Enable(false);
} else {
EndModal(0);
}
}
/// @brief DOCME
/// @param evt
///
void ProgressSink::OnConfigDialog(ShowConfigDialogEvent &evt)
{
// assume we're in the GUI thread here
DoUpdateDisplay();
if (evt.config_dialog) {
wxDialog *w = new wxDialog(this, -1, title); // container dialog box
wxBoxSizer *s = new wxBoxSizer(wxHORIZONTAL); // sizer for putting contents in
wxWindow *ww = evt.config_dialog->GetWindow(w); // get/generate actual dialog contents
s->Add(ww, 0, wxALL, 5); // add contents to dialog
w->SetSizerAndFit(s);
w->CenterOnParent();
w->ShowModal();
evt.config_dialog->ReadBack();
evt.config_dialog->DeleteWindow();
delete w;
} else {
wxMessageBox("Uh... no config dialog?");
}
// See note in auto4_base.h
if (evt.sync_sema) {
evt.sync_sema->Post();
}
impl->Run(bind(progress_sink_wrapper, task, std::tr1::placeholders::_1, this), prio);
}
@ -891,12 +736,12 @@ namespace Automation4 {
while (more) {
script_path.SetName(fn);
try {
wxString fullpath = script_path.GetFullPath();
if (ScriptFactory::CanHandleScriptFormat(fullpath)) {
Script *s = ScriptFactory::CreateFromFile(fullpath, true);
Add(s);
if (!s->GetLoadedState()) error_count++;
}
wxString fullpath = script_path.GetFullPath();
if (ScriptFactory::CanHandleScriptFormat(fullpath)) {
Script *s = ScriptFactory::CreateFromFile(fullpath, true);
Add(s);
if (!s->GetLoadedState()) error_count++;
}
}
catch (const char *e) {
error_count++;
@ -1094,7 +939,7 @@ namespace Automation4 {
/// @param filename
///
UnknownScript::UnknownScript(const wxString &filename)
: Script(filename)
: Script(filename)
{
wxFileName fn(filename);
name = fn.GetName();

View File

@ -50,6 +50,9 @@
#include <wx/timer.h>
#endif
#include <libaegisub/background_runner.h>
#include <libaegisub/exception.h>
#include <libaegisub/scoped_ptr.h>
#include <libaegisub/signal.h>
#include "ass_export_filter.h"
@ -58,6 +61,8 @@
class AssFile;
class AssStyle;
class DialogProgress;
class SubtitleFormat;
class wxWindow;
class wxDialog;
class wxStopWatch;
@ -264,137 +269,44 @@ namespace Automation4 {
virtual void Unserialise(const wxString &serialised) { }
};
class ProgressSink;
// Config dialog event class and related stuff (wx </3)
extern const wxEventType EVT_SHOW_CONFIG_DIALOG_t;
class BackgroundScriptRunner {
agi::scoped_ptr<DialogProgress> impl;
/// DOCME
/// @class ShowConfigDialogEvent
/// @brief DOCME
///
/// DOCME
class ShowConfigDialogEvent : public wxCommandEvent {
void OnConfigDialog(wxThreadEvent &evt);
public:
/// @brief DOCME
/// @param event
/// @return
///
ShowConfigDialogEvent(const wxEventType &event = EVT_SHOW_CONFIG_DIALOG_t)
: wxCommandEvent(event)
, config_dialog(0)
, sync_sema(0) { };
void QueueEvent(wxEvent *evt);
void Run(std::tr1::function<void(ProgressSink*)> task);
/// @brief DOCME
/// @return
///
virtual wxEvent *Clone() const { return new ShowConfigDialogEvent(*this); }
/// DOCME
ScriptConfigDialog *config_dialog;
/// DOCME
wxSemaphore *sync_sema;
BackgroundScriptRunner(wxWindow *parent, wxString const& title);
~BackgroundScriptRunner();
};
/// DOCME
typedef void (wxEvtHandler::*ShowConfigDialogEventFunction)(ShowConfigDialogEvent&);
/// DOCME
#define EVT_SHOW_CONFIG_DIALOG(fn) DECLARE_EVENT_TABLE_ENTRY( EVT_SHOW_CONFIG_DIALOG_t, -1, -1, (wxObjectEventFunction)(wxEventFunction)(ShowConfigDialogEventFunction)&fn, (wxObject*)0 ),
/// DOCME
/// @class ProgressSink
/// @brief DOCME
///
/// DOCME
class ProgressSink : public wxDialog {
private:
/// DOCME
wxBoxSizer *sizer;
/// DOCME
wxGauge *progress_display;
/// DOCME
wxButton *cancel_button;
/// DOCME
wxStaticText *title_display;
/// DOCME
wxStaticText *task_display;
/// DOCME
wxTextCtrl *debug_output;
/// DOCME
volatile bool debug_visible;
/// DOCME
volatile bool data_updated;
/// DOCME
float progress;
/// DOCME
wxString task;
/// DOCME
wxString title;
/// DOCME
wxString pending_debug_output;
/// DOCME
wxMutex data_mutex;
/// DOCME
wxTimer *update_timer;
void OnCancel(wxCommandEvent &evt);
void OnInit(wxInitDialogEvent &evt);
void OnIdle(wxIdleEvent &evt);
void OnConfigDialog(ShowConfigDialogEvent &evt);
void DoUpdateDisplay();
protected:
/// DOCME
volatile bool cancelled;
/// DOCME
/// A wrapper around agi::ProgressSink which adds the ability to open
/// dialogs on the GUI thread
class ProgressSink : public agi::ProgressSink {
agi::ProgressSink *impl;
BackgroundScriptRunner *bsr;
int trace_level;
ProgressSink(wxWindow *parent);
virtual ~ProgressSink();
public:
void SetProgress(float _progress);
void SetTask(const wxString &_task);
void SetTitle(const wxString &_title);
void AddDebugOutput(const wxString &msg);
void SetIndeterminate() { impl->SetIndeterminate(); }
void SetTitle(std::string const& title) { impl->SetTitle(title); }
void SetMessage(std::string const& msg) { impl->SetMessage(msg); }
void SetProgress(int cur, int max) { impl->SetProgress(cur, max); }
void Log(std::string const& str) { impl->Log(str); }
bool IsCancelled() { return impl->IsCancelled(); }
/// Show the passed dialog on the GUI thread, blocking the calling
/// thread until it closes
void ShowConfigDialog(ScriptConfigDialog *config_dialog);
/// DOCME
volatile bool has_inited;
/// Get the current automation trace level
int GetTraceLevel() const { return trace_level; }
/// DOCME
volatile bool script_finished;
DECLARE_EVENT_TABLE()
ProgressSink(agi::ProgressSink *impl, BackgroundScriptRunner *bsr);
};
@ -511,6 +423,8 @@ namespace Automation4 {
void Reload();
};
/// Both a base class for script factories and a manager of registered
/// script factories
class ScriptFactory {
/// Vector of loaded script engines
static std::vector<ScriptFactory*> *factories;
@ -556,12 +470,8 @@ namespace Automation4 {
static const std::vector<ScriptFactory*>& GetFactories();
};
/// DOCME
/// @class UnknownScript
/// @brief DOCME
///
/// DOCME
/// A script which represents a file not recognized by any registered
/// automation engines
class UnknownScript : public Script {
public:
UnknownScript(const wxString &filename);

View File

@ -324,7 +324,7 @@ namespace Automation4 {
name = GetPrettyFilename();
description = "Unknown error initialising Lua script";
}
}
}
/// @brief DOCME
@ -503,7 +503,7 @@ namespace Automation4 {
lua_pushnil(L);
return 1;
}
}
}
/// @brief DOCME
@ -521,7 +521,7 @@ namespace Automation4 {
lua_pushnil(L);
return 1;
}
}
}
/// @brief DOCME
@ -543,62 +543,31 @@ namespace Automation4 {
}
}
// LuaThreadedCall
/// @brief DOCME
/// @param _L
/// @param _nargs
/// @param _nresults
///
LuaThreadedCall::LuaThreadedCall(lua_State *_L, int _nargs, int _nresults)
: wxThread(wxTHREAD_JOINABLE)
, L(_L)
, nargs(_nargs)
, nresults(_nresults)
static void lua_threaded_call(ProgressSink *ps, lua_State *L, int nargs, int nresults, bool can_open_config)
{
int prio = OPT_GET("Automation/Lua/Thread Priority")->GetInt();
if (prio == 0) prio = 50; // normal
else if (prio == 1) prio = 30; // below normal
else if (prio == 2) prio = 10; // lowest
else prio = 50; // fallback normal
Create();
SetPriority(prio);
Run();
}
LuaProgressSink lps(L, ps, can_open_config);
/// @brief DOCME
/// @return
///
wxThread::ExitCode LuaThreadedCall::Entry()
{
int result = lua_pcall(L, nargs, nresults, 0);
// see if there's a progress sink window to close
lua_getfield(L, LUA_REGISTRYINDEX, "progress_sink");
if (lua_isuserdata(L, -1)) {
LuaProgressSink *ps = LuaProgressSink::GetObjPointer(L, -1);
if (result) {
if (lua_pcall(L, nargs, nresults, 0)) {
// if the call failed, log the error here
wxString errmsg(lua_tostring(L, -2), wxConvUTF8);
ps->AddDebugOutput("\n\nLua reported a runtime error:\n");
ps->AddDebugOutput(errmsg);
ps->Log("\n\nLua reported a runtime error:\n");
ps->Log(lua_tostring(L, -1));
lua_pop(L, 1);
}
// don't bother protecting this with a mutex, it should be safe enough like this
ps->script_finished = true;
// tell wx to run its idle-events now, just to make the progress window notice earlier that we're done
wxWakeUpIdle();
}
lua_pop(L, 1);
lua_gc(L, LUA_GCCOLLECT, 0);
if (result) return (wxThread::ExitCode) 1;
else return 0;
}
// LuaThreadedCall
void LuaThreadedCall(lua_State *L, int nargs, int nresults, wxString const& title, wxWindow *parent, bool can_open_config)
{
BackgroundScriptRunner bsr(parent, title);
try {
bsr.Run(bind(lua_threaded_call, std::tr1::placeholders::_1, L, nargs, nresults, can_open_config));
}
catch (agi::UserCancelException const&) {
/// @todo perhaps this needs to continue up for exporting?
}
}
@ -779,25 +748,16 @@ namespace Automation4 {
void LuaFeatureMacro::Process(AssFile *subs, std::vector<int> &selected, int active, wxWindow * const progress_parent)
{
GetFeatureFunction(1); // 1 = processing function
// prepare function call
LuaAssFile *subsobj = new LuaAssFile(L, subs, true, true);
(void) subsobj;
CreateIntegerArray(selected); // selected items
lua_pushinteger(L, -1); // active line
LuaProgressSink *ps = new LuaProgressSink(L, progress_parent);
ps->SetTitle(GetName());
// do call
// 3 args: subtitles, selected lines, active line
// 1 result: new selected lines
LuaThreadedCall call(L, 3, 1);
LuaThreadedCall(L, 3, 1, GetName(), progress_parent, true);
ps->ShowModal();
wxThread::ExitCode code = call.Wait();
(void) code; // ignore
//if (code) ThrowError();
subsobj->ProcessingComplete(GetName());
// top of stack will be selected lines array, if any was returned
if (lua_istable(L, -1)) {
@ -815,8 +775,6 @@ namespace Automation4 {
}
// either way, there will be something on the stack
lua_pop(L, 1);
delete ps;
}
@ -905,24 +863,11 @@ namespace Automation4 {
assert(lua_istable(L, -1));
stackcheck.check_stack(3);
LuaProgressSink *ps = new LuaProgressSink(L, export_dialog, false);
ps->SetTitle(GetName());
stackcheck.check_stack(3);
// do call
LuaThreadedCall call(L, 2, 0);
ps->ShowModal();
wxThread::ExitCode code = call.Wait();
(void) code;
//if (code) ThrowError();
LuaThreadedCall(L, 2, 0, AssExportFilter::GetName(), export_dialog, false);
stackcheck.check_stack(0);
// Just ensure that subsobj survives until here
(void) subsobj;
delete ps;
subsobj->ProcessingComplete();
}

View File

@ -122,17 +122,7 @@ namespace Automation4 {
LuaAssFile(lua_State *L, AssFile *ass, bool can_modify = false, bool can_set_undo = false);
};
/// DOCME
/// @class LuaProgressSink
/// @brief DOCME
///
/// DOCME
class LuaProgressSink : public ProgressSink {
private:
/// DOCME
class LuaProgressSink {
lua_State *L;
static int LuaSetProgress(lua_State *L);
@ -143,10 +133,10 @@ namespace Automation4 {
static int LuaDisplayDialog(lua_State *L);
public:
LuaProgressSink(lua_State *_L, wxWindow *parent, bool allow_config_dialog = true);
virtual ~LuaProgressSink();
LuaProgressSink(lua_State *L, ProgressSink *ps, bool allow_config_dialog = true);
~LuaProgressSink();
static LuaProgressSink* GetObjPointer(lua_State *L, int idx);
static ProgressSink* GetObjPointer(lua_State *L, int idx);
};
@ -317,26 +307,7 @@ namespace Automation4 {
/// DOCME
/// @class LuaThreadedCall
/// @brief DOCME
///
/// DOCME
class LuaThreadedCall : public wxThread {
private:
/// DOCME
lua_State *L;
/// DOCME
int nargs;
/// DOCME
int nresults;
public:
LuaThreadedCall(lua_State *_L, int _nargs, int _nresults);
virtual ExitCode Entry();
};
void LuaThreadedCall(lua_State *L, int nargs, int nresults, wxString const& title, wxWindow *parent, bool can_open_config);

View File

@ -107,7 +107,7 @@ namespace {
lua_setfield(L, -2, name);
}
DEFINE_SIMPLE_EXCEPTION_NOINNER(BadField, Automation4::MacroRunError, "automation/macro/bad_field")
DEFINE_SIMPLE_EXCEPTION_NOINNER(BadField, agi::Exception, "automation/macro/bad_field")
BadField bad_field(const char *expected_type, const char *name, const char *line_clasee)
{
return BadField(std::string("Invalid ") + expected_type + " '" + name + "' field in '" + line_clasee + "' class subtitle line");

View File

@ -44,45 +44,50 @@
#include <lua.hpp>
#endif
static void push_closure(lua_State *L, const char *name, lua_CFunction fn) {
lua_pushvalue(L, -3);
lua_pushcclosure(L, fn, 1);
lua_setfield(L, -2, name);
namespace {
void set_field_to_closure(lua_State *L, const char *name, lua_CFunction fn, int ps_idx = -3)
{
lua_pushvalue(L, ps_idx);
lua_pushcclosure(L, fn, 1);
lua_setfield(L, -2, name);
}
void set_field_to_nil(lua_State *L, int idx, const char *name)
{
lua_pushnil(L);
lua_setfield(L, idx, name);
}
}
namespace Automation4 {
LuaProgressSink::LuaProgressSink(lua_State *L, wxWindow *parent, bool allow_config_dialog)
: ProgressSink(parent)
, L(L)
LuaProgressSink::LuaProgressSink(lua_State *L, ProgressSink *ps, bool allow_config_dialog)
: L(L)
{
LuaProgressSink **ud = (LuaProgressSink**)lua_newuserdata(L, sizeof(LuaProgressSink*));
*ud = this;
ProgressSink **ud = (ProgressSink**)lua_newuserdata(L, sizeof(ProgressSink*));
*ud = ps;
// register progress reporting stuff
lua_getglobal(L, "aegisub");
// Create aegisub.progress table
lua_newtable(L);
push_closure(L, "set", LuaSetProgress);
push_closure(L, "task", LuaSetTask);
push_closure(L, "title", LuaSetTitle);
push_closure(L, "is_cancelled", LuaGetCancelled);
set_field_to_closure(L, "set", LuaSetProgress);
set_field_to_closure(L, "task", LuaSetTask);
set_field_to_closure(L, "title", LuaSetTitle);
set_field_to_closure(L, "is_cancelled", LuaGetCancelled);
lua_setfield(L, -2, "progress");
// Create aegisub.debug table
lua_newtable(L);
lua_pushvalue(L, -3);
lua_pushcclosure(L, LuaDebugOut, 1);
lua_setfield(L, -2, "out");
set_field_to_closure(L, "out", LuaDebugOut);
lua_setfield(L, -2, "debug");
lua_pushvalue(L, -2);
lua_pushcclosure(L, LuaDebugOut, 1);
lua_setfield(L, -2, "log");
// Set aegisub.log
set_field_to_closure(L, "log", LuaDebugOut, -2);
if (allow_config_dialog) {
lua_newtable(L);
lua_pushvalue(L, -3);
lua_pushcclosure(L, LuaDisplayDialog, 1);
lua_setfield(L, -2, "display");
set_field_to_closure(L, "display", LuaDisplayDialog);
lua_setfield(L, -2, "dialog");
}
@ -97,54 +102,50 @@ namespace Automation4 {
{
// remove progress reporting stuff
lua_getglobal(L, "aegisub");
lua_pushnil(L);
lua_setfield(L, -2, "progress");
lua_pushnil(L);
lua_setfield(L, -2, "debug");
set_field_to_nil(L, -2, "progress");
set_field_to_nil(L, -2, "debug");
lua_pop(L, 1);
lua_pushnil(L);
lua_setfield(L, LUA_REGISTRYINDEX, "progress_sink");
set_field_to_nil(L, LUA_REGISTRYINDEX, "progress_sink");
}
LuaProgressSink* LuaProgressSink::GetObjPointer(lua_State *L, int idx)
ProgressSink* LuaProgressSink::GetObjPointer(lua_State *L, int idx)
{
assert(lua_type(L, idx) == LUA_TUSERDATA);
void *ud = lua_touserdata(L, idx);
return *((LuaProgressSink**)ud);
return *((ProgressSink**)lua_touserdata(L, idx));
}
int LuaProgressSink::LuaSetProgress(lua_State *L)
{
GetObjPointer(L, lua_upvalueindex(1))->SetProgress(lua_tonumber(L, 1));
GetObjPointer(L, lua_upvalueindex(1))->SetProgress(lua_tonumber(L, 1), 100);
return 0;
}
int LuaProgressSink::LuaSetTask(lua_State *L)
{
GetObjPointer(L, lua_upvalueindex(1))->SetTask(wxString(lua_tostring(L, 1), wxConvUTF8));
GetObjPointer(L, lua_upvalueindex(1))->SetMessage(lua_tostring(L, 1));
return 0;
}
int LuaProgressSink::LuaSetTitle(lua_State *L)
{
GetObjPointer(L, lua_upvalueindex(1))->SetTitle(wxString(lua_tostring(L, 1), wxConvUTF8));
GetObjPointer(L, lua_upvalueindex(1))->SetTitle(lua_tostring(L, 1));
return 0;
}
int LuaProgressSink::LuaGetCancelled(lua_State *L)
{
lua_pushboolean(L, GetObjPointer(L, lua_upvalueindex(1))->cancelled);
lua_pushboolean(L, GetObjPointer(L, lua_upvalueindex(1))->IsCancelled());
return 1;
}
int LuaProgressSink::LuaDebugOut(lua_State *L)
{
LuaProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
ProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
// Check trace level
if (lua_isnumber(L, 1)) {
int level = lua_tointeger(L, 1);
if (level > ps->trace_level)
if (lua_tointeger(L, 1) > ps->GetTraceLevel())
return 0;
// remove trace level
lua_remove(L, 1);
@ -166,14 +167,13 @@ namespace Automation4 {
}
// Top of stack is now a string to output
wxString msg(lua_tostring(L, 1), wxConvUTF8);
ps->AddDebugOutput(msg);
ps->Log(lua_tostring(L, 1));
return 0;
}
int LuaProgressSink::LuaDisplayDialog(lua_State *L)
{
LuaProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
ProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
// Check that two arguments were actually given
// If only one, add another empty table for buttons
@ -185,19 +185,8 @@ namespace Automation4 {
lua_settop(L, 2);
}
// Send the "show dialog" event
// See comments in auto4_base.h for more info on this synchronisation
ShowConfigDialogEvent evt;
LuaConfigDialog dlg(L, true); // magically creates the config dialog structure etc
evt.config_dialog = &dlg;
wxSemaphore sema(0, 1);
evt.sync_sema = &sema;
ps->AddPendingEvent(evt);
sema.Wait();
ps->ShowConfigDialog(&dlg);
// more magic: puts two values on stack: button pushed and table with control results
return dlg.LuaReadBack(L);

View File

@ -1,216 +1,236 @@
// Copyright (c) 2005, Rodrigo Braz Monteiro
// All rights reserved.
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// $Id$
/// @file dialog_progress.cpp
/// @brief Progress-bar dialogue box for displaying during long operations
/// @brief Progress-bar dialog box for displaying during long operations
/// @ingroup utility
///
///////////
// Headers
#include "config.h"
#include "dialog_progress.h"
#include <libaegisub/exception.h>
#include "compat.h"
#include "utils.h"
#ifndef AGI_PRE
#include <wx/button.h>
#include <wx/gauge.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#endif
#include "dialog_progress.h"
#include "utils.h"
wxDEFINE_EVENT(EVT_TITLE, wxThreadEvent);
wxDEFINE_EVENT(EVT_MESSAGE, wxThreadEvent);
wxDEFINE_EVENT(EVT_PROGRESS, wxThreadEvent);
wxDEFINE_EVENT(EVT_INDETERMINATE, wxThreadEvent);
wxDEFINE_EVENT(EVT_LOG, wxThreadEvent);
wxDEFINE_EVENT(EVT_COMPLETE, wxThreadEvent);
class DialogProgressSink : public agi::ProgressSink {
DialogProgress *dialog;
bool cancelled;
wxMutex cancelled_mutex;
DEFINE_EVENT_TYPE(wxEVT_PROGRESS_UPDATE)
/// @brief Constructor
/// @param parent
/// @param title
/// @param cancel
/// @param message
/// @param cur
/// @param max
///
DialogProgress::DialogProgress(wxWindow *parent,wxString title,volatile bool *cancel,wxString message,int cur,int max)
: wxDialog(parent,-1,title,wxDefaultPosition,wxDefaultSize,wxBORDER_RAISED/* | wxSTAY_ON_TOP*/)
{
// Variables
canceled = cancel;
if (cancel) *canceled = false;
virtualMax = max;
// Gauge
gauge = new wxGauge(this, -1, 100, wxDefaultPosition, wxSize(300,20), wxGA_HORIZONTAL);
wxButton *cancelButton = NULL;
if (cancel) cancelButton = new wxButton(this,wxID_CANCEL);
text = new wxStaticText(this, -1, message, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE | wxST_NO_AUTORESIZE);
// Main sizer
wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL);
MainSizer->Add(gauge,1,wxEXPAND | wxALL,5);
MainSizer->Add(text,0,wxEXPAND | wxALIGN_CENTER | wxBOTTOM,5);
if (cancel) MainSizer->Add(cancelButton,0,wxALIGN_CENTER | wxLEFT | wxRIGHT | wxBOTTOM,5);
MainSizer->SetSizeHints(this);
SetSizer(MainSizer);
CenterOnParent();
Connect(0,wxEVT_PROGRESS_UPDATE,wxCommandEventHandler(DialogProgress::OnUpdateProgress));
}
/// @brief Set progress
/// @param cur
/// @param max
/// @return
///
void DialogProgress::SetProgress(int cur,int max) {
// Return if there's nothing to do
int value = cur*100/virtualMax;
if (gauge->GetValue() == value && virtualMax == max) return;
virtualMax = max;
// Check if it's the main thread, if so, just process it now
if (wxIsMainThread()) {
gauge->SetValue(mid(0,value,100));
wxYield();
return;
template<class T>
void SafeQueue(wxEventType type, T const& value) {
wxThreadEvent *evt = new wxThreadEvent(type);
evt->SetPayload(value);
wxQueueEvent(dialog, evt);
}
// Otherwise, go on
public:
DialogProgressSink(DialogProgress *dialog)
: dialog(dialog)
, cancelled(false)
{
wxMutexLocker locker(mutex);
if (count >= 2) return;
else count++;
}
wxCommandEvent evt(wxEVT_PROGRESS_UPDATE,0);
evt.SetInt(value);
AddPendingEvent(evt);
}
void SetTitle(std::string const& title) {
SafeQueue(EVT_TITLE, lagi_wxString(title));
}
void SetMessage(std::string const& msg) {
SafeQueue(EVT_MESSAGE, lagi_wxString(msg));
}
void SetProgress(int cur, int max) {
SafeQueue(EVT_PROGRESS, int(double(cur) / max * 100));
}
/// @brief Update progress
/// @param event
///
void DialogProgress::OnUpdateProgress(wxCommandEvent &event)
void Log(std::string const& str) {
SafeQueue(EVT_LOG, lagi_wxString(str));
}
bool IsCancelled() {
wxMutexLocker l(cancelled_mutex);
return cancelled;
}
void Cancel() {
wxMutexLocker l(cancelled_mutex);
cancelled = true;
}
void SetIndeterminate() {
wxQueueEvent(dialog, new wxThreadEvent(EVT_INDETERMINATE));
}
};
class TaskRunner : public wxThread {
std::tr1::function<void(agi::ProgressSink*)> task;
agi::ProgressSink *ps;
wxDialog *dialog;
public:
TaskRunner(std::tr1::function<void(agi::ProgressSink*)> task, agi::ProgressSink *ps, wxDialog *dialog, int priority)
: task(task)
, ps(ps)
, dialog(dialog)
{
Create();
if (priority != -1)
SetPriority(priority);
Run();
}
wxThread::ExitCode Entry() {
task(ps);
wxQueueEvent(dialog, new wxThreadEvent(EVT_COMPLETE));
return 0;
}
};
DialogProgress::DialogProgress(wxWindow *parent, wxString const& title_text, wxString const& message)
: wxDialog(parent, -1, title_text, wxDefaultPosition, wxDefaultSize, wxBORDER_RAISED)
, pulse_timer(GetEventHandler())
{
int value = event.GetInt();
if (gauge->GetValue() != value) gauge->SetValue(mid(0,value,100));
wxMutexLocker locker(mutex);
count--;
title = new wxStaticText(this, -1, title_text, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE | wxST_NO_AUTORESIZE);
gauge = new wxGauge(this, -1, 100, wxDefaultPosition, wxSize(300,20));
text = new wxStaticText(this, -1, message, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE | wxST_NO_AUTORESIZE);
cancel_button = new wxButton(this, wxID_CANCEL);
log_output = new wxTextCtrl(this, -1, "", wxDefaultPosition, wxSize(300, 120), wxTE_MULTILINE | wxTE_READONLY);
// make the title a slightly larger font
wxFont title_font = title->GetFont();
int fontsize = title_font.GetPointSize();
title_font.SetPointSize(fontsize * 1.375);
title_font.SetWeight(wxFONTWEIGHT_BOLD);
title->SetFont(title_font);
wxSizer *sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(title, wxSizerFlags().Expand().Center());
sizer->Add(gauge, wxSizerFlags(1).Expand().Border());
sizer->Add(text, wxSizerFlags().Expand().Center());
sizer->Add(cancel_button, wxSizerFlags().Center().Border());
sizer->Add(log_output, wxSizerFlags().Expand().Border(wxALL & ~wxTOP));
sizer->Hide(log_output);
SetSizerAndFit(sizer);
CenterOnParent();
Bind(wxEVT_SHOW, &DialogProgress::OnShow, this);
Bind(wxEVT_TIMER, &DialogProgress::OnPulseTimer, this);
Bind(EVT_TITLE, &DialogProgress::OnSetTitle, this);
Bind(EVT_MESSAGE, &DialogProgress::OnSetMessage, this);
Bind(EVT_PROGRESS, &DialogProgress::OnSetProgress, this);
Bind(EVT_INDETERMINATE, &DialogProgress::OnSetIndeterminate, this);
Bind(EVT_COMPLETE, &DialogProgress::OnComplete, this);
Bind(EVT_LOG, &DialogProgress::OnLog, this);
}
/// @brief Set progress
/// @param setto
///
void DialogProgress::SetText(wxString setto) {
// Lock
bool isMain = wxIsMainThread();
if (!isMain) wxMutexGuiEnter();
// Update
text->SetLabel(setto);
wxYield();
// Unlock
if (!isMain) wxMutexGuiLeave();
void DialogProgress::Run(std::tr1::function<void(agi::ProgressSink*)> task, int priority) {
DialogProgressSink ps(this);
this->ps = &ps;
new TaskRunner(task, &ps, this, priority);
if (ShowModal())
throw agi::UserCancelException("Cancelled by user");
}
void DialogProgress::OnShow(wxShowEvent&) {
// Restore the cancel button in case it was previously switched to a close
// button
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogProgress::OnCancel, this, wxID_CANCEL);
cancel_button->SetLabelText(_("Cancel"));
cancel_button->Enable();
///////////////
// Event table
BEGIN_EVENT_TABLE(DialogProgress,wxDialog)
EVT_BUTTON(wxID_CANCEL,DialogProgress::OnCancel)
END_EVENT_TABLE()
/// @brief Cancel
/// @param event
///
void DialogProgress::OnCancel(wxCommandEvent &event) {
if (canceled) *canceled = true;
bool isMain = wxIsMainThread();
if (!isMain) wxMutexGuiEnter();
Destroy();
if (!isMain) wxMutexGuiLeave();
wxSizer *sizer = GetSizer();
if (sizer->IsShown(log_output)) {
sizer->Hide(log_output);
Layout();
sizer->Fit(this);
log_output->Clear();
}
}
/// @brief Thread constructor
/// @param parent
/// @param title
/// @param canceled
/// @param message
/// @param cur
/// @param max
///
DialogProgressThread::DialogProgressThread(wxWindow *parent,wxString title,volatile bool *canceled,wxString message,int cur,int max)
: wxThread(wxTHREAD_DETACHED)
{
dialog = new DialogProgress(parent,title,canceled,message,cur,max);
void DialogProgress::OnSetTitle(wxThreadEvent &evt) {
title->SetLabelText(evt.GetPayload<wxString>());
}
/// @brief Thread destructor
///
DialogProgressThread::~DialogProgressThread() {
void DialogProgress::OnSetMessage(wxThreadEvent &evt) {
text->SetLabelText(evt.GetPayload<wxString>());
}
/// @brief Thread entry point
/// @return
///
wxThread::ExitCode DialogProgressThread::Entry() {
dialog->ShowModal();
dialog = NULL;
Delete();
return 0;
void DialogProgress::OnSetProgress(wxThreadEvent &evt) {
gauge->SetValue(mid(0, evt.GetPayload<int>(), 100));
}
/// @brief Close
///
void DialogProgressThread::Close() {
wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED,wxID_CANCEL);
dialog->canceled = NULL;
dialog->GetEventHandler()->ProcessEvent(event);
void DialogProgress::OnSetIndeterminate(wxThreadEvent &evt) {
pulse_timer.Start(1000);
}
void DialogProgress::Pulse() {
void DialogProgress::OnComplete(wxThreadEvent &evt) {
pulse_timer.Stop();
// Unbind the cancel handler so that the default behavior happens (i.e. the
// dialog is closed) as there's no longer a task to cancel
Unbind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogProgress::OnCancel, this, wxID_CANCEL);
// If it ran to completion and there is debug output, leave the window open
// so the user can read the debug output and switch the cancel button to a
// close button
bool cancelled = ps->IsCancelled();
if (cancelled || log_output->IsEmpty())
EndModal(cancelled);
else
cancel_button->SetLabelText(_("Close"));
}
void DialogProgress::OnLog(wxThreadEvent &evt) {
if (log_output->IsEmpty()) {
wxSizer *sizer = GetSizer();
sizer->Show(log_output);
Layout();
sizer->Fit(this);
}
*log_output << evt.GetPayload<wxString>();
log_output->SetInsertionPointEnd();
}
void DialogProgress::OnCancel(wxCommandEvent &evt) {
ps->Cancel();
cancel_button->Enable(false);
cancel_button->SetLabelText(_("Cancelling..."));
}
void DialogProgress::OnPulseTimer(wxTimerEvent&) {
gauge->Pulse();
}

View File

@ -1,31 +1,16 @@
// Copyright (c) 2005, Rodrigo Braz Monteiro
// All rights reserved.
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// $Id$
@ -34,74 +19,52 @@
/// @ingroup utility
///
///////////
// Headers
#ifndef AGI_PRE
#include <wx/dialog.h>
#include <wx/gauge.h>
#include <wx/stattext.h>
#include <wx/timer.h>
#endif
#include <libaegisub/background_runner.h>
#include <libaegisub/scoped_ptr.h>
class DialogProgressSink;
class wxButton;
class wxGauge;
class wxStaticText;
class wxTextCtrl;
/// DOCME
/// @class DialogProgress
/// @brief DOCME
///
/// DOCME
class DialogProgress : public wxDialog {
private:
/// @brief Progress-bar dialog box for displaying during long operations
class DialogProgress : public wxDialog, public agi::BackgroundRunner {
DialogProgressSink *ps;
/// DOCME
volatile int count;
/// DOCME
int virtualMax;
/// DOCME
wxMutex mutex;
/// DOCME
wxGauge *gauge;
/// DOCME
wxStaticText *title;
wxStaticText *text;
void OnCancel(wxCommandEvent &event);
void OnUpdateProgress(wxCommandEvent &event);
wxGauge *gauge;
wxButton *cancel_button;
wxTextCtrl *log_output;
wxTimer pulse_timer;
void OnSetTitle(wxThreadEvent &evt);
void OnSetMessage(wxThreadEvent &evt);
void OnSetProgress(wxThreadEvent &evt);
void OnSetIndeterminate(wxThreadEvent &evt);
void OnLog(wxThreadEvent &evt);
void OnComplete(wxThreadEvent &evt);
void OnShow(wxShowEvent&);
void OnCancel(wxCommandEvent &);
void OnPulseTimer(wxTimerEvent&);
public:
/// Constructor
/// @param parent Parent window of the dialog
/// @param title Initial title of the dialog
/// @param message Initial message of the dialog
DialogProgress(wxWindow *parent, wxString const& title="", wxString const& message="");
/// DOCME
volatile bool *canceled;
DialogProgress(wxWindow *parent,wxString title,volatile bool *cancel,wxString message,int cur,int max);
void SetProgress(int cur,int max);
void SetText(wxString text);
void Run();
void Pulse();
DECLARE_EVENT_TABLE()
};
/// DOCME
/// @class DialogProgressThread
/// @brief DOCME
///
/// DOCME
class DialogProgressThread : public wxThread {
DialogProgressThread(wxWindow *parent,wxString title,volatile bool *canceled,wxString message,int cur,int max);
public:
/// DOCME
DialogProgress *dialog;
~DialogProgressThread();
wxThread::ExitCode Entry();
void Close();
/// BackgroundWorker implementation
void Run(std::tr1::function<void(agi::ProgressSink *)> task, int priority=-1);
};

View File

@ -49,6 +49,7 @@
#include <libaegisub/log.h>
#include "compat.h"
#include "dialog_progress.h"
#include "ffmpegsource_common.h"
#include "frame_main.h"
#include "main.h"
@ -58,26 +59,23 @@
wxMutex FFmpegSourceProvider::CleaningInProgress;
/// @brief Callback function that updates the indexing progress dialog
/// @param Current The current file positition in bytes
/// @param Total The total file size in bytes
/// @param Private A pointer to the progress dialog box to update
/// @param Private A pointer to the progress sink to update
/// @return Returns non-0 if indexing is cancelled, 0 otherwise.
///
int FFMS_CC FFmpegSourceProvider::UpdateIndexingProgress(int64_t Current, int64_t Total, void *Private) {
IndexingProgressDialog *Progress = (IndexingProgressDialog *)Private;
if (Progress->IndexingCanceled)
return 1;
// no one cares about a little bit of a rounding error here anyway
Progress->ProgressDialog->SetProgress(((int64_t)1000*Current)/Total, 1000);
return 0;
static int FFMS_CC UpdateIndexingProgress(int64_t Current, int64_t Total, void *Private) {
agi::ProgressSink *ps = static_cast<agi::ProgressSink*>(Private);
ps->SetProgress(Current, Total);
return ps->IsCancelled();
}
/// A wrapper around FFMS_DoIndexing to make the signature void -> void
static void DoIndexingWrapper(FFMS_Index **Ret, FFMS_Indexer *Indexer, int IndexMask, int ErrorHandling, void *ICPrivate, FFMS_ErrorInfo *ErrorInfo) {
*Ret = FFMS_DoIndexing(Indexer, IndexMask, FFMS_TRACKMASK_NONE, NULL, NULL, ErrorHandling,
UpdateIndexingProgress, ICPrivate, ErrorInfo);
}
/// @brief Does indexing of a source file
/// @param Indexer A pointer to the indexer object representing the file to be indexed
@ -96,21 +94,12 @@ FFMS_Index *FFmpegSourceProvider::DoIndexing(FFMS_Indexer *Indexer, const wxStri
wxString MsgString;
// set up progress dialog callback
IndexingProgressDialog Progress;
Progress.IndexingCanceled = false;
Progress.ProgressDialog = new DialogProgress(AegisubApp::Get()->frame,
_("Indexing"), &Progress.IndexingCanceled,
_("Reading timecodes and frame/sample data"), 0, 1);
Progress.ProgressDialog->Show();
Progress.ProgressDialog->SetProgress(0,1);
DialogProgress Progress(AegisubApp::Get()->frame, _("Indexing"), _("Reading timecodes and frame/sample data"));
// index all audio tracks
FFMS_Index *Index = FFMS_DoIndexing(Indexer, Trackmask, FFMS_TRACKMASK_NONE, NULL, NULL, IndexEH,
FFmpegSourceProvider::UpdateIndexingProgress, &Progress, &ErrInfo);
Progress.ProgressDialog->Destroy();
if (Progress.IndexingCanceled) {
throw agi::UserCancelException("indexing cancelled by user");
}
FFMS_Index *Index;
Progress.Run(bind(DoIndexingWrapper, &Index, Indexer, Trackmask, IndexEH, std::tr1::placeholders::_1, &ErrInfo));
if (Index == NULL) {
MsgString.Append("Failed to index: ").Append(wxString(ErrInfo.Buffer, wxConvUTF8));
throw MsgString;

View File

@ -45,8 +45,6 @@
#include <ffms.h>
#include "dialog_progress.h"
/// Index all tracks
#define FFMS_TRACKMASK_ALL -1
/// Index no tracks
@ -70,18 +68,10 @@ public:
FFMS_LOG_DEBUG = 48,
};
/// Indexing progress report dialog
struct IndexingProgressDialog {
volatile bool IndexingCanceled;
DialogProgress *ProgressDialog;
};
/// Mutex preventing two cache cleaner threads from running at the same time
static wxMutex CleaningInProgress;
bool CleanCache();
static int FFMS_CC UpdateIndexingProgress(int64_t Current, int64_t Total, void *Private);
FFMS_Index *DoIndexing(FFMS_Indexer *Indexer, const wxString& Cachename, int Trackmask, FFMS_IndexErrorHandling IndexEH);
std::map<int,wxString> GetTracksOfType(FFMS_Indexer *Indexer, FFMS_TrackType Type);
int AskForTrackSelection(const std::map<int,wxString>& TrackList, FFMS_TrackType Type);

View File

@ -86,9 +86,7 @@
"Automation" : {
"Autoreload Mode" : 1,
"Lua" : {
"Thread Priority" : 1
},
"Thread Priority" : 1,
"Trace Level" : 3
},

View File

@ -68,6 +68,69 @@ public:
#define CACHESIZE 65536
static void read_subtitles(agi::ProgressSink *ps, MatroskaFile *file, MkvStdIO *input, bool srt, bool ssa, double totalTime, AssFile *target) {
std::map<int, wxString> subList;
char *readBuf = 0;
size_t readBufSize = 0;
// Load blocks
ulonglong startTime, endTime, filePos;
unsigned int rt, frameSize, frameFlags;
while (mkv_ReadFrame(file,0,&rt,&startTime,&endTime,&filePos,&frameSize,&frameFlags) == 0) {
if (ps->IsCancelled()) {
delete readBuf;
return;
}
// Read to temp
if (frameSize > readBufSize) {
delete readBuf;
readBufSize = frameSize * 2;
readBuf = new char[readBufSize];
}
fseek(input->fp, filePos, SEEK_SET);
fread(readBuf, 1, frameSize, input->fp);
wxString blockString(readBuf, wxConvUTF8, frameSize);
// Get start and end times
longlong timecodeScaleLow = 1000000;
AssTime subStart,subEnd;
subStart.SetMS(startTime / timecodeScaleLow);
subEnd.SetMS(endTime / timecodeScaleLow);
// Process SSA/ASS
if (!srt) {
long order = 0, layer = 0;
blockString.BeforeFirst(',', &blockString).ToLong(&order);
blockString.BeforeFirst(',', &blockString).ToLong(&layer);
subList[order] = wxString::Format("Dialogue: %d,%s,%s,%s", layer, subStart.GetASSFormated(), subEnd.GetASSFormated(), blockString);
}
// Process SRT
else {
blockString = wxString::Format("Dialogue: 0,%s,%s,%s", subStart.GetASSFormated(), subEnd.GetASSFormated(), blockString);
blockString.Replace("\r\n","\\N");
blockString.Replace("\r","\\N");
blockString.Replace("\n","\\N");
subList[subList.size()] = blockString;
}
ps->SetProgress(startTime, totalTime);
}
delete readBuf;
// Insert into file
wxString group = "[Events]";
int version = ssa;
for (std::map<int, wxString>::iterator it = subList.begin(); it != subList.end(); ++it) {
target->AddLine(it->second, group, version, &group);
}
}
void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
MkvStdIO input(filename);
char err[2048];
@ -82,10 +145,6 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
wxArrayString tracksNames;
unsigned trackToRead;
// Haali's library variables
ulonglong startTime, endTime, filePos;
unsigned int rt, frameSize, frameFlags;
// Find tracks
for (unsigned track = 0; track < tracks; track++) {
trackInfo = mkv_GetTrackInfo(file,track);
@ -122,14 +181,14 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
}
// Picked track
// Get codec type (0 = ASS/SSA, 1 = SRT)
mkv_SetTrackMask(file, ~(1 << trackToRead));
trackInfo = mkv_GetTrackInfo(file,trackToRead);
wxString CodecID = wxString(trackInfo->CodecID,*wxConvCurrent);
int codecType = 0;
if (CodecID == "S_TEXT/UTF8") codecType = 1;
bool srt = CodecID == "S_TEXT/UTF8";
bool ssa = CodecID == "S_TEXT/SSA";
// Read private data if it's ASS/SSA
if (codecType == 0) {
if (!srt) {
// Read raw data
trackInfo = mkv_GetTrackInfo(file,trackToRead);
wxString privString((const char *)trackInfo->CodecPrivate, wxConvUTF8, trackInfo->CodecPrivateSize);
@ -155,73 +214,9 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
longlong timecodeScale = mkv_TruncFloat(trackInfo->TimecodeScale) * segInfo->TimecodeScale;
// Progress bar
int totalTime = int(double(segInfo->Duration) / timecodeScale);
volatile bool canceled = false;
DialogProgress *progress = new DialogProgress(NULL,_("Parsing Matroska"),&canceled,_("Reading subtitles from Matroska file."),0,totalTime);
progress->Show();
progress->SetProgress(0,1);
std::map<int, wxString> subList;
char *readBuf = 0;
size_t readBufSize = 0;
// Load blocks
mkv_SetTrackMask(file, ~(1 << trackToRead));
while (mkv_ReadFrame(file,0,&rt,&startTime,&endTime,&filePos,&frameSize,&frameFlags) == 0) {
if (canceled) {
delete readBuf;
throw agi::UserCancelException("cancelled");
}
// Read to temp
if (frameSize > readBufSize) {
delete readBuf;
readBufSize = frameSize * 2;
readBuf = new char[readBufSize];
}
fseek(input.fp, filePos, SEEK_SET);
fread(readBuf, 1, frameSize, input.fp);
wxString blockString(readBuf, wxConvUTF8, frameSize);
// Get start and end times
longlong timecodeScaleLow = 1000000;
AssTime subStart,subEnd;
subStart.SetMS(startTime / timecodeScaleLow);
subEnd.SetMS(endTime / timecodeScaleLow);
// Process SSA/ASS
if (codecType == 0) {
long order = 0, layer = 0;
blockString.BeforeFirst(',', &blockString).ToLong(&order);
blockString.BeforeFirst(',', &blockString).ToLong(&layer);
subList[order] = wxString::Format("Dialogue: %d,%s,%s,%s", layer, subStart.GetASSFormated(), subEnd.GetASSFormated(), blockString);
}
// Process SRT
else {
blockString = wxString::Format("Dialogue: 0,%s,%s,%s", subStart.GetASSFormated(), subEnd.GetASSFormated(), blockString);
blockString.Replace("\r\n","\\N");
blockString.Replace("\r","\\N");
blockString.Replace("\n","\\N");
subList[subList.size()] = blockString;
}
progress->SetProgress(int(double(startTime) / 1000000.0),totalTime);
}
delete readBuf;
// Insert into file
wxString group = "[Events]";
int version = (CodecID == "S_TEXT/SSA");
for (std::map<int, wxString>::iterator it = subList.begin(); it != subList.end(); ++it) {
target->AddLine(it->second,group,version,&group);
}
// Close progress bar
if (!canceled) progress->Destroy();
double totalTime = double(segInfo->Duration) / timecodeScale * 1000000.0;
DialogProgress progress(NULL, _("Parsing Matroska"), _("Reading subtitles from Matroska file."));
progress.Run(bind(read_subtitles, std::tr1::placeholders::_1, file, &input, srt, ssa, totalTime, target));
}
catch (...) {
mkv_Close(file);

View File

@ -267,7 +267,7 @@ Automation::Automation(wxTreebook *book, Preferences *parent): OptionPage(book,
const wxString tp_arr[3] = { _("Normal"), _("Below Normal (recommended)"), _("Lowest") };
wxArrayString tp_choice(3, tp_arr);
OptionChoice(general, _("Thread priority"), tp_choice, "Automation/Lua/Thread Priority");
OptionChoice(general, _("Thread priority"), tp_choice, "Automation/Thread Priority");
const wxString ar_arr[4] = { _("No scripts"), _("Subtitle-local scripts"), _("Global autoload scripts"), _("All scripts") };
wxArrayString ar_choice(4, ar_arr);

View File

@ -121,19 +121,17 @@ public:
}
};
static void do_wait(agi::ProgressSink *ps, FontConfigCacheThread const * const * const cache_worker) {
ps->SetIndeterminate();
while (*cache_worker && !ps->IsCancelled())
wxMilliSleep(100);
}
static void wait_for_cache_thread(FontConfigCacheThread const * const * const cache_worker) {
if (!*cache_worker) return;
bool canceled;
DialogProgress *progress = new DialogProgress(AegisubApp::Get()->frame, "", &canceled, "Caching fonts", 0, 1);
progress->Show();
while (*cache_worker) {
if (canceled) throw agi::UserCancelException("Font caching cancelled");
progress->Pulse();
wxYield();
wxMilliSleep(100);
}
progress->Destroy();
DialogProgress progress(AegisubApp::Get()->frame, "Updating font index", "This may take several minutes");
progress.Run(bind(do_wait, std::tr1::placeholders::_1, cache_worker));
}
/// @brief Constructor