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_selection_controller.cpp" />
<ClCompile Include="$(SrcDir)search_replace_engine.cpp" />
<ClCompile Include="$(SrcDir)selection_controller.cpp" />
<ClCompile Include="$(SrcDir)spellchecker.cpp" />
<ClCompile Include="$(SrcDir)spellchecker_hunspell.cpp" />
<ClCompile Include="$(SrcDir)spline.cpp" />

View File

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

View File

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

View File

@ -277,7 +277,7 @@ void AssKaraoke::SplitLines(std::set<AssDialogue*> const& lines, agi::Context *c
AssKaraoke kara;
SubtitleSelection sel = c->selectionController->GetSelectedSet();
Selection sel = c->selectionController->GetSelectedSet();
bool did_split = false;
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; }
#include "audio_marker.h"
#include "selection_controller.h"
/// @class AudioTimingController
/// @brief Base class for objects controlling audio timing

View File

@ -348,7 +348,7 @@ class AudioTimingControllerDialogue final : public AudioTimingController {
void RegenerateSelectedLines();
/// 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
void RegenerateMarkers();
@ -727,7 +727,7 @@ void AudioTimingControllerDialogue::RegenerateInactiveLines()
bool was_empty = inactive_lines.empty();
inactive_lines.clear();
SubtitleSelection const& sel = context->selectionController->GetSelectedSet();
auto const& sel = context->selectionController->GetSelectedSet();
switch (int mode = inactive_line_mode->GetInt())
{
@ -778,7 +778,7 @@ void AudioTimingControllerDialogue::RegenerateInactiveLines()
RegenerateMarkers();
}
void AudioTimingControllerDialogue::AddInactiveLine(SubtitleSelection const& sel, AssDialogue *diag)
void AudioTimingControllerDialogue::AddInactiveLine(Selection const& sel, AssDialogue *diag)
{
if (sel.count(diag)) return;

View File

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

View File

@ -834,7 +834,7 @@ namespace Automation4 {
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();
lua_newtable(L);

View File

@ -48,6 +48,7 @@
#include "frame_main.h"
#include "options.h"
#include "utils.h"
#include "selection_controller.h"
#include "subs_controller.h"
#include "video_context.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)
: wxWindow(parent, -1, wxDefaultPosition, size, style, name)
BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context)
: wxWindow(parent, -1, wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS | wxSUNKEN_BORDER)
, scrollBar(new wxScrollBar(this, GRID_SCROLLBAR, wxDefaultPosition, wxDefaultSize, wxSB_VERTICAL))
, seek_listener(context->videoController->AddSeekListener(std::bind(&BaseGrid::Refresh, this, false, nullptr)))
, context(context)
@ -108,26 +109,30 @@ BaseGrid::BaseGrid(wxWindow* parent, agi::Context *context, const wxSize& size,
UpdateStyle();
OnHighlightVisibleChange(*OPT_GET("Subtitle/Grid/Highlight Subtitles in Frame"));
OPT_SUB("Subtitle/Grid/Font Face", &BaseGrid::UpdateStyle, this);
OPT_SUB("Subtitle/Grid/Font Size", &BaseGrid::UpdateStyle, this);
OPT_SUB("Subtitle/Grid/Highlight Subtitles in Frame", &BaseGrid::OnHighlightVisibleChange, this);
context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this);
context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this);
context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this);
connections.push_back(context->ass->AddCommitListener(&BaseGrid::OnSubtitlesCommit, this));
connections.push_back(context->subsController->AddFileOpenListener(&BaseGrid::OnSubtitlesOpen, this));
connections.push_back(context->subsController->AddFileSaveListener(&BaseGrid::OnSubtitlesSave, this));
OPT_SUB("Colour/Subtitle Grid/Active Border", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Background/Background", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Background/Comment", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Header", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Left Column", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Lines", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Selection", &BaseGrid::UpdateStyle, this);
OPT_SUB("Colour/Subtitle Grid/Standard", &BaseGrid::UpdateStyle, this);
OPT_SUB("Subtitle/Grid/Hide Overrides", std::bind(&BaseGrid::Refresh, this, false, nullptr));
connections.push_back(context->selectionController->AddActiveLineListener(&BaseGrid::OnActiveLineChanged, this));
connections.push_back(context->selectionController->AddSelectionListener([&]{ Refresh(false); }));
connections.push_back(OPT_SUB("Subtitle/Grid/Font Face", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Subtitle/Grid/Font Size", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Active Border", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Background", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Comment", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Inframe", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Selected Comment", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Background/Selection", &BaseGrid::UpdateStyle, this));
connections.push_back(OPT_SUB("Colour/Subtitle Grid/Collision", &BaseGrid::UpdateStyle, this));
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);
}
@ -155,34 +160,16 @@ void BaseGrid::OnSubtitlesCommit(int type) {
}
if (type & AssFile::COMMIT_DIAG_TIME)
Refresh(false);
//RefreshRect(wxRect(time_cols_x, 0, time_cols_w, GetClientSize().GetHeight()), false);
else if (type & AssFile::COMMIT_DIAG_TEXT)
RefreshRect(wxRect(text_col_x, 0, text_col_w, GetClientSize().GetHeight()), false);
}
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"));
EndBatch();
SetColumnWidths();
}
void BaseGrid::OnSubtitlesSave() {
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) {
@ -239,20 +226,7 @@ void BaseGrid::UpdateStyle() {
Refresh(false);
}
void BaseGrid::ClearMaps() {
index_line_map.clear();
line_index_map.clear();
selection.clear();
yPos = 0;
AdjustScrollbar();
AnnounceSelectedSetChanged();
}
void BaseGrid::UpdateMaps() {
BeginBatch();
int active_row = line_index_map[active_line];
index_line_map.clear();
line_index_map.clear();
@ -261,47 +235,17 @@ void BaseGrid::UpdateMaps() {
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();
Refresh(false);
}
void BaseGrid::BeginBatch() {
++batch_level;
}
void BaseGrid::EndBatch() {
--batch_level;
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;
void BaseGrid::OnActiveLineChanged(AssDialogue *new_active) {
if (new_active) {
int row = GetDialogueIndex(new_active);
MakeRowVisible(row);
extendRow = row;
Refresh(false);
}
AdjustScrollbar();
}
void BaseGrid::MakeRowVisible(int row) {
@ -319,23 +263,19 @@ void BaseGrid::SelectRow(int row, bool addToSelected, bool select) {
AssDialogue *line = index_line_map[row];
if (!addToSelected) {
Selection sel;
if (select) sel.insert(line);
SetSelectedSet(std::move(sel));
context->selectionController->SetSelectedSet(Selection{line});
return;
}
if (select && selection.find(line) == selection.end()) {
selection.insert(line);
AnnounceSelectedSetChanged();
bool selected = !!context->selectionController->GetSelectedSet().count(line);
if (select != selected) {
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 &) {
@ -401,9 +341,13 @@ void BaseGrid::DrawImage(wxDC &dc, bool paint_columns[]) {
if (override_mode == 1)
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++) {
int curRow = i + yPos - 1;
RowColor curColor = COLOR_DEFAULT;
AssDialogue *curDiag = nullptr;
// Header
if (i == 0) {
@ -411,7 +355,7 @@ void BaseGrid::DrawImage(wxDC &dc, bool paint_columns[]) {
dc.SetTextForeground(text_standard);
}
// Lines
else if (AssDialogue *curDiag = GetDialogue(curRow)) {
else if ((curDiag = GetDialogue(curRow))) {
GetRowStrings(curRow, curDiag, paint_columns, strings, !!override_mode, replace_char);
bool inSel = !!selection.count(curDiag);
@ -467,28 +411,27 @@ void BaseGrid::DrawImage(wxDC &dc, bool paint_columns[]) {
// Draw grid
dc.DestroyClippingRegion();
dc.SetPen(grid_pen);
dc.DrawLine(0,dy+lineHeight,w,dy+lineHeight);
if (curDiag == active_line) {
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);
}
// Draw grid columns
int dx = 0;
dc.SetPen(grid_pen);
for (int i=0;i<10;i++) {
for (int i = 0; i < 10; ++i) {
dx += colWidth[i];
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(dx, 0, dx, maxH);
}
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 {
@ -604,8 +547,11 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
// but we don't want to scroll until the mouse moves or the button is
// released, to avoid selecting multiple lines on a click
int old_y_pos = yPos;
SetActiveLine(dlg);
context->selectionController->SetActiveLine(dlg);
ScrollTo(old_y_pos);
extendRow = row;
auto const& selection = context->selectionController->GetSelectedSet();
// Toggle selected
if (click && ctrl && !shift && !alt) {
@ -643,7 +589,7 @@ void BaseGrid::OnMouseEvent(wxMouseEvent &event) {
if (ctrl) newsel = selection;
for (int i = i1; i <= i2; i++)
newsel.insert(GetDialogue(i));
SetSelectedSet(std::move(newsel));
context->selectionController->SetSelectedSet(std::move(newsel));
return;
}
@ -830,7 +776,6 @@ void BaseGrid::SetColumnWidths() {
colWidth[i] += 10;
}
// Set size of last
int total = std::accumulate(colWidth, colWidth + 10, 0);
colWidth[10] = std::max(w - total, 0);
@ -911,8 +856,8 @@ void BaseGrid::OnKeyDown(wxKeyEvent &event) {
}
int old_extend = extendRow;
int next = mid(0, GetDialogueIndex(active_line) + dir * step, GetRows() - 1);
SetActiveLine(GetDialogue(next));
int next = mid(0, GetDialogueIndex(context->selectionController->GetActiveLine()) + dir * step, GetRows() - 1);
context->selectionController->SetActiveLine(GetDialogue(next));
// Move selection
if (!ctrl && !shift && !alt) {
@ -921,10 +866,8 @@ void BaseGrid::OnKeyDown(wxKeyEvent &event) {
}
// Move active only
if (alt && !shift && !ctrl) {
Refresh(false);
if (alt && !shift && !ctrl)
return;
}
// Shift-selection
if (shift && !ctrl && !alt) {
@ -940,7 +883,7 @@ void BaseGrid::OnKeyDown(wxKeyEvent &event) {
for (int i = begin; i <= end; i++)
newsel.insert(GetDialogue(i));
SetSelectedSet(std::move(newsel));
context->selectionController->SetSelectedSet(std::move(newsel));
MakeRowVisible(next);
return;
@ -953,59 +896,3 @@ void BaseGrid::SetByFrame(bool state) {
SetColumnWidths();
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
///
#pragma once
#include <libaegisub/signal.h>
#include <map>
@ -41,15 +39,14 @@
#include <vector>
#include <wx/window.h>
#include "selection_controller.h"
namespace agi {
struct Context;
class OptionValue;
}
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
bool holding = false; ///< Is a drag selection in process?
wxFont font; ///< Current grid font
@ -61,19 +58,9 @@ class BaseGrid final : public wxWindow, public SubtitleSelectionController {
/// keyboard, shift-clicking or dragging
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::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
/// blocked if the relevant option is disabled
agi::signal::Connection seek_listener;
@ -93,6 +80,7 @@ class BaseGrid final : public wxWindow, public SubtitleSelectionController {
void OnSubtitlesCommit(int type);
void OnSubtitlesOpen();
void OnSubtitlesSave();
void OnActiveLineChanged(AssDialogue *);
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;
@ -115,35 +103,15 @@ class BaseGrid final : public wxWindow, public SubtitleSelectionController {
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
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();
/// @brief Update the row <-> AssDialogue mappings
void UpdateMaps();
void UpdateStyle();
void SelectRow(int row, bool addToSelected = false, bool select=true);
int GetRows() const { return index_line_map.size(); }
void MakeRowVisible(int row);
@ -157,8 +125,11 @@ public:
/// @return Subtitle index for object, or -1 if unknown subtitle
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();
void SetByFrame(bool state);
DECLARE_EVENT_TABLE()
};

View File

@ -195,7 +195,7 @@ struct audio_save_clip final : public Command {
}
void operator()(agi::Context *c) override {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet();
auto const& sel = c->selectionController->GetSelectedSet();
if (sel.empty()) return;
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;
AssDialogue *first = nullptr;
SubtitleSelection newsel;
Selection newsel;
boost::char_separator<char> sep("\r\n");
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) {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet();
auto const& sel = c->selectionController->GetSelectedSet();
std::string text = c->selectionController->GetActiveLine()->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 void copy_lines(agi::Context *c) {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet();
auto const& sel = c->selectionController->GetSelectedSet();
SetClipboard(join(c->ass->Events
| filtered([&](AssDialogue &d) { return sel.count(&d); })
| 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) {
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
AssDialogue *pre_sel = nullptr;
@ -595,7 +595,7 @@ static void duplicate_lines(agi::Context *c, int shift) {
auto const& sel = c->selectionController->GetSelectedSet();
auto in_selection = [&](AssDialogue const& d) { return sel.count(const_cast<AssDialogue *>(&d)); };
SubtitleSelectionController::Selection new_sel;
Selection new_sel;
AssDialogue *new_active = nullptr;
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) {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet();
auto const& sel = c->selectionController->GetSelectedSet();
AssDialogue *first = nullptr;
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();
SubtitleSelection new_selection;
Selection new_selection;
for (auto& line : parsed)
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
SubtitleSelection lines, new_sel;
Selection lines, new_sel;
boost::copy(c->ass->Events | agi::address_of, inserter(lines, lines.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 {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet();
auto const& sel = c->selectionController->GetSelectedSet();
if (sel.size() == 2) {
(*sel.begin())->swap_nodes(**sel.rbegin());
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")
void operator()(agi::Context *c) override {
SubtitleSelection sel;
Selection sel;
boost::copy(c->ass->Events | agi::address_of, inserter(sel, sel.end()));
c->selectionController->SetSelectedSet(std::move(sel));
}
@ -390,7 +390,7 @@ struct subtitle_select_visible final : public Command {
if (!c->videoController->IsLoaded()) return;
c->videoController->Stop();
SubtitleSelectionController::Selection new_selection;
Selection new_selection;
int frame = c->videoController->GetFrameN();
for (auto& diag : c->ass->Events) {

View File

@ -62,7 +62,7 @@ namespace {
struct validate_adjoinable : public Command {
CMD_TYPE(COMMAND_VALIDATE)
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();
size_t found = 0;
@ -138,8 +138,8 @@ struct time_frame_current final : public validate_video_loaded {
void operator()(agi::Context *c) override {
if (!c->videoController->IsLoaded()) return;
SubtitleSelection const& sel = c->selectionController->GetSelectedSet();
const AssDialogue *active_line = c->selectionController->GetActiveLine();
auto const& sel = c->selectionController->GetSelectedSet();
const auto active_line = c->selectionController->GetActiveLine();
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) {
SubtitleSelection const& sel = c->selectionController->GetSelectedSet();
auto const& sel = c->selectionController->GetSelectedSet();
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());
SubtitleSelection old_sel, new_sel;
Selection old_sel, new_sel;
if (action != Action::SET)
con->selectionController->GetSelectedSet(old_sel);

View File

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

View File

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

View File

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

View File

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

View File

@ -35,11 +35,6 @@
#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/menu.h"
#include "include/aegisub/toolbar.h"
@ -61,6 +56,7 @@
#include "libresrc/libresrc.h"
#include "main.h"
#include "options.h"
#include "selection_controller.h"
#include "search_replace_engine.h"
#include "subs_controller.h"
#include "subs_edit_box.h"
@ -72,6 +68,11 @@
#include "video_display.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 <wx/dnd.h>
@ -82,7 +83,7 @@
#include <wx/sysopt.h>
enum {
ID_APP_TIMER_STATUSCLEAR = 12002
ID_APP_TIMER_STATUSCLEAR = 12002
};
#ifdef WITH_STARTUPLOG
@ -220,8 +221,8 @@ FrameMain::FrameMain()
context->local_scripts = new Automation4::LocalScriptManager(context.get());
// Initialized later due to that the selection controller is currently the subtitles grid
context->selectionController = nullptr;
context->selectionController = new SelectionController(context.get());
context->subsController->SetSelectionController(context->selectionController);
context->videoController = VideoContext::Get(); // derp
context->videoController->AddVideoOpenListener(&FrameMain::OnVideoOpen, this);
@ -264,7 +265,6 @@ 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));
@ -302,40 +302,13 @@ FrameMain::FrameMain()
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 () {
wxGetApp().frame = nullptr;
context->videoController->SetVideo("");
context->audioController->CloseAudio();
// SubsGrid needs to be deleted last due to being the selection
// controller, but everything else needs to be deleted before the context
// is cleaned up
delete_children(this, SubsGrid);
DestroyChildren();
delete context->ass;
delete context->audioController;
@ -367,8 +340,7 @@ void FrameMain::InitContents() {
wxPanel *Panel = new wxPanel(this,-1,wxDefaultPosition,wxDefaultSize,wxTAB_TRAVERSAL | wxCLIP_CHILDREN);
StartupLog("Create subtitles grid");
context->subsGrid = SubsGrid = new BaseGrid(Panel, context.get(), wxDefaultSize, wxWANTS_CHARS | wxSUNKEN_BORDER);
context->selectionController = context->subsGrid;
context->subsGrid = new BaseGrid(Panel, context.get());
context->search = new SearchReplaceEngine(context.get());
context->initialLineState = new InitialLineState(context.get());
@ -391,7 +363,7 @@ void FrameMain::InitContents() {
MainSizer = new wxBoxSizer(wxVERTICAL);
MainSizer->Add(new wxStaticLine(Panel),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);
StartupLog("Perform layout");

View File

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

View File

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

View File

@ -301,7 +301,7 @@ bool SearchReplaceEngine::ReplaceAll() {
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;
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/
/// @file selection_controller.h
/// @ingroup controllers
/// @brief Interface declaration for the SubtitleSelectionController
#pragma once
#include <libaegisub/signal.h>
#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 {
public:
typedef std::set<ItemDataType> Selection;
protected:
agi::signal::Signal<ItemDataType> AnnounceActiveLineChanged;
agi::signal::Signal<AssDialogue *> AnnounceActiveLineChanged;
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:
/// Virtual destructor for safety
virtual ~SelectionController() { }
SelectionController(agi::Context *context);
/// @brief Change the active line
/// @param new_line Subtitle line to become the new active line
@ -81,11 +64,11 @@ public:
/// the active line was actually changed.
///
/// 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
/// @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
/// @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
/// because the new set was identical to the old set, no change notification may
/// be sent.
virtual void SetSelectedSet(Selection new_selection) = 0;
void SetSelectedSet(Selection new_selection);
/// @brief Obtain the selected set
/// @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
/// @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
/// @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
/// change to either of them, and is guaranteed to announce the active line
/// 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
///
/// If there is no logical next line in sequence, no change happens. This should