mirror of https://github.com/odrling/Aegisub
Merge branch 'folding' into feature
This commit is contained in:
commit
11b5a80618
|
@ -26,9 +26,11 @@
|
||||||
// POSSIBILITY OF SUCH DAMAGE.
|
// POSSIBILITY OF SUCH DAMAGE.
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include "ass_entry.h"
|
#include "ass_entry.h"
|
||||||
#include "ass_override.h"
|
#include "ass_override.h"
|
||||||
|
#include "fold_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/ass/time.h>
|
#include <libaegisub/ass/time.h>
|
||||||
|
|
||||||
|
@ -124,6 +126,9 @@ struct AssDialogueBase {
|
||||||
|
|
||||||
int Row = -1;
|
int Row = -1;
|
||||||
|
|
||||||
|
/// Data describing line folds starting or ending at this line
|
||||||
|
FoldInfo Fold;
|
||||||
|
|
||||||
/// Is this a comment line?
|
/// Is this a comment line?
|
||||||
bool Comment = false;
|
bool Comment = false;
|
||||||
/// Layer number
|
/// Layer number
|
||||||
|
|
|
@ -175,6 +175,8 @@ int AssFile::Commit(wxString const& desc, int type, int amend_id, AssDialogue *s
|
||||||
event.Row = i++;
|
event.Row = i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnnouncePreCommit(type, single_line);
|
||||||
|
|
||||||
PushState({desc, &amend_id, single_line});
|
PushState({desc, &amend_id, single_line});
|
||||||
|
|
||||||
AnnounceCommit(type, single_line);
|
AnnounceCommit(type, single_line);
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
//
|
//
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
#include "ass_entry.h"
|
#include "ass_entry.h"
|
||||||
|
|
||||||
#include <libaegisub/fs_fwd.h>
|
#include <libaegisub/fs_fwd.h>
|
||||||
|
@ -52,6 +54,13 @@ struct ExtradataEntry {
|
||||||
std::string value;
|
std::string value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Both start and end are inclusive
|
||||||
|
struct LineFold {
|
||||||
|
int start;
|
||||||
|
int end;
|
||||||
|
bool collapsed;
|
||||||
|
};
|
||||||
|
|
||||||
struct AssFileCommit {
|
struct AssFileCommit {
|
||||||
wxString const& message;
|
wxString const& message;
|
||||||
int *commit_id;
|
int *commit_id;
|
||||||
|
@ -76,11 +85,13 @@ 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 {
|
||||||
/// 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, const AssDialogue*> AnnounceCommit;
|
agi::signal::Signal<int, const AssDialogue*> AnnounceCommit;
|
||||||
|
agi::signal::Signal<int, const AssDialogue*> AnnouncePreCommit;
|
||||||
agi::signal::Signal<AssFileCommit> PushState;
|
agi::signal::Signal<AssFileCommit> PushState;
|
||||||
public:
|
public:
|
||||||
/// The lines in the file
|
/// The lines in the file
|
||||||
|
@ -166,8 +177,11 @@ public:
|
||||||
COMMIT_DIAG_FULL = COMMIT_DIAG_META | COMMIT_DIAG_TIME | COMMIT_DIAG_TEXT,
|
COMMIT_DIAG_FULL = COMMIT_DIAG_META | COMMIT_DIAG_TIME | COMMIT_DIAG_TEXT,
|
||||||
/// Extradata entries were added/modified/removed
|
/// Extradata entries were added/modified/removed
|
||||||
COMMIT_EXTRADATA = 0x100,
|
COMMIT_EXTRADATA = 0x100,
|
||||||
|
/// Folds were added or removed
|
||||||
|
COMMIT_FOLD = 0x200,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DEFINE_SIGNAL_ADDERS(AnnouncePreCommit, AddPreCommitListener)
|
||||||
DEFINE_SIGNAL_ADDERS(AnnounceCommit, AddCommitListener)
|
DEFINE_SIGNAL_ADDERS(AnnounceCommit, AddCommitListener)
|
||||||
DEFINE_SIGNAL_ADDERS(PushState, AddUndoManager)
|
DEFINE_SIGNAL_ADDERS(PushState, AddUndoManager)
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
#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>
|
||||||
|
@ -39,7 +40,8 @@ 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;
|
||||||
|
|
||||||
|
@ -58,6 +60,7 @@ 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},
|
||||||
|
@ -80,6 +83,29 @@ 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,6 +40,7 @@
|
||||||
#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>
|
||||||
|
@ -100,6 +101,16 @@ namespace {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void get_userdata_field(lua_State *L, const char *name, const char *line_class, T *target)
|
||||||
|
{
|
||||||
|
lua_getfield(L, -1, name);
|
||||||
|
if (!lua_isuserdata(L, -1))
|
||||||
|
throw bad_field("userdata", name, line_class);
|
||||||
|
*target = *static_cast<T *>(lua_touserdata(L, -1));
|
||||||
|
lua_pop(L, 1);
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
@ -181,6 +192,10 @@ namespace Automation4 {
|
||||||
|
|
||||||
set_field(L, "text", dia->Text);
|
set_field(L, "text", 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)) {
|
||||||
|
@ -301,6 +316,7 @@ 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");
|
||||||
|
get_userdata_field(L, "_foldinfo", "dialogue", &dia->Fold);
|
||||||
|
|
||||||
std::vector<uint32_t> new_ids;
|
std::vector<uint32_t> new_ids;
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
#include "ass_file.h"
|
#include "ass_file.h"
|
||||||
#include "audio_box.h"
|
#include "audio_box.h"
|
||||||
#include "compat.h"
|
#include "compat.h"
|
||||||
|
#include "fold_controller.h"
|
||||||
#include "grid_column.h"
|
#include "grid_column.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
|
@ -100,6 +101,8 @@ BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
|
||||||
OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this),
|
OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this),
|
||||||
OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this),
|
OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this),
|
||||||
OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this),
|
OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this),
|
||||||
|
OPT_SUB("Colour/Subtitle Grid/Background/Open Fold", &BaseGrid::UpdateStyle, this),
|
||||||
|
OPT_SUB("Colour/Subtitle Grid/Background/Closed Fold", &BaseGrid::UpdateStyle, this),
|
||||||
OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this),
|
OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this),
|
||||||
OPT_SUB("Colour/Subtitle Grid/Header", &BaseGrid::UpdateStyle, this),
|
OPT_SUB("Colour/Subtitle Grid/Header", &BaseGrid::UpdateStyle, this),
|
||||||
OPT_SUB("Colour/Subtitle Grid/Left Column", &BaseGrid::UpdateStyle, this),
|
OPT_SUB("Colour/Subtitle Grid/Left Column", &BaseGrid::UpdateStyle, this),
|
||||||
|
@ -127,7 +130,7 @@ BEGIN_EVENT_TABLE(BaseGrid,wxWindow)
|
||||||
END_EVENT_TABLE()
|
END_EVENT_TABLE()
|
||||||
|
|
||||||
void BaseGrid::OnSubtitlesCommit(int type) {
|
void BaseGrid::OnSubtitlesCommit(int type) {
|
||||||
if (type == AssFile::COMMIT_NEW || type & AssFile::COMMIT_ORDER || type & AssFile::COMMIT_DIAG_ADDREM)
|
if (type == AssFile::COMMIT_NEW || type & AssFile::COMMIT_ORDER || type & AssFile::COMMIT_DIAG_ADDREM || type & AssFile::COMMIT_FOLD)
|
||||||
UpdateMaps();
|
UpdateMaps();
|
||||||
|
|
||||||
if (type & AssFile::COMMIT_DIAG_META) {
|
if (type & AssFile::COMMIT_DIAG_META) {
|
||||||
|
@ -184,6 +187,8 @@ void BaseGrid::UpdateStyle() {
|
||||||
row_colors.Comment.SetColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Comment")->GetColor()));
|
row_colors.Comment.SetColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Comment")->GetColor()));
|
||||||
row_colors.Visible.SetColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Inframe")->GetColor()));
|
row_colors.Visible.SetColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Inframe")->GetColor()));
|
||||||
row_colors.SelectedComment.SetColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Selected Comment")->GetColor()));
|
row_colors.SelectedComment.SetColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Selected Comment")->GetColor()));
|
||||||
|
row_colors.FoldOpen.SetColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Open Fold")->GetColor()));
|
||||||
|
row_colors.FoldClosed.SetColour(to_wx(OPT_GET("Colour/Subtitle Grid/Background/Closed Fold")->GetColor()));
|
||||||
row_colors.LeftCol.SetColour(to_wx(OPT_GET("Colour/Subtitle Grid/Left Column")->GetColor()));
|
row_colors.LeftCol.SetColour(to_wx(OPT_GET("Colour/Subtitle Grid/Left Column")->GetColor()));
|
||||||
|
|
||||||
SetColumnWidths();
|
SetColumnWidths();
|
||||||
|
@ -194,10 +199,14 @@ void BaseGrid::UpdateStyle() {
|
||||||
|
|
||||||
void BaseGrid::UpdateMaps() {
|
void BaseGrid::UpdateMaps() {
|
||||||
index_line_map.clear();
|
index_line_map.clear();
|
||||||
|
vis_index_line_map.clear();
|
||||||
|
|
||||||
for (auto& curdiag : context->ass->Events)
|
for (auto& curdiag : context->ass->Events)
|
||||||
index_line_map.push_back(&curdiag);
|
index_line_map.push_back(&curdiag);
|
||||||
|
|
||||||
|
for (AssDialogue *curdiag = &*context->ass->Events.begin(); curdiag != nullptr; curdiag = curdiag->Fold.getNextVisible())
|
||||||
|
vis_index_line_map.push_back(&*curdiag);
|
||||||
|
|
||||||
SetColumnWidths();
|
SetColumnWidths();
|
||||||
AdjustScrollbar();
|
AdjustScrollbar();
|
||||||
Refresh(false);
|
Refresh(false);
|
||||||
|
@ -215,6 +224,10 @@ void BaseGrid::OnActiveLineChanged(AssDialogue *new_active) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseGrid::MakeRowVisible(int row) {
|
void BaseGrid::MakeRowVisible(int row) {
|
||||||
|
MakeVisRowVisible(GetDialogue(row)->Fold.getVisibleRow());
|
||||||
|
}
|
||||||
|
|
||||||
|
void BaseGrid::MakeVisRowVisible(int row) {
|
||||||
int h = GetClientSize().GetHeight();
|
int h = GetClientSize().GetHeight();
|
||||||
|
|
||||||
if (row < yPos + 1)
|
if (row < yPos + 1)
|
||||||
|
@ -224,9 +237,9 @@ void BaseGrid::MakeRowVisible(int row) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseGrid::SelectRow(int row, bool addToSelected, bool select) {
|
void BaseGrid::SelectRow(int row, bool addToSelected, bool select) {
|
||||||
if (row < 0 || (size_t)row >= index_line_map.size()) return;
|
if (row < 0 || (size_t)row >= vis_index_line_map.size()) return;
|
||||||
|
|
||||||
AssDialogue *line = index_line_map[row];
|
AssDialogue *line = vis_index_line_map[row];
|
||||||
|
|
||||||
if (!addToSelected) {
|
if (!addToSelected) {
|
||||||
context->selectionController->SetSelectedSet(Selection{line});
|
context->selectionController->SetSelectedSet(Selection{line});
|
||||||
|
@ -246,11 +259,11 @@ void BaseGrid::SelectRow(int row, bool addToSelected, bool select) {
|
||||||
|
|
||||||
void BaseGrid::OnSeek() {
|
void BaseGrid::OnSeek() {
|
||||||
int lines = GetClientSize().GetHeight() / lineHeight + 1;
|
int lines = GetClientSize().GetHeight() / lineHeight + 1;
|
||||||
lines = mid(0, lines, GetRows() - yPos);
|
lines = mid(0, lines, GetVisRows() - yPos);
|
||||||
|
|
||||||
auto it = begin(visible_rows);
|
auto it = begin(visible_rows);
|
||||||
for (int i : boost::irange(yPos, yPos + lines)) {
|
for (int i : boost::irange(yPos, yPos + lines)) {
|
||||||
if (IsDisplayed(index_line_map[i])) {
|
if (IsDisplayed(vis_index_line_map[i])) {
|
||||||
if (it == end(visible_rows) || *it != i) {
|
if (it == end(visible_rows) || *it != i) {
|
||||||
Refresh(false);
|
Refresh(false);
|
||||||
return;
|
return;
|
||||||
|
@ -338,7 +351,7 @@ void BaseGrid::OnPaint(wxPaintEvent &) {
|
||||||
|
|
||||||
// Paint the rows
|
// Paint the rows
|
||||||
const int drawPerScreen = h/lineHeight + 1;
|
const int drawPerScreen = h/lineHeight + 1;
|
||||||
const int nDraw = mid(0, drawPerScreen, GetRows() - yPos);
|
const int nDraw = mid(0, drawPerScreen, GetVisRows() - yPos);
|
||||||
const int grid_x = columns[0]->Width();
|
const int grid_x = columns[0]->Width();
|
||||||
|
|
||||||
const auto active_line = context->selectionController->GetActiveLine();
|
const auto active_line = context->selectionController->GetActiveLine();
|
||||||
|
@ -347,7 +360,7 @@ void BaseGrid::OnPaint(wxPaintEvent &) {
|
||||||
|
|
||||||
for (int i : agi::util::range(nDraw)) {
|
for (int i : agi::util::range(nDraw)) {
|
||||||
wxBrush color = row_colors.Default;
|
wxBrush color = row_colors.Default;
|
||||||
AssDialogue *curDiag = index_line_map[i + yPos];
|
AssDialogue *curDiag = vis_index_line_map[i + yPos];
|
||||||
|
|
||||||
bool inSel = !!selection.count(curDiag);
|
bool inSel = !!selection.count(curDiag);
|
||||||
if (inSel && curDiag->Comment)
|
if (inSel && curDiag->Comment)
|
||||||
|
@ -362,6 +375,11 @@ void BaseGrid::OnPaint(wxPaintEvent &) {
|
||||||
color = row_colors.Visible;
|
color = row_colors.Visible;
|
||||||
visible_rows.push_back(i + yPos);
|
visible_rows.push_back(i + yPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (curDiag->Fold.hasFold() && !inSel) {
|
||||||
|
color = curDiag->Fold.isFolded() ? row_colors.FoldClosed : row_colors.FoldOpen;
|
||||||
|
}
|
||||||
|
|
||||||
dc.SetBrush(color);
|
dc.SetBrush(color);
|
||||||
|
|
||||||
// Draw row background color
|
// Draw row background color
|
||||||
|
@ -406,10 +424,10 @@ void BaseGrid::OnPaint(wxPaintEvent &) {
|
||||||
dc.DrawLine(w, 0, w, maxH);
|
dc.DrawLine(w, 0, w, maxH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (active_line && active_line->Row >= yPos && active_line->Row < yPos + nDraw) {
|
if (active_line && active_line->Fold.getVisibleRow() >= yPos && active_line->Fold.getVisibleRow() < yPos + nDraw) {
|
||||||
dc.SetPen(wxPen(to_wx(OPT_GET("Colour/Subtitle Grid/Active Border")->GetColor())));
|
dc.SetPen(wxPen(to_wx(OPT_GET("Colour/Subtitle Grid/Active Border")->GetColor())));
|
||||||
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
||||||
dc.DrawRectangle(0, (active_line->Row - yPos + 1) * lineHeight, w, lineHeight + 1);
|
dc.DrawRectangle(0, (active_line->Fold.getVisibleRow() - yPos + 1) * lineHeight, w, lineHeight + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,17 +455,28 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
|
||||||
bool dclick = event.LeftDClick();
|
bool dclick = event.LeftDClick();
|
||||||
int row = event.GetY() / lineHeight + yPos - 1;
|
int row = event.GetY() / lineHeight + yPos - 1;
|
||||||
if (holding && !click)
|
if (holding && !click)
|
||||||
row = mid(0, row, GetRows()-1);
|
row = mid(0, row, GetVisRows()-1);
|
||||||
AssDialogue *dlg = GetDialogue(row);
|
AssDialogue *dlg = GetVisDialogue(row);
|
||||||
if (!dlg) row = 0;
|
if (!dlg) row = 0;
|
||||||
|
|
||||||
|
// Find the column the mouse is over
|
||||||
|
int colx = event.GetX();
|
||||||
|
int col;
|
||||||
|
for (col = 0; col < columns.size(); col++) {
|
||||||
|
int w = columns[col]->Width();
|
||||||
|
if (colx < w) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
colx -= w;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.ButtonDown() && OPT_GET("Subtitle/Grid/Focus Allow")->GetBool())
|
if (event.ButtonDown() && OPT_GET("Subtitle/Grid/Focus Allow")->GetBool())
|
||||||
SetFocus();
|
SetFocus();
|
||||||
|
|
||||||
if (holding) {
|
if (holding) {
|
||||||
if (!event.LeftIsDown()) {
|
if (!event.LeftIsDown()) {
|
||||||
if (dlg)
|
if (dlg)
|
||||||
MakeRowVisible(row);
|
MakeVisRowVisible(row);
|
||||||
holding = false;
|
holding = false;
|
||||||
ReleaseMouse();
|
ReleaseMouse();
|
||||||
}
|
}
|
||||||
|
@ -470,6 +499,10 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
|
||||||
CaptureMouse();
|
CaptureMouse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (columns[col]->OnMouseEvent(dlg, context, event)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ((click || holding || dclick) && dlg) {
|
if ((click || holding || dclick) && dlg) {
|
||||||
int old_extend = extendRow;
|
int old_extend = extendRow;
|
||||||
|
|
||||||
|
@ -517,7 +550,7 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
|
||||||
// Toggle each
|
// Toggle each
|
||||||
Selection newsel;
|
Selection newsel;
|
||||||
if (ctrl) newsel = selection;
|
if (ctrl) newsel = selection;
|
||||||
for (int i = i1; i <= i2; i++)
|
for (int i = VisRowToRow(i1); i <= VisRowToRow(i2); i++)
|
||||||
newsel.insert(GetDialogue(i));
|
newsel.insert(GetDialogue(i));
|
||||||
context->selectionController->SetSelectedSet(std::move(newsel));
|
context->selectionController->SetSelectedSet(std::move(newsel));
|
||||||
return;
|
return;
|
||||||
|
@ -555,7 +588,7 @@ void BaseGrid::OnContextMenu(wxContextMenuEvent &evt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void BaseGrid::ScrollTo(int y) {
|
void BaseGrid::ScrollTo(int y) {
|
||||||
int nextY = mid(0, y, GetRows() - 1);
|
int nextY = mid(0, y, GetVisRows() - 1);
|
||||||
if (yPos != nextY) {
|
if (yPos != nextY) {
|
||||||
context->ass->Properties.scroll_position = yPos = nextY;
|
context->ass->Properties.scroll_position = yPos = nextY;
|
||||||
scrollBar->SetThumbPosition(yPos);
|
scrollBar->SetThumbPosition(yPos);
|
||||||
|
@ -570,7 +603,7 @@ void BaseGrid::AdjustScrollbar() {
|
||||||
scrollBar->Freeze();
|
scrollBar->Freeze();
|
||||||
scrollBar->SetSize(clientSize.GetWidth() - scrollbarSize.GetWidth(), 0, scrollbarSize.GetWidth(), clientSize.GetHeight());
|
scrollBar->SetSize(clientSize.GetWidth() - scrollbarSize.GetWidth(), 0, scrollbarSize.GetWidth(), clientSize.GetHeight());
|
||||||
|
|
||||||
if (GetRows() <= 1) {
|
if (GetVisRows() <= 1) {
|
||||||
yPos = 0;
|
yPos = 0;
|
||||||
scrollBar->Enable(false);
|
scrollBar->Enable(false);
|
||||||
scrollBar->Thaw();
|
scrollBar->Thaw();
|
||||||
|
@ -581,7 +614,7 @@ void BaseGrid::AdjustScrollbar() {
|
||||||
scrollBar->Enable(true);
|
scrollBar->Enable(true);
|
||||||
|
|
||||||
int drawPerScreen = clientSize.GetHeight() / lineHeight;
|
int drawPerScreen = clientSize.GetHeight() / lineHeight;
|
||||||
int rows = GetRows();
|
int rows = GetVisRows();
|
||||||
|
|
||||||
context->ass->Properties.scroll_position = yPos = mid(0, yPos, rows - 1);
|
context->ass->Properties.scroll_position = yPos = mid(0, yPos, rows - 1);
|
||||||
|
|
||||||
|
@ -618,6 +651,16 @@ AssDialogue *BaseGrid::GetDialogue(int n) const {
|
||||||
return index_line_map[n];
|
return index_line_map[n];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AssDialogue *BaseGrid::GetVisDialogue(int n) const {
|
||||||
|
if (static_cast<size_t>(n) >= vis_index_line_map.size()) return nullptr;
|
||||||
|
return vis_index_line_map[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
int BaseGrid::VisRowToRow(int n) const {
|
||||||
|
AssDialogue *d = GetVisDialogue(n);
|
||||||
|
return d != nullptr ? d->Row : GetRows() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
bool BaseGrid::IsDisplayed(const AssDialogue *line) const {
|
bool BaseGrid::IsDisplayed(const AssDialogue *line) const {
|
||||||
if (!context->project->VideoProvider()) return false;
|
if (!context->project->VideoProvider()) return false;
|
||||||
int frame = context->videoController->GetFrameN();
|
int frame = context->videoController->GetFrameN();
|
||||||
|
@ -665,11 +708,11 @@ void BaseGrid::OnKeyDown(wxKeyEvent &event) {
|
||||||
}
|
}
|
||||||
else if (key == WXK_HOME) {
|
else if (key == WXK_HOME) {
|
||||||
dir = -1;
|
dir = -1;
|
||||||
step = GetRows();
|
step = GetVisRows();
|
||||||
}
|
}
|
||||||
else if (key == WXK_END) {
|
else if (key == WXK_END) {
|
||||||
dir = 1;
|
dir = 1;
|
||||||
step = GetRows();
|
step = GetVisRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dir) {
|
if (!dir) {
|
||||||
|
@ -679,8 +722,8 @@ void BaseGrid::OnKeyDown(wxKeyEvent &event) {
|
||||||
|
|
||||||
auto active_line = context->selectionController->GetActiveLine();
|
auto active_line = context->selectionController->GetActiveLine();
|
||||||
int old_extend = extendRow;
|
int old_extend = extendRow;
|
||||||
int next = mid(0, (active_line ? active_line->Row : 0) + dir * step, GetRows() - 1);
|
int next = mid(0, (active_line ? active_line->Fold.getVisibleRow() : 0) + dir * step, GetVisRows() - 1);
|
||||||
context->selectionController->SetActiveLine(GetDialogue(next));
|
context->selectionController->SetActiveLine(GetVisDialogue(next));
|
||||||
|
|
||||||
// Move selection
|
// Move selection
|
||||||
if (!ctrl && !shift && !alt) {
|
if (!ctrl && !shift && !alt) {
|
||||||
|
@ -703,12 +746,12 @@ void BaseGrid::OnKeyDown(wxKeyEvent &event) {
|
||||||
|
|
||||||
// Select range
|
// Select range
|
||||||
Selection newsel;
|
Selection newsel;
|
||||||
for (int i = begin; i <= end; i++)
|
for (int i = VisRowToRow(begin); i <= VisRowToRow(end); i++)
|
||||||
newsel.insert(GetDialogue(i));
|
newsel.insert(GetDialogue(i));
|
||||||
|
|
||||||
context->selectionController->SetSelectedSet(std::move(newsel));
|
context->selectionController->SetSelectedSet(std::move(newsel));
|
||||||
|
|
||||||
MakeRowVisible(next);
|
MakeVisRowVisible(next);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,9 +80,12 @@ class BaseGrid final : public wxWindow {
|
||||||
wxBrush Visible;
|
wxBrush Visible;
|
||||||
wxBrush SelectedComment;
|
wxBrush SelectedComment;
|
||||||
wxBrush LeftCol;
|
wxBrush LeftCol;
|
||||||
|
wxBrush FoldOpen;
|
||||||
|
wxBrush FoldClosed;
|
||||||
} row_colors;
|
} row_colors;
|
||||||
|
|
||||||
std::vector<AssDialogue*> index_line_map; ///< Row number -> dialogue line
|
std::vector<AssDialogue*> index_line_map; ///< Row number -> dialogue line
|
||||||
|
std::vector<AssDialogue*> vis_index_line_map; ///< Visible Row number -> dialogue line
|
||||||
|
|
||||||
/// Connection for video seek event. Stored explicitly so that it can be
|
/// Connection for video seek event. Stored explicitly so that it can be
|
||||||
/// blocked if the relevant option is disabled
|
/// blocked if the relevant option is disabled
|
||||||
|
@ -115,13 +118,22 @@ class BaseGrid final : public wxWindow {
|
||||||
void SelectRow(int row, bool addToSelected = false, bool select=true);
|
void SelectRow(int row, bool addToSelected = false, bool select=true);
|
||||||
|
|
||||||
int GetRows() const { return index_line_map.size(); }
|
int GetRows() const { return index_line_map.size(); }
|
||||||
|
int GetVisRows() const { return vis_index_line_map.size(); }
|
||||||
void MakeRowVisible(int row);
|
void MakeRowVisible(int row);
|
||||||
|
void MakeVisRowVisible(int row);
|
||||||
|
|
||||||
/// @brief Get dialogue by index
|
/// @brief Get dialogue by index
|
||||||
/// @param n Index to look up
|
/// @param n Index to look up
|
||||||
/// @return Subtitle dialogue line for index, or 0 if invalid index
|
/// @return Subtitle dialogue line for index, or 0 if invalid index
|
||||||
AssDialogue *GetDialogue(int n) const;
|
AssDialogue *GetDialogue(int n) const;
|
||||||
|
|
||||||
|
/// @brief Get visible dialogue by the displayed row's index
|
||||||
|
/// @param n Displayed ndex to look up
|
||||||
|
/// @return Visible ubtitle dialogue line for index, or 0 if invalid index
|
||||||
|
AssDialogue *GetVisDialogue(int n) const;
|
||||||
|
|
||||||
|
int VisRowToRow(int n) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
BaseGrid(wxWindow* parent, agi::Context *context);
|
BaseGrid(wxWindow* parent, agi::Context *context);
|
||||||
~BaseGrid();
|
~BaseGrid();
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
#include "../ass_file.h"
|
#include "../ass_file.h"
|
||||||
#include "../audio_controller.h"
|
#include "../audio_controller.h"
|
||||||
#include "../audio_timing.h"
|
#include "../audio_timing.h"
|
||||||
|
#include "../fold_controller.h"
|
||||||
#include "../frame_main.h"
|
#include "../frame_main.h"
|
||||||
#include "../include/aegisub/context.h"
|
#include "../include/aegisub/context.h"
|
||||||
#include "../libresrc/libresrc.h"
|
#include "../libresrc/libresrc.h"
|
||||||
|
@ -398,6 +399,123 @@ struct grid_swap final : public Command {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct grid_fold_create final : public Command {
|
||||||
|
CMD_NAME("grid/fold/create")
|
||||||
|
STR_MENU("Create new Fold")
|
||||||
|
STR_DISP("Create new Fold")
|
||||||
|
STR_HELP("Create a new fold collapsing the selected lines into a group")
|
||||||
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
|
bool Validate(const agi::Context *c) override {
|
||||||
|
return c->selectionController->GetSelectedSet().size() >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(agi::Context *c) override {
|
||||||
|
auto const& sel = c->selectionController->GetSortedSelection();
|
||||||
|
if (sel.size() >= 2) {
|
||||||
|
c->foldController->AddFold(**sel.begin(), **sel.rbegin(), true);
|
||||||
|
c->selectionController->SetSelectionAndActive({ *sel.begin() }, *sel.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct grid_fold_open final : public Command {
|
||||||
|
CMD_NAME("grid/fold/open")
|
||||||
|
STR_MENU("Open Folds")
|
||||||
|
STR_DISP("Open Folds")
|
||||||
|
STR_HELP("Expand the folds under the selected lines")
|
||||||
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
|
bool Validate(const agi::Context *c) override {
|
||||||
|
return c->foldController->AreFoldsAt(c->selectionController->GetSortedSelection());
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(agi::Context *c) override {
|
||||||
|
c->foldController->OpenFoldsAt(c->selectionController->GetSortedSelection());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct grid_fold_close final : public Command {
|
||||||
|
CMD_NAME("grid/fold/close")
|
||||||
|
STR_MENU("Close Folds")
|
||||||
|
STR_DISP("Close Folds")
|
||||||
|
STR_HELP("Collapse the folds around the selected lines")
|
||||||
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
|
bool Validate(const agi::Context *c) override {
|
||||||
|
return c->foldController->AreFoldsAt(c->selectionController->GetSortedSelection());
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(agi::Context *c) override {
|
||||||
|
c->foldController->CloseFoldsAt(c->selectionController->GetSortedSelection());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct grid_fold_clear final : public Command {
|
||||||
|
CMD_NAME("grid/fold/clear")
|
||||||
|
STR_MENU("Clear Folds")
|
||||||
|
STR_DISP("Clear Folds")
|
||||||
|
STR_HELP("Remove the folds around the selected lines")
|
||||||
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
|
bool Validate(const agi::Context *c) override {
|
||||||
|
return c->foldController->AreFoldsAt(c->selectionController->GetSortedSelection());
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(agi::Context *c) override {
|
||||||
|
c->foldController->ClearFoldsAt(c->selectionController->GetSortedSelection());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct grid_fold_toggle final : public Command {
|
||||||
|
CMD_NAME("grid/fold/toggle")
|
||||||
|
STR_MENU("Toggle Folds")
|
||||||
|
STR_DISP("Toggle Folds")
|
||||||
|
STR_HELP("Open or close the folds around the selected lines")
|
||||||
|
CMD_TYPE(COMMAND_VALIDATE)
|
||||||
|
|
||||||
|
bool Validate(const agi::Context *c) override {
|
||||||
|
return c->foldController->AreFoldsAt(c->selectionController->GetSortedSelection());
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(agi::Context *c) override {
|
||||||
|
c->foldController->ToggleFoldsAt(c->selectionController->GetSortedSelection());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct grid_fold_open_all final : public Command {
|
||||||
|
CMD_NAME("grid/fold/open_all")
|
||||||
|
STR_MENU("Open all Folds")
|
||||||
|
STR_DISP("Open all Folds")
|
||||||
|
STR_HELP("Open all Folds")
|
||||||
|
|
||||||
|
void operator()(agi::Context *c) override {
|
||||||
|
c->foldController->OpenAllFolds();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct grid_fold_close_all final : public Command {
|
||||||
|
CMD_NAME("grid/fold/close_all")
|
||||||
|
STR_MENU("Close all Folds")
|
||||||
|
STR_DISP("Close all Folds")
|
||||||
|
STR_HELP("Close all Folds")
|
||||||
|
|
||||||
|
void operator()(agi::Context *c) override {
|
||||||
|
c->foldController->CloseAllFolds();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct grid_fold_clear_all final : public Command {
|
||||||
|
CMD_NAME("grid/fold/clear_all")
|
||||||
|
STR_MENU("Clear all Folds")
|
||||||
|
STR_DISP("Clear all Folds")
|
||||||
|
STR_HELP("Remove all Folds")
|
||||||
|
|
||||||
|
void operator()(agi::Context *c) override {
|
||||||
|
c->foldController->ClearAllFolds();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace cmd {
|
namespace cmd {
|
||||||
|
@ -420,6 +538,14 @@ namespace cmd {
|
||||||
reg(agi::make_unique<grid_move_down>());
|
reg(agi::make_unique<grid_move_down>());
|
||||||
reg(agi::make_unique<grid_move_up>());
|
reg(agi::make_unique<grid_move_up>());
|
||||||
reg(agi::make_unique<grid_swap>());
|
reg(agi::make_unique<grid_swap>());
|
||||||
|
reg(agi::make_unique<grid_fold_create>());
|
||||||
|
reg(agi::make_unique<grid_fold_open>());
|
||||||
|
reg(agi::make_unique<grid_fold_close>());
|
||||||
|
reg(agi::make_unique<grid_fold_toggle>());
|
||||||
|
reg(agi::make_unique<grid_fold_clear>());
|
||||||
|
reg(agi::make_unique<grid_fold_open_all>());
|
||||||
|
reg(agi::make_unique<grid_fold_close_all>());
|
||||||
|
reg(agi::make_unique<grid_fold_clear_all>());
|
||||||
reg(agi::make_unique<grid_tag_cycle_hiding>());
|
reg(agi::make_unique<grid_tag_cycle_hiding>());
|
||||||
reg(agi::make_unique<grid_tags_hide>());
|
reg(agi::make_unique<grid_tags_hide>());
|
||||||
reg(agi::make_unique<grid_tags_show>());
|
reg(agi::make_unique<grid_tags_show>());
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "audio_controller.h"
|
#include "audio_controller.h"
|
||||||
#include "auto4_base.h"
|
#include "auto4_base.h"
|
||||||
#include "dialog_manager.h"
|
#include "dialog_manager.h"
|
||||||
|
#include "fold_controller.h"
|
||||||
#include "initial_line_state.h"
|
#include "initial_line_state.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
|
@ -40,6 +41,7 @@ Context::Context()
|
||||||
, project(make_unique<Project>(this))
|
, project(make_unique<Project>(this))
|
||||||
, local_scripts(make_unique<Automation4::LocalScriptManager>(this))
|
, local_scripts(make_unique<Automation4::LocalScriptManager>(this))
|
||||||
, selectionController(make_unique<SelectionController>(this))
|
, selectionController(make_unique<SelectionController>(this))
|
||||||
|
, foldController(make_unique<FoldController>(this))
|
||||||
, videoController(make_unique<VideoController>(this))
|
, videoController(make_unique<VideoController>(this))
|
||||||
, audioController(make_unique<AudioController>(this))
|
, audioController(make_unique<AudioController>(this))
|
||||||
, initialLineState(make_unique<InitialLineState>(this))
|
, initialLineState(make_unique<InitialLineState>(this))
|
||||||
|
|
|
@ -0,0 +1,315 @@
|
||||||
|
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>>
|
||||||
|
//
|
||||||
|
// 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 "fold_controller.h"
|
||||||
|
|
||||||
|
#include "ass_file.h"
|
||||||
|
#include "include/aegisub/context.h"
|
||||||
|
#include "format.h"
|
||||||
|
#include "subs_controller.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <libaegisub/log.h>
|
||||||
|
|
||||||
|
static int next_fold_id = 0;
|
||||||
|
|
||||||
|
FoldController::FoldController(agi::Context *c)
|
||||||
|
: context(c)
|
||||||
|
, pre_commit_listener(c->ass->AddPreCommitListener(&FoldController::FixFoldsPreCommit, this))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
bool FoldController::CanAddFold(AssDialogue& start, AssDialogue& end) {
|
||||||
|
if (start.Fold.exists || end.Fold.exists) {
|
||||||
|
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) {
|
||||||
|
folddepth += it->Fold.side ? -1 : 1;
|
||||||
|
}
|
||||||
|
if (folddepth < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return folddepth == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldController::RawAddFold(AssDialogue& start, AssDialogue& end, bool collapsed) {
|
||||||
|
int id = next_fold_id++;
|
||||||
|
|
||||||
|
start.Fold.exists = true;
|
||||||
|
start.Fold.collapsed = collapsed;
|
||||||
|
start.Fold.id = id;
|
||||||
|
start.Fold.side = false;
|
||||||
|
|
||||||
|
end.Fold.exists = true;
|
||||||
|
end.Fold.collapsed = collapsed;
|
||||||
|
end.Fold.id = id;
|
||||||
|
end.Fold.side = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldController::AddFold(AssDialogue& start, AssDialogue& end, bool collapsed) {
|
||||||
|
if (CanAddFold(start, end)) {
|
||||||
|
RawAddFold(start, end, true);
|
||||||
|
context->ass->Commit(_("add fold"), AssFile::COMMIT_FOLD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FoldController::DoForAllFolds(bool action(AssDialogue& line)) {
|
||||||
|
for (AssDialogue& line : context->ass->Events) {
|
||||||
|
if (line.Fold.exists) {
|
||||||
|
if (action(line)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
//
|
||||||
|
// In general, this can leave the folds in an inconsistent state, so unless action() is read-only this should always
|
||||||
|
// be followed by a commit.
|
||||||
|
bool FoldController::DoForFoldsAt(std::vector<AssDialogue *> const& lines, bool action(AssDialogue& line)) {
|
||||||
|
for (AssDialogue *line : lines) {
|
||||||
|
if (line->Fold.parent != nullptr && !(line->Fold.exists && !line->Fold.side)) {
|
||||||
|
line = line->Fold.parent;
|
||||||
|
}
|
||||||
|
if (!line->Fold.visited && action(*line)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
line->Fold.visited = true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldController::FixFolds() {
|
||||||
|
// Stack of which folds we've desended into so far
|
||||||
|
std::vector<AssDialogue *> foldStack;
|
||||||
|
|
||||||
|
// ID's for which we've found starters
|
||||||
|
std::unordered_map<int, AssDialogue*> foldHeads;
|
||||||
|
|
||||||
|
// ID's for which we've either found a valid starter and ender,
|
||||||
|
// or determined that the respective fold is invalid. All further
|
||||||
|
// fold data with this ID is skipped and deleted.
|
||||||
|
std::unordered_map<int, bool> completedFolds;
|
||||||
|
|
||||||
|
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.side) {
|
||||||
|
if (foldHeads.count(line->Fold.id)) { // Duplicate entry
|
||||||
|
line->Fold.exists = false;
|
||||||
|
} else {
|
||||||
|
foldHeads[line->Fold.id] = &*line;
|
||||||
|
foldStack.push_back(&*line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!foldHeads.count(line->Fold.id)) { // Non-matching ender
|
||||||
|
// 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;
|
||||||
|
} else {
|
||||||
|
// We found a fold. Now we need to see if the stack matches.
|
||||||
|
// We scan our stack for the counterpart of the fold.
|
||||||
|
// If one exists, we assume all starters above it are invalid.
|
||||||
|
// If none exists, we assume this ender is invalid.
|
||||||
|
// If none of these assumptions are true, the folds are probably
|
||||||
|
// broken beyond repair.
|
||||||
|
|
||||||
|
completedFolds[line->Fold.id] = true;
|
||||||
|
bool found = false;
|
||||||
|
for (int i = foldStack.size() - 1; i >= 0; i--) {
|
||||||
|
if (foldStack[i]->Fold.id == line->Fold.id) {
|
||||||
|
// 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;
|
||||||
|
foldStack.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync the found fold and pop the stack
|
||||||
|
line->Fold.collapsed = foldStack[i]->Fold.collapsed;
|
||||||
|
foldStack.pop_back();
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
completedFolds[line->Fold.id] = true;
|
||||||
|
line->Fold.exists = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All remaining lines are invalid
|
||||||
|
for (AssDialogue *line : foldStack) {
|
||||||
|
line->Fold.exists = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LinkFolds();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldController::LinkFolds() {
|
||||||
|
std::vector<AssDialogue *> foldStack;
|
||||||
|
AssDialogue *lastVisible = nullptr;
|
||||||
|
context->ass->Properties.folds.clear();
|
||||||
|
|
||||||
|
maxdepth = 0;
|
||||||
|
|
||||||
|
int visibleRow = 0;
|
||||||
|
int highestFolded = 1;
|
||||||
|
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.visited = false;
|
||||||
|
line->Fold.visibleRow = visibleRow;
|
||||||
|
|
||||||
|
if (line->Fold.visible) {
|
||||||
|
if (lastVisible != nullptr) {
|
||||||
|
lastVisible->Fold.nextVisible = &*line;
|
||||||
|
}
|
||||||
|
lastVisible = &*line;
|
||||||
|
visibleRow++;
|
||||||
|
}
|
||||||
|
if (line->Fold.exists && !line->Fold.side) {
|
||||||
|
foldStack.push_back(&*line);
|
||||||
|
if (!line->Fold.collapsed && highestFolded == foldStack.size()) {
|
||||||
|
highestFolded++;
|
||||||
|
}
|
||||||
|
if (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,
|
||||||
|
});
|
||||||
|
|
||||||
|
line->Fold.counterpart = foldStack.back();
|
||||||
|
(*foldStack.rbegin())->Fold.counterpart = &*line;
|
||||||
|
|
||||||
|
if (highestFolded >= foldStack.size()) {
|
||||||
|
highestFolded = foldStack.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
foldStack.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int FoldController::GetMaxDepth() {
|
||||||
|
return maxdepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FoldController::ActionHasFold(AssDialogue& line) { return line.Fold.exists; }
|
||||||
|
|
||||||
|
bool FoldController::ActionClearFold(AssDialogue& line) { line.Fold.exists = false; return false; }
|
||||||
|
|
||||||
|
bool FoldController::ActionOpenFold(AssDialogue& line) { line.Fold.collapsed = false; return false; }
|
||||||
|
|
||||||
|
bool FoldController::ActionCloseFold(AssDialogue& line) { line.Fold.collapsed = true; return false; }
|
||||||
|
|
||||||
|
bool FoldController::ActionToggleFold(AssDialogue& line) { line.Fold.collapsed = !line.Fold.collapsed; return false; }
|
||||||
|
|
||||||
|
|
||||||
|
void FoldController::ClearAllFolds() {
|
||||||
|
FoldController::DoForAllFolds(FoldController::ActionClearFold);
|
||||||
|
context->ass->Commit(_("clear all folds"), AssFile::COMMIT_FOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldController::OpenAllFolds() {
|
||||||
|
FoldController::DoForAllFolds(FoldController::ActionOpenFold);
|
||||||
|
context->ass->Commit(_("open all folds"), AssFile::COMMIT_FOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldController::CloseAllFolds() {
|
||||||
|
FoldController::DoForAllFolds(FoldController::ActionCloseFold);
|
||||||
|
context->ass->Commit(_("close all folds"), AssFile::COMMIT_FOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FoldController::HasFolds() {
|
||||||
|
return FoldController::DoForAllFolds(FoldController::ActionHasFold);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldController::ClearFoldsAt(std::vector<AssDialogue *> const& lines) {
|
||||||
|
FoldController::DoForFoldsAt(lines, FoldController::ActionClearFold);
|
||||||
|
context->ass->Commit(_("clear folds"), AssFile::COMMIT_FOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldController::OpenFoldsAt(std::vector<AssDialogue *> const& lines) {
|
||||||
|
FoldController::DoForFoldsAt(lines, FoldController::ActionOpenFold);
|
||||||
|
context->ass->Commit(_("open folds"), AssFile::COMMIT_FOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldController::CloseFoldsAt(std::vector<AssDialogue *> const& lines) {
|
||||||
|
FoldController::DoForFoldsAt(lines, FoldController::ActionCloseFold);
|
||||||
|
context->ass->Commit(_("close folds"), AssFile::COMMIT_FOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FoldController::ToggleFoldsAt(std::vector<AssDialogue *> const& lines) {
|
||||||
|
FoldController::DoForFoldsAt(lines, FoldController::ActionToggleFold);
|
||||||
|
context->ass->Commit(_("toggle folds"), AssFile::COMMIT_FOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FoldController::AreFoldsAt(std::vector<AssDialogue *> const& lines) {
|
||||||
|
return FoldController::DoForFoldsAt(lines, FoldController::ActionHasFold);
|
||||||
|
}
|
|
@ -0,0 +1,173 @@
|
||||||
|
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>>
|
||||||
|
//
|
||||||
|
// 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/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libaegisub/signal.h>
|
||||||
|
#include "ass_file.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace agi { struct Context; }
|
||||||
|
|
||||||
|
/// 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)
|
||||||
|
/// A fold may *strictly* contain other folds or be *strictly* contained in other folds, but it may not intersect another fold with
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
|
||||||
|
/// Part of the data for an AssDialogue object, describing folds starting or ending at this line.
|
||||||
|
class FoldInfo {
|
||||||
|
// Base data describing the folds:w
|
||||||
|
|
||||||
|
/// Whether a fold starts or ends at the line. All other fields are only valid if this is true.
|
||||||
|
bool exists = false;
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
friend class FoldController;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool hasFold() const { return exists; }
|
||||||
|
bool isFolded() const { return collapsed; }
|
||||||
|
bool isEnd() const { return side; }
|
||||||
|
|
||||||
|
// The following functions are only valid directly after a commit.
|
||||||
|
// Their behaviour is undefined as soon as any uncommitted change is made to the Events.
|
||||||
|
AssDialogue *getFoldOpener() const { return parent; }
|
||||||
|
AssDialogue *getNextVisible() const { return nextVisible; }
|
||||||
|
int getVisibleRow() const { return visibleRow; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "ass_dialogue.h"
|
||||||
|
|
||||||
|
class FoldController {
|
||||||
|
agi::Context *context;
|
||||||
|
agi::signal::Connection pre_commit_listener;
|
||||||
|
int maxdepth = 0;
|
||||||
|
|
||||||
|
bool CanAddFold(AssDialogue& start, AssDialogue& end);
|
||||||
|
|
||||||
|
void RawAddFold(AssDialogue& start, AssDialogue& end, bool collapsed);
|
||||||
|
|
||||||
|
bool DoForFoldsAt(std::vector<AssDialogue *> const& lines, bool action(AssDialogue& line));
|
||||||
|
|
||||||
|
bool DoForAllFolds(bool action(AssDialogue& 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
|
||||||
|
|
||||||
|
static bool ActionHasFold(AssDialogue& line);
|
||||||
|
|
||||||
|
static bool ActionClearFold(AssDialogue& line);
|
||||||
|
|
||||||
|
static bool ActionOpenFold(AssDialogue& line);
|
||||||
|
|
||||||
|
static bool ActionCloseFold(AssDialogue& line);
|
||||||
|
|
||||||
|
static bool ActionToggleFold(AssDialogue& line);
|
||||||
|
|
||||||
|
/// After lines have been added or deleted, this ensures consistency again. Run with every relevant commit.
|
||||||
|
void FixFolds();
|
||||||
|
|
||||||
|
/// If the fold base dataa is valid, sets up all the cached links in the FoldData
|
||||||
|
void LinkFolds();
|
||||||
|
|
||||||
|
public:
|
||||||
|
FoldController(agi::Context *context);
|
||||||
|
|
||||||
|
int GetMaxDepth();
|
||||||
|
|
||||||
|
// All of the following functions are only valid directly after a commit.
|
||||||
|
// Their behaviour is undefined as soon as any uncommitted change is made to the Events.
|
||||||
|
|
||||||
|
/// @brief Add a new fold
|
||||||
|
///
|
||||||
|
/// The new fold must not intersect with any existing fold.
|
||||||
|
///
|
||||||
|
/// Calling this method should only cause a commit if the fold was
|
||||||
|
/// successfully added.
|
||||||
|
void AddFold(AssDialogue& start, AssDialogue& end, bool collapsed);
|
||||||
|
|
||||||
|
void ClearAllFolds();
|
||||||
|
|
||||||
|
void OpenAllFolds();
|
||||||
|
|
||||||
|
void CloseAllFolds();
|
||||||
|
|
||||||
|
bool HasFolds();
|
||||||
|
|
||||||
|
/// @brief Remove the folds in which the given lines are contained, if they exist
|
||||||
|
/// @param lines The lines whose folds should be removed
|
||||||
|
void ClearFoldsAt(std::vector<AssDialogue *> const& lines);
|
||||||
|
|
||||||
|
/// @brief Open the folds in which the given lines are contained, if they exist
|
||||||
|
/// @param lines The lines whose folds should be opened
|
||||||
|
void OpenFoldsAt(std::vector<AssDialogue *> const& lines);
|
||||||
|
|
||||||
|
/// @brief Open or closes the folds in which the given lines are contained, if they exist
|
||||||
|
/// @param lines The lines whose folds should be opened
|
||||||
|
void ToggleFoldsAt(std::vector<AssDialogue *> const& lines);
|
||||||
|
|
||||||
|
/// @brief Close the folds in which the given lines are contained, if they exist
|
||||||
|
/// @param lines The lines whose folds should be closed
|
||||||
|
void CloseFoldsAt(std::vector<AssDialogue *> const& lines);
|
||||||
|
|
||||||
|
/// @brief Returns whether any of the given lines are contained in folds
|
||||||
|
/// @param lines The lines
|
||||||
|
bool AreFoldsAt(std::vector<AssDialogue *> const& lines);
|
||||||
|
|
||||||
|
};
|
|
@ -22,6 +22,7 @@
|
||||||
#include "include/aegisub/context.h"
|
#include "include/aegisub/context.h"
|
||||||
#include "options.h"
|
#include "options.h"
|
||||||
#include "video_controller.h"
|
#include "video_controller.h"
|
||||||
|
#include "fold_controller.h"
|
||||||
|
|
||||||
#include <libaegisub/character_count.h>
|
#include <libaegisub/character_count.h>
|
||||||
|
|
||||||
|
@ -125,6 +126,53 @@ T max_value(T AssDialogueBase::*field, EntryList<AssDialogue> const& lines) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GridColumnFolds final : GridColumn {
|
||||||
|
COLUMN_HEADER(_(" >"))
|
||||||
|
COLUMN_DESCRIPTION(_("Folds"))
|
||||||
|
bool Centered() const override { return false; }
|
||||||
|
|
||||||
|
wxString Value(const AssDialogue *d, const agi::Context *) const override {
|
||||||
|
std::string value;
|
||||||
|
if (d->Fold.hasFold()) {
|
||||||
|
if (!d->Fold.isEnd()) {
|
||||||
|
value = d->Fold.isFolded() ? ">" : "v";
|
||||||
|
} else if (!d->Fold.isFolded()) {
|
||||||
|
value = "-";
|
||||||
|
}
|
||||||
|
while (d->Fold.getFoldOpener()) {
|
||||||
|
d = d->Fold.getFoldOpener();
|
||||||
|
value = " " + value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return " " + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OnMouseEvent(AssDialogue *d, agi::Context *c, wxMouseEvent &event) const override {
|
||||||
|
if ((event.LeftDown() || event.LeftDClick()) && !event.ShiftDown() && !event.CmdDown() && !event.AltDown()) {
|
||||||
|
if (d->Fold.hasFold() && !d->Fold.isEnd()) {
|
||||||
|
std::vector<AssDialogue *> lines;
|
||||||
|
lines.push_back(d);
|
||||||
|
c->foldController->ToggleFoldsAt(lines);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Width(const agi::Context *c, WidthHelper &helper) const override {
|
||||||
|
int maxdepth = c->foldController->GetMaxDepth();
|
||||||
|
if (maxdepth == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::string maxentry;
|
||||||
|
for (int i = 0; i < maxdepth; i++) {
|
||||||
|
maxentry += " ";
|
||||||
|
}
|
||||||
|
maxentry += ">";
|
||||||
|
return helper(maxentry);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct GridColumnLayer final : GridColumn {
|
struct GridColumnLayer final : GridColumn {
|
||||||
COLUMN_HEADER(_("L"))
|
COLUMN_HEADER(_("L"))
|
||||||
COLUMN_DESCRIPTION(_("Layer"))
|
COLUMN_DESCRIPTION(_("Layer"))
|
||||||
|
@ -409,6 +457,7 @@ std::unique_ptr<GridColumn> make() {
|
||||||
std::vector<std::unique_ptr<GridColumn>> GetGridColumns() {
|
std::vector<std::unique_ptr<GridColumn>> GetGridColumns() {
|
||||||
std::vector<std::unique_ptr<GridColumn>> ret;
|
std::vector<std::unique_ptr<GridColumn>> ret;
|
||||||
ret.push_back(make<GridColumnLineNumber>());
|
ret.push_back(make<GridColumnLineNumber>());
|
||||||
|
ret.push_back(make<GridColumnFolds>());
|
||||||
ret.push_back(make<GridColumnLayer>());
|
ret.push_back(make<GridColumnLayer>());
|
||||||
ret.push_back(make<GridColumnStartTime>());
|
ret.push_back(make<GridColumnStartTime>());
|
||||||
ret.push_back(make<GridColumnEndTime>());
|
ret.push_back(make<GridColumnEndTime>());
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
// Aegisub Project http://www.aegisub.org/
|
// Aegisub Project http://www.aegisub.org/
|
||||||
|
|
||||||
#include "flyweight_hash.h"
|
#include "flyweight_hash.h"
|
||||||
|
#include "wx/event.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
@ -68,6 +69,9 @@ public:
|
||||||
virtual wxString const& Description() const = 0;
|
virtual wxString const& Description() const = 0;
|
||||||
virtual void Paint(wxDC &dc, int x, int y, const AssDialogue *d, const agi::Context *c) const;
|
virtual void Paint(wxDC &dc, int x, int y, const AssDialogue *d, const agi::Context *c) const;
|
||||||
|
|
||||||
|
// Returns true if the default action should be skipped
|
||||||
|
virtual bool OnMouseEvent(AssDialogue *d, agi::Context *c, wxMouseEvent &event) const { return false; }
|
||||||
|
|
||||||
int Width() const { return width; }
|
int Width() const { return width; }
|
||||||
bool Visible() const { return visible; }
|
bool Visible() const { return visible; }
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ class Project;
|
||||||
class SearchReplaceEngine;
|
class SearchReplaceEngine;
|
||||||
class InitialLineState;
|
class InitialLineState;
|
||||||
class SelectionController;
|
class SelectionController;
|
||||||
|
class FoldController;
|
||||||
class SubsController;
|
class SubsController;
|
||||||
class BaseGrid;
|
class BaseGrid;
|
||||||
class TextSelectionController;
|
class TextSelectionController;
|
||||||
|
@ -47,6 +48,7 @@ struct Context {
|
||||||
std::unique_ptr<Project> project;
|
std::unique_ptr<Project> project;
|
||||||
std::unique_ptr<Automation4::ScriptManager> local_scripts;
|
std::unique_ptr<Automation4::ScriptManager> local_scripts;
|
||||||
std::unique_ptr<SelectionController> selectionController;
|
std::unique_ptr<SelectionController> selectionController;
|
||||||
|
std::unique_ptr<FoldController> foldController;
|
||||||
std::unique_ptr<VideoController> videoController;
|
std::unique_ptr<VideoController> videoController;
|
||||||
std::unique_ptr<AudioController> audioController;
|
std::unique_ptr<AudioController> audioController;
|
||||||
std::unique_ptr<InitialLineState> initialLineState;
|
std::unique_ptr<InitialLineState> initialLineState;
|
||||||
|
|
|
@ -213,7 +213,9 @@
|
||||||
"Comment" : "rgb(216, 222, 245)",
|
"Comment" : "rgb(216, 222, 245)",
|
||||||
"Inframe" : "rgb(255, 253, 234)",
|
"Inframe" : "rgb(255, 253, 234)",
|
||||||
"Selected Comment" : "rgb(211, 238, 238)",
|
"Selected Comment" : "rgb(211, 238, 238)",
|
||||||
"Selection" : "rgb(206, 255, 231)"
|
"Selection" : "rgb(206, 255, 231)",
|
||||||
|
"Open Fold" : "rgb(235, 235, 235)",
|
||||||
|
"Closed Fold" : "rgb(200, 200, 200)"
|
||||||
},
|
},
|
||||||
"Collision" : "rgb(255,0,0)",
|
"Collision" : "rgb(255,0,0)",
|
||||||
"CPS Error" : "rgb(255,0,0)",
|
"CPS Error" : "rgb(255,0,0)",
|
||||||
|
|
|
@ -263,6 +263,9 @@
|
||||||
"subtitle/select/all" : [
|
"subtitle/select/all" : [
|
||||||
"Ctrl-A"
|
"Ctrl-A"
|
||||||
],
|
],
|
||||||
|
"grid/toggle" : [
|
||||||
|
"Enter"
|
||||||
|
],
|
||||||
"video/frame/next" : [
|
"video/frame/next" : [
|
||||||
"Right"
|
"Right"
|
||||||
],
|
],
|
||||||
|
@ -359,4 +362,4 @@
|
||||||
"J"
|
"J"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,10 @@
|
||||||
{},
|
{},
|
||||||
{ "command" : "audio/save/clip" },
|
{ "command" : "audio/save/clip" },
|
||||||
{},
|
{},
|
||||||
|
{ "command" : "grid/fold/create" },
|
||||||
|
{ "command" : "grid/fold/toggle" },
|
||||||
|
{ "command" : "grid/fold/clear" },
|
||||||
|
{},
|
||||||
{ "command" : "edit/line/cut" },
|
{ "command" : "edit/line/cut" },
|
||||||
{ "command" : "edit/line/copy" },
|
{ "command" : "edit/line/copy" },
|
||||||
{ "command" : "edit/line/paste" },
|
{ "command" : "edit/line/paste" },
|
||||||
|
@ -86,6 +90,10 @@
|
||||||
{ "command" : "edit/line/recombine" },
|
{ "command" : "edit/line/recombine" },
|
||||||
{ "command" : "edit/line/split/by_karaoke" },
|
{ "command" : "edit/line/split/by_karaoke" },
|
||||||
{},
|
{},
|
||||||
|
{ "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 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" },
|
||||||
|
|
|
@ -213,7 +213,9 @@
|
||||||
"Comment" : "rgb(216, 222, 245)",
|
"Comment" : "rgb(216, 222, 245)",
|
||||||
"Inframe" : "rgb(255, 253, 234)",
|
"Inframe" : "rgb(255, 253, 234)",
|
||||||
"Selected Comment" : "rgb(211, 238, 238)",
|
"Selected Comment" : "rgb(211, 238, 238)",
|
||||||
"Selection" : "rgb(206, 255, 231)"
|
"Selection" : "rgb(206, 255, 231)",
|
||||||
|
"Open Fold" : "rgb(235, 235, 235)",
|
||||||
|
"Closed Fold" : "rgb(200, 200, 200)"
|
||||||
},
|
},
|
||||||
"Collision" : "rgb(255,0,0)",
|
"Collision" : "rgb(255,0,0)",
|
||||||
"CPS Error" : "rgb(255,0,0)",
|
"CPS Error" : "rgb(255,0,0)",
|
||||||
|
|
|
@ -273,6 +273,9 @@
|
||||||
"subtitle/select/all" : [
|
"subtitle/select/all" : [
|
||||||
"Ctrl-A"
|
"Ctrl-A"
|
||||||
],
|
],
|
||||||
|
"grid/toggle" : [
|
||||||
|
"Enter"
|
||||||
|
],
|
||||||
"video/frame/next" : [
|
"video/frame/next" : [
|
||||||
"Right"
|
"Right"
|
||||||
],
|
],
|
||||||
|
|
|
@ -20,6 +20,10 @@
|
||||||
{},
|
{},
|
||||||
{ "command" : "audio/save/clip" },
|
{ "command" : "audio/save/clip" },
|
||||||
{},
|
{},
|
||||||
|
{ "command" : "grid/fold/create" },
|
||||||
|
{ "command" : "grid/fold/toggle" },
|
||||||
|
{ "command" : "grid/fold/clear" },
|
||||||
|
{},
|
||||||
{ "command" : "edit/line/cut" },
|
{ "command" : "edit/line/cut" },
|
||||||
{ "command" : "edit/line/copy" },
|
{ "command" : "edit/line/copy" },
|
||||||
{ "command" : "edit/line/paste" },
|
{ "command" : "edit/line/paste" },
|
||||||
|
@ -89,6 +93,10 @@
|
||||||
{ "command" : "edit/line/recombine" },
|
{ "command" : "edit/line/recombine" },
|
||||||
{ "command" : "edit/line/split/by_karaoke" },
|
{ "command" : "edit/line/split/by_karaoke" },
|
||||||
{},
|
{},
|
||||||
|
{ "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 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" },
|
||||||
|
|
|
@ -89,6 +89,7 @@ aegisub_src = files(
|
||||||
'export_fixstyle.cpp',
|
'export_fixstyle.cpp',
|
||||||
'export_framerate.cpp',
|
'export_framerate.cpp',
|
||||||
'fft.cpp',
|
'fft.cpp',
|
||||||
|
'fold_controller.cpp',
|
||||||
'font_file_lister.cpp',
|
'font_file_lister.cpp',
|
||||||
'frame_main.cpp',
|
'frame_main.cpp',
|
||||||
'gl_text.cpp',
|
'gl_text.cpp',
|
||||||
|
|
|
@ -285,6 +285,8 @@ void Interface_Colours(wxTreebook *book, Preferences *parent) {
|
||||||
p->OptionAdd(grid, _("In frame background"), "Colour/Subtitle Grid/Background/Inframe");
|
p->OptionAdd(grid, _("In frame background"), "Colour/Subtitle Grid/Background/Inframe");
|
||||||
p->OptionAdd(grid, _("Comment background"), "Colour/Subtitle Grid/Background/Comment");
|
p->OptionAdd(grid, _("Comment background"), "Colour/Subtitle Grid/Background/Comment");
|
||||||
p->OptionAdd(grid, _("Selected comment background"), "Colour/Subtitle Grid/Background/Selected Comment");
|
p->OptionAdd(grid, _("Selected comment background"), "Colour/Subtitle Grid/Background/Selected Comment");
|
||||||
|
p->OptionAdd(grid, _("Open fold background"), "Colour/Subtitle Grid/Background/Open Fold");
|
||||||
|
p->OptionAdd(grid, _("Closed fold background"), "Colour/Subtitle Grid/Background/Closed Fold");
|
||||||
p->OptionAdd(grid, _("Header background"), "Colour/Subtitle Grid/Header");
|
p->OptionAdd(grid, _("Header background"), "Colour/Subtitle Grid/Header");
|
||||||
p->OptionAdd(grid, _("Left Column"), "Colour/Subtitle Grid/Left Column");
|
p->OptionAdd(grid, _("Left Column"), "Colour/Subtitle Grid/Left Column");
|
||||||
p->OptionAdd(grid, _("Active Line Border"), "Colour/Subtitle Grid/Active Border");
|
p->OptionAdd(grid, _("Active Line Border"), "Colour/Subtitle Grid/Active Border");
|
||||||
|
|
|
@ -108,6 +108,20 @@ struct Writer {
|
||||||
WriteIfNotZero("Scroll Position: ", properties.scroll_position);
|
WriteIfNotZero("Scroll Position: ", properties.scroll_position);
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue