From 53b6765dd83c6cb466af2864b88a26c9d8648e01 Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Wed, 28 Sep 2011 19:47:40 +0000 Subject: [PATCH] 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. --- .../libaegisub_vs2008.vcproj | 4 + .../include/libaegisub/background_runner.h | 87 +++++ aegisub/src/audio_provider_hd.cpp | 56 ++- aegisub/src/audio_provider_hd.h | 4 + aegisub/src/audio_provider_ram.cpp | 63 +-- aegisub/src/audio_provider_ram.h | 3 + aegisub/src/auto4_base.cpp | 261 +++---------- aegisub/src/auto4_base.h | 158 ++------ aegisub/src/auto4_lua.cpp | 105 ++--- aegisub/src/auto4_lua.h | 39 +- aegisub/src/auto4_lua_assfile.cpp | 2 +- aegisub/src/auto4_lua_progresssink.cpp | 99 +++-- aegisub/src/dialog_progress.cpp | 364 +++++++++--------- aegisub/src/dialog_progress.h | 131 +++---- aegisub/src/ffmpegsource_common.cpp | 41 +- aegisub/src/ffmpegsource_common.h | 10 - aegisub/src/libresrc/default_config.json | 4 +- aegisub/src/mkv_wrap.cpp | 145 ++++--- aegisub/src/preferences.cpp | 2 +- aegisub/src/subtitles_provider_libass.cpp | 18 +- 20 files changed, 636 insertions(+), 960 deletions(-) create mode 100644 aegisub/libaegisub/include/libaegisub/background_runner.h diff --git a/aegisub/build/libaegisub_vs2008/libaegisub_vs2008.vcproj b/aegisub/build/libaegisub_vs2008/libaegisub_vs2008.vcproj index b1f8df819..77b194fbf 100644 --- a/aegisub/build/libaegisub_vs2008/libaegisub_vs2008.vcproj +++ b/aegisub/build/libaegisub_vs2008/libaegisub_vs2008.vcproj @@ -397,6 +397,10 @@ RelativePath="..\..\libaegisub\include\libaegisub\access.h" > + + diff --git a/aegisub/libaegisub/include/libaegisub/background_runner.h b/aegisub/libaegisub/include/libaegisub/background_runner.h new file mode 100644 index 000000000..09f374f55 --- /dev/null +++ b/aegisub/libaegisub/include/libaegisub/background_runner.h @@ -0,0 +1,87 @@ +// Copyright (c) 2011, Thomas Goyne +// +// 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 +#ifdef _WIN32 +#include +#else +#include +#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 task, int priority=-1)=0; + }; +} diff --git a/aegisub/src/audio_provider_hd.cpp b/aegisub/src/audio_provider_hd.cpp index 566a149f1..209afdee6 100644 --- a/aegisub/src/audio_provider_hd.cpp +++ b/aegisub/src/audio_provider_hd.cpp @@ -49,9 +49,9 @@ #include "standard_paths.h" #include "utils.h" -/// @brief Constructor -/// @param source -/// + + + HDAudioProvider::HDAudioProvider(AudioProvider *src) { std::auto_ptr 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) 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) { diff --git a/aegisub/src/audio_provider_hd.h b/aegisub/src/audio_provider_hd.h index f62af2396..e2a22d252 100644 --- a/aegisub/src/audio_provider_hd.h +++ b/aegisub/src/audio_provider_hd.h @@ -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(); diff --git a/aegisub/src/audio_provider_ram.cpp b/aegisub/src/audio_provider_ram.cpp index dc2986850..f17ad2705 100644 --- a/aegisub/src/audio_provider_ram.cpp +++ b/aegisub/src/audio_provider_ram.cpp @@ -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 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(CacheBlockSize,ssize-i*CacheBlockSize)]; + blockcache[i] = new char[std::min(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;iGetAudio((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) { diff --git a/aegisub/src/audio_provider_ram.h b/aegisub/src/audio_provider_ram.h index efba843ae..0e6c82024 100644 --- a/aegisub/src/audio_provider_ram.h +++ b/aegisub/src/audio_provider_ram.h @@ -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); diff --git a/aegisub/src/auto4_base.cpp b/aegisub/src/auto4_base.cpp index 304de6baa..4b5c85c1e 100644 --- a/aegisub/src/auto4_base.cpp +++ b/aegisub/src/auto4_base.cpp @@ -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 payload = evt.GetPayload >(); + + 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 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 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(); diff --git a/aegisub/src/auto4_base.h b/aegisub/src/auto4_base.h index ed4c96a1a..98e89f96f 100644 --- a/aegisub/src/auto4_base.h +++ b/aegisub/src/auto4_base.h @@ -50,6 +50,9 @@ #include #endif +#include +#include +#include #include #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 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 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 *factories; @@ -556,12 +470,8 @@ namespace Automation4 { static const std::vector& 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); diff --git a/aegisub/src/auto4_lua.cpp b/aegisub/src/auto4_lua.cpp index 3e687d60e..f838b76aa 100644 --- a/aegisub/src/auto4_lua.cpp +++ b/aegisub/src/auto4_lua.cpp @@ -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 &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(); } diff --git a/aegisub/src/auto4_lua.h b/aegisub/src/auto4_lua.h index 25d70f9a0..c78c72ebd 100644 --- a/aegisub/src/auto4_lua.h +++ b/aegisub/src/auto4_lua.h @@ -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); diff --git a/aegisub/src/auto4_lua_assfile.cpp b/aegisub/src/auto4_lua_assfile.cpp index 49d244165..ffe9cb731 100644 --- a/aegisub/src/auto4_lua_assfile.cpp +++ b/aegisub/src/auto4_lua_assfile.cpp @@ -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"); diff --git a/aegisub/src/auto4_lua_progresssink.cpp b/aegisub/src/auto4_lua_progresssink.cpp index c40afc4e5..b95cc34c4 100644 --- a/aegisub/src/auto4_lua_progresssink.cpp +++ b/aegisub/src/auto4_lua_progresssink.cpp @@ -44,45 +44,50 @@ #include #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); diff --git a/aegisub/src/dialog_progress.cpp b/aegisub/src/dialog_progress.cpp index 02ca179d9..8cb9e7031 100644 --- a/aegisub/src/dialog_progress.cpp +++ b/aegisub/src/dialog_progress.cpp @@ -1,216 +1,236 @@ -// Copyright (c) 2005, Rodrigo Braz Monteiro -// All rights reserved. +// Copyright (c) 2011, Thomas Goyne // -// 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 + +#include "compat.h" +#include "utils.h" + #ifndef AGI_PRE #include +#include #include +#include +#include #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 + 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 task; + agi::ProgressSink *ps; + wxDialog *dialog; + +public: + TaskRunner(std::tr1::function 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 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()); } - - -/// @brief Thread destructor -/// -DialogProgressThread::~DialogProgressThread() { +void DialogProgress::OnSetMessage(wxThreadEvent &evt) { + text->SetLabelText(evt.GetPayload()); } - - -/// @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(), 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(); + log_output->SetInsertionPointEnd(); +} + +void DialogProgress::OnCancel(wxCommandEvent &evt) { + ps->Cancel(); + cancel_button->Enable(false); + cancel_button->SetLabelText(_("Cancelling...")); +} + +void DialogProgress::OnPulseTimer(wxTimerEvent&) { gauge->Pulse(); } diff --git a/aegisub/src/dialog_progress.h b/aegisub/src/dialog_progress.h index d9d878192..837298071 100644 --- a/aegisub/src/dialog_progress.h +++ b/aegisub/src/dialog_progress.h @@ -1,31 +1,16 @@ -// Copyright (c) 2005, Rodrigo Braz Monteiro -// All rights reserved. +// Copyright (c) 2011, Thomas Goyne // -// 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 -#include -#include +#include #endif +#include +#include + +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 task, int priority=-1); }; diff --git a/aegisub/src/ffmpegsource_common.cpp b/aegisub/src/ffmpegsource_common.cpp index 73246b2a7..4077c0cd6 100644 --- a/aegisub/src/ffmpegsource_common.cpp +++ b/aegisub/src/ffmpegsource_common.cpp @@ -49,6 +49,7 @@ #include #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(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; diff --git a/aegisub/src/ffmpegsource_common.h b/aegisub/src/ffmpegsource_common.h index 6f63712d8..61e72e23c 100644 --- a/aegisub/src/ffmpegsource_common.h +++ b/aegisub/src/ffmpegsource_common.h @@ -45,8 +45,6 @@ #include -#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 GetTracksOfType(FFMS_Indexer *Indexer, FFMS_TrackType Type); int AskForTrackSelection(const std::map& TrackList, FFMS_TrackType Type); diff --git a/aegisub/src/libresrc/default_config.json b/aegisub/src/libresrc/default_config.json index d259ed59b..b59b74963 100644 --- a/aegisub/src/libresrc/default_config.json +++ b/aegisub/src/libresrc/default_config.json @@ -86,9 +86,7 @@ "Automation" : { "Autoreload Mode" : 1, - "Lua" : { - "Thread Priority" : 1 - }, + "Thread Priority" : 1, "Trace Level" : 3 }, diff --git a/aegisub/src/mkv_wrap.cpp b/aegisub/src/mkv_wrap.cpp index 0c017906b..9f3ff3501 100644 --- a/aegisub/src/mkv_wrap.cpp +++ b/aegisub/src/mkv_wrap.cpp @@ -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 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::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 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::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); diff --git a/aegisub/src/preferences.cpp b/aegisub/src/preferences.cpp index 6069df78e..0da40a75a 100644 --- a/aegisub/src/preferences.cpp +++ b/aegisub/src/preferences.cpp @@ -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); diff --git a/aegisub/src/subtitles_provider_libass.cpp b/aegisub/src/subtitles_provider_libass.cpp index 0de56f454..c3667b8ef 100644 --- a/aegisub/src/subtitles_provider_libass.cpp +++ b/aegisub/src/subtitles_provider_libass.cpp @@ -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