diff --git a/aegisub/src/ass_dialogue.cpp b/aegisub/src/ass_dialogue.cpp index 95f3b8e73..6954fb974 100644 --- a/aegisub/src/ass_dialogue.cpp +++ b/aegisub/src/ass_dialogue.cpp @@ -61,9 +61,7 @@ AssDialogue::AssDialogue(AssDialogue const& that) : AssDialogueBase(that) { Id = ++next_id; } -AssDialogue::AssDialogue(AssDialogueBase const& that) : AssDialogueBase(that) { - Id = ++next_id; -} +AssDialogue::AssDialogue(AssDialogueBase const& that) : AssDialogueBase(that) { } AssDialogue::AssDialogue(std::string const& data) { Id = ++next_id; diff --git a/aegisub/src/base_grid.cpp b/aegisub/src/base_grid.cpp index 669b91dda..92c6f0963 100644 --- a/aegisub/src/base_grid.cpp +++ b/aegisub/src/base_grid.cpp @@ -141,9 +141,7 @@ BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context, const wxSize& size, Bind(wxEVT_CONTEXT_MENU, &BaseGrid::OnContextMenu, this); } -BaseGrid::~BaseGrid() { - ClearMaps(); -} +BaseGrid::~BaseGrid() { } BEGIN_EVENT_TABLE(BaseGrid,wxWindow) EVT_PAINT(BaseGrid::OnPaint) @@ -156,10 +154,8 @@ BEGIN_EVENT_TABLE(BaseGrid,wxWindow) END_EVENT_TABLE() void BaseGrid::OnSubtitlesCommit(int type) { - if (type == AssFile::COMMIT_NEW) - UpdateMaps(true); - else if (type & AssFile::COMMIT_ORDER || type & AssFile::COMMIT_DIAG_ADDREM) - UpdateMaps(false); + if (type == AssFile::COMMIT_NEW || type & AssFile::COMMIT_ORDER || type & AssFile::COMMIT_DIAG_ADDREM) + UpdateMaps(); if (type & AssFile::COMMIT_DIAG_META) { SetColumnWidths(); @@ -264,17 +260,10 @@ void BaseGrid::ClearMaps() { AnnounceSelectedSetChanged(Selection(), old_selection); } -void BaseGrid::UpdateMaps(bool preserve_selected_rows) { +void BaseGrid::UpdateMaps() { BeginBatch(); int active_row = line_index_map[active_line]; - std::vector sel_rows; - if (preserve_selected_rows) { - sel_rows.reserve(selection.size()); - transform(selection.begin(), selection.end(), back_inserter(sel_rows), - [this](AssDialogue *diag) { return GetDialogueIndex(diag); }); - } - index_line_map.clear(); line_index_map.clear(); @@ -283,44 +272,19 @@ void BaseGrid::UpdateMaps(bool preserve_selected_rows) { index_line_map.push_back(curdiag); } - if (preserve_selected_rows) { - Selection sel; + auto sorted = index_line_map; + sort(begin(sorted), end(sorted)); + Selection new_sel; + // Remove lines which no longer exist from the selection + set_intersection(selection.begin(), selection.end(), + sorted.begin(), sorted.end(), + inserter(new_sel, new_sel.begin())); - // If the file shrank enough that no selected rows are left, select the - // last row - if (sel_rows.empty()) - sel_rows.push_back(index_line_map.size() - 1); - else if (sel_rows[0] >= (int)index_line_map.size()) - sel_rows[0] = index_line_map.size() - 1; - - for (int row : sel_rows) { - if (row >= (int)index_line_map.size()) break; - sel.insert(index_line_map[row]); - } - - SetSelectedSet(sel); - } - else { - auto sorted = index_line_map; - sort(begin(sorted), end(sorted)); - Selection new_sel; - // Remove lines which no longer exist from the selection - set_intersection(selection.begin(), selection.end(), - sorted.begin(), sorted.end(), - inserter(new_sel, new_sel.begin())); - - SetSelectedSet(new_sel); - } + SetSelectedSet(new_sel); // The active line may have ceased to exist; pick a new one if so - if (line_index_map.size() && !line_index_map.count(active_line)) { - if (active_row < (int)index_line_map.size()) - SetActiveLine(index_line_map[active_row]); - else if (preserve_selected_rows && !selection.empty()) - SetActiveLine(index_line_map[sel_rows[0]]); - else - SetActiveLine(index_line_map.back()); - } + if (line_index_map.size() && !line_index_map.count(active_line)) + SetActiveLine(index_line_map[std::min((size_t)active_row, index_line_map.size() - 1)]); if (selection.empty() && active_line) SetSelectedSet({ active_line }); @@ -394,15 +358,6 @@ void BaseGrid::SelectRow(int row, bool addToSelected, bool select) { RefreshRect(wxRect(0, (row + 1 - yPos) * lineHeight, w, lineHeight), false); } -wxArrayInt BaseGrid::GetSelection() const { - wxArrayInt res; - res.reserve(selection.size()); - transform(selection.begin(), selection.end(), std::back_inserter(res), - std::bind(&BaseGrid::GetDialogueIndex, this, std::placeholders::_1)); - std::sort(res.begin(), res.end()); - return res; -} - void BaseGrid::OnPaint(wxPaintEvent &) { // Get size and pos wxSize cs = GetClientSize(); diff --git a/aegisub/src/base_grid.h b/aegisub/src/base_grid.h index 2d829fd04..76cbf982a 100644 --- a/aegisub/src/base_grid.h +++ b/aegisub/src/base_grid.h @@ -142,13 +142,10 @@ public: void SetByFrame(bool state); void SelectRow(int row, bool addToSelected = false, bool select=true); - wxArrayInt GetSelection() const; void ClearMaps(); /// @brief Update the row <-> AssDialogue mappings - /// @param preserve_selected_rows Try to keep the same rows selected rather - /// rather than the same lines - void UpdateMaps(bool preserve_selected_rows = false); + void UpdateMaps(); void UpdateStyle(); int GetRows() const { return index_line_map.size(); } diff --git a/aegisub/src/frame_main.cpp b/aegisub/src/frame_main.cpp index 84a53b7e7..c3c7ff0c9 100644 --- a/aegisub/src/frame_main.cpp +++ b/aegisub/src/frame_main.cpp @@ -264,6 +264,7 @@ FrameMain::FrameMain() StartupLog("Complete context initialization"); context->videoController->SetContext(context.get()); + context->subsController->SetSelectionController(context->selectionController); StartupLog("Set up drag/drop target"); SetDropTarget(new AegisubFileDropTarget(this)); diff --git a/aegisub/src/subs_controller.cpp b/aegisub/src/subs_controller.cpp index c38baf13a..b398aefb4 100644 --- a/aegisub/src/subs_controller.cpp +++ b/aegisub/src/subs_controller.cpp @@ -23,11 +23,13 @@ #include "ass_file.h" #include "ass_info.h" #include "ass_style.h" +#include "base_grid.h" #include "charset_detect.h" #include "compat.h" #include "command/command.h" #include "include/aegisub/context.h" #include "options.h" +#include "selection_controller.h" #include "subtitle_format.h" #include "text_file_reader.h" #include "utils.h" @@ -58,19 +60,23 @@ struct SubsController::UndoInfo { std::vector graphics; std::vector fonts; + mutable std::vector selection; + int active_line_id = 0; + wxString undo_description; int commit_id; - UndoInfo(AssFile const& f, wxString const& d, int c) - : undo_description(d), commit_id(c) + + UndoInfo(const agi::Context *c, wxString const& d, int commit_id) + : undo_description(d), commit_id(commit_id) { size_t info_count = 0, style_count = 0, event_count = 0, font_count = 0, graphics_count = 0; - for (auto const& line : f.Line) { + for (auto const& line : c->ass->Line) { switch (line.Group()) { - case AssEntryGroup::DIALOGUE: ++event_count; break; - case AssEntryGroup::INFO: ++info_count; break; - case AssEntryGroup::STYLE: ++style_count; break; - case AssEntryGroup::FONT: ++font_count; break; - case AssEntryGroup::GRAPHIC: ++graphics_count; break; + case AssEntryGroup::DIALOGUE: ++event_count; break; + case AssEntryGroup::INFO: ++info_count; break; + case AssEntryGroup::STYLE: ++style_count; break; + case AssEntryGroup::FONT: ++font_count; break; + case AssEntryGroup::GRAPHIC: ++graphics_count; break; default: assert(false); break; } } @@ -79,7 +85,7 @@ struct SubsController::UndoInfo { styles.reserve(style_count); events.reserve(event_count); - for (auto const& line : f.Line) { + for (auto const& line : c->ass->Line) { switch (line.Group()) { case AssEntryGroup::DIALOGUE: events.push_back(static_cast(line)); @@ -103,21 +109,57 @@ struct SubsController::UndoInfo { break; } } + + UpdateActiveLine(c); + UpdateSelection(c); } - operator AssFile() const { - AssFile ret; + void Apply(agi::Context *c) const { + // Keep old lines alive until after the commit is complete + AssFile old; + old.swap(*c->ass); + + sort(begin(selection), end(selection)); + + AssDialogue *active_line = nullptr; + SubtitleSelection new_sel; + for (auto const& info : script_info) - ret.Line.push_back(*new AssInfo(info.first, info.second)); + c->ass->Line.push_back(*new AssInfo(info.first, info.second)); for (auto const& style : styles) - ret.Line.push_back(*new AssStyle(style)); - for (auto const& event : events) - ret.Line.push_back(*new AssDialogue(event)); + c->ass->Line.push_back(*new AssStyle(style)); + for (auto const& event : events) { + auto copy = new AssDialogue(event); + c->ass->Line.push_back(*copy); + if (copy->Id == active_line_id) + active_line = copy; + if (binary_search(begin(selection), end(selection), copy->Id)) + new_sel.insert(copy); + } for (auto const& attachment : graphics) - ret.Line.push_back(*new AssAttachment(attachment)); + c->ass->Line.push_back(*new AssAttachment(attachment)); for (auto const& attachment : fonts) - ret.Line.push_back(*new AssAttachment(attachment)); - return ret; + c->ass->Line.push_back(*new AssAttachment(attachment)); + + c->subsGrid->BeginBatch(); + c->selectionController->SetSelectedSet({ }); + c->ass->Commit("", AssFile::COMMIT_NEW); + c->selectionController->SetSelectionAndActive(new_sel, active_line); + c->subsGrid->EndBatch(); + } + + void UpdateActiveLine(const agi::Context *c) { + auto line = c->selectionController->GetActiveLine(); + if (line) + active_line_id = line->Id; + } + + void UpdateSelection(const agi::Context *c) { + auto const& sel = c->selectionController->GetSelectedSet(); + selection.clear(); + selection.reserve(sel.size()); + for (const auto diag : sel) + selection.push_back(diag->Id); } }; @@ -144,6 +186,11 @@ SubsController::SubsController(agi::Context *context) }); } +void SubsController::SetSelectionController(SelectionController *selection_controller) { + active_line_connection = context->selectionController->AddActiveLineListener(&SubsController::OnActiveLineChanged, this); + selection_connection = context->selectionController->AddSelectionListener(&SubsController::OnSelectionChanged, this); +} + void SubsController::Load(agi::fs::path const& filename, std::string charset) { try { try { @@ -357,7 +404,7 @@ void SubsController::OnCommit(AssFileCommit c) { redo_stack.clear(); - undo_stack.emplace_back(*context->ass, c.message, commit_id); + undo_stack.emplace_back(context, c.message, commit_id); int depth = std::max(OPT_GET("Limits/Undo Levels")->GetInt(), 2); while ((int)undo_stack.size() > depth) @@ -369,27 +416,30 @@ void SubsController::OnCommit(AssFileCommit c) { *c.commit_id = commit_id; } -void SubsController::ApplyUndo() { - // Keep old lines alive until after the commit is complete - AssFile old; - old.swap(*context->ass); +void SubsController::OnActiveLineChanged() { + if (!undo_stack.empty()) + undo_stack.back().UpdateActiveLine(context); +} - *context->ass = undo_stack.back(); - commit_id = undo_stack.back().commit_id; - - context->ass->Commit("", AssFile::COMMIT_NEW); +void SubsController::OnSelectionChanged() { + if (!undo_stack.empty()) + undo_stack.back().UpdateSelection(context); } void SubsController::Undo() { if (undo_stack.size() <= 1) return; redo_stack.splice(redo_stack.end(), undo_stack, std::prev(undo_stack.end())); - ApplyUndo(); + + commit_id = undo_stack.back().commit_id; + undo_stack.back().Apply(context); } void SubsController::Redo() { if (redo_stack.empty()) return; undo_stack.splice(undo_stack.end(), redo_stack, std::prev(redo_stack.end())); - ApplyUndo(); + + commit_id = undo_stack.back().commit_id; + undo_stack.back().Apply(context); } wxString SubsController::GetUndoDescription() const { diff --git a/aegisub/src/subs_controller.h b/aegisub/src/subs_controller.h index c2a179fa0..f0c5fbd99 100644 --- a/aegisub/src/subs_controller.h +++ b/aegisub/src/subs_controller.h @@ -22,15 +22,19 @@ #include #include +class AssDialogue; class AssEntry; class AssFile; struct AssFileCommit; +template class SelectionController; namespace agi { struct Context; } class SubsController { agi::Context *context; agi::signal::Connection undo_connection; + agi::signal::Connection active_line_connection; + agi::signal::Connection selection_connection; struct UndoInfo; boost::container::list undo_stack; @@ -57,17 +61,22 @@ class SubsController { /// The filename of the currently open file, if any agi::fs::path filename; - void OnCommit(AssFileCommit c); - /// Set the filename, updating things like the MRU and last used path void SetFileName(agi::fs::path const& file); - /// Set the current file to the file on top of the undo stack - void ApplyUndo(); + void OnCommit(AssFileCommit c); + void OnActiveLineChanged(); + void OnSelectionChanged(); public: SubsController(agi::Context *context); + /// Set the selection controller to use + /// + /// Required due to that the selection controller is the subtitles grid, and + /// so is created long after the subtitles controller + void SetSelectionController(SelectionController *selection_controller); + /// The file's path and filename if any, or platform-appropriate "untitled" agi::fs::path Filename() const;