PDF draw widget (first part)

This commit is contained in:
Jakub Melka 2019-01-27 17:55:22 +01:00
parent 7631265ba4
commit d4087eae1a
10 changed files with 896 additions and 49 deletions

View File

@ -15,7 +15,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
QT -= gui
QT += gui widgets
TARGET = PdfForQtLib
TEMPLATE = lib
@ -46,7 +46,8 @@ SOURCES += \
sources/pdfcatalog.cpp \
sources/pdfpage.cpp \
sources/pdfstreamfilters.cpp \
sources/pdfdrawspacecontroller.cpp
sources/pdfdrawspacecontroller.cpp \
sources/pdfdrawwidget.cpp
HEADERS += \
sources/pdfobject.h \
@ -63,7 +64,8 @@ HEADERS += \
sources/pdfnumbertreeloader.h \
sources/pdfpage.h \
sources/pdfstreamfilters.h \
sources/pdfdrawspacecontroller.h
sources/pdfdrawspacecontroller.h \
sources/pdfdrawwidget.h
unix {
target.path = /usr/lib

View File

@ -17,6 +17,9 @@
#include "pdfdrawspacecontroller.h"
#include "pdfdrawwidget.h"
#include <QPainter>
namespace pdf
{
@ -31,6 +34,54 @@ PDFDrawSpaceController::PDFDrawSpaceController(QObject* parent) :
}
void PDFDrawSpaceController::setDocument(const PDFDocument* document)
{
if (document != m_document)
{
m_document = document;
recalculate();
}
}
void PDFDrawSpaceController::setPageLayout(PageLayout pageLayout)
{
if (m_pageLayoutMode != pageLayout)
{
m_pageLayoutMode = pageLayout;
recalculate();
}
}
QRectF PDFDrawSpaceController::getBlockBoundingRectangle(size_t blockIndex) const
{
if (blockIndex < m_blockItems.size())
{
return m_blockItems[blockIndex].blockRectMM;
}
return QRectF();
}
PDFDrawSpaceController::LayoutItems PDFDrawSpaceController::getLayoutItems(size_t blockIndex) const
{
LayoutItems result;
auto comparator = [](const LayoutItem& l, const LayoutItem& r)
{
return l.blockIndex < r.blockIndex;
};
Q_ASSERT(std::is_sorted(m_layoutItems.cbegin(), m_layoutItems.cend(), comparator));
LayoutItem templateItem;
templateItem.blockIndex = blockIndex;
auto range = std::equal_range(m_layoutItems.cbegin(), m_layoutItems.cend(), templateItem, comparator);
result.reserve(std::distance(range.first, range.second));
std::copy(range.first, range.second, std::back_inserter(result));
return result;
}
void PDFDrawSpaceController::recalculate()
{
if (!m_document)
@ -55,6 +106,9 @@ void PDFDrawSpaceController::recalculate()
pageRotation[layoutItem.pageIndex] = layoutItem.pageRotation;
}
// Clear the old draw space
clear(false);
static constexpr size_t INVALID_PAGE_INDEX = std::numeric_limits<size_t>::max();
// Places the pages on the left/right sides. Pages can be nullptr, but not both of them.
@ -269,4 +323,231 @@ void PDFDrawSpaceController::clear(bool emitSignal)
}
}
PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
QObject(parent),
m_updateDisabled(false),
m_currentBlock(INVALID_BLOCK_INDEX),
m_pixelPerMM(PDF_DEFAULT_DPMM),
m_zoom(1.0),
m_pixelToDeviceSpaceUnit(0.0),
m_deviceSpaceUnitToPixel(0.0),
m_verticalOffset(0),
m_horizontalOffset(0),
m_controller(nullptr),
m_widget(nullptr),
m_horizontalScrollbar(nullptr),
m_verticalScrollbar(nullptr)
{
m_controller = new PDFDrawSpaceController(this);
connect(m_controller, &PDFDrawSpaceController::drawSpaceChanged, this, &PDFDrawWidgetProxy::update);
}
void PDFDrawWidgetProxy::setDocument(const PDFDocument* document)
{
m_controller->setDocument(document);
}
void PDFDrawWidgetProxy::init(PDFWidget* widget)
{
m_widget = widget->getDrawWidget();
m_horizontalScrollbar = widget->getHorizontalScrollbar();
m_verticalScrollbar = widget->getVerticalScrollbar();
// We must update the draw space - widget has been set
update();
}
void PDFDrawWidgetProxy::update()
{
if (m_updateDisabled)
{
return;
}
PDFBoolGuard guard(m_updateDisabled);
Q_ASSERT(m_widget);
Q_ASSERT(m_horizontalScrollbar);
Q_ASSERT(m_verticalScrollbar);
// First, we must calculate pixel per mm ratio to obtain DPMM (device pixel per mm),
// we also assume, that zoom is correctly set.
m_pixelPerMM = static_cast<PDFReal>(m_widget->width()) / static_cast<PDFReal>(m_widget->widthMM());
Q_ASSERT(m_zoom > 0.0);
Q_ASSERT(m_pixelPerMM > 0.0);
m_deviceSpaceUnitToPixel = m_pixelPerMM * m_zoom;
m_pixelToDeviceSpaceUnit = 1.0 / m_deviceSpaceUnitToPixel;
m_layout.clear();
// Switch to the first block, if we haven't selected any, otherwise fix active
// block item (select first block available).
if (m_controller->getBlockCount() > 0)
{
if (m_currentBlock == INVALID_BLOCK_INDEX)
{
m_currentBlock = 0;
}
else
{
m_currentBlock = qBound<PDFInteger>(0, m_currentBlock, m_controller->getBlockCount());
}
}
else
{
m_currentBlock = INVALID_BLOCK_INDEX;
}
// Then, create pixel size layout of the pages using the draw space controller
QRectF rectangle = m_controller->getBlockBoundingRectangle(m_currentBlock);
if (rectangle.isValid())
{
// We must have a valid block
PDFDrawSpaceController::LayoutItems items = m_controller->getLayoutItems(m_currentBlock);
m_layout.items.reserve(items.size());
for (const PDFDrawSpaceController::LayoutItem& item : items)
{
m_layout.items.emplace_back(item.pageIndex, item.pageRotation, fromDeviceSpace(item.pageRectMM).toRect());
}
m_layout.blockRect = fromDeviceSpace(rectangle).toRect();
}
QSize blockSize = m_layout.blockRect.size();
QSize widgetSize = m_widget->size();
// Horizontal scrollbar
const int horizontalDifference = blockSize.width() - widgetSize.width();
if (horizontalDifference > 0)
{
m_horizontalScrollbar->setVisible(true);
m_horizontalScrollbar->setMinimum(0);
m_horizontalScrollbar->setMaximum(horizontalDifference);
m_horizontalOffset = qBound<PDFInteger>(0, m_horizontalOffset, horizontalDifference);
m_horizontalScrollbar->setValue(m_horizontalOffset);
}
else
{
// We do not need the horizontal scrollbar, because block can be draw onto widget entirely.
// We set the offset to the half of available empty space.
m_horizontalScrollbar->setVisible(false);
m_horizontalOffset = -horizontalDifference / 2;
}
// Vertical scrollbar - has two meanings, in block mode, it switches between blocks,
// in continuous mode, it controls the vertical offset.
if (isBlockMode())
{
size_t blockCount = m_controller->getBlockCount();
if (blockCount > 0)
{
Q_ASSERT(m_currentBlock != INVALID_BLOCK_INDEX);
m_verticalScrollbar->setVisible(blockCount > 1);
m_verticalScrollbar->setMinimum(0);
m_verticalScrollbar->setMaximum(static_cast<int>(blockCount - 1));
m_verticalScrollbar->setValue(static_cast<int>(m_currentBlock));
m_verticalScrollbar->setSingleStep(1);
m_verticalScrollbar->setPageStep(1);
}
else
{
Q_ASSERT(m_currentBlock == INVALID_BLOCK_INDEX);
m_verticalScrollbar->setVisible(false);
}
// We must fix case, when we can display everything on the widget (we have
// enough space). Then we will center the page on the widget.
const int verticalDifference = blockSize.height() - widgetSize.height();
if (verticalDifference < 0)
{
m_verticalOffset = -verticalDifference / 2;
}
}
else
{
const int verticalDifference = blockSize.height() - widgetSize.height();
if (verticalDifference > 0)
{
m_verticalScrollbar->setVisible(true);
m_verticalScrollbar->setMinimum(0);
m_verticalScrollbar->setMaximum(verticalDifference);
// We must also calculate single step/page step. Because pages can have different size,
// we use first page to compute page step.
if (!m_layout.items.empty())
{
const LayoutItem& item = m_layout.items.front();
const int pageStep = qMax(item.pageRect.height(), 1);
const int singleStep = qMax(pageStep / 10, 1);
m_verticalScrollbar->setPageStep(pageStep);
m_verticalScrollbar->setSingleStep(singleStep);
}
m_verticalOffset = qBound<PDFInteger>(0, m_verticalOffset, verticalDifference);
m_verticalScrollbar->setValue(m_verticalOffset);
}
else
{
m_verticalScrollbar->setVisible(false);
m_verticalOffset = -verticalDifference / 2;
}
}
emit drawSpaceChanged();
}
void PDFDrawWidgetProxy::draw(QPainter* painter, QRect rect)
{
painter->fillRect(rect, Qt::lightGray);
// Iterate trough pages and display them on the painter device
for (const LayoutItem& item : m_layout.items)
{
// The offsets m_horizontalOffset and m_verticalOffset are offsets to the
// topleft point of the block. But block maybe doesn't start at (0, 0),
// so we must also use translation from the block beginning.
QRect placedRect = item.pageRect.translated(m_horizontalOffset - m_layout.blockRect.left(), m_verticalOffset - m_layout.blockRect.top());
if (placedRect.intersects(rect))
{
// Clear the page space by white color
painter->fillRect(placedRect, Qt::white);
}
}
}
QRectF PDFDrawWidgetProxy::fromDeviceSpace(const QRectF& rect) const
{
Q_ASSERT(rect.isValid());
return QRectF(rect.left() * m_deviceSpaceUnitToPixel,
rect.top() * m_deviceSpaceUnitToPixel,
rect.width() * m_deviceSpaceUnitToPixel,
rect.height() * m_deviceSpaceUnitToPixel);
}
bool PDFDrawWidgetProxy::isBlockMode() const
{
switch (m_controller->getPageLayout())
{
case PageLayout::OneColumn:
case PageLayout::TwoColumnLeft:
case PageLayout::TwoColumnRight:
return false;
case PageLayout::SinglePage:
case PageLayout::TwoPagesLeft:
case PageLayout::TwoPagesRight:
return true;
}
Q_ASSERT(false);
return false;
}
} // namespace pdf

View File

@ -24,8 +24,13 @@
#include <QObject>
#include <QMarginsF>
class QPainter;
class QScrollBar;
namespace pdf
{
class PDFWidget;
class PDFDrawWidget;
/// This class controls draw space - page layout. Pages are divided into blocks
/// each block can contain one or multiple pages. Units are in milimeters.
@ -37,15 +42,25 @@ class PDFDrawSpaceController : public QObject
public:
explicit PDFDrawSpaceController(QObject* parent);
signals:
void drawSpaceChanged();
/// Sets the document and recalculates the draw space. Document can be nullptr,
/// in that case, draw space is cleared.
/// \param document Document
void setDocument(const PDFDocument* document);
private:
/// Recalculates the draw space. Preserves setted page rotation.
void recalculate();
/// Sets the page layout. Page layout can be one of the PDF's page layouts.
/// \param pageLayout Page layout
void setPageLayout(PageLayout pageLayout);
/// Clears the draw space. Emits signal if desired.
void clear(bool emitSignal);
/// Returns the page layout
PageLayout getPageLayout() const { return m_pageLayoutMode; }
/// Returns the block count
size_t getBlockCount() const { return m_blockItems.size(); }
/// Return the bounding rectangle of the block. If block doesn't exist,
/// then invalid rectangle is returned (no exception is thrown).
/// \param blockIndex Index of the block
QRectF getBlockBoundingRectangle(size_t blockIndex) const;
/// Represents layouted page. This structure contains index of the block, index of the
/// page and page rectangle, in which the page is contained.
@ -63,6 +78,21 @@ private:
using LayoutItems = std::vector<LayoutItem>;
/// Returns the layout items for desired block. If block doesn't exist,
/// then empty array is returned.
/// \param blockIndex Index of the block
LayoutItems getLayoutItems(size_t blockIndex) const;
signals:
void drawSpaceChanged();
private:
/// Recalculates the draw space. Preserves setted page rotation.
void recalculate();
/// Clears the draw space. Emits signal if desired.
void clear(bool emitSignal);
/// Represents data for the single block. Contains block size in milimeters.
struct LayoutBlock
{
@ -83,6 +113,118 @@ private:
PDFReal m_horizontalSpacingMM;
};
/// This is a proxy class to draw space controller using widget. We have two spaces, pixel space
/// (on the controlled widget) and device space (device is draw space controller).
class PDFDrawWidgetProxy : public QObject
{
Q_OBJECT
public:
explicit PDFDrawWidgetProxy(QObject* parent);
/// Sets the document and updates the draw space. Document can be nullptr,
/// in that case, draw space is cleared.
/// \param document Document
void setDocument(const PDFDocument* document);
void init(PDFWidget* widget);
/// Updates the draw space area
void update();
/// Draws the actually visible pages on the painter using the rectangle.
/// Rectangle is space in the widget, which is used for painting the PDF.
/// \param painter Painter to paint the PDF pages
/// \param rect Rectangle in which the content is painted
void draw(QPainter* painter, QRect rect);
signals:
void drawSpaceChanged();
private:
struct LayoutItem
{
constexpr inline explicit LayoutItem() : pageIndex(-1), pageRotation(PageRotation::None) { }
constexpr inline explicit LayoutItem(PDFInteger pageIndex, PageRotation rotation, const QRect& pageRect) :
pageIndex(pageIndex), pageRotation(rotation), pageRect(pageRect) { }
PDFInteger pageIndex;
PageRotation pageRotation;
QRect pageRect;
};
struct Layout
{
inline void clear()
{
items.clear();
blockRect = QRect();
}
std::vector<LayoutItem> items;
QRect blockRect;
};
static constexpr size_t INVALID_BLOCK_INDEX = std::numeric_limits<size_t>::max();
// Minimal/maximal zoom is from 8% to 6400 %, according to the PDF 1.7 Reference,
// Appendix C.
static constexpr PDFReal MIN_ZOOM = 8.0 / 100.0;
static constexpr PDFReal MAX_ZOOM = 6400.0 / 100.0;
/// Converts rectangle from device space to the pixel space
QRectF fromDeviceSpace(const QRectF& rect) const;
/// Returns true, if we are in the block mode (multiple blocks with separate pages),
/// or continuous mode (single block with continuous list of separated pages).
bool isBlockMode() const;
/// Flag, disables the update
bool m_updateDisabled;
/// Current block (in the draw space controller)
size_t m_currentBlock;
/// Number of pixels (fractional) per milimeter (unit is pixel/mm) of the screen,
/// so, size of the area in milimeters can be computed as pixelCount * m_pixelPerMM [mm].
PDFReal m_pixelPerMM;
/// Zoom from widget space to device space. So, for example 2.00 corresponds to 200% zoom,
/// and each 1 cm of widget area corresponds to 0.5 cm of the device space area.
PDFReal m_zoom;
/// Converts pixel to device space units (mm) using zoom
PDFReal m_pixelToDeviceSpaceUnit;
/// Converts device space units (mm) to real pixels using zoom
PDFReal m_deviceSpaceUnitToPixel;
/// Actual vertical offset of the draw space area in the widget (so block will be drawn
/// with this vertical offset)
PDFInteger m_verticalOffset;
/// Actual horizontal offset of the draw space area in the widget (so block will be drawn
/// with this horizontal offset)
PDFInteger m_horizontalOffset;
/// Draw space controller
PDFDrawSpaceController* m_controller;
/// Controlled draw widget (proxy is for this widget)
PDFDrawWidget* m_widget;
/// Vertical scrollbar
QScrollBar* m_verticalScrollbar;
/// Horizontal scrollbar
QScrollBar* m_horizontalScrollbar;
/// Current page layout
Layout m_layout;
};
} // namespace pdf
#endif // PDFDRAWSPACECONTROLLER_H

