Store selection and active line in undo info and restore them on undo
This commit is contained in:
parent
9ecb54333a
commit
c3fb54153f
6 changed files with 109 additions and 99 deletions
|
@ -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;
|
||||||
|
|
|
@ -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,44 +272,19 @@ void BaseGrid::UpdateMaps(bool preserve_selected_rows) {
|
||||||
index_line_map.push_back(curdiag);
|
index_line_map.push_back(curdiag);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preserve_selected_rows) {
|
auto sorted = index_line_map;
|
||||||
Selection sel;
|
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
|
SetSelectedSet(new_sel);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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();
|
||||||
|
|
|
@ -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(); }
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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,19 +60,23 @@ 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;
|
||||||
case AssEntryGroup::STYLE: ++style_count; break;
|
case AssEntryGroup::STYLE: ++style_count; break;
|
||||||
case AssEntryGroup::FONT: ++font_count; break;
|
case AssEntryGroup::FONT: ++font_count; break;
|
||||||
case AssEntryGroup::GRAPHIC: ++graphics_count; break;
|
case AssEntryGroup::GRAPHIC: ++graphics_count; break;
|
||||||
default: assert(false); break;
|
default: assert(false); 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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue