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(¤tAction->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 }