Extract SelectionController from BaseGrid

This commit is contained in:
Thomas Goyne 2014-03-24 17:15:14 -07:00
parent eb548306e9
commit 523d858374
34 changed files with 246 additions and 363 deletions

View file

@ -390,6 +390,7 @@
<ClCompile Include="$(SrcDir)scintilla_text_ctrl.cpp" /> <ClCompile Include="$(SrcDir)scintilla_text_ctrl.cpp" />
<ClCompile Include="$(SrcDir)scintilla_text_selection_controller.cpp" /> <ClCompile Include="$(SrcDir)scintilla_text_selection_controller.cpp" />
<ClCompile Include="$(SrcDir)search_replace_engine.cpp" /> <ClCompile Include="$(SrcDir)search_replace_engine.cpp" />
<ClCompile Include="$(SrcDir)selection_controller.cpp" />
<ClCompile Include="$(SrcDir)spellchecker.cpp" /> <ClCompile Include="$(SrcDir)spellchecker.cpp" />
<ClCompile Include="$(SrcDir)spellchecker_hunspell.cpp" /> <ClCompile Include="$(SrcDir)spellchecker_hunspell.cpp" />
<ClCompile Include="$(SrcDir)spline.cpp" /> <ClCompile Include="$(SrcDir)spline.cpp" />

View file

@ -1001,6 +1001,9 @@
<ClCompile Include="$(SrcDir)base_grid.cpp"> <ClCompile Include="$(SrcDir)base_grid.cpp">
<Filter>Main UI\Grid</Filter> <Filter>Main UI\Grid</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="$(SrcDir)selection_controller.cpp">
<Filter>Main UI\Grid</Filter>
</ClCompile>
<ClCompile Include="$(SrcDir)scintilla_text_ctrl.cpp"> <ClCompile Include="$(SrcDir)scintilla_text_ctrl.cpp">
<Filter>Main UI\Edit box</Filter> <Filter>Main UI\Edit box</Filter>
</ClCompile> </ClCompile>

View file

@ -209,6 +209,7 @@ SRC += \
scintilla_text_ctrl.cpp \ scintilla_text_ctrl.cpp \
scintilla_text_selection_controller.cpp \ scintilla_text_selection_controller.cpp \
search_replace_engine.cpp \ search_replace_engine.cpp \
selection_controller.cpp \
spellchecker.cpp \ spellchecker.cpp \
spline.cpp \ spline.cpp \
spline_curve.cpp \ spline_curve.cpp \

View file

@ -277,7 +277,7 @@ void AssKaraoke::SplitLines(std::set<AssDialogue*> const& lines, agi::Context *c
AssKaraoke kara; AssKaraoke kara;
SubtitleSelection sel = c->selectionController->GetSelectedSet(); Selection sel = c->selectionController->GetSelectedSet();
bool did_split = false; bool did_split = false;
for (auto it = c->ass->Events.begin(); it != c->ass->Events.end(); ++it) { for (auto it = c->ass->Events.begin(); it != c->ass->Events.end(); ++it) {

View file

@ -38,7 +38,6 @@ class AudioRenderingStyleRanges;
namespace agi { struct Context; } namespace agi { struct Context; }
#include "audio_marker.h" #include "audio_marker.h"
#include "selection_controller.h"
/// @class AudioTimingController /// @class AudioTimingController
/// @brief Base class for objects controlling audio timing /// @brief Base class for objects controlling audio timing

View file

@ -348,7 +348,7 @@ class AudioTimingControllerDialogue final : public AudioTimingController {
void RegenerateSelectedLines(); void RegenerateSelectedLines();
/// Add a line to the list of timeable inactive lines /// Add a line to the list of timeable inactive lines
void AddInactiveLine(SubtitleSelection const& sel, AssDialogue *diag); void AddInactiveLine(Selection const& sel, AssDialogue *diag);
/// Regenerate the list of active and inactive line markers /// Regenerate the list of active and inactive line markers
void RegenerateMarkers(); void RegenerateMarkers();
@ -727,7 +727,7 @@ void AudioTimingControllerDialogue::RegenerateInactiveLines()
bool was_empty = inactive_lines.empty(); bool was_empty = inactive_lines.empty();
inactive_lines.clear(); inactive_lines.clear();
SubtitleSelection const& sel = context->selectionController->GetSelectedSet(); auto const& sel = context->selectionController->GetSelectedSet();
switch (int mode = inactive_line_mode->GetInt()) switch (int mode = inactive_line_mode->GetInt())
{ {
@ -778,7 +778,7 @@ void AudioTimingControllerDialogue::RegenerateInactiveLines()
RegenerateMarkers(); RegenerateMarkers();
} }
void AudioTimingControllerDialogue::AddInactiveLine(SubtitleSelection const& sel, AssDialogue *diag) void AudioTimingControllerDialogue::AddInactiveLine(Selection const& sel, AssDialogue *diag)
{ {
if (sel.count(diag)) return; if (sel.count(diag)) return;

View file

@ -33,6 +33,7 @@
#include "include/aegisub/context.h" #include "include/aegisub/context.h"
#include "options.h" #include "options.h"
#include "pen.h" #include "pen.h"
#include "selection_controller.h"
#include "utils.h" #include "utils.h"
#include <deque> #include <deque>

View file

@ -834,7 +834,7 @@ namespace Automation4 {
static int transform_selection(lua_State *L, const agi::Context *c) static int transform_selection(lua_State *L, const agi::Context *c)
{ {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet(); auto const& sel = c->selectionController->GetSelectedSet();
AssDialogue *active_line = c->selectionController->GetActiveLine(); AssDialogue *active_line = c->selectionController->GetActiveLine();
lua_newtable(L); lua_newtable(L);

View file

@ -48,6 +48,7 @@
#include "frame_main.h" #include "frame_main.h"
#include "options.h" #include "options.h"
#include "utils.h" #include "utils.h"
#include "selection_controller.h"
#include "subs_controller.h" #include "subs_controller.h"
#include "video_context.h" #include "video_context.h"
#include "video_slider.h" #include "video_slider.h"
@ -89,8 +90,8 @@ namespace std {
}; };
} }
BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context, const wxSize& size, long style, const wxString& name) BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
: wxWindow(parent, -1, wxDefaultPosition, size, style, name) : wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxSUNKEN_BORDER)
, scrollBar(new wxScrollBar(this, GRID_SCROLLBAR, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL)) , scrollBar(new wxScrollBar(this, GRID_SCROLLBAR, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL))
, seek_listener(context->videoController->AddSeekListener(std::bind(&BaseGrid::Refresh, this, false, nullptr))) , seek_listener(context->videoController->AddSeekListener(std::bind(&BaseGrid::Refresh, this, false, nullptr)))
, context(context) , context(context)
@ -108,26 +109,30 @@ BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context, const wxSize& size,
UpdateStyle(); UpdateStyle();
OnHighlightVisibleChange(*OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame")); OnHighlightVisibleChange(*OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame"));
OPT_SUB("Subtitle/Grid/Font Face", &BaseGrid::UpdateStyle, this); connections.push_back(context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this));
OPT_SUB("Subtitle/Grid/Font Size", &BaseGrid::UpdateStyle, this); connections.push_back(context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this));
OPT_SUB("Subtitle/Grid/Highlight Subtitles in Frame", &BaseGrid::OnHighlightVisibleChange, this); connections.push_back(context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this));
context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this);
context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this);
context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this);
OPT_SUB("Colour/Subtitle Grid/Active Border", &BaseGrid::UpdateStyle, this); connections.push_back(context->selectionController->AddActiveLineListener(&BaseGrid::OnActiveLineChanged, this));
OPT_SUB("Colour/Subtitle Grid/Background/Background", &BaseGrid::UpdateStyle, this); connections.push_back(context->selectionController->AddSelectionListener([&]{ Refresh(false); }));
OPT_SUB("Colour/Subtitle Grid/Background/Comment", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this); connections.push_back(OPT_SUB("Subtitle/Grid/Font Face", &BaseGrid::UpdateStyle, this));
OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this); connections.push_back(OPT_SUB("Subtitle/Grid/Font Size", &BaseGrid::UpdateStyle, this));
OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this); connections.push_back(OPT_SUB("Colour/Subtitle Grid/Active Border", &BaseGrid::UpdateStyle, this));
OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this); connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Background", &BaseGrid::UpdateStyle, this));
OPT_SUB("Colour/Subtitle Grid/Header", &BaseGrid::UpdateStyle, this); connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Comment", &BaseGrid::UpdateStyle, this));
OPT_SUB("Colour/Subtitle Grid/Left Column", &BaseGrid::UpdateStyle, this); connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this));
OPT_SUB("Colour/Subtitle Grid/Lines", &BaseGrid::UpdateStyle, this); connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this));
OPT_SUB("Colour/Subtitle Grid/Selection", &BaseGrid::UpdateStyle, this); connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this));
OPT_SUB("Colour/Subtitle Grid/Standard", &BaseGrid::UpdateStyle, this); connections.push_back(OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this));
OPT_SUB("Subtitle/Grid/Hide Overrides", std::bind(&BaseGrid::Refresh, this, false, nullptr)); connections.push_back(OPT_SUB("Colour/Subtitle Grid/Header", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Left Column", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Lines", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Selection", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Standard", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Subtitle/Grid/Highlight Subtitles in Frame", &BaseGrid::OnHighlightVisibleChange, this));
connections.push_back(OPT_SUB("Subtitle/Grid/Hide Overrides", [&](agi::OptionValue const&) { Refresh(false); }));
Bind(wxEVT_CONTEXT_MENU, &BaseGrid::OnContextMenu, this); Bind(wxEVT_CONTEXT_MENU, &BaseGrid::OnContextMenu, this);
} }
@ -155,34 +160,16 @@ void BaseGrid::OnSubtitlesCommit(int type) {
} }
if (type & AssFile::COMMIT_DIAG_TIME) if (type & AssFile::COMMIT_DIAG_TIME)
Refresh(false); Refresh(false);
//RefreshRect(wxRect(time_cols_x, 0, time_cols_w, GetClientSize().GetHeight()), false);
else if (type & AssFile::COMMIT_DIAG_TEXT) else if (type & AssFile::COMMIT_DIAG_TEXT)
RefreshRect(wxRect(text_col_x, 0, text_col_w, GetClientSize().GetHeight()), false); RefreshRect(wxRect(text_col_x, 0, text_col_w, GetClientSize().GetHeight()), false);
} }
void BaseGrid::OnSubtitlesOpen() { void BaseGrid::OnSubtitlesOpen() {
BeginBatch();
ClearMaps();
UpdateMaps();
if (GetRows()) {
int row = context->ass->GetUIStateAsInt("Active Line");
if (row < 0 || row >= GetRows())
row = 0;
SetActiveLine(GetDialogue(row));
SelectRow(row);
}
ScrollTo(context->ass->GetUIStateAsInt("Scroll Position")); ScrollTo(context->ass->GetUIStateAsInt("Scroll Position"));
EndBatch();
SetColumnWidths();
} }
void BaseGrid::OnSubtitlesSave() { void BaseGrid::OnSubtitlesSave() {
context->ass->SaveUIState("Scroll Position", std::to_string(yPos)); context->ass->SaveUIState("Scroll Position", std::to_string(yPos));
context->ass->SaveUIState("Active Line", std::to_string(GetDialogueIndex(active_line)));
} }
void BaseGrid::OnShowColMenu(wxCommandEvent &event) { void BaseGrid::OnShowColMenu(wxCommandEvent &event) {
@ -239,20 +226,7 @@ void BaseGrid::UpdateStyle() {
Refresh(false); Refresh(false);
} }
void BaseGrid::ClearMaps() {
index_line_map.clear();
line_index_map.clear();
selection.clear();
yPos = 0;
AdjustScrollbar();
AnnounceSelectedSetChanged();
}
void BaseGrid::UpdateMaps() { void BaseGrid::UpdateMaps() {
BeginBatch();
int active_row = line_index_map[active_line];
index_line_map.clear(); index_line_map.clear();
line_index_map.clear(); line_index_map.clear();
@ -261,47 +235,17 @@ void BaseGrid::UpdateMaps() {
index_line_map.push_back(&curdiag); index_line_map.push_back(&curdiag);
} }
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(std::move(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))
SetActiveLine(index_line_map[std::min((size_t)active_row, index_line_map.size() - 1)]);
if (selection.empty() && active_line)
SetSelectedSet({ active_line });
EndBatch();
SetColumnWidths(); SetColumnWidths();
Refresh(false); Refresh(false);
} }
void BaseGrid::BeginBatch() { void BaseGrid::OnActiveLineChanged(AssDialogue *new_active) {
++batch_level; if (new_active) {
} int row = GetDialogueIndex(new_active);
MakeRowVisible(row);
void BaseGrid::EndBatch() { extendRow = row;
--batch_level; Refresh(false);
assert(batch_level >= 0);
if (batch_level == 0) {
if (batch_active_line_changed)
AnnounceActiveLineChanged(active_line);
batch_active_line_changed = false;
if (batch_selection_changed)
AnnounceSelectedSetChanged();
batch_selection_changed = false;
} }
AdjustScrollbar();
} }
void BaseGrid::MakeRowVisible(int row) { void BaseGrid::MakeRowVisible(int row) {
@ -319,23 +263,19 @@ void BaseGrid::SelectRow(int row, bool addToSelected, bool select) {
AssDialogue *line = index_line_map[row]; AssDialogue *line = index_line_map[row];
if (!addToSelected) { if (!addToSelected) {
Selection sel; context->selectionController->SetSelectedSet(Selection{line});
if (select) sel.insert(line);
SetSelectedSet(std::move(sel));
return; return;
} }
if (select && selection.find(line) == selection.end()) { bool selected = !!context->selectionController->GetSelectedSet().count(line);
selection.insert(line); if (select != selected) {
AnnounceSelectedSetChanged(); auto selection = context->selectionController->GetSelectedSet();
if (select)
selection.insert(line);
else
selection.erase(line);
context->selectionController->SetSelectedSet(std::move(selection));
} }
else if (!select && selection.find(line) != selection.end()) {
selection.erase(line);
AnnounceSelectedSetChanged();
}
int w = GetClientSize().GetWidth();
RefreshRect(wxRect(0, (row + 1 - yPos) * lineHeight, w, lineHeight), false);
} }
void BaseGrid::OnPaint(wxPaintEvent &) { void BaseGrid::OnPaint(wxPaintEvent &) {
@ -401,9 +341,13 @@ void BaseGrid::DrawImage(wxDC &dc, bool paint_columns[]) {
if (override_mode == 1) if (override_mode == 1)
replace_char = to_wx(OPT_GET("Subtitle/Grid/Hide Overrides Char")->GetString()); replace_char = to_wx(OPT_GET("Subtitle/Grid/Hide Overrides Char")->GetString());
auto active_line = context->selectionController->GetActiveLine();
auto const& selection = context->selectionController->GetSelectedSet();
for (int i = 0; i < nDraw + 1; i++) { for (int i = 0; i < nDraw + 1; i++) {
int curRow = i + yPos - 1; int curRow = i + yPos - 1;
RowColor curColor = COLOR_DEFAULT; RowColor curColor = COLOR_DEFAULT;
AssDialogue *curDiag = nullptr;
// Header // Header
if (i == 0) { if (i == 0) {
@ -411,7 +355,7 @@ void BaseGrid::DrawImage(wxDC &dc, bool paint_columns[]) {
dc.SetTextForeground(text_standard); dc.SetTextForeground(text_standard);
} }
// Lines // Lines
else if (AssDialogue *curDiag = GetDialogue(curRow)) { else if ((curDiag = GetDialogue(curRow))) {
GetRowStrings(curRow, curDiag, paint_columns, strings, !!override_mode, replace_char); GetRowStrings(curRow, curDiag, paint_columns, strings, !!override_mode, replace_char);
bool inSel = !!selection.count(curDiag); bool inSel = !!selection.count(curDiag);
@ -467,28 +411,27 @@ void BaseGrid::DrawImage(wxDC &dc, bool paint_columns[]) {
// Draw grid // Draw grid
dc.DestroyClippingRegion(); dc.DestroyClippingRegion();
dc.SetPen(grid_pen); if (curDiag == active_line) {
dc.DrawLine(0,dy+lineHeight,w,dy+lineHeight); dc.SetPen(wxPen(to_wx(OPT_GET("Colour/Subtitle Grid/Active Border")->GetColor())));
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(0, dy, w, lineHeight + 1);
}
else {
dc.SetPen(grid_pen);
dc.DrawLine(0, dy + lineHeight, w , dy + lineHeight);
}
dc.SetPen(*wxTRANSPARENT_PEN); dc.SetPen(*wxTRANSPARENT_PEN);
} }
// Draw grid columns // Draw grid columns
int dx = 0; int dx = 0;
dc.SetPen(grid_pen); dc.SetPen(grid_pen);
for (int i=0;i<10;i++) { for (int i = 0; i < 10; ++i) {
dx += colWidth[i]; dx += colWidth[i];
dc.DrawLine(dx,0,dx,maxH); dc.DrawLine(dx, 0, dx, maxH);
}
dc.DrawLine(0,0,0,maxH);
dc.DrawLine(w-1,0,w-1,maxH);
// Draw currently active line border
if (GetActiveLine()) {
dc.SetPen(wxPen(to_wx(OPT_GET("Colour/Subtitle Grid/Active Border")->GetColor())));
dc.SetBrush(*wxTRANSPARENT_BRUSH);
int dy = (line_index_map[GetActiveLine()]+1-yPos) * lineHeight;
dc.DrawRectangle(0,dy,w,lineHeight+1);
} }
dc.DrawLine(0, 0, 0, maxH);
dc.DrawLine(w-1, 0, w-1, maxH);
} }
void BaseGrid::GetRowStrings(int row, AssDialogue *line, bool *paint_columns, wxString *strings, bool replace, wxString const& rep_char) const { void BaseGrid::GetRowStrings(int row, AssDialogue *line, bool *paint_columns, wxString *strings, bool replace, wxString const& rep_char) const {
@ -604,8 +547,11 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
// but we don't want to scroll until the mouse moves or the button is // but we don't want to scroll until the mouse moves or the button is
// released, to avoid selecting multiple lines on a click // released, to avoid selecting multiple lines on a click
int old_y_pos = yPos; int old_y_pos = yPos;
SetActiveLine(dlg); context->selectionController->SetActiveLine(dlg);
ScrollTo(old_y_pos); ScrollTo(old_y_pos);
extendRow = row;
auto const& selection = context->selectionController->GetSelectedSet();
// Toggle selected // Toggle selected
if (click && ctrl && !shift && !alt) { if (click && ctrl && !shift && !alt) {
@ -643,7 +589,7 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
if (ctrl) newsel = selection; if (ctrl) newsel = selection;
for (int i = i1; i <= i2; i++) for (int i = i1; i <= i2; i++)
newsel.insert(GetDialogue(i)); newsel.insert(GetDialogue(i));
SetSelectedSet(std::move(newsel)); context->selectionController->SetSelectedSet(std::move(newsel));
return; return;
} }
@ -830,7 +776,6 @@ void BaseGrid::SetColumnWidths() {
colWidth[i] += 10; colWidth[i] += 10;
} }
// Set size of last // Set size of last
int total = std::accumulate(colWidth, colWidth + 10, 0); int total = std::accumulate(colWidth, colWidth + 10, 0);
colWidth[10] = std::max(w - total, 0); colWidth[10] = std::max(w - total, 0);
@ -911,8 +856,8 @@ void BaseGrid::OnKeyDown(wxKeyEvent &event) {
} }
int old_extend = extendRow; int old_extend = extendRow;
int next = mid(0, GetDialogueIndex(active_line) + dir * step, GetRows() - 1); int next = mid(0, GetDialogueIndex(context->selectionController->GetActiveLine()) + dir * step, GetRows() - 1);
SetActiveLine(GetDialogue(next)); context->selectionController->SetActiveLine(GetDialogue(next));
// Move selection // Move selection
if (!ctrl && !shift && !alt) { if (!ctrl && !shift && !alt) {
@ -921,10 +866,8 @@ void BaseGrid::OnKeyDown(wxKeyEvent &event) {
} }
// Move active only // Move active only
if (alt && !shift && !ctrl) { if (alt && !shift && !ctrl)
Refresh(false);
return; return;
}
// Shift-selection // Shift-selection
if (shift && !ctrl && !alt) { if (shift && !ctrl && !alt) {
@ -940,7 +883,7 @@ void BaseGrid::OnKeyDown(wxKeyEvent &event) {
for (int i = begin; i <= end; i++) for (int i = begin; i <= end; i++)
newsel.insert(GetDialogue(i)); newsel.insert(GetDialogue(i));
SetSelectedSet(std::move(newsel)); context->selectionController->SetSelectedSet(std::move(newsel));
MakeRowVisible(next); MakeRowVisible(next);
return; return;
@ -953,59 +896,3 @@ void BaseGrid::SetByFrame(bool state) {
SetColumnWidths(); SetColumnWidths();
Refresh(false); Refresh(false);
} }
void BaseGrid::SetSelectedSet(Selection new_selection) {
selection = std::move(new_selection);
AnnounceSelectedSetChanged();
Refresh(false);
}
void BaseGrid::SetActiveLine(AssDialogue *new_line) {
if (new_line != active_line) {
assert(new_line == nullptr || line_index_map.count(new_line));
active_line = new_line;
AnnounceActiveLineChanged(active_line);
MakeRowVisible(GetDialogueIndex(active_line));
Refresh(false);
}
// extendRow may not equal the active row if it was set via a shift-click,
// so update it even if the active line didn't change
extendRow = GetDialogueIndex(new_line);
}
void BaseGrid::SetSelectionAndActive(Selection new_selection, AssDialogue *new_line) {
BeginBatch();
SetSelectedSet(std::move(new_selection));
SetActiveLine(new_line);
EndBatch();
}
void BaseGrid::PrevLine() {
if (!active_line) return;
auto it = context->ass->Events.iterator_to(*active_line);
if (it != context->ass->Events.begin()) {
--it;
SetSelectionAndActive({&*it}, &*it);
}
}
void BaseGrid::NextLine() {
if (!active_line) return;
auto it = context->ass->Events.iterator_to(*active_line);
if (++it != context->ass->Events.end())
SetSelectionAndActive({&*it}, &*it);
}
void BaseGrid::AnnounceActiveLineChanged(AssDialogue *new_line) {
if (batch_level > 0)
batch_active_line_changed = true;
else
SubtitleSelectionController::AnnounceActiveLineChanged(new_line);
}
void BaseGrid::AnnounceSelectedSetChanged() {
if (batch_level > 0)
batch_selection_changed = true;
else
SubtitleSelectionController::AnnounceSelectedSetChanged();
}

View file

@ -32,8 +32,6 @@
/// @ingroup main_ui /// @ingroup main_ui
/// ///
#pragma once
#include <libaegisub/signal.h> #include <libaegisub/signal.h>
#include <map> #include <map>
@ -41,15 +39,14 @@
#include <vector> #include <vector>
#include <wx/window.h> #include <wx/window.h>
#include "selection_controller.h"
namespace agi { namespace agi {
struct Context; struct Context;
class OptionValue; class OptionValue;
} }
class AssDialogue; class AssDialogue;
class BaseGrid final : public wxWindow, public SubtitleSelectionController { class BaseGrid final : public wxWindow {
std::vector<agi::signal::Connection> connections;
int lineHeight = 1; ///< Height of a line in pixels in the current font int lineHeight = 1; ///< Height of a line in pixels in the current font
bool holding = false; ///< Is a drag selection in process? bool holding = false; ///< Is a drag selection in process?
wxFont font; ///< Current grid font wxFont font; ///< Current grid font
@ -61,19 +58,9 @@ class BaseGrid final : public wxWindow, public SubtitleSelectionController {
/// keyboard, shift-clicking or dragging /// keyboard, shift-clicking or dragging
int extendRow = -1; int extendRow = -1;
Selection selection; ///< Currently selected lines
AssDialogue *active_line = nullptr; ///< The currently active line or 0 if none
std::vector<AssDialogue*> index_line_map; ///< Row number -> dialogue line std::vector<AssDialogue*> index_line_map; ///< Row number -> dialogue line
std::map<AssDialogue*,int> line_index_map; ///< Dialogue line -> row number std::map<AssDialogue*,int> line_index_map; ///< Dialogue line -> row number
/// Selection batch nesting depth; changes are commited only when this
/// hits zero
int batch_level = 0;
/// Has the active line been changed in the current batch?
bool batch_active_line_changed = false;
/// Has the selection been changed in the current batch?
bool batch_selection_changed = false;
/// Connection for video seek event. Stored explicitly so that it can be /// Connection for video seek event. Stored explicitly so that it can be
/// blocked if the relevant option is disabled /// blocked if the relevant option is disabled
agi::signal::Connection seek_listener; agi::signal::Connection seek_listener;
@ -93,6 +80,7 @@ class BaseGrid final : public wxWindow, public SubtitleSelectionController {
void OnSubtitlesCommit(int type); void OnSubtitlesCommit(int type);
void OnSubtitlesOpen(); void OnSubtitlesOpen();
void OnSubtitlesSave(); void OnSubtitlesSave();
void OnActiveLineChanged(AssDialogue *);
void DrawImage(wxDC &dc, bool paint_columns[]); void DrawImage(wxDC &dc, bool paint_columns[]);
void GetRowStrings(int row, AssDialogue *line, bool *paint_columns, wxString *strings, bool replace, wxString const& rep_char) const; void GetRowStrings(int row, AssDialogue *line, bool *paint_columns, wxString *strings, bool replace, wxString const& rep_char) const;
@ -115,35 +103,15 @@ class BaseGrid final : public wxWindow, public SubtitleSelectionController {
bool IsDisplayed(const AssDialogue *line) const; bool IsDisplayed(const AssDialogue *line) const;
// Re-implement functions from BaseSelectionController to add batching
void AnnounceActiveLineChanged(AssDialogue *new_line);
void AnnounceSelectedSetChanged();
protected:
agi::Context *context; ///< Current project context agi::Context *context; ///< Current project context
public:
// SelectionController implementation
void SetActiveLine(AssDialogue *new_line) override;
AssDialogue * GetActiveLine() const override { return active_line; }
void SetSelectedSet(Selection new_selection) override;
void GetSelectedSet(Selection &res) const override { res = selection; }
Selection const& GetSelectedSet() const override { return selection; }
void SetSelectionAndActive(Selection new_selection, AssDialogue *new_line) override;;
void NextLine() override;
void PrevLine() override;
void BeginBatch();
void EndBatch();
void SetByFrame(bool state);
void SelectRow(int row, bool addToSelected = false, bool select=true);
void ClearMaps(); void ClearMaps();
/// @brief Update the row <-> AssDialogue mappings /// @brief Update the row <-> AssDialogue mappings
void UpdateMaps(); void UpdateMaps();
void UpdateStyle(); void UpdateStyle();
void SelectRow(int row, bool addToSelected = false, bool select=true);
int GetRows() const { return index_line_map.size(); } int GetRows() const { return index_line_map.size(); }
void MakeRowVisible(int row); void MakeRowVisible(int row);
@ -157,8 +125,11 @@ public:
/// @return Subtitle index for object, or -1 if unknown subtitle /// @return Subtitle index for object, or -1 if unknown subtitle
int GetDialogueIndex(AssDialogue *diag) const; int GetDialogueIndex(AssDialogue *diag) const;
BaseGrid(wxWindow* parent, agi::Context *context, const wxSize& size = wxDefaultSize, long style = wxWANTS_CHARS, const wxString& name = wxPanelNameStr); public:
BaseGrid(wxWindow* parent, agi::Context *context);
~BaseGrid(); ~BaseGrid();
void SetByFrame(bool state);
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
}; };

View file

@ -195,7 +195,7 @@ struct audio_save_clip final : public Command {
} }
void operator()(agi::Context *c) override { void operator()(agi::Context *c) override {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet(); auto const& sel = c->selectionController->GetSelectedSet();
if (sel.empty()) return; if (sel.empty()) return;
AssTime start = INT_MAX, end = 0; AssTime start = INT_MAX, end = 0;

View file

@ -101,7 +101,7 @@ void paste_lines(agi::Context *c, bool paste_over, Paster&& paste_line) {
if (data.empty()) return; if (data.empty()) return;
AssDialogue *first = nullptr; AssDialogue *first = nullptr;
SubtitleSelection newsel; Selection newsel;
boost::char_separator<char> sep("\r\n"); boost::char_separator<char> sep("\r\n");
for (auto curdata : boost::tokenizer<boost::char_separator<char>>(data, sep)) { for (auto curdata : boost::tokenizer<boost::char_separator<char>>(data, sep)) {
@ -270,7 +270,7 @@ void set_tag(AssDialogue *line, boost::ptr_vector<AssDialogueBlock> &blocks, std
} }
void commit_text(agi::Context const * const c, wxString const& desc, int sel_start = -1, int sel_end = -1, int *commit_id = nullptr) { void commit_text(agi::Context const * const c, wxString const& desc, int sel_start = -1, int sel_end = -1, int *commit_id = nullptr) {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet(); auto const& sel = c->selectionController->GetSelectedSet();
std::string text = c->selectionController->GetActiveLine()->Text; std::string text = c->selectionController->GetActiveLine()->Text;
for_each(sel.begin(), sel.end(), [&](AssDialogue *d) { d->Text = text; }); for_each(sel.begin(), sel.end(), [&](AssDialogue *d) { d->Text = text; });
@ -488,7 +488,7 @@ struct edit_find_replace final : public Command {
static std::string get_entry_data(AssDialogue &d) { return d.GetEntryData(); } static std::string get_entry_data(AssDialogue &d) { return d.GetEntryData(); }
static void copy_lines(agi::Context *c) { static void copy_lines(agi::Context *c) {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet(); auto const& sel = c->selectionController->GetSelectedSet();
SetClipboard(join(c->ass->Events SetClipboard(join(c->ass->Events
| filtered([&](AssDialogue &d) { return sel.count(&d); }) | filtered([&](AssDialogue &d) { return sel.count(&d); })
| transformed(get_entry_data), | transformed(get_entry_data),
@ -496,7 +496,7 @@ static void copy_lines(agi::Context *c) {
} }
static void delete_lines(agi::Context *c, wxString const& commit_message) { static void delete_lines(agi::Context *c, wxString const& commit_message) {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet(); auto const& sel = c->selectionController->GetSelectedSet();
// Find a line near the active line not being deleted to make the new active line // Find a line near the active line not being deleted to make the new active line
AssDialogue *pre_sel = nullptr; AssDialogue *pre_sel = nullptr;
@ -595,7 +595,7 @@ static void duplicate_lines(agi::Context *c, int shift) {
auto const& sel = c->selectionController->GetSelectedSet(); auto const& sel = c->selectionController->GetSelectedSet();
auto in_selection = [&](AssDialogue const& d) { return sel.count(const_cast<AssDialogue *>(&d)); }; auto in_selection = [&](AssDialogue const& d) { return sel.count(const_cast<AssDialogue *>(&d)); };
SubtitleSelectionController::Selection new_sel; Selection new_sel;
AssDialogue *new_active = nullptr; AssDialogue *new_active = nullptr;
auto start = c->ass->Events.begin(); auto start = c->ass->Events.begin();
@ -693,7 +693,7 @@ struct edit_line_duplicate_shift_back final : public validate_video_and_sel_none
}; };
static void combine_lines(agi::Context *c, void (*combiner)(AssDialogue *, AssDialogue *), wxString const& message) { static void combine_lines(agi::Context *c, void (*combiner)(AssDialogue *, AssDialogue *), wxString const& message) {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet(); auto const& sel = c->selectionController->GetSelectedSet();
AssDialogue *first = nullptr; AssDialogue *first = nullptr;
for (auto it = c->ass->Events.begin(); it != c->ass->Events.end(); ) { for (auto it = c->ass->Events.begin(); it != c->ass->Events.end(); ) {
@ -776,7 +776,7 @@ static bool try_paste_lines(agi::Context *c) {
} }
AssDialogue *new_active = &*parsed.begin(); AssDialogue *new_active = &*parsed.begin();
SubtitleSelection new_selection; Selection new_selection;
for (auto& line : parsed) for (auto& line : parsed)
new_selection.insert(&line); new_selection.insert(&line);
@ -975,7 +975,7 @@ struct edit_line_recombine final : public validate_sel_multiple {
} }
// Remove now non-existent lines from the selection // Remove now non-existent lines from the selection
SubtitleSelection lines, new_sel; Selection lines, new_sel;
boost::copy(c->ass->Events | agi::address_of, inserter(lines, lines.begin())); boost::copy(c->ass->Events | agi::address_of, inserter(lines, lines.begin()));
boost::set_intersection(lines, sel_set, inserter(new_sel, new_sel.begin())); boost::set_intersection(lines, sel_set, inserter(new_sel, new_sel.begin()));

View file

@ -389,7 +389,7 @@ struct grid_swap final : public Command {
} }
void operator()(agi::Context *c) override { void operator()(agi::Context *c) override {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet(); auto const& sel = c->selectionController->GetSelectedSet();
if (sel.size() == 2) { if (sel.size() == 2) {
(*sel.begin())->swap_nodes(**sel.rbegin()); (*sel.begin())->swap_nodes(**sel.rbegin());
c->ass->Commit(_("swap lines"), AssFile::COMMIT_ORDER); c->ass->Commit(_("swap lines"), AssFile::COMMIT_ORDER);

View file

@ -372,7 +372,7 @@ struct subtitle_select_all final : public Command {
STR_HELP("Select all dialogue lines") STR_HELP("Select all dialogue lines")
void operator()(agi::Context *c) override { void operator()(agi::Context *c) override {
SubtitleSelection sel; Selection sel;
boost::copy(c->ass->Events | agi::address_of, inserter(sel, sel.end())); boost::copy(c->ass->Events | agi::address_of, inserter(sel, sel.end()));
c->selectionController->SetSelectedSet(std::move(sel)); c->selectionController->SetSelectedSet(std::move(sel));
} }
@ -390,7 +390,7 @@ struct subtitle_select_visible final : public Command {
if (!c->videoController->IsLoaded()) return; if (!c->videoController->IsLoaded()) return;
c->videoController->Stop(); c->videoController->Stop();
SubtitleSelectionController::Selection new_selection; Selection new_selection;
int frame = c->videoController->GetFrameN(); int frame = c->videoController->GetFrameN();
for (auto& diag : c->ass->Events) { for (auto& diag : c->ass->Events) {

View file

@ -62,7 +62,7 @@ namespace {
struct validate_adjoinable : public Command { struct validate_adjoinable : public Command {
CMD_TYPE(COMMAND_VALIDATE) CMD_TYPE(COMMAND_VALIDATE)
bool Validate(const agi::Context *c) override { bool Validate(const agi::Context *c) override {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet(); auto const& sel = c->selectionController->GetSelectedSet();
if (sel.size() < 2) return !sel.empty(); if (sel.size() < 2) return !sel.empty();
size_t found = 0; size_t found = 0;
@ -138,8 +138,8 @@ struct time_frame_current final : public validate_video_loaded {
void operator()(agi::Context *c) override { void operator()(agi::Context *c) override {
if (!c->videoController->IsLoaded()) return; if (!c->videoController->IsLoaded()) return;
SubtitleSelection const& sel = c->selectionController->GetSelectedSet(); auto const& sel = c->selectionController->GetSelectedSet();
const AssDialogue *active_line = c->selectionController->GetActiveLine(); const auto active_line = c->selectionController->GetActiveLine();
if (sel.empty() || !active_line) return; if (sel.empty() || !active_line) return;
@ -168,7 +168,7 @@ struct time_shift final : public Command {
}; };
static void snap_subs_video(agi::Context *c, bool set_start) { static void snap_subs_video(agi::Context *c, bool set_start) {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet(); auto const& sel = c->selectionController->GetSelectedSet();
if (!c->videoController->IsLoaded() || sel.empty()) return; if (!c->videoController->IsLoaded() || sel.empty()) return;

View file

@ -186,7 +186,7 @@ void DialogSelection::Process(wxCommandEvent&) {
auto action = static_cast<Action>(selection_change_type->GetSelection()); auto action = static_cast<Action>(selection_change_type->GetSelection());
SubtitleSelection old_sel, new_sel; Selection old_sel, new_sel;
if (action != Action::SET) if (action != Action::SET)
con->selectionController->GetSelectedSet(old_sel); con->selectionController->GetSelectedSet(old_sel);

View file

@ -31,6 +31,7 @@
#include "help_button.h" #include "help_button.h"
#include "libresrc/libresrc.h" #include "libresrc/libresrc.h"
#include "options.h" #include "options.h"
#include "selection_controller.h"
#include "subs_controller.h" #include "subs_controller.h"
#include "timeedit_ctrl.h" #include "timeedit_ctrl.h"
#include "video_context.h" #include "video_context.h"
@ -331,7 +332,7 @@ void DialogShiftTimes::Process(wxCommandEvent &) {
bool start = type != 2; bool start = type != 2;
bool end = type != 1; bool end = type != 1;
SubtitleSelection const& sel = context->selectionController->GetSelectedSet(); auto const& sel = context->selectionController->GetSelectedSet();
long shift; long shift;
if (by_time) { if (by_time) {

View file

@ -19,8 +19,6 @@
/// @ingroup secondary_ui /// @ingroup secondary_ui
/// ///
#include "selection_controller.h"
#include <libaegisub/fs_fwd.h> #include <libaegisub/fs_fwd.h>
#include <libaegisub/signal.h> #include <libaegisub/signal.h>
#include <libaegisub/vfr.h> #include <libaegisub/vfr.h>

View file

@ -35,6 +35,7 @@
#include "help_button.h" #include "help_button.h"
#include "libresrc/libresrc.h" #include "libresrc/libresrc.h"
#include "persist_location.h" #include "persist_location.h"
#include "selection_controller.h"
#include "video_context.h" #include "video_context.h"
#include <libaegisub/util.h> #include <libaegisub/util.h>

View file

@ -14,12 +14,7 @@
// //
// Aegisub Project http://www.aegisub.org/ // Aegisub Project http://www.aegisub.org/
/// @file dialog_styling_assistant.h #include <libaegisub/signal.h>
/// @see dialog_styling_assistant.cpp
/// @ingroup tools_ui
///
#include "selection_controller.h"
#include <memory> #include <memory>
#include <wx/dialog.h> #include <wx/dialog.h>

View file

@ -35,11 +35,6 @@
#include "frame_main.h" #include "frame_main.h"
#include <libaegisub/fs.h>
#include <libaegisub/log.h>
#include <libaegisub/path.h>
#include <libaegisub/util.h>
#include "include/aegisub/context.h" #include "include/aegisub/context.h"
#include "include/aegisub/menu.h" #include "include/aegisub/menu.h"
#include "include/aegisub/toolbar.h" #include "include/aegisub/toolbar.h"
@ -61,6 +56,7 @@
#include "libresrc/libresrc.h" #include "libresrc/libresrc.h"
#include "main.h" #include "main.h"
#include "options.h" #include "options.h"
#include "selection_controller.h"
#include "search_replace_engine.h" #include "search_replace_engine.h"
#include "subs_controller.h" #include "subs_controller.h"
#include "subs_edit_box.h" #include "subs_edit_box.h"
@ -72,6 +68,11 @@
#include "video_display.h" #include "video_display.h"
#include "video_slider.h" #include "video_slider.h"
#include <libaegisub/fs.h>
#include <libaegisub/log.h>
#include <libaegisub/path.h>
#include <libaegisub/util.h>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <wx/dnd.h> #include <wx/dnd.h>
@ -82,7 +83,7 @@
#include <wx/sysopt.h> #include <wx/sysopt.h>
enum { enum {
ID_APP_TIMER_STATUSCLEAR = 12002 ID_APP_TIMER_STATUSCLEAR = 12002
}; };
#ifdef WITH_STARTUPLOG #ifdef WITH_STARTUPLOG
@ -220,8 +221,8 @@ FrameMain::FrameMain()
context->local_scripts = new Automation4::LocalScriptManager(context.get()); context->local_scripts = new Automation4::LocalScriptManager(context.get());
// Initialized later due to that the selection controller is currently the subtitles grid context->selectionController = new SelectionController(context.get());
context->selectionController = nullptr; context->subsController->SetSelectionController(context->selectionController);
context->videoController = VideoContext::Get(); // derp context->videoController = VideoContext::Get(); // derp
context->videoController->AddVideoOpenListener(&FrameMain::OnVideoOpen, this); context->videoController->AddVideoOpenListener(&FrameMain::OnVideoOpen, this);
@ -264,7 +265,6 @@ 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));
@ -302,40 +302,13 @@ FrameMain::FrameMain()
StartupLog("Leaving FrameMain constructor"); StartupLog("Leaving FrameMain constructor");
} }
/// @brief Delete everything but @a keep and its parents
/// @param window Root window to delete the children of
/// @param keep Window to keep alive
/// @return Was @a keep found?
static bool delete_children(wxWindow *window, wxWindow *keep) {
bool found = false;
while (window->GetChildren().size() > (size_t)found) {
auto it = window->GetChildren().begin();
if (*it == keep)
found = true;
if (found) {
if (++it != window->GetChildren().end())
(*it)->wxWindowBase::Destroy();
}
else if (!delete_children(*it, keep))
(*it)->wxWindowBase::Destroy();
else
found = true;
}
return found;
}
FrameMain::~FrameMain () { FrameMain::~FrameMain () {
wxGetApp().frame = nullptr; wxGetApp().frame = nullptr;
context->videoController->SetVideo(""); context->videoController->SetVideo("");
context->audioController->CloseAudio(); context->audioController->CloseAudio();
// SubsGrid needs to be deleted last due to being the selection DestroyChildren();
// controller, but everything else needs to be deleted before the context
// is cleaned up
delete_children(this, SubsGrid);
delete context->ass; delete context->ass;
delete context->audioController; delete context->audioController;
@ -367,8 +340,7 @@ void FrameMain::InitContents() {
wxPanel *Panel = new wxPanel(this,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL | wxCLIP_CHILDREN); wxPanel *Panel = new wxPanel(this,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL | wxCLIP_CHILDREN);
StartupLog("Create subtitles grid"); StartupLog("Create subtitles grid");
context->subsGrid = SubsGrid = new BaseGrid(Panel, context.get(), wxDefaultSize, wxWANTS_CHARS | wxSUNKEN_BORDER); context->subsGrid = new BaseGrid(Panel, context.get());
context->selectionController = context->subsGrid;
context->search = new SearchReplaceEngine(context.get()); context->search = new SearchReplaceEngine(context.get());
context->initialLineState = new InitialLineState(context.get()); context->initialLineState = new InitialLineState(context.get());
@ -391,7 +363,7 @@ void FrameMain::InitContents() {
MainSizer = new wxBoxSizer(wxVERTICAL); MainSizer = new wxBoxSizer(wxVERTICAL);
MainSizer->Add(new wxStaticLine(Panel),0,wxEXPAND | wxALL,0); MainSizer->Add(new wxStaticLine(Panel),0,wxEXPAND | wxALL,0);
MainSizer->Add(TopSizer,0,wxEXPAND | wxALL,0); MainSizer->Add(TopSizer,0,wxEXPAND | wxALL,0);
MainSizer->Add(SubsGrid,1,wxEXPAND | wxALL,0); MainSizer->Add(context->subsGrid,1,wxEXPAND | wxALL,0);
Panel->SetSizer(MainSizer); Panel->SetSizer(MainSizer);
StartupLog("Perform layout"); StartupLog("Perform layout");

View file

@ -37,11 +37,7 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <wx/combobox.h>
#include <wx/frame.h> #include <wx/frame.h>
#include <wx/log.h>
#include <wx/menu.h>
#include <wx/panel.h>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/timer.h> #include <wx/timer.h>
@ -49,7 +45,6 @@ class AegisubApp;
class AegisubFileDropTarget; class AegisubFileDropTarget;
class AudioBox; class AudioBox;
class AudioProvider; class AudioProvider;
class BaseGrid;
class VideoBox; class VideoBox;
namespace agi { struct Context; class OptionValue; } namespace agi { struct Context; class OptionValue; }
@ -98,7 +93,6 @@ class FrameMain: public wxFrame {
void EnableToolBar(agi::OptionValue const& opt); void EnableToolBar(agi::OptionValue const& opt);
BaseGrid *SubsGrid; ///< The subtitle editing area
AudioBox *audioBox; ///< The audio area AudioBox *audioBox; ///< The audio area
VideoBox *videoBox; ///< The video area VideoBox *videoBox; ///< The video area

View file

@ -6,7 +6,7 @@ class AudioKaraoke;
class DialogManager; class DialogManager;
class SearchReplaceEngine; class SearchReplaceEngine;
class InitialLineState; class InitialLineState;
template<class T> class SelectionController; class SelectionController;
class SubsController; class SubsController;
class SubsTextEditCtrl; class SubsTextEditCtrl;
class BaseGrid; class BaseGrid;
@ -26,7 +26,7 @@ struct Context {
// Controllers // Controllers
AudioController *audioController; AudioController *audioController;
SelectionController<AssDialogue *> *selectionController; SelectionController *selectionController;
SubsController *subsController; SubsController *subsController;
TextSelectionController *textSelectionController; TextSelectionController *textSelectionController;
VideoContext *videoController; VideoContext *videoController;

View file

@ -301,7 +301,7 @@ bool SearchReplaceEngine::ReplaceAll() {
auto matches = GetMatcher(settings); auto matches = GetMatcher(settings);
SubtitleSelection const& sel = context->selectionController->GetSelectedSet(); auto const& sel = context->selectionController->GetSelectedSet();
bool selection_only = settings.limit_to == SearchReplaceSettings::Limit::SELECTED; bool selection_only = settings.limit_to == SearchReplaceSettings::Limit::SELECTED;
for (auto& diag : context->ass->Events) { for (auto& diag : context->ass->Events) {

View file

@ -0,0 +1,83 @@
// Copyright (c) 2014, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
#include "config.h"
#include "selection_controller.h"
#include "ass_dialogue.h"
#include "ass_file.h"
#include "include/aegisub/context.h"
#include "subs_controller.h"
#include "utils.h"
SelectionController::SelectionController(agi::Context *c)
: context(c)
, open_connection(c->subsController->AddFileOpenListener(&SelectionController::OnSubtitlesOpen, this))
, save_connection(c->subsController->AddFileSaveListener(&SelectionController::OnSubtitlesSave, this))
{
}
void SelectionController::OnSubtitlesOpen() {
selection.clear();
active_line = nullptr;
if (!context->ass->Events.empty()) {
int row = mid<int>(0, context->ass->GetUIStateAsInt("Active Line"), context->ass->Events.size());
active_line = &*std::next(context->ass->Events.begin(), row);
selection.insert(active_line);
}
AnnounceSelectedSetChanged();
AnnounceActiveLineChanged(active_line);
}
void SelectionController::OnSubtitlesSave() {
if (active_line)
context->ass->SaveUIState("Active Line", std::to_string(std::distance(
context->ass->Events.begin(), context->ass->Events.iterator_to(*active_line))));
}
void SelectionController::SetSelectedSet(Selection new_selection) {
selection = std::move(new_selection);
AnnounceSelectedSetChanged();
}
void SelectionController::SetActiveLine(AssDialogue *new_line) {
if (new_line != active_line) {
active_line = new_line;
AnnounceActiveLineChanged(new_line);
}
}
void SelectionController::SetSelectionAndActive(Selection new_selection, AssDialogue *new_line) {
SetSelectedSet(std::move(new_selection));
SetActiveLine(new_line);
}
void SelectionController::PrevLine() {
if (!active_line) return;
auto it = context->ass->Events.iterator_to(*active_line);
if (it != context->ass->Events.begin()) {
--it;
SetSelectionAndActive({&*it}, &*it);
}
}
void SelectionController::NextLine() {
if (!active_line) return;
auto it = context->ass->Events.iterator_to(*active_line);
if (++it != context->ass->Events.end())
SetSelectionAndActive({&*it}, &*it);
}

View file

@ -27,49 +27,32 @@
// //
// Aegisub Project http://www.aegisub.org/ // Aegisub Project http://www.aegisub.org/
/// @file selection_controller.h #include <libaegisub/signal.h>
/// @ingroup controllers
/// @brief Interface declaration for the SubtitleSelectionController
#pragma once
#include <set> #include <set>
#include <libaegisub/signal.h> class AssDialogue;
typedef std::set<AssDialogue *> Selection;
namespace agi { struct Context; }
/// @class SelectionController
/// @brief Abstract interface for selection controllers
///
/// Two concepts are managed by implementations of this interface: The concept of the
/// active line, and the concept of the set of selected lines. There is one or zero
/// active lines, the active line is the base for subtitle manipulation in the GUI.
/// The set of selected lines may contain any number of subtitle lines, and those
/// lines are the primary target of subtitle manipulation. In other words, the active
/// line controls what values the user is presented to modify, and the selected set
/// controls what lines are actually modified when the user performs modifications.
/// In most cases, the active line will be a member of the selected set. It will be
/// the responsibility of manipulators to affect the appropriate lines.
///
/// There is only intended to be one instance of a class implementing this interface
/// per editing session, but there may be many different implementations of it.
/// The primary implementation would be the subtitle grid in the main GUI, allowing
/// the user to actively manipulate the active and selected line sets, but other
/// potential implementations are in a test driver and in a non-interactive scenario.
///
/// Objects implementing the SelectionListener interface can subscribe to
/// changes in the active line and the selected set.
template <typename ItemDataType>
class SelectionController { class SelectionController {
public: agi::signal::Signal<AssDialogue *> AnnounceActiveLineChanged;
typedef std::set<ItemDataType> Selection;
protected:
agi::signal::Signal<ItemDataType> AnnounceActiveLineChanged;
agi::signal::Signal<> AnnounceSelectedSetChanged; agi::signal::Signal<> AnnounceSelectedSetChanged;
agi::Context *context;
Selection selection; ///< Currently selected lines
AssDialogue *active_line = nullptr; ///< The currently active line or 0 if none
agi::signal::Connection open_connection;
agi::signal::Connection save_connection;
void OnSubtitlesOpen();
void OnSubtitlesSave();
public: public:
/// Virtual destructor for safety SelectionController(agi::Context *context);
virtual ~SelectionController() { }
/// @brief Change the active line /// @brief Change the active line
/// @param new_line Subtitle line to become the new active line /// @param new_line Subtitle line to become the new active line
@ -81,11 +64,11 @@ public:
/// the active line was actually changed. /// the active line was actually changed.
/// ///
/// This method must not affect the selected set. /// This method must not affect the selected set.
virtual void SetActiveLine(ItemDataType new_line) = 0; void SetActiveLine(AssDialogue *new_line);
/// @brief Obtain the active line /// @brief Obtain the active line
/// @return The active line or nullptr if there is none /// @return The active line or nullptr if there is none
virtual ItemDataType GetActiveLine() const = 0; AssDialogue *GetActiveLine() const { return active_line; }
/// @brief Change the selected set /// @brief Change the selected set
/// @param new_selection The set of subtitle lines to become the new selected set /// @param new_selection The set of subtitle lines to become the new selected set
@ -97,15 +80,15 @@ public:
/// If no change happens to the selected set, whether because it was refused or /// If no change happens to the selected set, whether because it was refused or
/// because the new set was identical to the old set, no change notification may /// because the new set was identical to the old set, no change notification may
/// be sent. /// be sent.
virtual void SetSelectedSet(Selection new_selection) = 0; void SetSelectedSet(Selection new_selection);
/// @brief Obtain the selected set /// @brief Obtain the selected set
/// @param[out] selection Filled with the selected set on return /// @param[out] selection Filled with the selected set on return
virtual void GetSelectedSet(Selection &selection) const = 0; void GetSelectedSet(Selection &out) const { out = selection; }
/// @brief Obtain the selected set /// @brief Obtain the selected set
/// @return The selected set /// @return The selected set
virtual Selection const& GetSelectedSet() const = 0; Selection const& GetSelectedSet() const { return selection; }
/// @brief Set both the selected set and active line /// @brief Set both the selected set and active line
/// @param new_line Subtitle line to become the new active line /// @param new_line Subtitle line to become the new active line
@ -114,26 +97,22 @@ public:
/// This sets both the active line and selected set before announcing the /// This sets both the active line and selected set before announcing the
/// change to either of them, and is guaranteed to announce the active line /// change to either of them, and is guaranteed to announce the active line
/// change before the selection change. /// change before the selection change.
virtual void SetSelectionAndActive(Selection new_selection, ItemDataType new_line) = 0; void SetSelectionAndActive(Selection new_selection, AssDialogue *new_line);
/// @brief Change the active line to the next in sequence /// @brief Change the active line to the next in sequence
/// ///
/// If there is no logical next line in sequence, no change happens. This should /// If there is no logical next line in sequence, no change happens. This should
/// also reset the selected set to consist of exactly the active line, if the /// also reset the selected set to consist of exactly the active line, if the
/// active line was changed. /// active line was changed.
virtual void NextLine() = 0; void NextLine();
/// @brief Change the active line to the previous in sequence /// @brief Change the active line to the previous in sequence
/// ///
/// If there is no logical previous line in sequence, no change happens. This /// If there is no logical previous line in sequence, no change happens. This
/// should also reset the selected set to consist of exactly the active line, if /// should also reset the selected set to consist of exactly the active line, if
/// the active line was changed. /// the active line was changed.
virtual void PrevLine() = 0; void PrevLine();
DEFINE_SIGNAL_ADDERS(AnnounceSelectedSetChanged, AddSelectionListener) DEFINE_SIGNAL_ADDERS(AnnounceSelectedSetChanged, AddSelectionListener)
DEFINE_SIGNAL_ADDERS(AnnounceActiveLineChanged, AddActiveLineListener) DEFINE_SIGNAL_ADDERS(AnnounceActiveLineChanged, AddActiveLineListener)
}; };
class AssDialogue;
typedef SelectionController<AssDialogue *> SubtitleSelectionController;
typedef SubtitleSelectionController::Selection SubtitleSelection;

View file

@ -23,7 +23,6 @@
#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"
@ -92,7 +91,7 @@ struct SubsController::UndoInfo {
sort(begin(selection), end(selection)); sort(begin(selection), end(selection));
AssDialogue *active_line = nullptr; AssDialogue *active_line = nullptr;
SubtitleSelection new_sel; Selection new_sel;
for (auto const& info : script_info) for (auto const& info : script_info)
c->ass->Info.push_back(*new AssInfo(info.first, info.second)); c->ass->Info.push_back(*new AssInfo(info.first, info.second));
@ -108,11 +107,8 @@ struct SubsController::UndoInfo {
new_sel.insert(copy); new_sel.insert(copy);
} }
c->subsGrid->BeginBatch();
c->selectionController->SetSelectedSet(std::set<AssDialogue *>{});
c->ass->Commit("", AssFile::COMMIT_NEW); c->ass->Commit("", AssFile::COMMIT_NEW);
c->selectionController->SetSelectionAndActive(std::move(new_sel), active_line); c->selectionController->SetSelectionAndActive(std::move(new_sel), active_line);
c->subsGrid->EndBatch();
} }
void UpdateActiveLine(const agi::Context *c) { void UpdateActiveLine(const agi::Context *c) {
@ -153,7 +149,7 @@ SubsController::SubsController(agi::Context *context)
}); });
} }
void SubsController::SetSelectionController(SelectionController<AssDialogue *> *selection_controller) { void SubsController::SetSelectionController(SelectionController *selection_controller) {
active_line_connection = context->selectionController->AddActiveLineListener(&SubsController::OnActiveLineChanged, this); active_line_connection = context->selectionController->AddActiveLineListener(&SubsController::OnActiveLineChanged, this);
selection_connection = context->selectionController->AddSelectionListener(&SubsController::OnSelectionChanged, this); selection_connection = context->selectionController->AddSelectionListener(&SubsController::OnSelectionChanged, this);
} }
@ -277,6 +273,7 @@ void SubsController::Close() {
blank.swap(*context->ass); blank.swap(*context->ass);
context->ass->LoadDefault(); context->ass->LoadDefault();
context->ass->Commit("", AssFile::COMMIT_NEW); context->ass->Commit("", AssFile::COMMIT_NEW);
FileOpen(filename);
} }
int SubsController::TryToClose(bool allow_cancel) const { int SubsController::TryToClose(bool allow_cancel) const {

View file

@ -25,7 +25,7 @@
class AssDialogue; class AssDialogue;
class AssFile; class AssFile;
struct AssFileCommit; struct AssFileCommit;
template<typename T> class SelectionController; class SelectionController;
namespace agi { struct Context; } namespace agi { struct Context; }
@ -74,7 +74,7 @@ public:
/// ///
/// Required due to that the selection controller is the subtitles grid, and /// Required due to that the selection controller is the subtitles grid, and
/// so is created long after the subtitles controller /// so is created long after the subtitles controller
void SetSelectionController(SelectionController<AssDialogue *> *selection_controller); void SetSelectionController(SelectionController *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;

View file

@ -48,6 +48,7 @@
#include "options.h" #include "options.h"
#include "placeholder_ctrl.h" #include "placeholder_ctrl.h"
#include "scintilla_text_selection_controller.h" #include "scintilla_text_selection_controller.h"
#include "selection_controller.h"
#include "subs_edit_ctrl.h" #include "subs_edit_ctrl.h"
#include "timeedit_ctrl.h" #include "timeedit_ctrl.h"
#include "tooltip_manager.h" #include "tooltip_manager.h"
@ -383,7 +384,6 @@ void SubsEditBox::OnActiveLineChanged(AssDialogue *new_line) {
} }
void SubsEditBox::OnSelectedSetChanged() { void SubsEditBox::OnSelectedSetChanged() {
sel = c->selectionController->GetSelectedSet();
initial_times.clear(); initial_times.clear();
} }
@ -421,6 +421,7 @@ void SubsEditBox::OnChange(wxStyledTextEvent &event) {
template<class setter> template<class setter>
void SubsEditBox::SetSelectedRows(setter set, wxString const& desc, int type, bool amend) { void SubsEditBox::SetSelectedRows(setter set, wxString const& desc, int type, bool amend) {
auto const& sel = c->selectionController->GetSelectedSet();
for_each(sel.begin(), sel.end(), set); for_each(sel.begin(), sel.end(), set);
file_changed_slot.Block(); file_changed_slot.Block();
@ -449,6 +450,7 @@ void SubsEditBox::CommitText(wxString const& desc) {
} }
void SubsEditBox::CommitTimes(TimeField field) { void SubsEditBox::CommitTimes(TimeField field) {
auto const& sel = c->selectionController->GetSelectedSet();
for (AssDialogue *d : sel) { for (AssDialogue *d : sel) {
if (!initial_times.count(d)) if (!initial_times.count(d))
initial_times[d] = std::make_pair(d->Start, d->End); initial_times[d] = std::make_pair(d->Start, d->End);

View file

@ -45,8 +45,6 @@
#include <libaegisub/signal.h> #include <libaegisub/signal.h>
#include "selection_controller.h"
namespace agi { namespace vfr { class Framerate; } } namespace agi { namespace vfr { class Framerate; } }
namespace agi { struct Context; } namespace agi { struct Context; }
class AssDialogue; class AssDialogue;
@ -80,8 +78,6 @@ class SubsEditBox final : public wxPanel {
/// Currently active dialogue line /// Currently active dialogue line
AssDialogue *line = nullptr; AssDialogue *line = nullptr;
/// Last seen grid selection
SubtitleSelection sel;
/// Are the buttons currently split into two lines? /// Are the buttons currently split into two lines?
bool button_bar_split = true; bool button_bar_split = true;

View file

@ -28,6 +28,7 @@
#include "ass_time.h" #include "ass_time.h"
#include "include/aegisub/context.h" #include "include/aegisub/context.h"
#include "options.h" #include "options.h"
#include "selection_controller.h"
#include "utils.h" #include "utils.h"
#include "video_context.h" #include "video_context.h"
#include "video_display.h" #include "video_display.h"
@ -297,7 +298,7 @@ void VisualTool<FeatureType>::SetSelection(FeatureType *feat, bool clear) {
sel_features.clear(); sel_features.clear();
if (sel_features.insert(feat).second && feat->line) { if (sel_features.insert(feat).second && feat->line) {
SubtitleSelection sel; Selection sel;
if (!clear) if (!clear)
sel = c->selectionController->GetSelectedSet(); sel = c->selectionController->GetSelectedSet();
if (sel.insert(feat->line).second) if (sel.insert(feat->line).second)
@ -311,7 +312,7 @@ void VisualTool<FeatureType>::RemoveSelection(FeatureType *feat) {
for (auto sel : sel_features) for (auto sel : sel_features)
if (sel->line == feat->line) return; if (sel->line == feat->line) return;
SubtitleSelection sel = c->selectionController->GetSelectedSet(); auto sel = c->selectionController->GetSelectedSet();
// Don't deselect the only selected line // Don't deselect the only selected line
if (sel.size() <= 1) return; if (sel.size() <= 1) return;

View file

@ -21,7 +21,6 @@
#pragma once #pragma once
#include "gl_wrap.h" #include "gl_wrap.h"
#include "selection_controller.h"
#include "vector2d.h" #include "vector2d.h"
#include <libaegisub/owning_intrusive_list.h> #include <libaegisub/owning_intrusive_list.h>

View file

@ -24,6 +24,7 @@
#include "gl_text.h" #include "gl_text.h"
#include "include/aegisub/context.h" #include "include/aegisub/context.h"
#include "selection_controller.h"
#include "video_display.h" #include "video_display.h"
#include <libaegisub/color.h> #include <libaegisub/color.h>

View file

@ -27,6 +27,7 @@
#include "include/aegisub/context.h" #include "include/aegisub/context.h"
#include "libresrc/libresrc.h" #include "libresrc/libresrc.h"
#include "options.h" #include "options.h"
#include "selection_controller.h"
#include "utils.h" #include "utils.h"
#include "video_context.h" #include "video_context.h"
#include "video_display.h" #include "video_display.h"