From be0d02bb5ca06c0ce8145a2bcd83f664b41f8b7e Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 1 Oct 2023 17:35:42 +0200 Subject: [PATCH 1/4] Issue #76: Basic functionality of outline editing --- Pdf4QtLib/sources/pdfaction.cpp | 170 ++++++++++++++ Pdf4QtLib/sources/pdfaction.h | 72 +++++- Pdf4QtLib/sources/pdfitemmodels.cpp | 275 ++++++++++++++++++++++- Pdf4QtLib/sources/pdfitemmodels.h | 47 +++- Pdf4QtLib/sources/pdfoutline.cpp | 23 ++ Pdf4QtLib/sources/pdfoutline.h | 2 + Pdf4QtViewer/pdfsidebarwidget.cpp | 129 ++++++++++- Pdf4QtViewer/pdfsidebarwidget.h | 2 + Pdf4QtViewer/pdfviewermainwindow.cpp | 2 +- Pdf4QtViewer/pdfviewermainwindowlite.cpp | 2 +- 10 files changed, 711 insertions(+), 13 deletions(-) diff --git a/Pdf4QtLib/sources/pdfaction.cpp b/Pdf4QtLib/sources/pdfaction.cpp index 116856c..3be3982 100644 --- a/Pdf4QtLib/sources/pdfaction.cpp +++ b/Pdf4QtLib/sources/pdfaction.cpp @@ -370,6 +370,17 @@ void PDFAction::fillActionList(std::vector& actionList) const } } +void PDFAction::cloneActionList(const PDFAction* sourceAction) +{ + if (sourceAction) + { + for (const auto& action : sourceAction->m_nextActions) + { + m_nextActions.push_back(action->clone()); + } + } +} + PDFDestination PDFDestination::parse(const PDFObjectStorage* storage, PDFObject object) { PDFDestination result; @@ -711,6 +722,16 @@ QString PDFActionURI::getURIString() const return QString::fromUtf8(m_URI); } +void PDFActionURI::setURI(const QByteArray& newURI) +{ + m_URI = newURI; +} + +void PDFActionURI::setIsMap(bool newIsMap) +{ + m_isMap = newIsMap; +} + void PDFActionGoTo::setDestination(const PDFDestination& destination) { m_destination = destination; @@ -721,4 +742,153 @@ void PDFActionGoTo::setStructureDestination(const PDFDestination& structureDesti m_structureDestination = structureDestination; } +PDFActionPtr PDFActionGoTo::clone() const +{ + PDFActionGoTo* clonedAction = new PDFActionGoTo(getDestination(), getStructureDestination()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionGoToR::clone() const +{ + PDFActionGoToR* clonedAction = new PDFActionGoToR(getDestination(), + getStructureDestination(), + getFileSpecification(), + isNewWindow()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionGoToE::clone() const +{ + PDFActionGoToE* clonedAction = new PDFActionGoToE(getDestination(), + getFileSpecification(), + isNewWindow(), + getTarget()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionGoToDp::clone() const +{ + PDFActionGoToDp* clonedAction = new PDFActionGoToDp(getDocumentPart()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionLaunch::clone() const +{ + PDFActionLaunch* clonedAction = new PDFActionLaunch(getFileSpecification(), isNewWindow(), getWinSpecification()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionThread::clone() const +{ + PDFActionThread* clonedAction = new PDFActionThread(getFileSpecification(), getThread(), getBead()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionURI::clone() const +{ + PDFActionURI* clonedAction = new PDFActionURI(getURI(), isMap()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionSound::clone() const +{ + PDFActionSound* clonedAction = new PDFActionSound(*getSound(), getVolume(), isSynchronous(), isRepeat(), isMix()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionMovie::clone() const +{ + PDFActionMovie* clonedAction = new PDFActionMovie(getAnnotation(), getTitle(), getOperation()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionHide::clone() const +{ + PDFActionHide* clonedAction = new PDFActionHide(getAnnotations(), getFieldNames(), isHide()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionNamed::clone() const +{ + PDFActionNamed* clonedAction = new PDFActionNamed(getNamedActionType(), getCustomNamedAction()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionSetOCGState::clone() const +{ + PDFActionSetOCGState* clonedAction = new PDFActionSetOCGState(getStateChangeItems(), isRadioButtonsPreserved()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionTransition::clone() const +{ + PDFActionTransition* clonedAction = new PDFActionTransition(getTransition()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionGoTo3DView::clone() const +{ + PDFActionGoTo3DView* clonedAction = new PDFActionGoTo3DView(getAnnotation(), getView()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionJavaScript::clone() const +{ + PDFActionJavaScript* clonedAction = new PDFActionJavaScript(getJavaScript()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionRichMediaExecute::clone() const +{ + PDFActionRichMediaExecute* clonedAction = new PDFActionRichMediaExecute(getRichMediaAnnotation(), + getRichMediaInstance(), + getCommand(), + getArguments()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionSubmitForm::clone() const +{ + PDFActionSubmitForm* clonedAction = new PDFActionSubmitForm(getFieldScope(), getFieldList(), getUrl(), getCharset(), getFlags()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionResetForm::clone() const +{ + PDFActionResetForm* clonedAction = new PDFActionResetForm(getFieldScope(), getFieldList(), getFlags()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionImportDataForm::clone() const +{ + PDFActionImportDataForm* clonedAction = new PDFActionImportDataForm(getFile()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + +PDFActionPtr PDFActionRendition::clone() const +{ + PDFActionRendition* clonedAction = new PDFActionRendition(m_rendition, getAnnotation(), getOperation(), getJavaScript()); + clonedAction->cloneActionList(this); + return PDFActionPtr(clonedAction); +} + } // namespace pdf diff --git a/Pdf4QtLib/sources/pdfaction.h b/Pdf4QtLib/sources/pdfaction.h index 88faaaf..9cf20cf 100644 --- a/Pdf4QtLib/sources/pdfaction.h +++ b/Pdf4QtLib/sources/pdfaction.h @@ -169,6 +169,12 @@ public: /// Returns list of actions to be executed std::vector getActionList() const; + /// Clone action + virtual PDFActionPtr clone() const = 0; + +protected: + void cloneActionList(const PDFAction* sourceAction); + private: static PDFActionPtr parseImpl(const PDFObjectStorage* storage, PDFObject object, std::set& usedReferences); @@ -180,7 +186,7 @@ private: /// Regular go-to action. Can contain also structure destinations, both regular page destination /// and structure destination are present, because if structure destination fails, then /// page destination can be used as fallback resolution. -class PDFActionGoTo : public PDFAction +class PDF4QTLIBSHARED_EXPORT PDFActionGoTo : public PDFAction { public: explicit inline PDFActionGoTo(PDFDestination destination, PDFDestination structureDestination) : @@ -194,6 +200,8 @@ public: void setDestination(const PDFDestination& destination); void setStructureDestination(const PDFDestination& structureDestination); + virtual PDFActionPtr clone() const override; + private: PDFDestination m_destination; PDFDestination m_structureDestination; @@ -218,6 +226,8 @@ public: const PDFFileSpecification& getFileSpecification() const { return m_fileSpecification; } bool isNewWindow() const { return m_newWindow; } + virtual PDFActionPtr clone() const override; + private: PDFDestination m_destination; PDFDestination m_structureDestination; @@ -244,6 +254,8 @@ public: bool isNewWindow() const { return m_newWindow; } const PDFObject& getTarget() const { return m_target; } + virtual PDFActionPtr clone() const override; + private: PDFDestination m_destination; PDFFileSpecification m_fileSpecification; @@ -262,6 +274,8 @@ public: PDFObjectReference getDocumentPart() const { return m_documentPart; } + virtual PDFActionPtr clone() const override; + private: PDFObjectReference m_documentPart; }; @@ -293,6 +307,8 @@ public: const Win& getWinSpecification() const { return m_win; } bool isNewWindow() const { return m_newWindow; } + virtual PDFActionPtr clone() const override; + private: PDFFileSpecification m_fileSpecification; bool m_newWindow = false; @@ -319,6 +335,8 @@ public: const Thread& getThread() const { return m_thread; } const Bead& getBead() const { return m_bead; } + virtual PDFActionPtr clone() const override; + private: PDFFileSpecification m_fileSpecification; Thread m_thread; @@ -344,6 +362,11 @@ public: /// to PDF specification, URI is UTF-8 encoded string. QString getURIString() const; + virtual PDFActionPtr clone() const override; + + void setURI(const QByteArray& newURI); + void setIsMap(bool newIsMap); + private: QByteArray m_URI; bool m_isMap; @@ -370,6 +393,8 @@ public: bool isRepeat() const { return m_isRepeat; } bool isMix() const { return m_isMix; } + virtual PDFActionPtr clone() const override; + private: PDFSound m_sound; PDFReal m_volume; @@ -403,6 +428,8 @@ public: const QString& getTitle() const { return m_title; } Operation getOperation() const { return m_operation; } + virtual PDFActionPtr clone() const override; + private: PDFObjectReference m_annotation; QString m_title; @@ -420,12 +447,22 @@ public: } + explicit inline PDFActionHide(const std::vector& annotations, const std::vector& fieldNames, bool hide) : + m_annotations(annotations), + m_fieldNames(fieldNames), + m_hide(hide) + { + + } + virtual ActionType getType() const override { return ActionType::Hide; } const std::vector& getAnnotations() const { return m_annotations; } const std::vector& getFieldNames() const { return m_fieldNames; } bool isHide() const { return m_hide; } + virtual PDFActionPtr clone() const override; + private: std::vector m_annotations; std::vector m_fieldNames; @@ -451,11 +488,20 @@ public: } + explicit inline PDFActionNamed(NamedActionType namedActionType, const QByteArray& customNamedAction) : + m_namedActionType(namedActionType), + m_customNamedAction(customNamedAction) + { + + } + virtual ActionType getType() const override { return ActionType::Named; } NamedActionType getNamedActionType() const { return m_namedActionType; } const QByteArray& getCustomNamedAction() const { return m_customNamedAction; } + virtual PDFActionPtr clone() const override; + private: NamedActionType m_namedActionType; QByteArray m_customNamedAction; @@ -475,7 +521,7 @@ public: using StateChangeItem = std::pair; using StateChangeItems = std::vector; - explicit inline PDFActionSetOCGState(StateChangeItems&& stateChangeItems, bool isRadioButtonsPreserved) : + explicit inline PDFActionSetOCGState(StateChangeItems stateChangeItems, bool isRadioButtonsPreserved) : m_items(qMove(stateChangeItems)), m_isRadioButtonsPreserved(isRadioButtonsPreserved) { @@ -487,6 +533,8 @@ public: const StateChangeItems& getStateChangeItems() const { return m_items; } bool isRadioButtonsPreserved() const { return m_isRadioButtonsPreserved; } + virtual PDFActionPtr clone() const override; + private: StateChangeItems m_items; bool m_isRadioButtonsPreserved; @@ -505,7 +553,7 @@ public: Play = 4 }; - explicit inline PDFActionRendition(std::optional&& rendition, PDFObjectReference annotation, Operation operation, QString javascript) : + explicit inline PDFActionRendition(std::optional rendition, PDFObjectReference annotation, Operation operation, QString javascript) : m_rendition(qMove(rendition)), m_annotation(annotation), m_operation(operation), @@ -521,6 +569,8 @@ public: Operation getOperation() const { return m_operation; } const QString& getJavaScript() const { return m_javascript; } + virtual PDFActionPtr clone() const override; + private: std::optional m_rendition; PDFObjectReference m_annotation; @@ -531,7 +581,7 @@ private: class PDFActionTransition : public PDFAction { public: - explicit inline PDFActionTransition(PDFPageTransition&& transition) : + explicit inline PDFActionTransition(PDFPageTransition transition) : m_transition(qMove(transition)) { @@ -541,6 +591,8 @@ public: const PDFPageTransition& getTransition() const { return m_transition; } + virtual PDFActionPtr clone() const override; + private: PDFPageTransition m_transition; }; @@ -560,6 +612,8 @@ public: const PDFObject& getAnnotation() const { return m_annotation; } const PDFObject& getView() const { return m_view; } + virtual PDFActionPtr clone() const override; + private: PDFObject m_annotation; PDFObject m_view; @@ -578,6 +632,8 @@ public: const QString& getJavaScript() const { return m_javaScript; } + virtual PDFActionPtr clone() const override; + private: QString m_javaScript; }; @@ -604,6 +660,8 @@ public: QString getCommand() const { return m_command; } PDFObject getArguments() const { return m_arguments; } + virtual PDFActionPtr clone() const override; + private: PDFObjectReference m_richMediaAnnotation; PDFObjectReference m_richMediaInstance; @@ -689,6 +747,8 @@ public: const QByteArray& getCharset() const { return m_charset; } SubmitFlags getFlags() const { return m_flags; } + virtual PDFActionPtr clone() const override; + private: PDFFileSpecification m_url; QByteArray m_charset; @@ -717,6 +777,8 @@ public: ResetFlags getFlags() const { return m_flags; } + virtual PDFActionPtr clone() const override; + private: ResetFlags m_flags = None; }; @@ -735,6 +797,8 @@ public: const PDFFileSpecification& getFile() const { return m_file; } + virtual PDFActionPtr clone() const override; + private: PDFFileSpecification m_file; }; diff --git a/Pdf4QtLib/sources/pdfitemmodels.cpp b/Pdf4QtLib/sources/pdfitemmodels.cpp index cfb347c..f205277 100644 --- a/Pdf4QtLib/sources/pdfitemmodels.cpp +++ b/Pdf4QtLib/sources/pdfitemmodels.cpp @@ -35,6 +35,13 @@ PDFTreeItem::~PDFTreeItem() qDeleteAll(m_children); } +PDFTreeItem* PDFTreeItem::takeChild(int index) +{ + PDFTreeItem* item = m_children.at(index); + m_children.erase(m_children.begin() + index); + return item; +} + PDFTreeItemModel::PDFTreeItemModel(QObject* parent) : QAbstractItemModel(parent), m_document(nullptr) @@ -368,6 +375,7 @@ QVariant PDFOutlineTreeItemModel::data(const QModelIndex& index, int role) const switch (role) { case Qt::DisplayRole: + case Qt::EditRole: return outlineItem->getTitle(); case Qt::ForegroundRole: @@ -409,6 +417,11 @@ void PDFOutlineTreeItemModel::update() } if (outlineRoot) { + if (m_editable) + { + outlineRoot = outlineRoot->clone(); + } + m_rootItem.reset(new PDFOutlineTreeItem(nullptr, qMove(outlineRoot))); } else @@ -425,13 +438,16 @@ Qt::ItemFlags PDFOutlineTreeItemModel::flags(const QModelIndex& index) const if (!index.isValid()) { + if (m_editable) + { + flags = flags | Qt::ItemIsDropEnabled; + } return flags; } - const PDFOutlineTreeItem* item = static_cast(index.internalPointer()); - if (item->getChildCount() == 0) + if (m_editable) { - flags = flags | Qt::ItemNeverHasChildren; + flags = flags | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; } return flags; @@ -449,6 +465,259 @@ const PDFAction* PDFOutlineTreeItemModel::getAction(const QModelIndex& index) co return nullptr; } +const PDFOutlineItem* PDFOutlineTreeItemModel::getOutlineItem(const QModelIndex& index) const +{ + if (index.isValid()) + { + const PDFOutlineTreeItem* item = static_cast(index.internalPointer()); + const PDFOutlineItem* outlineItem = item->getOutlineItem(); + return outlineItem; + } + + return nullptr; +} + +PDFOutlineItem* PDFOutlineTreeItemModel::getOutlineItem(const QModelIndex& index) +{ + if (index.isValid()) + { + PDFOutlineTreeItem* item = static_cast(index.internalPointer()); + PDFOutlineItem* outlineItem = item->getOutlineItem(); + return outlineItem; + } + + return nullptr; +} + +void PDFOutlineTreeItemModel::setFontBold(const QModelIndex& index, bool value) +{ + if (PDFOutlineItem* outlineItem = getOutlineItem(index)) + { + if (outlineItem->isFontBold() != value) + { + outlineItem->setFontBold(value); + Q_EMIT dataChanged(index, index); + } + } +} + +void PDFOutlineTreeItemModel::setFontItalics(const QModelIndex& index, bool value) +{ + if (PDFOutlineItem* outlineItem = getOutlineItem(index)) + { + if (outlineItem->isFontItalics() != value) + { + outlineItem->setFontItalics(value); + Q_EMIT dataChanged(index, index); + } + } +} + +void PDFOutlineTreeItemModel::setDestination(const QModelIndex& index, const PDFDestination& destination) +{ + if (PDFOutlineItem* outlineItem = getOutlineItem(index)) + { + outlineItem->setAction(PDFActionPtr(new PDFActionGoTo(destination, PDFDestination()))); + Q_EMIT dataChanged(index, index); + } +} + +bool PDFOutlineTreeItemModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!m_editable || !index.isValid() || role != Qt::EditRole) + { + return false; + } + + PDFOutlineTreeItem* item = static_cast(index.internalPointer()); + PDFOutlineItem* outlineItem = item->getOutlineItem(); + if (outlineItem->getTitle() != value.toString()) + { + outlineItem->setTitle(value.toString()); + Q_EMIT dataChanged(index, index); + } + + return true; +} + +Qt::DropActions PDFOutlineTreeItemModel::supportedDropActions() const +{ + if (!m_editable) + { + return Qt::IgnoreAction; + } + + return Qt::CopyAction | Qt::MoveAction; +} + +Qt::DropActions PDFOutlineTreeItemModel::supportedDragActions() const +{ + if (!m_editable) + { + return Qt::IgnoreAction; + } + + return Qt::CopyAction | Qt::MoveAction; +} + +QStringList PDFOutlineTreeItemModel::mimeTypes() const +{ + return QStringList() << "application/PDF4QT_PDFOutlineTreeItemModel"; +} + +QMimeData* PDFOutlineTreeItemModel::mimeData(const QModelIndexList& indexes) const +{ + QMimeData* mimeData = new QMimeData(); + + if (indexes.size() == 1) + { + QByteArray ba; + + { + QDataStream stream(&ba, QDataStream::WriteOnly); + stream << indexes.front().internalId(); + } + + mimeData->setData(mimeTypes().front(), ba); + } + + return mimeData; +} + +bool PDFOutlineTreeItemModel::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const +{ + Q_UNUSED(row); + Q_UNUSED(column); + Q_UNUSED(parent); + + return action == Qt::MoveAction && data->hasFormat(mimeTypes().front()); +} + +bool PDFOutlineTreeItemModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) +{ + if (!data || action != Qt::MoveAction) + { + return false; + } + + QByteArray pointerData = data->data(mimeTypes().front()); + QDataStream stream(pointerData); + quintptr pointer = 0; + stream >> pointer; + + PDFOutlineTreeItem* item = reinterpret_cast(pointer); + QModelIndex sourceIndex = createIndex(item->getRow(), column, item); + return moveRow(sourceIndex.parent(), sourceIndex.row(), parent, row); +} + +bool PDFOutlineTreeItemModel::insertRows(int row, int count, const QModelIndex& parent) +{ + if (!m_editable || row < 0 || count <= 0 || row > rowCount(parent)) + { + return false; + } + + beginInsertRows(parent, row, row + count - 1); + + PDFOutlineTreeItem* item = parent.isValid() ? static_cast(parent.internalPointer()) : static_cast(m_rootItem.get()); + while (count > 0) + { + QSharedPointer outlineItem(new PDFOutlineItem()); + outlineItem->setTitle(tr("Item %1").arg(row + 1)); + PDFOutlineTreeItem* newTreeItem = new PDFOutlineTreeItem(item, qMove(outlineItem)); + item->insertCreatedChild(row, newTreeItem); + + ++row; + --count; + } + + endInsertRows(); + + return true; +} + +bool PDFOutlineTreeItemModel::removeRows(int row, int count, const QModelIndex& parent) +{ + if (!m_editable || count <= 0 || row < 0 || row + count > rowCount(parent)) + { + return false; + } + + beginRemoveRows(parent, row, row + count - 1); + + PDFOutlineTreeItem* item = parent.isValid() ? static_cast(parent.internalPointer()) : static_cast(m_rootItem.get()); + while (count > 0) + { + delete item->takeChild(row); + --count; + } + + endRemoveRows(); + + return false; +} + +bool PDFOutlineTreeItemModel::moveRows(const QModelIndex& sourceParent, int sourceRow, int count, const QModelIndex& destinationParent, int destinationChild) +{ + if (sourceRow < 0 || count <= 0 || (sourceParent == destinationParent && (sourceRow == destinationChild || sourceRow + count <= destinationChild))) + { + return false; + } + + PDFOutlineTreeItem* sourceNode = nullptr; + PDFOutlineTreeItem* destNode = nullptr; + + if (sourceParent.isValid()) + { + sourceNode = static_cast(sourceParent.internalPointer()); + } + else + { + sourceNode = static_cast(m_rootItem.get()); + } + + if (destinationParent.isValid()) + { + destNode = static_cast(destinationParent.internalPointer()); + } + else + { + destNode = static_cast(m_rootItem.get()); + } + + if (sourceRow + count > sourceNode->getChildCount()) + { + return false; + } + + if (destinationChild < 0) + { + destinationChild = 0; + } + + // Signalizace začátku přesunu řádků + if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild)) + { + return false; + } + + QList nodesToMove; + for (int i = 0; i < count; ++i) + { + nodesToMove.append(static_cast(sourceNode->takeChild(sourceRow))); + } + + for (PDFOutlineTreeItem* node : nodesToMove) + { + destNode->insertCreatedChild(destinationChild++, node); + } + + // Signalizace konce přesunu řádků + endMoveRows(); + + return true; +} + int PDFAttachmentsTreeItemModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); diff --git a/Pdf4QtLib/sources/pdfitemmodels.h b/Pdf4QtLib/sources/pdfitemmodels.h index 0222219..05344e4 100644 --- a/Pdf4QtLib/sources/pdfitemmodels.h +++ b/Pdf4QtLib/sources/pdfitemmodels.h @@ -36,6 +36,7 @@ class PDFModifiedDocument; class PDFFileSpecification; class PDFOptionalContentActivity; class PDFDrawWidgetProxy; +class PDFDestination; /// Represents tree item in the GUI tree class PDF4QTLIBSHARED_EXPORT PDFTreeItem @@ -53,6 +54,20 @@ public: return item; } + template + inline T* insertChild(int position, Arguments&&... arguments) + { + T* item = new T(this, std::forward(arguments)...); + m_children.insert(std::next(m_children.begin(), position), item); + return item; + } + + void insertCreatedChild(int position, PDFTreeItem* item) + { + item->m_parent = this; + m_children.insert(std::next(m_children.begin(), position), item); + } + void addCreatedChild(PDFTreeItem* item) { item->m_parent = this; @@ -64,6 +79,7 @@ public: const PDFTreeItem* getChild(int index) const { return m_children.at(index); } PDFTreeItem* getChild(int index) { return m_children.at(index); } const PDFTreeItem* getParent() const { return m_parent; } + PDFTreeItem* takeChild(int index); private: PDFTreeItem* m_parent = nullptr; @@ -145,6 +161,7 @@ public: explicit PDFOutlineTreeItem(PDFOutlineTreeItem* parent, QSharedPointer outlineItem); const PDFOutlineItem* getOutlineItem() const { return m_outlineItem.data(); } + PDFOutlineItem* getOutlineItem() { return m_outlineItem.data(); } private: QSharedPointer m_outlineItem; @@ -154,9 +171,10 @@ class PDF4QTLIBSHARED_EXPORT PDFOutlineTreeItemModel : public PDFTreeItemModel { Q_OBJECT public: - PDFOutlineTreeItemModel(QIcon icon, QObject* parent) : + PDFOutlineTreeItemModel(QIcon icon, bool editable, QObject* parent) : PDFTreeItemModel(parent), - m_icon(qMove(icon)) + m_icon(qMove(icon)), + m_editable(editable) { } @@ -165,14 +183,37 @@ public: virtual QVariant data(const QModelIndex& index, int role) const override; virtual void update() override; virtual Qt::ItemFlags flags(const QModelIndex& index) const override; + virtual bool setData(const QModelIndex& index, const QVariant& value, int role) override; + virtual Qt::DropActions supportedDropActions() const override; + virtual Qt::DropActions supportedDragActions() const override; + virtual bool insertRows(int row, int count, const QModelIndex& parent) override; + virtual bool removeRows(int row, int count, const QModelIndex& parent) override; + virtual bool moveRows(const QModelIndex& sourceParent, int sourceRow, int count, const QModelIndex& destinationParent, int destinationChild) override; + virtual QStringList mimeTypes() const override; + virtual QMimeData* mimeData(const QModelIndexList& indexes) const override; + virtual bool canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const override; + virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; /// Returns action assigned to the index. If index is invalid, or /// points to the invalid item, nullptr is returned. /// \param index Index of the outline item const PDFAction* getAction(const QModelIndex& index) const; + /// Returns the outline item for the given index, or nullptr + /// if no outline item is assigned to that index. + const PDFOutlineItem* getOutlineItem(const QModelIndex& index) const; + + /// Returns the outline item for the given index, or nullptr + /// if no outline item is assigned to that index. + PDFOutlineItem* getOutlineItem(const QModelIndex& index); + + void setFontBold(const QModelIndex& index, bool value); + void setFontItalics(const QModelIndex& index, bool value); + void setDestination(const QModelIndex& index, const PDFDestination& destination); + private: QIcon m_icon; + bool m_editable; }; class PDF4QTLIBSHARED_EXPORT PDFSelectableOutlineTreeItemModel : public PDFOutlineTreeItemModel @@ -184,7 +225,7 @@ private: public: PDFSelectableOutlineTreeItemModel(QIcon icon, QObject* parent) : - BaseClass(qMove(icon), parent) + BaseClass(qMove(icon), false, parent) { } diff --git a/Pdf4QtLib/sources/pdfoutline.cpp b/Pdf4QtLib/sources/pdfoutline.cpp index e88f4ad..fb54187 100644 --- a/Pdf4QtLib/sources/pdfoutline.cpp +++ b/Pdf4QtLib/sources/pdfoutline.cpp @@ -156,6 +156,29 @@ void PDFOutlineItem::apply(const std::function& functor) } } +QSharedPointer PDFOutlineItem::clone() const +{ + QSharedPointer result(new PDFOutlineItem()); + + result->setTitle(getTitle()); + result->setTextColor(getTextColor()); + result->setStructureElement(getStructureElement()); + result->setFontItalics(isFontItalics()); + result->setFontBold(isFontBold()); + + if (auto action = getAction()) + { + result->setAction(action->clone()); + } + + for (size_t i = 0; i < getChildCount(); ++i) + { + result->addChild(getChild(i)->clone()); + } + + return result; +} + bool PDFOutlineItem::isFontBold() const { return m_fontBold; diff --git a/Pdf4QtLib/sources/pdfoutline.h b/Pdf4QtLib/sources/pdfoutline.h index 85822cd..37479f3 100644 --- a/Pdf4QtLib/sources/pdfoutline.h +++ b/Pdf4QtLib/sources/pdfoutline.h @@ -65,6 +65,8 @@ public: void apply(const std::function& functor); + QSharedPointer clone() const; + private: static void parseImpl(const PDFObjectStorage* storage, PDFOutlineItem* parent, diff --git a/Pdf4QtViewer/pdfsidebarwidget.cpp b/Pdf4QtViewer/pdfsidebarwidget.cpp index c23617c..e06cb0d 100644 --- a/Pdf4QtViewer/pdfsidebarwidget.cpp +++ b/Pdf4QtViewer/pdfsidebarwidget.cpp @@ -23,6 +23,8 @@ #include "pdfwidgetutils.h" #include "pdftexttospeech.h" #include "pdfdbgheap.h" +#include "pdfdrawwidget.h" +#include "pdfwidgettool.h" #include "pdfdocument.h" #include "pdfitemmodels.h" @@ -53,6 +55,7 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, PDFTextToSpeech* textToSpeech, pdf::PDFCertificateStore* certificateStore, PDFViewerSettings* settings, + bool editableOutline, QWidget* parent) : QWidget(parent), ui(new Ui::PDFSidebarWidget), @@ -73,9 +76,20 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, // Outline QIcon bookmarkIcon(":/resources/bookmark.svg"); - m_outlineTreeModel = new pdf::PDFOutlineTreeItemModel(qMove(bookmarkIcon), this); + m_outlineTreeModel = new pdf::PDFOutlineTreeItemModel(qMove(bookmarkIcon), editableOutline, this); ui->bookmarksTreeView->setModel(m_outlineTreeModel); ui->bookmarksTreeView->header()->hide(); + + if (editableOutline) + { + ui->bookmarksTreeView->setDragEnabled(true); + ui->bookmarksTreeView->setAcceptDrops(true); + ui->bookmarksTreeView->setDropIndicatorShown(true); + ui->bookmarksTreeView->setDragDropMode(QAbstractItemView::InternalMove); + ui->bookmarksTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui->bookmarksTreeView, &QTreeView::customContextMenuRequested, this, &PDFSidebarWidget::onBookmarksTreeViewContextMenuRequested); + } + connect(ui->bookmarksTreeView, &QTreeView::clicked, this, &PDFSidebarWidget::onOutlineItemClicked); // Thumbnails @@ -735,6 +749,119 @@ void PDFSidebarWidget::onSignatureCustomContextMenuRequested(const QPoint& pos) } } +void PDFSidebarWidget::onBookmarksTreeViewContextMenuRequested(const QPoint& pos) +{ + QMenu contextMenu; + + QModelIndex index = ui->bookmarksTreeView->indexAt(pos); + + auto onFollow = [this, index]() + { + onOutlineItemClicked(index); + }; + + auto onInsert = [this, index]() + { + if (index.isValid()) + { + ui->bookmarksTreeView->model()->insertRow(index.row() + 1, index.parent()); + } + else + { + ui->bookmarksTreeView->model()->insertRow(ui->bookmarksTreeView->model()->rowCount()); + } + }; + + auto onDelete = [this, index]() + { + ui->bookmarksTreeView->model()->removeRow(index.row(), index.parent()); + }; + + auto onRename = [this, index]() + { + ui->bookmarksTreeView->edit(index); + }; + + QAction* followAction = contextMenu.addAction(tr("Follow"), onFollow); + followAction->setEnabled(index.isValid()); + contextMenu.addSeparator(); + + QAction* deleteAction = contextMenu.addAction(tr("Delete"), onDelete); + QAction* insertAction = contextMenu.addAction(tr("Insert"), this, onInsert); + QAction* renameAction = contextMenu.addAction(tr("Rename"), this, onRename); + + deleteAction->setEnabled(index.isValid()); + insertAction->setEnabled(true); + renameAction->setEnabled(index.isValid()); + + contextMenu.addSeparator(); + + const pdf::PDFOutlineItem* outlineItem = m_outlineTreeModel->getOutlineItem(index); + const bool isFontBold = outlineItem && outlineItem->isFontBold(); + const bool isFontItalics = outlineItem && outlineItem->isFontItalics(); + + auto onFontBold = [this, index, isFontBold]() + { + m_outlineTreeModel->setFontBold(index, !isFontBold); + }; + + auto onFontItalic = [this, index, isFontItalics]() + { + m_outlineTreeModel->setFontItalics(index, !isFontItalics); + }; + + QAction* fontBoldAction = contextMenu.addAction(tr("Font Bold"), onFontBold); + QAction* fontItalicAction = contextMenu.addAction(tr("Font Italic"), onFontItalic); + fontBoldAction->setCheckable(true); + fontItalicAction->setCheckable(true); + fontBoldAction->setChecked(isFontBold); + fontItalicAction->setChecked(isFontItalics); + fontBoldAction->setEnabled(index.isValid()); + fontItalicAction->setEnabled(index.isValid()); + + QMenu* submenu = new QMenu(tr("Set Target"), &contextMenu); + QAction* targetAction = contextMenu.addMenu(submenu); + targetAction->setEnabled(index.isValid()); + + auto createOnSetTarget = [this, index](pdf::DestinationType destinationType) + { + auto onSetTarget = [this, index, destinationType]() + { + pdf::PDFToolManager* toolManager = m_proxy->getWidget()->getToolManager(); + + auto pickRectangle = [this, index, destinationType](pdf::PDFInteger pageIndex, QRectF rect) + { + pdf::PDFDestination destination; + destination.setDestinationType(destinationType); + destination.setPageIndex(pageIndex); + destination.setPageReference(m_document->getCatalog()->getPage(pageIndex)->getPageReference()); + destination.setLeft(rect.left()); + destination.setRight(rect.right()); + destination.setTop(rect.bottom()); + destination.setBottom(rect.top()); + destination.setZoom(m_proxy->getZoom()); + m_outlineTreeModel->setDestination(index, destination); + }; + + toolManager->pickRectangle(pickRectangle); + }; + + return onSetTarget; + }; + + submenu->addAction(tr("Named Destination")); + submenu->addAction(tr("Fit Page"), createOnSetTarget(pdf::DestinationType::Fit)); + submenu->addAction(tr("Fit Page Horizontally"), createOnSetTarget(pdf::DestinationType::FitH)); + submenu->addAction(tr("Fit Page Vertically"), createOnSetTarget(pdf::DestinationType::FitV)); + submenu->addAction(tr("Fit Rectangle"), createOnSetTarget(pdf::DestinationType::FitR)); + submenu->addAction(tr("Fit Bounding Box"), createOnSetTarget(pdf::DestinationType::FitB)); + submenu->addAction(tr("Fit Bounding Box Horizontally"), createOnSetTarget(pdf::DestinationType::FitBH)); + submenu->addAction(tr("Fit Bounding Box Vertically"), createOnSetTarget(pdf::DestinationType::FitBV)); + submenu->addAction(tr("XYZ"), createOnSetTarget(pdf::DestinationType::XYZ)); + + contextMenu.exec(ui->bookmarksTreeView->mapToGlobal(pos)); +} + void PDFSidebarWidget::paintEvent(QPaintEvent* event) { Q_UNUSED(event); diff --git a/Pdf4QtViewer/pdfsidebarwidget.h b/Pdf4QtViewer/pdfsidebarwidget.h index 0aa30f3..4ecac46 100644 --- a/Pdf4QtViewer/pdfsidebarwidget.h +++ b/Pdf4QtViewer/pdfsidebarwidget.h @@ -62,6 +62,7 @@ public: PDFTextToSpeech* textToSpeech, pdf::PDFCertificateStore* certificateStore, PDFViewerSettings* settings, + bool editableOutline, QWidget* parent); virtual ~PDFSidebarWidget() override; @@ -111,6 +112,7 @@ private: void onAttachmentCustomContextMenuRequested(const QPoint& pos); void onThumbnailClicked(const QModelIndex& index); void onSignatureCustomContextMenuRequested(const QPoint &pos); + void onBookmarksTreeViewContextMenuRequested(const QPoint &pos); struct PageInfo { diff --git a/Pdf4QtViewer/pdfviewermainwindow.cpp b/Pdf4QtViewer/pdfviewermainwindow.cpp index 88c8d8c..889bb34 100644 --- a/Pdf4QtViewer/pdfviewermainwindow.cpp +++ b/Pdf4QtViewer/pdfviewermainwindow.cpp @@ -253,7 +253,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : setCentralWidget(m_programController->getPdfWidget()); setFocusProxy(m_programController->getPdfWidget()); - m_sidebarWidget = new PDFSidebarWidget(m_programController->getPdfWidget()->getDrawWidgetProxy(), m_programController->getTextToSpeech(), m_programController->getCertificateStore(), m_programController->getSettings(), this); + m_sidebarWidget = new PDFSidebarWidget(m_programController->getPdfWidget()->getDrawWidgetProxy(), m_programController->getTextToSpeech(), m_programController->getCertificateStore(), m_programController->getSettings(), true, this); m_sidebarDockWidget = new QDockWidget(tr("Sidebar"), this); m_sidebarDockWidget->setObjectName("SidebarDockWidget"); m_sidebarDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); diff --git a/Pdf4QtViewer/pdfviewermainwindowlite.cpp b/Pdf4QtViewer/pdfviewermainwindowlite.cpp index bd7dd0b..392d79f 100644 --- a/Pdf4QtViewer/pdfviewermainwindowlite.cpp +++ b/Pdf4QtViewer/pdfviewermainwindowlite.cpp @@ -188,7 +188,7 @@ PDFViewerMainWindowLite::PDFViewerMainWindowLite(QWidget* parent) : setCentralWidget(m_programController->getPdfWidget()); setFocusProxy(m_programController->getPdfWidget()); - m_sidebarWidget = new PDFSidebarWidget(m_programController->getPdfWidget()->getDrawWidgetProxy(), m_programController->getTextToSpeech(), m_programController->getCertificateStore(), m_programController->getSettings(), this); + m_sidebarWidget = new PDFSidebarWidget(m_programController->getPdfWidget()->getDrawWidgetProxy(), m_programController->getTextToSpeech(), m_programController->getCertificateStore(), m_programController->getSettings(), false, this); m_sidebarDockWidget = new QDockWidget(tr("Sidebar"), this); m_sidebarDockWidget->setObjectName("SidebarDockWidget"); m_sidebarDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); From bf53e7419b33fbf54bf2dace64fd444a3edd6f83 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 7 Oct 2023 15:40:45 +0200 Subject: [PATCH 2/4] Issue #76: Drag and drop --- Pdf4QtLib/sources/pdfitemmodels.cpp | 44 ++++++++++++++++++++--------- Pdf4QtLib/sources/pdfitemmodels.h | 1 + Pdf4QtLib/sources/pdfoutline.cpp | 2 +- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/Pdf4QtLib/sources/pdfitemmodels.cpp b/Pdf4QtLib/sources/pdfitemmodels.cpp index f205277..855ee30 100644 --- a/Pdf4QtLib/sources/pdfitemmodels.cpp +++ b/Pdf4QtLib/sources/pdfitemmodels.cpp @@ -574,8 +574,11 @@ QMimeData* PDFOutlineTreeItemModel::mimeData(const QModelIndexList& indexes) con QByteArray ba; { + QModelIndex index = indexes.front(); + const PDFOutlineTreeItem* item = static_cast(index.internalPointer()); + m_dragDropItem = item->getOutlineItem()->clone(); QDataStream stream(&ba, QDataStream::WriteOnly); - stream << indexes.front().internalId(); + stream << reinterpret_cast(m_dragDropItem.data()); } mimeData->setData(mimeTypes().front(), ba); @@ -605,9 +608,29 @@ bool PDFOutlineTreeItemModel::dropMimeData(const QMimeData* data, Qt::DropAction quintptr pointer = 0; stream >> pointer; - PDFOutlineTreeItem* item = reinterpret_cast(pointer); - QModelIndex sourceIndex = createIndex(item->getRow(), column, item); - return moveRow(sourceIndex.parent(), sourceIndex.row(), parent, row); + PDFOutlineItem* item = reinterpret_cast(pointer); + if (item == m_dragDropItem.data()) + { + if (row == -1) + { + row = rowCount(parent); + } + + if (column == -1) + { + column = 0; + } + + if (insertRow(row, parent)) + { + QModelIndex targetIndex = this->index(row, column, parent); + PDFOutlineTreeItem* targetTreeItem = static_cast(targetIndex.internalPointer()); + *targetTreeItem->getOutlineItem() = *item; + return true; + } + } + + return false; } bool PDFOutlineTreeItemModel::insertRows(int row, int count, const QModelIndex& parent) @@ -695,25 +718,20 @@ bool PDFOutlineTreeItemModel::moveRows(const QModelIndex& sourceParent, int sour destinationChild = 0; } - // Signalizace začátku přesunu řádků - if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1, destinationParent, destinationChild)) - { - return false; - } - + beginRemoveRows(sourceParent, sourceRow, sourceRow + count - 1); QList nodesToMove; for (int i = 0; i < count; ++i) { nodesToMove.append(static_cast(sourceNode->takeChild(sourceRow))); } + endRemoveRows(); + beginInsertRows(destinationParent, destinationChild, destinationChild + count - 1); for (PDFOutlineTreeItem* node : nodesToMove) { destNode->insertCreatedChild(destinationChild++, node); } - - // Signalizace konce přesunu řádků - endMoveRows(); + endInsertRows(); return true; } diff --git a/Pdf4QtLib/sources/pdfitemmodels.h b/Pdf4QtLib/sources/pdfitemmodels.h index 05344e4..b4b0fce 100644 --- a/Pdf4QtLib/sources/pdfitemmodels.h +++ b/Pdf4QtLib/sources/pdfitemmodels.h @@ -214,6 +214,7 @@ public: private: QIcon m_icon; bool m_editable; + mutable QSharedPointer m_dragDropItem; }; class PDF4QTLIBSHARED_EXPORT PDFSelectableOutlineTreeItemModel : public PDFOutlineTreeItemModel diff --git a/Pdf4QtLib/sources/pdfoutline.cpp b/Pdf4QtLib/sources/pdfoutline.cpp index fb54187..1496ca6 100644 --- a/Pdf4QtLib/sources/pdfoutline.cpp +++ b/Pdf4QtLib/sources/pdfoutline.cpp @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Jakub Melka +// Copyright (C) 2019-2023 Jakub Melka // // This file is part of PDF4QT. // From 73024fbe9991e29340bf0a7985c123f78a5b70e2 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 7 Oct 2023 16:21:26 +0200 Subject: [PATCH 3/4] Issue #76: Modify document, when bookmarks are changed --- Pdf4QtLib/sources/pdfitemmodels.cpp | 6 ++++++ Pdf4QtLib/sources/pdfitemmodels.h | 2 ++ Pdf4QtViewer/pdfprogramcontroller.h | 2 +- Pdf4QtViewer/pdfsidebarwidget.cpp | 18 ++++++++++++++++++ Pdf4QtViewer/pdfsidebarwidget.h | 2 ++ Pdf4QtViewer/pdfviewermainwindow.cpp | 1 + Pdf4QtViewer/pdfviewermainwindowlite.cpp | 1 + 7 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Pdf4QtLib/sources/pdfitemmodels.cpp b/Pdf4QtLib/sources/pdfitemmodels.cpp index 855ee30..1a8ffaa 100644 --- a/Pdf4QtLib/sources/pdfitemmodels.cpp +++ b/Pdf4QtLib/sources/pdfitemmodels.cpp @@ -522,6 +522,12 @@ void PDFOutlineTreeItemModel::setDestination(const QModelIndex& index, const PDF } } +const PDFOutlineItem* PDFOutlineTreeItemModel::getRootOutlineItem() const +{ + PDFOutlineTreeItem* item = static_cast(m_rootItem.get()); + return item->getOutlineItem(); +} + bool PDFOutlineTreeItemModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!m_editable || !index.isValid() || role != Qt::EditRole) diff --git a/Pdf4QtLib/sources/pdfitemmodels.h b/Pdf4QtLib/sources/pdfitemmodels.h index b4b0fce..f26fa25 100644 --- a/Pdf4QtLib/sources/pdfitemmodels.h +++ b/Pdf4QtLib/sources/pdfitemmodels.h @@ -211,6 +211,8 @@ public: void setFontItalics(const QModelIndex& index, bool value); void setDestination(const QModelIndex& index, const PDFDestination& destination); + const PDFOutlineItem* getRootOutlineItem() const; + private: QIcon m_icon; bool m_editable; diff --git a/Pdf4QtViewer/pdfprogramcontroller.h b/Pdf4QtViewer/pdfprogramcontroller.h index 7e79e6c..ba051be 100644 --- a/Pdf4QtViewer/pdfprogramcontroller.h +++ b/Pdf4QtViewer/pdfprogramcontroller.h @@ -285,6 +285,7 @@ public: void performSaveAs(); void onActionTriggered(const pdf::PDFAction* action); + void onDocumentModified(pdf::PDFModifiedDocument document); void updateActionsAvailability(); bool getIsBusy() const; @@ -356,7 +357,6 @@ private: void onDrawSpaceChanged(); void onPageLayoutChanged(); void onDocumentReadingFinished(); - void onDocumentModified(pdf::PDFModifiedDocument document); void onDocumentUndoRedo(pdf::PDFModifiedDocument document); void onQueryPasswordRequest(QString* password, bool* ok); void onPageRenderingErrorsChanged(pdf::PDFInteger pageIndex, int errorsCount); diff --git a/Pdf4QtViewer/pdfsidebarwidget.cpp b/Pdf4QtViewer/pdfsidebarwidget.cpp index e06cb0d..4d2f0ba 100644 --- a/Pdf4QtViewer/pdfsidebarwidget.cpp +++ b/Pdf4QtViewer/pdfsidebarwidget.cpp @@ -31,6 +31,7 @@ #include "pdfexception.h" #include "pdfsignaturehandler.h" #include "pdfdrawspacecontroller.h" +#include "pdfdocumentbuilder.h" #include #include @@ -88,6 +89,10 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, ui->bookmarksTreeView->setDragDropMode(QAbstractItemView::InternalMove); ui->bookmarksTreeView->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->bookmarksTreeView, &QTreeView::customContextMenuRequested, this, &PDFSidebarWidget::onBookmarksTreeViewContextMenuRequested); + connect(m_outlineTreeModel, &pdf::PDFOutlineTreeItemModel::dataChanged, this, &PDFSidebarWidget::onOutlineItemsChanged); + connect(m_outlineTreeModel, &pdf::PDFOutlineTreeItemModel::rowsInserted, this, &PDFSidebarWidget::onOutlineItemsChanged); + connect(m_outlineTreeModel, &pdf::PDFOutlineTreeItemModel::rowsRemoved, this, &PDFSidebarWidget::onOutlineItemsChanged); + connect(m_outlineTreeModel, &pdf::PDFOutlineTreeItemModel::rowsMoved, this, &PDFSidebarWidget::onOutlineItemsChanged); } connect(ui->bookmarksTreeView, &QTreeView::clicked, this, &PDFSidebarWidget::onOutlineItemClicked); @@ -862,6 +867,19 @@ void PDFSidebarWidget::onBookmarksTreeViewContextMenuRequested(const QPoint& pos contextMenu.exec(ui->bookmarksTreeView->mapToGlobal(pos)); } +void PDFSidebarWidget::onOutlineItemsChanged() +{ + if (m_document) + { + pdf::PDFDocumentBuilder builder(m_document); + builder.setOutline(m_outlineTreeModel->getRootOutlineItem()); + + pdf::PDFDocumentPointer pointer(new pdf::PDFDocument(builder.build())); + pdf::PDFModifiedDocument document(qMove(pointer), m_optionalContentActivity, pdf::PDFModifiedDocument::None); + Q_EMIT documentModified(qMove(document)); + } +} + void PDFSidebarWidget::paintEvent(QPaintEvent* event) { Q_UNUSED(event); diff --git a/Pdf4QtViewer/pdfsidebarwidget.h b/Pdf4QtViewer/pdfsidebarwidget.h index 4ecac46..1a215e3 100644 --- a/Pdf4QtViewer/pdfsidebarwidget.h +++ b/Pdf4QtViewer/pdfsidebarwidget.h @@ -100,6 +100,7 @@ public: signals: void actionTriggered(const pdf::PDFAction* action); + void documentModified(pdf::PDFModifiedDocument document); private: void updateGUI(Page preferredPage); @@ -113,6 +114,7 @@ private: void onThumbnailClicked(const QModelIndex& index); void onSignatureCustomContextMenuRequested(const QPoint &pos); void onBookmarksTreeViewContextMenuRequested(const QPoint &pos); + void onOutlineItemsChanged(); struct PageInfo { diff --git a/Pdf4QtViewer/pdfviewermainwindow.cpp b/Pdf4QtViewer/pdfviewermainwindow.cpp index 889bb34..5a242db 100644 --- a/Pdf4QtViewer/pdfviewermainwindow.cpp +++ b/Pdf4QtViewer/pdfviewermainwindow.cpp @@ -261,6 +261,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : addDockWidget(Qt::LeftDockWidgetArea, m_sidebarDockWidget); m_sidebarDockWidget->hide(); connect(m_sidebarWidget, &PDFSidebarWidget::actionTriggered, m_programController, &PDFProgramController::onActionTriggered); + connect(m_sidebarWidget, &PDFSidebarWidget::documentModified, m_programController, &PDFProgramController::onDocumentModified); m_advancedFindWidget = new PDFAdvancedFindWidget(m_programController->getPdfWidget()->getDrawWidgetProxy(), this); m_advancedFindDockWidget = new QDockWidget(tr("Advanced find"), this); diff --git a/Pdf4QtViewer/pdfviewermainwindowlite.cpp b/Pdf4QtViewer/pdfviewermainwindowlite.cpp index 392d79f..23efb9a 100644 --- a/Pdf4QtViewer/pdfviewermainwindowlite.cpp +++ b/Pdf4QtViewer/pdfviewermainwindowlite.cpp @@ -196,6 +196,7 @@ PDFViewerMainWindowLite::PDFViewerMainWindowLite(QWidget* parent) : addDockWidget(Qt::LeftDockWidgetArea, m_sidebarDockWidget); m_sidebarDockWidget->hide(); connect(m_sidebarWidget, &PDFSidebarWidget::actionTriggered, m_programController, &PDFProgramController::onActionTriggered); + connect(m_sidebarWidget, &PDFSidebarWidget::documentModified, m_programController, &PDFProgramController::onDocumentModified); ui->menuView->addSeparator(); ui->menuView->addAction(m_sidebarDockWidget->toggleViewAction()); From 93496339d4e37a8c52861273ea4279d1e4c5588d Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sat, 7 Oct 2023 16:43:12 +0200 Subject: [PATCH 4/4] Issue #76: Finished --- Pdf4QtViewer/pdfsidebarwidget.cpp | 60 ++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/Pdf4QtViewer/pdfsidebarwidget.cpp b/Pdf4QtViewer/pdfsidebarwidget.cpp index 4d2f0ba..2cc8063 100644 --- a/Pdf4QtViewer/pdfsidebarwidget.cpp +++ b/Pdf4QtViewer/pdfsidebarwidget.cpp @@ -32,6 +32,7 @@ #include "pdfsignaturehandler.h" #include "pdfdrawspacecontroller.h" #include "pdfdocumentbuilder.h" +#include "pdfwidgetutils.h" #include #include @@ -40,6 +41,10 @@ #include #include #include +#include +#include +#include +#include namespace pdfviewer { @@ -854,7 +859,60 @@ void PDFSidebarWidget::onBookmarksTreeViewContextMenuRequested(const QPoint& pos return onSetTarget; }; - submenu->addAction(tr("Named Destination")); + auto onNamedDestinationTriggered = [this, index]() + { + class SelectNamedDestinationDialog : public QDialog + { + public: + explicit SelectNamedDestinationDialog(const QStringList& items, QWidget* parent) + : QDialog(parent) + { + setWindowTitle(tr("Select Named Destination")); + setMinimumWidth(pdf::PDFWidgetUtils::scaleDPI_x(this, 150)); + + QVBoxLayout* layout = new QVBoxLayout(this); + + m_comboBox = new QComboBox(this); + m_comboBox->addItems(items); + m_comboBox->setEditable(false); + layout->addWidget(m_comboBox); + + QHBoxLayout* buttonsLayout = new QHBoxLayout(); + QPushButton* okButton = new QPushButton(tr("OK"), this); + QPushButton* cancelButton = new QPushButton(tr("Cancel"), this); + + buttonsLayout->addWidget(okButton); + buttonsLayout->addWidget(cancelButton); + + layout->addLayout(buttonsLayout); + + connect(okButton, &QPushButton::clicked, this, &QDialog::accept); + connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); + } + + QString selectedText() const + { + return m_comboBox->currentText(); + } + + private: + QComboBox* m_comboBox; + }; + + QStringList items; + for (const auto& namedDestination : m_document->getCatalog()->getNamedDestinations()) + { + items << QString::fromLatin1(namedDestination.first); + } + + SelectNamedDestinationDialog dialog(items, m_proxy->getWidget()); + if (dialog.exec() == QDialog::Accepted) + { + m_outlineTreeModel->setDestination(index, pdf::PDFDestination::createNamed(dialog.selectedText().toLatin1())); + } + }; + + submenu->addAction(tr("Named Destination"), onNamedDestinationTriggered); submenu->addAction(tr("Fit Page"), createOnSetTarget(pdf::DestinationType::Fit)); submenu->addAction(tr("Fit Page Horizontally"), createOnSetTarget(pdf::DestinationType::FitH)); submenu->addAction(tr("Fit Page Vertically"), createOnSetTarget(pdf::DestinationType::FitV));