Extract Loading/Saving/Undo stuff from AssFile

Add SubsController, which deals with things like what subtitle file is
currently open, rather than the contents of the current subtitle file.
Move the rest of the relevant logic from FrameMain there in addition to
all of the stuff from AssFile.
This commit is contained in:
Thomas Goyne 2013-01-25 17:57:46 -08:00
parent 9a146d1fc0
commit a0d3dbc550
30 changed files with 564 additions and 471 deletions

View file

@ -265,6 +265,7 @@
<ClInclude Include="$(SrcDir)ass_info.h" />
<ClInclude Include="$(SrcDir)options.h" />
<ClInclude Include="$(SrcDir)search_replace_engine.h" />
<ClInclude Include="$(SrcDir)subs_controller.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(SrcDir)aegisublocale.cpp" />
@ -458,6 +459,7 @@
<ClCompile Include="$(SrcDir)dialog_autosave.cpp" />
<ClCompile Include="$(SrcDir)search_replace_engine.cpp" />
<ClCompile Include="$(SrcDir)initial_line_state.cpp" />
<ClCompile Include="$(SrcDir)subs_controller.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="$(SrcDir)res.rc" />

View file

@ -687,6 +687,9 @@
<ClInclude Include="$(SrcDir)initial_line_state.h">
<Filter>Utilities</Filter>
</ClInclude>
<ClInclude Include="$(SrcDir)subs_controller.h">
<Filter>ASS</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(SrcDir)ass_dialogue.cpp">
@ -1235,6 +1238,9 @@
<ClCompile Include="$(SrcDir)initial_line_state.cpp">
<Filter>Utilities</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)subs_controller.cpp">
<Filter>ASS</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="$(SrcDir)res.rc">

View file

@ -221,6 +221,7 @@ SRC += \
spline_curve.cpp \
standard_paths.cpp \
string_codec.cpp \
subs_controller.cpp \
subs_edit_box.cpp \
subs_edit_ctrl.cpp \
subs_grid.cpp \

View file

