// Copyright (c) 2005, Rodrigo Braz Monteiro, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of the Aegisub Group nor the names of its contributors
//     may be used to endorse or promote products derived from this software
//     without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Aegisub Project http://www.aegisub.org/
//
// $Id$

/// @file frame_main.cpp
/// @brief Main window creation and control management
/// @ingroup main_ui

#include "config.h"

#include "frame_main.h"

#ifndef AGI_PRE
#include <wx/clipbrd.h>
#include <wx/dnd.h>
#include <wx/filename.h>
#include <wx/image.h>
#include <wx/statline.h>
#include <wx/sysopt.h>
#include <wx/tokenzr.h>
#endif

#include <libaegisub/log.h>

#include "include/aegisub/context.h"
#include "include/aegisub/menu.h"
#include "include/aegisub/toolbar.h"
#include "include/aegisub/hotkey.h"

#include "ass_file.h"
#include "audio_controller.h"
#include "audio_box.h"
#include "auto4_base.h"
#include "compat.h"
#include "command/command.h"
#include "dialog_detached_video.h"
#include "dialog_manager.h"
#include "dialog_search_replace.h"
#include "dialog_version_check.h"
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "main.h"
#include "standard_paths.h"
#include "subs_edit_box.h"
#include "subs_edit_ctrl.h"
#include "subs_grid.h"
#include "text_file_reader.h"
#include "utils.h"
#include "version.h"
#include "video_box.h"
#include "video_context.h"
#include "video_display.h"
#include "video_provider_manager.h"
#include "video_slider.h"

enum {
	ID_APP_TIMER_AUTOSAVE					= 12001,
	ID_APP_TIMER_STATUSCLEAR				= 12002,
	ID_SASH_MAIN_AUDIO						= 14001
};

#ifdef WITH_STARTUPLOG
#define StartupLog(a) MessageBox(0, a, "Aegisub startup log", 0)
#else
#define StartupLog(a) LOG_I("frame_main/init") << a
#endif

static void autosave_timer_changed(wxTimer *timer);

wxDEFINE_EVENT(FILE_LIST_DROPPED, wxThreadEvent);

static void get_files_to_load(wxArrayString const& list, wxString &subs, wxString &audio, wxString &video) {
	// Keep these lists sorted

	// Video formats
	const wxString videoList[] = {
		"asf",
		"avi",
		"avs",
		"d2v",
		"m2ts",
		"m4v",
		"mkv",
		"mov",
		"mp4",
		"mpeg",
		"mpg",
		"ogm",
		"rm",
		"rmvb",
		"ts",
		"webm"
		"wmv",
		"y4m",
		"yuv"
	};

	// Subtitle formats
	const wxString subsList[] = {
		"ass",
		"srt",
		"ssa",
		"sub",
		"ttxt",
		"txt"
	};

	// Audio formats
	const wxString audioList[] = {
		"aac",
		"ac3",
		"ape",
		"dts",
		"flac",
		"m4a",
		"mka",
		"mp3",
		"ogg",
		"w64",
		"wav",
		"wma"
	};

	// Scan list
	for (size_t i = 0; i < list.size(); ++i) {
		wxFileName file(list[i]);
		if (file.IsRelative()) file.MakeAbsolute();
		if (!file.FileExists()) continue;

		wxString ext = file.GetExt().Lower();

		if (subs.empty() && std::binary_search(subsList, subsList + countof(subsList), ext))
			subs = file.GetFullPath();
		if (video.empty() && std::binary_search(videoList, videoList + countof(videoList), ext))
			video = file.GetFullPath();
		if (audio.empty() && std::binary_search(audioList, audioList + countof(audioList), ext))
			audio = file.GetFullPath();
	}
}

/// Handle files drag and dropped onto Aegisub
class AegisubFileDropTarget : public wxFileDropTarget {
	FrameMain *parent;
public:
	AegisubFileDropTarget(FrameMain *parent) : parent(parent) { }
	bool OnDropFiles(wxCoord, wxCoord, const wxArrayString& filenames) {
		wxString subs, audio, video;
		get_files_to_load(filenames, subs, audio, video);

		if (!subs && !audio && !video)
			return false;

		wxThreadEvent *evt = new wxThreadEvent(FILE_LIST_DROPPED);
		evt->SetPayload(filenames);
		parent->QueueEvent(evt);
		return true;
	}
};

FrameMain::FrameMain (wxArrayString args)
: wxFrame(0,-1,"",wxDefaultPosition,wxSize(920,700),wxDEFAULT_FRAME_STYLE | wxCLIP_CHILDREN)
, context(new agi::Context)
, showVideo(true)
, showAudio(true)
, blockVideoLoad(false)
, blockAudioLoad(false)
{
	StartupLog("Entering FrameMain constructor");

#ifdef __WXGTK__
	// XXX HACK XXX
	// We need to set LC_ALL to "" here for input methods to work reliably.
	setlocale(LC_ALL, "");

	// However LC_NUMERIC must be "C", otherwise some parsing fails.
	setlocale(LC_NUMERIC, "C");
#endif
#ifdef __APPLE__
	// When run from an app bundle, LC_CTYPE defaults to "C", which breaks on
	// anything involving unicode and in some cases number formatting.
	// The right thing to do here would be to query CoreFoundation for the user's
	// locale and add .UTF-8 to that, but :effort:
	LOG_D("locale") << setlocale(LC_ALL, 0);
	setlocale(LC_CTYPE, "en_US.UTF-8");
	LOG_D("locale") << setlocale(LC_ALL, 0);
#endif

	StartupLog("Initializing context models");
	memset(context.get(), 0, sizeof(*context));
	AssFile::top = context->ass = new AssFile;
	context->ass->AddCommitListener(&FrameMain::UpdateTitle, this);
	context->ass->AddFileOpenListener(&FrameMain::OnSubtitlesOpen, this);
	context->ass->AddFileSaveListener(&FrameMain::UpdateTitle, this);

	context->local_scripts = new Automation4::LocalScriptManager(context.get());

	StartupLog("Initializing context controls");
	context->audioController = new AudioController(context.get());
	context->audioController->AddAudioOpenListener(&FrameMain::OnAudioOpen, this);
	context->audioController->AddAudioCloseListener(&FrameMain::OnAudioClose, this);

	// Initialized later due to that the selection controller is currently the subtitles grid
	context->selectionController = 0;

	context->videoController = VideoContext::Get(); // derp
	context->videoController->AddVideoOpenListener(&FrameMain::OnVideoOpen, this);

	StartupLog("Initializing context frames");
	context->parent = this;
	context->previousFocus = 0;
	wxGetApp().frame = this;

	StartupLog("Install PNG handler");
	wxImage::AddHandler(new wxPNGHandler);
#ifndef __APPLE__
	wxSafeYield();
#endif

	StartupLog("Apply saved Maximized state");
	if (OPT_GET("App/Maximized")->GetBool()) Maximize(true);

	StartupLog("Initialize toolbar");
	InitToolbar();

	StartupLog("Initialize menu bar");
	menu::GetMenuBar("main", this, context.get());

	StartupLog("Create status bar");
	CreateStatusBar(2);

	StartupLog("Set icon");
#ifdef _WIN32
	SetIcon(wxICON(wxicon));
#else
	wxIcon icon;
	icon.CopyFromBitmap(GETIMAGE(wxicon));
	SetIcon(icon);
#endif

	StartupLog("Create views and inner main window controls");
	context->dialog = new DialogManager;
	InitContents();
	OPT_SUB("Video/Detached/Enabled", &FrameMain::OnVideoDetach, this, agi::signal::_1);

	StartupLog("Complete context initialization");
	context->videoController->SetContext(context.get());

	StartupLog("Set up Auto Save");
	AutoSave.SetOwner(this, ID_APP_TIMER_AUTOSAVE);
	autosave_timer_changed(&AutoSave);
	OPT_SUB("App/Auto/Save", autosave_timer_changed, &AutoSave);
	OPT_SUB("App/Auto/Save Every Seconds", autosave_timer_changed, &AutoSave);

	StartupLog("Set up drag/drop target");
	SetDropTarget(new AegisubFileDropTarget(this));

	StartupLog("Load default file");
	context->ass->LoadDefault();

	StartupLog("Display main window");
	AddFullScreenButton(this);
	Show();
	SetDisplayMode(1, 1);

	StartupLog("Load files specified on command line");
	LoadList(args);

	// Version checker
	StartupLog("Possibly perform automatic updates check");
	if (OPT_GET("App/First Start")->GetBool()) {
		OPT_SET("App/First Start")->SetBool(false);
#ifdef WITH_UPDATE_CHECKER
		int result = wxMessageBox(_("Do you want Aegisub to check for updates whenever it starts? You can still do it manually via the Help menu."),_("Check for updates?"), wxYES_NO | wxCENTER);
		OPT_SET("App/Auto/Check For Updates")->SetBool(result == wxYES);
		config::opt->Flush();
#endif
	}

#ifdef WITH_UPDATE_CHECKER
	PerformVersionCheck(false);
#endif

	Bind(FILE_LIST_DROPPED, &FrameMain::OnFilesDropped, this);

	StartupLog("Leaving FrameMain constructor");
}

/// @brief Delete everything but @a keep and its parents
/// @param window Root window to delete the children of
/// @param keep Window to keep alive
/// @return Was @a keep found?
static bool delete_children(wxWindow *window, wxWindow *keep) {
	bool found = false;
	while (window->GetChildren().size() > (size_t)found) {
		wxWindowList::iterator it = window->GetChildren().begin();

		if (*it == keep)
			found = true;

		if (found) {
			if (++it != window->GetChildren().end())
				(*it)->wxWindowBase::Destroy();
		}
		else if (!delete_children(*it, keep))
			(*it)->wxWindowBase::Destroy();
		else
			found = true;
	}
	return found;
}

FrameMain::~FrameMain () {
	wxGetApp().frame = 0;

	context->videoController->SetVideo("");
	context->audioController->CloseAudio();

	// SubsGrid needs to be deleted last due to being the selection
	// controller, but everything else needs to be deleted before the context
	// is cleaned up
	delete_children(this, SubsGrid);

	delete context->ass;
	HelpButton::ClearPages();
	delete context->audioController;
	delete context->local_scripts;
}

void FrameMain::InitToolbar() {
	wxSystemOptions::SetOption("msw.remap", 0);
	OPT_SUB("App/Show Toolbar", &FrameMain::EnableToolBar, this);
	EnableToolBar(*OPT_GET("App/Show Toolbar"));
}

void FrameMain::EnableToolBar(agi::OptionValue const& opt) {
	if (opt.GetBool()) {
		if (!GetToolBar()) {
			toolbar::AttachToolbar(this, "main", context.get(), "Default");
			GetToolBar()->Realize();
		}
	}
	else if (wxToolBar *old_tb = GetToolBar()) {
		SetToolBar(0);
		delete old_tb;
		Layout();
	}
}

