1
// Copyright (c) 2005, Rodrigo Braz Monteiro
2
// All rights reserved.
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are met:
7
// * Redistributions of source code must retain the above copyright notice,
8
// this list of conditions and the following disclaimer.
9
// * Redistributions in binary form must reproduce the above copyright notice,
10
// this list of conditions and the following disclaimer in the documentation
11
// and/or other materials provided with the distribution.
12
// * Neither the name of the Aegisub Group nor the names of its contributors
13
// may be used to endorse or promote products derived from this software
14
// without specific prior written permission.
16
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
// POSSIBILITY OF SUCH DAMAGE.
28
// -----------------------------------------------------------------------------
32
// Website: http://aegisub.cellosoft.com
33
// Contact: mailto:zeratul@cellosoft.com
41
#include <wx/wxprec.h>
42
#include <wx/config.h>
43
#include <wx/filename.h>
44
#include <wx/msgdlg.h>
45
#include <wx/mimetype.h>
47
#include <wx/stdpaths.h>
48
#include <wx/filefn.h>
49
#include <wx/datetime.h>
52
#include "frame_main.h"
55
#include "dialog_associations.h"
57
#include "audio_box.h"
58
#include "audio_display.h"
59
#include "export_framerate.h"
60
#include "ass_export_filter.h"
62
#include "ass_dialogue.h"
63
#include "subs_grid.h"
64
#include "subtitle_format.h"
65
#include "video_context.h"
66
#include "standard_paths.h"
67
#ifdef WITH_AUTOMATION
68
#include "auto4_base.h"
71
#include "plugin_manager.h"
76
IMPLEMENT_APP(AegisubApp)
79
#ifdef WITH_STARTUPLOG
80
#define StartupLog(a) MessageBox(0, a, _T("Aegisub startup log"), 0)
86
#define MS_VC_EXCEPTION 0x406d1388
88
typedef struct tagTHREADNAME_INFO {
89
DWORD dwType; // must be 0x1000
90
LPCSTR szName; // pointer to name (in same addr space)
91
DWORD dwThreadID; // thread ID (-1 caller thread)
92
DWORD dwFlags; // reserved for future use, most be zero
95
void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName) {
98
info.szName = szThreadName;
99
info.dwThreadID = dwThreadID;
102
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info);
104
__except (EXCEPTION_CONTINUE_EXECUTION) {}
108
///////////////////////////
109
// Initialization function
110
// -----------------------
111
// Gets called when application starts, creates MainFrame
112
bool AegisubApp::OnInit() {
114
SetThreadName((DWORD) -1,"AegiMain");
117
StartupLog(_T("Inside OnInit"));
120
// Initialize randomizer
121
StartupLog(_T("Initialize random generator"));
124
// locale for loading options
125
StartupLog(_T("Set initial locale"));
126
setlocale(LC_NUMERIC, "C");
127
setlocale(LC_CTYPE, "C");
129
// App name (yeah, this is a little weird to get rid of an odd warning)
131
SetAppName(_T("Aegisub"));
134
SetAppName(_T("Aegisub"));
136
SetAppName(_T("aegisub"));
141
#if !defined(_DEBUG) || defined(WITH_EXCEPTIONS)
142
StartupLog(_T("Install exception handler"));
143
wxHandleFatalExceptions(true);
147
StartupLog(_T("Load configuration"));
148
Options.LoadDefaults();
150
// Try loading configuration from the install dir if one exists there
151
if (wxFileName::FileExists(StandardPaths::DecodePath(_T("?data/config.dat")))) {
152
Options.SetFile(StandardPaths::DecodePath(_T("?data/config.dat")));
155
if (Options.AsBool(_T("Local config"))) {
156
// Local config, make ?user mean ?data so all user settings are placed in install dir
157
StandardPaths::SetPathValue(_T("?user"), StandardPaths::DecodePath(_T("?data")));
160
// Not local config, we don't want that config.dat file here any more
161
// It might be a leftover from a really old install
162
wxRemoveFile(StandardPaths::DecodePath(_T("?data/config.dat")));
166
// TODO: Check if we can write to config.dat and warn the user if we can't
167
// If we had local config, ?user now means ?data so this will still be loaded from the correct location
168
Options.SetFile(StandardPaths::DecodePath(_T("?user/config.dat")));
171
StartupLog(_T("Store options back"));
172
Options.SetInt(_T("Last Version"),GetSVNRevision());
173
Options.LoadDefaults(false,true); // Override options based on version number
175
AssTime::UseMSPrecision = Options.AsBool(_T("Use nonstandard Milisecond Times"));
178
StartupLog(_T("Load hotkeys"));
179
Hotkeys.SetFile(StandardPaths::DecodePath(_T("?user/hotkeys.dat")));
182
StartupLog(_T("Initialize final locale"));
185
int lang = Options.AsInt(_T("Locale Code"));
187
lang = locale.PickLanguage();
188
Options.SetInt(_T("Locale Code"),lang);
194
plugins = new PluginManager();
195
plugins->RegisterBuiltInPlugins();
197
// Load Automation scripts
198
#ifdef WITH_AUTOMATION
199
StartupLog(_T("Load global Automation scripts"));
200
global_scripts = new Automation4::AutoloadScriptManager(Options.AsText(_T("Automation Autoload Path")));
203
// Load export filters
204
StartupLog(_T("Prepare export filters"));
205
AssExportFilterChain::PrepareFilters();
208
#if !defined(_DEBUG) || defined(WITH_EXCEPTIONS)
209
StartupLog(_T("Check file type associations"));
210
CheckFileAssociations(0);
213
// Get parameter subs
214
StartupLog(_T("Parse command line"));
216
for (int i=1;i<argc;i++) {
221
StartupLog(_T("Create main window"));
222
frame = new FrameMain(subs);
226
catch (const wchar_t *err) {
227
wxMessageBox(err,_T("Fatal error while initializing"));
232
wxMessageBox(_T("Unhandled exception"),_T("Fatal error while initializing"));
236
StartupLog(_T("Initialization complete"));
243
int AegisubApp::OnExit() {
244
SubtitleFormat::DestroyFormats();
245
VideoContext::Clear();
248
#ifdef WITH_AUTOMATION
249
delete global_scripts;
251
return wxApp::OnExit();
255
#if !defined(_DEBUG) || defined(WITH_EXCEPTIONS)
256
/////////////////////////////////////////////
257
// Message displayed on unhandled exceptions
258
const static wxChar unhandled_exception_message[] = _T("Oops, Aegisub has crashed!\n\nI have tried to emergency-save a copy of your file, and a crash log file has been generated.\n\nYou can find the emergency-saved file in:\n%s\n\nIf you submit the crash log to the Aegisub team, we will investigate the problem and attempt to fix it. You can find the crashlog in:\n%s\n\nAegisub will now close.");
259
const static wxChar unhandled_exception_message_nocrashlog[] = _T("Oops, Aegisub has crashed!\n\nI have tried to emergency-save a copy of your file.\n\nYou can find the emergency-saved file in:\n%s\n\nAegisub will now close.");
261
///////////////////////
262
// Unhandled exception
263
void AegisubApp::OnUnhandledException() {
264
// Attempt to recover file
265
wxFileName origfile(AssFile::top->filename);
266
wxString path = Options.AsText(_T("Auto recovery path"));
267
if (path.IsEmpty()) path = StandardPaths::DecodePath(_T("?user/"));
268
wxFileName dstpath(path);
269
if (!dstpath.IsAbsolute()) path = StandardPaths::DecodePathMaybeRelative(path, _T("?user/"));
271
dstpath.Assign(path);
272
if (!dstpath.DirExists()) wxMkdir(path);
273
wxString filename = path + origfile.GetName() + _T(".RECOVER.ass");
274
AssFile::top->Save(filename,false,false);
276
// Inform user of crash
277
wxMessageBox(wxString::Format(unhandled_exception_message, filename.c_str(), StandardPaths::DecodePath(_T("?user/crashlog.txt")).c_str()), _T("Unhandled exception"), wxOK | wxICON_ERROR, NULL);
283
void AegisubApp::OnFatalException() {
284
// Attempt to recover file
285
wxFileName origfile(AssFile::top->filename);
286
wxString path = Options.AsText(_T("Auto recovery path"));
287
if (path.IsEmpty()) path = StandardPaths::DecodePath(_T("?user/"));
288
wxFileName dstpath(path);
289
if (!dstpath.IsAbsolute()) path = StandardPaths::DecodePathMaybeRelative(path, _T("?user/"));
291
dstpath.Assign(path);
292
if (!dstpath.DirExists()) wxMkdir(path);
293
wxString filename = path + origfile.GetName() + _T(".RECOVER.ass");
294
AssFile::top->Save(filename,false,false);
296
#if wxUSE_STACKWALKER == 1
298
StackWalker walker(_T("Fatal exception"));
299
walker.WalkFromException();
301
// Inform user of crash
302
wxMessageBox(wxString::Format(unhandled_exception_message, filename.c_str(), StandardPaths::DecodePath(_T("?user/crashlog.txt")).c_str()), _T("Fatal exception"), wxOK | wxICON_ERROR, NULL);
304
// Inform user of crash
305
wxMessageBox(wxString::Format(unhandled_exception_message_nocrashlog, filename.c_str()), _T("Fatal exception"), wxOK | wxICON_ERROR, NULL);
313
#if wxUSE_STACKWALKER == 1
314
void StackWalker::OnStackFrame(const wxStackFrame &frame) {
315
wxString dst = wxString::Format(_T("%03i - 0x%08X: "),frame.GetLevel(),frame.GetAddress()) + frame.GetName();
316
if (frame.HasSourceLocation()) dst += _T(" on ") + frame.GetFileName() + wxString::Format(_T(":%i"),frame.GetLine());
317
if (file.is_open()) {
318
file << dst.mb_str() << std::endl;
320
else wxLogMessage(dst);
323
StackWalker::StackWalker(wxString cause) {
324
file.open(wxString(StandardPaths::DecodePath(_T("?user/crashlog.txt"))).mb_str(),std::ios::out | std::ios::app);
325
if (file.is_open()) {
326
wxDateTime time = wxDateTime::Now();
327
wxString timeStr = _T("---") + time.FormatISODate() + _T(" ") + time.FormatISOTime() + _T("------------------");
328
formatLen = timeStr.Length();
329
file << std::endl << timeStr.mb_str(wxConvLocal);
330
file << "\nVER - " << GetAegisubLongVersionString().mb_str(wxConvUTF8);
331
file << "\nFTL - Begining stack dump for \"" << cause.mb_str(wxConvUTF8) <<"\":\n";
335
StackWalker::~StackWalker() {
336
if (file.is_open()) {
339
for (i=0;i<formatLen;i++) dashes[i] = '-';
341
file << "End of stack dump.\n";
353
int AegisubApp::OnRun() {
358
if (m_exitOnFrameDelete == Later) m_exitOnFrameDelete = Yes;
363
catch (wxString &err) { error = err; }
364
catch (wxChar *err) { error = err; }
365
catch (std::exception &e) { error = wxString(_T("std::exception: ")) + wxString(e.what(),wxConvUTF8); }
366
catch (...) { error = _T("Program terminated in error."); }
369
if (!error.IsEmpty()) {
371
file.open(wxString(StandardPaths::DecodePath(_T("?user/crashlog.txt"))).mb_str(),std::ios::out | std::ios::app);
372
if (file.is_open()) {
373
wxDateTime time = wxDateTime::Now();
374
wxString timeStr = _T("---") + time.FormatISODate() + _T(" ") + time.FormatISOTime() + _T("------------------");
375
file << std::endl << timeStr.mb_str(wxConvLocal);
376
file << "\nVER - " << GetAegisubLongVersionString().mb_str(wxConvUTF8);
377
file << "\nEXC - Aegisub has crashed with unhandled exception \"" << error.mb_str(wxConvLocal) <<"\".\n";
378
int formatLen = timeStr.Length();
381
for (i=0;i<formatLen;i++) dashes[i] = '-';
388
OnUnhandledException();
398
void AegisubApp::OpenURL(wxString url) {
399
wxLaunchDefaultBrowser(url, wxBROWSER_NEW_WINDOW);
406
void AegisubApp::MacOpenFile(const wxString &filename) {
407
if (frame != NULL && !filename.empty()) {
408
frame->LoadSubtitles(filename);
409
wxFileName filepath(filename);
410
Options.SetText(_T("Last open subtitles path"), filepath.GetPath());
418
BEGIN_EVENT_TABLE(AegisubApp,wxApp)
419
EVT_MOUSEWHEEL(AegisubApp::OnMouseWheel)
420
EVT_KEY_DOWN(AegisubApp::OnKey)
424
/////////////////////
426
void AegisubApp::OnMouseWheel(wxMouseEvent &event) {
428
wxWindow *target = wxFindWindowAtPointer(pt);
429
if (frame && (target == frame->audioBox->audioDisplay || target == frame->SubsBox)) {
430
#if wxCHECK_VERSION(2,9,0)
431
if (target->IsShownOnScreen()) target->GetEventHandler()->ProcessEvent(event);
433
if (target->IsShownOnScreen()) target->AddPendingEvent(event);
443
void AegisubApp::OnKey(wxKeyEvent &event) {
444
//frame->audioBox->audioDisplay->AddPendingEvent(event);
445
if (!event.GetSkipped()) {