mirror of https://github.com/odrling/Aegisub
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
|
@ -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"
|
||||||
>
|
>
|
||||||
|
|
|
@ -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 "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) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue