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;
}
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;

View file

@ -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<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();
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();

View file

@ -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(); }

View file

@ -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));

View file

@ -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<AssAttachment> graphics;
std::vector<AssAttachment> fonts;
mutable std::vector<int> 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<AssDialogue const&>(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<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) {
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<int>(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 {

View file

@ -22,15 +22,19 @@
#include <set>
#include <wx/timer.h>
class AssDialogue;
class AssEntry;
class AssFile;
struct AssFileCommit;
template<typename T> 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<UndoInfo> 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<AssDialogue *> *selection_controller);
/// The file's path and filename if any, or platform-appropriate "untitled"
agi::fs::path Filename() const;