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" RelativePath="..\..\libaegisub\include\libaegisub\access.h"
> >
</File> </File>
<File
RelativePath="..\..\libaegisub\include\libaegisub\background_runner.h"
>
</File>
<File <File
RelativePath="..\..\libaegisub\include\libaegisub\charset.h" 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 "standard_paths.h"
#include "utils.h" #include "utils.h"
/// @brief Constructor
/// @param source
///
HDAudioProvider::HDAudioProvider(AudioProvider *src) { HDAudioProvider::HDAudioProvider(AudioProvider *src) {
std::auto_ptr<AudioProvider> source(src); std::auto_ptr<AudioProvider> source(src);
// Copy parameters // Copy parameters
@ -76,44 +76,34 @@ HDAudioProvider::HDAudioProvider(AudioProvider *src) {
file_cache.Open(diskCacheFilename,wxFile::read_write); file_cache.Open(diskCacheFilename,wxFile::read_write);
if (!file_cache.IsOpened()) throw AudioOpenError("Unable to write to audio disk cache."); if (!file_cache.IsOpened()) throw AudioOpenError("Unable to write to audio disk cache.");
// Start progress DialogProgress progress(AegisubApp::Get()->frame, "Load audio", "Reading to Hard Disk cache");
volatile bool canceled = false; progress.Run(bind(&HDAudioProvider::FillCache, this, src, std::tr1::placeholders::_1));
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();
} }
/// @brief Destructor
///
HDAudioProvider::~HDAudioProvider() { HDAudioProvider::~HDAudioProvider() {
file_cache.Close(); file_cache.Close();
wxRemoveFile(diskCacheFilename); wxRemoveFile(diskCacheFilename);
delete[] data; delete[] data;
} }
/// @brief Get audio void HDAudioProvider::FillCache(AudioProvider *src, agi::ProgressSink *ps) {
/// @param buf int64_t block = 4096;
/// @param start data = new char[block * channels * bytes_per_sample];
/// @param count 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 { void HDAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
// Requested beyond the length of audio // Requested beyond the length of audio
if (start+count > num_samples) { if (start+count > num_samples) {

View File

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

View File

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

View File

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

View File

@ -62,6 +62,7 @@
#include "ass_style.h" #include "ass_style.h"
#include "auto4_base.h" #include "auto4_base.h"
#include "compat.h" #include "compat.h"
#include "dialog_progress.h"
#include "include/aegisub/context.h" #include "include/aegisub/context.h"
#include "main.h" #include "main.h"
#include "standard_paths.h" #include "standard_paths.h"
@ -292,7 +293,7 @@ namespace Automation4 {
FeatureFilter::FeatureFilter(const wxString &_name, const wxString &_description, int _priority) FeatureFilter::FeatureFilter(const wxString &_name, const wxString &_description, int _priority)
: Feature(SCRIPTFEATURE_FILTER, _name) : Feature(SCRIPTFEATURE_FILTER, _name)
, AssExportFilter(_name, _description, _priority) , AssExportFilter(_name, _description, _priority)
, config_dialog(0) , config_dialog(0)
{ {
AssExportFilterChain::Register(this); AssExportFilterChain::Register(this);
} }
@ -311,7 +312,7 @@ namespace Automation4 {
/// ///
wxString FeatureFilter::GetScriptSettingsIdentifier() 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); return !filename.Right(extension.Length()).CmpNoCase(extension);
} }
// ShowConfigDialogEvent
/// DOCME
const wxEventType EVT_SHOW_CONFIG_DIALOG_t = wxNewEventType();
// ScriptConfigDialog // ScriptConfigDialog
@ -436,220 +429,72 @@ namespace Automation4 {
// ProgressSink // ProgressSink
wxDEFINE_EVENT(EVT_SHOW_CONFIG_DIALOG, wxThreadEvent);
ProgressSink::ProgressSink(agi::ProgressSink *impl, BackgroundScriptRunner *bsr)
/// @brief DOCME : impl(impl)
/// @param parent , bsr(bsr)
/// , trace_level(OPT_GET("Automation/Trace Level")->GetInt())
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)
{ {
// 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();
} }
void ProgressSink::ShowConfigDialog(ScriptConfigDialog *config_dialog)
/// @brief DOCME
///
ProgressSink::~ProgressSink()
{ {
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();
} }
BackgroundScriptRunner::BackgroundScriptRunner(wxWindow *parent, wxString const& title)
/// @brief DOCME : impl(new DialogProgress(parent, title))
/// @param evt
///
void ProgressSink::OnIdle(wxIdleEvent &evt)
{ {
// The big glossy "update display" event impl->Bind(EVT_SHOW_CONFIG_DIALOG, &BackgroundScriptRunner::OnConfigDialog, this);
DoUpdateDisplay();
if (script_finished) {
if (!debug_visible) {
EndModal(0);
} else {
cancel_button->Enable(true);
cancel_button->SetLabel(_("Close"));
SetProgress(100.0);
SetTask(_("Script completed"));
}
}
} }
BackgroundScriptRunner::~BackgroundScriptRunner()
/// @brief DOCME
/// @return
///
void ProgressSink::DoUpdateDisplay()
{ {
// 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;
} }
void BackgroundScriptRunner::OnConfigDialog(wxThreadEvent &evt)
/// @brief DOCME
/// @param _progress
///
void ProgressSink::SetProgress(float _progress)
{ {
wxMutexLocker lock(data_mutex); std::pair<ScriptConfigDialog*, wxSemaphore*> payload = evt.GetPayload<std::pair<ScriptConfigDialog*, wxSemaphore*> >();
progress = _progress;
data_updated = true; 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();
} }
void BackgroundScriptRunner::QueueEvent(wxEvent *evt) {
/// @brief DOCME wxQueueEvent(impl.get(), evt);
/// @param _task
///
void ProgressSink::SetTask(const wxString &_task)
{
wxMutexLocker lock(data_mutex);
task = _task;
data_updated = true;
} }
// Convert a function taking an Automation4::ProgressSink to one taking an
/// @brief DOCME // agi::ProgressSink so that we can pass it to an agi::BackgroundWorker
/// @param _title static void progress_sink_wrapper(std::tr1::function<void (ProgressSink*)> task, agi::ProgressSink *ps, BackgroundScriptRunner *bsr)
///
void ProgressSink::SetTitle(const wxString &_title)
{ {
wxMutexLocker lock(data_mutex); ProgressSink aps(ps, bsr);
title = _title; task(&aps);
data_updated = true;
} }
void BackgroundScriptRunner::Run(std::tr1::function<void (ProgressSink*)> task)
/// @brief DOCME
/// @param msg
///
void ProgressSink::AddDebugOutput(const wxString &msg)
{ {
wxMutexLocker lock(data_mutex); int prio = OPT_GET("Automation/Thread Priority")->GetInt();
pending_debug_output << msg; if (prio == 0) prio = 50; // normal
data_updated = true; 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) impl->Run(bind(progress_sink_wrapper, task, std::tr1::placeholders::_1, this), prio);
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();
}
} }
@ -891,12 +736,12 @@ namespace Automation4 {
while (more) { while (more) {
script_path.SetName(fn); script_path.SetName(fn);
try { try {
wxString fullpath = script_path.GetFullPath(); wxString fullpath = script_path.GetFullPath();
if (ScriptFactory::CanHandleScriptFormat(fullpath)) { if (ScriptFactory::CanHandleScriptFormat(fullpath)) {
Script *s = ScriptFactory::CreateFromFile(fullpath, true); Script *s = ScriptFactory::CreateFromFile(fullpath, true);
Add(s); Add(s);
if (!s->GetLoadedState()) error_count++; if (!s->GetLoadedState()) error_count++;
} }
} }
catch (const char *e) { catch (const char *e) {
error_count++; error_count++;
@ -1094,7 +939,7 @@ namespace Automation4 {
/// @param filename /// @param filename
/// ///
UnknownScript::UnknownScript(const wxString &filename) UnknownScript::UnknownScript(const wxString &filename)
: Script(filename) : Script(filename)
{ {
wxFileName fn(filename); wxFileName fn(filename);
name = fn.GetName(); name = fn.GetName();

View File

@ -50,6 +50,9 @@
#include <wx/timer.h> #include <wx/timer.h>
#endif #endif
#include <libaegisub/background_runner.h>
#include <libaegisub/exception.h>
#include <libaegisub/scoped_ptr.h>
#include <libaegisub/signal.h> #include <libaegisub/signal.h>
#include "ass_export_filter.h" #include "ass_export_filter.h"
@ -58,6 +61,8 @@
class AssFile; class AssFile;
class AssStyle; class AssStyle;
class DialogProgress;
class SubtitleFormat;
class wxWindow; class wxWindow;
class wxDialog; class wxDialog;
class wxStopWatch; class wxStopWatch;
@ -264,137 +269,44 @@ namespace Automation4 {
virtual void Unserialise(const wxString &serialised) { } virtual void Unserialise(const wxString &serialised) { }
}; };
class ProgressSink;
// Config dialog event class and related stuff (wx </3) class BackgroundScriptRunner {
extern const wxEventType EVT_SHOW_CONFIG_DIALOG_t; agi::scoped_ptr<DialogProgress> impl;
void OnConfigDialog(wxThreadEvent &evt);
/// DOCME
/// @class ShowConfigDialogEvent
/// @brief DOCME
///
/// DOCME
class ShowConfigDialogEvent : public wxCommandEvent {
public: public:
/// @brief DOCME void QueueEvent(wxEvent *evt);
/// @param event
/// @return
///
ShowConfigDialogEvent(const wxEventType &event = EVT_SHOW_CONFIG_DIALOG_t)
: wxCommandEvent(event)
, config_dialog(0)
, sync_sema(0) { };
void Run(std::tr1::function<void(ProgressSink*)> task);
/// @brief DOCME BackgroundScriptRunner(wxWindow *parent, wxString const& title);
/// @return ~BackgroundScriptRunner();
///
virtual wxEvent *Clone() const { return new ShowConfigDialogEvent(*this); }
/// DOCME
ScriptConfigDialog *config_dialog;
/// DOCME
wxSemaphore *sync_sema;
}; };
/// A wrapper around agi::ProgressSink which adds the ability to open
/// DOCME /// dialogs on the GUI thread
typedef void (wxEvtHandler::*ShowConfigDialogEventFunction)(ShowConfigDialogEvent&); class ProgressSink : public agi::ProgressSink {
agi::ProgressSink *impl;
BackgroundScriptRunner *bsr;
/// 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
int trace_level; int trace_level;
ProgressSink(wxWindow *parent);
virtual ~ProgressSink();
public: public:
void SetProgress(float _progress); void SetIndeterminate() { impl->SetIndeterminate(); }
void SetTask(const wxString &_task); void SetTitle(std::string const& title) { impl->SetTitle(title); }
void SetTitle(const wxString &_title); void SetMessage(std::string const& msg) { impl->SetMessage(msg); }
void AddDebugOutput(const wxString &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 /// Get the current automation trace level
volatile bool has_inited; int GetTraceLevel() const { return trace_level; }
/// DOCME ProgressSink(agi::ProgressSink *impl, BackgroundScriptRunner *bsr);
volatile bool script_finished;
DECLARE_EVENT_TABLE()
}; };
@ -511,6 +423,8 @@ namespace Automation4 {
void Reload(); void Reload();
}; };
/// Both a base class for script factories and a manager of registered
/// script factories
class ScriptFactory { class ScriptFactory {
/// Vector of loaded script engines /// Vector of loaded script engines
static std::vector<ScriptFactory*> *factories; static std::vector<ScriptFactory*> *factories;
@ -556,12 +470,8 @@ namespace Automation4 {
static const std::vector<ScriptFactory*>& GetFactories(); static const std::vector<ScriptFactory*>& GetFactories();
}; };
/// A script which represents a file not recognized by any registered
/// DOCME /// automation engines
/// @class UnknownScript
/// @brief DOCME
///
/// DOCME
class UnknownScript : public Script { class UnknownScript : public Script {
public: public:
UnknownScript(const wxString &filename); UnknownScript(const wxString &filename);

View File

@ -324,7 +324,7 @@ namespace Automation4 {
name = GetPrettyFilename(); name = GetPrettyFilename();
description = "Unknown error initialising Lua script"; description = "Unknown error initialising Lua script";
} }
} }
/// @brief DOCME /// @brief DOCME
@ -503,7 +503,7 @@ namespace Automation4 {
lua_pushnil(L); lua_pushnil(L);
return 1; return 1;
} }
} }
/// @brief DOCME /// @brief DOCME
@ -521,7 +521,7 @@ namespace Automation4 {
lua_pushnil(L); lua_pushnil(L);
return 1; return 1;
} }
} }
/// @brief DOCME /// @brief DOCME
@ -543,62 +543,31 @@ namespace Automation4 {
} }
} }
static void lua_threaded_call(ProgressSink *ps, lua_State *L, int nargs, int nresults, bool can_open_config)
// 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)
{ {
int prio = OPT_GET("Automation/Lua/Thread Priority")->GetInt(); LuaProgressSink lps(L, ps, can_open_config);
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();
}
if (lua_pcall(L, nargs, nresults, 0)) {
/// @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 the call failed, log the error here // if the call failed, log the error here
wxString errmsg(lua_tostring(L, -2), wxConvUTF8); ps->Log("\n\nLua reported a runtime error:\n");
ps->AddDebugOutput("\n\nLua reported a runtime error:\n"); ps->Log(lua_tostring(L, -1));
ps->AddDebugOutput(errmsg);
lua_pop(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); 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) void LuaFeatureMacro::Process(AssFile *subs, std::vector<int> &selected, int active, wxWindow * const progress_parent)
{ {
GetFeatureFunction(1); // 1 = processing function GetFeatureFunction(1); // 1 = processing function
// prepare function call
LuaAssFile *subsobj = new LuaAssFile(L, subs, true, true); LuaAssFile *subsobj = new LuaAssFile(L, subs, true, true);
(void) subsobj;
CreateIntegerArray(selected); // selected items CreateIntegerArray(selected); // selected items
lua_pushinteger(L, -1); // active line lua_pushinteger(L, -1); // active line
LuaProgressSink *ps = new LuaProgressSink(L, progress_parent);
ps->SetTitle(GetName());
// do call // do call
// 3 args: subtitles, selected lines, active line // 3 args: subtitles, selected lines, active line
// 1 result: new selected lines // 1 result: new selected lines
LuaThreadedCall call(L, 3, 1); LuaThreadedCall(L, 3, 1, GetName(), progress_parent, true);
ps->ShowModal(); subsobj->ProcessingComplete(GetName());
wxThread::ExitCode code = call.Wait();
(void) code; // ignore
//if (code) ThrowError();
// top of stack will be selected lines array, if any was returned // top of stack will be selected lines array, if any was returned
if (lua_istable(L, -1)) { if (lua_istable(L, -1)) {
@ -815,8 +775,6 @@ namespace Automation4 {
} }
// either way, there will be something on the stack // either way, there will be something on the stack
lua_pop(L, 1); lua_pop(L, 1);
delete ps;
} }
@ -905,24 +863,11 @@ namespace Automation4 {
assert(lua_istable(L, -1)); assert(lua_istable(L, -1));
stackcheck.check_stack(3); stackcheck.check_stack(3);
LuaProgressSink *ps = new LuaProgressSink(L, export_dialog, false); LuaThreadedCall(L, 2, 0, AssExportFilter::GetName(), 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();
stackcheck.check_stack(0); stackcheck.check_stack(0);
// Just ensure that subsobj survives until here subsobj->ProcessingComplete();
(void) subsobj;
delete ps;
} }

View File

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

View File

@ -107,7 +107,7 @@ namespace {
lua_setfield(L, -2, name); 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) 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"); return BadField(std::string("Invalid ") + expected_type + " '" + name + "' field in '" + line_clasee + "' class subtitle line");

View File

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

View File

@ -1,216 +1,236 @@
// Copyright (c) 2005, Rodrigo Braz Monteiro // Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
// All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without // Permission to use, copy, modify, and distribute this software for any
// modification, are permitted provided that the following conditions are met: // 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, // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// this list of conditions and the following disclaimer. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// * Redistributions in binary form must reproduce the above copyright notice, // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// this list of conditions and the following disclaimer in the documentation // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// and/or other materials provided with the distribution. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// * Neither the name of the Aegisub Group nor the names of its contributors // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// may be used to endorse or promote products derived from this software // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 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/
// //
// $Id$ // $Id$
/// @file dialog_progress.cpp /// @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 /// @ingroup utility
/// ///
///////////
// Headers
#include "config.h" #include "config.h"
#include "dialog_progress.h"
#include <libaegisub/exception.h>
#include "compat.h"
#include "utils.h"
#ifndef AGI_PRE #ifndef AGI_PRE
#include <wx/button.h> #include <wx/button.h>
#include <wx/gauge.h>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#endif #endif
#include "dialog_progress.h" wxDEFINE_EVENT(EVT_TITLE, wxThreadEvent);
#include "utils.h" 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) template<class T>
void SafeQueue(wxEventType type, T const& value) {
wxThreadEvent *evt = new wxThreadEvent(type);
/// @brief Constructor evt->SetPayload(value);
/// @param parent wxQueueEvent(dialog, evt);
/// @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;
} }
// 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); void SetTitle(std::string const& title) {
evt.SetInt(value); SafeQueue(EVT_TITLE, lagi_wxString(title));
AddPendingEvent(evt); }
}
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 void Log(std::string const& str) {
/// @param event SafeQueue(EVT_LOG, lagi_wxString(str));
/// }
void DialogProgress::OnUpdateProgress(wxCommandEvent &event)
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(); title = new wxStaticText(this, -1, title_text, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE | wxST_NO_AUTORESIZE);
if (gauge->GetValue() != value) gauge->SetValue(mid(0,value,100)); gauge = new wxGauge(this, -1, 100, wxDefaultPosition, wxSize(300,20));
wxMutexLocker locker(mutex); text = new wxStaticText(this, -1, message, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE | wxST_NO_AUTORESIZE);
count--; 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);
} }
void DialogProgress::Run(std::tr1::function<void(agi::ProgressSink*)> task, int priority) {
DialogProgressSink ps(this);
/// @brief Set progress this->ps = &ps;
/// @param setto new TaskRunner(task, &ps, this, priority);
/// if (ShowModal())
void DialogProgress::SetText(wxString setto) { throw agi::UserCancelException("Cancelled by user");
// Lock
bool isMain = wxIsMainThread();
if (!isMain) wxMutexGuiEnter();
// Update
text->SetLabel(setto);
wxYield();
// Unlock
if (!isMain) wxMutexGuiLeave();
} }
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();
/////////////// wxSizer *sizer = GetSizer();
// Event table if (sizer->IsShown(log_output)) {
BEGIN_EVENT_TABLE(DialogProgress,wxDialog) sizer->Hide(log_output);
EVT_BUTTON(wxID_CANCEL,DialogProgress::OnCancel) Layout();
END_EVENT_TABLE() sizer->Fit(this);
log_output->Clear();
}
/// @brief Cancel
/// @param event
///
void DialogProgress::OnCancel(wxCommandEvent &event) {
if (canceled) *canceled = true;
bool isMain = wxIsMainThread();
if (!isMain) wxMutexGuiEnter();
Destroy();
if (!isMain) wxMutexGuiLeave();
} }
void DialogProgress::OnSetTitle(wxThreadEvent &evt) {
/// @brief Thread constructor title->SetLabelText(evt.GetPayload<wxString>());
/// @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::OnSetMessage(wxThreadEvent &evt) {
text->SetLabelText(evt.GetPayload<wxString>());
/// @brief Thread destructor
///
DialogProgressThread::~DialogProgressThread() {
} }
void DialogProgress::OnSetProgress(wxThreadEvent &evt) {
gauge->SetValue(mid(0, evt.GetPayload<int>(), 100));
/// @brief Thread entry point
/// @return
///
wxThread::ExitCode DialogProgressThread::Entry() {
dialog->ShowModal();
dialog = NULL;
Delete();
return 0;
} }
void DialogProgress::OnSetIndeterminate(wxThreadEvent &evt) {
pulse_timer.Start(1000);
/// @brief Close
///
void DialogProgressThread::Close() {
wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED,wxID_CANCEL);
dialog->canceled = NULL;
dialog->GetEventHandler()->ProcessEvent(event);
} }
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(); gauge->Pulse();
} }

View File

@ -1,31 +1,16 @@
// Copyright (c) 2005, Rodrigo Braz Monteiro // Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
// All rights reserved.
// //
// Redistribution and use in source and binary forms, with or without // Permission to use, copy, modify, and distribute this software for any
// modification, are permitted provided that the following conditions are met: // 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, // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// this list of conditions and the following disclaimer. // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// * Redistributions in binary form must reproduce the above copyright notice, // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// this list of conditions and the following disclaimer in the documentation // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// and/or other materials provided with the distribution. // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// * Neither the name of the Aegisub Group nor the names of its contributors // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// may be used to endorse or promote products derived from this software // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 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/
// //
// $Id$ // $Id$
@ -34,74 +19,52 @@
/// @ingroup utility /// @ingroup utility
/// ///
///////////
// Headers
#ifndef AGI_PRE #ifndef AGI_PRE
#include <wx/dialog.h> #include <wx/dialog.h>
#include <wx/gauge.h> #include <wx/timer.h>
#include <wx/stattext.h>
#endif #endif
#include <libaegisub/background_runner.h>
#include <libaegisub/scoped_ptr.h>
class DialogProgressSink;
class wxButton;
class wxGauge;
class wxStaticText;
class wxTextCtrl;
/// DOCME /// DOCME
/// @class DialogProgress /// @class DialogProgress
/// @brief DOCME /// @brief Progress-bar dialog box for displaying during long operations
/// class DialogProgress : public wxDialog, public agi::BackgroundRunner {
/// DOCME DialogProgressSink *ps;
class DialogProgress : public wxDialog {
private:
/// DOCME wxStaticText *title;
volatile int count;
/// DOCME
int virtualMax;
/// DOCME
wxMutex mutex;
/// DOCME
wxGauge *gauge;
/// DOCME
wxStaticText *text; wxStaticText *text;
void OnCancel(wxCommandEvent &event); wxGauge *gauge;
void OnUpdateProgress(wxCommandEvent &event); 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: 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 /// BackgroundWorker implementation
volatile bool *canceled; void Run(std::tr1::function<void(agi::ProgressSink *)> task, int priority=-1);
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();
}; };

View File

@ -49,6 +49,7 @@
#include <libaegisub/log.h> #include <libaegisub/log.h>
#include "compat.h" #include "compat.h"
#include "dialog_progress.h"
#include "ffmpegsource_common.h" #include "ffmpegsource_common.h"
#include "frame_main.h" #include "frame_main.h"
#include "main.h" #include "main.h"
@ -58,26 +59,23 @@
wxMutex FFmpegSourceProvider::CleaningInProgress; wxMutex FFmpegSourceProvider::CleaningInProgress;
/// @brief Callback function that updates the indexing progress dialog /// @brief Callback function that updates the indexing progress dialog
/// @param Current The current file positition in bytes /// @param Current The current file positition in bytes
/// @param Total The total file size 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. /// @return Returns non-0 if indexing is cancelled, 0 otherwise.
/// ///
int FFMS_CC FFmpegSourceProvider::UpdateIndexingProgress(int64_t Current, int64_t Total, void *Private) { static int FFMS_CC UpdateIndexingProgress(int64_t Current, int64_t Total, void *Private) {
IndexingProgressDialog *Progress = (IndexingProgressDialog *)Private; agi::ProgressSink *ps = static_cast<agi::ProgressSink*>(Private);
ps->SetProgress(Current, Total);
if (Progress->IndexingCanceled) return ps->IsCancelled();
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;
} }
/// 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 /// @brief Does indexing of a source file
/// @param Indexer A pointer to the indexer object representing the file to be indexed /// @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; wxString MsgString;
// set up progress dialog callback // set up progress dialog callback
IndexingProgressDialog Progress; DialogProgress Progress(AegisubApp::Get()->frame, _("Indexing"), _("Reading timecodes and frame/sample data"));
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);
// index all audio tracks // index all audio tracks
FFMS_Index *Index = FFMS_DoIndexing(Indexer, Trackmask, FFMS_TRACKMASK_NONE, NULL, NULL, IndexEH, FFMS_Index *Index;
FFmpegSourceProvider::UpdateIndexingProgress, &Progress, &ErrInfo); Progress.Run(bind(DoIndexingWrapper, &Index, Indexer, Trackmask, IndexEH, std::tr1::placeholders::_1, &ErrInfo));
Progress.ProgressDialog->Destroy();
if (Progress.IndexingCanceled) {
throw agi::UserCancelException("indexing cancelled by user");
}
if (Index == NULL) { if (Index == NULL) {
MsgString.Append("Failed to index: ").Append(wxString(ErrInfo.Buffer, wxConvUTF8)); MsgString.Append("Failed to index: ").Append(wxString(ErrInfo.Buffer, wxConvUTF8));
throw MsgString; throw MsgString;

View File

@ -45,8 +45,6 @@
#include <ffms.h> #include <ffms.h>
#include "dialog_progress.h"
/// Index all tracks /// Index all tracks
#define FFMS_TRACKMASK_ALL -1 #define FFMS_TRACKMASK_ALL -1
/// Index no tracks /// Index no tracks
@ -70,18 +68,10 @@ public:
FFMS_LOG_DEBUG = 48, 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 /// Mutex preventing two cache cleaner threads from running at the same time
static wxMutex CleaningInProgress; static wxMutex CleaningInProgress;
bool CleanCache(); 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); 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); std::map<int,wxString> GetTracksOfType(FFMS_Indexer *Indexer, FFMS_TrackType Type);
int AskForTrackSelection(const std::map<int,wxString>& TrackList, FFMS_TrackType Type); int AskForTrackSelection(const std::map<int,wxString>& TrackList, FFMS_TrackType Type);

View File

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

View File

@ -68,6 +68,69 @@ public:
#define CACHESIZE 65536 #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) { void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
MkvStdIO input(filename); MkvStdIO input(filename);
char err[2048]; char err[2048];
@ -82,10 +145,6 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
wxArrayString tracksNames; wxArrayString tracksNames;
unsigned trackToRead; unsigned trackToRead;
// Haali's library variables
ulonglong startTime, endTime, filePos;
unsigned int rt, frameSize, frameFlags;
// Find tracks // Find tracks
for (unsigned track = 0; track < tracks; track++) { for (unsigned track = 0; track < tracks; track++) {
trackInfo = mkv_GetTrackInfo(file,track); trackInfo = mkv_GetTrackInfo(file,track);
@ -122,14 +181,14 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
} }
// Picked track // Picked track
// Get codec type (0 = ASS/SSA, 1 = SRT) mkv_SetTrackMask(file, ~(1 << trackToRead));
trackInfo = mkv_GetTrackInfo(file,trackToRead); trackInfo = mkv_GetTrackInfo(file,trackToRead);
wxString CodecID = wxString(trackInfo->CodecID,*wxConvCurrent); wxString CodecID = wxString(trackInfo->CodecID,*wxConvCurrent);
int codecType = 0; bool srt = CodecID == "S_TEXT/UTF8";
if (CodecID == "S_TEXT/UTF8") codecType = 1; bool ssa = CodecID == "S_TEXT/SSA";
// Read private data if it's ASS/SSA // Read private data if it's ASS/SSA
if (codecType == 0) { if (!srt) {
// Read raw data // Read raw data
trackInfo = mkv_GetTrackInfo(file,trackToRead); trackInfo = mkv_GetTrackInfo(file,trackToRead);
wxString privString((const char *)trackInfo->CodecPrivate, wxConvUTF8, trackInfo->CodecPrivateSize); 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; longlong timecodeScale = mkv_TruncFloat(trackInfo->TimecodeScale) * segInfo->TimecodeScale;
// Progress bar // Progress bar
int totalTime = int(double(segInfo->Duration) / timecodeScale); double totalTime = double(segInfo->Duration) / timecodeScale * 1000000.0;
volatile bool canceled = false; DialogProgress progress(NULL, _("Parsing Matroska"), _("Reading subtitles from Matroska file."));
DialogProgress *progress = new DialogProgress(NULL,_("Parsing Matroska"),&canceled,_("Reading subtitles from Matroska file."),0,totalTime); progress.Run(bind(read_subtitles, std::tr1::placeholders::_1, file, &input, srt, ssa, totalTime, target));
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();
} }
catch (...) { catch (...) {
mkv_Close(file); 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") }; const wxString tp_arr[3] = { _("Normal"), _("Below Normal (recommended)"), _("Lowest") };
wxArrayString tp_choice(3, tp_arr); 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") }; const wxString ar_arr[4] = { _("No scripts"), _("Subtitle-local scripts"), _("Global autoload scripts"), _("All scripts") };
wxArrayString ar_choice(4, ar_arr); 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) { static void wait_for_cache_thread(FontConfigCacheThread const * const * const cache_worker) {
if (!*cache_worker) return; if (!*cache_worker) return;
bool canceled; DialogProgress progress(AegisubApp::Get()->frame, "Updating font index", "This may take several minutes");
DialogProgress *progress = new DialogProgress(AegisubApp::Get()->frame, "", &canceled, "Caching fonts", 0, 1); progress.Run(bind(do_wait, std::tr1::placeholders::_1, cache_worker));
progress->Show();
while (*cache_worker) {
if (canceled) throw agi::UserCancelException("Font caching cancelled");
progress->Pulse();
wxYield();
wxMilliSleep(100);
}
progress->Destroy();
} }
/// @brief Constructor /// @brief Constructor