ae5d6edaed
Originally committed to SVN as r6596.
414 lines
11 KiB
C++
414 lines
11 KiB
C++
// Copyright (c) 2006, Rodrigo Braz Monteiro
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
// * Neither the name of the Aegisub Group nor the names of its contributors
|
|
// may be used to endorse or promote products derived from this software
|
|
// without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Aegisub Project http://www.aegisub.org/
|
|
//
|
|
// $Id$
|
|
|
|
/// @file subs_grid.cpp
|
|
/// @brief Subtitles grid control in main window
|
|
/// @ingroup main_ui
|
|
///
|
|
|
|
#include "config.h"
|
|
|
|
#ifndef AGI_PRE
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#include <wx/clipbrd.h>
|
|
#include <wx/filename.h>
|
|
#include <wx/regex.h>
|
|
#include <wx/tokenzr.h>
|
|
#endif
|
|
|
|
#include "subs_grid.h"
|
|
|
|
#include "include/aegisub/context.h"
|
|
|
|
#include "ass_dialogue.h"
|
|
#include "ass_file.h"
|
|
#include "dialog_paste_over.h"
|
|
#include "main.h"
|
|
#include "utils.h"
|
|
#include "video_context.h"
|
|
|
|
SubtitlesGrid::SubtitlesGrid(wxWindow *parent, agi::Context *context, const wxSize& size, long style, const wxString& name)
|
|
: BaseGrid(parent,context,size,style,name)
|
|
{
|
|
}
|
|
|
|
static void trim_text(AssDialogue *diag) {
|
|
static wxRegEx start("^( |\t|\\\\[nNh])+");
|
|
static wxRegEx end("( |\t|\\\\[nNh])+$");
|
|
start.ReplaceFirst(&diag->Text, "");
|
|
end.ReplaceFirst(&diag->Text, "");
|
|
}
|
|
|
|
static void expand_times(AssDialogue *src, AssDialogue *dst) {
|
|
dst->Start = std::min(dst->Start, src->Start);
|
|
dst->End = std::max(dst->End, src->End);
|
|
}
|
|
|
|
/// @brief Recombine
|
|
void SubtitlesGrid::RecombineLines() {
|
|
using namespace std;
|
|
|
|
Selection selectedSet = GetSelectedSet();
|
|
if (selectedSet.size() < 2) return;
|
|
|
|
AssDialogue *activeLine = GetActiveLine();
|
|
|
|
vector<AssDialogue*> sel;
|
|
sel.reserve(selectedSet.size());
|
|
copy(selectedSet.begin(), selectedSet.end(), back_inserter(sel));
|
|
for_each(sel.begin(), sel.end(), trim_text);
|
|
sort(sel.begin(), sel.end(), &AssFile::CompStart);
|
|
|
|
typedef vector<AssDialogue*>::iterator diag_iter;
|
|
diag_iter end = sel.end() - 1;
|
|
for (diag_iter cur = sel.begin(); cur != end; ++cur) {
|
|
AssDialogue *d1 = *cur;
|
|
diag_iter d2 = cur + 1;
|
|
|
|
// 1, 1+2 (or 2+1), 2 gets turned into 1, 2, 2 so kill the duplicate
|
|
if (d1->Text == (*d2)->Text) {
|
|
expand_times(d1, *d2);
|
|
delete d1;
|
|
context->ass->Line.remove(d1);
|
|
continue;
|
|
}
|
|
|
|
// 1, 1+2, 1 turns into 1, 2, [empty]
|
|
if (d1->Text.empty()) {
|
|
delete d1;
|
|
context->ass->Line.remove(d1);
|
|
continue;
|
|
}
|
|
|
|
// 1, 1+2
|
|
while (d2 <= end && (*d2)->Text.StartsWith(d1->Text, &(*d2)->Text)) {
|
|
expand_times(*d2, d1);
|
|
trim_text(*d2);
|
|
++d2;
|
|
}
|
|
|
|
// 1, 2+1
|
|
while (d2 <= end && (*d2)->Text.EndsWith(d1->Text, &(*d2)->Text)) {
|
|
expand_times(*d2, d1);
|
|
trim_text(*d2);
|
|
++d2;
|
|
}
|
|
|
|
// 1+2, 2
|
|
while (d2 <= end && d1->Text.EndsWith((*d2)->Text, &d1->Text)) {
|
|
expand_times(d1, *d2);
|
|
trim_text(d1);
|
|
++d2;
|
|
}
|
|
|
|
// 2+1, 2
|
|
while (d2 <= end && d1->Text.StartsWith((*d2)->Text, &d1->Text)) {
|
|
expand_times(d1, *d2);
|
|
trim_text(d1);
|
|
++d2;
|
|
}
|
|
}
|
|
|
|
// Remove now non-existent lines from the selection
|
|
Selection lines;
|
|
transform(context->ass->Line.begin(), context->ass->Line.end(), inserter(lines, lines.begin()), cast<AssDialogue*>());
|
|
Selection newSel;
|
|
set_intersection(lines.begin(), lines.end(), selectedSet.begin(), selectedSet.end(), inserter(newSel, newSel.begin()));
|
|
|
|
if (newSel.empty())
|
|
newSel.insert(*lines.begin());
|
|
|
|
// Restore selection
|
|
SetSelectedSet(newSel);
|
|
if (find(newSel.begin(), newSel.end(), activeLine) == newSel.end()) {
|
|
activeLine = *newSel.begin();
|
|
}
|
|
SetActiveLine(activeLine);
|
|
|
|
context->ass->Commit(_("combining"), AssFile::COMMIT_DIAG_ADDREM | AssFile::COMMIT_DIAG_FULL);
|
|
}
|
|
|
|
/// @brief Insert a line
|
|
/// @param line
|
|
/// @param n
|
|
/// @param after
|
|
/// @param update
|
|
void SubtitlesGrid::InsertLine(AssDialogue *line,int n,bool after,bool update) {
|
|
AssDialogue *rel_line = GetDialogue(n);
|
|
entryIter pos = std::find(context->ass->Line.begin(), context->ass->Line.end(), rel_line);
|
|
if (after) ++pos;
|
|
|
|
context->ass->Line.insert(pos,line);
|
|
|
|
// Update
|
|
if (update) {
|
|
context->ass->Commit(_("line insertion"), AssFile::COMMIT_DIAG_ADDREM);
|
|
}
|
|
else {
|
|
UpdateMaps();
|
|
}
|
|
}
|
|
|
|
void SubtitlesGrid::CopyLines(wxArrayInt target) {
|
|
// Prepare text
|
|
wxString data;
|
|
AssDialogue *cur;
|
|
int nrows = target.Count();
|
|
bool first = true;
|
|
for (int i=0;i<nrows;i++) {
|
|
if (!first) data += "\r\n";
|
|
first = false;
|
|
cur = GetDialogue(target[i]);
|
|
data += cur->GetEntryData();
|
|
}
|
|
|
|
// Send to clipboard
|
|
if (wxTheClipboard->Open()) {
|
|
wxTheClipboard->SetData(new wxTextDataObject(data));
|
|
wxTheClipboard->Close();
|
|
}
|
|
}
|
|
|
|
void SubtitlesGrid::CutLines(wxArrayInt target) {
|
|
BeginBatch();
|
|
CopyLines(target);
|
|
DeleteLines(target);
|
|
EndBatch();
|
|
}
|
|
|
|
/// @brief Paste lines from clipboard
|
|
/// @param n
|
|
/// @param pasteOver
|
|
void SubtitlesGrid::PasteLines(int n,bool pasteOver) {
|
|
BeginBatch();
|
|
|
|
// Prepare text
|
|
wxString data;
|
|
|
|
// Read from clipboard
|
|
if (wxTheClipboard->Open()) {
|
|
if (wxTheClipboard->IsSupported(wxDF_TEXT)) {
|
|
wxTextDataObject rawdata;
|
|
wxTheClipboard->GetData(rawdata);
|
|
data = rawdata.GetText();
|
|
}
|
|
wxTheClipboard->Close();
|
|
}
|
|
|
|
// Check if it actually got anything
|
|
if (!data.empty()) {
|
|
// Insert data
|
|
int inserted = 0;
|
|
std::vector<bool> pasteOverOptions;
|
|
wxStringTokenizer token (data,"\r\n",wxTOKEN_STRTOK);
|
|
while (token.HasMoreTokens()) {
|
|
// Convert data into an AssDialogue
|
|
wxString curdata = token.GetNextToken();
|
|
curdata.Trim(true);
|
|
curdata.Trim(false);
|
|
AssDialogue *curdiag;
|
|
try {
|
|
// Try to interpret the line as an ASS line
|
|
curdiag = new AssDialogue(curdata);
|
|
}
|
|
catch (...) {
|
|
// Line didn't parse correctly, assume it's plain text that
|
|
// should be pasted in the Text field only
|
|
curdiag = new AssDialogue();
|
|
curdiag->Text = curdata;
|
|
// Make sure pasted plain-text lines always are blank-timed
|
|
curdiag->Start = 0;
|
|
curdiag->End = 0;
|
|
}
|
|
|
|
// Paste over
|
|
if (pasteOver) {
|
|
if (n+inserted < GetRows()) {
|
|
// Get list of options to paste over, if not asked yet
|
|
if (pasteOverOptions.empty()) {
|
|
DialogPasteOver diag(context->parent);
|
|
if (diag.ShowModal()) {
|
|
delete curdiag;
|
|
EndBatch();
|
|
return;
|
|
}
|
|
pasteOverOptions = OPT_GET("Tool/Paste Lines Over/Fields")->GetListBool();
|
|
}
|
|
|
|
// Paste over
|
|
AssDialogue *target = GetDialogue(n+inserted);
|
|
if (pasteOverOptions[0]) target->Layer = curdiag->Layer;
|
|
if (pasteOverOptions[1]) target->Start = curdiag->Start;
|
|
if (pasteOverOptions[2]) target->End = curdiag->End;
|
|
if (pasteOverOptions[3]) target->Style = curdiag->Style;
|
|
if (pasteOverOptions[4]) target->Actor = curdiag->Actor;
|
|
if (pasteOverOptions[5]) target->Margin[0] = curdiag->Margin[0];
|
|
if (pasteOverOptions[6]) target->Margin[1] = curdiag->Margin[1];
|
|
if (pasteOverOptions[7]) target->Margin[2] = curdiag->Margin[2];
|
|
if (pasteOverOptions[8]) target->Effect = curdiag->Effect;
|
|
if (pasteOverOptions[9]) target->Text = curdiag->Text;
|
|
}
|
|
delete curdiag;
|
|
}
|
|
|
|
// Paste normally
|
|
else InsertLine(curdiag,n+inserted,false,false);
|
|
|
|
// Increment insertion
|
|
inserted++;
|
|
}
|
|
|
|
// Update data post-insertion
|
|
if (inserted > 0) {
|
|
context->ass->Commit(_("paste"), pasteOver ? AssFile::COMMIT_DIAG_FULL : AssFile::COMMIT_DIAG_ADDREM);
|
|
|
|
// Set selection
|
|
if (!pasteOver) {
|
|
Selection newsel;
|
|
for (int i=n;i<n+inserted;i++) {
|
|
newsel.insert(GetDialogue(i));
|
|
}
|
|
SetSelectedSet(newsel);
|
|
SetActiveLine(GetDialogue(GetFirstSelRow()));
|
|
}
|
|
}
|
|
}
|
|
EndBatch();
|
|
}
|
|
|
|
void SubtitlesGrid::DeleteLines(wxArrayInt target, bool flagModified) {
|
|
entryIter before_first = std::find_if(context->ass->Line.begin(), context->ass->Line.end(), cast<AssDialogue*>()); --before_first;
|
|
|
|
int row = -1;
|
|
size_t deleted = 0;
|
|
for (entryIter cur = context->ass->Line.begin(); cur != context->ass->Line.end();) {
|
|
if (dynamic_cast<AssDialogue*>(*cur) && ++row == target[deleted]) {
|
|
delete *cur;
|
|
cur = context->ass->Line.erase(cur);
|
|
++deleted;
|
|
if (deleted == target.size()) break;
|
|
}
|
|
else {
|
|
++cur;
|
|
}
|
|
}
|
|
|
|
// Add default line if file was wiped
|
|
if ((size_t)GetRows() == deleted) {
|
|
AssDialogue *def = new AssDialogue;
|
|
++before_first;
|
|
context->ass->Line.insert(before_first, def);
|
|
}
|
|
|
|
if (flagModified) {
|
|
context->ass->Commit(_("delete"), AssFile::COMMIT_DIAG_ADDREM);
|
|
}
|
|
else {
|
|
UpdateMaps();
|
|
}
|
|
}
|
|
|
|
void SubtitlesGrid::AdjoinLines(int n1,int n2,bool setStart) {
|
|
if (n1 == n2) {
|
|
if (setStart) {
|
|
--n1;
|
|
}
|
|
else {
|
|
++n2;
|
|
}
|
|
}
|
|
// Set start
|
|
if (setStart) {
|
|
AssDialogue *prev = GetDialogue(n1);
|
|
AssDialogue *cur;
|
|
for (int i=n1+1;i<=n2;i++) {
|
|
cur = GetDialogue(i);
|
|
if (!cur) return;
|
|
cur->Start = prev->End;
|
|
prev = cur;
|
|
}
|
|
}
|
|
|
|
// Set end
|
|
else {
|
|
AssDialogue *next;
|
|
AssDialogue *cur = GetDialogue(n1);
|
|
for (int i=n1;i<n2;i++) {
|
|
next = GetDialogue(i+1);
|
|
if (!next) return;
|
|
cur->End = next->Start;
|
|
cur = next;
|
|
}
|
|
}
|
|
|
|
context->ass->Commit(_("adjoin"), AssFile::COMMIT_DIAG_TIME);
|
|
}
|
|
|
|
/// @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() const {
|
|
Selection sel = GetSelectedSet();
|
|
|
|
std::vector<int> result;
|
|
result.reserve(sel.size());
|
|
|
|
int line_index = 0;
|
|
for (entryIter it = context->ass->Line.begin(); it != context->ass->Line.end(); ++it, ++line_index) {
|
|
if (sel.find(dynamic_cast<AssDialogue*>(*it)) != sel.end())
|
|
result.push_back(line_index);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// @brief Update list of selected lines from absolute selection
|
|
/// @param selection
|
|
///
|
|
void SubtitlesGrid::SetSelectionFromAbsolute(std::vector<int> &selection) {
|
|
Selection newsel;
|
|
|
|
std::sort(selection.begin(), selection.end());
|
|
|
|
int i = 0;
|
|
std::list<AssEntry*>::iterator j = context->ass->Line.begin();
|
|
|
|
for (size_t selveci = 0; selveci < selection.size(); ++selveci) {
|
|
while (i != selection[selveci] && j != context->ass->Line.end()) ++i, ++j;
|
|
if (j == context->ass->Line.end()) break; /// @todo Report error somehow
|
|
AssDialogue *dlg = dynamic_cast<AssDialogue*>(*j);
|
|
if (dlg) newsel.insert(dlg);
|
|
}
|
|
|
|
SetSelectedSet(newsel);
|
|
}
|