View File

@ -0,0 +1,119 @@
// Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
#include "pdfdrawwidget.h"
#include "pdfdrawspacecontroller.h"
#include <QPainter>
#include <QGridLayout>
#include <QKeyEvent>
namespace pdf
{
PDFWidget::PDFWidget(QWidget* parent) :
QWidget(parent),
m_drawWidget(nullptr),
m_horizontalScrollBar(nullptr),
m_verticalScrollBar(nullptr),
m_proxy(nullptr)
{
m_drawWidget = new PDFDrawWidget(this, this);
m_horizontalScrollBar = new QScrollBar(Qt::Horizontal, this);
m_verticalScrollBar = new QScrollBar(Qt::Vertical, this);
QGridLayout* layout = new QGridLayout(this);
layout->setSpacing(0);
layout->addWidget(m_drawWidget, 0, 0);
layout->addWidget(m_horizontalScrollBar, 1, 0);
layout->addWidget(m_verticalScrollBar, 0, 1);
layout->setMargin(0);
setLayout(layout);
setFocusProxy(m_drawWidget);
m_proxy = new PDFDrawWidgetProxy(this);
m_proxy->init(this);
}
PDFWidget::~PDFWidget()
{
}
void PDFWidget::setDocument(const PDFDocument* document)
{
m_proxy->setDocument(document);
}
PDFDrawWidget::PDFDrawWidget(PDFWidget* widget, QWidget* parent) :
QWidget(parent),
m_widget(widget)
{
setFocusPolicy(Qt::StrongFocus);
}
PDFDrawWidget::~PDFDrawWidget()
{
}
QSize PDFDrawWidget::minimumSizeHint() const
{
return QSize(200, 200);
}
void PDFDrawWidget::paintEvent(QPaintEvent* event)
{
Q_UNUSED(event);
QPainter painter(this);
m_widget->getDrawWidgetProxy()->draw(&painter, this->rect());
}
void PDFDrawWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
m_widget->getDrawWidgetProxy()->update();
}
void PDFDrawWidget::keyPressEvent(QKeyEvent* event)
{
QScrollBar* verticalScrollbar = m_widget->getVerticalScrollbar();
// Vertical navigation
if (verticalScrollbar->isVisible())
{
if (event->matches(QKeySequence::MoveToStartOfDocument))
{
verticalScrollbar->setValue(0);
}
else if (event->matches(QKeySequence::MoveToEndOfDocument))
{
verticalScrollbar->setValue(verticalScrollbar->maximum());
}
else if (event->matches(QKeySequence::MoveToNextPage))
{
verticalScrollbar->setValue(verticalScrollbar->value() + verticalScrollbar->pageStep());
}
}
event->accept();
}
} // namespace pdf

View File

@ -0,0 +1,79 @@
// Copyright (C) 2018 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 <https://www.gnu.org/licenses/>.
#ifndef PDFDRAWWIDGET_H
#define PDFDRAWWIDGET_H
#include "pdfglobal.h"
#include <QWidget>
#include <QScrollBar>
namespace pdf
{
class PDFDocument;
class PDFDrawWidget;
class PDFDrawWidgetProxy;
class PDFFORQTLIBSHARED_EXPORT PDFWidget : public QWidget
{
Q_OBJECT
public:
explicit PDFWidget(QWidget* parent);
virtual ~PDFWidget() override;
/// Sets the document to be viewed in this widget. Document can be nullptr,
/// in that case, widget contents are cleared.
/// \param document Document
void setDocument(const PDFDocument* document);
PDFDrawWidget* getDrawWidget() const { return m_drawWidget; }
QScrollBar* getHorizontalScrollbar() const { return m_horizontalScrollBar; }
QScrollBar* getVerticalScrollbar() const { return m_verticalScrollBar; }
PDFDrawWidgetProxy* getDrawWidgetProxy() const { return m_proxy; }
private:
PDFDrawWidget* m_drawWidget;
QScrollBar* m_horizontalScrollBar;
QScrollBar* m_verticalScrollBar;
PDFDrawWidgetProxy* m_proxy;
};
class PDFFORQTLIBSHARED_EXPORT PDFDrawWidget : public QWidget
{
Q_OBJECT
public:
explicit PDFDrawWidget(PDFWidget* widget, QWidget* parent);
virtual ~PDFDrawWidget() override;
virtual QSize minimumSizeHint() const override;
protected:
virtual void paintEvent(QPaintEvent* event) override;
virtual void resizeEvent(QResizeEvent* event) override;
virtual void keyPressEvent(QKeyEvent* event) override;
private:
PDFWidget* m_widget;
};
} // namespace pdf
#endif // PDFDRAWWIDGET_H

View File

@ -105,14 +105,37 @@ struct PDFTranslationContext
};
constexpr PDFReal PDF_POINT_TO_INCH = 1.0 / 72.0;
constexpr PDFReal PDF_INT_TO_MM = 25.4;
constexpr PDFReal PDF_POINT_TO_MM = PDF_POINT_TO_INCH * PDF_INT_TO_MM;
constexpr PDFReal PDF_INCH_TO_MM = 25.4; // [mm / inch]
constexpr PDFReal PDF_POINT_TO_MM = PDF_POINT_TO_INCH * PDF_INCH_TO_MM;
/// This is default "DPI", but in milimeters, so the name is DPMM (device pixel per milimeter)
constexpr PDFReal PDF_DEFAULT_DPMM = 96.0 / PDF_INCH_TO_MM;
constexpr PDFReal convertPDFPointToMM(PDFReal point)
{
return point * PDF_POINT_TO_MM;
}
class PDFBoolGuard final
{
public:
inline explicit PDFBoolGuard(bool& value) :
m_value(value),
m_oldValue(value)
{
m_value = true;
}
inline ~PDFBoolGuard()
{
m_value = m_oldValue;
}
private:
bool& m_value;
bool m_oldValue;
};
} // namespace pdf
#endif // PDFGLOBAL_H

View File

@ -1,12 +1,41 @@
// 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 <https://www.gnu.org/licenses/>.
#include "pdfviewermainwindow.h"
#include <QApplication>
#include <QCommandLineParser>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
pdfviewer::PDFViewerMainWindow w;
w.show();
QApplication application(argc, argv);
return a.exec();
QCoreApplication::setOrganizationName("MelkaJ");
QCoreApplication::setApplicationName("PDF Viewer");
QCoreApplication::setApplicationVersion("0.1");
QCommandLineParser parser;
parser.setApplicationDescription(QCoreApplication::applicationName());
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument("file", "The PDF file to open.");
parser.process(application);
pdfviewer::PDFViewerMainWindow mainWindow;
mainWindow.show();
return application.exec();
}

View File

@ -1,23 +1,60 @@
// 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 <https://www.gnu.org/licenses/>.
#include "pdfviewermainwindow.h"
#include "ui_pdfviewermainwindow.h"
#include "pdfdocumentreader.h"
#include "pdfvisitor.h"
#include "pdfstreamfilters.h"
#include "pdfdrawwidget.h"
#include <QSettings>
#include <QFileDialog>
#include <QMessageBox>
#include <QCloseEvent>
#include <QApplication>
#include <QDesktopWidget>
#include <QStandardPaths>
namespace pdfviewer
{
PDFViewerMainWindow::PDFViewerMainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::PDFViewerMainWindow)
ui(new Ui::PDFViewerMainWindow),
m_pdfWidget(nullptr)
{
ui->setupUi(this);
// Initialize shortcuts
ui->actionOpen->setShortcut(QKeySequence::Open);
ui->actionClose->setShortcut(QKeySequence::Close);
ui->actionQuit->setShortcut(QKeySequence::Quit);
connect(ui->actionOpen, &QAction::triggered, this, &PDFViewerMainWindow::onActionOpenTriggered);
connect(ui->actionClose, &QAction::triggered, this, &PDFViewerMainWindow::onActionCloseTriggered);
connect(ui->actionQuit, &QAction::triggered, this, &PDFViewerMainWindow::onActionQuitTriggered);
m_pdfWidget = new pdf::PDFWidget(this);
setCentralWidget(m_pdfWidget);
setFocusProxy(m_pdfWidget);
readSettings();
}
PDFViewerMainWindow::~PDFViewerMainWindow()
@ -27,35 +64,120 @@ PDFViewerMainWindow::~PDFViewerMainWindow()
void PDFViewerMainWindow::onActionOpenTriggered()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Select PDF document"), "K:/Programming/PDF/testpdf", tr("PDF document (*.pdf)"));
QString fileName = QFileDialog::getOpenFileName(this, tr("Select PDF document"), m_directory, tr("PDF document (*.pdf)"));
if (!fileName.isEmpty())
{
pdf::PDFDocumentReader reader;
pdf::PDFDocument document = reader.readFromFile(fileName);
pdf::PDFStatisticsCollector collector;
pdf::PDFApplyVisitor(document, &collector);
if (reader.isSuccessfull())
{
QMessageBox::information(this, tr("PDF Reader"), tr("Document '%1' was successfully loaded!").arg(fileName));
}
else
{
QMessageBox::information(this, tr("PDF Reader"), tr("Document read error: %1").arg(reader.getErrorMessage()));
}
const pdf::PDFCatalog* catalog = document.getCatalog();
const pdf::PDFPage* page = catalog->getPage(0);
const pdf::PDFObject& contents = page->getContents();
if (contents.isStream())
{
const pdf::PDFStream* stream = contents.getStream();
const QByteArray* compressed = stream->getContent();
pdf::PDFFlateDecodeFilter fd;
QByteArray uncompressed = fd.apply(*compressed, &document, pdf::PDFObject());
}
openDocument(fileName);
}
}
void PDFViewerMainWindow::onActionCloseTriggered()
{
closeDocument();
}
void PDFViewerMainWindow::onActionQuitTriggered()
{
close();
}
void PDFViewerMainWindow::readSettings()
{
QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
QByteArray geometry = settings.value("geometry", QByteArray()).toByteArray();
if (geometry.isEmpty())
{
QRect availableGeometry = QApplication::desktop()->availableGeometry(this);
QRect windowRect(0, 0, availableGeometry.width() / 2, availableGeometry.height() / 2);
windowRect = windowRect.translated(availableGeometry.center() - windowRect.center());
setGeometry(windowRect);
}
else
{
restoreGeometry(geometry);
}
QByteArray state = settings.value("windowState", QByteArray()).toByteArray();
if (!state.isEmpty())
{
restoreState(state);
}
m_directory = settings.value("defaultDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)).toString();
}
void PDFViewerMainWindow::writeSettings()
{
QSettings settings(QCoreApplication::organizationName(), QCoreApplication::applicationName());
settings.setValue("geometry", saveGeometry());
settings.setValue("windowState", saveState());
settings.setValue("defaultDirectory", m_directory);
}
void PDFViewerMainWindow::updateTitle()
{
if (m_pdfDocument)
{
QString title = m_pdfDocument->getInfo()->title;
if (title.isEmpty())
{
title = m_currentFile;
}
setWindowTitle(tr("%1 - PDF Viewer").arg(m_currentFile));
}
else
{
setWindowTitle(tr("PDF Viewer"));
}
}
void PDFViewerMainWindow::openDocument(const QString& fileName)
{
// First close old document
closeDocument();
// Try to open a new document
QApplication::setOverrideCursor(Qt::WaitCursor);
pdf::PDFDocumentReader reader;
pdf::PDFDocument document = reader.readFromFile(fileName);
QApplication::restoreOverrideCursor();
if (reader.isSuccessfull())
{
// Mark current directory as this
QFileInfo fileInfo(fileName);
m_directory = fileInfo.dir().absolutePath();
m_currentFile = fileInfo.fileName();
m_pdfDocument.reset(new pdf::PDFDocument(std::move(document)));
setDocument(m_pdfDocument.data());
statusBar()->showMessage(tr("Document '%1' was successfully loaded!").arg(fileName), 4000);
}
else
{
QMessageBox::critical(this, tr("PDF Viewer"), tr("Document read error: %1").arg(reader.getErrorMessage()));
}
}
void PDFViewerMainWindow::setDocument(const pdf::PDFDocument* document)
{
m_pdfWidget->setDocument(document);
updateTitle();
}
void PDFViewerMainWindow::closeDocument()
{
setDocument(nullptr);
m_pdfDocument.reset();
}
void PDFViewerMainWindow::closeEvent(QCloseEvent* event)
{
writeSettings();
closeDocument();
event->accept();
}
} // namespace pdfviewer

View File

@ -1,13 +1,37 @@
// 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 <https://www.gnu.org/licenses/>.
#ifndef PDFVIEWERMAINWINDOW_H
#define PDFVIEWERMAINWINDOW_H
#include <QMainWindow>
#include <QSharedPointer>
namespace Ui
{
class PDFViewerMainWindow;
}
namespace pdf
{
class PDFWidget;
class PDFDocument;
}
namespace pdfviewer
{
@ -19,10 +43,27 @@ public:
explicit PDFViewerMainWindow(QWidget *parent = nullptr);
virtual ~PDFViewerMainWindow() override;
virtual void closeEvent(QCloseEvent* event) override;
private:
void onActionOpenTriggered();
void onActionCloseTriggered();
void onActionQuitTriggered();
void readSettings();
void writeSettings();
void updateTitle();
void openDocument(const QString& fileName);
void setDocument(const pdf::PDFDocument* document);
void closeDocument();
Ui::PDFViewerMainWindow* ui;
pdf::PDFWidget* m_pdfWidget;
QSharedPointer<pdf::PDFDocument> m_pdfDocument;
QString m_directory;
QString m_currentFile;
};
} // namespace pdfviewer

View File

@ -6,12 +6,12 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<width>431</width>
<height>333</height>
</rect>
</property>
<property name="windowTitle">
<string>PDFViewerMainWindow</string>
<string>PDF Viewer</string>
</property>
<widget class="QWidget" name="centralWidget"/>
<widget class="QMenuBar" name="menuBar">
@ -19,7 +19,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<width>431</width>
<height>21</height>
</rect>
</property>
@ -28,6 +28,8 @@
<string>File</string>
</property>
<addaction name="actionOpen"/>
<addaction name="actionClose"/>
<addaction name="actionQuit"/>
</widget>
<addaction name="menuFile"/>
</widget>
@ -44,8 +46,15 @@
<property name="text">
<string>Open</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</action>
<action name="actionClose">
<property name="text">
<string>Close</string>
</property>
</action>
<action name="actionQuit">
<property name="text">
<string>Quit</string>
</property>
</action>
</widget>