mirror of https://github.com/odrling/Aegisub
Store selection and active line in undo info and restore them on undo
This commit is contained in:
parent
9ecb54333a
commit
c3fb54153f
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in New Issue