Store selection and active line in undo info and restore them on undo

This commit is contained in:
Thomas Goyne 2014-03-04 17:36:02 -08:00
parent 9ecb54333a
commit c3fb54153f
6 changed files with 109 additions and 99 deletions

View file

@ -61,9 +61,7 @@ AssDialogue::AssDialogue(AssDialogue const& that) : AssDialogueBase(that) {
Id = ++next_id; Id = ++next_id;
} }
AssDialogue::AssDialogue(AssDialogueBase const& that) : AssDialogueBase(that) { AssDialogue::AssDialogue(AssDialogueBase const& that) : AssDialogueBase(that) { }
Id = ++next_id;
}
AssDialogue::AssDialogue(std::string const& data) { AssDialogue::AssDialogue(std::string const& data) {
Id = ++next_id; Id = ++next_id;

View file

@ -141,9 +141,7 @@ BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context, const wxSize& size,
Bind(wxEVT_CONTEXT_MENU, &BaseGrid::OnContextMenu, this); Bind(wxEVT_CONTEXT_MENU, &BaseGrid::OnContextMenu, this);
} }
BaseGrid::~BaseGrid() { BaseGrid::~BaseGrid() { }
ClearMaps();
}
BEGIN_EVENT_TABLE(BaseGrid,wxWindow) BEGIN_EVENT_TABLE(BaseGrid,wxWindow)
EVT_PAINT(BaseGrid::OnPaint) EVT_PAINT(BaseGrid::OnPaint)
@ -156,10 +154,8 @@ 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) if (type == AssFile::COMMIT_NEW || type & AssFile::COMMIT_ORDER || type & AssFile::COMMIT_DIAG_ADDREM)
UpdateMaps(true); UpdateMaps();
else if (type & AssFile::COMMIT_ORDER || type & AssFile::COMMIT_DIAG_ADDREM)
UpdateMaps(false);
if (type & AssFile::COMMIT_DIAG_META) { if (type & AssFile::COMMIT_DIAG_META) {
SetColumnWidths(); SetColumnWidths();
@ -264,17 +260,10 @@ void BaseGrid::ClearMaps() {
AnnounceSelectedSetChanged(Selection(), old_selection); AnnounceSelectedSetChanged(Selection(), old_selection);
} }
void BaseGrid::UpdateMaps(bool preserve_selected_rows) { void BaseGrid::UpdateMaps() {
BeginBatch(); BeginBatch();
int active_row = line_index_map[active_line]; int active_row = line_index_map[active_line];
std::vector<int> 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(); index_line_map.clear();
line_index_map.clear(); line_index_map.clear();
@ -283,24 +272,6 @@ void BaseGrid::UpdateMaps(bool preserve_selected_rows) {
index_line_map.push_back(curdiag); index_line_map.push_back(curdiag);
} }
if (preserve_selected_rows) {
Selection sel;
// 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; auto sorted = index_line_map;
sort(begin(sorted), end(sorted)); sort(begin(sorted), end(sorted));
Selection new_sel; Selection new_sel;
@ -310,17 +281,10 @@ void BaseGrid::UpdateMaps(bool preserve_selected_rows) {
inserter(new_sel, new_sel.begin())); 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 // 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 (line_index_map.size() && !line_index_map.count(active_line))
if (active_row < (int)index_line_map.size()) SetActiveLine(index_line_map[std::min((size_t)active_row, index_line_map.size() - 1)]);
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 (selection.empty() && active_line) if (selection.empty() && active_line)
SetSelectedSet({ 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); 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 &) { void BaseGrid::OnPaint(wxPaintEvent &) {
// Get size and pos // Get size and pos
wxSize cs = GetClientSize(); wxSize cs = GetClientSize();

View file

@ -142,13 +142,10 @@ public:
void SetByFrame(bool state); void SetByFrame(bool state);
void SelectRow(int row, bool addToSelected = false, bool select=true); void SelectRow(int row, bool addToSelected = false, bool select=true);
wxArrayInt GetSelection() const;
void ClearMaps(); void ClearMaps();
/// @brief Update the row <-> AssDialogue mappings /// @brief Update the row <-> AssDialogue mappings
/// @param preserve_selected_rows Try to keep the same rows selected rather void UpdateMaps();
/// rather than the same lines
void UpdateMaps(bool preserve_selected_rows = false);
void UpdateStyle(); void UpdateStyle();
int GetRows() const { return index_line_map.size(); } int GetRows() const { return index_line_map.size(); }

View file

@ -264,6 +264,7 @@ FrameMain::FrameMain()
StartupLog("Complete context initialization"); StartupLog("Complete context initialization");
context->videoController->SetContext(context.get()); context->videoController->SetContext(context.get());
context->subsController->SetSelectionController(context->selectionController);
StartupLog("Set up drag/drop target"); StartupLog("Set up drag/drop target");
SetDropTarget(new AegisubFileDropTarget(this)); SetDropTarget(new AegisubFileDropTarget(this));

View file

@ -23,11 +23,13 @@
#include "ass_file.h" #include "ass_file.h"
#include "ass_info.h" #include "ass_info.h"
#include "ass_style.h" #include "ass_style.h"
#include "base_grid.h"
#include "charset_detect.h" #include "charset_detect.h"
#include "compat.h" #include "compat.h"
#include "command/command.h" #include "command/command.h"
#include "include/aegisub/context.h" #include "include/aegisub/context.h"
#include "options.h" #include "options.h"
#include "selection_controller.h"
#include "subtitle_format.h" #include "subtitle_format.h"
#include "text_file_reader.h" #include "text_file_reader.h"
#include "utils.h" #include "utils.h"
@ -58,13 +60,17 @@ struct SubsController::UndoInfo {
std::vector<AssAttachment> graphics; std::vector<AssAttachment> graphics;
std::vector<AssAttachment> fonts; std::vector<AssAttachment> fonts;
mutable std::vector<int> selection;
int active_line_id = 0;
wxString undo_description; wxString undo_description;
int commit_id; 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; 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()) { switch (line.Group()) {
case AssEntryGroup::DIALOGUE: ++event_count; break; case AssEntryGroup::DIALOGUE: ++event_count; break;
case AssEntryGroup::INFO: ++info_count; break; case AssEntryGroup::INFO: ++info_count; break;
@ -79,7 +85,7 @@ struct SubsController::UndoInfo {
styles.reserve(style_count); styles.reserve(style_count);
events.reserve(event_count); events.reserve(event_count);
for (auto const& line : f.Line) { for (auto const& line : c->ass->Line) {
switch (line.Group()) { switch (line.Group()) {
case AssEntryGroup::DIALOGUE: case AssEntryGroup::DIALOGUE:
events.push_back(static_cast<AssDialogue const&>(line)); events.push_back(static_cast<AssDialogue const&>(line));
@ -103,21 +109,57 @@ struct SubsController::UndoInfo {
break; break;
} }
} }
UpdateActiveLine(c);
UpdateSelection(c);
} }
operator AssFile() const { void Apply(agi::Context *c) const {
AssFile ret; // 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) 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) for (auto const& style : styles)
ret.Line.push_back(*new AssStyle(style)); c->ass->Line.push_back(*new AssStyle(style));
for (auto const& event : events) for (auto const& event : events) {
ret.Line.push_back(*new AssDialogue(event)); 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) 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) for (auto const& attachment : fonts)
ret.Line.push_back(*new AssAttachment(attachment)); c->ass->Line.push_back(*new AssAttachment(attachment));
return ret;
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<AssDialogue *> *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) { void SubsController::Load(agi::fs::path const& filename, std::string charset) {
try { try {
try { try {
@ -357,7 +404,7 @@ void SubsController::OnCommit(AssFileCommit c) {
redo_stack.clear(); 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<int>(OPT_GET("Limits/Undo Levels")->GetInt(), 2); int depth = std::max<int>(OPT_GET("Limits/Undo Levels")->GetInt(), 2);
while ((int)undo_stack.size() > depth) while ((int)undo_stack.size() > depth)
@ -369,27 +416,30 @@ void SubsController::OnCommit(AssFileCommit c) {
*c.commit_id = commit_id; *c.commit_id = commit_id;
} }
void SubsController::ApplyUndo() { void SubsController::OnActiveLineChanged() {
// Keep old lines alive until after the commit is complete if (!undo_stack.empty())
AssFile old; undo_stack.back().UpdateActiveLine(context);
old.swap(*context->ass); }
*context->ass = undo_stack.back(); void SubsController::OnSelectionChanged() {
commit_id = undo_stack.back().commit_id; if (!undo_stack.empty())
undo_stack.back().UpdateSelection(context);
context->ass->Commit("", AssFile::COMMIT_NEW);
} }
void SubsController::Undo() { void SubsController::Undo() {
if (undo_stack.size() <= 1) return; if (undo_stack.size() <= 1) return;
redo_stack.splice(redo_stack.end(), undo_stack, std::prev(undo_stack.end())); 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() { void SubsController::Redo() {
if (redo_stack.empty()) return; if (redo_stack.empty()) return;
undo_stack.splice(undo_stack.end(), redo_stack, std::prev(redo_stack.end())); 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 { wxString SubsController::GetUndoDescription() const {

View file

@ -22,15 +22,19 @@
#include <set> #include <set>
#include <wx/timer.h> #include <wx/timer.h>
class AssDialogue;
class AssEntry; class AssEntry;
class AssFile; class AssFile;
struct AssFileCommit; struct AssFileCommit;
template<typename T> class SelectionController;
namespace agi { struct Context; } namespace agi { struct Context; }
class SubsController { class SubsController {
agi::Context *context; agi::Context *context;
agi::signal::Connection undo_connection; agi::signal::Connection undo_connection;
agi::signal::Connection active_line_connection;
agi::signal::Connection selection_connection;
struct UndoInfo; struct UndoInfo;
boost::container::list<UndoInfo> undo_stack; boost::container::list<UndoInfo> undo_stack;
@ -57,17 +61,22 @@ class SubsController {
/// The filename of the currently open file, if any /// The filename of the currently open file, if any
agi::fs::path filename; agi::fs::path filename;
void OnCommit(AssFileCommit c);
/// Set the filename, updating things like the MRU and last used path /// Set the filename, updating things like the MRU and last used path
void SetFileName(agi::fs::path const& file); void SetFileName(agi::fs::path const& file);
/// Set the current file to the file on top of the undo stack void OnCommit(AssFileCommit c);
void ApplyUndo(); void OnActiveLineChanged();
void OnSelectionChanged();
public: public:
SubsController(agi::Context *context); 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<AssDialogue *> *selection_controller);
/// The file's path and filename if any, or platform-appropriate "untitled" /// The file's path and filename if any, or platform-appropriate "untitled"
agi::fs::path Filename() const; agi::fs::path Filename() const;