void FrameMain::InitContents() {
	StartupLog("Create background panel");
	wxPanel *Panel = new wxPanel(this,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL | wxCLIP_CHILDREN);

	StartupLog("Create subtitles grid");
	context->subsGrid = SubsGrid = new SubtitlesGrid(Panel,context.get(),wxSize(600,100),wxWANTS_CHARS | wxSUNKEN_BORDER,"Subs grid");
	context->selectionController = context->subsGrid;
	Search.context = context.get();

	StartupLog("Create video box");
	videoBox = new VideoBox(Panel, false, context.get());

	StartupLog("Create audio box");
	context->audioBox = audioBox = new AudioBox(Panel, context.get());

	StartupLog("Create subtitle editing box");
	SubsEditBox *EditBox = new SubsEditBox(Panel, context.get());
	context->editBox = EditBox->TextEdit;

	StartupLog("Arrange main sizers");
	ToolsSizer = new wxBoxSizer(wxVERTICAL);
	ToolsSizer->Add(audioBox, 0, wxEXPAND);
	ToolsSizer->Add(EditBox, 1, wxEXPAND);
	TopSizer = new wxBoxSizer(wxHORIZONTAL);
	TopSizer->Add(videoBox, 0, wxEXPAND, 0);
	TopSizer->Add(ToolsSizer, 1, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
	MainSizer = new wxBoxSizer(wxVERTICAL);
	MainSizer->Add(new wxStaticLine(Panel),0,wxEXPAND | wxALL,0);
	MainSizer->Add(TopSizer,0,wxEXPAND | wxALL,0);
	MainSizer->Add(SubsGrid,1,wxEXPAND | wxALL,0);
	Panel->SetSizer(MainSizer);

	StartupLog("Perform layout");
	Layout();
	StartupLog("Set focus to edting box");
	EditBox->TextEdit->SetFocus();
	StartupLog("Leaving InitContents");
}

void FrameMain::LoadSubtitles(wxString filename,wxString charset) {
	if (context->ass->loaded) {
		if (TryToCloseSubs() == wxCANCEL) return;
	}

	try {
		// Make sure that file isn't actually a timecode file
		try {
			TextFileReader testSubs(filename,charset);
			wxString cur = testSubs.ReadLineFromFile();
			if (cur.Left(10) == "# timecode") {
				context->videoController->LoadTimecodes(filename);
				return;
			}
		}
		catch (...) {
			// if trying to load the file as timecodes fails it's fairly
			// safe to assume that it is in fact not a timecode file
		}

		context->ass->Load(filename,charset);
	}
	catch (agi::FileNotFoundError const&) {
		wxMessageBox(filename + " not found.", "Error", wxOK | wxICON_ERROR | wxCENTER, this);
		config::mru->Remove("Subtitle", STD_STR(filename));
		return;
	}
	catch (const char *err) {
		wxMessageBox(wxString(err), "Error", wxOK | wxICON_ERROR | wxCENTER, this);
		return;
	}
	catch (wxString const& err) {
		wxMessageBox(err, "Error", wxOK | wxICON_ERROR | wxCENTER, this);
		return;
	}
	catch (...) {
		wxMessageBox("Unknown error", "Error", wxOK | wxICON_ERROR | wxCENTER, this);
		return;
	}
}

int FrameMain::TryToCloseSubs(bool enableCancel) {
	if (context->ass->IsModified()) {
		int flags = wxYES_NO;
		if (enableCancel) flags |= wxCANCEL;
		int result = wxMessageBox(wxString::Format(_("Do you want to save changes to %s?"), GetScriptFileName()), _("Unsaved changes"), flags, this);
		if (result == wxYES) {
			cmd::call("subtitle/save", context.get());
			// If it fails saving, return cancel anyway
			return context->ass->IsModified() ? wxCANCEL : wxYES;
		}
		return result;
	}
	else {
		return wxYES;
	}
}

void FrameMain::SetDisplayMode(int video, int audio) {
	if (!IsShownOnScreen()) return;

	bool sv = false, sa = false;

	if (video == -1) sv = showVideo;
	else if (video)  sv = context->videoController->IsLoaded() && !context->dialog->Get<DialogDetachedVideo>();

	if (audio == -1) sa = showAudio;
	else if (audio)  sa = context->audioController->IsAudioOpen();

	// See if anything changed
	if (sv == showVideo && sa == showAudio) return;

	showVideo = sv;
	showAudio = sa;

	bool didFreeze = !IsFrozen();
	if (didFreeze) Freeze();

	context->videoController->Stop();

	TopSizer->Show(videoBox, showVideo, true);
	ToolsSizer->Show(audioBox, showAudio, true);

	MainSizer->CalcMin();
	MainSizer->RecalcSizes();
	MainSizer->Layout();
	Layout();

	if (didFreeze) Thaw();
}

void FrameMain::UpdateTitle() {
	wxString newTitle;
	if (context->ass->IsModified()) newTitle << "* ";
	newTitle << GetScriptFileName();

#ifndef __WXMAC__
	newTitle << " - Aegisub " << GetAegisubLongVersionString();
#endif

#if defined(__WXMAC__) && !defined(__LP64__)
	// On Mac, set the mark in the close button
	OSXSetModified(context->ass->IsModified());
#endif

	if (GetTitle() != newTitle) SetTitle(newTitle);
}

void FrameMain::OnVideoOpen() {
	if (!context->videoController->IsLoaded()) {
		SetDisplayMode(0, -1);
		return;
	}

	Freeze();
	int vidx = context->videoController->GetWidth(),
		vidy = context->videoController->GetHeight();

	// Set zoom level based on video resolution and window size
	double zoom = context->videoDisplay->GetZoom();
	wxSize windowSize = GetSize();
	if (vidx*3*zoom > windowSize.GetX()*4 || vidy*4*zoom > windowSize.GetY()*6)
		context->videoDisplay->SetZoom(zoom * .25);
	else if (vidx*3*zoom > windowSize.GetX()*2 || vidy*4*zoom > windowSize.GetY()*3)
		context->videoDisplay->SetZoom(zoom * .5);

	SetDisplayMode(1,-1);

	if (OPT_GET("Video/Detached/Enabled")->GetBool() && !context->dialog->Get<DialogDetachedVideo>())
		cmd::call("video/detach", context.get());
	Thaw();

	if (!blockAudioLoad && OPT_GET("Video/Open Audio")->GetBool() && context->audioController->GetAudioURL() != context->videoController->GetVideoName()) {
		try {
			context->audioController->OpenAudio(context->videoController->GetVideoName());
		}
		catch (agi::UserCancelException const&) { }
		// Opening a video with no audio data isn't an error, so just log
		// and move on
		catch (agi::FileNotAccessibleError const&) {
			LOG_D("video/open/audio") << "File " << context->videoController->GetVideoName() << " found by video provider but not audio provider";
		}
		catch (agi::AudioDataNotFoundError const& e) {
			LOG_D("video/open/audio") << "File " << context->videoController->GetVideoName() << " has no audio data: " << e.GetChainedMessage();
		}
		catch (agi::AudioOpenError const& err) {
			wxMessageBox(lagi_wxString(err.GetMessage()), "Error loading audio", wxOK | wxICON_ERROR | wxCENTER);
		}
	}
}

void FrameMain::OnVideoDetach(agi::OptionValue const& opt) {
	if (opt.GetBool())
		SetDisplayMode(0, -1);
	else if (context->videoController->IsLoaded())
		SetDisplayMode(1, -1);
}

void FrameMain::StatusTimeout(wxString text,int ms) {
	SetStatusText(text,1);
	StatusClear.SetOwner(this, ID_APP_TIMER_STATUSCLEAR);
	StatusClear.Start(ms,true);
}

void FrameMain::OnFilesDropped(wxThreadEvent &evt) {
	LoadList(evt.GetPayload<wxArrayString>());
}

bool FrameMain::LoadList(wxArrayString list) {
	wxString audio, video, subs;
	get_files_to_load(list, subs, audio, video);

	blockVideoLoad = !video.empty();
	blockAudioLoad = !audio.empty();

	// Load files
	if (subs.size())
		LoadSubtitles(subs);

	if (blockVideoLoad) {
		blockVideoLoad = false;
		context->videoController->SetVideo(video);
	}

	if (blockAudioLoad) {
		blockAudioLoad = false;
		try {
			context->audioController->OpenAudio(audio);
		} catch (agi::UserCancelException const&) { }
	}

	bool loaded_any = subs.size() || audio.size() || video.size();
	if (loaded_any)
		Refresh(false);

	return loaded_any;
}

static void autosave_timer_changed(wxTimer *timer) {
	int freq = OPT_GET("App/Auto/Save Every Seconds")->GetInt();
	if (freq > 0 && OPT_GET("App/Auto/Save")->GetBool())
		timer->Start(freq * 1000);
	else
		timer->Stop();
}
BEGIN_EVENT_TABLE(FrameMain, wxFrame)
	EVT_TIMER(ID_APP_TIMER_AUTOSAVE, FrameMain::OnAutoSave)
	EVT_TIMER(ID_APP_TIMER_STATUSCLEAR, FrameMain::OnStatusClear)

	EVT_CLOSE(FrameMain::OnCloseWindow)

	EVT_CHAR_HOOK(FrameMain::OnKeyDown)
	EVT_MOUSEWHEEL(FrameMain::OnMouseWheel)
END_EVENT_TABLE()

void FrameMain::OnCloseWindow (wxCloseEvent &event) {
	// Stop audio and video
	context->videoController->Stop();
	context->audioController->Stop();

	// Ask user if he wants to save first
	bool canVeto = event.CanVeto();
	int result = TryToCloseSubs(canVeto);
	if (canVeto && result == wxCANCEL) {
		event.Veto();
		return;
	}

	delete context->dialog;
	context->dialog = 0;

	// Store maximization state
	OPT_SET("App/Maximized")->SetBool(IsMaximized());

	Destroy();
}

void FrameMain::OnAutoSave(wxTimerEvent &) try {
	wxString fn = context->ass->AutoSave();
	if (!fn.empty())
		StatusTimeout(wxString::Format(_("File backup saved as \"%s\"."), fn));
}
catch (const agi::Exception& err) {
	StatusTimeout(lagi_wxString("Exception when attempting to autosave file: " + err.GetMessage()));
}
catch (wxString err) {
	StatusTimeout("Exception when attempting to autosave file: " + err);
}
catch (const char *err) {
	StatusTimeout("Exception when attempting to autosave file: " + wxString(err));
}
catch (...) {
	StatusTimeout("Unhandled exception when attempting to autosave file.");
}

void FrameMain::OnStatusClear(wxTimerEvent &) {
	SetStatusText("",1);
}

void FrameMain::OnAudioOpen(AudioProvider *) {
	SetDisplayMode(-1, 1);
}

void FrameMain::OnAudioClose() {
	SetDisplayMode(-1, 0);
}

void FrameMain::OnSubtitlesOpen() {
	UpdateTitle();

	/// @todo figure out how to move this to the relevant controllers without
	///       prompting for each file loaded/unloaded

	// Load stuff from the new script
	wxString curSubsVideo = DecodeRelativePath(context->ass->GetScriptInfo("Video File"), context->ass->filename);
	wxString curSubsVFR = DecodeRelativePath(context->ass->GetScriptInfo("VFR File"), context->ass->filename);
	wxString curSubsKeyframes = DecodeRelativePath(context->ass->GetScriptInfo("Keyframes File"), context->ass->filename);
	wxString curSubsAudio = DecodeRelativePath(context->ass->GetScriptInfo("Audio URI"), context->ass->filename);

	bool videoChanged = !blockVideoLoad && curSubsVideo != context->videoController->GetVideoName();
	bool timecodesChanged = curSubsVFR != context->videoController->GetTimecodesName();
	bool keyframesChanged = curSubsKeyframes != context->videoController->GetKeyFramesName();
	bool audioChanged = !blockAudioLoad && curSubsAudio != context->audioController->GetAudioURL();

	// Check if there is anything to change
	int autoLoadMode = OPT_GET("App/Auto/Load Linked Files")->GetInt();
	if (autoLoadMode == 0 || (!videoChanged && !timecodesChanged && !keyframesChanged && !audioChanged)) {
		SetDisplayMode(1, 1);
		return;
	}

	if (autoLoadMode == 2) {
		if (wxMessageBox(_("Do you want to load/unload the associated files?"), _("(Un)Load files?"), wxYES_NO | wxCENTRE, this) != wxYES) {
			SetDisplayMode(1, 1);
			return;
		}
	}

	if (audioChanged)
		blockAudioLoad = true;

	// Video
	if (videoChanged) {
		wxString arString = context->ass->GetScriptInfo("Video Aspect Ratio");
		context->videoController->SetVideo(curSubsVideo);
		if (context->videoController->IsLoaded()) {
			context->videoController->JumpToFrame(context->ass->GetScriptInfoAsInt("Video Position"));

			long videoAr = 0;
			double videoArValue = 0.;
			if (arString.StartsWith("c")) {
				videoAr = 4;
				arString.Mid(1).ToDouble(&videoArValue);
			}
			else
				arString.ToLong(&videoAr);

			context->videoController->SetAspectRatio(videoAr, videoArValue);

			double videoZoom = 0.;
			if (context->ass->GetScriptInfo("Video Zoom Percent").ToDouble(&videoZoom))
				context->videoDisplay->SetZoom(videoZoom);
		}
	}

	context->videoController->LoadTimecodes(curSubsVFR);
	context->videoController->LoadKeyframes(curSubsKeyframes);

	// Audio
	if (audioChanged) {
		blockAudioLoad = false;
		try {
			if (!curSubsAudio)
				context->audioController->CloseAudio();
			else
				context->audioController->OpenAudio(curSubsAudio);
		}
		catch (agi::UserCancelException const&) { }
		catch (agi::FileNotAccessibleError const& err) {
			config::mru->Remove("Audio", STD_STR(curSubsAudio));
			wxMessageBox(lagi_wxString(err.GetMessage()), "Error opening audio", wxOK | wxICON_ERROR | wxCENTER, this);
		}
	}

	SetDisplayMode(1, 1);
}

void FrameMain::OnKeyDown(wxKeyEvent &event) {
	hotkey::check("Main Frame", context.get(), event);
}

void FrameMain::OnMouseWheel(wxMouseEvent &evt) {
	ForwardMouseWheelEvent(this, evt);
}

wxString FrameMain::GetScriptFileName() const {
	if (context->ass->filename.empty()) {
		// Apple HIG says "untitled" should not be capitalised
		// and the window is a document window, it shouldn't contain the app name
		// (The app name is already present in the menu bar)
#ifndef __WXMAC__
		return _("Untitled");
#else
		return _("untitled");
#endif
	}
	else
		return wxFileName(context->ass->filename).GetFullName();
}
