diff --git a/aegisub/src/ass_dialogue.cpp b/aegisub/src/ass_dialogue.cpp index 19f9568e2..95f3b8e73 100644 --- a/aegisub/src/ass_dialogue.cpp +++ b/aegisub/src/ass_dialogue.cpp @@ -53,34 +53,24 @@ using namespace boost::adaptors; static int next_id = 0; -AssDialogue::AssDialogue() -: Id(++next_id) -{ - memset(Margin, 0, sizeof Margin); +AssDialogue::AssDialogue() { + Id = ++next_id; } -AssDialogue::AssDialogue(AssDialogue const& that) -: Id(++next_id) -, Comment(that.Comment) -, Layer(that.Layer) -, Start(that.Start) -, End(that.End) -, Style(that.Style) -, Actor(that.Actor) -, Effect(that.Effect) -, Text(that.Text) -{ - memmove(Margin, that.Margin, sizeof Margin); +AssDialogue::AssDialogue(AssDialogue const& that) : AssDialogueBase(that) { + Id = ++next_id; } -AssDialogue::AssDialogue(std::string const& data) -: Id(++next_id) -{ +AssDialogue::AssDialogue(AssDialogueBase const& that) : AssDialogueBase(that) { + Id = ++next_id; +} + +AssDialogue::AssDialogue(std::string const& data) { + Id = ++next_id; Parse(data); } -AssDialogue::~AssDialogue () { -} +AssDialogue::~AssDialogue () { } class tokenizer { agi::StringRange str; @@ -177,14 +167,6 @@ std::string AssDialogue::GetData(bool ssa) const { return str; } -const std::string AssDialogue::GetEntryData() const { - return GetData(false); -} - -std::string AssDialogue::GetSSAText() const { - return GetData(true); -} - std::auto_ptr> AssDialogue::ParseTags() const { boost::ptr_vector Blocks; @@ -278,6 +260,6 @@ std::string AssDialogue::GetStrippedText() const { AssEntry *AssDialogue::Clone() const { auto clone = new AssDialogue(*this); - *const_cast(&clone->Id) = Id; + clone->Id = Id; return clone; } diff --git a/aegisub/src/ass_dialogue.h b/aegisub/src/ass_dialogue.h index 85b5a6f71..56af7cacc 100644 --- a/aegisub/src/ass_dialogue.h +++ b/aegisub/src/ass_dialogue.h @@ -38,6 +38,7 @@ #include +#include #include #include #include @@ -123,24 +124,18 @@ public: void ProcessParameters(ProcessParametersCallback callback, void *userData); }; -class AssDialogue : public AssEntry { - std::string GetData(bool ssa) const; - - /// @brief Parse raw ASS data into everything else - /// @param data ASS line - void Parse(std::string const& data); -public: +struct AssDialogueBase { /// Unique ID of this line. Copies of the line for Undo/Redo purposes /// preserve the unique ID, so that the equivalent lines can be found in /// the different versions of the file. - const int Id; + int Id; /// Is this a comment line? bool Comment = false; /// Layer number int Layer = 0; /// Margins: 0 = Left, 1 = Right, 2 = Top (Vertical) - int Margin[3]; + std::array Margin = {{0, 0, 0}}; /// Starting time AssTime Start = 0; /// Ending time @@ -153,7 +148,15 @@ public: boost::flyweight Effect; /// Raw text data boost::flyweight Text; +}; +class AssDialogue : public AssEntry, public AssDialogueBase { + std::string GetData(bool ssa) const; + + /// @brief Parse raw ASS data into everything else + /// @param data ASS line + void Parse(std::string const& data); +public: AssEntryGroup Group() const override { return AssEntryGroup::DIALOGUE; } /// Parse text as ASS and return block information @@ -167,10 +170,10 @@ public: /// Update the text of the line from parsed blocks void UpdateText(boost::ptr_vector& blocks); - const std::string GetEntryData() const override; + const std::string GetEntryData() const override { return GetData(false); } /// Get the line as SSA rather than ASS - std::string GetSSAText() const override; + std::string GetSSAText() const override { return GetData(true); } /// Does this line collide with the passed line? bool CollidesWith(const AssDialogue *target) const; @@ -178,6 +181,7 @@ public: AssDialogue(); AssDialogue(AssDialogue const&); + AssDialogue(AssDialogueBase const&); AssDialogue(std::string const& data); ~AssDialogue(); }; diff --git a/aegisub/src/ass_file.cpp b/aegisub/src/ass_file.cpp index b57326de2..025b86441 100644 --- a/aegisub/src/ass_file.cpp +++ b/aegisub/src/ass_file.cpp @@ -206,8 +206,7 @@ AssStyle *AssFile::GetStyle(std::string const& name) { } int AssFile::Commit(wxString const& desc, int type, int amend_id, AssEntry *single_line) { - AssFileCommit c = { desc, &amend_id, single_line }; - PushState(c); + PushState({desc, &amend_id, single_line}); std::set changed_lines; if (single_line) diff --git a/aegisub/src/ass_info.h b/aegisub/src/ass_info.h index 2e69d9a96..acededc66 100644 --- a/aegisub/src/ass_info.h +++ b/aegisub/src/ass_info.h @@ -23,7 +23,7 @@ class AssInfo : public AssEntry { std::string value; public: - AssInfo(AssInfo const& o) : key(o.key), value(o.value) { } + AssInfo(AssInfo const& o) = default; AssInfo(std::string key, std::string value) : key(std::move(key)), value(std::move(value)) { } AssEntry *Clone() const override { return new AssInfo(*this); } diff --git a/aegisub/src/search_replace_engine.cpp b/aegisub/src/search_replace_engine.cpp index 568bb161d..53a50e7cb 100644 --- a/aegisub/src/search_replace_engine.cpp +++ b/aegisub/src/search_replace_engine.cpp @@ -34,17 +34,17 @@ static const size_t bad_pos = -1; namespace { -auto get_dialogue_field(SearchReplaceSettings::Field field) -> decltype(&AssDialogue::Text) { +auto get_dialogue_field(SearchReplaceSettings::Field field) -> decltype(&AssDialogueBase::Text) { switch (field) { - case SearchReplaceSettings::Field::TEXT: return &AssDialogue::Text; - case SearchReplaceSettings::Field::STYLE: return &AssDialogue::Style; - case SearchReplaceSettings::Field::ACTOR: return &AssDialogue::Actor; - case SearchReplaceSettings::Field::EFFECT: return &AssDialogue::Effect; + case SearchReplaceSettings::Field::TEXT: return &AssDialogueBase::Text; + case SearchReplaceSettings::Field::STYLE: return &AssDialogueBase::Style; + case SearchReplaceSettings::Field::ACTOR: return &AssDialogueBase::Actor; + case SearchReplaceSettings::Field::EFFECT: return &AssDialogueBase::Effect; } throw agi::InternalError("Bad field for search", nullptr); } -std::string const& get_normalized(const AssDialogue *diag, decltype(&AssDialogue::Text) field) { +std::string const& get_normalized(const AssDialogue *diag, decltype(&AssDialogueBase::Text) field) { auto& value = const_cast(diag)->*field; auto normalized = boost::locale::normalize(value.get()); if (normalized != value) @@ -55,7 +55,7 @@ std::string const& get_normalized(const AssDialogue *diag, decltype(&AssDialogue typedef std::function matcher; class noop_accessor { - boost::flyweight AssDialogue::*field; + boost::flyweight AssDialogueBase::*field; size_t start; public: @@ -72,7 +72,7 @@ public: }; class skip_tags_accessor { - boost::flyweight AssDialogue::*field; + boost::flyweight AssDialogueBase::*field; std::vector> blocks; size_t start; diff --git a/aegisub/src/subs_controller.cpp b/aegisub/src/subs_controller.cpp index 4720fd5d2..c38baf13a 100644 --- a/aegisub/src/subs_controller.cpp +++ b/aegisub/src/subs_controller.cpp @@ -18,8 +18,10 @@ #include "subs_controller.h" +#include "ass_attachment.h" #include "ass_dialogue.h" #include "ass_file.h" +#include "ass_info.h" #include "ass_style.h" #include "charset_detect.h" #include "compat.h" @@ -50,10 +52,73 @@ namespace { } struct SubsController::UndoInfo { - AssFile file; + std::vector> script_info; + std::vector styles; + std::vector events; + std::vector graphics; + std::vector fonts; + wxString undo_description; int commit_id; - UndoInfo(AssFile const& f, wxString const& d, int c) : file(f), undo_description(d), commit_id(c) { } + UndoInfo(AssFile const& f, wxString const& d, int c) + : undo_description(d), commit_id(c) + { + size_t info_count = 0, style_count = 0, event_count = 0, font_count = 0, graphics_count = 0; + for (auto const& line : f.Line) { + switch (line.Group()) { + case AssEntryGroup::DIALOGUE: ++event_count; break; + case AssEntryGroup::INFO: ++info_count; break; + case AssEntryGroup::STYLE: ++style_count; break; + case AssEntryGroup::FONT: ++font_count; break; + case AssEntryGroup::GRAPHIC: ++graphics_count; break; + default: assert(false); break; + } + } + + script_info.reserve(info_count); + styles.reserve(style_count); + events.reserve(event_count); + + for (auto const& line : f.Line) { + switch (line.Group()) { + case AssEntryGroup::DIALOGUE: + events.push_back(static_cast(line)); + break; + case AssEntryGroup::INFO: { + auto info = static_cast(&line); + script_info.emplace_back(info->Key(), info->Value()); + break; + } + case AssEntryGroup::STYLE: + styles.push_back(static_cast(line)); + break; + case AssEntryGroup::FONT: + fonts.push_back(static_cast(line)); + break; + case AssEntryGroup::GRAPHIC: + graphics.push_back(static_cast(line)); + break; + default: + assert(false); + break; + } + } + } + + operator AssFile() const { + AssFile ret; + for (auto const& info : script_info) + ret.Line.push_back(*new AssInfo(info.first, info.second)); + for (auto const& style : styles) + ret.Line.push_back(*new AssStyle(style)); + for (auto const& event : events) + ret.Line.push_back(*new AssDialogue(event)); + for (auto const& attachment : graphics) + ret.Line.push_back(*new AssAttachment(attachment)); + for (auto const& attachment : fonts) + ret.Line.push_back(*new AssAttachment(attachment)); + return ret; + } }; SubsController::SubsController(agi::Context *context) @@ -275,20 +340,19 @@ void SubsController::OnCommit(AssFileCommit c) { // 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; + if (c.single_line && c.single_line->Group() == AssEntryGroup::DIALOGUE) { + auto src_diag = static_cast(c.single_line); + for (auto& diag : undo_stack.back().events) { + if (diag.Id == src_diag->Id) { + diag = *src_diag; + break; + } } - undo_stack.back().file.Line.insert(undo_it, *c.single_line->Clone()); - delete &*undo_it; + *c.commit_id = commit_id; + return; } - else - undo_stack.back().file = *context->ass; - *c.commit_id = commit_id; - return; + undo_stack.pop_back(); } redo_stack.clear(); @@ -305,28 +369,27 @@ void SubsController::OnCommit(AssFileCommit c) { *c.commit_id = commit_id; } -void SubsController::Undo() { - if (undo_stack.size() <= 1) return; +void SubsController::ApplyUndo() { + // Keep old lines alive until after the commit is complete + AssFile old; + old.swap(*context->ass); - redo_stack.splice(redo_stack.end(), undo_stack, std::prev(undo_stack.end())); - *context->ass = undo_stack.back().file; + *context->ass = undo_stack.back(); commit_id = undo_stack.back().commit_id; context->ass->Commit("", AssFile::COMMIT_NEW); } +void SubsController::Undo() { + if (undo_stack.size() <= 1) return; + redo_stack.splice(redo_stack.end(), undo_stack, std::prev(undo_stack.end())); + ApplyUndo(); +} + 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); - - context->ass->Commit("", AssFile::COMMIT_NEW); - - // Done after commit so that the old active line and selection stay alive - // while the commit is being processed - redo_stack.pop_back(); + undo_stack.splice(undo_stack.end(), redo_stack, std::prev(redo_stack.end())); + ApplyUndo(); } wxString SubsController::GetUndoDescription() const { diff --git a/aegisub/src/subs_controller.h b/aegisub/src/subs_controller.h index 6ca52a351..c2a179fa0 100644 --- a/aegisub/src/subs_controller.h +++ b/aegisub/src/subs_controller.h @@ -62,6 +62,9 @@ class SubsController { /// Set the filename, updating things like the MRU and last used path void SetFileName(agi::fs::path const& file); + /// Set the current file to the file on top of the undo stack + void ApplyUndo(); + public: SubsController(agi::Context *context); diff --git a/aegisub/src/visual_tool.cpp b/aegisub/src/visual_tool.cpp index 450d0de57..ad767ad9d 100644 --- a/aegisub/src/visual_tool.cpp +++ b/aegisub/src/visual_tool.cpp @@ -361,8 +361,7 @@ Vector2D VisualToolBase::GetLinePosition(AssDialogue *diag) { if (Vector2D ret = vec_or_bad(find_tag(blocks, "\\move"), 0, 1)) return ret; // Get default position - int margin[3]; - memcpy(margin, diag->Margin, sizeof margin); + auto margin = diag->Margin; int align = 2; if (AssStyle *style = c->ass->GetStyle(diag->Style)) {