Change the grid to use more sensible maps to keep track of index/object mappings for subtitle lines, and follow the SelectionController model. (Hopefully the new maps can also give slightly better performance.)

Try to keep the original API intact. Some redundant or unused functions in SubtitleGrid were removed. The LoadFromAss() function was renamed to UpdateMaps() since that was its real purpose. (Note that SubtitleGrid now has an UpdateMaps() function that overshadows BaseGrid::UpdateMaps(), but SubtitleGrid::UpdateMaps() calls the hidden function. They are not virtual.)
This does not yet fix visual tool feature selection.
Generally, things just feel more broken still. Further work will fix things.

Originally committed to SVN as r4603.
This commit is contained in:
Niels Martin Hansen 2010-06-26 07:26:27 +00:00
parent 70d41d31b2
commit c4a27da4fc
7 changed files with 173 additions and 246 deletions

View File

@ -100,6 +100,7 @@ BaseGrid::BaseGrid(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wx
/// @brief Destructor
///
BaseGrid::~BaseGrid() {
ClearMaps();
delete bmp;
}
@ -140,16 +141,16 @@ void BaseGrid::UpdateStyle() {
/// @brief Clears grid
///
void BaseGrid::Clear () {
Selection lines_removed;
GetSelectedSet(lines_removed);
AnnounceSelectedSetChanged(Selection(), lines_removed);
void BaseGrid::ClearMaps() {
Selection old_selection(selection);
diagMap.clear();
diagPtrMap.clear();
selMap.clear();
index_line_map.clear();
line_index_map.clear();
selection.clear();
yPos = 0;
AdjustScrollbar();
AnnounceSelectedSetChanged(Selection(), old_selection);
}
@ -211,34 +212,33 @@ void BaseGrid::MakeCellVisible(int row, int col,bool center) {
/// @param select
///
void BaseGrid::SelectRow(int row, bool addToSelected, bool select) {
if (row < 0 || (size_t)row >= selMap.size()) return;
if (row < 0 || (size_t)row >= index_line_map.size()) return;
AssDialogue *line = index_line_map[row];
if (!addToSelected) {
// Sends change notifications for itself
ClearSelection();
Selection old_selection(selection);
selection.clear();
selection.insert(line);
AnnounceSelectedSetChanged(selection, old_selection);
}
if (select != !!selMap[row]) {
selMap[row] = select;
else if (select && selection.find(line) == selection.end()) {
selection.insert(line);
if (!addToSelected) {
Refresh(false);
}
else {
int w = 0;
int h = 0;
GetClientSize(&w,&h);
RefreshRect(wxRect(0,(row+1-yPos)*lineHeight,w,lineHeight),false);
}
Selection added;
added.insert(line);
Selection lines_added;
Selection lines_removed;
if (select)
lines_added.insert(diagPtrMap[row]);
else
lines_removed.insert(diagPtrMap[row]);
AnnounceSelectedSetChanged(added, Selection());
}
AnnounceSelectedSetChanged(lines_added, lines_removed);
else if (!select && selection.find(line) != selection.end()) {
selection.erase(line);
Selection removed;
removed.insert(line);
AnnounceSelectedSetChanged(Selection(), removed);
}
}
@ -276,16 +276,11 @@ void BaseGrid::SelectVisible() {
/// @brief Unselects all cells
///
void BaseGrid::ClearSelection() {
Selection lines_removed;
GetSelectedSet(lines_removed);
Selection old_selection(selection.begin(), selection.end());
int rows = selMap.size();
for (int i=0;i<rows;i++) {
selMap[i] = false;
}
Refresh(false);
selection.clear();
AnnounceSelectedSetChanged(Selection(), lines_removed);
AnnounceSelectedSetChanged(Selection(), old_selection);
}
@ -296,8 +291,9 @@ void BaseGrid::ClearSelection() {
/// @return
///
bool BaseGrid::IsInSelection(int row, int) const {
if ((size_t)row >= selMap.size() || row < 0) return false;
return !!selMap[row];
if ((size_t)row >= index_line_map.size() || row < 0) return false;
return selection.find(index_line_map[row]) != selection.end();
}
@ -306,18 +302,27 @@ bool BaseGrid::IsInSelection(int row, int) const {
/// @return
///
int BaseGrid::GetNumberSelection() const {
return std::count(selMap.begin(), selMap.end(), 1);
return selection.size();
}
/// @brief Gets first selected row
/// @return
/// @brief Gets first selected row index
/// @return Row index of first selected row, -1 if no selection
///
int BaseGrid::GetFirstSelRow() const {
std::vector<int>::const_iterator first = std::find(selMap.begin(), selMap.end(), 1);
if (first == selMap.end()) return -1;
return std::distance(selMap.begin(), first);
Selection::const_iterator it = selection.begin();
if (it == selection.end()) return -1;
int index = GetDialogueIndex(*it);
for (; it != selection.end(); ++it) {
int other_index = GetDialogueIndex(*it);
if (other_index < index) index = other_index;
}
return index;
}
@ -340,24 +345,25 @@ int BaseGrid::GetLastSelRow() const {
/// @return Array with indices of selected lines
///
wxArrayInt BaseGrid::GetSelection(bool *cont) const {
// Prepare
int nrows = GetRows();
int last = -1;
bool continuous = true;
wxArrayInt selections;
// Scan
for (int i=0;i<nrows;i++) {
if (selMap[i]) {
selections.Add(i);
if (last != -1 && i != last+1) continuous = false;
last = i;
}
std::set<int> sel_row_indices;
for (Selection::const_iterator it = selection.begin(); it != selection.end(); ++it) {
sel_row_indices.insert(GetDialogueIndex(*it));
}
// Iterating the int set yields a sorted list
wxArrayInt res;
for (std::set<int>::iterator it = sel_row_indices.begin(); it != sel_row_indices.end(); ++it) {
res.Add(*it);
if (last != -1 && *it != last+1) continuous = false;
last = *it;
}
// Return
if (cont) *cont = continuous;
return selections;
return res;
}
@ -366,7 +372,7 @@ wxArrayInt BaseGrid::GetSelection(bool *cont) const {
/// @return
///
int BaseGrid::GetRows() const {
return diagMap.size();
return index_line_map.size();
}
@ -981,18 +987,24 @@ void BaseGrid::SetColumnWidths() {
/// @brief Gets dialogue from map
/// @param n
/// @return
/// @brief Get dialogue by index
/// @param n Index to look up
/// @return Subtitle dialogue line for index, or 0 if invalid index
///
AssDialogue *BaseGrid::GetDialogue(int n) const {
try {
if (n < 0 || (size_t)n >= diagMap.size()) return NULL;
return dynamic_cast<AssDialogue*>(*diagMap.at(n));
}
catch (...) {
return NULL;
}
if (n < 0 || n >= (int)index_line_map.size()) return 0;
return index_line_map[n];
}
/// @brief Get index by dialogue line
/// @param diag Dialogue line to look up
/// @return Subtitle index for object, or -1 if unknown subtitle
int BaseGrid::GetDialogueIndex(AssDialogue *diag) const {
std::map<AssDialogue*,int>::const_iterator it = line_index_map.find(diag);
if (it != line_index_map.end()) return it->second;
else return -1;
}
@ -1015,37 +1027,17 @@ bool BaseGrid::IsDisplayed(AssDialogue *line) {
/// @brief Update maps
///
void BaseGrid::UpdateMaps() {
// Store old
int len = selMap.size();
std::vector<AssDialogue *> tmpDiagPtrMap(diagPtrMap);
std::vector<int> tmpSelMap(selMap);
index_line_map.clear();
line_index_map.clear();
// Clear old
diagPtrMap.clear();
diagMap.clear();
selMap.clear();
// Re-generate lines
for (entryIter cur=AssFile::top->Line.begin();cur != AssFile::top->Line.end();cur++) {
AssDialogue *curdiag = dynamic_cast<AssDialogue*>(*cur);
if (curdiag) {
// Find old pos
int sel = 0;
for (int i=0;i<len;i++) {
if (tmpDiagPtrMap[i] == curdiag) {
sel = tmpSelMap[i];
break;
}
}
// Add new
diagMap.push_back(cur);
diagPtrMap.push_back(curdiag);
selMap.push_back(sel);
line_index_map[curdiag] = (int)index_line_map.size();
index_line_map.push_back(curdiag);
}
}
// Refresh
Refresh(false);
}
@ -1211,12 +1203,14 @@ wxArrayInt BaseGrid::GetRangeArray(int n1,int n2) const {
// SelectionController
void BaseGrid::GetSelectedSet(Selection &selection) const {
for (size_t i = 0; i < selMap.size(); ++i) {
if (selMap[i] != 0) {
selection.insert(GetDialogue((int)i));
}
}
void BaseGrid::GetSelectedSet(Selection &os) const {
os.insert(selection.begin(), selection.end());
}
void BaseGrid::SetSelectedSet(const Selection &new_selection) {
Selection old_selection(selection);
selection = new_selection;
AnnounceSelectedSetChanged(new_selection, old_selection);
}

View File

@ -105,6 +105,10 @@ private:
void DrawImage(wxDC &dc);
Selection selection;
std::vector<AssDialogue*> index_line_map;
std::map<AssDialogue*,int> line_index_map;
protected:
/// DOCME
@ -124,14 +128,11 @@ protected:
/// DOCME
int yPos;
/// DOCME
std::vector<int> selMap;
public:
// SelectionController implementation
virtual void SetActiveLine(AssDialogue *new_line) { }
virtual AssDialogue * GetActiveLine() const { return 0; }
virtual void SetSelectedSet(const Selection &new_selection) { }
virtual void SetSelectedSet(const Selection &new_selection);
virtual void GetSelectedSet(Selection &selection) const;
virtual void NextLine() { }
virtual void PrevLine() { }
@ -145,12 +146,6 @@ public:
/// DOCME
bool byFrame;
/// DOCME
std::vector<entryIter> diagMap;
/// DOCME
std::vector<AssDialogue *> diagPtrMap;
void AdjustScrollbar();
void SetColumnWidths();
void BeginBatch();
@ -167,7 +162,7 @@ public:
void SelectVisible();
wxArrayInt GetSelection(bool *continuous=NULL) const;
void Clear();
void ClearMaps();
void UpdateMaps();
void UpdateStyle();
@ -176,6 +171,7 @@ public:
void MakeCellVisible(int row, int col,bool center=true);
AssDialogue *GetDialogue(int n) const;
int GetDialogueIndex(AssDialogue *diag) const;
BaseGrid(wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxWANTS_CHARS, const wxString& name = wxPanelNameStr);
~BaseGrid();

View File

@ -274,8 +274,7 @@ void DialogStyling::OnActivate(wxActivateEvent &event) {
PlayVideoButton->Enable(video->IsLoaded());
PlayAudioButton->Enable(audio->loaded);
// Update grid
if (grid->ass != AssFile::top)
grid->LoadFromAss(AssFile::top,false,true);
grid->UpdateMaps();
// Fix style list
Styles->Set(grid->ass->GetStyles());
// Fix line selection

View File

@ -628,10 +628,11 @@ void FrameMain::InitContents() {
void FrameMain::DeInitContents() {
if (detachedVideo) detachedVideo->Destroy();
if (stylingAssistant) stylingAssistant->Destroy();
AssFile::StackReset();
delete AssFile::top;
SubsGrid->ClearMaps();
delete EditBox;
delete videoBox;
AssFile::StackReset();
delete AssFile::top;
HelpButton::ClearPages();
}
@ -698,17 +699,18 @@ void FrameMain::LoadSubtitles (wxString filename,wxString charset) {
}
// Proceed into loading
SubsGrid->Clear();
SubsGrid->ClearMaps();
AssFile::StackReset();
if (isFile) {
AssFile::top->Load(filename,charset);
SubsGrid->LoadFromAss(AssFile::top,false,true);
SubsGrid->SetSelectedSet(SubtitleSelectionController::Selection());
SubsGrid->UpdateMaps();
wxFileName fn(filename);
StandardPaths::SetPathValue(_T("?script"),fn.GetPath());
OPT_SET("Path/Last/Subtitles")->SetString(STD_STR(fn.GetPath()));
}
else {
SubsGrid->LoadDefault(AssFile::top);
SubsGrid->LoadDefault();
StandardPaths::SetPathValue(_T("?script"),_T(""));
}
}

View File

@ -1051,7 +1051,7 @@ void FrameMain::OnAutomationMacro (wxCommandEvent &event) {
int first_sel = SubsGrid->GetFirstSelRow();
// Clear all maps from the subs grid before running the macro
// The stuff done by the macro might invalidate some of the iterators held by the grid, which will cause great crashing
SubsGrid->Clear();
SubsGrid->ClearMaps();
// Run the macro...
activeMacroItems[event.GetId()-Menu_Automation_Macro]->Process(SubsGrid->ass, selected_lines, first_sel, this);
// Have the grid update its maps, this properly refreshes it to reflect the changed subs
@ -1183,7 +1183,7 @@ void FrameMain::OnShiftToFrame (wxCommandEvent &) {
void FrameMain::OnUndo(wxCommandEvent&) {
VideoContext::Get()->Stop();
AssFile::StackPop();
SubsGrid->LoadFromAss(AssFile::top,true);
SubsGrid->UpdateMaps();
AssFile::Popping = false;
}
@ -1191,7 +1191,7 @@ void FrameMain::OnUndo(wxCommandEvent&) {
void FrameMain::OnRedo(wxCommandEvent&) {
VideoContext::Get()->Stop();
AssFile::StackRedo();
SubsGrid->LoadFromAss(AssFile::top,true);
SubsGrid->UpdateMaps();
AssFile::Popping = false;
}

View File

@ -41,6 +41,7 @@
#ifndef AGI_PRE
#include <algorithm>
#include <utility>
#include <wx/clipbrd.h>
#include <wx/filename.h>
@ -121,6 +122,7 @@ SubtitlesGrid::SubtitlesGrid(FrameMain* parentFr, wxWindow *parent, wxWindowID i
/// @brief Destructor
///
SubtitlesGrid::~SubtitlesGrid() {
ClearMaps();
}
@ -793,94 +795,44 @@ void SubtitlesGrid::OnAudioClip(wxCommandEvent &event) {
/// @brief Clears grid and sets it to default
/// @param _ass
///
void SubtitlesGrid::LoadDefault (AssFile *_ass) {
if (_ass) {
ass = _ass;
}
void SubtitlesGrid::LoadDefault () {
ass = AssFile::top;
ass->LoadDefault();
LoadFromAss(NULL,false,true);
UpdateMaps();
}
/// @brief Read data from ASS file structure
/// @param _ass
/// @param keepSelection
/// @param dontModify
///
void SubtitlesGrid::LoadFromAss (AssFile *_ass,bool keepSelection,bool dontModify) {
// Store selected rows
wxArrayInt srows;
if (keepSelection) {
srows = GetSelection();
}
/// @brief Clear internal data structures
void SubtitlesGrid::ClearMaps() {
line_iter_map.clear();
BaseGrid::ClearMaps();
}
// Clear grid
/// @brief Update internal data structures
void SubtitlesGrid::UpdateMaps() {
BeginBatch();
int oldPos = yPos;
Clear();
if (keepSelection) yPos = oldPos;
// Clear from video
VideoContext::Get()->curLine = NULL;
line_iter_map.clear();
BaseGrid::ClearMaps();
// Get subtitles
if (_ass) ass = _ass;
else {
if (!ass) throw _T("Trying to set subs grid to current ass file, but there is none");
}
ass = AssFile::top;
// Run through subs adding them
int n = 0;
AssDialogue *curdiag;
ready = false;
for (entryIter cur=ass->Line.begin();cur != ass->Line.end();cur++) {
curdiag = dynamic_cast<AssDialogue*>(*cur);
if (curdiag) {
diagMap.push_back(cur);
diagPtrMap.push_back(curdiag);
selMap.push_back(false);
n++;
}
}
ready = true;
BaseGrid::UpdateMaps();
// Restore selection
if (keepSelection) {
if (srows.empty() || srows[0] >= (signed)selMap.size()) {
SelectRow(selMap.size() - 1);
}
else {
for (size_t i=0;i<srows.size();i++) {
SelectRow(srows[i],true);
}
}
for (entryIter it = ass->Line.begin(); it != ass->Line.end(); ++it) {
AssDialogue *dlg = dynamic_cast<AssDialogue*>(*it);
if (dlg) line_iter_map.insert(std::pair<AssDialogue*,entryIter>(dlg, it));
}
// Select first
else {
SelectRow(0);
}
// Commit
if (!AssFile::Popping) {
if (dontModify) AssFile::StackPush(_("load"));
else ass->FlagAsModified(_("load"));
}
CommitChanges();
// Set edit box
if (editBox) {
int nrows = GetRows();
int firstsel = -1;
for (int i=0;i<nrows;i++) {
if (IsInSelection(i,0)) {
firstsel = i;
break;
}
}
int firstsel = GetFirstSelRow();
editBox->UpdateGlobals();
if (_ass) editBox->SetToLine(firstsel);
if (ass) editBox->SetToLine(firstsel);
}
// Finish setting layout
@ -896,20 +848,13 @@ void SubtitlesGrid::LoadFromAss (AssFile *_ass,bool keepSelection,bool dontModif
/// @return
///
void SubtitlesGrid::SwapLines(int n1,int n2) {
// Check bounds and get iterators
int rows = GetRows();
if (n1 < 0 || n2 < 0 || n1 >= rows || n2 >= rows) return;
entryIter src1 = diagMap.at(n1);
entryIter src2 = diagMap.at(n2);
AssDialogue *dlg1 = GetDialogue(n1);
AssDialogue *dlg2 = GetDialogue(n2);
if (n1 == 0 || n2 == 0) return;
// Swaps
iter_swap(src1,src2);
std::swap(*dlg1, *dlg2);
UpdateMaps();
// Update mapping
diagMap[n1] = src1;
diagPtrMap[n1] = (AssDialogue*) *src1;
diagMap[n2] = src2;
diagPtrMap[n2] = (AssDialogue*) *src2;
ass->FlagAsModified(_("swap lines"));
CommitChanges();
}
@ -923,20 +868,11 @@ void SubtitlesGrid::SwapLines(int n1,int n2) {
/// @param update
///
void SubtitlesGrid::InsertLine(AssDialogue *line,int n,bool after,bool update) {
// Check bounds and get iterators
entryIter pos = diagMap.at(n);
AssDialogue *rel_line = GetDialogue(n) + (after?1:0);
entryIter pos = line_iter_map[rel_line];
// Insert
if (after) {
n++;
pos++;
}
entryIter newIter = ass->Line.insert(pos,line);
//InsertRows(n);
//SetRowToLine(n,line);
diagMap.insert(diagMap.begin() + n,newIter);
diagPtrMap.insert(diagPtrMap.begin() + n,(AssDialogue*)(*newIter));
selMap.insert(selMap.begin() + n,false);
UpdateMaps();
// Update
if (update) {
@ -1100,19 +1036,20 @@ void SubtitlesGrid::PasteLines(int n,bool pasteOver) {
void SubtitlesGrid::DeleteLines(wxArrayInt target, bool flagModified) {
// Check if it's wiping file
int deleted = 0;
entryIter before_first = line_iter_map[GetDialogue(0)]; --before_first;
// Delete lines
int size = target.Count();
for (int i=0;i<size;i++) {
delete (*diagMap.at(target[i]));
ass->Line.erase(diagMap.at(target[i]));
ass->Line.erase(line_iter_map[GetDialogue(target[i])]);
deleted++;
}
// Add default line if file was wiped
if (GetRows() == deleted) {
AssDialogue *def = new AssDialogue;
ass->Line.push_back(def);
++before_first;
ass->Line.insert(before_first, def);
}
// Update
@ -1124,7 +1061,7 @@ void SubtitlesGrid::DeleteLines(wxArrayInt target, bool flagModified) {
}
// Update selected line
int newSelected = MID(0, editBox->linen,GetRows() - 1);
int newSelected = ClampSignedInteger32(editBox->linen, 0, GetRows()-1);
editBox->SetToLine(newSelected);
SelectRow(newSelected);
}
@ -1526,25 +1463,20 @@ void SubtitlesGrid::SetVideoToSubs(bool start) {
/// @brief (ie. not as displayed in the grid but as represented in the file) Retrieve a list of selected lines in the actual ASS file
/// @brief Retrieve a list of selected lines in the actual ASS file (ie. not as displayed in the grid but as represented in the file)
/// @return
///
std::vector<int> SubtitlesGrid::GetAbsoluteSelection() {
std::vector<int> result;
result.reserve(GetNumberSelection());
int nrows = GetRows();
for (int i = 0; i != nrows; ++i) {
if (selMap.at(i)) {
entryIter l = diagMap.at(i);
int n = 0;
for (std::list<AssEntry*>::iterator j = ass->Line.begin(); j != ass->Line.end(); ++j, ++n) {
if (j == l) {
result.push_back(n);
break;
}
}
}
Selection sel;
GetSelectedSet(sel);
int line_index = 0;
for (entryIter it = ass->Line.begin(); it != ass->Line.end(); ++it, ++line_index) {
if (sel.find(dynamic_cast<AssDialogue*>(*it)) != sel.end())
result.push_back(line_index);
}
return result;
@ -1552,21 +1484,25 @@ std::vector<int> SubtitlesGrid::GetAbsoluteSelection() {
/// @brief selection vector must be sorted Update list of selected lines from absolute selection
/// @brief Update list of selected lines from absolute selection
/// @param selection
///
void SubtitlesGrid::SetSelectionFromAbsolute(std::vector<int> &selection) {
Selection newsel;
int nrows = GetRows();
std::sort(selection.begin(), selection.end());
int i = 0;
std::list<AssEntry*>::iterator j = ass->Line.begin();
int index = 0;
for (int i = 0; i != nrows; ++i) {
entryIter l = diagMap.at(i);
while(j != l && j != ass->Line.end()) ++j, ++index;
if(j == l && binary_search(selection.begin(), selection.end(), index)) {
selMap[i] = true;
} else selMap[i] = false;
for (size_t selveci = 0; selveci < selection.size(); ++selveci) {
while (i != selection[selveci] && j != ass->Line.end()) ++i, ++j;
if (j == ass->Line.end()) break; /// @todo Report error somehow
AssDialogue *dlg = dynamic_cast<AssDialogue*>(*j);
if (dlg) newsel.insert(dlg);
}
SetSelectedSet(newsel);
}

View File

@ -78,10 +78,6 @@ typedef std::list<AssEntry*>::iterator entryIter;
/// DOCME
class SubtitlesGrid: public BaseGrid {
private:
/// DOCME
bool ready;
void OnPopupMenu(bool alternate=false);
void OnKeyDown(wxKeyEvent &event);
@ -110,6 +106,8 @@ private:
void OnAudioClip(wxCommandEvent &event);
void OnShowColMenu(wxCommandEvent &event);
std::map<AssDialogue*,entryIter> line_iter_map;
public:
/// DOCME
@ -118,10 +116,12 @@ public:
SubtitlesGrid(FrameMain* parentFrame,wxWindow *parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxWANTS_CHARS, const wxString& name = wxPanelNameStr);
~SubtitlesGrid();
void LoadDefault(AssFile *ass=NULL);
void LoadFromAss(AssFile *ass=NULL,bool keepSelection=false,bool dontModify=false);
void LoadDefault();
void CommitChanges(bool force=false,bool videoOnly=false);
void ClearMaps();
void UpdateMaps();
void SetVideoToSubs(bool start);
void SetSubsToVideo(bool start);