File indexing completed on 2024-06-16 04:38:13

0001 /*
0002     SPDX-FileCopyrightText: 2007-2009 Sergio Pistone <sergio_pistone@yahoo.com.ar>
0003     SPDX-FileCopyrightText: 2010-2022 Mladen Milinkovic <max@smoothware.net>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "core/undo/subtitleactions.h"
0009 #include "core/subtitleiterator.h"
0010 #include "core/subtitleline.h"
0011 #include "core/richstring.h"
0012 
0013 #include <QObject>
0014 #include <QTextDocument>
0015 #include <QTextEdit>
0016 
0017 #include <KLocalizedString>
0018 
0019 using namespace SubtitleComposer;
0020 
0021 // *** SubtitleAction
0022 SubtitleAction::SubtitleAction(Subtitle *subtitle, UndoStack::DirtyMode dirtyMode, const QString &description)
0023     : UndoAction(dirtyMode, subtitle, description)
0024 {}
0025 
0026 SubtitleAction::~SubtitleAction()
0027 {}
0028 
0029 
0030 // *** SetFramesPerSecondAction
0031 SetFramesPerSecondAction::SetFramesPerSecondAction(Subtitle *subtitle, double framesPerSecond)
0032     : SubtitleAction(subtitle, UndoStack::Both, i18n("Set Frame Rate")),
0033       m_framesPerSecond(framesPerSecond)
0034 {}
0035 
0036 SetFramesPerSecondAction::~SetFramesPerSecondAction()
0037 {}
0038 
0039 void
0040 SetFramesPerSecondAction::redo()
0041 {
0042     double tmp = m_subtitle->m_framesPerSecond;
0043     m_subtitle->m_framesPerSecond = m_framesPerSecond;
0044     m_framesPerSecond = tmp;
0045 
0046     emit m_subtitle->framesPerSecondChanged(m_subtitle->m_framesPerSecond);
0047 }
0048 
0049 
0050 // *** InsertLinesAction
0051 InsertLinesAction::InsertLinesAction(Subtitle *subtitle, const QList<SubtitleLine *> &lines, int insertIndex)
0052     : SubtitleAction(subtitle, UndoStack::Both, i18n("Insert Lines")),
0053       m_insertIndex(insertIndex < 0 ? subtitle->linesCount() : insertIndex),
0054       m_lastIndex(m_insertIndex + lines.count() - 1),
0055       m_lines(lines)
0056 {
0057     Q_ASSERT(m_insertIndex >= 0 && m_insertIndex <= m_subtitle->linesCount());
0058     Q_ASSERT(m_lastIndex >= 0);
0059     Q_ASSERT(m_insertIndex <= m_lastIndex);
0060 }
0061 
0062 InsertLinesAction::~InsertLinesAction()
0063 {
0064     qDeleteAll(m_lines);
0065 }
0066 
0067 bool
0068 InsertLinesAction::mergeWith(const QUndoCommand *command)
0069 {
0070     const InsertLinesAction *currentAction = static_cast<const InsertLinesAction *>(command);
0071     if(currentAction->m_subtitle != m_subtitle)
0072         return false;
0073 
0074     if(currentAction->m_insertIndex == m_lastIndex + 1 || (m_insertIndex <= currentAction->m_lastIndex && currentAction->m_insertIndex <= m_lastIndex)) {
0075         m_lastIndex += currentAction->m_lastIndex - currentAction->m_insertIndex + 1;
0076         if(m_insertIndex > currentAction->m_insertIndex) {
0077             m_lastIndex -= m_insertIndex - currentAction->m_insertIndex;
0078             m_insertIndex = currentAction->m_insertIndex;
0079         }
0080         return true;
0081     }
0082 
0083     return false;
0084 }
0085 
0086 void
0087 InsertLinesAction::redo()
0088 {
0089     emit m_subtitle->linesAboutToBeInserted(m_insertIndex, m_lastIndex);
0090 
0091     SubtitleLine *line;
0092     int insertOffset = 0;
0093     int lineIndex = -1;
0094 
0095     while(!m_lines.isEmpty()) {
0096         line = m_lines.takeFirst();
0097         lineIndex = m_insertIndex + insertOffset++;
0098         setLineSubtitle(line);
0099         m_subtitle->m_lines.insert(lineIndex, line);
0100     }
0101 
0102     emit m_subtitle->linesInserted(m_insertIndex, m_lastIndex);
0103 }
0104 
0105 void
0106 InsertLinesAction::undo()
0107 {
0108     emit m_subtitle->linesAboutToBeRemoved(m_insertIndex, m_lastIndex);
0109 
0110     for(int index = m_insertIndex; index <= m_lastIndex; ++index) {
0111         SubtitleLine *line = m_subtitle->takeAt(m_insertIndex);
0112         clearLineSubtitle(line);
0113         m_lines.append(line);
0114     }
0115 
0116     emit m_subtitle->linesRemoved(m_insertIndex, m_lastIndex);
0117 }
0118 
0119 
0120 // *** RemoveLinesAction
0121 RemoveLinesAction::RemoveLinesAction(Subtitle *subtitle, int firstIndex, int lastIndex)
0122     : SubtitleAction(subtitle, UndoStack::Both, i18n("Remove Lines")),
0123       m_firstIndex(firstIndex),
0124       m_lastIndex(lastIndex < 0 ? subtitle->lastIndex() : lastIndex),
0125       m_lines()
0126 {
0127     Q_ASSERT(m_firstIndex >= 0);
0128     Q_ASSERT(m_firstIndex <= m_subtitle->linesCount());
0129     Q_ASSERT(m_lastIndex >= 0);
0130     Q_ASSERT(m_lastIndex <= m_subtitle->linesCount());
0131     Q_ASSERT(m_firstIndex <= m_lastIndex);
0132 }
0133 
0134 RemoveLinesAction::~RemoveLinesAction()
0135 {
0136     qDeleteAll(m_lines);
0137 }
0138 
0139 bool
0140 RemoveLinesAction::mergeWith(const QUndoCommand *command)
0141 {
0142     const RemoveLinesAction *currentAction = static_cast<const RemoveLinesAction *>(command);
0143     if(&currentAction->m_subtitle != &m_subtitle)
0144         return false;
0145 
0146     if(m_lastIndex + 1 == currentAction->m_firstIndex) {
0147         // currentAction removed lines immediately after ours
0148         m_lastIndex = currentAction->m_lastIndex;
0149         while(!currentAction->m_lines.isEmpty())
0150             m_lines.append(const_cast<RemoveLinesAction *>(currentAction)->m_lines.takeFirst());
0151         return true;
0152     }
0153 
0154     if(currentAction->m_lastIndex + 1 == m_firstIndex) {
0155         // currentAction removed lines immediately before ours
0156         m_firstIndex = currentAction->m_firstIndex;
0157         while(!currentAction->m_lines.isEmpty())
0158             m_lines.prepend(const_cast<RemoveLinesAction *>(currentAction)->m_lines.takeLast());
0159         return true;
0160     }
0161 
0162     return false;
0163 }
0164 
0165 void
0166 RemoveLinesAction::redo()
0167 {
0168     emit m_subtitle->linesAboutToBeRemoved(m_firstIndex, m_lastIndex);
0169 
0170     for(int index = m_firstIndex; index <= m_lastIndex; ++index) {
0171         SubtitleLine *line = m_subtitle->takeAt(m_firstIndex);
0172         clearLineSubtitle(line);
0173         m_lines.append(line);
0174     }
0175 
0176     emit m_subtitle->linesRemoved(m_firstIndex, m_lastIndex);
0177 }
0178 
0179 void
0180 RemoveLinesAction::undo()
0181 {
0182     emit m_subtitle->linesAboutToBeInserted(m_firstIndex, m_lastIndex);
0183 
0184     int insertOffset = 0;
0185     int lineIndex = -1;
0186 
0187     while(!m_lines.isEmpty()) {
0188         SubtitleLine *line = m_lines.takeFirst();
0189         lineIndex = m_firstIndex + insertOffset++;
0190         setLineSubtitle(line);
0191         m_subtitle->m_lines.insert(lineIndex, line);
0192     }
0193 
0194     emit m_subtitle->linesInserted(m_firstIndex, m_lastIndex);
0195 }
0196 
0197 
0198 // *** MoveLineAction
0199 MoveLineAction::MoveLineAction(Subtitle *subtitle, int fromIndex, int toIndex) :
0200     SubtitleAction(subtitle, UndoStack::Both, i18n("Move Line")),
0201     m_fromIndex(fromIndex),
0202     m_toIndex(toIndex < 0 ? subtitle->lastIndex() : toIndex)
0203 {
0204     Q_ASSERT(m_fromIndex >= 0);
0205     Q_ASSERT(m_fromIndex <= m_subtitle->linesCount());
0206     Q_ASSERT(m_toIndex >= 0);
0207     Q_ASSERT(m_toIndex <= m_subtitle->linesCount());
0208     Q_ASSERT(m_fromIndex != m_toIndex);
0209 }
0210 
0211 MoveLineAction::~MoveLineAction()
0212 {}
0213 
0214 bool
0215 MoveLineAction::mergeWith(const QUndoCommand *command)
0216 {
0217     const MoveLineAction *currentAction = static_cast<const MoveLineAction *>(command);
0218     if(currentAction->m_subtitle != m_subtitle)
0219         return false;
0220 
0221     Q_ASSERT(command != this);
0222 
0223     // TODO: FIXME: this and currentAction were swapped in new Qt's implementation, so below code is not working
0224     // since move is used only when sorting - this will never be called
0225     if(currentAction->m_toIndex == m_fromIndex) {
0226         m_fromIndex = currentAction->m_fromIndex;
0227         return true;
0228     } else if(m_toIndex - m_fromIndex == 1 || m_fromIndex - m_toIndex == 1) {
0229         if(currentAction->m_toIndex == m_toIndex) {
0230             // when the distance between fromIndex and toIndex is 1, the action is the same as if the values were swapped
0231             m_toIndex = m_fromIndex;
0232             m_fromIndex = currentAction->m_fromIndex;
0233             return true;
0234         }
0235         if(currentAction->m_toIndex - currentAction->m_fromIndex == 1 || currentAction->m_fromIndex - currentAction->m_toIndex == 1) {
0236             // same as before, but now we consider inverting the previous action too
0237             if(currentAction->m_fromIndex == m_toIndex) {
0238                 m_toIndex = m_fromIndex;
0239                 m_fromIndex = currentAction->m_toIndex;
0240                 return true;
0241             }
0242         }
0243     } else if(currentAction->m_toIndex - currentAction->m_fromIndex == 1 || currentAction->m_fromIndex - currentAction->m_toIndex == 1) {
0244         // again, same as before, but now we consider inverting only the previous action
0245         if(currentAction->m_fromIndex == m_fromIndex) {
0246             m_fromIndex = currentAction->m_toIndex;
0247             return true;
0248         }
0249     }
0250 
0251     return false;
0252 }
0253 
0254 void
0255 MoveLineAction::redo()
0256 {
0257     emit m_subtitle->linesAboutToBeRemoved(m_fromIndex, m_fromIndex);
0258     SubtitleLine *line = m_subtitle->takeAt(m_fromIndex);
0259     clearLineSubtitle(line);
0260     emit m_subtitle->linesRemoved(m_fromIndex, m_fromIndex);
0261 
0262     emit m_subtitle->linesAboutToBeInserted(m_toIndex, m_toIndex);
0263     setLineSubtitle(line);
0264     m_subtitle->m_lines.insert(m_toIndex, line);
0265     emit m_subtitle->linesInserted(m_toIndex, m_toIndex);
0266 }
0267 
0268 void
0269 MoveLineAction::undo()
0270 {
0271     emit m_subtitle->linesAboutToBeRemoved(m_toIndex, m_toIndex);
0272     SubtitleLine *line = m_subtitle->takeAt(m_toIndex);
0273     clearLineSubtitle(line);
0274     emit m_subtitle->linesRemoved(m_toIndex, m_toIndex);
0275 
0276     emit m_subtitle->linesAboutToBeInserted(m_fromIndex, m_fromIndex);
0277     setLineSubtitle(line);
0278     m_subtitle->m_lines.insert(m_fromIndex, line);
0279     emit m_subtitle->linesInserted(m_fromIndex, m_fromIndex);
0280 }
0281 
0282 
0283 // *** SwapLinesTextsAction
0284 SwapLinesTextsAction::SwapLinesTextsAction(Subtitle *subtitle, const RangeList &ranges) :
0285     SubtitleAction(subtitle, UndoStack::Both, i18n("Swap Texts")),
0286     m_ranges(ranges)
0287 {}
0288 
0289 SwapLinesTextsAction::~SwapLinesTextsAction()
0290 {}
0291 
0292 void
0293 SwapLinesTextsAction::redo()
0294 {
0295     for(SubtitleIterator it(*m_subtitle, m_ranges); it.current(); ++it) {
0296         SubtitleLine *line = it.current();
0297         RichDocument *tmp = line->m_primaryDoc;
0298         line->m_primaryDoc = line->m_secondaryDoc;
0299         line->m_secondaryDoc = tmp;
0300         emit line->primaryTextChanged();
0301         emit line->secondaryTextChanged();
0302     }
0303 }
0304 
0305 
0306 // *** ChangeStylesheetAction
0307 EditStylesheetAction::EditStylesheetAction(Subtitle *subtitle, QTextEdit *textEdit)
0308     : SubtitleAction(subtitle, UndoStack::Primary, i18n("Change stylesheet")),
0309       m_stylesheetEdit(textEdit),
0310       m_stylesheetDocState(m_stylesheetEdit->document()->availableUndoSteps())
0311 {
0312 }
0313 
0314 EditStylesheetAction::~EditStylesheetAction()
0315 {
0316 }
0317 
0318 bool
0319 EditStylesheetAction::mergeWith(const QUndoCommand *command)
0320 {
0321     const EditStylesheetAction *cur = static_cast<const EditStylesheetAction *>(command);
0322     Q_ASSERT(cur->m_stylesheetEdit == m_stylesheetEdit);
0323     return cur->m_stylesheetDocState == m_stylesheetDocState;
0324 }
0325 
0326 void
0327 EditStylesheetAction::update(const QString &stylesheet)
0328 {
0329     RichCSS *ss = m_subtitle->m_stylesheet;
0330     ss->blockSignals(true);
0331     ss->clear();
0332     ss->blockSignals(false);
0333     ss->parse(stylesheet);
0334 }
0335 
0336 void
0337 EditStylesheetAction::undo()
0338 {
0339     const bool prev = m_subtitle->ignoreDocChanges(true);
0340     while(m_stylesheetEdit->document()->isUndoAvailable() && m_stylesheetEdit->document()->availableUndoSteps() >= m_stylesheetDocState)
0341         m_stylesheetEdit->undo();
0342     update(m_stylesheetEdit->document()->toPlainText());
0343     m_subtitle->ignoreDocChanges(prev);
0344 }
0345 
0346 void
0347 EditStylesheetAction::redo()
0348 {
0349     const bool prev = m_subtitle->ignoreDocChanges(true);
0350     while(m_stylesheetEdit->document()->isRedoAvailable() && m_stylesheetEdit->document()->availableUndoSteps() < m_stylesheetDocState)
0351         m_stylesheetEdit->redo();
0352     update(m_stylesheetEdit->document()->toPlainText());
0353     m_subtitle->ignoreDocChanges(prev);
0354 }