Aegisub/aegisub/src/subs_grid.cpp

421 lines
11 KiB
C++
Raw Normal View History

// Copyright (c) 2006, Rodrigo Braz Monteiro
2006-01-16 22:02:54 +01:00
// 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/
2006-01-16 22:02:54 +01:00
//
// $Id$
/// @file subs_grid.cpp
/// @brief Subtitles grid control in main window
/// @ingroup main_ui
///
2006-01-16 22:02:54 +01:00
#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"
2006-01-16 22:02:54 +01:00
#include "ass_file.h"
#include "dialog_paste_over.h"
#include "main.h"
#include "utils.h"
#include "video_context.h"
2006-01-16 22:02:54 +01:00
SubtitlesGrid::SubtitlesGrid(wxWindow *parent, agi::Context *context, const wxSize& size, long style, const wxString& name)
: BaseGrid(parent,context,size,style,name)
2006-01-16 22:02:54 +01:00
{
}
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;
}
// If d2 is the last line in the selection it'll never hit the above test
if (d2 == end && (*d2)->Text.empty()) {
delete *d2;
context->ass->Line.remove(*d2);
continue;
}
2006-01-16 22:02:54 +01:00
// 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;
}
2006-01-16 22:02:54 +01:00
// 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);
2006-01-16 22:02:54 +01:00
}
/// @brief Insert a line
/// @param line
/// @param n
/// @param after
/// @param update
2006-01-16 22:02:54 +01:00
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);
2006-01-16 22:02:54 +01:00
// Update
if (update) {
context->ass->Commit(_("line insertion"), AssFile::COMMIT_DIAG_ADDREM);
}
else {
UpdateMaps();
2006-01-16 22:02:54 +01:00
}
}
void SubtitlesGrid::CopyLines(wxArrayInt target) {
2006-01-16 22:02:54 +01:00
// Prepare text
wxString data;
2006-01-16 22:02:54 +01:00
AssDialogue *cur;
int nrows = target.Count();
2006-01-16 22:02:54 +01:00
bool first = true;
for (int i=0;i<nrows;i++) {
if (!first) data += "\r\n";
first = false;
cur = GetDialogue(target[i]);
data += cur->GetEntryData();
2006-01-16 22:02:54 +01:00
}
// 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();
2006-01-16 22:02:54 +01:00
// Prepare text
wxString data;
2006-01-16 22:02:54 +01:00
// 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
2006-01-16 22:02:54 +01:00
int inserted = 0;
std::vector<bool> pasteOverOptions;
wxStringTokenizer token (data,"\r\n",wxTOKEN_STRTOK);
2006-01-16 22:02:54 +01:00
while (token.HasMoreTokens()) {
// Convert data into an AssDialogue
2006-01-16 22:02:54 +01:00
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);
2006-01-16 22:02:54 +01:00
}
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;
2006-01-16 22:02:54 +01:00
}
// 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++;
2006-01-16 22:02:54 +01:00
}
// Update data post-insertion
2006-01-16 22:02:54 +01:00
if (inserted > 0) {
context->ass->Commit(_("paste"), pasteOver ? AssFile::COMMIT_DIAG_FULL : AssFile::COMMIT_DIAG_ADDREM);
2006-01-16 22:02:54 +01:00
// Set selection
if (!pasteOver) {
Selection newsel;
for (int i=n;i<n+inserted;i++) {
newsel.insert(GetDialogue(i));
}
SetSelectedSet(newsel);
SetActiveLine(GetDialogue(GetFirstSelRow()));
2006-01-16 22:02:54 +01:00
}
}
}
EndBatch();
2006-01-16 22:02:54 +01:00
}
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;
2006-01-16 22:02:54 +01:00
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;
}
2006-01-16 22:02:54 +01:00
}
// Add default line if file was wiped
if ((size_t)GetRows() == deleted) {
2006-01-16 22:02:54 +01:00
AssDialogue *def = new AssDialogue;
++before_first;
context->ass->Line.insert(before_first, def);
2006-01-16 22:02:54 +01:00
}
if (flagModified) {
context->ass->Commit(_("delete"), AssFile::COMMIT_DIAG_ADDREM);
}
else {
UpdateMaps();
}
2006-01-16 22:02:54 +01:00
}
void SubtitlesGrid::AdjoinLines(int n1,int n2,bool setStart) {
if (n1 == n2) {
if (setStart) {
--n1;
}
else {
++n2;
}
}
2006-01-16 22:02:54 +01:00
// 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);
2006-01-16 22:02:54 +01:00
}
/// @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);
}