From abe2a81c992fc345f169b262ba6c5550ceeb1beb Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Sun, 11 Dec 2022 18:53:23 +0100 Subject: [PATCH 1/2] folding: Fix crash when clicking fold column header --- src/base_grid.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/base_grid.cpp b/src/base_grid.cpp index 0dc7bc538..51f9a9546 100644 --- a/src/base_grid.cpp +++ b/src/base_grid.cpp @@ -499,7 +499,7 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) { CaptureMouse(); } - if (columns[col]->OnMouseEvent(dlg, context, event)) { + if (dlg && columns[col]->OnMouseEvent(dlg, context, event)) { return; } From 1bd426f69ae135d1fa9bf150d0448edae34a27d3 Mon Sep 17 00:00:00 2001 From: arch1t3cht Date: Mon, 12 Dec 2022 02:19:10 +0100 Subject: [PATCH 2/2] folding: Switch to extradata for storage This makes the internal juggling of fold data even more complicated, but it has a number of advantages: - Folds will preserved even when opening the file with Aegisub builds that don't know about folding. - Folds will be preserved by automation scripts that re-insert every line. - Folds do not break when adding or deleting lines in a text editor or some other editor, as long as the lines involving folds aren't touched. - In particular, merging changes using tools like git will not break folds. - Copy/pasting folds also keeps the folds to some extent. This is impossible to solve in full generality, but simple cases like cut/pasting a folded section somewhere else work, as long as the file isn't saved in between. - This will give automation scripts full access to folding without needing to set up any additional API functions. --- src/ass_file.cpp | 33 +++++ src/ass_file.h | 9 +- src/ass_parser.cpp | 28 +---- src/auto4_lua_assfile.cpp | 24 ---- src/fold_controller.cpp | 185 +++++++++++++++++------------ src/fold_controller.h | 65 ++++++---- src/libresrc/default_menu.json | 2 +- src/libresrc/osx/default_menu.json | 2 +- src/subtitle_format_ass.cpp | 13 -- 9 files changed, 197 insertions(+), 164 deletions(-) diff --git a/src/ass_file.cpp b/src/ass_file.cpp index 743bbfea2..6d36d37a5 100644 --- a/src/ass_file.cpp +++ b/src/ass_file.cpp @@ -240,6 +240,39 @@ uint32_t AssFile::AddExtradata(std::string const& key, std::string const& value) return next_extradata_id++; // return old value, then post-increment } +void AssFile::SetExtradataValue(AssDialogue& line, std::string const& key, std::string const& value, bool del) { + std::vector id_list = line.ExtradataIds; + std::vector to_erase(id_list.size()); + bool dirty = false; + bool found = false; + + std::vector entry_list = GetExtradata(id_list); + for (int i = entry_list.size() - 1; i >= 0; i--) { + if (entry_list[i].key == key) { + if (!del && entry_list[i].value == value) { + found = true; + } else { + to_erase[i] = true; + dirty = true; + } + } + } + + // The key is already set, we don't need to change anything + if (found && !dirty) + return; + + for (int i = id_list.size() - 1; i >= 0; i--) { + if (to_erase[i]) + id_list.erase(id_list.begin() + i, id_list.begin() + i + 1); + } + + if (!del && !found) + id_list.push_back(AddExtradata(key, value)); + + line.ExtradataIds = id_list; +} + namespace { struct extradata_id_cmp { bool operator()(ExtradataEntry const& e, uint32_t id) { diff --git a/src/ass_file.h b/src/ass_file.h index bf5f39398..719b28bb8 100644 --- a/src/ass_file.h +++ b/src/ass_file.h @@ -85,7 +85,6 @@ struct ProjectProperties { int active_row = 0; int ar_mode = 0; int video_position = 0; - std::vector folds; }; class AssFile { @@ -93,6 +92,8 @@ class AssFile { agi::signal::Signal AnnounceCommit; agi::signal::Signal AnnouncePreCommit; agi::signal::Signal PushState; + + void SetExtradataValue(AssDialogue& line, std::string const& key, std::string const& value, bool del); public: /// The lines in the file std::vector Info; @@ -144,6 +145,10 @@ public: uint32_t AddExtradata(std::string const& key, std::string const& value); /// Fetch all extradata entries from a list of IDs std::vector GetExtradata(std::vector const& id_list) const; + /// Set an extradata kex:value pair for a dialogue line, clearing previous values for this key if necessary + void SetExtradataValue(AssDialogue& line, std::string const& key, std::string const& value) { SetExtradataValue(line, key, value, false); }; + /// Delete any extradata values for the given key + void DeleteExtradataValue(AssDialogue& line, std::string const& key) { SetExtradataValue(line, key, "", true); }; /// Remove unreferenced extradata entries void CleanExtradata(); @@ -178,7 +183,7 @@ public: /// Extradata entries were added/modified/removed COMMIT_EXTRADATA = 0x100, /// Folds were added or removed - COMMIT_FOLD = 0x200, + COMMIT_FOLD = COMMIT_EXTRADATA, }; DEFINE_SIGNAL_ADDERS(AnnouncePreCommit, AddPreCommitListener) diff --git a/src/ass_parser.cpp b/src/ass_parser.cpp index 33e878b71..f55fc3567 100644 --- a/src/ass_parser.cpp +++ b/src/ass_parser.cpp @@ -24,7 +24,6 @@ #include #include -#include #include #include @@ -40,8 +39,7 @@ class AssParser::HeaderToProperty { using field = boost::variant< std::string ProjectProperties::*, int ProjectProperties::*, - double ProjectProperties::*, - std::vector ProjectProperties::* + double ProjectProperties::* >; std::unordered_map fields; @@ -60,7 +58,6 @@ public: {"Video Zoom Percent", &ProjectProperties::video_zoom}, {"Scroll Position", &ProjectProperties::scroll_position}, {"Active Line", &ProjectProperties::active_row}, - {"Line Folds", &ProjectProperties::folds}, {"Video Position", &ProjectProperties::video_position}, {"Video AR Mode", &ProjectProperties::ar_mode}, {"Video AR Value", &ProjectProperties::ar_value}, @@ -83,29 +80,6 @@ public: void operator()(std::string ProjectProperties::*f) const { obj.*f = value; } void operator()(int ProjectProperties::*f) const { try_parse(value, &(obj.*f)); } void operator()(double ProjectProperties::*f) const { try_parse(value, &(obj.*f)); } - void operator()(std::vector ProjectProperties::*f) const { - std::vector folds; - - for (auto foldstr : agi::Split(value, ',')) { - LineFold fold; - std::vector parsed; - agi::Split(parsed, foldstr, ':'); - if (parsed.size() != 3) { - continue; - } - - int collapsed; - try_parse(parsed[0], &fold.start); - try_parse(parsed[1], &fold.end); - try_parse(parsed[2], &collapsed); - fold.collapsed = !!collapsed; - - if (fold.start >= 0 && fold.end > fold.start) { - folds.push_back(fold); - } - } - obj.*f = folds; - } } visitor {target->Properties, value}; boost::apply_visitor(visitor, it->second); return true; diff --git a/src/auto4_lua_assfile.cpp b/src/auto4_lua_assfile.cpp index c3e3902e6..3aef3b769 100644 --- a/src/auto4_lua_assfile.cpp +++ b/src/auto4_lua_assfile.cpp @@ -40,7 +40,6 @@ #include "ass_karaoke.h" #include "ass_style.h" #include "compat.h" -#include "fold_controller.h" #include #include @@ -101,22 +100,6 @@ namespace { return ret; } - template - bool get_userdata_field(lua_State *L, const char *name, const char *line_class, T *target, bool required) - { - lua_getfield(L, -1, name); - if (!lua_isuserdata(L, -1)) { - if (!required) { - lua_pop(L, 1); - return false; - } - throw bad_field("userdata", name, line_class); - } - *target = *static_cast(lua_touserdata(L, -1)); - lua_pop(L, 1); - return true; - } - using namespace Automation4; template int closure_wrapper(lua_State *L) @@ -198,10 +181,6 @@ namespace Automation4 { set_field(L, "text", dia->Text); - // preserve the folds - *static_cast(lua_newuserdata(L, sizeof(FoldInfo))) = dia->Fold; - lua_setfield(L, -2, "_foldinfo"); - // create extradata table lua_newtable(L); for (auto const& ed : ass->GetExtradata(dia->ExtradataIds)) { @@ -322,9 +301,6 @@ namespace Automation4 { dia->Margin[2] = get_int_field(L, "margin_t", "dialogue"); dia->Effect = get_string_field(L, "effect", "dialogue"); dia->Text = get_string_field(L, "text", "dialogue"); - if (!get_userdata_field(L, "_foldinfo", "dialogue", &dia->Fold, false)) { - dia->Fold = FoldInfo(); - } std::vector new_ids; diff --git a/src/fold_controller.cpp b/src/fold_controller.cpp index ac1cd2084..a0b578759 100644 --- a/src/fold_controller.cpp +++ b/src/fold_controller.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2022, arch1t3cht > +// Copyright (c) 2022, arch1t3cht // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -24,9 +24,10 @@ #include #include -#include +#include +#include -static int next_fold_id = 0; +const char *folds_key = "_aegi_folddata"; FoldController::FoldController(agi::Context *c) : context(c) @@ -35,12 +36,12 @@ FoldController::FoldController(agi::Context *c) bool FoldController::CanAddFold(AssDialogue& start, AssDialogue& end) { - if (start.Fold.exists || end.Fold.exists) { + if (start.Fold.valid || end.Fold.valid) { return false; } int folddepth = 0; for (auto it = std::next(context->ass->Events.begin(), start.Row); it->Row < end.Row; it++) { - if (it->Fold.exists) { + if (it->Fold.valid) { folddepth += it->Fold.side ? -1 : 1; } if (folddepth < 0) { @@ -51,17 +52,24 @@ bool FoldController::CanAddFold(AssDialogue& start, AssDialogue& end) { } void FoldController::RawAddFold(AssDialogue& start, AssDialogue& end, bool collapsed) { - int id = next_fold_id++; + int id = ++max_fold_id; + context->ass->SetExtradataValue(start, folds_key, agi::format("0;%d;%d", int(collapsed), id)); + context->ass->SetExtradataValue(end, folds_key, agi::format("1;%d;%d", int(collapsed), id)); +} - start.Fold.exists = true; - start.Fold.collapsed = collapsed; - start.Fold.id = id; - start.Fold.side = false; +void FoldController::UpdateLineExtradata(AssDialogue &line) { + if (line.Fold.extraExists) + context->ass->SetExtradataValue(line, folds_key, agi::format("%d;%d;%d", int(line.Fold.side), int(line.Fold.collapsed), int(line.Fold.id))); + else + context->ass->DeleteExtradataValue(line, folds_key); +} - end.Fold.exists = true; - end.Fold.collapsed = collapsed; - end.Fold.id = id; - end.Fold.side = true; +void FoldController::InvalidateLineFold(AssDialogue &line) { + line.Fold.valid = false; + if (++line.Fold.invalidCount > 100) { + line.Fold.extraExists = false; + UpdateLineExtradata(line); + } } void FoldController::AddFold(AssDialogue& start, AssDialogue& end, bool collapsed) { @@ -73,10 +81,11 @@ void FoldController::AddFold(AssDialogue& start, AssDialogue& end, bool collapse bool FoldController::DoForAllFolds(bool action(AssDialogue& line)) { for (AssDialogue& line : context->ass->Events) { - if (line.Fold.exists) { - if (action(line)) { + if (line.Fold.valid) { + bool result = action(line); + UpdateLineExtradata(line); + if (result) return true; - } } } return false; @@ -84,34 +93,10 @@ bool FoldController::DoForAllFolds(bool action(AssDialogue& line)) { void FoldController::FixFoldsPreCommit(int type, const AssDialogue *single_line) { if ((type & (AssFile::COMMIT_FOLD | AssFile::COMMIT_DIAG_ADDREM | AssFile::COMMIT_ORDER)) || type == AssFile::COMMIT_NEW) { - if (type == AssFile::COMMIT_NEW && context->subsController->IsUndoStackEmpty()) { - // This might be the biggest hack in all of this. We want to hook into the FileOpen signal to - // read and apply the folds from the project data, but if we do it naively, this will only happen - // after the first commit has been pushed to the undo stack. Thus, if a user uses Ctrl+Z after opening - // a file, all folds will be cleared. - // Instead, we hook into the first commit which is made after loading a file, right after the undo stack was cleared. - DoForAllFolds(FoldController::ActionClearFold); - MakeFoldsFromFile(); - } - FixFolds(); + UpdateFoldInfo(); } } -void FoldController::MakeFoldsFromFile() { - if (context->ass->Properties.folds.empty()) { - return; - } - - int numlines = context->ass->Events.size(); - for (LineFold fold : context->ass->Properties.folds) { - if (fold.start >= 0 && fold.start < fold.end && fold.end <= numlines) { - auto opener = std::next(context->ass->Events.begin(), fold.start); - RawAddFold(*opener, *std::next(opener, fold.end - fold.start), fold.collapsed); - } - } -} - - // For each line in lines, applies action() to the opening delimiter of the innermost fold containing this line. // Returns true as soon as any action() call returned true. // @@ -119,17 +104,56 @@ void FoldController::MakeFoldsFromFile() { // be followed by a commit. bool FoldController::DoForFoldsAt(std::vector const& lines, bool action(AssDialogue& line)) { for (AssDialogue *line : lines) { - if (line->Fold.parent != nullptr && !(line->Fold.exists && !line->Fold.side)) { + if (line->Fold.parent != nullptr && !(line->Fold.valid && !line->Fold.side)) { line = line->Fold.parent; } - if (!line->Fold.visited && action(*line)) { - return true; + if (!line->Fold.visited) { + bool result = action(*line); + UpdateLineExtradata(*line); + if (result) + return true; } line->Fold.visited = true; } return false; } +void FoldController::UpdateFoldInfo() { + ReadFromExtradata(); + FixFolds(); + LinkFolds(); +} + +void FoldController::ReadFromExtradata() { + max_fold_id = 0; + + for (auto line = context->ass->Events.begin(); line != context->ass->Events.end(); line++) { + line->Fold.extraExists = false; + + for (auto const& extra : context->ass->GetExtradata(line->ExtradataIds)) { + if (extra.key == folds_key) { + std::vector fields; + agi::Split(fields, extra.value, ';'); + if (fields.size() != 3) + break; + + int side; + int collapsed; + if (!agi::util::try_parse(fields[0], &side)) break; + if (!agi::util::try_parse(fields[1], &collapsed)) break; + if (!agi::util::try_parse(fields[2], &line->Fold.id)) break; + line->Fold.side = side; + line->Fold.collapsed = collapsed; + + line->Fold.extraExists = true; + max_fold_id = std::max(max_fold_id, line->Fold.id); + break; + } + } + line->Fold.valid = line->Fold.extraExists; + } +} + void FoldController::FixFolds() { // Stack of which folds we've desended into so far std::vector foldStack; @@ -142,15 +166,28 @@ void FoldController::FixFolds() { // fold data with this ID is skipped and deleted. std::unordered_map completedFolds; + // Map iteratively applied to all id's. + // Once some fold has been completely found, subsequent markers found with the same id will be mapped to this new id. + std::unordered_map idRemap; + for (auto line = context->ass->Events.begin(); line != context->ass->Events.end(); line++) { - if (line->Fold.exists) { - if (completedFolds.count(line->Fold.id)) { // Duplicate entry - line->Fold.exists = false; - continue; + if (line->Fold.extraExists) { + bool needs_update = false; + + while (idRemap.count(line->Fold.id)) { + line->Fold.id = idRemap[line->Fold.id]; + needs_update = true; } + + if (completedFolds.count(line->Fold.id)) { // Duplicate entry - try to start a new one + idRemap[line->Fold.id] = ++max_fold_id; + line->Fold.id = idRemap[line->Fold.id]; + needs_update = true; + } + if (!line->Fold.side) { if (foldHeads.count(line->Fold.id)) { // Duplicate entry - line->Fold.exists = false; + InvalidateLineFold(*line); } else { foldHeads[line->Fold.id] = &*line; foldStack.push_back(&*line); @@ -160,7 +197,7 @@ void FoldController::FixFolds() { // Deactivate it. Because we can, also push it to completedFolds: // If its counterpart appears further below, we can delete it right away. completedFolds[line->Fold.id] = true; - line->Fold.exists = false; + InvalidateLineFold(*line); } else { // We found a fold. Now we need to see if the stack matches. // We scan our stack for the counterpart of the fold. @@ -176,12 +213,15 @@ void FoldController::FixFolds() { // Erase all folds further inward for (int j = foldStack.size() - 1; j > i; j--) { completedFolds[foldStack[j]->Fold.id] = true; - foldStack[j]->Fold.exists = false; + InvalidateLineFold(*foldStack[j]); foldStack.pop_back(); } // Sync the found fold and pop the stack - line->Fold.collapsed = foldStack[i]->Fold.collapsed; + if (line->Fold.collapsed != foldStack[i]->Fold.collapsed) { + line->Fold.collapsed = foldStack[i]->Fold.collapsed; + needs_update = true; + } foldStack.pop_back(); found = true; @@ -190,25 +230,30 @@ void FoldController::FixFolds() { } if (!found) { completedFolds[line->Fold.id] = true; - line->Fold.exists = false; + InvalidateLineFold(*line); } } } + + if (needs_update) { + UpdateLineExtradata(*line); + } } } // All remaining lines are invalid for (AssDialogue *line : foldStack) { - line->Fold.exists = false; + line->Fold.valid = false; + if (++line->Fold.invalidCount > 100) { + line->Fold.extraExists = false; + UpdateLineExtradata(*line); + } } - - LinkFolds(); } void FoldController::LinkFolds() { std::vector foldStack; AssDialogue *lastVisible = nullptr; - context->ass->Properties.folds.clear(); maxdepth = 0; @@ -217,10 +262,10 @@ void FoldController::LinkFolds() { for (auto line = context->ass->Events.begin(); line != context->ass->Events.end(); line++) { line->Fold.parent = foldStack.empty() ? nullptr : foldStack.back(); line->Fold.nextVisible = nullptr; - line->Fold.visible = highestFolded > foldStack.size(); + line->Fold.visible = highestFolded > (int) foldStack.size(); line->Fold.visited = false; line->Fold.visibleRow = visibleRow; - + if (line->Fold.visible) { if (lastVisible != nullptr) { lastVisible->Fold.nextVisible = &*line; @@ -228,26 +273,20 @@ void FoldController::LinkFolds() { lastVisible = &*line; visibleRow++; } - if (line->Fold.exists && !line->Fold.side) { + if (line->Fold.valid && !line->Fold.side) { foldStack.push_back(&*line); - if (!line->Fold.collapsed && highestFolded == foldStack.size()) { + if (!line->Fold.collapsed && highestFolded == (int) foldStack.size()) { highestFolded++; } - if (foldStack.size() > maxdepth) { + if ((int) foldStack.size() > maxdepth) { maxdepth = foldStack.size(); } } - if (line->Fold.exists && line->Fold.side) { - context->ass->Properties.folds.push_back(LineFold { - foldStack.back()->Row, - line->Row, - line->Fold.collapsed, - }); - + if (line->Fold.valid && line->Fold.side) { line->Fold.counterpart = foldStack.back(); (*foldStack.rbegin())->Fold.counterpart = &*line; - if (highestFolded >= foldStack.size()) { + if (highestFolded >= (int) foldStack.size()) { highestFolded = foldStack.size(); } @@ -260,9 +299,9 @@ int FoldController::GetMaxDepth() { return maxdepth; } -bool FoldController::ActionHasFold(AssDialogue& line) { return line.Fold.exists; } +bool FoldController::ActionHasFold(AssDialogue& line) { return line.Fold.valid; } -bool FoldController::ActionClearFold(AssDialogue& line) { line.Fold.exists = false; return false; } +bool FoldController::ActionClearFold(AssDialogue& line) { line.Fold.extraExists = false; line.Fold.valid = false; return false; } bool FoldController::ActionOpenFold(AssDialogue& line) { line.Fold.collapsed = false; return false; } diff --git a/src/fold_controller.h b/src/fold_controller.h index c1adfb8f3..c53db5d82 100644 --- a/src/fold_controller.h +++ b/src/fold_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022, arch1t3cht > +// Copyright (c) 2022, arch1t3cht // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above @@ -23,6 +23,8 @@ namespace agi { struct Context; } +extern const char *folds_key; + /// We allow hiding ass lines using cascading folds, each of which collapses a contiguous collection of dialogue lines into a single one. /// A fold is described by inclusive start and end points of the contiguous set of dialogue line it extends over. /// An existing fold can be active (collapsed) or inactive (existing, but not collapsed at the moment) @@ -30,55 +32,58 @@ namespace agi { struct Context; } /// an intersection set not equal to one of the two folds. /// Only one fold may be started or ended at any given line. -/// Since we need to track how the folds move when lines are inserted or deleted, we need to represent the fold -/// data as part of the individual AssDialogue lines. Hooking into insertion or deletion calls is not possible -/// without extensive restructuring, and also wouldn't interact well with undo/redo functionality. +/// In order for folds to be preserved while adding or deleting lines and work nicely with operations like copy/paste, +/// they need to be stored as extradata. Furthermore, in order for the subtitle grid and fold management commands to efficiently +/// navigate the folds, we cache some information on the fold after each commit. /// -/// Because of this, we store the data defining folds as part of the AssDialogue lines. We use a pre-commit hook -/// to fix any format violations after changes are made. Furthermore, to be able to traverse the folds more easily, -/// we compute various metadata and set up pointers between the fold parts. +/// A fold descriptor for a line is an extradata field of the form ;;, where +/// direction is 0 if this line starts a fold, and 1 if the line ends one +/// collapsed is 1 if the fold is collapsed and 0 otherwise +/// id is a unique id pairing this fold with its counterpart /// Part of the data for an AssDialogue object, describing folds starting or ending at this line. class FoldInfo { - // Base data describing the folds:w + // Cached, parsed versions of the contents of the extradata entry - /// Whether a fold starts or ends at the line. All other fields are only valid if this is true. - bool exists = false; + /// Whether there is some extradata entry on folds here + bool extraExists = false; + /// Whether a fold starts or ends at the line. The following three fields are only valid if this is true. + bool valid = false; + /// The id + int id = 0; /// Whether the fold is currently collapsed bool collapsed = false; /// False if a fold is started here, true otherwise. bool side = false; - /// A unique ID describing the fold. The other end of the fold has a matching ID and the opposite value for side. - int id = 0; + // Used in DoForFoldsAt to ensure each line is visited only once bool visited = false; - - // The following is cached data used for making traversing folds more efficient. These are only valid directly after - // a commit and shouldn't be changed outside of the pre-commit handler. - /// Whether the line is currently visible bool visible = true; /// If exists is true, this is a pointer to the other line with the given fold id AssDialogue *counterpart = nullptr; - /// A pointer to the opener of the innermost fold containing the line, if one exists. /// If the line starts a fold, this points to the next bigger fold. AssDialogue *parent = nullptr; - /// If this line is visible, this points to the next visible line, if one exists AssDialogue *nextVisible = nullptr; + /// Increased when there's an extradata entry in here that turned out to be invalid. + /// Once this hits some threshold, the extradata entry is deleted. + /// We don't delete it immediately to allow cut/pasting fold delimiters around. + int invalidCount = 0; + /// The row number where this line would appear in the subtitle grid. That is, the ordinary /// Row value, but with hidden lines skipped. /// Out of all AssDialogue lines with the same visibleRow, only the one with the lowest Row is shown. - int visibleRow; + int visibleRow = -1; friend class FoldController; public: - bool hasFold() const { return exists; } + bool hasFold() const { return valid; } bool isFolded() const { return collapsed; } bool isEnd() const { return side; } @@ -95,6 +100,7 @@ class FoldController { agi::Context *context; agi::signal::Connection pre_commit_listener; int maxdepth = 0; + int max_fold_id = 0; bool CanAddFold(AssDialogue& start, AssDialogue& end); @@ -106,8 +112,6 @@ class FoldController { void FixFoldsPreCommit(int type, const AssDialogue *single_line); - void MakeFoldsFromFile(); - // These are used for the DoForAllFolds action and should not be used as ordinary getters/setters static bool ActionHasFold(AssDialogue& line); @@ -120,10 +124,25 @@ class FoldController { static bool ActionToggleFold(AssDialogue& line); + /// Updates the line's extradata entry from the values in FoldInfo. Used after actions like toggling folds. + void UpdateLineExtradata(AssDialogue& line); + + /// Sets valid = false and increases the invalidCounter, deleting the extradata if necessary + void InvalidateLineFold(AssDialogue &line); + /// After lines have been added or deleted, this ensures consistency again. Run with every relevant commit. + /// Performs the three actions below in order. + void UpdateFoldInfo(); + + /// Parses the extradata of all lines and sets the respective lines in the FoldInfo. + /// Also deduplicates extradata entries and mangles fold id's when necessary. + void ReadFromExtradata(); + + /// Ensures consistency by making sure every fold has two delimiters and folds are properly nested. + /// Cleans up extradata entries if they've been invalid for long enough. void FixFolds(); - /// If the fold base dataa is valid, sets up all the cached links in the FoldData + /// Once the fold base data is valid, sets up all the cached links in the FoldData. void LinkFolds(); public: diff --git a/src/libresrc/default_menu.json b/src/libresrc/default_menu.json index 9fe227c5b..ca0f53336 100644 --- a/src/libresrc/default_menu.json +++ b/src/libresrc/default_menu.json @@ -93,7 +93,7 @@ { "command" : "grid/fold/open_all" }, { "command" : "grid/fold/close_all" }, { "command" : "grid/fold/clear_all" }, - {}, + {}, { "submenu" : "main/subtitle/sort lines", "text" : "Sort All Lines" }, { "submenu" : "main/subtitle/sort selected lines", "text" : "Sort Selected Lines" }, { "command" : "grid/swap" }, diff --git a/src/libresrc/osx/default_menu.json b/src/libresrc/osx/default_menu.json index 2b3928341..4a5c4459a 100644 --- a/src/libresrc/osx/default_menu.json +++ b/src/libresrc/osx/default_menu.json @@ -96,7 +96,7 @@ { "command" : "grid/fold/open_all" }, { "command" : "grid/fold/close_all" }, { "command" : "grid/fold/clear_all" }, - {}, + {}, { "submenu" : "main/subtitle/sort lines", "text" : "Sort All Lines" }, { "submenu" : "main/subtitle/sort selected lines", "text" : "Sort Selected Lines" }, { "command" : "grid/swap" }, diff --git a/src/subtitle_format_ass.cpp b/src/subtitle_format_ass.cpp index 97fe61060..6815c229f 100644 --- a/src/subtitle_format_ass.cpp +++ b/src/subtitle_format_ass.cpp @@ -109,19 +109,6 @@ struct Writer { WriteIfNotZero("Active Line: ", properties.active_row); WriteIfNotZero("Video Position: ", properties.video_position); } - - std::string foldsdata; - for (LineFold fold : properties.folds) { - if (!foldsdata.empty()) { - foldsdata += ","; - } - foldsdata += std::to_string(fold.start); - foldsdata += ":"; - foldsdata += std::to_string(fold.end); - foldsdata += ":"; - foldsdata += fold.collapsed ? "1" : "0"; - } - WriteIfNotEmpty("Line Folds: ", foldsdata); } void WriteIfNotEmpty(const char *key, std::string const& value) {