// Copyright (C) 2019 Jakub Melka // // This file is part of PdfForQt. // // PdfForQt is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // PdfForQt is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with PDFForQt. If not, see . #include "pdfsidebarwidget.h" #include "ui_pdfsidebarwidget.h" #include "pdfdocument.h" #include "pdfitemmodels.h" #include "pdfexception.h" #include #include #include #include #include namespace pdfviewer { PDFSidebarWidget::PDFSidebarWidget(QWidget* parent) : QWidget(parent), ui(new Ui::PDFSidebarWidget), m_outlineTreeModel(nullptr), m_optionalContentTreeModel(nullptr), m_document(nullptr), m_optionalContentActivity(nullptr), m_attachmentsTreeModel(nullptr) { ui->setupUi(this); // Outline QIcon bookmarkIcon(":/resources/bookmark.svg"); m_outlineTreeModel = new pdf::PDFOutlineTreeItemModel(qMove(bookmarkIcon), this); ui->bookmarksTreeView->setModel(m_outlineTreeModel); ui->bookmarksTreeView->header()->hide(); connect(ui->bookmarksTreeView, &QTreeView::clicked, this, &PDFSidebarWidget::onOutlineItemClicked); // Optional content ui->optionalContentTreeView->header()->hide(); m_optionalContentTreeModel = new pdf::PDFOptionalContentTreeItemModel(this); ui->optionalContentTreeView->setModel(m_optionalContentTreeModel); // Attachments ui->attachmentsTreeView->header()->hide(); m_attachmentsTreeModel = new pdf::PDFAttachmentsTreeItemModel(this); ui->attachmentsTreeView->setModel(m_attachmentsTreeModel); ui->attachmentsTreeView->setSelectionMode(QAbstractItemView::SingleSelection); ui->attachmentsTreeView->setSelectionBehavior(QAbstractItemView::SelectItems); ui->attachmentsTreeView->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui->attachmentsTreeView, &QTreeView::customContextMenuRequested, this, &PDFSidebarWidget::onAttachmentCustomContextMenuRequested); m_pageInfo[Invalid] = { nullptr, ui->emptyPage }; m_pageInfo[OptionalContent] = { ui->optionalContentButton, ui->optionalContentPage }; m_pageInfo[Bookmarks] = { ui->bookmarksButton, ui->bookmarksPage }; m_pageInfo[Thumbnails] = { ui->thumbnailsButton, ui->thumbnailsPage }; m_pageInfo[Attachments] = { ui->attachmentsButton, ui->attachmentsPage }; setAutoFillBackground(true); selectPage(Invalid); updateButtons(); } PDFSidebarWidget::~PDFSidebarWidget() { delete ui; } void PDFSidebarWidget::setDocument(const pdf::PDFDocument* document, pdf::PDFOptionalContentActivity* optionalContentActivity) { m_document = document; m_optionalContentActivity = optionalContentActivity; // Update outline m_outlineTreeModel->setDocument(document); // Update optional content m_optionalContentTreeModel->setDocument(document); m_optionalContentTreeModel->setActivity(m_optionalContentActivity); ui->optionalContentTreeView->expandAll(); // Update attachments m_attachmentsTreeModel->setDocument(document); ui->attachmentsTreeView->expandAll(); ui->attachmentsTreeView->resizeColumnToContents(0); Page preferred = Invalid; if (m_document) { const pdf::PageMode pageMode = m_document->getCatalog()->getPageMode(); switch (pageMode) { case pdf::PageMode::UseOutlines: preferred = Bookmarks; break; case pdf::PageMode::UseThumbnails: preferred = Thumbnails; break; case pdf::PageMode::UseOptionalContent: preferred = OptionalContent; break; case pdf::PageMode::UseAttachments: preferred = Attachments; break; default: break; } } // Update GUI updateGUI(preferred); updateButtons(); } bool PDFSidebarWidget::isEmpty() const { for (int i = _BEGIN; i < _END; ++i) { if (!isEmpty(static_cast(i))) { return false; } } return true; } bool PDFSidebarWidget::isEmpty(Page page) const { switch (page) { case Invalid: return true; case Bookmarks: return m_outlineTreeModel->isEmpty(); case Thumbnails: return true; case OptionalContent: return m_optionalContentTreeModel->isEmpty(); case Attachments: return m_attachmentsTreeModel->isEmpty(); default: Q_ASSERT(false); break; } return true; } void PDFSidebarWidget::selectPage(Page page) { // Switch state of the buttons and select the page for (const auto& pageInfo : m_pageInfo) { if (pageInfo.second.button) { pageInfo.second.button->setChecked(pageInfo.first == page); } if (pageInfo.first == page) { ui->stackedWidget->setCurrentWidget(pageInfo.second.page); } } } std::vector PDFSidebarWidget::getValidPages() const { std::vector result; result.reserve(_END - _BEGIN + 1); for (int i = _BEGIN; i < _END; ++i) { if (!isEmpty(static_cast(i))) { result.push_back(static_cast(i)); } } return result; } void PDFSidebarWidget::updateGUI(Page preferredPage) { if (preferredPage != Invalid && !isEmpty(preferredPage)) { selectPage(preferredPage); } else { // Select first nonempty page std::vector validPages = getValidPages(); if (!validPages.empty()) { selectPage(validPages.front()); } else { selectPage(Invalid); } } } void PDFSidebarWidget::updateButtons() { for (const auto& pageInfo : m_pageInfo) { if (pageInfo.second.button) { pageInfo.second.button->setEnabled(!isEmpty(pageInfo.first)); } } } void PDFSidebarWidget::onOutlineItemClicked(const QModelIndex& index) { if (const pdf::PDFAction* action = m_outlineTreeModel->getAction(index)) { emit actionTriggered(action); } } void PDFSidebarWidget::onAttachmentCustomContextMenuRequested(const QPoint& pos) { if (const pdf::PDFFileSpecification* fileSpecification = m_attachmentsTreeModel->getFileSpecification(ui->attachmentsTreeView->indexAt(pos))) { QMenu menu(this); QAction* action = new QAction(QApplication::style()->standardIcon(QStyle::SP_DialogSaveButton, nullptr, ui->attachmentsTreeView), tr("Save to File..."), &menu); auto onSaveTriggered = [this, fileSpecification]() { const pdf::PDFEmbeddedFile* platformFile = fileSpecification->getPlatformFile(); if (platformFile && platformFile->isValid()) { QString defaultFileName = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + QDir::separator() + fileSpecification->getPlatformFileName(); QString saveFileName = QFileDialog::getSaveFileName(this, tr("Save attachment"), defaultFileName); if (!saveFileName.isEmpty()) { try { QByteArray data = m_document->getDecodedStream(platformFile->getStream()); QFile file(saveFileName); if (file.open(QFile::WriteOnly | QFile::Truncate)) { file.write(data); file.close(); } else { QMessageBox::critical(this, tr("Error"), tr("Failed to save attachment to file. %1").arg(file.errorString())); } } catch (pdf::PDFException e) { QMessageBox::critical(this, tr("Error"), tr("Failed to save attachment to file. %1").arg(e.getMessage())); } } } else { QMessageBox::critical(this, tr("Error"), tr("Failed to save attachment to file. Attachment is corrupted.")); } }; connect(action, &QAction::triggered, this, onSaveTriggered); menu.addAction(action); menu.exec(ui->attachmentsTreeView->viewport()->mapToGlobal(pos)); } } } // namespace pdfviewer