/* SPDX-FileCopyrightText: 2013 Jon Mease Work sponsored by the LiMux project of the city of Munich: SPDX-FileCopyrightText: 2017 Klarälvdalens Datakonsult AB a KDAB Group company SPDX-License-Identifier: GPL-2.0-or-later */ #include "documentcommands_p.h" #include "annotations.h" #include "debug_p.h" #include "document_p.h" #include "form.h" #include "page.h" #include "page_p.h" #include "utils_p.h" #include namespace Okular { void moveViewportIfBoundingRectNotFullyVisible(Okular::NormalizedRect boundingRect, DocumentPrivate *docPriv, int pageNumber) { const Rotation pageRotation = docPriv->m_parent->page(pageNumber)->rotation(); const QTransform rotationMatrix = Okular::buildRotationMatrix(pageRotation); boundingRect.transform(rotationMatrix); if (!docPriv->isNormalizedRectangleFullyVisible(boundingRect, pageNumber)) { DocumentViewport searchViewport(pageNumber); searchViewport.rePos.enabled = true; searchViewport.rePos.normalizedX = (boundingRect.left + boundingRect.right) / 2.0; searchViewport.rePos.normalizedY = (boundingRect.top + boundingRect.bottom) / 2.0; docPriv->m_parent->setViewport(searchViewport, nullptr, true); } } Okular::NormalizedRect buildBoundingRectangleForButtons(const QList &formButtons) { // Initialize coordinates of the bounding rect double left = 1.0; double top = 1.0; double right = 0.0; double bottom = 0.0; for (const FormFieldButton *formButton : formButtons) { left = qMin(left, formButton->rect().left); top = qMin(top, formButton->rect().top); right = qMax(right, formButton->rect().right); bottom = qMax(bottom, formButton->rect().bottom); } Okular::NormalizedRect boundingRect(left, top, right, bottom); return boundingRect; } AddAnnotationCommand::AddAnnotationCommand(Okular::DocumentPrivate *docPriv, Okular::Annotation *annotation, int pageNumber) : m_docPriv(docPriv) , m_annotation(annotation) , m_pageNumber(pageNumber) , m_done(false) { setText(i18nc("Add an annotation to the page", "add annotation")); } AddAnnotationCommand::~AddAnnotationCommand() { if (!m_done) { delete m_annotation; } } void AddAnnotationCommand::undo() { moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber); m_docPriv->performRemovePageAnnotation(m_pageNumber, m_annotation); m_done = false; } void AddAnnotationCommand::redo() { moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber); m_docPriv->performAddPageAnnotation(m_pageNumber, m_annotation); m_done = true; } bool AddAnnotationCommand::refreshInternalPageReferences(const QVector &newPagesVector) { if (m_done) { // We don't always update m_annotation because even if the annotation has been added to the document // it can have been removed later so the annotation pointer is stored inside a following RemoveAnnotationCommand // and thus doesn't need updating because it didn't change // because of the document reload auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName()); if (a) { m_annotation = a; } } return true; } RemoveAnnotationCommand::RemoveAnnotationCommand(Okular::DocumentPrivate *doc, Okular::Annotation *annotation, int pageNumber) : m_docPriv(doc) , m_annotation(annotation) , m_pageNumber(pageNumber) , m_done(false) { setText(i18nc("Remove an annotation from the page", "remove annotation")); } RemoveAnnotationCommand::~RemoveAnnotationCommand() { if (m_done) { delete m_annotation; } } void RemoveAnnotationCommand::undo() { moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber); m_docPriv->performAddPageAnnotation(m_pageNumber, m_annotation); m_done = false; } void RemoveAnnotationCommand::redo() { moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber); m_docPriv->performRemovePageAnnotation(m_pageNumber, m_annotation); m_done = true; } bool RemoveAnnotationCommand::refreshInternalPageReferences(const QVector &newPagesVector) { if (!m_done) { // We don't always update m_annotation because it can happen that the annotation remove has been undo // and that annotation addition has also been undone so the annotation pointer is stored inside // a previous AddAnnotationCommand and thus doesn't need updating because it didn't change // because of the document reload auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName()); if (a) { m_annotation = a; } } return true; } ModifyAnnotationPropertiesCommand::ModifyAnnotationPropertiesCommand(DocumentPrivate *docPriv, Annotation *annotation, int pageNumber, const QDomNode &oldProperties, const QDomNode &newProperties) : m_docPriv(docPriv) , m_annotation(annotation) , m_pageNumber(pageNumber) , m_prevProperties(oldProperties) , m_newProperties(newProperties) { setText(i18nc("Modify an annotation's internal properties (Color, line-width, etc.)", "modify annotation properties")); } void ModifyAnnotationPropertiesCommand::undo() { moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber); m_annotation->setAnnotationProperties(m_prevProperties); m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true); } void ModifyAnnotationPropertiesCommand::redo() { moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber); m_annotation->setAnnotationProperties(m_newProperties); m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true); } bool ModifyAnnotationPropertiesCommand::refreshInternalPageReferences(const QVector &newPagesVector) { // Same reason for not unconditionally updating m_annotation, the annotation pointer can be stored in an add/Remove command auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName()); if (a) { m_annotation = a; } return true; } TranslateAnnotationCommand::TranslateAnnotationCommand(DocumentPrivate *docPriv, Annotation *annotation, int pageNumber, const Okular::NormalizedPoint &delta, bool completeDrag) : m_docPriv(docPriv) , m_annotation(annotation) , m_pageNumber(pageNumber) , m_delta(delta) , m_completeDrag(completeDrag) { setText(i18nc("Translate an annotation's position on the page", "translate annotation")); } void TranslateAnnotationCommand::undo() { moveViewportIfBoundingRectNotFullyVisible(translateBoundingRectangle(minusDelta()), m_docPriv, m_pageNumber); m_annotation->translate(minusDelta()); m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true); } void TranslateAnnotationCommand::redo() { moveViewportIfBoundingRectNotFullyVisible(translateBoundingRectangle(m_delta), m_docPriv, m_pageNumber); m_annotation->translate(m_delta); m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true); } int TranslateAnnotationCommand::id() const { return 1; } bool TranslateAnnotationCommand::mergeWith(const QUndoCommand *uc) { TranslateAnnotationCommand *tuc = (TranslateAnnotationCommand *)uc; if (tuc->m_annotation != m_annotation) { return false; } if (m_completeDrag) { return false; } m_delta = Okular::NormalizedPoint(tuc->m_delta.x + m_delta.x, tuc->m_delta.y + m_delta.y); m_completeDrag = tuc->m_completeDrag; return true; } Okular::NormalizedPoint TranslateAnnotationCommand::minusDelta() { return Okular::NormalizedPoint(-m_delta.x, -m_delta.y); } Okular::NormalizedRect TranslateAnnotationCommand::translateBoundingRectangle(const Okular::NormalizedPoint &delta) { Okular::NormalizedRect annotBoundingRect = m_annotation->boundingRectangle(); annotBoundingRect.left = annotBoundingRect.left + delta.x; annotBoundingRect.right = annotBoundingRect.right + delta.x; annotBoundingRect.top = annotBoundingRect.top + delta.y; annotBoundingRect.bottom = annotBoundingRect.bottom + delta.y; return annotBoundingRect; } bool TranslateAnnotationCommand::refreshInternalPageReferences(const QVector &newPagesVector) { // Same reason for not unconditionally updating m_annotation, the annotation pointer can be stored in an add/Remove command auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName()); if (a) { m_annotation = a; } return true; } AdjustAnnotationCommand::AdjustAnnotationCommand(Okular::DocumentPrivate *docPriv, Okular::Annotation *annotation, int pageNumber, const Okular::NormalizedPoint &delta1, const Okular::NormalizedPoint &delta2, bool completeDrag) : m_docPriv(docPriv) , m_annotation(annotation) , m_pageNumber(pageNumber) , m_delta1(delta1) , m_delta2(delta2) , m_completeDrag(completeDrag) { setText(i18nc("Change an annotation's size", "adjust annotation")); } void AdjustAnnotationCommand::undo() { const NormalizedPoint minusDelta1 = Okular::NormalizedPoint(-m_delta1.x, -m_delta1.y); const NormalizedPoint minusDelta2 = Okular::NormalizedPoint(-m_delta2.x, -m_delta2.y); moveViewportIfBoundingRectNotFullyVisible(adjustBoundingRectangle(minusDelta1, minusDelta2), m_docPriv, m_pageNumber); m_annotation->adjust(minusDelta1, minusDelta2); m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true); } void AdjustAnnotationCommand::redo() { moveViewportIfBoundingRectNotFullyVisible(adjustBoundingRectangle(m_delta1, m_delta2), m_docPriv, m_pageNumber); m_annotation->adjust(m_delta1, m_delta2); m_docPriv->performModifyPageAnnotation(m_pageNumber, m_annotation, true); } int AdjustAnnotationCommand::id() const { return 5; } bool AdjustAnnotationCommand::mergeWith(const QUndoCommand *uc) { AdjustAnnotationCommand *tuc = (AdjustAnnotationCommand *)uc; if (tuc->m_annotation != m_annotation) { return false; } if (m_completeDrag) { return false; } m_delta1 = Okular::NormalizedPoint(tuc->m_delta1.x + m_delta1.x, tuc->m_delta1.y + m_delta1.y); m_delta2 = Okular::NormalizedPoint(tuc->m_delta2.x + m_delta2.x, tuc->m_delta2.y + m_delta2.y); m_completeDrag = tuc->m_completeDrag; return true; } Okular::NormalizedRect AdjustAnnotationCommand::adjustBoundingRectangle(const Okular::NormalizedPoint &delta1, const Okular::NormalizedPoint &delta2) { Okular::NormalizedRect annotBoundingRect = m_annotation->boundingRectangle(); annotBoundingRect.left = annotBoundingRect.left + delta1.x; annotBoundingRect.right = annotBoundingRect.right + delta2.x; annotBoundingRect.top = annotBoundingRect.top + delta1.y; annotBoundingRect.bottom = annotBoundingRect.bottom + delta2.y; return annotBoundingRect; } bool AdjustAnnotationCommand::refreshInternalPageReferences(const QVector &newPagesVector) { // Same reason for not unconditionally updating m_annotation, the annotation pointer can be stored in an add/Remove command auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName()); if (a) { m_annotation = a; } return true; } EditTextCommand::EditTextCommand(const QString &newContents, int newCursorPos, const QString &prevContents, int prevCursorPos, int prevAnchorPos) : m_newContents(newContents) , m_newCursorPos(newCursorPos) , m_prevContents(prevContents) , m_prevCursorPos(prevCursorPos) , m_prevAnchorPos(prevAnchorPos) { setText(i18nc("Generic text edit command", "edit text")); //// Determine edit type // If There was a selection then edit was not a simple single character backspace, delete, or insert if (m_prevCursorPos != m_prevAnchorPos) { qCDebug(OkularCoreDebug) << "OtherEdit, selection"; m_editType = OtherEdit; } else if (newContentsRightOfCursor() == oldContentsRightOfCursor() && newContentsLeftOfCursor() == oldContentsLeftOfCursor().left(oldContentsLeftOfCursor().length() - 1) && oldContentsLeftOfCursor().rightRef(1) != QLatin1Char('\n')) { qCDebug(OkularCoreDebug) << "CharBackspace"; m_editType = CharBackspace; } else if (newContentsLeftOfCursor() == oldContentsLeftOfCursor() && newContentsRightOfCursor() == oldContentsRightOfCursor().right(oldContentsRightOfCursor().length() - 1) && oldContentsRightOfCursor().leftRef(1) != QLatin1Char('\n')) { qCDebug(OkularCoreDebug) << "CharDelete"; m_editType = CharDelete; } else if (newContentsRightOfCursor() == oldContentsRightOfCursor() && newContentsLeftOfCursor().left(newContentsLeftOfCursor().length() - 1) == oldContentsLeftOfCursor() && newContentsLeftOfCursor().rightRef(1) != QLatin1Char('\n')) { qCDebug(OkularCoreDebug) << "CharInsert"; m_editType = CharInsert; } else { qCDebug(OkularCoreDebug) << "OtherEdit"; m_editType = OtherEdit; } } bool EditTextCommand::mergeWith(const QUndoCommand *uc) { EditTextCommand *euc = (EditTextCommand *)uc; // Only attempt merge of euc into this if our new state matches euc's old state and // the editTypes match and are not type OtherEdit if (m_newContents == euc->m_prevContents && m_newCursorPos == euc->m_prevCursorPos && m_editType == euc->m_editType && m_editType != OtherEdit) { m_newContents = euc->m_newContents; m_newCursorPos = euc->m_newCursorPos; return true; } return false; } QString EditTextCommand::oldContentsLeftOfCursor() { return m_prevContents.left(m_prevCursorPos); } QString EditTextCommand::oldContentsRightOfCursor() { return m_prevContents.right(m_prevContents.length() - m_prevCursorPos); } QString EditTextCommand::newContentsLeftOfCursor() { return m_newContents.left(m_newCursorPos); } QString EditTextCommand::newContentsRightOfCursor() { return m_newContents.right(m_newContents.length() - m_newCursorPos); } EditAnnotationContentsCommand::EditAnnotationContentsCommand(DocumentPrivate *docPriv, Annotation *annotation, int pageNumber, const QString &newContents, int newCursorPos, const QString &prevContents, int prevCursorPos, int prevAnchorPos) : EditTextCommand(newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos) , m_docPriv(docPriv) , m_annotation(annotation) , m_pageNumber(pageNumber) { setText(i18nc("Edit an annotation's text contents", "edit annotation contents")); } void EditAnnotationContentsCommand::undo() { moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber); m_docPriv->performSetAnnotationContents(m_prevContents, m_annotation, m_pageNumber); Q_EMIT m_docPriv->m_parent->annotationContentsChangedByUndoRedo(m_annotation, m_prevContents, m_prevCursorPos, m_prevAnchorPos); } void EditAnnotationContentsCommand::redo() { moveViewportIfBoundingRectNotFullyVisible(m_annotation->boundingRectangle(), m_docPriv, m_pageNumber); m_docPriv->performSetAnnotationContents(m_newContents, m_annotation, m_pageNumber); Q_EMIT m_docPriv->m_parent->annotationContentsChangedByUndoRedo(m_annotation, m_newContents, m_newCursorPos, m_newCursorPos); } int EditAnnotationContentsCommand::id() const { return 2; } bool EditAnnotationContentsCommand::mergeWith(const QUndoCommand *uc) { EditAnnotationContentsCommand *euc = (EditAnnotationContentsCommand *)uc; // Only attempt merge of euc into this if they modify the same annotation if (m_annotation == euc->m_annotation) { return EditTextCommand::mergeWith(uc); } else { return false; } } bool EditAnnotationContentsCommand::refreshInternalPageReferences(const QVector &newPagesVector) { auto a = newPagesVector[m_pageNumber]->annotation(m_annotation->uniqueName()); if (a) { m_annotation = a; } return true; } EditFormTextCommand::EditFormTextCommand(Okular::DocumentPrivate *docPriv, Okular::FormFieldText *form, int pageNumber, const QString &newContents, int newCursorPos, const QString &prevContents, int prevCursorPos, int prevAnchorPos) : EditTextCommand(newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos) , m_docPriv(docPriv) , m_form(form) , m_pageNumber(pageNumber) { setText(i18nc("Edit an form's text contents", "edit form contents")); } void EditFormTextCommand::undo() { moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber); m_form->setText(m_prevContents); Q_EMIT m_docPriv->m_parent->formTextChangedByUndoRedo(m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos); m_docPriv->notifyFormChanges(m_pageNumber); } void EditFormTextCommand::redo() { moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber); m_form->setText(m_newContents); Q_EMIT m_docPriv->m_parent->formTextChangedByUndoRedo(m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos); m_docPriv->notifyFormChanges(m_pageNumber); } int EditFormTextCommand::id() const { return 3; } bool EditFormTextCommand::mergeWith(const QUndoCommand *uc) { EditFormTextCommand *euc = (EditFormTextCommand *)uc; // Only attempt merge of euc into this if they modify the same form if (m_form == euc->m_form) { return EditTextCommand::mergeWith(uc); } else { return false; } } bool EditFormTextCommand::refreshInternalPageReferences(const QVector &newPagesVector) { m_form = dynamic_cast(Okular::PagePrivate::findEquivalentForm(newPagesVector[m_pageNumber], m_form)); return m_form; } EditFormListCommand::EditFormListCommand(Okular::DocumentPrivate *docPriv, FormFieldChoice *form, int pageNumber, const QList &newChoices, const QList &prevChoices) : m_docPriv(docPriv) , m_form(form) , m_pageNumber(pageNumber) , m_newChoices(newChoices) , m_prevChoices(prevChoices) { setText(i18nc("Edit a list form's choices", "edit list form choices")); } void EditFormListCommand::undo() { moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber); m_form->setCurrentChoices(m_prevChoices); Q_EMIT m_docPriv->m_parent->formListChangedByUndoRedo(m_pageNumber, m_form, m_prevChoices); m_docPriv->notifyFormChanges(m_pageNumber); } void EditFormListCommand::redo() { moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber); m_form->setCurrentChoices(m_newChoices); Q_EMIT m_docPriv->m_parent->formListChangedByUndoRedo(m_pageNumber, m_form, m_newChoices); m_docPriv->notifyFormChanges(m_pageNumber); } bool EditFormListCommand::refreshInternalPageReferences(const QVector &newPagesVector) { m_form = dynamic_cast(Okular::PagePrivate::findEquivalentForm(newPagesVector[m_pageNumber], m_form)); return m_form; } EditFormComboCommand::EditFormComboCommand(Okular::DocumentPrivate *docPriv, FormFieldChoice *form, int pageNumber, const QString &newContents, int newCursorPos, const QString &prevContents, int prevCursorPos, int prevAnchorPos) : EditTextCommand(newContents, newCursorPos, prevContents, prevCursorPos, prevAnchorPos) , m_docPriv(docPriv) , m_form(form) , m_pageNumber(pageNumber) , m_newIndex(-1) , m_prevIndex(-1) { setText(i18nc("Edit a combo form's selection", "edit combo form selection")); // Determine new and previous choice indices (if any) for (int i = 0; i < m_form->choices().size(); i++) { if (m_form->choices().at(i) == m_prevContents) { m_prevIndex = i; } if (m_form->choices().at(i) == m_newContents) { m_newIndex = i; } } } void EditFormComboCommand::undo() { if (m_prevIndex != -1) { m_form->setCurrentChoices(QList() << m_prevIndex); } else { m_form->setEditChoice(m_prevContents); } moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber); Q_EMIT m_docPriv->m_parent->formComboChangedByUndoRedo(m_pageNumber, m_form, m_prevContents, m_prevCursorPos, m_prevAnchorPos); m_docPriv->notifyFormChanges(m_pageNumber); } void EditFormComboCommand::redo() { if (m_newIndex != -1) { m_form->setCurrentChoices(QList() << m_newIndex); } else { m_form->setEditChoice(m_newContents); } moveViewportIfBoundingRectNotFullyVisible(m_form->rect(), m_docPriv, m_pageNumber); Q_EMIT m_docPriv->m_parent->formComboChangedByUndoRedo(m_pageNumber, m_form, m_newContents, m_newCursorPos, m_newCursorPos); m_docPriv->notifyFormChanges(m_pageNumber); } int EditFormComboCommand::id() const { return 4; } bool EditFormComboCommand::mergeWith(const QUndoCommand *uc) { EditFormComboCommand *euc = (EditFormComboCommand *)uc; // Only attempt merge of euc into this if they modify the same form if (m_form == euc->m_form) { bool shouldMerge = EditTextCommand::mergeWith(uc); if (shouldMerge) { m_newIndex = euc->m_newIndex; } return shouldMerge; } else { return false; } } bool EditFormComboCommand::refreshInternalPageReferences(const QVector &newPagesVector) { m_form = dynamic_cast(Okular::PagePrivate::findEquivalentForm(newPagesVector[m_pageNumber], m_form)); return m_form; } EditFormButtonsCommand::EditFormButtonsCommand(Okular::DocumentPrivate *docPriv, int pageNumber, const QList &formButtons, const QList &newButtonStates) : m_docPriv(docPriv) , m_pageNumber(pageNumber) , m_formButtons(formButtons) , m_newButtonStates(newButtonStates) , m_prevButtonStates(QList()) { setText(i18nc("Edit the state of a group of form buttons", "edit form button states")); for (const FormFieldButton *formButton : qAsConst(m_formButtons)) { m_prevButtonStates.append(formButton->state()); } } void EditFormButtonsCommand::undo() { clearFormButtonStates(); for (int i = 0; i < m_formButtons.size(); i++) { bool checked = m_prevButtonStates.at(i); if (checked) { m_formButtons.at(i)->setState(checked); } } Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons(m_formButtons); moveViewportIfBoundingRectNotFullyVisible(boundingRect, m_docPriv, m_pageNumber); Q_EMIT m_docPriv->m_parent->formButtonsChangedByUndoRedo(m_pageNumber, m_formButtons); m_docPriv->notifyFormChanges(m_pageNumber); } void EditFormButtonsCommand::redo() { clearFormButtonStates(); for (int i = 0; i < m_formButtons.size(); i++) { bool checked = m_newButtonStates.at(i); if (checked) { m_formButtons.at(i)->setState(checked); } } Okular::NormalizedRect boundingRect = buildBoundingRectangleForButtons(m_formButtons); moveViewportIfBoundingRectNotFullyVisible(boundingRect, m_docPriv, m_pageNumber); Q_EMIT m_docPriv->m_parent->formButtonsChangedByUndoRedo(m_pageNumber, m_formButtons); m_docPriv->notifyFormChanges(m_pageNumber); } bool EditFormButtonsCommand::refreshInternalPageReferences(const QVector &newPagesVector) { const QList oldFormButtons = m_formButtons; m_formButtons.clear(); for (FormFieldButton *oldFormButton : oldFormButtons) { FormFieldButton *button = dynamic_cast(Okular::PagePrivate::findEquivalentForm(newPagesVector[m_pageNumber], oldFormButton)); if (!button) { return false; } m_formButtons << button; } return true; } void EditFormButtonsCommand::clearFormButtonStates() { for (FormFieldButton *formButton : qAsConst(m_formButtons)) { formButton->setState(false); } } }