From 89ea815696b7d3fbb68df95218513e81e9133c98 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Sun, 24 Dec 2023 19:03:28 +0100 Subject: [PATCH 1/3] Issue #128: Create list of markup annotations --- Pdf4QtLibCore/sources/pdfannotation.cpp | 127 ++++++++++ Pdf4QtLibCore/sources/pdfannotation.h | 12 +- Pdf4QtViewer/pdf4qtviewer.qrc | 4 + Pdf4QtViewer/pdfsidebarwidget.cpp | 238 ++++++++++++++---- Pdf4QtViewer/pdfsidebarwidget.h | 13 +- Pdf4QtViewer/pdfsidebarwidget.ui | 111 +++++++- Pdf4QtViewer/resources/bubble.svg | 58 +++++ Pdf4QtViewer/resources/page.svg | 58 +++++ .../resources/sidebar-annotations.svg | 58 +++++ Pdf4QtViewer/resources/user.svg | 58 +++++ 10 files changed, 685 insertions(+), 52 deletions(-) create mode 100644 Pdf4QtViewer/resources/bubble.svg create mode 100644 Pdf4QtViewer/resources/page.svg create mode 100644 Pdf4QtViewer/resources/sidebar-annotations.svg create mode 100644 Pdf4QtViewer/resources/user.svg diff --git a/Pdf4QtLibCore/sources/pdfannotation.cpp b/Pdf4QtLibCore/sources/pdfannotation.cpp index 7decac0..70dda15 100644 --- a/Pdf4QtLibCore/sources/pdfannotation.cpp +++ b/Pdf4QtLibCore/sources/pdfannotation.cpp @@ -189,6 +189,114 @@ PDFAnnotation::PDFAnnotation() : } +QString PDFAnnotation::getGUICaption() const +{ + QStringList texts; + + switch (getType()) + { + case pdf::AnnotationType::Text: + texts << PDFTranslationContext::tr("Text"); + break; + case pdf::AnnotationType::Link: + texts << PDFTranslationContext::tr("Line"); + break; + case pdf::AnnotationType::FreeText: + texts << PDFTranslationContext::tr("Free Text"); + break; + case pdf::AnnotationType::Line: + texts << PDFTranslationContext::tr("Line"); + break; + case pdf::AnnotationType::Square: + texts << PDFTranslationContext::tr("Square"); + break; + case pdf::AnnotationType::Circle: + texts << PDFTranslationContext::tr("Circle"); + break; + case pdf::AnnotationType::Polygon: + texts << PDFTranslationContext::tr("Polygon"); + break; + case pdf::AnnotationType::Polyline: + texts << PDFTranslationContext::tr("Polyline"); + break; + case pdf::AnnotationType::Highlight: + texts << PDFTranslationContext::tr("Highlight"); + break; + case pdf::AnnotationType::Underline: + texts << PDFTranslationContext::tr("Underline"); + break; + case pdf::AnnotationType::Squiggly: + texts << PDFTranslationContext::tr("Squiggly"); + break; + case pdf::AnnotationType::StrikeOut: + texts << PDFTranslationContext::tr("Strike Out"); + break; + case pdf::AnnotationType::Stamp: + texts << PDFTranslationContext::tr("Stamp"); + break; + case pdf::AnnotationType::Caret: + texts << PDFTranslationContext::tr("Caret"); + break; + case pdf::AnnotationType::Ink: + texts << PDFTranslationContext::tr("Ink"); + break; + case pdf::AnnotationType::Popup: + texts << PDFTranslationContext::tr("Popup"); + break; + case pdf::AnnotationType::FileAttachment: + texts << PDFTranslationContext::tr("File Attachment"); + break; + case pdf::AnnotationType::Sound: + texts << PDFTranslationContext::tr("Sound"); + break; + case pdf::AnnotationType::Movie: + texts << PDFTranslationContext::tr("Movie"); + break; + case pdf::AnnotationType::Widget: + texts << PDFTranslationContext::tr("Widget"); + break; + case pdf::AnnotationType::Screen: + texts << PDFTranslationContext::tr("Screen"); + break; + case pdf::AnnotationType::PrinterMark: + texts << PDFTranslationContext::tr("Printer Mark"); + break; + case pdf::AnnotationType::TrapNet: + texts << PDFTranslationContext::tr("Trap Net"); + break; + case pdf::AnnotationType::Watermark: + texts << PDFTranslationContext::tr("Watermark"); + break; + case pdf::AnnotationType::Redact: + texts << PDFTranslationContext::tr("Redaction"); + break; + case pdf::AnnotationType::Projection: + texts << PDFTranslationContext::tr("Projection"); + break; + case pdf::AnnotationType::_3D: + texts << PDFTranslationContext::tr("3D"); + break; + case pdf::AnnotationType::RichMedia: + texts << PDFTranslationContext::tr("Rich Media"); + break; + + default: + break; + } + + if (isReplyTo()) + { + texts << PDFTranslationContext::tr("Reply"); + } + + if (!getContents().isEmpty()) + { + texts << getContents(); + } + + return texts.join(" | "); +} + void PDFAnnotation::draw(AnnotationDrawParameters& parameters) const { Q_UNUSED(parameters); @@ -1634,6 +1742,25 @@ bool PDFAnnotationManager::hasAnyPageAnnotation(const std::vector& p return std::any_of(pageIndices.cbegin(), pageIndices.cend(), std::bind(&PDFAnnotationManager::hasAnnotation, this, std::placeholders::_1)); } +bool PDFAnnotationManager::hasAnyPageAnnotation() const +{ + if (!m_document) + { + return false; + } + + size_t pageCount = m_document->getCatalog()->getPageCount(); + for (size_t i = 0; i < pageCount; ++i) + { + if (hasAnnotation(i)) + { + return true; + } + } + + return false; +} + PDFFormManager* PDFAnnotationManager::getFormManager() const { return m_formManager; diff --git a/Pdf4QtLibCore/sources/pdfannotation.h b/Pdf4QtLibCore/sources/pdfannotation.h index d57c3c8..8e58568 100644 --- a/Pdf4QtLibCore/sources/pdfannotation.h +++ b/Pdf4QtLibCore/sources/pdfannotation.h @@ -509,6 +509,7 @@ public: virtual PDFMarkupAnnotation* asMarkupAnnotation() { return nullptr; } virtual const PDFMarkupAnnotation* asMarkupAnnotation() const { return nullptr; } virtual bool isReplyTo() const { return false; } + virtual QString getGUICaption() const; /// Draws the annotation using parameters. Annotation is drawn onto painter, /// but actual graphics can be drawn outside of annotation's rectangle. @@ -1524,10 +1525,10 @@ public: /// \param page Page /// \param[in,out] annotationRectangle Input/output annotation rectangle QTransform prepareTransformations(const QTransform& pagePointToDevicePointMatrix, - QPaintDevice* device, - const PDFAnnotation::Flags annotationFlags, - const PDFPage* page, - QRectF& annotationRectangle) const; + QPaintDevice* device, + const PDFAnnotation::Flags annotationFlags, + const PDFPage* page, + QRectF& annotationRectangle) const; /// Returns current appearance stream for given page annotation /// \param pageAnnotation Page annotation @@ -1549,6 +1550,9 @@ public: /// Returns true, if any page in the given indices has annotation bool hasAnyPageAnnotation(const std::vector& pageIndices) const; + /// Returns true, if any page in the document has annotation + bool hasAnyPageAnnotation() const; + protected: void drawWidgetAnnotationHighlight(QRectF annotationRectangle, const PDFAnnotation* annotation, diff --git a/Pdf4QtViewer/pdf4qtviewer.qrc b/Pdf4QtViewer/pdf4qtviewer.qrc index 37379d8..6e997ba 100644 --- a/Pdf4QtViewer/pdf4qtviewer.qrc +++ b/Pdf4QtViewer/pdf4qtviewer.qrc @@ -95,6 +95,7 @@ resources/book.svg resources/wallet.svg resources/web.svg + resources/user.svg resources/create-bitonal-document.svg resources/sanitize-document.svg resources/sidebar-attachment.svg @@ -103,10 +104,13 @@ resources/sidebar-speech.svg resources/sidebar-thumbnails.svg resources/sidebar-visibility.svg + resources/sidebar-annotations.svg resources/outline.svg resources/sidebar-favourites.svg resources/bookmark.svg resources/bookmark-next.svg resources/bookmark-previous.svg + resources/page.svg + resources/bubble.svg diff --git a/Pdf4QtViewer/pdfsidebarwidget.cpp b/Pdf4QtViewer/pdfsidebarwidget.cpp index e71f03d..e3f181b 100644 --- a/Pdf4QtViewer/pdfsidebarwidget.cpp +++ b/Pdf4QtViewer/pdfsidebarwidget.cpp @@ -33,6 +33,7 @@ #include "pdfdocumentbuilder.h" #include "pdfwidgetutils.h" #include "pdfbookmarkui.h" +#include "pdfwidgetannotation.h" #include #include @@ -45,20 +46,14 @@ #include #include #include +#include +#include #include "pdfdbgheap.h" namespace pdfviewer { -constexpr const char* STYLESHEET = - "QPushButton { background-color: #404040; color: #FFFFFF; }" - "QPushButton:disabled { background-color: #404040; color: #000000; }" - "QPushButton:checked { background-color: #808080; color: #FFFFFF; }" - "QWidget#thumbnailsToolbarWidget { background-color: #F0F0F0 }" - "QWidget#speechPage { background-color: #F0F0F0 }" - "QWidget#PDFSidebarWidget { background-color: #404040; background: green;}"; - PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, PDFTextToSpeech* textToSpeech, pdf::PDFCertificateStore* certificateStore, @@ -74,22 +69,32 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, m_bookmarkManager(bookmarkManager), m_settings(settings), m_outlineTreeModel(nullptr), + m_outlineSortProxyTreeModel(nullptr), m_thumbnailsModel(nullptr), m_optionalContentTreeModel(nullptr), m_bookmarkItemModel(nullptr), + m_notesTreeModel(nullptr), + m_notesSortProxyTreeModel(nullptr), m_document(nullptr), m_optionalContentActivity(nullptr), m_attachmentsTreeModel(nullptr) { ui->setupUi(this); - setStyleSheet(STYLESHEET); - // Outline QIcon outlineIcon(":/resources/outline.svg"); m_outlineTreeModel = new pdf::PDFOutlineTreeItemModel(qMove(outlineIcon), editableOutline, this); - ui->outlineTreeView->setModel(m_outlineTreeModel); + m_outlineSortProxyTreeModel = new QSortFilterProxyModel(this); + m_outlineSortProxyTreeModel->setFilterKeyColumn(0); + m_outlineSortProxyTreeModel->setFilterRole(Qt::DisplayRole); + m_outlineSortProxyTreeModel->setAutoAcceptChildRows(false); + m_outlineSortProxyTreeModel->setRecursiveFilteringEnabled(true); + m_outlineSortProxyTreeModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_outlineSortProxyTreeModel->setSourceModel(m_outlineTreeModel); + ui->outlineTreeView->setModel(m_outlineSortProxyTreeModel); ui->outlineTreeView->header()->hide(); + connect(ui->outlineSearchLineEdit, &QLineEdit::editingFinished, this, &PDFSidebarWidget::onOutlineSearchText); + connect(ui->outlineSearchLineEdit, &QLineEdit::textChanged, this, &PDFSidebarWidget::onOutlineSearchText); if (editableOutline) { @@ -139,6 +144,21 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, connect(ui->bookmarksView->selectionModel(), &QItemSelectionModel::currentChanged, this, &PDFSidebarWidget::onBookmarsCurrentIndexChanged); connect(ui->bookmarksView, &QListView::clicked, this, &PDFSidebarWidget::onBookmarkClicked); + // Notes + m_notesTreeModel = new QStandardItemModel(this); + m_notesSortProxyTreeModel = new QSortFilterProxyModel(this); + m_notesSortProxyTreeModel->setFilterKeyColumn(0); + m_notesSortProxyTreeModel->setFilterRole(Qt::DisplayRole); + m_notesSortProxyTreeModel->setAutoAcceptChildRows(false); + m_notesSortProxyTreeModel->setRecursiveFilteringEnabled(true); + m_notesSortProxyTreeModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_notesSortProxyTreeModel->setSourceModel(m_notesTreeModel); + ui->notesTreeView->setModel(m_notesSortProxyTreeModel); + ui->notesTreeView->header()->hide(); + connect(ui->notesSearchLineEdit, &QLineEdit::editingFinished, this, &PDFSidebarWidget::onNotesSearchText); + connect(ui->notesSearchLineEdit, &QLineEdit::textChanged, this, &PDFSidebarWidget::onNotesSearchText); + connect(ui->notesTreeView, &QTreeView::clicked, this, &PDFSidebarWidget::onNotesItemClicked); + m_pageInfo[Invalid] = { nullptr, ui->emptyPage }; m_pageInfo[OptionalContent] = { ui->optionalContentButton, ui->optionalContentPage }; m_pageInfo[Outline] = { ui->outlineButton, ui->outlinePage }; @@ -147,6 +167,7 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, m_pageInfo[Speech] = { ui->speechButton, ui->speechPage }; m_pageInfo[Signatures] = { ui->signaturesButton, ui->signaturesPage }; m_pageInfo[Bookmarks] = { ui->bookmarksButton, ui->bookmarksPage }; + m_pageInfo[Notes] = { ui->notesButton, ui->notesPage }; for (const auto& pageInfo : m_pageInfo) { @@ -251,6 +272,11 @@ void PDFSidebarWidget::setDocument(const pdf::PDFModifiedDocument& document, con updateGUI(preferred); updateButtons(); updateSignatures(signatures); + + if (document.hasReset() || document.hasFlag(pdf::PDFModifiedDocument::Annotation)) + { + updateNotes(); + } } bool PDFSidebarWidget::isEmpty() const @@ -294,6 +320,9 @@ bool PDFSidebarWidget::isEmpty(Page page) const case Signatures: return m_signatures.empty(); + case Notes: + return !m_document || !m_proxy->getAnnotationManager()->hasAnyPageAnnotation(); + default: Q_ASSERT(false); break; @@ -663,6 +692,120 @@ void PDFSidebarWidget::updateSignatures(const std::vectorsignatureTreeWidget->setUpdatesEnabled(true); } +void PDFSidebarWidget::updateNotes() +{ + const bool updatesEnabled = ui->notesTreeView->updatesEnabled(); + ui->notesTreeView->setUpdatesEnabled(false); + + m_notesTreeModel->clear(); + m_markupAnnotations.clear(); + + if (m_document) + { + QIcon bubbleIcon(":/resources/bubble.svg"); + QIcon pageIcon(":/resources/page.svg"); + QIcon userIcon(":/resources/user.svg"); + + pdf::PDFAnnotationManager annotationManager(m_proxy->getFontCache(), + m_proxy->getCMSManager(), + m_optionalContentActivity, + pdf::PDFMeshQualitySettings(), + m_proxy->getFeatures(), + pdf::PDFAnnotationManager::Target::View, + nullptr); + annotationManager.setDocument(pdf::PDFModifiedDocument(const_cast(m_document), m_optionalContentActivity)); + + pdf::PDFInteger pageCount = m_document->getCatalog()->getPageCount(); + for (pdf::PDFInteger pageIndex = 0; pageIndex < pageCount; ++pageIndex) + { + const pdf::PDFAnnotationManager::PageAnnotations& pageAnnotations = annotationManager.getPageAnnotations(pageIndex); + + if (pageAnnotations.isEmpty()) + { + continue; + } + + std::map> annotations; + + for (const pdf::PDFAnnotationManager::PageAnnotation& pageAnnotation : pageAnnotations.annotations) + { + if (!pageAnnotation.annotation || !pageAnnotation.annotation->asMarkupAnnotation()) + { + continue; + } + + const pdf::PDFMarkupAnnotation* markupAnnotation = pageAnnotation.annotation->asMarkupAnnotation(); + + QString user = markupAnnotation->getWindowTitle(); + + if (user.isEmpty()) + { + user = tr("User"); + } + + annotations[user].push_back(markupAnnotation); + } + + if (!annotations.empty()) + { + QStandardItem* pageItem = new QStandardItem(pageIcon, tr("Page %1").arg(pageIndex + 1)); + pageItem->setFlags(Qt::ItemIsEnabled); + + for (const auto& annotationItem : annotations) + { + QStandardItem* userItem = new QStandardItem(userIcon, annotationItem.first); + userItem->setFlags(Qt::ItemIsEnabled); + pageItem->appendRow(userItem); + + for (const pdf::PDFMarkupAnnotation* markupAnnotation : annotationItem.second) + { + QStandardItem* annotationTreeItem = new QStandardItem(bubbleIcon, markupAnnotation->getGUICaption()); + annotationTreeItem->setData(int(m_markupAnnotations.size()), Qt::UserRole); + annotationTreeItem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + userItem->appendRow(annotationTreeItem); + m_markupAnnotations.push_back(std::make_pair(markupAnnotation->getSelfReference(), pageIndex)); + } + } + + m_notesTreeModel->appendRow(pageItem); + } + } + } + + ui->notesTreeView->setUpdatesEnabled(updatesEnabled); + ui->notesTreeView->expandAll(); +} + +void PDFSidebarWidget::onOutlineSearchText() +{ + QString text = ui->outlineSearchLineEdit->text(); + const bool isWildcard = text.contains(QChar('*')) || text.contains(QChar('?')); + + if (isWildcard) + { + m_outlineSortProxyTreeModel->setFilterWildcard(text); + } + else + { + m_outlineSortProxyTreeModel->setFilterFixedString(text); + } +} + +void PDFSidebarWidget::onNotesSearchText() +{ + QString text = ui->notesSearchLineEdit->text(); + const bool isWildcard = text.contains(QChar('*')) || text.contains(QChar('?')); + + if (isWildcard) + { + m_notesSortProxyTreeModel->setFilterWildcard(text); + } + else + { + m_notesSortProxyTreeModel->setFilterFixedString(text); + } +} + void PDFSidebarWidget::onPageButtonClicked() { QObject* pushButton = sender(); @@ -680,7 +823,8 @@ void PDFSidebarWidget::onPageButtonClicked() void PDFSidebarWidget::onOutlineItemClicked(const QModelIndex& index) { - if (const pdf::PDFAction* action = m_outlineTreeModel->getAction(index)) + QModelIndex sourceIndex = m_outlineSortProxyTreeModel->mapToSource(index); + if (const pdf::PDFAction* action = m_outlineTreeModel->getAction(sourceIndex)) { Q_EMIT actionTriggered(action); } @@ -785,18 +929,18 @@ void PDFSidebarWidget::onOutlineTreeViewContextMenuRequested(const QPoint& pos) { QMenu contextMenu; - QModelIndex index = ui->outlineTreeView->indexAt(pos); + QModelIndex proxyIndex = ui->outlineTreeView->indexAt(pos); - auto onFollow = [this, index]() + auto onFollow = [this, proxyIndex]() { - onOutlineItemClicked(index); + onOutlineItemClicked(proxyIndex); }; - auto onInsert = [this, index]() + auto onInsert = [this, proxyIndex]() { - if (index.isValid()) + if (proxyIndex.isValid()) { - ui->outlineTreeView->model()->insertRow(index.row() + 1, index.parent()); + ui->outlineTreeView->model()->insertRow(proxyIndex.row() + 1, proxyIndex.parent()); } else { @@ -804,42 +948,43 @@ void PDFSidebarWidget::onOutlineTreeViewContextMenuRequested(const QPoint& pos) } }; - auto onDelete = [this, index]() + auto onDelete = [this, proxyIndex]() { - ui->outlineTreeView->model()->removeRow(index.row(), index.parent()); + ui->outlineTreeView->model()->removeRow(proxyIndex.row(), proxyIndex.parent()); }; - auto onRename = [this, index]() + auto onRename = [this, proxyIndex]() { - ui->outlineTreeView->edit(index); + ui->outlineTreeView->edit(proxyIndex); }; QAction* followAction = contextMenu.addAction(tr("Follow"), onFollow); - followAction->setEnabled(index.isValid()); + followAction->setEnabled(proxyIndex.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()); + deleteAction->setEnabled(proxyIndex.isValid()); insertAction->setEnabled(true); - renameAction->setEnabled(index.isValid()); + renameAction->setEnabled(proxyIndex.isValid()); contextMenu.addSeparator(); - const pdf::PDFOutlineItem* outlineItem = m_outlineTreeModel->getOutlineItem(index); + QModelIndex sourceIndex = m_outlineSortProxyTreeModel->mapToSource(proxyIndex); + const pdf::PDFOutlineItem* outlineItem = m_outlineTreeModel->getOutlineItem(sourceIndex); const bool isFontBold = outlineItem && outlineItem->isFontBold(); const bool isFontItalics = outlineItem && outlineItem->isFontItalics(); - auto onFontBold = [this, index, isFontBold]() + auto onFontBold = [this, sourceIndex, isFontBold]() { - m_outlineTreeModel->setFontBold(index, !isFontBold); + m_outlineTreeModel->setFontBold(sourceIndex, !isFontBold); }; - auto onFontItalic = [this, index, isFontItalics]() + auto onFontItalic = [this, sourceIndex, isFontItalics]() { - m_outlineTreeModel->setFontItalics(index, !isFontItalics); + m_outlineTreeModel->setFontItalics(sourceIndex, !isFontItalics); }; QAction* fontBoldAction = contextMenu.addAction(tr("Font Bold"), onFontBold); @@ -848,20 +993,20 @@ void PDFSidebarWidget::onOutlineTreeViewContextMenuRequested(const QPoint& pos) fontItalicAction->setCheckable(true); fontBoldAction->setChecked(isFontBold); fontItalicAction->setChecked(isFontItalics); - fontBoldAction->setEnabled(index.isValid()); - fontItalicAction->setEnabled(index.isValid()); + fontBoldAction->setEnabled(sourceIndex.isValid()); + fontItalicAction->setEnabled(sourceIndex.isValid()); QMenu* submenu = new QMenu(tr("Set Target"), &contextMenu); QAction* targetAction = contextMenu.addMenu(submenu); - targetAction->setEnabled(index.isValid()); + targetAction->setEnabled(sourceIndex.isValid()); - auto createOnSetTarget = [this, index](pdf::DestinationType destinationType) + auto createOnSetTarget = [this, sourceIndex](pdf::DestinationType destinationType) { - auto onSetTarget = [this, index, destinationType]() + auto onSetTarget = [this, sourceIndex, destinationType]() { pdf::PDFToolManager* toolManager = m_proxy->getWidget()->getToolManager(); - auto pickRectangle = [this, index, destinationType](pdf::PDFInteger pageIndex, QRectF rect) + auto pickRectangle = [this, sourceIndex, destinationType](pdf::PDFInteger pageIndex, QRectF rect) { pdf::PDFDestination destination; destination.setDestinationType(destinationType); @@ -872,7 +1017,7 @@ void PDFSidebarWidget::onOutlineTreeViewContextMenuRequested(const QPoint& pos) destination.setTop(rect.bottom()); destination.setBottom(rect.top()); destination.setZoom(m_proxy->getZoom()); - m_outlineTreeModel->setDestination(index, destination); + m_outlineTreeModel->setDestination(sourceIndex, destination); }; toolManager->pickRectangle(pickRectangle); @@ -881,7 +1026,7 @@ void PDFSidebarWidget::onOutlineTreeViewContextMenuRequested(const QPoint& pos) return onSetTarget; }; - auto onNamedDestinationTriggered = [this, index]() + auto onNamedDestinationTriggered = [this, sourceIndex]() { class SelectNamedDestinationDialog : public QDialog { @@ -930,7 +1075,7 @@ void PDFSidebarWidget::onOutlineTreeViewContextMenuRequested(const QPoint& pos) SelectNamedDestinationDialog dialog(items, m_proxy->getWidget()); if (dialog.exec() == QDialog::Accepted) { - m_outlineTreeModel->setDestination(index, pdf::PDFDestination::createNamed(dialog.selectedText().toLatin1())); + m_outlineTreeModel->setDestination(sourceIndex, pdf::PDFDestination::createNamed(dialog.selectedText().toLatin1())); } }; @@ -1000,11 +1145,18 @@ void PDFSidebarWidget::onBookmarkClicked(const QModelIndex& index) } } -void PDFSidebarWidget::paintEvent(QPaintEvent* event) +void PDFSidebarWidget::onNotesItemClicked(const QModelIndex& index) { - Q_UNUSED(event); - QPainter painter(this); - painter.fillRect(rect(), QColor(64, 64, 64)); + QVariant userData = index.data(Qt::UserRole); + if (userData.isValid()) + { + int i = userData.toInt(); + if (i >= 0 && i < m_markupAnnotations.size()) + { + pdf::PDFInteger pageIndex = m_markupAnnotations[i].second; + m_proxy->goToPage(pageIndex); + } + } } } // namespace pdfviewer diff --git a/Pdf4QtViewer/pdfsidebarwidget.h b/Pdf4QtViewer/pdfsidebarwidget.h index 069ab5e..756dbf7 100644 --- a/Pdf4QtViewer/pdfsidebarwidget.h +++ b/Pdf4QtViewer/pdfsidebarwidget.h @@ -27,6 +27,8 @@ class QPushButton; class QToolButton; class QWidget; +class QStandardItemModel; +class QSortFilterProxyModel; namespace Ui { @@ -69,8 +71,6 @@ public: QWidget* parent); virtual ~PDFSidebarWidget() override; - virtual void paintEvent(QPaintEvent* event) override; - enum Page { Invalid, @@ -82,6 +82,7 @@ public: Speech, Signatures, Bookmarks, + Notes, _END }; @@ -110,7 +111,10 @@ private: void updateGUI(Page preferredPage); void updateButtons(); void updateSignatures(const std::vector& signatures); + void updateNotes(); + void onOutlineSearchText(); + void onNotesSearchText(); void onPageButtonClicked(); void onOutlineItemClicked(const QModelIndex& index); void onThumbnailsSizeChanged(int size); @@ -122,6 +126,7 @@ private: void onBookmarkActivated(int index, PDFBookmarkManager::Bookmark bookmark); void onBookmarsCurrentIndexChanged(const QModelIndex& current, const QModelIndex& previous); void onBookmarkClicked(const QModelIndex& index); + void onNotesItemClicked(const QModelIndex& index); struct PageInfo { @@ -136,15 +141,19 @@ private: PDFBookmarkManager* m_bookmarkManager; PDFViewerSettings* m_settings; pdf::PDFOutlineTreeItemModel* m_outlineTreeModel; + QSortFilterProxyModel* m_outlineSortProxyTreeModel; pdf::PDFThumbnailsItemModel* m_thumbnailsModel; pdf::PDFOptionalContentTreeItemModel* m_optionalContentTreeModel; PDFBookmarkItemModel* m_bookmarkItemModel; + QStandardItemModel* m_notesTreeModel; + QSortFilterProxyModel* m_notesSortProxyTreeModel; const pdf::PDFDocument* m_document; pdf::PDFOptionalContentActivity* m_optionalContentActivity; pdf::PDFAttachmentsTreeItemModel* m_attachmentsTreeModel; std::map m_pageInfo; std::vector m_signatures; std::vector m_certificateInfos; + std::vector> m_markupAnnotations; bool m_bookmarkChangeInProgress = false; }; diff --git a/Pdf4QtViewer/pdfsidebarwidget.ui b/Pdf4QtViewer/pdfsidebarwidget.ui index 04ea4ef..081fa90 100644 --- a/Pdf4QtViewer/pdfsidebarwidget.ui +++ b/Pdf4QtViewer/pdfsidebarwidget.ui @@ -6,8 +6,8 @@ 0 0 - 388 - 681 + 404 + 778 @@ -254,6 +254,35 @@ + + + + + 96 + 0 + + + + Notes + + + + :/resources/sidebar-annotations.svg:/resources/sidebar-annotations.svg + + + + 64 + 64 + + + + Ctrl+S + + + Qt::ToolButtonTextUnderIcon + + + @@ -272,7 +301,7 @@ - 5 + 6 @@ -292,6 +321,32 @@ 0 + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + Search text or wildcard... + + + true + + + + + @@ -438,6 +493,56 @@ + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + Search text or wildcard... + + + true + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/bubble.svg b/Pdf4QtViewer/resources/bubble.svg new file mode 100644 index 0000000..addc999 --- /dev/null +++ b/Pdf4QtViewer/resources/bubble.svg @@ -0,0 +1,58 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Pdf4QtViewer/resources/page.svg b/Pdf4QtViewer/resources/page.svg new file mode 100644 index 0000000..e990e77 --- /dev/null +++ b/Pdf4QtViewer/resources/page.svg @@ -0,0 +1,58 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Pdf4QtViewer/resources/sidebar-annotations.svg b/Pdf4QtViewer/resources/sidebar-annotations.svg new file mode 100644 index 0000000..c6d5cd7 --- /dev/null +++ b/Pdf4QtViewer/resources/sidebar-annotations.svg @@ -0,0 +1,58 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Pdf4QtViewer/resources/user.svg b/Pdf4QtViewer/resources/user.svg new file mode 100644 index 0000000..6607cc9 --- /dev/null +++ b/Pdf4QtViewer/resources/user.svg @@ -0,0 +1,58 @@ + + + + + + + + image/svg+xml + + + + + + + + + From 5ba4cef2f711bde0ff9f06b700d59351e46ed792 Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Mon, 25 Dec 2023 11:39:20 +0100 Subject: [PATCH 2/3] Issue #128: Create list of markup annotations --- .../sources/pdfwidgetannotation.cpp | 41 ++++++++++++------- .../sources/pdfwidgetannotation.h | 4 ++ Pdf4QtViewer/pdfsidebarwidget.cpp | 27 ++++++++++++ Pdf4QtViewer/pdfsidebarwidget.h | 5 ++- Pdf4QtViewer/pdfsidebarwidget.ui | 9 +++- 5 files changed, 69 insertions(+), 17 deletions(-) diff --git a/Pdf4QtLibWidgets/sources/pdfwidgetannotation.cpp b/Pdf4QtLibWidgets/sources/pdfwidgetannotation.cpp index 3a65e58..4d797ef 100644 --- a/Pdf4QtLibWidgets/sources/pdfwidgetannotation.cpp +++ b/Pdf4QtLibWidgets/sources/pdfwidgetannotation.cpp @@ -126,21 +126,34 @@ void PDFWidgetAnnotationManager::mousePressEvent(QWidget* widget, QMouseEvent* e } } - if (m_editableAnnotation.isValid()) - { - QMenu menu(tr("Annotation"), pdfWidget); - QAction* showPopupAction = menu.addAction(tr("Show Popup Window")); - QAction* copyAction = menu.addAction(tr("Copy to Multiple Pages")); - QAction* editAction = menu.addAction(tr("Edit")); - QAction* deleteAction = menu.addAction(tr("Delete")); - connect(showPopupAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onShowPopupAnnotation); - connect(copyAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onCopyAnnotation); - connect(editAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onEditAnnotation); - connect(deleteAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onDeleteAnnotation); + QPoint menuPosition = pdfWidget->mapToGlobal(event->pos()); + showAnnotationMenu(m_editableAnnotation, m_editableAnnotationPage, menuPosition); + } +} - m_editableAnnotationGlobalPosition = pdfWidget->mapToGlobal(event->pos()); - menu.exec(m_editableAnnotationGlobalPosition); - } +void PDFWidgetAnnotationManager::showAnnotationMenu(PDFObjectReference annotationReference, + PDFObjectReference pageReference, + QPoint globalMenuPosition) +{ + m_editableAnnotation = annotationReference; + m_editableAnnotationPage = pageReference; + + if (m_editableAnnotation.isValid()) + { + PDFWidget* pdfWidget = m_proxy->getWidget(); + + QMenu menu(tr("Annotation"), pdfWidget); + QAction* showPopupAction = menu.addAction(tr("Show Popup Window")); + QAction* copyAction = menu.addAction(tr("Copy to Multiple Pages")); + QAction* editAction = menu.addAction(tr("Edit")); + QAction* deleteAction = menu.addAction(tr("Delete")); + connect(showPopupAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onShowPopupAnnotation); + connect(copyAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onCopyAnnotation); + connect(editAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onEditAnnotation); + connect(deleteAction, &QAction::triggered, this, &PDFWidgetAnnotationManager::onDeleteAnnotation); + + m_editableAnnotationGlobalPosition = globalMenuPosition; + menu.exec(m_editableAnnotationGlobalPosition); } } diff --git a/Pdf4QtLibWidgets/sources/pdfwidgetannotation.h b/Pdf4QtLibWidgets/sources/pdfwidgetannotation.h index aa0ef98..4ad1865 100644 --- a/Pdf4QtLibWidgets/sources/pdfwidgetannotation.h +++ b/Pdf4QtLibWidgets/sources/pdfwidgetannotation.h @@ -65,6 +65,10 @@ public: virtual int getInputPriority() const override { return AnnotationPriority; } + void showAnnotationMenu(pdf::PDFObjectReference annotationReference, + pdf::PDFObjectReference pageReference, + QPoint globalMenuPosition); + signals: void actionTriggered(const PDFAction* action); void documentModified(PDFModifiedDocument document); diff --git a/Pdf4QtViewer/pdfsidebarwidget.cpp b/Pdf4QtViewer/pdfsidebarwidget.cpp index e3f181b..522e7d0 100644 --- a/Pdf4QtViewer/pdfsidebarwidget.cpp +++ b/Pdf4QtViewer/pdfsidebarwidget.cpp @@ -158,6 +158,7 @@ PDFSidebarWidget::PDFSidebarWidget(pdf::PDFDrawWidgetProxy* proxy, connect(ui->notesSearchLineEdit, &QLineEdit::editingFinished, this, &PDFSidebarWidget::onNotesSearchText); connect(ui->notesSearchLineEdit, &QLineEdit::textChanged, this, &PDFSidebarWidget::onNotesSearchText); connect(ui->notesTreeView, &QTreeView::clicked, this, &PDFSidebarWidget::onNotesItemClicked); + connect(ui->notesTreeView, &QTreeView::customContextMenuRequested, this, &PDFSidebarWidget::onNotesTreeViewContextMenuRequested); m_pageInfo[Invalid] = { nullptr, ui->emptyPage }; m_pageInfo[OptionalContent] = { ui->optionalContentButton, ui->optionalContentPage }; @@ -1092,6 +1093,32 @@ void PDFSidebarWidget::onOutlineTreeViewContextMenuRequested(const QPoint& pos) contextMenu.exec(ui->outlineTreeView->mapToGlobal(pos)); } +void PDFSidebarWidget::onNotesTreeViewContextMenuRequested(const QPoint& pos) +{ + QModelIndex index = ui->notesTreeView->indexAt(pos); + + if (index.isValid()) + { + QVariant userData = index.data(Qt::UserRole); + if (userData.isValid()) + { + const int annotationIndex = userData.toInt(); + + if (annotationIndex >= 0 && annotationIndex < m_markupAnnotations.size()) + { + const auto& annotationItem = m_markupAnnotations[annotationIndex]; + QPoint globalPos = ui->notesTreeView->viewport()->mapToGlobal(pos); + + pdf::PDFObjectReference annotationReference = annotationItem.first; + pdf::PDFObjectReference pageReference = m_document->getCatalog()->getPage(annotationItem.second)->getPageReference(); + + m_proxy->goToPage(annotationItem.second); + m_proxy->getAnnotationManager()->showAnnotationMenu(annotationReference, pageReference, globalPos); + } + } + } +} + void PDFSidebarWidget::onOutlineItemsChanged() { if (m_document) diff --git a/Pdf4QtViewer/pdfsidebarwidget.h b/Pdf4QtViewer/pdfsidebarwidget.h index 756dbf7..778ee12 100644 --- a/Pdf4QtViewer/pdfsidebarwidget.h +++ b/Pdf4QtViewer/pdfsidebarwidget.h @@ -120,8 +120,9 @@ private: void onThumbnailsSizeChanged(int size); void onAttachmentCustomContextMenuRequested(const QPoint& pos); void onThumbnailClicked(const QModelIndex& index); - void onSignatureCustomContextMenuRequested(const QPoint &pos); - void onOutlineTreeViewContextMenuRequested(const QPoint &pos); + void onSignatureCustomContextMenuRequested(const QPoint& pos); + void onOutlineTreeViewContextMenuRequested(const QPoint& pos); + void onNotesTreeViewContextMenuRequested(const QPoint& pos); void onOutlineItemsChanged(); void onBookmarkActivated(int index, PDFBookmarkManager::Bookmark bookmark); void onBookmarsCurrentIndexChanged(const QModelIndex& current, const QModelIndex& previous); diff --git a/Pdf4QtViewer/pdfsidebarwidget.ui b/Pdf4QtViewer/pdfsidebarwidget.ui index 081fa90..2765718 100644 --- a/Pdf4QtViewer/pdfsidebarwidget.ui +++ b/Pdf4QtViewer/pdfsidebarwidget.ui @@ -539,7 +539,14 @@ - + + + Qt::CustomContextMenu + + + true + + From 0f47f1a6d03696aa28f2300bb5d4485982828b4f Mon Sep 17 00:00:00 2001 From: Jakub Melka Date: Mon, 25 Dec 2023 11:40:55 +0100 Subject: [PATCH 3/3] Issue #128: Update of RELEASES.txt --- RELEASES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.txt b/RELEASES.txt index afb0207..08eb77f 100644 --- a/RELEASES.txt +++ b/RELEASES.txt @@ -1,5 +1,6 @@ CURRENT: - Issue #129: Cannot compile with lcms 2.16 + - Issue #128: Create list of markup annotations - Issue #126: Remove include from main headers - Issue #119: Improve search bar 2 (the revenge) - Issue #118: Adding CMAKE options for minimal builds