Merge branch 'folding' into feature
This commit is contained in:
commit
79a0655eb8
10 changed files with 198 additions and 165 deletions
|
@ -246,6 +246,39 @@ uint32_t AssFile::AddExtradata(std::string const& key, std::string const& value)
|
||||||
return next_extradata_id++; // return old value, then post-increment
|
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<uint32_t> id_list = line.ExtradataIds;
|
||||||
|
std::vector<bool> to_erase(id_list.size());
|
||||||
|
bool dirty = false;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
std::vector<ExtradataEntry> 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 {
|
namespace {
|
||||||
struct extradata_id_cmp {
|
struct extradata_id_cmp {
|
||||||
bool operator()(ExtradataEntry const& e, uint32_t id) {
|
bool operator()(ExtradataEntry const& e, uint32_t id) {
|
||||||
|
|
|
@ -85,7 +85,6 @@ struct ProjectProperties {
|
||||||
int active_row = 0;
|
int active_row = 0;
|
||||||
int ar_mode = 0;
|
int ar_mode = 0;
|
||||||
int video_position = 0;
|
int video_position = 0;
|
||||||
std::vector<LineFold> folds;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class AssFile {
|
class AssFile {
|
||||||
|
@ -93,6 +92,8 @@ class AssFile {
|
||||||
agi::signal::Signal<int, const AssDialogue*> AnnounceCommit;
|
agi::signal::Signal<int, const AssDialogue*> AnnounceCommit;
|
||||||
agi::signal::Signal<int, const AssDialogue*> AnnouncePreCommit;
|
agi::signal::Signal<int, const AssDialogue*> AnnouncePreCommit;
|
||||||
agi::signal::Signal<AssFileCommit> PushState;
|
agi::signal::Signal<AssFileCommit> PushState;
|
||||||
|
|
||||||
|
void SetExtradataValue(AssDialogue& line, std::string const& key, std::string const& value, bool del);
|
||||||
public:
|
public:
|
||||||
/// The lines in the file
|
/// The lines in the file
|
||||||
std::vector<AssInfo> Info;
|
std::vector<AssInfo> Info;
|
||||||
|
@ -144,6 +145,10 @@ public:
|
||||||
uint32_t AddExtradata(std::string const& key, std::string const& value);
|
uint32_t AddExtradata(std::string const& key, std::string const& value);
|
||||||
/// Fetch all extradata entries from a list of IDs
|
/// Fetch all extradata entries from a list of IDs
|
||||||
std::vector<ExtradataEntry> GetExtradata(std::vector<uint32_t> const& id_list) const;
|
std::vector<ExtradataEntry> GetExtradata(std::vector<uint32_t> 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
|
/// Remove unreferenced extradata entries
|
||||||
void CleanExtradata();
|
void CleanExtradata();
|
||||||
|
|
||||||
|
@ -178,7 +183,7 @@ public:
|
||||||
/// Extradata entries were added/modified/removed
|
/// Extradata entries were added/modified/removed
|
||||||
COMMIT_EXTRADATA = 0x100,
|
COMMIT_EXTRADATA = 0x100,
|
||||||
/// Folds were added or removed
|
/// Folds were added or removed
|
||||||
COMMIT_FOLD = 0x200,
|
COMMIT_FOLD = COMMIT_EXTRADATA,
|
||||||
};
|
};
|
||||||
|
|
||||||
DEFINE_SIGNAL_ADDERS(AnnouncePreCommit, AddPreCommitListener)
|
DEFINE_SIGNAL_ADDERS(AnnouncePreCommit, AddPreCommitListener)
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
|
|
||||||
#include <libaegisub/ass/uuencode.h>
|
#include <libaegisub/ass/uuencode.h>
|
||||||
#include <libaegisub/make_unique.h>
|
#include <libaegisub/make_unique.h>
|
||||||
#include <libaegisub/split.h>
|
|
||||||
#include <libaegisub/util.h>
|
#include <libaegisub/util.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
@ -40,8 +39,7 @@ class AssParser::HeaderToProperty {
|
||||||
using field = boost::variant<
|
using field = boost::variant<
|
||||||
std::string ProjectProperties::*,
|
std::string ProjectProperties::*,
|
||||||
int ProjectProperties::*,
|
int ProjectProperties::*,
|
||||||
double ProjectProperties::*,
|
double ProjectProperties::*
|
||||||
std::vector<LineFold> ProjectProperties::*
|
|
||||||
>;
|
>;
|
||||||
std::unordered_map<std::string, field> fields;
|
std::unordered_map<std::string, field> fields;
|
||||||
|
|
||||||
|
@ -60,7 +58,6 @@ public:
|
||||||
{"Video Zoom Percent", &ProjectProperties::video_zoom},
|
{"Video Zoom Percent", &ProjectProperties::video_zoom},
|
||||||
{"Scroll Position", &ProjectProperties::scroll_position},
|
{"Scroll Position", &ProjectProperties::scroll_position},
|
||||||
{"Active Line", &ProjectProperties::active_row},
|
{"Active Line", &ProjectProperties::active_row},
|
||||||
{"Line Folds", &ProjectProperties::folds},
|
|
||||||
{"Video Position", &ProjectProperties::video_position},
|
{"Video Position", &ProjectProperties::video_position},
|
||||||
{"Video AR Mode", &ProjectProperties::ar_mode},
|
{"Video AR Mode", &ProjectProperties::ar_mode},
|
||||||
{"Video AR Value", &ProjectProperties::ar_value},
|
{"Video AR Value", &ProjectProperties::ar_value},
|
||||||
|
@ -83,29 +80,6 @@ public:
|
||||||
void operator()(std::string ProjectProperties::*f) const { obj.*f = value; }
|
void operator()(std::string ProjectProperties::*f) const { obj.*f = value; }
|
||||||
void operator()(int ProjectProperties::*f) const { try_parse(value, &(obj.*f)); }
|
void operator()(int ProjectProperties::*f) const { try_parse(value, &(obj.*f)); }
|
||||||
void operator()(double ProjectProperties::*f) const { try_parse(value, &(obj.*f)); }
|
void operator()(double ProjectProperties::*f) const { try_parse(value, &(obj.*f)); }
|
||||||
void operator()(std::vector<LineFold> ProjectProperties::*f) const {
|
|
||||||
std::vector<LineFold> folds;
|
|
||||||
|
|
||||||
for (auto foldstr : agi::Split(value, ',')) {
|
|
||||||
LineFold fold;
|
|
||||||
std::vector<std::string> 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};
|
} visitor {target->Properties, value};
|
||||||
boost::apply_visitor(visitor, it->second);
|
boost::apply_visitor(visitor, it->second);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
#include "ass_karaoke.h"
|
#include "ass_karaoke.h"
|
||||||
#include "ass_style.h"
|
#include "ass_style.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
#include "fold_controller.h"
|
|
||||||
|
|
||||||
#include <libaegisub/exception.h>
|
#include <libaegisub/exception.h>
|
||||||
#include <libaegisub/log.h>
|
#include <libaegisub/log.h>
|
||||||
|
@ -101,22 +100,6 @@ namespace {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
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<T *>(lua_touserdata(L, -1));
|
|
||||||
lua_pop(L, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
using namespace Automation4;
|
using namespace Automation4;
|
||||||
template<int (LuaAssFile::*closure)(lua_State *)>
|
template<int (LuaAssFile::*closure)(lua_State *)>
|
||||||
int closure_wrapper(lua_State *L)
|
int closure_wrapper(lua_State *L)
|
||||||
|
@ -198,10 +181,6 @@ namespace Automation4 {
|
||||||
|
|
||||||
set_field(L, "text", std::string(dia->Text));
|
set_field(L, "text", std::string(dia->Text));
|
||||||
|
|
||||||
// preserve the folds
|
|
||||||
*static_cast<FoldInfo*>(lua_newuserdata(L, sizeof(FoldInfo))) = dia->Fold;
|
|
||||||
lua_setfield(L, -2, "_foldinfo");
|
|
||||||
|
|
||||||
// create extradata table
|
// create extradata table
|
||||||
lua_newtable(L);
|
lua_newtable(L);
|
||||||
for (auto const& ed : ass->GetExtradata(dia->ExtradataIds)) {
|
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->Margin[2] = get_int_field(L, "margin_t", "dialogue");
|
||||||
dia->Effect = get_string_field(L, "effect", "dialogue");
|
dia->Effect = get_string_field(L, "effect", "dialogue");
|
||||||
dia->Text = get_string_field(L, "text", "dialogue");
|
dia->Text = get_string_field(L, "text", "dialogue");
|
||||||
if (!get_userdata_field(L, "_foldinfo", "dialogue", &dia->Fold, false)) {
|
|
||||||
dia->Fold = FoldInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint32_t> new_ids;
|
std::vector<uint32_t> new_ids;
|
||||||
|
|
||||||
|
|
|
@ -502,7 +502,7 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
|
||||||
CaptureMouse();
|
CaptureMouse();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (columns[col]->OnMouseEvent(dlg, context, event)) {
|
if (dlg && columns[col]->OnMouseEvent(dlg, context, event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>>
|
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
|
||||||
//
|
//
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
@ -24,9 +24,10 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include <libaegisub/log.h>
|
#include <libaegisub/split.h>
|
||||||
|
#include <libaegisub/util.h>
|
||||||
|
|
||||||
static int next_fold_id = 0;
|
const char *folds_key = "_aegi_folddata";
|
||||||
|
|
||||||
FoldController::FoldController(agi::Context *c)
|
FoldController::FoldController(agi::Context *c)
|
||||||
: context(c)
|
: context(c)
|
||||||
|
@ -35,12 +36,12 @@ FoldController::FoldController(agi::Context *c)
|
||||||
|
|
||||||
|
|
||||||
bool FoldController::CanAddFold(AssDialogue& start, AssDialogue& end) {
|
bool FoldController::CanAddFold(AssDialogue& start, AssDialogue& end) {
|
||||||
if (start.Fold.exists || end.Fold.exists) {
|
if (start.Fold.valid || end.Fold.valid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
int folddepth = 0;
|
int folddepth = 0;
|
||||||
for (auto it = std::next(context->ass->Events.begin(), start.Row); it->Row < end.Row; it++) {
|
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;
|
folddepth += it->Fold.side ? -1 : 1;
|
||||||
}
|
}
|
||||||
if (folddepth < 0) {
|
if (folddepth < 0) {
|
||||||
|
@ -51,17 +52,24 @@ bool FoldController::CanAddFold(AssDialogue& start, AssDialogue& end) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void FoldController::RawAddFold(AssDialogue& start, AssDialogue& end, bool collapsed) {
|
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;
|
void FoldController::UpdateLineExtradata(AssDialogue &line) {
|
||||||
start.Fold.collapsed = collapsed;
|
if (line.Fold.extraExists)
|
||||||
start.Fold.id = id;
|
context->ass->SetExtradataValue(line, folds_key, agi::format("%d;%d;%d", int(line.Fold.side), int(line.Fold.collapsed), int(line.Fold.id)));
|
||||||
start.Fold.side = false;
|
else
|
||||||
|
context->ass->DeleteExtradataValue(line, folds_key);
|
||||||
|
}
|
||||||
|
|
||||||
end.Fold.exists = true;
|
void FoldController::InvalidateLineFold(AssDialogue &line) {
|
||||||
end.Fold.collapsed = collapsed;
|
line.Fold.valid = false;
|
||||||
end.Fold.id = id;
|
if (++line.Fold.invalidCount > 100) {
|
||||||
end.Fold.side = true;
|
line.Fold.extraExists = false;
|
||||||
|
UpdateLineExtradata(line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FoldController::AddFold(AssDialogue& start, AssDialogue& end, bool collapsed) {
|
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)) {
|
bool FoldController::DoForAllFolds(bool action(AssDialogue& line)) {
|
||||||
for (AssDialogue& line : context->ass->Events) {
|
for (AssDialogue& line : context->ass->Events) {
|
||||||
if (line.Fold.exists) {
|
if (line.Fold.valid) {
|
||||||
if (action(line)) {
|
bool result = action(line);
|
||||||
|
UpdateLineExtradata(line);
|
||||||
|
if (result)
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -84,34 +93,10 @@ bool FoldController::DoForAllFolds(bool action(AssDialogue& line)) {
|
||||||
|
|
||||||
void FoldController::FixFoldsPreCommit(int type, const AssDialogue *single_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_FOLD | AssFile::COMMIT_DIAG_ADDREM | AssFile::COMMIT_ORDER)) || type == AssFile::COMMIT_NEW) {
|
||||||
if (type == AssFile::COMMIT_NEW && context->subsController->IsUndoStackEmpty()) {
|
UpdateFoldInfo();
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// 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.
|
// Returns true as soon as any action() call returned true.
|
||||||
//
|
//
|
||||||
|
@ -119,17 +104,56 @@ void FoldController::MakeFoldsFromFile() {
|
||||||
// be followed by a commit.
|
// be followed by a commit.
|
||||||
bool FoldController::DoForFoldsAt(std::vector<AssDialogue *> const& lines, bool action(AssDialogue& line)) {
|
bool FoldController::DoForFoldsAt(std::vector<AssDialogue *> const& lines, bool action(AssDialogue& line)) {
|
||||||
for (AssDialogue *line : lines) {
|
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;
|
line = line->Fold.parent;
|
||||||
}
|
}
|
||||||
if (!line->Fold.visited && action(*line)) {
|
if (!line->Fold.visited) {
|
||||||
return true;
|
bool result = action(*line);
|
||||||
|
UpdateLineExtradata(*line);
|
||||||
|
if (result)
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
line->Fold.visited = true;
|
line->Fold.visited = true;
|
||||||
}
|
}
|
||||||
return false;
|
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<std::string> 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() {
|
void FoldController::FixFolds() {
|
||||||
// Stack of which folds we've desended into so far
|
// Stack of which folds we've desended into so far
|
||||||
std::vector<AssDialogue *> foldStack;
|
std::vector<AssDialogue *> foldStack;
|
||||||
|
@ -142,15 +166,28 @@ void FoldController::FixFolds() {
|
||||||
// fold data with this ID is skipped and deleted.
|
// fold data with this ID is skipped and deleted.
|
||||||
std::unordered_map<int, bool> completedFolds;
|
std::unordered_map<int, bool> 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<int, int> idRemap;
|
||||||
|
|
||||||
for (auto line = context->ass->Events.begin(); line != context->ass->Events.end(); line++) {
|
for (auto line = context->ass->Events.begin(); line != context->ass->Events.end(); line++) {
|
||||||
if (line->Fold.exists) {
|
if (line->Fold.extraExists) {
|
||||||
if (completedFolds.count(line->Fold.id)) { // Duplicate entry
|
bool needs_update = false;
|
||||||
line->Fold.exists = false;
|
|
||||||
continue;
|
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 (!line->Fold.side) {
|
||||||
if (foldHeads.count(line->Fold.id)) { // Duplicate entry
|
if (foldHeads.count(line->Fold.id)) { // Duplicate entry
|
||||||
line->Fold.exists = false;
|
InvalidateLineFold(*line);
|
||||||
} else {
|
} else {
|
||||||
foldHeads[line->Fold.id] = &*line;
|
foldHeads[line->Fold.id] = &*line;
|
||||||
foldStack.push_back(&*line);
|
foldStack.push_back(&*line);
|
||||||
|
@ -160,7 +197,7 @@ void FoldController::FixFolds() {
|
||||||
// Deactivate it. Because we can, also push it to completedFolds:
|
// Deactivate it. Because we can, also push it to completedFolds:
|
||||||
// If its counterpart appears further below, we can delete it right away.
|
// If its counterpart appears further below, we can delete it right away.
|
||||||
completedFolds[line->Fold.id] = true;
|
completedFolds[line->Fold.id] = true;
|
||||||
line->Fold.exists = false;
|
InvalidateLineFold(*line);
|
||||||
} else {
|
} else {
|
||||||
// We found a fold. Now we need to see if the stack matches.
|
// We found a fold. Now we need to see if the stack matches.
|
||||||
// We scan our stack for the counterpart of the fold.
|
// We scan our stack for the counterpart of the fold.
|
||||||
|
@ -176,12 +213,15 @@ void FoldController::FixFolds() {
|
||||||
// Erase all folds further inward
|
// Erase all folds further inward
|
||||||
for (int j = foldStack.size() - 1; j > i; j--) {
|
for (int j = foldStack.size() - 1; j > i; j--) {
|
||||||
completedFolds[foldStack[j]->Fold.id] = true;
|
completedFolds[foldStack[j]->Fold.id] = true;
|
||||||
foldStack[j]->Fold.exists = false;
|
InvalidateLineFold(*foldStack[j]);
|
||||||
foldStack.pop_back();
|
foldStack.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync the found fold and pop the stack
|
// 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();
|
foldStack.pop_back();
|
||||||
|
|
||||||
found = true;
|
found = true;
|
||||||
|
@ -190,25 +230,30 @@ void FoldController::FixFolds() {
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
completedFolds[line->Fold.id] = true;
|
completedFolds[line->Fold.id] = true;
|
||||||
line->Fold.exists = false;
|
InvalidateLineFold(*line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (needs_update) {
|
||||||
|
UpdateLineExtradata(*line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// All remaining lines are invalid
|
// All remaining lines are invalid
|
||||||
for (AssDialogue *line : foldStack) {
|
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() {
|
void FoldController::LinkFolds() {
|
||||||
std::vector<AssDialogue *> foldStack;
|
std::vector<AssDialogue *> foldStack;
|
||||||
AssDialogue *lastVisible = nullptr;
|
AssDialogue *lastVisible = nullptr;
|
||||||
context->ass->Properties.folds.clear();
|
|
||||||
|
|
||||||
maxdepth = 0;
|
maxdepth = 0;
|
||||||
|
|
||||||
|
@ -217,10 +262,10 @@ void FoldController::LinkFolds() {
|
||||||
for (auto line = context->ass->Events.begin(); line != context->ass->Events.end(); line++) {
|
for (auto line = context->ass->Events.begin(); line != context->ass->Events.end(); line++) {
|
||||||
line->Fold.parent = foldStack.empty() ? nullptr : foldStack.back();
|
line->Fold.parent = foldStack.empty() ? nullptr : foldStack.back();
|
||||||
line->Fold.nextVisible = nullptr;
|
line->Fold.nextVisible = nullptr;
|
||||||
line->Fold.visible = highestFolded > foldStack.size();
|
line->Fold.visible = highestFolded > (int) foldStack.size();
|
||||||
line->Fold.visited = false;
|
line->Fold.visited = false;
|
||||||
line->Fold.visibleRow = visibleRow;
|
line->Fold.visibleRow = visibleRow;
|
||||||
|
|
||||||
if (line->Fold.visible) {
|
if (line->Fold.visible) {
|
||||||
if (lastVisible != nullptr) {
|
if (lastVisible != nullptr) {
|
||||||
lastVisible->Fold.nextVisible = &*line;
|
lastVisible->Fold.nextVisible = &*line;
|
||||||
|
@ -228,26 +273,20 @@ void FoldController::LinkFolds() {
|
||||||
lastVisible = &*line;
|
lastVisible = &*line;
|
||||||
visibleRow++;
|
visibleRow++;
|
||||||
}
|
}
|
||||||
if (line->Fold.exists && !line->Fold.side) {
|
if (line->Fold.valid && !line->Fold.side) {
|
||||||
foldStack.push_back(&*line);
|
foldStack.push_back(&*line);
|
||||||
if (!line->Fold.collapsed && highestFolded == foldStack.size()) {
|
if (!line->Fold.collapsed && highestFolded == (int) foldStack.size()) {
|
||||||
highestFolded++;
|
highestFolded++;
|
||||||
}
|
}
|
||||||
if (foldStack.size() > maxdepth) {
|
if ((int) foldStack.size() > maxdepth) {
|
||||||
maxdepth = foldStack.size();
|
maxdepth = foldStack.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (line->Fold.exists && line->Fold.side) {
|
if (line->Fold.valid && line->Fold.side) {
|
||||||
context->ass->Properties.folds.push_back(LineFold {
|
|
||||||
foldStack.back()->Row,
|
|
||||||
line->Row,
|
|
||||||
line->Fold.collapsed,
|
|
||||||
});
|
|
||||||
|
|
||||||
line->Fold.counterpart = foldStack.back();
|
line->Fold.counterpart = foldStack.back();
|
||||||
(*foldStack.rbegin())->Fold.counterpart = &*line;
|
(*foldStack.rbegin())->Fold.counterpart = &*line;
|
||||||
|
|
||||||
if (highestFolded >= foldStack.size()) {
|
if (highestFolded >= (int) foldStack.size()) {
|
||||||
highestFolded = foldStack.size();
|
highestFolded = foldStack.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,9 +299,9 @@ int FoldController::GetMaxDepth() {
|
||||||
return maxdepth;
|
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; }
|
bool FoldController::ActionOpenFold(AssDialogue& line) { line.Fold.collapsed = false; return false; }
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>>
|
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
|
||||||
//
|
//
|
||||||
// Permission to use, copy, modify, and distribute this software for any
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
// purpose with or without fee is hereby granted, provided that the above
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
@ -23,6 +23,8 @@
|
||||||
|
|
||||||
namespace agi { struct Context; }
|
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.
|
/// 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.
|
/// 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)
|
/// 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.
|
/// an intersection set not equal to one of the two folds.
|
||||||
/// Only one fold may be started or ended at any given line.
|
/// 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
|
/// In order for folds to be preserved while adding or deleting lines and work nicely with operations like copy/paste,
|
||||||
/// data as part of the individual AssDialogue lines. Hooking into insertion or deletion calls is not possible
|
/// they need to be stored as extradata. Furthermore, in order for the subtitle grid and fold management commands to efficiently
|
||||||
/// without extensive restructuring, and also wouldn't interact well with undo/redo functionality.
|
/// 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
|
/// A fold descriptor for a line is an extradata field of the form <direction>;<collapsed>;<id>, where
|
||||||
/// to fix any format violations after changes are made. Furthermore, to be able to traverse the folds more easily,
|
/// direction is 0 if this line starts a fold, and 1 if the line ends one
|
||||||
/// we compute various metadata and set up pointers between the fold parts.
|
/// 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.
|
/// Part of the data for an AssDialogue object, describing folds starting or ending at this line.
|
||||||
class FoldInfo {
|
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.
|
/// Whether there is some extradata entry on folds here
|
||||||
bool exists = false;
|
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
|
/// Whether the fold is currently collapsed
|
||||||
bool collapsed = false;
|
bool collapsed = false;
|
||||||
/// False if a fold is started here, true otherwise.
|
/// False if a fold is started here, true otherwise.
|
||||||
bool side = false;
|
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
|
// Used in DoForFoldsAt to ensure each line is visited only once
|
||||||
bool visited = false;
|
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
|
/// Whether the line is currently visible
|
||||||
bool visible = true;
|
bool visible = true;
|
||||||
|
|
||||||
/// If exists is true, this is a pointer to the other line with the given fold id
|
/// If exists is true, this is a pointer to the other line with the given fold id
|
||||||
AssDialogue *counterpart = nullptr;
|
AssDialogue *counterpart = nullptr;
|
||||||
|
|
||||||
/// A pointer to the opener of the innermost fold containing the line, if one exists.
|
/// 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.
|
/// If the line starts a fold, this points to the next bigger fold.
|
||||||
AssDialogue *parent = nullptr;
|
AssDialogue *parent = nullptr;
|
||||||
|
|
||||||
/// If this line is visible, this points to the next visible line, if one exists
|
/// If this line is visible, this points to the next visible line, if one exists
|
||||||
AssDialogue *nextVisible = nullptr;
|
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
|
/// The row number where this line would appear in the subtitle grid. That is, the ordinary
|
||||||
/// Row value, but with hidden lines skipped.
|
/// 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.
|
/// 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;
|
friend class FoldController;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool hasFold() const { return exists; }
|
bool hasFold() const { return valid; }
|
||||||
bool isFolded() const { return collapsed; }
|
bool isFolded() const { return collapsed; }
|
||||||
bool isEnd() const { return side; }
|
bool isEnd() const { return side; }
|
||||||
|
|
||||||
|
@ -95,6 +100,7 @@ class FoldController {
|
||||||
agi::Context *context;
|
agi::Context *context;
|
||||||
agi::signal::Connection pre_commit_listener;
|
agi::signal::Connection pre_commit_listener;
|
||||||
int maxdepth = 0;
|
int maxdepth = 0;
|
||||||
|
int max_fold_id = 0;
|
||||||
|
|
||||||
bool CanAddFold(AssDialogue& start, AssDialogue& end);
|
bool CanAddFold(AssDialogue& start, AssDialogue& end);
|
||||||
|
|
||||||
|
@ -106,8 +112,6 @@ class FoldController {
|
||||||
|
|
||||||
void FixFoldsPreCommit(int type, const AssDialogue *single_line);
|
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
|
// These are used for the DoForAllFolds action and should not be used as ordinary getters/setters
|
||||||
|
|
||||||
static bool ActionHasFold(AssDialogue& line);
|
static bool ActionHasFold(AssDialogue& line);
|
||||||
|
@ -120,10 +124,25 @@ class FoldController {
|
||||||
|
|
||||||
static bool ActionToggleFold(AssDialogue& line);
|
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.
|
/// 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();
|
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();
|
void LinkFolds();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
{ "command" : "grid/fold/open_all" },
|
{ "command" : "grid/fold/open_all" },
|
||||||
{ "command" : "grid/fold/close_all" },
|
{ "command" : "grid/fold/close_all" },
|
||||||
{ "command" : "grid/fold/clear_all" },
|
{ "command" : "grid/fold/clear_all" },
|
||||||
{},
|
{},
|
||||||
{ "submenu" : "main/subtitle/sort lines", "text" : "Sort All Lines" },
|
{ "submenu" : "main/subtitle/sort lines", "text" : "Sort All Lines" },
|
||||||
{ "submenu" : "main/subtitle/sort selected lines", "text" : "Sort Selected Lines" },
|
{ "submenu" : "main/subtitle/sort selected lines", "text" : "Sort Selected Lines" },
|
||||||
{ "command" : "grid/swap" },
|
{ "command" : "grid/swap" },
|
||||||
|
|
|
@ -96,7 +96,7 @@
|
||||||
{ "command" : "grid/fold/open_all" },
|
{ "command" : "grid/fold/open_all" },
|
||||||
{ "command" : "grid/fold/close_all" },
|
{ "command" : "grid/fold/close_all" },
|
||||||
{ "command" : "grid/fold/clear_all" },
|
{ "command" : "grid/fold/clear_all" },
|
||||||
{},
|
{},
|
||||||
{ "submenu" : "main/subtitle/sort lines", "text" : "Sort All Lines" },
|
{ "submenu" : "main/subtitle/sort lines", "text" : "Sort All Lines" },
|
||||||
{ "submenu" : "main/subtitle/sort selected lines", "text" : "Sort Selected Lines" },
|
{ "submenu" : "main/subtitle/sort selected lines", "text" : "Sort Selected Lines" },
|
||||||
{ "command" : "grid/swap" },
|
{ "command" : "grid/swap" },
|
||||||
|
|
|
@ -109,19 +109,6 @@ struct Writer {
|
||||||
WriteIfNotZero("Active Line: ", properties.active_row);
|
WriteIfNotZero("Active Line: ", properties.active_row);
|
||||||
WriteIfNotZero("Video Position: ", properties.video_position);
|
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) {
|
void WriteIfNotEmpty(const char *key, std::string const& value) {
|
||||||
|
|
Loading…
Reference in a new issue