@ -40,6 +40,7 @@
#include "ass_file.h"
#include "compat.h"
#include "include/aegisub/context.h"
#include "subtitle_format.h"
#include <algorithm>
#include <memory>
@ -106,7 +107,11 @@ AssFile *AssExporter::ExportTransform(wxWindow *export_dialog, bool copy) {
void AssExporter::Export(agi::fs::path const& filename, std::string const& charset, wxWindow *export_dialog) {
std::unique_ptr<AssFile> subs(ExportTransform(export_dialog, true));
subs->Save(filename, false, false, charset);
const SubtitleFormat *writer = SubtitleFormat::GetWriter(filename);
if (!writer)
throw "Unknown file type.";
writer->WriteFile(subs.get(), filename, charset);
}
wxSizer *AssExporter::GetSettingsSizer(std::string const& name) {

View file

@ -40,23 +40,14 @@
#include "ass_info.h"
#include "ass_style.h"
#include "options.h"
#include "standard_paths.h"
#include "subtitle_format.h"
#include "text_file_reader.h"
#include "text_file_writer.h"
#include "utils.h"
#include <libaegisub/dispatch.h>
#include <libaegisub/fs.h>
#include <libaegisub/of_type_adaptor.h>
#include <libaegisub/util.h>
#include <algorithm>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/format.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>
#include <list>
#include <boost/filesystem/path.hpp>
namespace std {
template<>
@ -65,136 +56,12 @@ namespace std {
}
}
AssFile::AssFile ()
: commitId(0)
{
}
AssFile::~AssFile() {
auto copy = new EntryList;
copy->swap(Line);
agi::dispatch::Background().Async([=]{ delete copy; });
}
/// @brief Load generic subs
void AssFile::Load(agi::fs::path const& filename, std::string const& charset) {
const SubtitleFormat *reader = SubtitleFormat::GetReader(filename);
try {
AssFile temp;
reader->ReadFile(&temp, filename, charset);
bool found_style = false;
bool found_dialogue = false;
// Check if the file has at least one style and at least one dialogue line
for (auto const& line : temp.Line) {
AssEntryGroup type = line.Group();
if (type == ENTRY_STYLE) found_style = true;
if (type == ENTRY_DIALOGUE) found_dialogue = true;
if (found_style && found_dialogue) break;
}
// And if it doesn't add defaults for each
if (!found_style)
temp.InsertLine(new AssStyle);
if (!found_dialogue)
temp.InsertLine(new AssDialogue);
swap(temp);
}
catch (agi::UserCancelException const&) {
return;
}
// Set general data
this->filename = filename;
// Add comments and set vars
SetScriptInfo("ScriptType", "v4.00+");
// Push the initial state of the file onto the undo stack
UndoStack.clear();
RedoStack.clear();
undoDescription.clear();
autosavedCommitId = savedCommitId = commitId + 1;
Commit("", COMMIT_NEW);
FileOpen(filename);
}
void AssFile::Save(agi::fs::path const& filename, bool setfilename, bool addToRecent, std::string const& encoding) {
const SubtitleFormat *writer = SubtitleFormat::GetWriter(filename);
if (!writer)
throw "Unknown file type.";
if (setfilename) {
autosavedCommitId = savedCommitId = commitId;
this->filename = filename;
StandardPaths::SetPathValue("?script", filename.parent_path());
}
FileSave();
writer->WriteFile(this, filename, encoding);
if (addToRecent)
AddToRecent(filename);
}
agi::fs::path AssFile::AutoSave() {
if (commitId == autosavedCommitId)
return "";
auto path = StandardPaths::DecodePath(OPT_GET("Path/Auto/Save")->GetString());
if (path.empty())
path = filename.parent_path();
agi::fs::CreateDirectory(path);
auto name = filename.filename();
if (name.empty())
name = "Untitled";
path /= str(boost::format("%s.%s.AUTOSAVE.ass") % name % agi::util::strftime("%Y-%m-%d-%H-%M-%S"));
Save(path, false, false);
autosavedCommitId = commitId;
return path;
}
void AssFile::SaveMemory(std::vector<char> &dst) {
// Check if subs contain at least one style
// Add a default style if they don't for compatibility with libass/asa
if (GetStyles().empty())
InsertLine(new AssStyle);
// Prepare vector
dst.clear();
dst.reserve(0x4000);
// Write file
AssEntryGroup group = ENTRY_GROUP_MAX;
for (auto const& line : Line) {
if (group != line.Group()) {
group = line.Group();
boost::push_back(dst, line.GroupHeader() + "\r\n");
}
boost::push_back(dst, line.GetEntryData() + "\r\n");
}
}
bool AssFile::CanSave() const {
try {
return SubtitleFormat::GetWriter(filename)->CanSave(this);
}
catch (...) {
return false;
}
}
void AssFile::LoadDefault(bool defline) {
Line.push_back(*new AssInfo("Title", "Default Aegisub file"));
Line.push_back(*new AssInfo("ScriptType", "v4.00+"));
@ -211,26 +78,13 @@ void AssFile::LoadDefault(bool defline) {
if (defline)
Line.push_back(*new AssDialogue);
autosavedCommitId = savedCommitId = commitId + 1;
Commit("", COMMIT_NEW);
StandardPaths::SetPathValue("?script", "");
FileOpen("");
}
void AssFile::swap(AssFile &that) throw() {
// Intentionally does not swap undo stack related things
using std::swap;
swap(commitId, that.commitId);
swap(undoDescription, that.undoDescription);
swap(Line, that.Line);
Line.swap(that.Line);
}
AssFile::AssFile(const AssFile &from)
: undoDescription(from.undoDescription)
, commitId(from.commitId)
, filename(from.filename)
{
AssFile::AssFile(const AssFile &from) {
Line.clone_from(from.Line, std::mem_fun_ref(&AssEntry::Clone), delete_ptr());
}
AssFile& AssFile::operator=(AssFile from) {
@ -332,83 +186,17 @@ AssStyle *AssFile::GetStyle(std::string const& name) {
return nullptr;
}
void AssFile::AddToRecent(agi::fs::path const& file) const {
config::mru->Add("Subtitle", file);
OPT_SET("Path/Last/Subtitles")->SetString(file.parent_path().string());
}
int AssFile::Commit(wxString const& desc, int type, int amend_id, AssEntry *single_line) {
AssFileCommit c = { desc, &amend_id, single_line };
PushState(c);
int AssFile::Commit(wxString const& desc, int type, int amendId, AssEntry *single_line) {
std::set<const AssEntry*> changed_lines;
if (single_line)
changed_lines.insert(single_line);
++commitId;
// Allow coalescing only if it's the last change and the file has not been
// saved since the last change
if (commitId == amendId+1 && RedoStack.empty() && savedCommitId+1 != commitId && autosavedCommitId+1 != commitId) {
// If only one line changed just modify it instead of copying the file
if (single_line) {
entryIter this_it = Line.begin(), undo_it = UndoStack.back().Line.begin();
while (&*this_it != single_line) {
++this_it;
++undo_it;
}
UndoStack.back().Line.insert(undo_it, *single_line->Clone());
delete &*undo_it;
}
else {
UndoStack.back() = *this;
}
AnnounceCommit(type, changed_lines);
return commitId;
}
RedoStack.clear();
// Place copy on stack
undoDescription = desc;
UndoStack.push_back(*this);
// Cap depth
int depth = std::max<int>(OPT_GET("Limits/Undo Levels")->GetInt(), 2);
while ((int)UndoStack.size() > depth) {
UndoStack.pop_front();
}
if (UndoStack.size() > 1 && OPT_GET("App/Auto/Save on Every Change")->GetBool() && !filename.empty() && CanSave())
Save(filename);
AnnounceCommit(type, changed_lines);
return commitId;
}
void AssFile::Undo() {
if (UndoStack.size() <= 1) return;
RedoStack.emplace_back();
swap(RedoStack.back());
UndoStack.pop_back();
*this = UndoStack.back();
AnnounceCommit(COMMIT_NEW, std::set<const AssEntry*>());
}
void AssFile::Redo() {
if (RedoStack.empty()) return;
swap(RedoStack.back());
UndoStack.push_back(*this);
RedoStack.pop_back();
AnnounceCommit(COMMIT_NEW, std::set<const AssEntry*>());
}
wxString AssFile::GetUndoDescription() const {
return IsUndoStackEmpty() ? "" : UndoStack.back().undoDescription;
}
wxString AssFile::GetRedoDescription() const {
return IsRedoStackEmpty() ? "" : RedoStack.back().undoDescription;
return amend_id;
}
bool AssFile::CompStart(const AssDialogue* lft, const AssDialogue* rgt) {
@ -434,13 +222,6 @@ void AssFile::Sort(CompFunc comp, std::set<AssDialogue*> const& limit) {
Sort(Line, comp, limit);
}
namespace {
struct AssEntryComp : public std::binary_function<AssEntry, AssEntry, bool> {
AssFile::CompFunc comp;
bool operator()(AssEntry const&a, AssEntry const&b) const {
return comp(static_cast<const AssDialogue*>(&a), static_cast<const AssDialogue*>(&b));
}
};
inline bool is_dialogue(AssEntry *e, std::set<AssDialogue*> const& limit) {
AssDialogue *d = dynamic_cast<AssDialogue*>(e);
return d && (limit.empty() || limit.count(d));
@ -448,8 +229,10 @@ namespace {
}
void AssFile::Sort(EntryList &lst, CompFunc comp, std::set<AssDialogue*> const& limit) {
AssEntryComp compE;
compE.comp = comp;
auto compE = [&](AssEntry const& a, AssEntry const& b) {
return comp(static_cast<const AssDialogue*>(&a), static_cast<const AssDialogue*>(&b));
};
// Sort each block of AssDialogues separately, leaving everything else untouched
for (entryIter begin = lst.begin(); begin != lst.end(); ++begin) {
if (!is_dialogue(&*begin, limit)) continue;
@ -465,5 +248,3 @@ void AssFile::Sort(EntryList &lst, CompFunc comp, std::set<AssDialogue*> const&
begin = --end;
}
}
AssFile *AssFile::top;

View file

@ -32,61 +32,43 @@
/// @ingroup subs_storage
///
#include <boost/container/list.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/intrusive/list.hpp>
#include <set>
#include <vector>
#include <wx/string.h>
#include "ass_entry.h"
#include <libaegisub/fs_fwd.h>
#include <libaegisub/signal.h>
#include "ass_entry.h"
#include <boost/intrusive/list.hpp>
#include <set>
#include <vector>
class AssDialogue;
class AssStyle;
class AssAttachment;
class wxString;
typedef boost::intrusive::make_list<AssEntry, boost::intrusive::constant_time_size<false>>::type EntryList;
typedef EntryList::iterator entryIter;
typedef EntryList::const_iterator constEntryIter;
struct AssFileCommit {
wxString const& message;
int *commit_id;
AssEntry *single_line;
};
class AssFile {
boost::container::list<AssFile> UndoStack;
boost::container::list<AssFile> RedoStack;
wxString undoDescription;
/// Revision counter for undo coalescing and modified state tracking
int commitId;
/// Last saved version of this file
int savedCommitId;
/// Last autosaved version of this file
int autosavedCommitId;
/// A set of changes has been committed to the file (AssFile::CommitType)
/// A set of changes has been committed to the file (AssFile::COMMITType)
agi::signal::Signal<int, std::set<const AssEntry*> const&> AnnounceCommit;
/// A new file has been opened (filename)
agi::signal::Signal<agi::fs::path> FileOpen;
/// The file is about to be saved
/// This signal is intended for adding metadata such as video filename,
/// frame number, etc. Ideally this would all be done immediately rather
/// than waiting for a save, but that causes (more) issues with undo
agi::signal::Signal<> FileSave;
agi::signal::Signal<AssFileCommit> PushState;
public:
/// The lines in the file
EntryList Line;
/// The filename of this file, if any
agi::fs::path filename;
AssFile();
AssFile() { }
AssFile(const AssFile &from);
AssFile& operator=(AssFile from);
~AssFile();
/// Does the file have unsaved changes?
bool IsModified() const { return commitId != savedCommitId; };
/// @brief Load default file
/// @param defline Add a blank line to the file
void LoadDefault(bool defline=true);
@ -103,30 +85,6 @@ public:
void swap(AssFile &) throw();
/// @brief Load from a file
/// @param file File name
/// @param charset Character set of file or empty to autodetect
void Load(agi::fs::path const& file, std::string const& charset="");
/// @brief Save to a file
/// @param file Path to save to
/// @param setfilename Should the filename be changed to the passed path?
/// @param addToRecent Should the file be added to the MRU list?
/// @param encoding Encoding to use, or empty to let the writer decide (which usually means "App/Save Charset")
void Save(agi::fs::path const& file, bool setfilename=false, bool addToRecent=true, std::string const& encoding="");
/// @brief Autosave the file if there have been any chances since the last autosave
/// @return File name used or empty if no save was performed
agi::fs::path AutoSave();
/// @brief Save to a memory buffer. Used for subtitle providers which support it
/// @param[out] dst Destination vector
void SaveMemory(std::vector<char> &dst);
/// Add file name to the MRU list
void AddToRecent(agi::fs::path const& file) const;
/// Can the file be saved in its current format?
bool CanSave() const;
/// @brief Get the script resolution
/// @param[out] w Width
/// @param[in] h Height
@ -169,8 +127,7 @@ public:
};
DEFINE_SIGNAL_ADDERS(AnnounceCommit, AddCommitListener)
DEFINE_SIGNAL_ADDERS(FileOpen, AddFileOpenListener)
DEFINE_SIGNAL_ADDERS(FileSave, AddFileSaveListener)
DEFINE_SIGNAL_ADDERS(PushState, AddUndoManager)
/// @brief Flag the file as modified and push a copy onto the undo stack
/// @param desc Undo description
@ -179,21 +136,6 @@ public:
/// @param single_line Line which was changed, if only one line was
/// @return Unique identifier for the new undo group
int Commit(wxString const& desc, int type, int commitId = -1, AssEntry *single_line = 0);
/// @brief Undo the last set of changes to the file
void Undo();
/// @brief Redo the last undone changes
void Redo();
/// Check if undo stack is empty
bool IsUndoStackEmpty() const { return UndoStack.size() <= 1; };
/// Check if redo stack is empty
bool IsRedoStackEmpty() const { return RedoStack.empty(); };
/// Get the description of the first undoable change
wxString GetUndoDescription() const;
/// Get the description of the first redoable change
wxString GetRedoDescription() const;
/// Current script file. It is "above" the stack.
static AssFile *top;
/// Comparison function for use when sorting
typedef bool (*CompFunc)(const AssDialogue* lft, const AssDialogue* rgt);

View file

@ -46,6 +46,7 @@
#include "pen.h"
#include "options.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "utils.h"
#include "video_context.h"
@ -56,7 +57,7 @@
AudioController::AudioController(agi::Context *context)
: context(context)
, subtitle_save_slot(context->ass->AddFileSaveListener(&AudioController::OnSubtitlesSave, this))
, subtitle_save_slot(context->subsController->AddFileSaveListener(&AudioController::OnSubtitlesSave, this))
, player(0)
, provider(0)
, playback_mode(PM_NotPlaying)
@ -238,10 +239,8 @@ void AudioController::SetTimingController(AudioTimingController *new_controller)
void AudioController::OnTimingControllerUpdatedPrimaryRange()
{
if (playback_mode == PM_PrimaryRange)
{
player->SetEndPosition(SamplesFromMilliseconds(timing_controller->GetPrimaryPlaybackRange().end()));
}
}
void AudioController::OnSubtitlesSave()
{

View file

@ -44,6 +44,7 @@
#include "options.h"
#include "standard_paths.h"
#include "string_codec.h"
#include "subs_controller.h"
#include "subtitle_format.h"
#include "utils.h"
@ -379,8 +380,8 @@ namespace Automation4 {
LocalScriptManager::LocalScriptManager(agi::Context *c)
: context(c)
{
slots.push_back(c->ass->AddFileSaveListener(&LocalScriptManager::OnSubtitlesSave, this));
slots.push_back(c->ass->AddFileOpenListener(&LocalScriptManager::Reload, this));
slots.push_back(c->subsController->AddFileSaveListener(&LocalScriptManager::OnSubtitlesSave, this));
slots.push_back(c->subsController->AddFileOpenListener(&LocalScriptManager::Reload, this));
}
void LocalScriptManager::Reload()
@ -403,7 +404,7 @@ namespace Automation4 {
agi::fs::path basepath;
if (first_char == '~') {
basepath = context->ass->filename.parent_path();
basepath = context->subsController->Filename().parent_path();
} else if (first_char == '$') {
basepath = autobasefn;
} else if (first_char == '/') {

View file

@ -47,6 +47,7 @@
#include "include/aegisub/context.h"
#include "main.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "standard_paths.h"
#include "video_context.h"
#include "utils.h"
@ -159,8 +160,8 @@ namespace {
int get_file_name(lua_State *L)
{
const agi::Context *c = get_context(L);
if (c && !c->ass->filename.empty())
push_value(L, c->ass->filename.filename());
if (c && !c->subsController->Filename().empty())
push_value(L, c->subsController->Filename().filename());
else
lua_pushnil(L);
return 1;

View file

@ -58,6 +58,7 @@
#include "frame_main.h"
#include "options.h"
#include "utils.h"
#include "subs_controller.h"
#include "video_context.h"
#include "video_slider.h"
@ -117,8 +118,8 @@ BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context, const wxSize& size,
OPT_SUB("Subtitle/Grid/Font Size", &BaseGrid::UpdateStyle, this);
OPT_SUB("Subtitle/Grid/Highlight Subtitles in Frame", &BaseGrid::OnHighlightVisibleChange, this);
context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this);
context->ass->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this);
context->ass->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this);
context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this);
context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this);
OPT_SUB("Colour/Subtitle Grid/Active Border", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Background/Background", &BaseGrid::UpdateStyle, this);

View file

@ -50,6 +50,7 @@
#include "../initial_line_state.h"
#include "../options.h"
#include "../search_replace_engine.h"
#include "../subs_controller.h"
#include "../subs_edit_ctrl.h"
#include "../subs_grid.h"
#include "../text_selection_controller.h"
@ -307,7 +308,7 @@ void show_color_picker(const agi::Context *c, agi::Color (AssStyle::*field), con
commit_text(c, _("set color"), -1, -1, &commit_id);
if (!ok) {
c->ass->Undo();
c->subsController->Undo();
c->textSelectionController->SetSelection(initial_sel_start, initial_sel_end);
}
}
@ -876,22 +877,22 @@ struct edit_redo : public Command {
CMD_TYPE(COMMAND_VALIDATE | COMMAND_DYNAMIC_NAME)
wxString StrMenu(const agi::Context *c) const {
return c->ass->IsRedoStackEmpty() ?
return c->subsController->IsRedoStackEmpty() ?
_("Nothing to &redo") :
wxString::Format(_("&Redo %s"), c->ass->GetRedoDescription());
wxString::Format(_("&Redo %s"), c->subsController->GetRedoDescription());
}
wxString StrDisplay(const agi::Context *c) const {
return c->ass->IsRedoStackEmpty() ?
return c->subsController->IsRedoStackEmpty() ?
_("Nothing to redo") :
wxString::Format(_("Redo %s"), c->ass->GetRedoDescription());
wxString::Format(_("Redo %s"), c->subsController->GetRedoDescription());
}
bool Validate(const agi::Context *c) {
return !c->ass->IsRedoStackEmpty();
return !c->subsController->IsRedoStackEmpty();
}
void operator()(agi::Context *c) {
c->ass->Redo();
c->subsController->Redo();
}
};
@ -902,22 +903,22 @@ struct edit_undo : public Command {
CMD_TYPE(COMMAND_VALIDATE | COMMAND_DYNAMIC_NAME)
wxString StrMenu(const agi::Context *c) const {
return c->ass->IsUndoStackEmpty() ?
return c->subsController->IsUndoStackEmpty() ?
_("Nothing to &undo") :
wxString::Format(_("&Undo %s"), c->ass->GetUndoDescription());
wxString::Format(_("&Undo %s"), c->subsController->GetUndoDescription());
}
wxString StrDisplay(const agi::Context *c) const {
return c->ass->IsUndoStackEmpty() ?
return c->subsController->IsUndoStackEmpty() ?
_("Nothing to undo") :
wxString::Format(_("Undo %s"), c->ass->GetUndoDescription());
wxString::Format(_("Undo %s"), c->subsController->GetUndoDescription());
}
bool Validate(const agi::Context *c) {
return !c->ass->IsUndoStackEmpty();
return !c->subsController->IsUndoStackEmpty();
}
void operator()(agi::Context *c) {
c->ass->Undo();
c->subsController->Undo();
}
};

View file

@ -38,10 +38,10 @@
#include "../audio_controller.h"
#include "../compat.h"
#include "../frame_main.h"
#include "../include/aegisub/context.h"
#include "../main.h"
#include "../options.h"
#include "../subs_controller.h"
#include "../video_context.h"
#include <wx/event.h>
@ -93,7 +93,7 @@ struct recent_subtitle_entry : public Command {
STR_HELP("Open recent subtitles")
void operator()(agi::Context *c, int id) {
wxGetApp().frame->LoadSubtitles(config::mru->GetEntry("Subtitle", id));
c->subsController->Load(config::mru->GetEntry("Subtitle", id));
}
};

View file

@ -47,12 +47,12 @@
#include "../dialog_properties.h"
#include "../dialog_search_replace.h"
#include "../dialog_spellchecker.h"
#include "../frame_main.h"
#include "../include/aegisub/context.h"
#include "../main.h"
#include "../options.h"
#include "../search_replace_engine.h"
#include "../selection_controller.h"
#include "../subs_controller.h"
#include "../subtitle_format.h"
#include "../utils.h"
#include "../video_context.h"
@ -246,8 +246,8 @@ struct subtitle_new : public Command {
STR_HELP("New subtitles")
void operator()(agi::Context *c) {
if (wxGetApp().frame->TryToCloseSubs() != wxCANCEL)
c->ass->LoadDefault();
if (c->subsController->TryToClose() != wxCANCEL)
c->subsController->Close();
}
};
@ -262,7 +262,7 @@ struct subtitle_open : public Command {
void operator()(agi::Context *c) {
auto filename = OpenFileSelector(_("Open subtitles file"), "Path/Last/Subtitles", "","", SubtitleFormat::GetWildcards(0), c->parent);
if (!filename.empty())
wxGetApp().frame->LoadSubtitles(filename);
c->subsController->Load(filename);
}
};
@ -275,7 +275,7 @@ struct subtitle_open_autosave : public Command {
void operator()(agi::Context *c) {
DialogAutosave dialog(c->parent);
if (dialog.ShowModal() == wxID_OK)
wxGetApp().frame->LoadSubtitles(dialog.ChosenFile());
c->subsController->Load(dialog.ChosenFile());
}
};
@ -293,7 +293,7 @@ struct subtitle_open_charset : public Command {
wxString charset = wxGetSingleChoice(_("Choose charset code:"), _("Charset"), agi::charset::GetEncodingsList<wxArrayString>(), c->parent, -1, -1, true, 250, 200);
if (charset.empty()) return;
wxGetApp().frame->LoadSubtitles(filename, from_wx(charset));
c->subsController->Load(filename, from_wx(charset));
}
};
@ -306,7 +306,7 @@ struct subtitle_open_video : public Command {
CMD_TYPE(COMMAND_VALIDATE)
void operator()(agi::Context *c) {
wxGetApp().frame->LoadSubtitles(c->videoController->GetVideoName(), "binary");
c->subsController->Load(c->videoController->GetVideoName(), "binary");
}
bool Validate(const agi::Context *c) {
@ -332,13 +332,13 @@ static void save_subtitles(agi::Context *c, agi::fs::path filename) {
if (filename.empty()) {
c->videoController->Stop();
filename = SaveFileSelector(_("Save subtitles file"), "Path/Last/Subtitles",
c->ass->filename.stem().string() + ".ass", "ass",
c->subsController->Filename().stem().string() + ".ass", "ass",
"Advanced Substation Alpha (*.ass)|*.ass", c->parent);
if (filename.empty()) return;
}
try {
c->ass->Save(filename, true, true);
c->subsController->Save(filename);
}
catch (const agi::Exception& err) {
wxMessageBox(to_wx(err.GetMessage()), "Error", wxOK | wxICON_ERROR | wxCENTER, c->parent);
@ -360,11 +360,11 @@ struct subtitle_save : public Command {
CMD_TYPE(COMMAND_VALIDATE)
void operator()(agi::Context *c) {
save_subtitles(c, c->ass->CanSave() ? c->ass->filename : "");
save_subtitles(c, c->subsController->CanSave() ? c->subsController->Filename() : "");
}
bool Validate(const agi::Context *c) {
return c->ass->IsModified();
return c->subsController->IsModified();
}
};

View file

@ -48,6 +48,7 @@
#include <libaegisub/charset_conv.h>
#include <algorithm>
#include <boost/filesystem/path.hpp>
#include <boost/tokenizer.hpp>
#include <wx/button.h>

View file

@ -35,6 +35,7 @@
#include "scintilla_text_ctrl.h"
#include "selection_controller.h"
#include "standard_paths.h"
#include "subs_controller.h"
#include "utils.h"
#include <libaegisub/dispatch.h>

View file

@ -31,6 +31,7 @@
#include "help_button.h"
#include "libresrc/libresrc.h"
#include "options.h"
#include "subs_controller.h"
#include "standard_paths.h"
#include "timeedit_ctrl.h"
#include "video_context.h"
@ -278,7 +279,7 @@ void DialogShiftTimes::OnHistoryClick(wxCommandEvent &evt) {
void DialogShiftTimes::SaveHistory(json::Array const& shifted_blocks) {
json::Object new_entry;
new_entry["filename"] = context->ass->filename.filename().string();
new_entry["filename"] = context->subsController->Filename().filename().string();
new_entry["is by time"] = shift_by_time->GetValue();
new_entry["is backward"] = shift_backward->GetValue();
new_entry["amount"] = from_wx(shift_by_time->GetValue() ? shift_time->GetValue() : shift_frames->GetValue());

View file

@ -47,6 +47,7 @@
#include "options.h"
#include "persist_location.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "standard_paths.h"
#include "subtitle_format.h"
#include "utils.h"
@ -565,7 +566,11 @@ void DialogStyleManager::OnCurrentImport() {
AssFile temp;
try {
temp.Load(filename);
auto reader = SubtitleFormat::GetReader(filename);
if (!reader)
wxMessageBox("Unsupported subtitle format", "Error", wxOK | wxICON_ERROR | wxCENTER, this);
else
reader->ReadFile(&temp, filename);
}
catch (agi::Exception const& err) {
wxMessageBox(to_wx(err.GetChainedMessage()), "Error", wxOK | wxICON_ERROR | wxCENTER, this);

View file

@ -60,10 +60,10 @@
#include "main.h"
#include "options.h"
#include "search_replace_engine.h"
#include "subs_controller.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"
@ -213,18 +213,20 @@ FrameMain::FrameMain (wxArrayString args)
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());
context->ass = new AssFile;
StartupLog("Initializing context controls");
context->subsController = new SubsController(context.get());
context->ass->AddCommitListener(&FrameMain::UpdateTitle, this);
context->subsController->AddFileOpenListener(&FrameMain::OnSubtitlesOpen, this);
context->subsController->AddFileSaveListener(&FrameMain::UpdateTitle, this);
context->audioController = new AudioController(context.get());
context->audioController->AddAudioOpenListener(&FrameMain::OnAudioOpen, this);
context->audioController->AddAudioCloseListener(&FrameMain::OnAudioClose, this);
context->local_scripts = new Automation4::LocalScriptManager(context.get());
// Initialized later due to that the selection controller is currently the subtitles grid
context->selectionController = 0;
@ -280,7 +282,7 @@ FrameMain::FrameMain (wxArrayString args)
SetDropTarget(new AegisubFileDropTarget(this));
StartupLog("Load default file");
context->ass->LoadDefault();
context->subsController->Close();
StartupLog("Display main window");
AddFullScreenButton(this);
@ -408,75 +410,6 @@ void FrameMain::InitContents() {
StartupLog("Leaving InitContents");
}
void FrameMain::LoadSubtitles(agi::fs::path const& filename, std::string const& charset) {
if (TryToCloseSubs() == wxCANCEL) return;
try {
// Make sure that file isn't actually a timecode file
try {
TextFileReader testSubs(filename, charset);
std::string cur = testSubs.ReadLineFromFile();
if (boost::starts_with(cur, "# 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);
StandardPaths::SetPathValue("?script", filename);
config::mru->Add("Subtitle", filename);
OPT_SET("Path/Last/Subtitles")->SetString(filename.parent_path().string());
// Save backup of file
if (context->ass->CanSave() && OPT_GET("App/Auto/Backup")->GetBool()) {
if (agi::fs::FileExists(filename)) {
auto path_str = OPT_GET("Path/Auto/Backup")->GetString();
agi::fs::path path;
if (path_str.empty())
path = filename.parent_path();
else
path = StandardPaths::DecodePath(path_str);
agi::fs::CreateDirectory(path);
agi::fs::Copy(filename, path/(filename.stem().string() + ".ORIGINAL" + filename.extension().string()));
}
}
}
catch (agi::fs::FileNotFound const&) {
wxMessageBox(filename.wstring() + " not found.", "Error", wxOK | wxICON_ERROR | wxCENTER, this);
config::mru->Remove("Subtitle", filename);
return;
}
catch (agi::Exception const& err) {
wxMessageBox(to_wx(err.GetChainedMessage()), "Error", wxOK | wxICON_ERROR | wxCENTER, this);
}
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;
@ -512,8 +445,8 @@ void FrameMain::SetDisplayMode(int video, int audio) {
void FrameMain::UpdateTitle() {
wxString newTitle;
if (context->ass->IsModified()) newTitle << "* ";
newTitle << GetScriptFileName();
if (context->subsController->IsModified()) newTitle << "* ";
newTitle << context->subsController->Filename().filename().wstring();
#ifndef __WXMAC__
newTitle << " - Aegisub " << GetAegisubLongVersionString();
@ -521,7 +454,7 @@ void FrameMain::UpdateTitle() {
#if defined(__WXMAC__) && !defined(__LP64__)
// On Mac, set the mark in the close button
OSXSetModified(context->ass->IsModified());
OSXSetModified(context->subsController->IsModified());
#endif
if (GetTitle() != newTitle) SetTitle(newTitle);
@ -596,7 +529,7 @@ bool FrameMain::LoadList(wxArrayString list) {
// Load files
if (subs.size())
LoadSubtitles(subs);
context->subsController->Load(subs);
if (blockVideoLoad) {
blockVideoLoad = false;
@ -635,20 +568,17 @@ BEGIN_EVENT_TABLE(FrameMain, wxFrame)
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) {
if (context->subsController->TryToClose(event.CanVeto()) == wxCANCEL) {
event.Veto();
return;
}
delete context->dialog;
context->dialog = 0;
context->dialog = nullptr;
// Store maximization state
OPT_SET("App/Maximized")->SetBool(IsMaximized());
@ -657,7 +587,7 @@ void FrameMain::OnCloseWindow (wxCloseEvent &event) {
}
void FrameMain::OnAutoSave(wxTimerEvent &) try {
auto fn = context->ass->AutoSave();
auto fn = context->subsController->AutoSave();
if (!fn.empty())
StatusTimeout(wxString::Format(_("File backup saved as \"%s\"."), fn.wstring()));
}
@ -766,18 +696,3 @@ void FrameMain::OnKeyDown(wxKeyEvent &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 context->ass->filename.filename().wstring();
}

View file

@ -45,6 +45,7 @@
#include <wx/sizer.h>
#include <wx/timer.h>
class AegisubApp;
class AegisubFileDropTarget;
class AssFile;
class AudioBox;
@ -62,6 +63,7 @@ class VideoZoomSlider;
namespace agi { struct Context; class OptionValue; }
class FrameMain: public wxFrame {
friend class AegisubApp;
friend class AegisubFileDropTarget;
std::unique_ptr<agi::Context> context;
@ -88,7 +90,6 @@ class FrameMain: public wxFrame {
void OnFilesDropped(wxThreadEvent &evt);
bool LoadList(wxArrayString list);
void UpdateTitle();
wxString GetScriptFileName() const;
void OnKeyDown(wxKeyEvent &event);
void OnMouseWheel(wxMouseEvent &evt);
@ -134,11 +135,5 @@ public:
bool IsVideoShown() const { return showVideo; }
bool IsAudioShown() const { return showAudio; }
/// Close the currently open subs, asking the user if they want to save if there are unsaved changes
/// @param enableCancel Should the user be able to cancel the close?
int TryToCloseSubs(bool enableCancel=true);
void LoadSubtitles(agi::fs::path const& filename, std::string const& charset="");
DECLARE_EVENT_TABLE()
};

View file

@ -7,6 +7,7 @@ class DialogManager;
class SearchReplaceEngine;
class InitialLineState;
template<class T> class SelectionController;
class SubsController;
class SubsTextEditCtrl;
class SubtitlesGrid;
class TextSelectionController;
@ -26,6 +27,7 @@ struct Context {
// Controllers
AudioController *audioController;
SelectionController<AssDialogue *> *selectionController;
SubsController *subsController;
TextSelectionController *textSelectionController;
VideoContext *videoController;

View file

@ -45,11 +45,13 @@
#include "export_fixstyle.h"
#include "export_framerate.h"
#include "frame_main.h"
#include "include/aegisub/context.h"
#include "main.h"
#include "libresrc/libresrc.h"
#include "options.h"
#include "plugin_manager.h"
#include "standard_paths.h"
#include "subs_controller.h"
#include "subtitle_format.h"
#include "version.h"
#include "video_context.h"
@ -364,15 +366,15 @@ StackWalker::~StackWalker() {
/// Message displayed when an exception has occurred.
const static wxString exception_message = _("Oops, Aegisub has crashed!\n\nAn attempt has been made to save a copy of your file to:\n\n%s\n\nAegisub will now close.");
static void UnhandledExeception(bool stackWalk) {
static void UnhandledExeception(bool stackWalk, agi::Context *c) {
#if (!defined(_DEBUG) || defined(WITH_EXCEPTIONS)) && (wxUSE_ON_FATAL_EXCEPTION+0)
if (AssFile::top) {
if (c->ass && c->subsController) {
auto path = StandardPaths::DecodePath("?user/recovered");
agi::fs::CreateDirectory(path);
auto filename = AssFile::top->filename.empty() ? "untitled" : AssFile::top->filename.stem().string();
auto filename = c->subsController->Filename().stem();
path /= str(boost::format("%s.%s.ass") % filename % agi::util::strftime("%Y-%m-%d-%H-%M-%S"));
AssFile::top->Save(path, false, false);
c->subsController->Save(path);
#if wxUSE_STACKWALKER == 1
if (stackWalk) {
@ -397,11 +399,11 @@ static void UnhandledExeception(bool stackWalk) {
}
void AegisubApp::OnUnhandledException() {
UnhandledExeception(false);
UnhandledExeception(false, frame ? frame->context.get() : nullptr);
}
void AegisubApp::OnFatalException() {
UnhandledExeception(true);
UnhandledExeception(true, frame ? frame->context.get() : nullptr);
}
void AegisubApp::HandleEvent(wxEvtHandler *handler, wxEventFunction func, wxEvent& event) const {
@ -456,9 +458,6 @@ int AegisubApp::OnRun() {
}
void AegisubApp::MacOpenFile(const wxString &filename) {
if (frame != nullptr && !filename.empty()) {
frame->LoadSubtitles(from_wx(filename));
wxFileName filepath(filename);
OPT_SET("Path/Last/Subtitles")->SetString(from_wx(filepath.GetPath()));
}
if (frame && !filename.empty())
frame->context->subsController->Load(agi::fs::path(filename));
}

View file

@ -0,0 +1,311 @@
// Copyright (c) 2013, 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.
//
// Aegisub Project http://www.aegisub.org/
#include "config.h"
#include "subs_controller.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "ass_style.h"
#include "compat.h"
#include "command/command.h"
#include "include/aegisub/context.h"
#include "options.h"
#include "standard_paths.h"
#include "subtitle_format.h"
#include "text_file_reader.h"
#include "video_context.h"
#include <libaegisub/fs.h>
#include <libaegisub/util.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/format.hpp>
#include <wx/msgdlg.h>
struct SubsController::UndoInfo {
AssFile file;
wxString undo_description;
int commit_id;
UndoInfo(AssFile const& f, wxString const& d, int c) : file(f), undo_description(d), commit_id(c) { }
};
SubsController::SubsController(agi::Context *context)
: context(context)
, undo_connection(context->ass->AddUndoManager(&SubsController::OnCommit, this))
, commit_id(0)
, saved_commit_id(0)
, autosaved_commit_id(0)
{
}
void SubsController::Load(agi::fs::path const& filename, std::string const& charset) {
if (TryToClose() == wxCANCEL) return;
// Make sure that file isn't actually a timecode file
try {
TextFileReader testSubs(filename, charset);
std::string cur = testSubs.ReadLineFromFile();
if (boost::starts_with(cur, "# 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
}
const SubtitleFormat *reader = SubtitleFormat::GetReader(filename);
try {
AssFile temp;
reader->ReadFile(&temp, filename, charset);
bool found_style = false;
bool found_dialogue = false;
// Check if the file has at least one style and at least one dialogue line
for (auto const& line : temp.Line) {
AssEntryGroup type = line.Group();
if (type == ENTRY_STYLE) found_style = true;
if (type == ENTRY_DIALOGUE) found_dialogue = true;
if (found_style && found_dialogue) break;
}
// And if it doesn't add defaults for each
if (!found_style)
temp.InsertLine(new AssStyle);
if (!found_dialogue)
temp.InsertLine(new AssDialogue);
context->ass->swap(temp);
}
catch (agi::UserCancelException const&) {
return;
}
catch (agi::fs::FileNotFound const&) {
wxMessageBox(filename.wstring() + " not found.", "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
config::mru->Remove("Subtitle", filename);
return;
}
catch (agi::Exception const& err) {
wxMessageBox(to_wx(err.GetChainedMessage()), "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
return;
}
catch (std::exception const& err) {
wxMessageBox(to_wx(err.what()), "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
return;
}
catch (...) {
wxMessageBox("Unknown error", "Error", wxOK | wxICON_ERROR | wxCENTER, context->parent);
return;
}
SetFileName(filename);
// Push the initial state of the file onto the undo stack
undo_stack.clear();
redo_stack.clear();
autosaved_commit_id = saved_commit_id = commit_id + 1;
context->ass->Commit("", AssFile::COMMIT_NEW);
// Save backup of file
if (CanSave() && OPT_GET("App/Auto/Backup")->GetBool()) {
auto path_str = OPT_GET("Path/Auto/Backup")->GetString();
agi::fs::path path;
if (path_str.empty())
path = filename.parent_path();
else
path = StandardPaths::DecodePath(path_str);
agi::fs::CreateDirectory(path);
agi::fs::Copy(filename, path/(filename.stem().string() + ".ORIGINAL" + filename.extension().string()));
}
FileOpen(filename);
}
void SubsController::Save(agi::fs::path const& filename, std::string const& encoding) {
const SubtitleFormat *writer = SubtitleFormat::GetWriter(filename);
if (!writer)
throw "Unknown file type.";
int old_autosaved_commit_id = autosaved_commit_id, old_saved_commit_id = saved_commit_id;
try {
autosaved_commit_id = saved_commit_id = commit_id;
// Have to set these now for the sake of things that want to save paths
// relative to the script in the header
this->filename = filename;
StandardPaths::SetPathValue("?script", filename.parent_path());
FileSave();
writer->WriteFile(context->ass, filename, encoding);
}
catch (...) {
autosaved_commit_id = old_autosaved_commit_id;
saved_commit_id = old_saved_commit_id;
throw;
}
SetFileName(filename);
}
void SubsController::Close() {
undo_stack.clear();
redo_stack.clear();
autosaved_commit_id = saved_commit_id = commit_id + 1;
filename.clear();
context->ass->Line.clear();
context->ass->LoadDefault();
context->ass->Commit("", AssFile::COMMIT_NEW);
}
int SubsController::TryToClose(bool allow_cancel) const {
if (!IsModified())
return wxYES;
int flags = wxYES_NO;
if (allow_cancel)
flags |= wxCANCEL;
int result = wxMessageBox(wxString::Format(_("Do you want to save changes to %s?"), Filename().wstring()), _("Unsaved changes"), flags, context->parent);
if (result == wxYES) {
cmd::call("subtitle/save", context);
// If it fails saving, return cancel anyway
return IsModified() ? wxCANCEL : wxYES;
}
return result;
}
agi::fs::path SubsController::AutoSave() {
if (commit_id == autosaved_commit_id)
return "";
auto path = StandardPaths::DecodePath(OPT_GET("Path/Auto/Save")->GetString());
if (path.empty())
path = filename.parent_path();
agi::fs::CreateDirectory(path);
auto name = filename.filename();
if (name.empty())
name = "Untitled";
path /= str(boost::format("%s.%s.AUTOSAVE.ass") % name.string() % agi::util::strftime("%Y-%m-%d-%H-%M-%S"));
SubtitleFormat::GetWriter(path)->WriteFile(context->ass, path);
autosaved_commit_id = commit_id;
return path;
}
bool SubsController::CanSave() const {
try {
return SubtitleFormat::GetWriter(filename)->CanSave(context->ass);
}
catch (...) {
return false;
}
}
void SubsController::SetFileName(agi::fs::path const& path) {
filename = path;
StandardPaths::SetPathValue("?script", path.parent_path());
config::mru->Add("Subtitle", path);
OPT_SET("Path/Last/Subtitles")->SetString(filename.parent_path().string());
}
void SubsController::OnCommit(AssFileCommit c) {
if (c.message.empty() && !undo_stack.empty()) return;
++commit_id;
// Allow coalescing only if it's the last change and the file has not been
// saved since the last change
if (commit_id == *c.commit_id+1 && redo_stack.empty() && saved_commit_id+1 != commit_id && autosaved_commit_id+1 != commit_id) {
// If only one line changed just modify it instead of copying the file
if (c.single_line) {
entryIter this_it = context->ass->Line.begin(), undo_it = undo_stack.back().file.Line.begin();
while (&*this_it != c.single_line) {
++this_it;
++undo_it;
}
undo_stack.back().file.Line.insert(undo_it, *c.single_line->Clone());
delete &*undo_it;
}
else
undo_stack.back().file = *context->ass;
*c.commit_id = commit_id;
return;
}
redo_stack.clear();
undo_stack.emplace_back(*context->ass, c.message, commit_id);
int depth = std::max<int>(OPT_GET("Limits/Undo Levels")->GetInt(), 2);
while ((int)undo_stack.size() > depth)
undo_stack.pop_front();
if (undo_stack.size() > 1 && OPT_GET("App/Auto/Save on Every Change")->GetBool() && !filename.empty() && CanSave())
Save(filename);
*c.commit_id = commit_id;
}
void SubsController::Undo() {
if (undo_stack.size() <= 1) return;
redo_stack.emplace_back(AssFile(), undo_stack.back().undo_description, commit_id);
context->ass->swap(redo_stack.back().file);
undo_stack.pop_back();
*context->ass = undo_stack.back().file;
commit_id = undo_stack.back().commit_id;
context->ass->Commit("", AssFile::COMMIT_NEW);
}
void SubsController::Redo() {
if (redo_stack.empty()) return;
context->ass->swap(redo_stack.back().file);
commit_id = redo_stack.back().commit_id;
undo_stack.emplace_back(*context->ass, redo_stack.back().undo_description, commit_id);
redo_stack.pop_back();
context->ass->Commit("", AssFile::COMMIT_NEW);
}
wxString SubsController::GetUndoDescription() const {
return IsUndoStackEmpty() ? "" : undo_stack.back().undo_description;
}
wxString SubsController::GetRedoDescription() const {
return IsRedoStackEmpty() ? "" : redo_stack.back().undo_description;
}
agi::fs::path SubsController::Filename() const {
if (!filename.empty()) return filename;
// Apple HIG says "untitled" should not be capitalised
#ifndef __WXMAC__
return _("Untitled").wx_str();
#else
return _("untitled").wx_str();
#endif
}

View file

@ -0,0 +1,110 @@
// Copyright (c) 2013, 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.
//
// Aegisub Project http://www.aegisub.org/
#include <libaegisub/fs_fwd.h>
#include <libaegisub/signal.h>
#include <boost/container/list.hpp>
#include <boost/filesystem/path.hpp>
#include <set>
class AssEntry;
class AssFile;
struct AssFileCommit;
namespace agi { struct Context; }
class SubsController {
agi::Context *context;
agi::signal::Connection undo_connection;
struct UndoInfo;
boost::container::list<UndoInfo> undo_stack;
boost::container::list<UndoInfo> redo_stack;
/// Revision counter for undo coalescing and modified state tracking
int commit_id;
/// Last saved version of this file
int saved_commit_id;
/// Last autosaved version of this file
int autosaved_commit_id;
/// A new file has been opened (filename)
agi::signal::Signal<agi::fs::path> FileOpen;
/// The file is about to be saved
/// This signal is intended for adding metadata such as video filename,
/// frame number, etc. Ideally this would all be done immediately rather
/// than waiting for a save, but that causes (more) issues with undo
agi::signal::Signal<> FileSave;
/// The filename of the currently open file, if any
agi::fs::path filename;
void OnCommit(AssFileCommit c);
/// Set the filename, updating things like the MRU and last used path
void SetFileName(agi::fs::path const& file);
public:
SubsController(agi::Context *context);
/// The file's path and filename if any, or platform-appropriate "untitled"
agi::fs::path Filename() const;
/// Does the file have unsaved changes?
bool IsModified() const { return commit_id != saved_commit_id; };
/// @brief Load from a file
/// @param file File name
/// @param charset Character set of file or empty to autodetect
void Load(agi::fs::path const& file, std::string const& charset="");
/// @brief Save to a file
/// @param file Path to save to
/// @param encoding Encoding to use, or empty to let the writer decide (which usually means "App/Save Charset")
void Save(agi::fs::path const& file, std::string const& encoding="");
/// Close the currently open file (i.e. open a new blank file)
void Close();
/// If there are unsaved changes, asl the user if they want to save them
/// @param allow_cancel Let the user cancel the closing
/// @return wxYES, wxNO or wxCANCEL (note: all three are true in a boolean context)
int TryToClose(bool allow_cancel = true) const;
/// @brief Autosave the file if there have been any chances since the last autosave
/// @return File name used or empty if no save was performed
agi::fs::path AutoSave();
/// Can the file be saved in its current format?
bool CanSave() const;
DEFINE_SIGNAL_ADDERS(FileOpen, AddFileOpenListener)
DEFINE_SIGNAL_ADDERS(FileSave, AddFileSaveListener)
/// @brief Undo the last set of changes to the file
void Undo();
/// @brief Redo the last undone changes
void Redo();
/// Check if undo stack is empty
bool IsUndoStackEmpty() const { return undo_stack.size() <= 1; };
/// Check if redo stack is empty
bool IsRedoStackEmpty() const { return redo_stack.empty(); };
/// Get the description of the first undoable change
wxString GetUndoDescription() const;
/// Get the description of the first redoable change
wxString GetRedoDescription() const;
};

View file

@ -183,7 +183,7 @@ class SubsEditBox : public wxPanel {
void SetSelectedRows(T AssDialogue::*field, wxString const& value, wxString const& desc, int type, bool amend = false);
/// @brief Reload the current line from the file
/// @param type AssFile::CommitType
/// @param type AssFile::COMMITType
void OnCommit(int type);
/// Regenerate a dropdown list with the unique values of a dialogue field

View file

@ -43,6 +43,7 @@
#include <libaegisub/of_type_adaptor.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/format.hpp>
EncoreSubtitleFormat::EncoreSubtitleFormat()

View file

@ -47,6 +47,7 @@
#include <libaegisub/of_type_adaptor.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/format.hpp>
TranStationSubtitleFormat::TranStationSubtitleFormat()

View file

@ -37,9 +37,8 @@
#ifdef WITH_CSRI
#include "subtitles_provider_csri.h"
#include "ass_file.h"
#include "subtitle_format.h"
#include "standard_paths.h"
#include "video_context.h"
#include "video_frame.h"
#include <libaegisub/fs.h>
@ -81,7 +80,7 @@ CSRISubtitlesProvider::~CSRISubtitlesProvider() {
void CSRISubtitlesProvider::LoadSubtitles(AssFile *subs) {
if (tempfile.empty())
tempfile = unique_path(StandardPaths::DecodePath("?temp/csri-%%%%-%%%%-%%%%-%%%%.ass"));
subs->Save(tempfile, false, false, "utf-8");
SubtitleFormat::GetWriter(tempfile)->WriteFile(subs, tempfile, "utf-8");
std::lock_guard<std::mutex> lock(csri_mutex);
instance = csri_open_file(renderer, tempfile.string().c_str(), nullptr);

View file

@ -52,6 +52,7 @@
#include <libaegisub/dispatch.h>
#include <libaegisub/log.h>
#include <boost/range/algorithm_ext/push_back.hpp>
#include <boost/gil/gil_all.hpp>
#include <memory>
#include <mutex>
@ -117,7 +118,17 @@ LibassSubtitlesProvider::~LibassSubtitlesProvider() {
void LibassSubtitlesProvider::LoadSubtitles(AssFile *subs) {
std::vector<char> data;
subs->SaveMemory(data);
data.clear();
data.reserve(0x4000);
AssEntryGroup group = ENTRY_GROUP_MAX;
for (auto const& line : subs->Line) {
if (group != line.Group()) {
group = line.Group();
boost::push_back(data, line.GroupHeader() + "\r\n");
}
boost::push_back(data, line.GetEntryData() + "\r\n");
}
if (ass_track) ass_free_track(ass_track);
ass_track = ass_read_memory(library, &data[0], data.size(),(char *)"UTF-8");

View file

@ -46,6 +46,7 @@
#include "mkv_wrap.h"
#include "options.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "time_range.h"
#include "threaded_frame_source.h"
#include "utils.h"
@ -116,7 +117,7 @@ void VideoContext::Reset() {
void VideoContext::SetContext(agi::Context *context) {
this->context = context;
context->ass->AddCommitListener(&VideoContext::OnSubtitlesCommit, this);
context->ass->AddFileSaveListener(&VideoContext::OnSubtitlesSave, this);
context->subsController->AddFileSaveListener(&VideoContext::OnSubtitlesSave, this);
}
void VideoContext::SetVideo(const agi::fs::path &filename) {

View file

@ -32,7 +32,6 @@
/// @ingroup video main_ui
///
// Includes
#include "config.h"
#include <algorithm>
@ -60,6 +59,7 @@
#include "include/aegisub/menu.h"
#include "options.h"
#include "spline_curve.h"
#include "subs_controller.h"
#include "threaded_frame_source.h"
#include "utils.h"
#include "video_out_gl.h"
@ -111,7 +111,7 @@ VideoDisplay::VideoDisplay(
slots.push_back(con->videoController->AddVideoOpenListener(&VideoDisplay::UpdateSize, this));
slots.push_back(con->videoController->AddARChangeListener(&VideoDisplay::UpdateSize, this));
slots.push_back(con->ass->AddFileSaveListener(&VideoDisplay::OnSubtitlesSave, this));
slots.push_back(con->subsController->AddFileSaveListener(&VideoDisplay::OnSubtitlesSave, this));
Bind(wxEVT_PAINT, std::bind(&VideoDisplay::Render, this));
Bind(wxEVT_SIZE, &VideoDisplay::OnSizeEvent, this);