1 /*
2     SPDX-FileCopyrightText: 2013 Jon Mease <jon.mease@gmail.com>
3 
4     Work sponsored by the LiMux project of the city of Munich:
5     SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company <info@kdab.com>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include "documentcommands_p.h"
11 
12 #include "annotations.h"
13 #include "debug_p.h"
14 #include "document_p.h"
15 #include "form.h"
16 #include "page.h"
17 #include "page_p.h"
18 #include "utils_p.h"
19 
20 #include <KLocalizedString>
21 
22 namespace Okular
23 {
moveViewportIfBoundingRectNotFullyVisible(Okular::NormalizedRect boundingRect,DocumentPrivate * docPriv,int pageNumber)24 void moveViewportIfBoundingRectNotFullyVisible(Okular::NormalizedRect boundingRect, DocumentPrivate *docPriv, int pageNumber)
25 {
26     const Rotation pageRotation = docPriv->m_parent->page(pageNumber)->rotation();
27     const QTransform rotationMatrix = Okular::buildRotationMatrix(pageRotation);
28     boundingRect.transform(rotationMatrix);
29     if (!docPriv->isNormalizedRectangleFullyVisible(boundingRect, pageNumber)) {
30         DocumentViewport searchViewport(pageNumber);
31         searchViewport.rePos.enabled = true;
32         searchViewport.rePos.normalizedX = (boundingRect.left + boundingRect.right) / 2.0;
33         searchViewport.rePos.normalizedY = (boundingRect.top + boundingRect.bottom) / 2.0;
34         docPriv->m_parent->setViewport(searchViewport, nullptr, true);
35     }
36 }
37 
buildBoundingRectangleForButtons(const QList<Okular::FormFieldButton * > & formButtons)38 Okular::NormalizedRect buildBoundingRectangleForButtons(const QList<Okular::FormFieldButton *> &formButtons)
39 {
40     // Initialize coordinates of the bounding rect
41     double left = 1.0;
42     double top = 1.0;
43     double right = 0.0;
44     double bottom = 0.0;
45 
46     for (const FormFieldButton *formButton : formButtons) {
47         left = qMin<double>(left, formButton->rect().left);
48         top = qMin<double>(top, formButton->rect().top);
49         right = qMax<double>(right, formButton->rect().right);
50         bottom = qMax<double>(bottom, formButton->rect().bottom);
51     }
52     Okular::NormalizedRect boundingRect(left, top, right, bottom);
53     return boundingRect;
54 }
55 
AddAnnotationCommand(Okular::DocumentPrivate * docPriv,Okular::Annotation * annotation,int pageNumber)56 AddAnnotationCommand::AddAnnotationCommand(Okular::DocumentPrivate *docPriv, Okular::Annotation *annotation, int pageNumber)
57     : m_docPriv(docPriv)
58     , m_annotation(annotation)
59     , m_pageNumber(pageNumber)
60     , m_done(false)
61 {
62     setText(i18nc("Add an annotation to the page", "add annotation"));
63 }
64 
~AddAnnotationCommand()65 AddAnnotationCommand::~AddAnnotationCommand()
66 {
67     if (!m_done) {
68         delete m_annotation;
69     }
70 }
71 
undo()72 void AddAnnotationCommand::undo()
73 {
74     moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber);
75     m_docPriv->performRemovePageAnnotation(m_pageNumber, m_annotation);
76     m_done = false;
77 }
78 
redo()79 void AddAnnotationCommand::redo()
80 {
81     moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber);
82     m_docPriv->performAddPageAnnotation(m_pageNumber, m_annotation);
83     m_done = true;
84 }
85 
refreshInternalPageReferences(const QVector<Okular::Page * > & newPagesVector)86 bool AddAnnotationCommand::refreshInternalPageReferences(const QVector<Okular::Page *> &newPagesVector)
87 {
88     if (m_done) {
89         // We don't always update m_annotation because even if the annotation has been added to the document
90         // it can have been removed later so the annotation pointer is stored inside a following RemoveAnnotationCommand
91         // and thus doesn't need updating because it didn't change
92         // because of the document reload
93         auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName());
94         if (a)
95             m_annotation = a;
96     }
97 
98     return true;
99 }
100 
RemoveAnnotationCommand(Okular::DocumentPrivate * doc,Okular::Annotation * annotation,int pageNumber)101 RemoveAnnotationCommand::RemoveAnnotationCommand(Okular::DocumentPrivate *doc, Okular::Annotation *annotation, int pageNumber)
102     : m_docPriv(doc)
103     , m_annotation(annotation)
104     , m_pageNumber(pageNumber)
105     , m_done(false)
106 {
107     setText(i18nc("Remove an annotation from the page", "remove annotation"));
108 }
109 
~RemoveAnnotationCommand()110 RemoveAnnotationCommand::~RemoveAnnotationCommand()
111 {
112     if (m_done) {
113         delete m_annotation;
114     }
115 }
116 
undo()117 void RemoveAnnotationCommand::undo()
118 {
119     moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber);
120     m_docPriv->performAddPageAnnotation(m_pageNumber, m_annotation);
121     m_done = false;
122 }
123 
redo()124 void RemoveAnnotationCommand::redo()
125 {
126     moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber);
127     m_docPriv->performRemovePageAnnotation(m_pageNumber, m_annotation);
128     m_done = true;
129 }
130 
refreshInternalPageReferences(const QVector<Okular::Page * > & newPagesVector)131 bool RemoveAnnotationCommand::refreshInternalPageReferences(const QVector<Okular::Page *> &newPagesVector)
132 {
133     if (!m_done) {
134         // We don't always update m_annotation because it can happen that the annotation remove has been undo
135         // and that annotation addition has also been undone so the annotation pointer is stored inside
136         // a previous AddAnnotationCommand and thus doesn't need updating because it didn't change
137         // because of the document reload
138         auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName());
139         if (a)
140             m_annotation = a;
141     }
142 
143     return true;
144 }
145 
ModifyAnnotationPropertiesCommand(DocumentPrivate * docPriv,Annotation * annotation,int pageNumber,const QDomNode & oldProperties,const QDomNode & newProperties)146 ModifyAnnotationPropertiesCommand::ModifyAnnotationPropertiesCommand(DocumentPrivate *docPriv, Annotation *annotation, int pageNumber, const QDomNode &oldProperties, const QDomNode &newProperties)
147     : m_docPriv(docPriv)
148     , m_annotation(annotation)
149     , m_pageNumber(pageNumber)
150     , m_prevProperties(oldProperties)
151     , m_newProperties(newProperties)
152 {
153     setText(i18nc("Modify an annotation's internal properties (Color, line-width, etc.)", "modify annotation properties"));
154 }
155 
undo()156 void ModifyAnnotationPropertiesCommand::undo()
157 {
158     moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber);
159     m_annotation->setAnnotationProperties(m_prevProperties);
160     m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true);
161 }
162 
redo()163 void ModifyAnnotationPropertiesCommand::redo()
164 {
165     moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber);
166     m_annotation->setAnnotationProperties(m_newProperties);
167     m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true);
168 }
169 
refreshInternalPageReferences(const QVector<Okular::Page * > & newPagesVector)170 bool ModifyAnnotationPropertiesCommand::refreshInternalPageReferences(const QVector<Okular::Page *> &newPagesVector)
171 {
172     // Same reason for not unconditionally updating m_annotation, the annotation pointer can be stored in an add/Remove command
173     auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName());
174     if (a)
175         m_annotation = a;
176 
177     return true;
178 }
179 
TranslateAnnotationCommand(DocumentPrivate * docPriv,Annotation * annotation,int pageNumber,const Okular::NormalizedPoint & delta,bool completeDrag)180 TranslateAnnotationCommand::TranslateAnnotationCommand(DocumentPrivate *docPriv, Annotation *annotation, int pageNumber, const Okular::NormalizedPoint &delta, bool completeDrag)
181     : m_docPriv(docPriv)
182     , m_annotation(annotation)
183     , m_pageNumber(pageNumber)
184     , m_delta(delta)
185     , m_completeDrag(completeDrag)
186 {
187     setText(i18nc("Translate an annotation's position on the page", "translate annotation"));
188 }
189 
undo()190 void TranslateAnnotationCommand::undo()
191 {
192     moveViewportIfBoundingRectNotFullyVisible(translateBoundingRectangle(minusDelta()), m_docPriv, m_pageNumber);
193     m_annotation->translate(minusDelta());
194     m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true);
195 }
196 
redo()197 void TranslateAnnotationCommand::redo()
198 {
199     moveViewportIfBoundingRectNotFullyVisible(translateBoundingRectangle(m_delta), m_docPriv, m_pageNumber);
200     m_annotation->translate(m_delta);
201     m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true);
202 }
203 
id() const204 int TranslateAnnotationCommand::id() const
205 {
206     return 1;
207 }
208 
mergeWith(const QUndoCommand * uc)209 bool TranslateAnnotationCommand::mergeWith(const QUndoCommand *uc)
210 {
211     TranslateAnnotationCommand *tuc = (TranslateAnnotationCommand *)uc;
212 
213     if (tuc->m_annotation != m_annotation)
214         return false;
215 
216     if (m_completeDrag) {
217         return false;
218     }
219     m_delta = Okular::NormalizedPoint(tuc->m_delta.x + m_delta.x, tuc->m_delta.y + m_delta.y);
220     m_completeDrag = tuc->m_completeDrag;
221     return true;
222 }
223 
minusDelta()224 Okular::NormalizedPoint TranslateAnnotationCommand::minusDelta()
225 {
226     return Okular::NormalizedPoint(-m_delta.x, -m_delta.y);
227 }
228 
translateBoundingRectangle(const Okular::NormalizedPoint & delta)229 Okular::NormalizedRect TranslateAnnotationCommand::translateBoundingRectangle(const Okular::NormalizedPoint &delta)
230 {
231     Okular::NormalizedRect annotBoundingRect = m_annotation->boundingRectangle();
232     double left = qMin<double>(annotBoundingRect.left, annotBoundingRect.left + delta.x);
233     double right = qMax<double>(annotBoundingRect.right, annotBoundingRect.right + delta.x);
234     double top = qMin<double>(annotBoundingRect.top, annotBoundingRect.top + delta.y);
235     double bottom = qMax<double>(annotBoundingRect.bottom, annotBoundingRect.bottom + delta.y);
236     Okular::NormalizedRect boundingRect(left, top, right, bottom);
237     return boundingRect;
238 }
239 
refreshInternalPageReferences(const QVector<Page * > & newPagesVector)240 bool TranslateAnnotationCommand::refreshInternalPageReferences(const QVector<Page *> &newPagesVector)
241 {
242     // Same reason for not unconditionally updating m_annotation, the annotation pointer can be stored in an add/Remove command
243     auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName());
244     if (a)
245         m_annotation = a;
246 
247     return true;
248 }
249 
AdjustAnnotationCommand(Okular::DocumentPrivate * docPriv,Okular::Annotation * annotation,int pageNumber,const Okular::NormalizedPoint & delta1,const Okular::NormalizedPoint & delta2,bool completeDrag)250 AdjustAnnotationCommand::AdjustAnnotationCommand(Okular::DocumentPrivate *docPriv, Okular::Annotation *annotation, int pageNumber, const Okular::NormalizedPoint &delta1, const Okular::NormalizedPoint &delta2, bool completeDrag)
251     : m_docPriv(docPriv)
252     , m_annotation(annotation)
253     , m_pageNumber(pageNumber)
254     , m_delta1(delta1)
255     , m_delta2(delta2)
256     , m_completeDrag(completeDrag)
257 {
258     setText(i18nc("Change an annotation's size", "adjust annotation"));
259 }
260 
undo()261 void AdjustAnnotationCommand::undo()
262 {
263     const NormalizedPoint minusDelta1 = Okular::NormalizedPoint(-m_delta1.x, -m_delta1.y);
264     const NormalizedPoint minusDelta2 = Okular::NormalizedPoint(-m_delta2.x, -m_delta2.y);
265     moveViewportIfBoundingRectNotFullyVisible(adjustBoundingRectangle(minusDelta1, minusDelta2), m_docPriv, m_pageNumber);
266     m_annotation->adjust(minusDelta1, minusDelta2);
267     m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true);
268 }
269 
redo()270 void AdjustAnnotationCommand::redo()
271 {
272     moveViewportIfBoundingRectNotFullyVisible(adjustBoundingRectangle(m_delta1, m_delta2), m_docPriv, m_pageNumber);
273     m_annotation->adjust(m_delta1, m_delta2);
274     m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true);
275 }
276 
id() const277 int AdjustAnnotationCommand::id() const
278 {
279     return 5;
280 }
281 
mergeWith(const QUndoCommand * uc)282 bool AdjustAnnotationCommand::mergeWith(const QUndoCommand *uc)
283 {
284     AdjustAnnotationCommand *tuc = (AdjustAnnotationCommand *)uc;
285 
286     if (tuc->m_annotation != m_annotation)
287         return false;
288 
289     if (m_completeDrag) {
290         return false;
291     }
292     m_delta1 = Okular::NormalizedPoint(tuc->m_delta1.x + m_delta1.x, tuc->m_delta1.y + m_delta1.y);
293     m_delta2 = Okular::NormalizedPoint(tuc->m_delta2.x + m_delta2.x, tuc->m_delta2.y + m_delta2.y);
294     m_completeDrag = tuc->m_completeDrag;
295     return true;
296 }
297 
adjustBoundingRectangle(const Okular::NormalizedPoint & delta1,const Okular::NormalizedPoint & delta2)298 Okular::NormalizedRect AdjustAnnotationCommand::adjustBoundingRectangle(const Okular::NormalizedPoint &delta1, const Okular::NormalizedPoint &delta2)
299 {
300     const Okular::NormalizedRect annotBoundingRect = m_annotation->boundingRectangle();
301     const double left = qMin<double>(annotBoundingRect.left, annotBoundingRect.left + delta1.x);
302     const double right = qMax<double>(annotBoundingRect.right, annotBoundingRect.right + delta2.x);
303     const double top = qMin<double>(annotBoundingRect.top, annotBoundingRect.top + delta1.y);
304     const double bottom = qMax<double>(annotBoundingRect.bottom, annotBoundingRect.bottom + delta2.y);
305     return Okular::NormalizedRect(left, top, right, bottom);
306 }
307 
refreshInternalPageReferences(const QVector<Page * > & newPagesVector)308 bool AdjustAnnotationCommand::refreshInternalPageReferences(const QVector<Page *> &newPagesVector)
309 {
310     // Same reason for not unconditionally updating m_annotation, the annotation pointer can be stored in an add/Remove command
311     auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName());
312     if (a)
313         m_annotation = a;
314 
315     return true;
316 }
317 
EditTextCommand(const QString & newContents,int newCursorPos,const QString & prevContents,int prevCursorPos,int prevAnchorPos)318 EditTextCommand::EditTextCommand(const QString &newContents, int newCursorPos, const QString &prevContents, int prevCursorPos, int prevAnchorPos)
319     : m_newContents(newContents)
320     , m_newCursorPos(newCursorPos)
321     , m_prevContents(prevContents)
322     , m_prevCursorPos(prevCursorPos)
323     , m_prevAnchorPos(prevAnchorPos)
324 {
325     setText(i18nc("Generic text edit command", "edit text"));
326 
327     //// Determine edit type
328     // If There was a selection then edit was not a simple single character backspace, delete, or insert
329     if (m_prevCursorPos != m_prevAnchorPos) {
330         qCDebug(OkularCoreDebug) << "OtherEdit, selection";
331         m_editType = OtherEdit;
332     } else if (newContentsRightOfCursor() == oldContentsRightOfCursor() && newContentsLeftOfCursor() == oldContentsLeftOfCursor().left(oldContentsLeftOfCursor().length() - 1) && oldContentsLeftOfCursor().rightRef(1) != "\n") {
333         qCDebug(OkularCoreDebug) << "CharBackspace";
334         m_editType = CharBackspace;
335     } else if (newContentsLeftOfCursor() == oldContentsLeftOfCursor() && newContentsRightOfCursor() == oldContentsRightOfCursor().right(oldContentsRightOfCursor().length() - 1) && oldContentsRightOfCursor().leftRef(1) != "\n") {
336         qCDebug(OkularCoreDebug) << "CharDelete";
337         m_editType = CharDelete;
338     } else if (newContentsRightOfCursor() == oldContentsRightOfCursor() && newContentsLeftOfCursor().left(newContentsLeftOfCursor().length() - 1) == oldContentsLeftOfCursor() && newContentsLeftOfCursor().rightRef(1) != "\n") {
339         qCDebug(OkularCoreDebug) << "CharInsert";
340         m_editType = CharInsert;
341     } else {
342         qCDebug(OkularCoreDebug) << "OtherEdit";
343         m_editType = OtherEdit;
344     }
345 }
346 
mergeWith(const QUndoCommand * uc)347 bool EditTextCommand::mergeWith(const QUndoCommand *uc)
348 {
349     EditTextCommand *euc = (EditTextCommand *)uc;
350 
351     // Only attempt merge of euc into this if our new state matches euc's old state and
352     // the editTypes match and are not type OtherEdit
353     if (m_newContents == euc->m_prevContents && m_newCursorPos == euc->m_prevCursorPos && m_editType == euc->m_editType && m_editType != OtherEdit) {
354         m_newContents = euc->m_newContents;
355         m_newCursorPos = euc->m_newCursorPos;
356         return true;
357     }
358     return false;
359 }
360 
oldContentsLeftOfCursor()361 QString EditTextCommand::oldContentsLeftOfCursor()
362 {
363     return m_prevContents.left(m_prevCursorPos);
364 }
365 
oldContentsRightOfCursor()366 QString EditTextCommand::oldContentsRightOfCursor()
367 {
368     return m_prevContents.right(m_prevContents.length() - m_prevCursorPos);
369 }
370 
newContentsLeftOfCursor()371 QString EditTextCommand::newContentsLeftOfCursor()
372 {
373     return m_newContents.left(m_newCursorPos);
374 }
375 
newContentsRightOfCursor()376 QString EditTextCommand::newContentsRightOfCursor()
377 {
378     return m_newContents.right(m_newContents.length() - m_newCursorPos);
379 }
380 
EditAnnotationContentsCommand(DocumentPrivate * docPriv,Annotation * annotation,int pageNumber,const QString & newContents,int newCursorPos,const QString & prevContents,int prevCursorPos,int prevAnchorPos)381 EditAnnotationContentsCommand::EditAnnotationContentsCommand(DocumentPrivate *docPriv, Annotation *annotation, int pageNumber, const QString &newContents, int newCursorPos, const QString &prevContents, int prevCursorPos, int prevAnchorPos)
382     : EditTextCommand(newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos)
383     , m_docPriv(docPriv)
384     , m_annotation(annotation)
385     , m_pageNumber(pageNumber)
386 {
387     setText(i18nc("Edit an annotation's text contents", "edit annotation contents"));
388 }
389 
undo()390 void EditAnnotationContentsCommand::undo()
391 {
392     moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber);
393     m_docPriv->performSetAnnotationContents(m_prevContents, m_annotation, m_pageNumber);
394     emit m_docPriv->m_parent->annotationContentsChangedByUndoRedo(m_annotation, m_prevContents, m_prevCursorPos, m_prevAnchorPos);
395 }
396 
redo()397 void EditAnnotationContentsCommand::redo()
398 {
399     moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber);
400     m_docPriv->performSetAnnotationContents(m_newContents, m_annotation, m_pageNumber);
401     emit m_docPriv->m_parent->annotationContentsChangedByUndoRedo(m_annotation, m_newContents, m_newCursorPos, m_newCursorPos);
402 }
403 
id() const404 int EditAnnotationContentsCommand::id() const
405 {
406     return 2;
407 }
408 
mergeWith(const QUndoCommand * uc)409 bool EditAnnotationContentsCommand::mergeWith(const QUndoCommand *uc)
410 {
411     EditAnnotationContentsCommand *euc = (EditAnnotationContentsCommand *)uc;
412     // Only attempt merge of euc into this if they modify the same annotation
413     if (m_annotation == euc->m_annotation) {
414         return EditTextCommand::mergeWith(uc);
415     } else {
416         return false;
417     }
418 }
419 
refreshInternalPageReferences(const QVector<Page * > & newPagesVector)420 bool EditAnnotationContentsCommand::refreshInternalPageReferences(const QVector<Page *> &newPagesVector)
421 {
422     auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName());
423     if (a)
424         m_annotation = a;
425 
426     return true;
427 }
428 
EditFormTextCommand(Okular::DocumentPrivate * docPriv,Okular::FormFieldText * form,int pageNumber,const QString & newContents,int newCursorPos,const QString & prevContents,int prevCursorPos,int prevAnchorPos)429 EditFormTextCommand::EditFormTextCommand(Okular::DocumentPrivate *docPriv, Okular::FormFieldText *form, int pageNumber, const QString &newContents, int newCursorPos, const QString &prevContents, int prevCursorPos, int prevAnchorPos)
430     : EditTextCommand(newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos)
431     , m_docPriv(docPriv)
432     , m_form(form)
433     , m_pageNumber(pageNumber)
434 {
435     setText(i18nc("Edit an form's text contents", "edit form contents"));
436 }
437 
undo()438 void EditFormTextCommand::undo()
439 {
440     moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber);
441     m_form->setText(m_prevContents);
442     emit m_docPriv->m_parent->formTextChangedByUndoRedo(m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos);
443     m_docPriv->notifyFormChanges(m_pageNumber);
444 }
445 
redo()446 void EditFormTextCommand::redo()
447 {
448     moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber);
449     m_form->setText(m_newContents);
450     emit m_docPriv->m_parent->formTextChangedByUndoRedo(m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos);
451     m_docPriv->notifyFormChanges(m_pageNumber);
452 }
453 
id() const454 int EditFormTextCommand::id() const
455 {
456     return 3;
457 }
458 
mergeWith(const QUndoCommand * uc)459 bool EditFormTextCommand::mergeWith(const QUndoCommand *uc)
460 {
461     EditFormTextCommand *euc = (EditFormTextCommand *)uc;
462     // Only attempt merge of euc into this if they modify the same form
463     if (m_form == euc->m_form) {
464         return EditTextCommand::mergeWith(uc);
465     } else {
466         return false;
467     }
468 }
469 
refreshInternalPageReferences(const QVector<Page * > & newPagesVector)470 bool EditFormTextCommand::refreshInternalPageReferences(const QVector<Page *> &newPagesVector)
471 {
472     m_form = dynamic_cast<FormFieldText *>(Okular::PagePrivate::findEquivalentForm(newPagesVector[m_pageNumber], m_form));
473 
474     return m_form;
475 }
476 
EditFormListCommand(Okular::DocumentPrivate * docPriv,FormFieldChoice * form,int pageNumber,const QList<int> & newChoices,const QList<int> & prevChoices)477 EditFormListCommand::EditFormListCommand(Okular::DocumentPrivate *docPriv, FormFieldChoice *form, int pageNumber, const QList<int> &newChoices, const QList<int> &prevChoices)
478     : m_docPriv(docPriv)
479     , m_form(form)
480     , m_pageNumber(pageNumber)
481     , m_newChoices(newChoices)
482     , m_prevChoices(prevChoices)
483 {
484     setText(i18nc("Edit a list form's choices", "edit list form choices"));
485 }
486 
undo()487 void EditFormListCommand::undo()
488 {
489     moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber);
490     m_form->setCurrentChoices(m_prevChoices);
491     emit m_docPriv->m_parent->formListChangedByUndoRedo(m_pageNumber, m_form, m_prevChoices);
492     m_docPriv->notifyFormChanges(m_pageNumber);
493 }
494 
redo()495 void EditFormListCommand::redo()
496 {
497     moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber);
498     m_form->setCurrentChoices(m_newChoices);
499     emit m_docPriv->m_parent->formListChangedByUndoRedo(m_pageNumber, m_form, m_newChoices);
500     m_docPriv->notifyFormChanges(m_pageNumber);
501 }
502 
refreshInternalPageReferences(const QVector<Page * > & newPagesVector)503 bool EditFormListCommand::refreshInternalPageReferences(const QVector<Page *> &newPagesVector)
504 {
505     m_form = dynamic_cast<FormFieldChoice *>(Okular::PagePrivate::findEquivalentForm(newPagesVector[m_pageNumber], m_form));
506 
507     return m_form;
508 }
509 
EditFormComboCommand(Okular::DocumentPrivate * docPriv,FormFieldChoice * form,int pageNumber,const QString & newContents,int newCursorPos,const QString & prevContents,int prevCursorPos,int prevAnchorPos)510 EditFormComboCommand::EditFormComboCommand(Okular::DocumentPrivate *docPriv, FormFieldChoice *form, int pageNumber, const QString &newContents, int newCursorPos, const QString &prevContents, int prevCursorPos, int prevAnchorPos)
511     : EditTextCommand(newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos)
512     , m_docPriv(docPriv)
513     , m_form(form)
514     , m_pageNumber(pageNumber)
515     , m_newIndex(-1)
516     , m_prevIndex(-1)
517 {
518     setText(i18nc("Edit a combo form's selection", "edit combo form selection"));
519 
520     // Determine new and previous choice indices (if any)
521     for (int i = 0; i < m_form->choices().size(); i++) {
522         if (m_form->choices().at(i) == m_prevContents) {
523             m_prevIndex = i;
524         }
525 
526         if (m_form->choices().at(i) == m_newContents) {
527             m_newIndex = i;
528         }
529     }
530 }
531 
undo()532 void EditFormComboCommand::undo()
533 {
534     if (m_prevIndex != -1) {
535         m_form->setCurrentChoices(QList<int>() << m_prevIndex);
536     } else {
537         m_form->setEditChoice(m_prevContents);
538     }
539     moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber);
540     emit m_docPriv->m_parent->formComboChangedByUndoRedo(m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos);
541     m_docPriv->notifyFormChanges(m_pageNumber);
542 }
543 
redo()544 void EditFormComboCommand::redo()
545 {
546     if (m_newIndex != -1) {
547         m_form->setCurrentChoices(QList<int>() << m_newIndex);
548     } else {
549         m_form->setEditChoice(m_newContents);
550     }
551     moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber);
552     emit m_docPriv->m_parent->formComboChangedByUndoRedo(m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos);
553     m_docPriv->notifyFormChanges(m_pageNumber);
554 }
555 
id() const556 int EditFormComboCommand::id() const
557 {
558     return 4;
559 }
560 
mergeWith(const QUndoCommand * uc)561 bool EditFormComboCommand::mergeWith(const QUndoCommand *uc)
562 {
563     EditFormComboCommand *euc = (EditFormComboCommand *)uc;
564     // Only attempt merge of euc into this if they modify the same form
565     if (m_form == euc->m_form) {
566         bool shouldMerge = EditTextCommand::mergeWith(uc);
567         if (shouldMerge) {
568             m_newIndex = euc->m_newIndex;
569         }
570         return shouldMerge;
571     } else {
572         return false;
573     }
574 }
575 
refreshInternalPageReferences(const QVector<Page * > & newPagesVector)576 bool EditFormComboCommand::refreshInternalPageReferences(const QVector<Page *> &newPagesVector)
577 {
578     m_form = dynamic_cast<FormFieldChoice *>(Okular::PagePrivate::findEquivalentForm(newPagesVector[m_pageNumber], m_form));
579 
580     return m_form;
581 }
582 
EditFormButtonsCommand(Okular::DocumentPrivate * docPriv,int pageNumber,const QList<FormFieldButton * > & formButtons,const QList<bool> & newButtonStates)583 EditFormButtonsCommand::EditFormButtonsCommand(Okular::DocumentPrivate *docPriv, int pageNumber, const QList<FormFieldButton *> &formButtons, const QList<bool> &newButtonStates)
584     : m_docPriv(docPriv)
585     , m_pageNumber(pageNumber)
586     , m_formButtons(formButtons)
587     , m_newButtonStates(newButtonStates)
588     , m_prevButtonStates(QList<bool>())
589 {
590     setText(i18nc("Edit the state of a group of form buttons", "edit form button states"));
591     for (const FormFieldButton *formButton : qAsConst(m_formButtons)) {
592         m_prevButtonStates.append(formButton->state());
593     }
594 }
595 
undo()596 void EditFormButtonsCommand::undo()
597 {
598     clearFormButtonStates();
599     for (int i = 0; i < m_formButtons.size(); i++) {
600         bool checked = m_prevButtonStates.at(i);
601         if (checked)
602             m_formButtons.at(i)->setState(checked);
603     }
604 
605     Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons(m_formButtons);
606     moveViewportIfBoundingRectNotFullyVisible(boundingRect, m_docPriv, m_pageNumber);
607     emit m_docPriv->m_parent->formButtonsChangedByUndoRedo(m_pageNumber, m_formButtons);
608     m_docPriv->notifyFormChanges(m_pageNumber);
609 }
610 
redo()611 void EditFormButtonsCommand::redo()
612 {
613     clearFormButtonStates();
614     for (int i = 0; i < m_formButtons.size(); i++) {
615         bool checked = m_newButtonStates.at(i);
616         if (checked)
617             m_formButtons.at(i)->setState(checked);
618     }
619 
620     Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons(m_formButtons);
621     moveViewportIfBoundingRectNotFullyVisible(boundingRect, m_docPriv, m_pageNumber);
622     emit m_docPriv->m_parent->formButtonsChangedByUndoRedo(m_pageNumber, m_formButtons);
623     m_docPriv->notifyFormChanges(m_pageNumber);
624 }
625 
refreshInternalPageReferences(const QVector<Okular::Page * > & newPagesVector)626 bool EditFormButtonsCommand::refreshInternalPageReferences(const QVector<Okular::Page *> &newPagesVector)
627 {
628     const QList<FormFieldButton *> oldFormButtons = m_formButtons;
629     m_formButtons.clear();
630     for (FormFieldButton *oldFormButton : oldFormButtons) {
631         FormFieldButton *button = dynamic_cast<FormFieldButton *>(Okular::PagePrivate::findEquivalentForm(newPagesVector[m_pageNumber], oldFormButton));
632         if (!button)
633             return false;
634         m_formButtons << button;
635     }
636 
637     return true;
638 }
639 
clearFormButtonStates()640 void EditFormButtonsCommand::clearFormButtonStates()
641 {
642     for (FormFieldButton *formButton : qAsConst(m_formButtons)) {
643         formButton->setState(false);
644     }
645 }
646 
647 }
648