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:
parent
5439c6dae6
commit
53b6765dd8
20 changed files with 636 additions and 960 deletions
|
@ -397,6 +397,10 @@
|
|||
RelativePath="..\..\libaegisub\include\libaegisub\access.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\libaegisub\include\libaegisub\background_runner.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\libaegisub\include\libaegisub\charset.h"
|
||||
>
|
||||
|
|
87
aegisub/libaegisub/include/libaegisub/background_runner.h
Normal file
87
aegisub/libaegisub/include/libaegisub/background_runner.h
Normal 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;
|
||||
};
|
||||
}
|
|
@ -49,9 +49,9 @@
|
|||
#include "standard_paths.h"
|
||||
#include "utils.h"
|
||||
|
||||
/// @brief Constructor
|
||||
/// @param source
|
||||
///
|
||||
|
||||
|
||||
|
||||
HDAudioProvider::HDAudioProvider(AudioProvider *src) {
|
||||
std::auto_ptr<AudioProvider> source(src);
|
||||
// Copy parameters
|
||||
|
@ -76,44 +76,34 @@ HDAudioProvider::HDAudioProvider(AudioProvider *src) {
|
|||
file_cache.Open(diskCacheFilename,wxFile::read_write);
|
||||
if (!file_cache.IsOpened()) throw AudioOpenError("Unable to write to audio disk cache.");
|
||||
|
||||
// Start progress
|
||||
volatile bool canceled = false;
|
||||
DialogProgress *progress = new DialogProgress(AegisubApp::Get()->frame,"Load audio",&canceled,"Reading to Hard Disk cache",0,num_samples);
|
||||
progress->Show();
|
||||
|
||||
// Write to disk
|
||||
int block = 4096;
|
||||
data = new char[block * channels * bytes_per_sample];
|
||||
for (int64_t i=0;i<num_samples && !canceled; i+=block) {
|
||||
if (block+i > num_samples) block = num_samples - i;
|
||||
source->GetAudio(data,i,block);
|
||||
file_cache.Write(data,block * channels * bytes_per_sample);
|
||||
progress->SetProgress(i,num_samples);
|
||||
}
|
||||
file_cache.Seek(0);
|
||||
|
||||
// Finish
|
||||
if (canceled) {
|
||||
file_cache.Close();
|
||||
delete[] data;
|
||||
throw agi::UserCancelException("Audio loading cancelled by user");
|
||||
}
|
||||
progress->Destroy();
|
||||
DialogProgress progress(AegisubApp::Get()->frame, "Load audio", "Reading to Hard Disk cache");
|
||||
progress.Run(bind(&HDAudioProvider::FillCache, this, src, std::tr1::placeholders::_1));
|
||||
}
|
||||
|
||||
/// @brief Destructor
|
||||
///
|
||||
HDAudioProvider::~HDAudioProvider() {
|
||||
file_cache.Close();
|
||||
wxRemoveFile(diskCacheFilename);
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
/// @brief Get audio
|
||||
/// @param buf
|
||||
/// @param start
|
||||
/// @param count
|
||||
///
|
||||
void HDAudioProvider::FillCache(AudioProvider *src, agi::ProgressSink *ps) {
|
||||
int64_t block = 4096;
|
||||
data = new char[block * channels * bytes_per_sample];
|
||||
for (int64_t i = 0; i < num_samples; i += block) {
|
||||
block = std::min(block, num_samples - i);
|
||||
src->GetAudio(data, i, block);
|
||||
file_cache.Write(data, block * channels * bytes_per_sample);
|
||||
ps->SetProgress(i, num_samples);
|
||||
|
||||
if (ps->IsCancelled()) {
|
||||
file_cache.Close();
|
||||
wxRemoveFile(diskCacheFilename);
|
||||
delete[] data;
|
||||
}
|
||||
}
|
||||
file_cache.Seek(0);
|
||||
}
|
||||
|
||||
void HDAudioProvider::GetAudio(void *buf, int64_t start, int64_t count) const {
|
||||
// Requested beyond the length of audio
|
||||
if (start+count > num_samples) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -42,30 +42,20 @@
|
|||
#include "main.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
/// DOCME
|
||||
#define CacheBits ((22))
|
||||
|
||||
/// DOCME
|
||||
#define CacheBlockSize ((1 << CacheBits))
|
||||
|
||||
/// @brief Constructor
|
||||
/// @param source
|
||||
///
|
||||
RAMAudioProvider::RAMAudioProvider(AudioProvider *src) {
|
||||
std::auto_ptr<AudioProvider> source(src);
|
||||
// Init
|
||||
blockcache = NULL;
|
||||
blockcount = 0;
|
||||
|
||||
samples_native_endian = source->AreSamplesNativeEndian();
|
||||
|
||||
// Allocate cache
|
||||
int64_t ssize = source->GetNumSamples() * source->GetBytesPerSample();
|
||||
blockcount = (ssize + CacheBlockSize - 1) >> CacheBits;
|
||||
blockcache = new char*[blockcount];
|
||||
for (int i = 0; i < blockcount; i++) {
|
||||
blockcache[i] = NULL;
|
||||
}
|
||||
memset(blockcache, blockcount * sizeof(char*), 0);
|
||||
|
||||
// Allocate cache blocks
|
||||
try {
|
||||
|
@ -73,7 +63,7 @@ RAMAudioProvider::RAMAudioProvider(AudioProvider *src) {
|
|||
blockcache[i] = new char[std::min<size_t>(CacheBlockSize, ssize - i * CacheBlockSize)];
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
catch (std::bad_alloc const&) {
|
||||
Clear();
|
||||
throw AudioOpenError("Couldn't open audio, not enough ram available.");
|
||||
}
|
||||
|
@ -85,35 +75,27 @@ RAMAudioProvider::RAMAudioProvider(AudioProvider *src) {
|
|||
sample_rate = source->GetSampleRate();
|
||||
filename = source->GetFilename();
|
||||
|
||||
// Start progress
|
||||
volatile bool canceled = false;
|
||||
DialogProgress *progress = new DialogProgress(AegisubApp::Get()->frame,_("Load audio"),&canceled,_("Reading into RAM"),0,source->GetNumSamples());
|
||||
progress->Show();
|
||||
progress->SetProgress(0,1);
|
||||
|
||||
// Read cache
|
||||
int readsize = CacheBlockSize / source->GetBytesPerSample();
|
||||
for (int i=0;i<blockcount && !canceled; i++) {
|
||||
source->GetAudio((char*)blockcache[i],i*readsize, i == blockcount-1 ? (source->GetNumSamples() - i*readsize) : readsize);
|
||||
progress->SetProgress(i,blockcount-1);
|
||||
DialogProgress progress(AegisubApp::Get()->frame, _("Load audio"), _("Reading into RAM"));
|
||||
progress.Run(std::tr1::bind(&RAMAudioProvider::FillCache, this, src, std::tr1::placeholders::_1));
|
||||
}
|
||||
|
||||
// Clean up progress
|
||||
if (canceled) {
|
||||
Clear();
|
||||
throw agi::UserCancelException("Audio loading cancelled by user");
|
||||
}
|
||||
progress->Destroy();
|
||||
}
|
||||
|
||||
/// @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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
@ -311,7 +312,7 @@ namespace Automation4 {
|
|||
///
|
||||
wxString FeatureFilter::GetScriptSettingsIdentifier()
|
||||
{
|
||||
return inline_string_encode(wxString::Format("Automation Settings %s", GetName()));
|
||||
return inline_string_encode(wxString::Format("Automation Settings %s", AssExportFilter::GetName()));
|
||||
}
|
||||
|
||||
|
||||
|
@ -395,14 +396,6 @@ namespace Automation4 {
|
|||
return !filename.Right(extension.Length()).CmpNoCase(extension);
|
||||
}
|
||||
|
||||
|
||||
// ShowConfigDialogEvent
|
||||
|
||||
|
||||
/// DOCME
|
||||
const wxEventType EVT_SHOW_CONFIG_DIALOG_t = wxNewEventType();
|
||||
|
||||
|
||||
// ScriptConfigDialog
|
||||
|
||||
|
||||
|
@ -436,220 +429,72 @@ namespace Automation4 {
|
|||
|
||||
|
||||
// ProgressSink
|
||||
wxDEFINE_EVENT(EVT_SHOW_CONFIG_DIALOG, wxThreadEvent);
|
||||
|
||||
|
||||
/// @brief DOCME
|
||||
/// @param parent
|
||||
///
|
||||
ProgressSink::ProgressSink(wxWindow *parent)
|
||||
: wxDialog(parent, -1, "Automation", wxDefaultPosition, wxDefaultSize, wxBORDER_RAISED)
|
||||
, debug_visible(false)
|
||||
, data_updated(false)
|
||||
, cancelled(false)
|
||||
, has_inited(false)
|
||||
, script_finished(false)
|
||||
ProgressSink::ProgressSink(agi::ProgressSink *impl, BackgroundScriptRunner *bsr)
|
||||
: impl(impl)
|
||||
, bsr(bsr)
|
||||
, trace_level(OPT_GET("Automation/Trace Level")->GetInt())
|
||||
{
|
||||
// make the controls
|
||||
progress_display = new wxGauge(this, -1, 1000, wxDefaultPosition, wxSize(300, 20));
|
||||
title_display = new wxStaticText(this, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE|wxST_NO_AUTORESIZE);
|
||||
task_display = new wxStaticText(this, -1, "", wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE|wxST_NO_AUTORESIZE);
|
||||
cancel_button = new wxButton(this, wxID_CANCEL);
|
||||
debug_output = new wxTextCtrl(this, -1, "", wxDefaultPosition, wxSize(300, 120), wxTE_MULTILINE|wxTE_READONLY);
|
||||
|
||||
// put it in a sizer
|
||||
sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(title_display, 0, wxEXPAND | wxALL, 5);
|
||||
sizer->Add(progress_display, 0, wxALL&~wxTOP, 5);
|
||||
sizer->Add(task_display, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
|
||||
sizer->Add(cancel_button, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT | wxBOTTOM, 5);
|
||||
sizer->Add(debug_output, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
|
||||
sizer->Show(debug_output, false);
|
||||
|
||||
// make the title a slightly larger font
|
||||
wxFont title_font = title_display->GetFont();
|
||||
int fontsize = title_font.GetPointSize();
|
||||
title_font.SetPointSize(fontsize + fontsize/4 + fontsize/8);
|
||||
title_font.SetWeight(wxFONTWEIGHT_BOLD);
|
||||
title_display->SetFont(title_font);
|
||||
|
||||
// Set up a timer to regularly update the status
|
||||
// It doesn't need an event handler attached, as just a the timer in itself
|
||||
// will ensure that the idle event is fired
|
||||
update_timer = new wxTimer();
|
||||
update_timer->Start(50, false);
|
||||
|
||||
sizer->SetSizeHints(this);
|
||||
SetSizer(sizer);
|
||||
Center();
|
||||
|
||||
// Init trace level
|
||||
trace_level = OPT_GET("Automation/Trace Level")->GetInt();
|
||||
}
|
||||
|
||||
|
||||
/// @brief DOCME
|
||||
///
|
||||
ProgressSink::~ProgressSink()
|
||||
void ProgressSink::ShowConfigDialog(ScriptConfigDialog *config_dialog)
|
||||
{
|
||||
delete update_timer;
|
||||
wxSemaphore sema(0, 1);
|
||||
wxThreadEvent *evt = new wxThreadEvent(EVT_SHOW_CONFIG_DIALOG);
|
||||
evt->SetPayload(std::make_pair(config_dialog, &sema));
|
||||
bsr->QueueEvent(evt);
|
||||
sema.Wait();
|
||||
}
|
||||
|
||||
|
||||
/// @brief DOCME
|
||||
/// @param evt
|
||||
///
|
||||
void ProgressSink::OnIdle(wxIdleEvent &evt)
|
||||
BackgroundScriptRunner::BackgroundScriptRunner(wxWindow *parent, wxString const& title)
|
||||
: impl(new DialogProgress(parent, title))
|
||||
{
|
||||
// The big glossy "update display" event
|
||||
DoUpdateDisplay();
|
||||
|
||||
if (script_finished) {
|
||||
if (!debug_visible) {
|
||||
EndModal(0);
|
||||
} else {
|
||||
cancel_button->Enable(true);
|
||||
cancel_button->SetLabel(_("Close"));
|
||||
SetProgress(100.0);
|
||||
SetTask(_("Script completed"));
|
||||
}
|
||||
}
|
||||
impl->Bind(EVT_SHOW_CONFIG_DIALOG, &BackgroundScriptRunner::OnConfigDialog, this);
|
||||
}
|
||||
|
||||
|
||||
/// @brief DOCME
|
||||
/// @return
|
||||
///
|
||||
void ProgressSink::DoUpdateDisplay()
|
||||
BackgroundScriptRunner::~BackgroundScriptRunner()
|
||||
{
|
||||
// If debug output isn't handled before the test for script_finished later,
|
||||
// there might actually be some debug output but the debug_visible flag won't
|
||||
// be set before the dialog closes itself.
|
||||
wxMutexLocker lock(data_mutex);
|
||||
if (!data_updated) return;
|
||||
if (!pending_debug_output.IsEmpty()) {
|
||||
if (!debug_visible) {
|
||||
sizer->Show(debug_output, true);
|
||||
Layout();
|
||||
sizer->Fit(this);
|
||||
|
||||
debug_visible = true;
|
||||
}
|
||||
|
||||
*debug_output << pending_debug_output;
|
||||
debug_output->SetInsertionPointEnd();
|
||||
|
||||
pending_debug_output = "";
|
||||
}
|
||||
|
||||
progress_display->SetValue((int)(progress*10));
|
||||
task_display->SetLabel(task);
|
||||
title_display->SetLabel(title);
|
||||
data_updated = false;
|
||||
}
|
||||
|
||||
|
||||
/// @brief DOCME
|
||||
/// @param _progress
|
||||
///
|
||||
void ProgressSink::SetProgress(float _progress)
|
||||
void BackgroundScriptRunner::OnConfigDialog(wxThreadEvent &evt)
|
||||
{
|
||||
wxMutexLocker lock(data_mutex);
|
||||
progress = _progress;
|
||||
data_updated = true;
|
||||
}
|
||||
std::pair<ScriptConfigDialog*, wxSemaphore*> payload = evt.GetPayload<std::pair<ScriptConfigDialog*, wxSemaphore*> >();
|
||||
|
||||
|
||||
/// @brief DOCME
|
||||
/// @param _task
|
||||
///
|
||||
void ProgressSink::SetTask(const wxString &_task)
|
||||
{
|
||||
wxMutexLocker lock(data_mutex);
|
||||
task = _task;
|
||||
data_updated = true;
|
||||
}
|
||||
|
||||
|
||||
/// @brief DOCME
|
||||
/// @param _title
|
||||
///
|
||||
void ProgressSink::SetTitle(const wxString &_title)
|
||||
{
|
||||
wxMutexLocker lock(data_mutex);
|
||||
title = _title;
|
||||
data_updated = true;
|
||||
}
|
||||
|
||||
|
||||
/// @brief DOCME
|
||||
/// @param msg
|
||||
///
|
||||
void ProgressSink::AddDebugOutput(const wxString &msg)
|
||||
{
|
||||
wxMutexLocker lock(data_mutex);
|
||||
pending_debug_output << msg;
|
||||
data_updated = true;
|
||||
}
|
||||
|
||||
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
|
||||
wxDialog w(impl.get(), -1, impl->GetTitle()); // 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
|
||||
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();
|
||||
evt.config_dialog->ReadBack();
|
||||
evt.config_dialog->DeleteWindow();
|
||||
delete w;
|
||||
} else {
|
||||
wxMessageBox("Uh... no config dialog?");
|
||||
w.SetSizerAndFit(s);
|
||||
w.CenterOnParent();
|
||||
w.ShowModal();
|
||||
payload.first->ReadBack();
|
||||
payload.first->DeleteWindow();
|
||||
|
||||
payload.second->Post();
|
||||
}
|
||||
|
||||
// See note in auto4_base.h
|
||||
if (evt.sync_sema) {
|
||||
evt.sync_sema->Post();
|
||||
void BackgroundScriptRunner::QueueEvent(wxEvent *evt) {
|
||||
wxQueueEvent(impl.get(), evt);
|
||||
}
|
||||
|
||||
// Convert a function taking an Automation4::ProgressSink to one taking an
|
||||
// agi::ProgressSink so that we can pass it to an agi::BackgroundWorker
|
||||
static void progress_sink_wrapper(std::tr1::function<void (ProgressSink*)> task, agi::ProgressSink *ps, BackgroundScriptRunner *bsr)
|
||||
{
|
||||
ProgressSink aps(ps, bsr);
|
||||
task(&aps);
|
||||
}
|
||||
|
||||
void BackgroundScriptRunner::Run(std::tr1::function<void (ProgressSink*)> task)
|
||||
{
|
||||
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
|
||||
|
||||
impl->Run(bind(progress_sink_wrapper, task, std::tr1::placeholders::_1, this), prio);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -50,6 +50,9 @@
|
|||
#include <wx/timer.h>
|
||||
#endif
|
||||
|
||||
#include <libaegisub/background_runner.h>
|
||||
#include <libaegisub/exception.h>
|
||||
#include <libaegisub/scoped_ptr.h>
|
||||
#include <libaegisub/signal.h>
|
||||
|
||||
#include "ass_export_filter.h"
|
||||
|
@ -58,6 +61,8 @@
|
|||
|
||||
class AssFile;
|
||||
class AssStyle;
|
||||
class DialogProgress;
|
||||
class SubtitleFormat;
|
||||
class wxWindow;
|
||||
class wxDialog;
|
||||
class wxStopWatch;
|
||||
|
@ -264,137 +269,44 @@ namespace Automation4 {
|
|||
virtual void Unserialise(const wxString &serialised) { }
|
||||
};
|
||||
|
||||
class ProgressSink;
|
||||
|
||||
// Config dialog event class and related stuff (wx </3)
|
||||
extern const wxEventType EVT_SHOW_CONFIG_DIALOG_t;
|
||||
class BackgroundScriptRunner {
|
||||
agi::scoped_ptr<DialogProgress> impl;
|
||||
|
||||
|
||||
/// DOCME
|
||||
/// @class ShowConfigDialogEvent
|
||||
/// @brief DOCME
|
||||
///
|
||||
/// DOCME
|
||||
class ShowConfigDialogEvent : public wxCommandEvent {
|
||||
void OnConfigDialog(wxThreadEvent &evt);
|
||||
public:
|
||||
|
||||
/// @brief DOCME
|
||||
/// @param event
|
||||
/// @return
|
||||
///
|
||||
ShowConfigDialogEvent(const wxEventType &event = EVT_SHOW_CONFIG_DIALOG_t)
|
||||
: wxCommandEvent(event)
|
||||
, config_dialog(0)
|
||||
, sync_sema(0) { };
|
||||
void QueueEvent(wxEvent *evt);
|
||||
|
||||
void Run(std::tr1::function<void(ProgressSink*)> task);
|
||||
|
||||
/// @brief DOCME
|
||||
/// @return
|
||||
///
|
||||
virtual wxEvent *Clone() const { return new ShowConfigDialogEvent(*this); }
|
||||
|
||||
|
||||
/// DOCME
|
||||
ScriptConfigDialog *config_dialog;
|
||||
|
||||
/// DOCME
|
||||
wxSemaphore *sync_sema;
|
||||
BackgroundScriptRunner(wxWindow *parent, wxString const& title);
|
||||
~BackgroundScriptRunner();
|
||||
};
|
||||
|
||||
|
||||
/// DOCME
|
||||
typedef void (wxEvtHandler::*ShowConfigDialogEventFunction)(ShowConfigDialogEvent&);
|
||||
|
||||
|
||||
/// DOCME
|
||||
#define EVT_SHOW_CONFIG_DIALOG(fn) DECLARE_EVENT_TABLE_ENTRY( EVT_SHOW_CONFIG_DIALOG_t, -1, -1, (wxObjectEventFunction)(wxEventFunction)(ShowConfigDialogEventFunction)&fn, (wxObject*)0 ),
|
||||
|
||||
|
||||
|
||||
/// DOCME
|
||||
/// @class ProgressSink
|
||||
/// @brief DOCME
|
||||
///
|
||||
/// DOCME
|
||||
class ProgressSink : public wxDialog {
|
||||
private:
|
||||
|
||||
/// DOCME
|
||||
wxBoxSizer *sizer;
|
||||
|
||||
/// DOCME
|
||||
wxGauge *progress_display;
|
||||
|
||||
/// DOCME
|
||||
wxButton *cancel_button;
|
||||
|
||||
/// DOCME
|
||||
wxStaticText *title_display;
|
||||
|
||||
/// DOCME
|
||||
wxStaticText *task_display;
|
||||
|
||||
/// DOCME
|
||||
wxTextCtrl *debug_output;
|
||||
|
||||
|
||||
/// DOCME
|
||||
volatile bool debug_visible;
|
||||
|
||||
/// DOCME
|
||||
volatile bool data_updated;
|
||||
|
||||
|
||||
/// DOCME
|
||||
float progress;
|
||||
|
||||
/// DOCME
|
||||
wxString task;
|
||||
|
||||
/// DOCME
|
||||
wxString title;
|
||||
|
||||
/// DOCME
|
||||
wxString pending_debug_output;
|
||||
|
||||
/// DOCME
|
||||
wxMutex data_mutex;
|
||||
|
||||
|
||||
/// DOCME
|
||||
wxTimer *update_timer;
|
||||
|
||||
void OnCancel(wxCommandEvent &evt);
|
||||
void OnInit(wxInitDialogEvent &evt);
|
||||
void OnIdle(wxIdleEvent &evt);
|
||||
void OnConfigDialog(ShowConfigDialogEvent &evt);
|
||||
|
||||
void DoUpdateDisplay();
|
||||
|
||||
protected:
|
||||
|
||||
/// DOCME
|
||||
volatile bool cancelled;
|
||||
|
||||
/// DOCME
|
||||
/// A wrapper around agi::ProgressSink which adds the ability to open
|
||||
/// dialogs on the GUI thread
|
||||
class ProgressSink : public agi::ProgressSink {
|
||||
agi::ProgressSink *impl;
|
||||
BackgroundScriptRunner *bsr;
|
||||
int trace_level;
|
||||
|
||||
ProgressSink(wxWindow *parent);
|
||||
virtual ~ProgressSink();
|
||||
|
||||
public:
|
||||
void SetProgress(float _progress);
|
||||
void SetTask(const wxString &_task);
|
||||
void SetTitle(const wxString &_title);
|
||||
void AddDebugOutput(const wxString &msg);
|
||||
void SetIndeterminate() { impl->SetIndeterminate(); }
|
||||
void SetTitle(std::string const& title) { impl->SetTitle(title); }
|
||||
void SetMessage(std::string const& msg) { impl->SetMessage(msg); }
|
||||
void SetProgress(int cur, int max) { impl->SetProgress(cur, max); }
|
||||
void Log(std::string const& str) { impl->Log(str); }
|
||||
bool IsCancelled() { return impl->IsCancelled(); }
|
||||
|
||||
/// Show the passed dialog on the GUI thread, blocking the calling
|
||||
/// thread until it closes
|
||||
void ShowConfigDialog(ScriptConfigDialog *config_dialog);
|
||||
|
||||
/// DOCME
|
||||
volatile bool has_inited;
|
||||
/// Get the current automation trace level
|
||||
int GetTraceLevel() const { return trace_level; }
|
||||
|
||||
/// DOCME
|
||||
volatile bool script_finished;
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
ProgressSink(agi::ProgressSink *impl, BackgroundScriptRunner *bsr);
|
||||
};
|
||||
|
||||
|
||||
|
@ -511,6 +423,8 @@ namespace Automation4 {
|
|||
void Reload();
|
||||
};
|
||||
|
||||
/// Both a base class for script factories and a manager of registered
|
||||
/// script factories
|
||||
class ScriptFactory {
|
||||
/// Vector of loaded script engines
|
||||
static std::vector<ScriptFactory*> *factories;
|
||||
|
@ -556,12 +470,8 @@ namespace Automation4 {
|
|||
static const std::vector<ScriptFactory*>& GetFactories();
|
||||
};
|
||||
|
||||
|
||||
/// DOCME
|
||||
/// @class UnknownScript
|
||||
/// @brief DOCME
|
||||
///
|
||||
/// DOCME
|
||||
/// A script which represents a file not recognized by any registered
|
||||
/// automation engines
|
||||
class UnknownScript : public Script {
|
||||
public:
|
||||
UnknownScript(const wxString &filename);
|
||||
|
|
|
@ -543,62 +543,31 @@ namespace Automation4 {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// LuaThreadedCall
|
||||
|
||||
|
||||
/// @brief DOCME
|
||||
/// @param _L
|
||||
/// @param _nargs
|
||||
/// @param _nresults
|
||||
///
|
||||
LuaThreadedCall::LuaThreadedCall(lua_State *_L, int _nargs, int _nresults)
|
||||
: wxThread(wxTHREAD_JOINABLE)
|
||||
, L(_L)
|
||||
, nargs(_nargs)
|
||||
, nresults(_nresults)
|
||||
static void lua_threaded_call(ProgressSink *ps, lua_State *L, int nargs, int nresults, bool can_open_config)
|
||||
{
|
||||
int prio = OPT_GET("Automation/Lua/Thread Priority")->GetInt();
|
||||
if (prio == 0) prio = 50; // normal
|
||||
else if (prio == 1) prio = 30; // below normal
|
||||
else if (prio == 2) prio = 10; // lowest
|
||||
else prio = 50; // fallback normal
|
||||
Create();
|
||||
SetPriority(prio);
|
||||
Run();
|
||||
}
|
||||
LuaProgressSink lps(L, ps, can_open_config);
|
||||
|
||||
|
||||
/// @brief DOCME
|
||||
/// @return
|
||||
///
|
||||
wxThread::ExitCode LuaThreadedCall::Entry()
|
||||
{
|
||||
int result = lua_pcall(L, nargs, nresults, 0);
|
||||
|
||||
// see if there's a progress sink window to close
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, "progress_sink");
|
||||
if (lua_isuserdata(L, -1)) {
|
||||
LuaProgressSink *ps = LuaProgressSink::GetObjPointer(L, -1);
|
||||
|
||||
if (result) {
|
||||
if (lua_pcall(L, nargs, nresults, 0)) {
|
||||
// if the call failed, log the error here
|
||||
wxString errmsg(lua_tostring(L, -2), wxConvUTF8);
|
||||
ps->AddDebugOutput("\n\nLua reported a runtime error:\n");
|
||||
ps->AddDebugOutput(errmsg);
|
||||
ps->Log("\n\nLua reported a runtime error:\n");
|
||||
ps->Log(lua_tostring(L, -1));
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
// don't bother protecting this with a mutex, it should be safe enough like this
|
||||
ps->script_finished = true;
|
||||
// tell wx to run its idle-events now, just to make the progress window notice earlier that we're done
|
||||
wxWakeUpIdle();
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_gc(L, LUA_GCCOLLECT, 0);
|
||||
if (result) return (wxThread::ExitCode) 1;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
|
||||
// LuaThreadedCall
|
||||
void LuaThreadedCall(lua_State *L, int nargs, int nresults, wxString const& title, wxWindow *parent, bool can_open_config)
|
||||
{
|
||||
BackgroundScriptRunner bsr(parent, title);
|
||||
try {
|
||||
bsr.Run(bind(lua_threaded_call, std::tr1::placeholders::_1, L, nargs, nresults, can_open_config));
|
||||
}
|
||||
catch (agi::UserCancelException const&) {
|
||||
/// @todo perhaps this needs to continue up for exporting?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -779,25 +748,16 @@ namespace Automation4 {
|
|||
void LuaFeatureMacro::Process(AssFile *subs, std::vector<int> &selected, int active, wxWindow * const progress_parent)
|
||||
{
|
||||
GetFeatureFunction(1); // 1 = processing function
|
||||
|
||||
// prepare function call
|
||||
LuaAssFile *subsobj = new LuaAssFile(L, subs, true, true);
|
||||
(void) subsobj;
|
||||
CreateIntegerArray(selected); // selected items
|
||||
lua_pushinteger(L, -1); // active line
|
||||
|
||||
LuaProgressSink *ps = new LuaProgressSink(L, progress_parent);
|
||||
ps->SetTitle(GetName());
|
||||
|
||||
// do call
|
||||
// 3 args: subtitles, selected lines, active line
|
||||
// 1 result: new selected lines
|
||||
LuaThreadedCall call(L, 3, 1);
|
||||
LuaThreadedCall(L, 3, 1, GetName(), progress_parent, true);
|
||||
|
||||
ps->ShowModal();
|
||||
wxThread::ExitCode code = call.Wait();
|
||||
(void) code; // ignore
|
||||
//if (code) ThrowError();
|
||||
subsobj->ProcessingComplete(GetName());
|
||||
|
||||
// top of stack will be selected lines array, if any was returned
|
||||
if (lua_istable(L, -1)) {
|
||||
|
@ -815,8 +775,6 @@ namespace Automation4 {
|
|||
}
|
||||
// either way, there will be something on the stack
|
||||
lua_pop(L, 1);
|
||||
|
||||
delete ps;
|
||||
}
|
||||
|
||||
|
||||
|
@ -905,24 +863,11 @@ namespace Automation4 {
|
|||
assert(lua_istable(L, -1));
|
||||
stackcheck.check_stack(3);
|
||||
|
||||
LuaProgressSink *ps = new LuaProgressSink(L, export_dialog, false);
|
||||
ps->SetTitle(GetName());
|
||||
stackcheck.check_stack(3);
|
||||
|
||||
// do call
|
||||
LuaThreadedCall call(L, 2, 0);
|
||||
|
||||
ps->ShowModal();
|
||||
wxThread::ExitCode code = call.Wait();
|
||||
(void) code;
|
||||
//if (code) ThrowError();
|
||||
LuaThreadedCall(L, 2, 0, AssExportFilter::GetName(), export_dialog, false);
|
||||
|
||||
stackcheck.check_stack(0);
|
||||
|
||||
// Just ensure that subsobj survives until here
|
||||
(void) subsobj;
|
||||
|
||||
delete ps;
|
||||
subsobj->ProcessingComplete();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -44,45 +44,50 @@
|
|||
#include <lua.hpp>
|
||||
#endif
|
||||
|
||||
static void push_closure(lua_State *L, const char *name, lua_CFunction fn) {
|
||||
lua_pushvalue(L, -3);
|
||||
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);
|
||||
}
|
||||
|
||||
namespace Automation4 {
|
||||
LuaProgressSink::LuaProgressSink(lua_State *L, wxWindow *parent, bool allow_config_dialog)
|
||||
: ProgressSink(parent)
|
||||
, L(L)
|
||||
void set_field_to_nil(lua_State *L, int idx, const char *name)
|
||||
{
|
||||
LuaProgressSink **ud = (LuaProgressSink**)lua_newuserdata(L, sizeof(LuaProgressSink*));
|
||||
*ud = this;
|
||||
lua_pushnil(L);
|
||||
lua_setfield(L, idx, name);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Automation4 {
|
||||
LuaProgressSink::LuaProgressSink(lua_State *L, ProgressSink *ps, bool allow_config_dialog)
|
||||
: L(L)
|
||||
{
|
||||
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);
|
||||
|
|
|
@ -1,216 +1,236 @@
|
|||
// Copyright (c) 2005, Rodrigo Braz Monteiro
|
||||
// All rights reserved.
|
||||
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
// * Neither the name of the Aegisub Group nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
/// @file dialog_progress.cpp
|
||||
/// @brief Progress-bar dialogue box for displaying during long operations
|
||||
/// @brief Progress-bar dialog box for displaying during long operations
|
||||
/// @ingroup utility
|
||||
///
|
||||
|
||||
|
||||
///////////
|
||||
// Headers
|
||||
#include "config.h"
|
||||
|
||||
#include "dialog_progress.h"
|
||||
|
||||
#include <libaegisub/exception.h>
|
||||
|
||||
#include "compat.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifndef AGI_PRE
|
||||
#include <wx/button.h>
|
||||
#include <wx/gauge.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#endif
|
||||
|
||||
#include "dialog_progress.h"
|
||||
#include "utils.h"
|
||||
wxDEFINE_EVENT(EVT_TITLE, wxThreadEvent);
|
||||
wxDEFINE_EVENT(EVT_MESSAGE, wxThreadEvent);
|
||||
wxDEFINE_EVENT(EVT_PROGRESS, wxThreadEvent);
|
||||
wxDEFINE_EVENT(EVT_INDETERMINATE, wxThreadEvent);
|
||||
wxDEFINE_EVENT(EVT_LOG, wxThreadEvent);
|
||||
wxDEFINE_EVENT(EVT_COMPLETE, wxThreadEvent);
|
||||
|
||||
class DialogProgressSink : public agi::ProgressSink {
|
||||
DialogProgress *dialog;
|
||||
bool cancelled;
|
||||
wxMutex cancelled_mutex;
|
||||
|
||||
DEFINE_EVENT_TYPE(wxEVT_PROGRESS_UPDATE)
|
||||
template<class T>
|
||||
void SafeQueue(wxEventType type, T const& value) {
|
||||
wxThreadEvent *evt = new wxThreadEvent(type);
|
||||
evt->SetPayload(value);
|
||||
wxQueueEvent(dialog, evt);
|
||||
}
|
||||
|
||||
|
||||
/// @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*/)
|
||||
public:
|
||||
DialogProgressSink(DialogProgress *dialog)
|
||||
: dialog(dialog)
|
||||
, cancelled(false)
|
||||
{
|
||||
// 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;
|
||||
void SetTitle(std::string const& title) {
|
||||
SafeQueue(EVT_TITLE, lagi_wxString(title));
|
||||
}
|
||||
|
||||
// Otherwise, go on
|
||||
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));
|
||||
}
|
||||
|
||||
void Log(std::string const& str) {
|
||||
SafeQueue(EVT_LOG, lagi_wxString(str));
|
||||
}
|
||||
|
||||
bool IsCancelled() {
|
||||
wxMutexLocker l(cancelled_mutex);
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
void Cancel() {
|
||||
wxMutexLocker l(cancelled_mutex);
|
||||
cancelled = true;
|
||||
}
|
||||
|
||||
void SetIndeterminate() {
|
||||
wxQueueEvent(dialog, new wxThreadEvent(EVT_INDETERMINATE));
|
||||
}
|
||||
};
|
||||
|
||||
class TaskRunner : public wxThread {
|
||||
std::tr1::function<void(agi::ProgressSink*)> task;
|
||||
agi::ProgressSink *ps;
|
||||
wxDialog *dialog;
|
||||
|
||||
public:
|
||||
TaskRunner(std::tr1::function<void(agi::ProgressSink*)> task, agi::ProgressSink *ps, wxDialog *dialog, int priority)
|
||||
: task(task)
|
||||
, ps(ps)
|
||||
, dialog(dialog)
|
||||
{
|
||||
wxMutexLocker locker(mutex);
|
||||
if (count >= 2) return;
|
||||
else count++;
|
||||
Create();
|
||||
if (priority != -1)
|
||||
SetPriority(priority);
|
||||
Run();
|
||||
}
|
||||
|
||||
wxCommandEvent evt(wxEVT_PROGRESS_UPDATE,0);
|
||||
evt.SetInt(value);
|
||||
AddPendingEvent(evt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// @brief Update progress
|
||||
/// @param event
|
||||
///
|
||||
void DialogProgress::OnUpdateProgress(wxCommandEvent &event)
|
||||
{
|
||||
int value = event.GetInt();
|
||||
if (gauge->GetValue() != value) gauge->SetValue(mid(0,value,100));
|
||||
wxMutexLocker locker(mutex);
|
||||
count--;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// @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();
|
||||
}
|
||||
|
||||
|
||||
///////////////
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
||||
/// @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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// @brief Thread destructor
|
||||
///
|
||||
DialogProgressThread::~DialogProgressThread() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// @brief Thread entry point
|
||||
/// @return
|
||||
///
|
||||
wxThread::ExitCode DialogProgressThread::Entry() {
|
||||
dialog->ShowModal();
|
||||
dialog = NULL;
|
||||
Delete();
|
||||
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())
|
||||
{
|
||||
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);
|
||||
|
||||
/// @brief Close
|
||||
///
|
||||
void DialogProgressThread::Close() {
|
||||
wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED,wxID_CANCEL);
|
||||
dialog->canceled = NULL;
|
||||
dialog->GetEventHandler()->ProcessEvent(event);
|
||||
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::Pulse() {
|
||||
void DialogProgress::Run(std::tr1::function<void(agi::ProgressSink*)> task, int priority) {
|
||||
DialogProgressSink ps(this);
|
||||
this->ps = &ps;
|
||||
new TaskRunner(task, &ps, this, priority);
|
||||
if (ShowModal())
|
||||
throw agi::UserCancelException("Cancelled by user");
|
||||
}
|
||||
|
||||
void DialogProgress::OnShow(wxShowEvent&) {
|
||||
// Restore the cancel button in case it was previously switched to a close
|
||||
// button
|
||||
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &DialogProgress::OnCancel, this, wxID_CANCEL);
|
||||
cancel_button->SetLabelText(_("Cancel"));
|
||||
cancel_button->Enable();
|
||||
|
||||
wxSizer *sizer = GetSizer();
|
||||
if (sizer->IsShown(log_output)) {
|
||||
sizer->Hide(log_output);
|
||||
Layout();
|
||||
sizer->Fit(this);
|
||||
log_output->Clear();
|
||||
}
|
||||
}
|
||||
|
||||
void DialogProgress::OnSetTitle(wxThreadEvent &evt) {
|
||||
title->SetLabelText(evt.GetPayload<wxString>());
|
||||
}
|
||||
|
||||
void DialogProgress::OnSetMessage(wxThreadEvent &evt) {
|
||||
text->SetLabelText(evt.GetPayload<wxString>());
|
||||
}
|
||||
|
||||
void DialogProgress::OnSetProgress(wxThreadEvent &evt) {
|
||||
gauge->SetValue(mid(0, evt.GetPayload<int>(), 100));
|
||||
}
|
||||
|
||||
void DialogProgress::OnSetIndeterminate(wxThreadEvent &evt) {
|
||||
pulse_timer.Start(1000);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -1,31 +1,16 @@
|
|||
// Copyright (c) 2005, Rodrigo Braz Monteiro
|
||||
// All rights reserved.
|
||||
// Copyright (c) 2011, Thomas Goyne <plorkyeran@aegisub.org>
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
// * Neither the name of the Aegisub Group nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Aegisub Project http://www.aegisub.org/
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
// $Id$
|
||||
|
||||
|
@ -34,74 +19,52 @@
|
|||
/// @ingroup utility
|
||||
///
|
||||
|
||||
|
||||
|
||||
|
||||
///////////
|
||||
// Headers
|
||||
#ifndef AGI_PRE
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/gauge.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/timer.h>
|
||||
#endif
|
||||
|
||||
#include <libaegisub/background_runner.h>
|
||||
#include <libaegisub/scoped_ptr.h>
|
||||
|
||||
class DialogProgressSink;
|
||||
class wxButton;
|
||||
class wxGauge;
|
||||
class wxStaticText;
|
||||
class wxTextCtrl;
|
||||
|
||||
/// DOCME
|
||||
/// @class DialogProgress
|
||||
/// @brief DOCME
|
||||
///
|
||||
/// DOCME
|
||||
class DialogProgress : public wxDialog {
|
||||
private:
|
||||
/// @brief Progress-bar dialog box for displaying during long operations
|
||||
class DialogProgress : public wxDialog, public agi::BackgroundRunner {
|
||||
DialogProgressSink *ps;
|
||||
|
||||
/// DOCME
|
||||
volatile int count;
|
||||
|
||||
/// DOCME
|
||||
int virtualMax;
|
||||
|
||||
/// DOCME
|
||||
wxMutex mutex;
|
||||
|
||||
|
||||
/// DOCME
|
||||
wxGauge *gauge;
|
||||
|
||||
/// DOCME
|
||||
wxStaticText *title;
|
||||
wxStaticText *text;
|
||||
void OnCancel(wxCommandEvent &event);
|
||||
void OnUpdateProgress(wxCommandEvent &event);
|
||||
wxGauge *gauge;
|
||||
wxButton *cancel_button;
|
||||
wxTextCtrl *log_output;
|
||||
|
||||
wxTimer pulse_timer;
|
||||
|
||||
void OnSetTitle(wxThreadEvent &evt);
|
||||
void OnSetMessage(wxThreadEvent &evt);
|
||||
void OnSetProgress(wxThreadEvent &evt);
|
||||
void OnSetIndeterminate(wxThreadEvent &evt);
|
||||
void OnLog(wxThreadEvent &evt);
|
||||
void OnComplete(wxThreadEvent &evt);
|
||||
|
||||
void OnShow(wxShowEvent&);
|
||||
void OnCancel(wxCommandEvent &);
|
||||
void OnPulseTimer(wxTimerEvent&);
|
||||
|
||||
public:
|
||||
/// Constructor
|
||||
/// @param parent Parent window of the dialog
|
||||
/// @param title Initial title of the dialog
|
||||
/// @param message Initial message of the dialog
|
||||
DialogProgress(wxWindow *parent, wxString const& title="", wxString const& message="");
|
||||
|
||||
/// DOCME
|
||||
volatile bool *canceled;
|
||||
|
||||
DialogProgress(wxWindow *parent,wxString title,volatile bool *cancel,wxString message,int cur,int max);
|
||||
void SetProgress(int cur,int max);
|
||||
void SetText(wxString text);
|
||||
void Run();
|
||||
void Pulse();
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
};
|
||||
|
||||
|
||||
|
||||
/// DOCME
|
||||
/// @class DialogProgressThread
|
||||
/// @brief DOCME
|
||||
///
|
||||
/// DOCME
|
||||
class DialogProgressThread : public wxThread {
|
||||
DialogProgressThread(wxWindow *parent,wxString title,volatile bool *canceled,wxString message,int cur,int max);
|
||||
|
||||
public:
|
||||
|
||||
/// DOCME
|
||||
DialogProgress *dialog;
|
||||
|
||||
~DialogProgressThread();
|
||||
wxThread::ExitCode Entry();
|
||||
void Close();
|
||||
/// BackgroundWorker implementation
|
||||
void Run(std::tr1::function<void(agi::ProgressSink *)> task, int priority=-1);
|
||||
};
|
||||
|
|
|
@ -49,6 +49,7 @@
|
|||
#include <libaegisub/log.h>
|
||||
|
||||
#include "compat.h"
|
||||
#include "dialog_progress.h"
|
||||
#include "ffmpegsource_common.h"
|
||||
#include "frame_main.h"
|
||||
#include "main.h"
|
||||
|
@ -58,26 +59,23 @@
|
|||
|
||||
wxMutex FFmpegSourceProvider::CleaningInProgress;
|
||||
|
||||
|
||||
/// @brief Callback function that updates the indexing progress dialog
|
||||
/// @param Current The current file positition in bytes
|
||||
/// @param Total The total file size in bytes
|
||||
/// @param Private A pointer to the progress dialog box to update
|
||||
/// @param Private A pointer to the progress sink to update
|
||||
/// @return Returns non-0 if indexing is cancelled, 0 otherwise.
|
||||
///
|
||||
int FFMS_CC FFmpegSourceProvider::UpdateIndexingProgress(int64_t Current, int64_t Total, void *Private) {
|
||||
IndexingProgressDialog *Progress = (IndexingProgressDialog *)Private;
|
||||
|
||||
if (Progress->IndexingCanceled)
|
||||
return 1;
|
||||
|
||||
// no one cares about a little bit of a rounding error here anyway
|
||||
Progress->ProgressDialog->SetProgress(((int64_t)1000*Current)/Total, 1000);
|
||||
|
||||
return 0;
|
||||
static int FFMS_CC UpdateIndexingProgress(int64_t Current, int64_t Total, void *Private) {
|
||||
agi::ProgressSink *ps = static_cast<agi::ProgressSink*>(Private);
|
||||
ps->SetProgress(Current, Total);
|
||||
return ps->IsCancelled();
|
||||
}
|
||||
|
||||
|
||||
/// A wrapper around FFMS_DoIndexing to make the signature void -> void
|
||||
static void DoIndexingWrapper(FFMS_Index **Ret, FFMS_Indexer *Indexer, int IndexMask, int ErrorHandling, void *ICPrivate, FFMS_ErrorInfo *ErrorInfo) {
|
||||
*Ret = FFMS_DoIndexing(Indexer, IndexMask, FFMS_TRACKMASK_NONE, NULL, NULL, ErrorHandling,
|
||||
UpdateIndexingProgress, ICPrivate, ErrorInfo);
|
||||
}
|
||||
|
||||
/// @brief Does indexing of a source file
|
||||
/// @param Indexer A pointer to the indexer object representing the file to be indexed
|
||||
|
@ -96,21 +94,12 @@ FFMS_Index *FFmpegSourceProvider::DoIndexing(FFMS_Indexer *Indexer, const wxStri
|
|||
wxString MsgString;
|
||||
|
||||
// set up progress dialog callback
|
||||
IndexingProgressDialog Progress;
|
||||
Progress.IndexingCanceled = false;
|
||||
Progress.ProgressDialog = new DialogProgress(AegisubApp::Get()->frame,
|
||||
_("Indexing"), &Progress.IndexingCanceled,
|
||||
_("Reading timecodes and frame/sample data"), 0, 1);
|
||||
Progress.ProgressDialog->Show();
|
||||
Progress.ProgressDialog->SetProgress(0,1);
|
||||
DialogProgress Progress(AegisubApp::Get()->frame, _("Indexing"), _("Reading timecodes and frame/sample data"));
|
||||
|
||||
// index all audio tracks
|
||||
FFMS_Index *Index = FFMS_DoIndexing(Indexer, Trackmask, FFMS_TRACKMASK_NONE, NULL, NULL, IndexEH,
|
||||
FFmpegSourceProvider::UpdateIndexingProgress, &Progress, &ErrInfo);
|
||||
Progress.ProgressDialog->Destroy();
|
||||
if (Progress.IndexingCanceled) {
|
||||
throw agi::UserCancelException("indexing cancelled by user");
|
||||
}
|
||||
FFMS_Index *Index;
|
||||
Progress.Run(bind(DoIndexingWrapper, &Index, Indexer, Trackmask, IndexEH, std::tr1::placeholders::_1, &ErrInfo));
|
||||
|
||||
if (Index == NULL) {
|
||||
MsgString.Append("Failed to index: ").Append(wxString(ErrInfo.Buffer, wxConvUTF8));
|
||||
throw MsgString;
|
||||
|
|
|
@ -45,8 +45,6 @@
|
|||
|
||||
#include <ffms.h>
|
||||
|
||||
#include "dialog_progress.h"
|
||||
|
||||
/// Index all tracks
|
||||
#define FFMS_TRACKMASK_ALL -1
|
||||
/// Index no tracks
|
||||
|
@ -70,18 +68,10 @@ public:
|
|||
FFMS_LOG_DEBUG = 48,
|
||||
};
|
||||
|
||||
/// Indexing progress report dialog
|
||||
struct IndexingProgressDialog {
|
||||
volatile bool IndexingCanceled;
|
||||
DialogProgress *ProgressDialog;
|
||||
};
|
||||
|
||||
/// Mutex preventing two cache cleaner threads from running at the same time
|
||||
static wxMutex CleaningInProgress;
|
||||
bool CleanCache();
|
||||
|
||||
static int FFMS_CC UpdateIndexingProgress(int64_t Current, int64_t Total, void *Private);
|
||||
|
||||
FFMS_Index *DoIndexing(FFMS_Indexer *Indexer, const wxString& Cachename, int Trackmask, FFMS_IndexErrorHandling IndexEH);
|
||||
std::map<int,wxString> GetTracksOfType(FFMS_Indexer *Indexer, FFMS_TrackType Type);
|
||||
int AskForTrackSelection(const std::map<int,wxString>& TrackList, FFMS_TrackType Type);
|
||||
|
|
|
@ -86,9 +86,7 @@
|
|||
|
||||
"Automation" : {
|
||||
"Autoreload Mode" : 1,
|
||||
"Lua" : {
|
||||
"Thread Priority" : 1
|
||||
},
|
||||
"Thread Priority" : 1,
|
||||
"Trace Level" : 3
|
||||
},
|
||||
|
||||
|
|
|
@ -68,6 +68,69 @@ public:
|
|||
|
||||
#define CACHESIZE 65536
|
||||
|
||||
static void read_subtitles(agi::ProgressSink *ps, MatroskaFile *file, MkvStdIO *input, bool srt, bool ssa, double totalTime, AssFile *target) {
|
||||
std::map<int, wxString> subList;
|
||||
char *readBuf = 0;
|
||||
size_t readBufSize = 0;
|
||||
|
||||
// Load blocks
|
||||
ulonglong startTime, endTime, filePos;
|
||||
unsigned int rt, frameSize, frameFlags;
|
||||
|
||||
while (mkv_ReadFrame(file,0,&rt,&startTime,&endTime,&filePos,&frameSize,&frameFlags) == 0) {
|
||||
if (ps->IsCancelled()) {
|
||||
delete readBuf;
|
||||
return;
|
||||
}
|
||||
|
||||
// Read to temp
|
||||
if (frameSize > readBufSize) {
|
||||
delete readBuf;
|
||||
readBufSize = frameSize * 2;
|
||||
readBuf = new char[readBufSize];
|
||||
}
|
||||
|
||||
fseek(input->fp, filePos, SEEK_SET);
|
||||
fread(readBuf, 1, frameSize, input->fp);
|
||||
wxString blockString(readBuf, wxConvUTF8, frameSize);
|
||||
|
||||
// Get start and end times
|
||||
longlong timecodeScaleLow = 1000000;
|
||||
AssTime subStart,subEnd;
|
||||
subStart.SetMS(startTime / timecodeScaleLow);
|
||||
subEnd.SetMS(endTime / timecodeScaleLow);
|
||||
|
||||
// Process SSA/ASS
|
||||
if (!srt) {
|
||||
long order = 0, layer = 0;
|
||||
blockString.BeforeFirst(',', &blockString).ToLong(&order);
|
||||
blockString.BeforeFirst(',', &blockString).ToLong(&layer);
|
||||
|
||||
subList[order] = wxString::Format("Dialogue: %d,%s,%s,%s", layer, subStart.GetASSFormated(), subEnd.GetASSFormated(), blockString);
|
||||
}
|
||||
// Process SRT
|
||||
else {
|
||||
blockString = wxString::Format("Dialogue: 0,%s,%s,%s", subStart.GetASSFormated(), subEnd.GetASSFormated(), blockString);
|
||||
blockString.Replace("\r\n","\\N");
|
||||
blockString.Replace("\r","\\N");
|
||||
blockString.Replace("\n","\\N");
|
||||
|
||||
subList[subList.size()] = blockString;
|
||||
}
|
||||
|
||||
ps->SetProgress(startTime, totalTime);
|
||||
}
|
||||
|
||||
delete readBuf;
|
||||
|
||||
// Insert into file
|
||||
wxString group = "[Events]";
|
||||
int version = ssa;
|
||||
for (std::map<int, wxString>::iterator it = subList.begin(); it != subList.end(); ++it) {
|
||||
target->AddLine(it->second, group, version, &group);
|
||||
}
|
||||
}
|
||||
|
||||
void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
|
||||
MkvStdIO input(filename);
|
||||
char err[2048];
|
||||
|
@ -82,10 +145,6 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
|
|||
wxArrayString tracksNames;
|
||||
unsigned trackToRead;
|
||||
|
||||
// Haali's library variables
|
||||
ulonglong startTime, endTime, filePos;
|
||||
unsigned int rt, frameSize, frameFlags;
|
||||
|
||||
// Find tracks
|
||||
for (unsigned track = 0; track < tracks; track++) {
|
||||
trackInfo = mkv_GetTrackInfo(file,track);
|
||||
|
@ -122,14 +181,14 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
|
|||
}
|
||||
|
||||
// Picked track
|
||||
// Get codec type (0 = ASS/SSA, 1 = SRT)
|
||||
mkv_SetTrackMask(file, ~(1 << trackToRead));
|
||||
trackInfo = mkv_GetTrackInfo(file,trackToRead);
|
||||
wxString CodecID = wxString(trackInfo->CodecID,*wxConvCurrent);
|
||||
int codecType = 0;
|
||||
if (CodecID == "S_TEXT/UTF8") codecType = 1;
|
||||
bool srt = CodecID == "S_TEXT/UTF8";
|
||||
bool ssa = CodecID == "S_TEXT/SSA";
|
||||
|
||||
// Read private data if it's ASS/SSA
|
||||
if (codecType == 0) {
|
||||
if (!srt) {
|
||||
// Read raw data
|
||||
trackInfo = mkv_GetTrackInfo(file,trackToRead);
|
||||
wxString privString((const char *)trackInfo->CodecPrivate, wxConvUTF8, trackInfo->CodecPrivateSize);
|
||||
|
@ -155,73 +214,9 @@ void MatroskaWrapper::GetSubtitles(wxString const& filename, AssFile *target) {
|
|||
longlong timecodeScale = mkv_TruncFloat(trackInfo->TimecodeScale) * segInfo->TimecodeScale;
|
||||
|
||||
// Progress bar
|
||||
int totalTime = int(double(segInfo->Duration) / timecodeScale);
|
||||
volatile bool canceled = false;
|
||||
DialogProgress *progress = new DialogProgress(NULL,_("Parsing Matroska"),&canceled,_("Reading subtitles from Matroska file."),0,totalTime);
|
||||
progress->Show();
|
||||
progress->SetProgress(0,1);
|
||||
|
||||
std::map<int, wxString> subList;
|
||||
char *readBuf = 0;
|
||||
size_t readBufSize = 0;
|
||||
|
||||
// Load blocks
|
||||
mkv_SetTrackMask(file, ~(1 << trackToRead));
|
||||
while (mkv_ReadFrame(file,0,&rt,&startTime,&endTime,&filePos,&frameSize,&frameFlags) == 0) {
|
||||
if (canceled) {
|
||||
delete readBuf;
|
||||
throw agi::UserCancelException("cancelled");
|
||||
}
|
||||
|
||||
// Read to temp
|
||||
if (frameSize > readBufSize) {
|
||||
delete readBuf;
|
||||
readBufSize = frameSize * 2;
|
||||
readBuf = new char[readBufSize];
|
||||
}
|
||||
|
||||
fseek(input.fp, filePos, SEEK_SET);
|
||||
fread(readBuf, 1, frameSize, input.fp);
|
||||
wxString blockString(readBuf, wxConvUTF8, frameSize);
|
||||
|
||||
// Get start and end times
|
||||
longlong timecodeScaleLow = 1000000;
|
||||
AssTime subStart,subEnd;
|
||||
subStart.SetMS(startTime / timecodeScaleLow);
|
||||
subEnd.SetMS(endTime / timecodeScaleLow);
|
||||
|
||||
// Process SSA/ASS
|
||||
if (codecType == 0) {
|
||||
long order = 0, layer = 0;
|
||||
blockString.BeforeFirst(',', &blockString).ToLong(&order);
|
||||
blockString.BeforeFirst(',', &blockString).ToLong(&layer);
|
||||
|
||||
subList[order] = wxString::Format("Dialogue: %d,%s,%s,%s", layer, subStart.GetASSFormated(), subEnd.GetASSFormated(), blockString);
|
||||
}
|
||||
// Process SRT
|
||||
else {
|
||||
blockString = wxString::Format("Dialogue: 0,%s,%s,%s", subStart.GetASSFormated(), subEnd.GetASSFormated(), blockString);
|
||||
blockString.Replace("\r\n","\\N");
|
||||
blockString.Replace("\r","\\N");
|
||||
blockString.Replace("\n","\\N");
|
||||
|
||||
subList[subList.size()] = blockString;
|
||||
}
|
||||
|
||||
progress->SetProgress(int(double(startTime) / 1000000.0),totalTime);
|
||||
}
|
||||
|
||||
delete readBuf;
|
||||
|
||||
// Insert into file
|
||||
wxString group = "[Events]";
|
||||
int version = (CodecID == "S_TEXT/SSA");
|
||||
for (std::map<int, wxString>::iterator it = subList.begin(); it != subList.end(); ++it) {
|
||||
target->AddLine(it->second,group,version,&group);
|
||||
}
|
||||
|
||||
// Close progress bar
|
||||
if (!canceled) progress->Destroy();
|
||||
double totalTime = double(segInfo->Duration) / timecodeScale * 1000000.0;
|
||||
DialogProgress progress(NULL, _("Parsing Matroska"), _("Reading subtitles from Matroska file."));
|
||||
progress.Run(bind(read_subtitles, std::tr1::placeholders::_1, file, &input, srt, ssa, totalTime, target));
|
||||
}
|
||||
catch (...) {
|
||||
mkv_Close(file);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue