summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Readme.md14
-rw-r--r--meson.build31
-rw-r--r--src/contentswidget/contentswidget.cpp61
-rw-r--r--src/contentswidget/contentswidget.h25
-rw-r--r--src/contentswidget/contentswidget.ui52
-rw-r--r--src/infowidget/infowidget.cpp42
-rw-r--r--src/infowidget/infowidget.h35
-rw-r--r--src/infowidget/infowidget.ui119
-rw-r--r--src/main.cpp44
-rw-r--r--src/mainwindow/mainwindow.cpp103
-rw-r--r--src/mainwindow/mainwindow.h35
-rw-r--r--src/mainwindow/mainwindow.ui115
12 files changed, 676 insertions, 0 deletions
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..e51c6fa
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,14 @@
+# cpdf
+
+## What is this?
+A simple PDF viewer.
+
+## Building from source
+You need meson, ninja, qt5 and poppler-qt5.
+
+```sh
+mkdir build
+meson build
+ninja -C build
+```
+
diff --git a/meson.build b/meson.build
new file mode 100644
index 0000000..36418ea
--- /dev/null
+++ b/meson.build
@@ -0,0 +1,31 @@
+project('cpdf', 'cpp',
+ default_options: ['cpp_std=c++17'],
+ license: 'GPL'
+)
+
+if not get_option('debug')
+ add_project_arguments('-DQT_NO_DEBUG', language: 'cpp')
+endif
+
+mod_qt5 = import('qt5')
+dep_qt5 = dependency('qt5', modules: ['Core', 'Xml', 'Widgets'], required: true)
+
+poppler = dependency('poppler-qt5', required: true)
+
+moc = mod_qt5.preprocess(
+ moc_headers: ['src/mainwindow/mainwindow.h', 'src/contentswidget/contentswidget.h', 'src/infowidget/infowidget.h'],
+ ui_files: ['src/mainwindow/mainwindow.ui', 'src/contentswidget/contentswidget.ui', 'src/infowidget/infowidget.ui'],
+ dependencies: dep_qt5
+)
+
+cpdf = executable('cpdf', install: true,
+ dependencies: [dep_qt5, poppler],
+ include_directories: include_directories('src'),
+ sources: ['src/main.cpp',
+ 'src/mainwindow/mainwindow.cpp',
+ 'src/contentswidget/contentswidget.cpp',
+ 'src/infowidget/infowidget.cpp',
+ moc
+ ]
+)
+
diff --git a/src/contentswidget/contentswidget.cpp b/src/contentswidget/contentswidget.cpp
new file mode 100644
index 0000000..40ea5f9
--- /dev/null
+++ b/src/contentswidget/contentswidget.cpp
@@ -0,0 +1,61 @@
+#include "contentswidget.h"
+#include "ui_contentswidget.h"
+#include <QDomDocument>
+
+ContentsForm::ContentsForm(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::ContentsForm)
+{
+ ui->setupUi(this);
+}
+
+ContentsForm::~ContentsForm()
+{
+ delete ui;
+}
+
+QTreeWidgetItem *treeNode(QTreeWidgetItem *parent, const QDomNode &node)
+{
+ auto *item = new QTreeWidgetItem(parent);
+ item->setText(0, node.nodeName());
+
+ if(node.hasAttributes()) {
+ const auto attr = node.attributes();
+ for(int i = 0; i < attr.length(); ++i) {
+ const QString name = attr.item(i).nodeName();
+ const QString value = attr.item(i).nodeValue();
+
+ if(name == "Open")
+ item->setText(1, value);
+ else if(name == "Destination")
+ item->setText(2, value);
+ else
+ qDebug("Unknown attribute name %s\nattr.value=%s", qUtf8Printable(name), qUtf8Printable(value));
+ }
+ }
+
+ auto child = node.firstChild();
+ while(!child.isNull()) {
+ treeNode(item, child);
+ child = child.nextSibling();
+ }
+
+ return item;
+}
+
+void ContentsForm::setContents(QDomDocument *document)
+{
+ ui->treeWidget->clear();
+
+ if(document && !document->isNull()) {
+ auto node = document->firstChild();
+ while(!node.isNull()) {
+ auto *item = treeNode(nullptr, node);
+ ui->treeWidget->addTopLevelItem(item);
+
+ node = node.nextSibling();
+ }
+ }
+ delete document;
+}
+
diff --git a/src/contentswidget/contentswidget.h b/src/contentswidget/contentswidget.h
new file mode 100644
index 0000000..9276bf4
--- /dev/null
+++ b/src/contentswidget/contentswidget.h
@@ -0,0 +1,25 @@
+#ifndef CPDF_CONTENTSWIDGET_H
+#define CPDF_CONTENTSWIDGET_H
+
+#include <QWidget>
+
+namespace Ui {
+ class ContentsForm;
+}
+
+class QDomDocument;
+class ContentsForm : public QWidget
+{
+ Q_OBJECT
+public:
+ explicit ContentsForm(QWidget *parent = nullptr);
+ ~ContentsForm();
+
+public slots:
+ void setContents(QDomDocument *document);
+
+private:
+ Ui::ContentsForm *ui = nullptr;
+};
+
+#endif // CPDF_CONTENTSWIDGET_H
diff --git a/src/contentswidget/contentswidget.ui b/src/contentswidget/contentswidget.ui
new file mode 100644
index 0000000..8b74c36
--- /dev/null
+++ b/src/contentswidget/contentswidget.ui
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ContentsForm</class>
+ <widget class="QWidget" name="ContentsForm">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>240</width>
+ <height>320</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Contents</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QTreeWidget" name="treeWidget">
+ <column>
+ <property name="text">
+ <string notr="true">Item</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Open</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Destination</string>
+ </property>
+ </column>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/infowidget/infowidget.cpp b/src/infowidget/infowidget.cpp
new file mode 100644
index 0000000..9541b29
--- /dev/null
+++ b/src/infowidget/infowidget.cpp
@@ -0,0 +1,42 @@
+#include "infowidget.h"
+#include "ui_infowidget.h"
+
+InfoForm::InfoForm(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::InfoForm)
+{
+ ui->setupUi(this);
+}
+
+InfoForm::~InfoForm()
+{
+ delete ui;
+}
+
+void InfoForm::setMetadata(Metadata which, const QString &value)
+{
+ switch(which) {
+ case InfoForm::Title:
+ ui->title->setText(value);
+ return;
+ case InfoForm::Subject:
+ ui->subject->setText(value);
+ return;
+ case InfoForm::Author:
+ ui->author->setText(value);
+ return;
+ case InfoForm::Creator:
+ ui->creator->setText(value);
+ return;
+ case InfoForm::CreationDate:
+ ui->creationDate->setText(value);
+ return;
+ case InfoForm::ModificationDate:
+ ui->modificationDate->setText(value);
+ return;
+ case InfoForm::Producer:
+ ui->producer->setText(value);
+ return;
+ }
+}
+
diff --git a/src/infowidget/infowidget.h b/src/infowidget/infowidget.h
new file mode 100644
index 0000000..d419901
--- /dev/null
+++ b/src/infowidget/infowidget.h
@@ -0,0 +1,35 @@
+#ifndef CPDF_INFOWIDGET_H
+#define CPDF_INFOWIDGET_H
+
+#include <QWidget>
+
+namespace Ui {
+ class InfoForm;
+}
+
+class InfoForm : public QWidget
+{
+ Q_OBJECT
+
+public:
+ enum Metadata {
+ Title,
+ Subject,
+ Author,
+ Creator,
+ CreationDate,
+ ModificationDate,
+ Producer
+ };
+
+ explicit InfoForm(QWidget *parent = nullptr);
+ ~InfoForm();
+
+public slots:
+ void setMetadata(Metadata which, const QString &value);
+
+private:
+ Ui::InfoForm *ui = nullptr;
+};
+
+#endif // CPDF_INFOWIDGET_H
diff --git a/src/infowidget/infowidget.ui b/src/infowidget/infowidget.ui
new file mode 100644
index 0000000..e22c0fa
--- /dev/null
+++ b/src/infowidget/infowidget.ui
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>InfoForm</class>
+ <widget class="QWidget" name="InfoForm">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>320</width>
+ <height>180</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="title_label">
+ <property name="text">
+ <string>Title</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="title">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="subject_label">
+ <property name="text">
+ <string>Subject</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="author_label">
+ <property name="text">
+ <string>Author</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="creator_label">
+ <property name="text">
+ <string>Creator</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="creationDate_label">
+ <property name="text">
+ <string>Creation date</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="modificationDate_label">
+ <property name="text">
+ <string>Modification date</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="producer_label">
+ <property name="text">
+ <string>Producer</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="subject">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="author">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="creator">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLabel" name="creationDate">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLabel" name="modificationDate">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QLabel" name="producer">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..494153e
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,44 @@
+#include <stdio.h>
+#include <QApplication>
+#include "mainwindow/mainwindow.h"
+#include <QCommandLineParser>
+
+inline MainWindow *openWindow(const QString &path)
+{
+ auto *window = new MainWindow;
+ window->setAttribute(Qt::WA_DeleteOnClose, true);
+
+ if(!path.isEmpty())
+ window->open(path);
+
+ window->show();
+ return window;
+}
+
+int main(int argc, char** argv)
+{
+ QApplication app(argc, argv);
+ QCoreApplication::setApplicationName(QLatin1Literal("cpdf"));
+ QCoreApplication::setApplicationVersion(QLatin1Literal("0.1.0"));
+
+ QCommandLineParser parser;
+ parser.setApplicationDescription(QLatin1Literal("Simple PDF viewer"));
+ parser.addHelpOption();
+ parser.addVersionOption();
+ parser.addPositionalArgument(QLatin1Literal("URLs"), QLatin1Literal("PDF files to open"));
+
+ parser.process(app);
+
+ {
+ const auto files = parser.positionalArguments();
+ if(files.count() == 0) {
+ openWindow(QString());
+ } else {
+ for(const auto file : files) {
+ openWindow(file);
+ }
+ }
+ }
+
+ return app.exec();
+}
diff --git a/src/mainwindow/mainwindow.cpp b/src/mainwindow/mainwindow.cpp
new file mode 100644
index 0000000..3f854da
--- /dev/null
+++ b/src/mainwindow/mainwindow.cpp
@@ -0,0 +1,103 @@
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include <QFileDialog>
+#include <QDockWidget>
+#include <QLabel>
+#include <poppler-qt5.h>
+#include "infowidget/infowidget.h"
+#include "contentswidget/contentswidget.h"
+
+MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags)
+ : QMainWindow(parent, flags)
+ , ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+ auto *infoDock = new QDockWidget(tr("Info"), this);
+ info = new InfoForm(infoDock);
+ infoDock->setWidget(info);
+ addDockWidget(Qt::LeftDockWidgetArea, infoDock);
+
+ auto *contentsDock = new QDockWidget(tr("Contents"), this);
+ contents = new ContentsForm(contentsDock);
+ contentsDock->setWidget(contents);
+ addDockWidget(Qt::LeftDockWidgetArea, contentsDock);
+
+ connect(ui->actionOpen, &QAction::triggered, this, [this]() {
+ const QString userPath = QFileDialog::getOpenFileName(this, tr("Select PDF to open"), QDir::homePath(), tr("PDF Files (*.pdf);;All Files (*)"));
+ if(!userPath.isEmpty())
+ this->open(userPath);
+ });
+ connect(ui->actionQuit, &QAction::triggered, qApp, &QApplication::quit);
+
+ // navigation
+ connect(ui->previous, &QToolButton::clicked, this, [this]() {
+ const int currentPage = ui->pageNumber->text().toInt();
+ this->showPage(currentPage - 1);
+ });
+ connect(ui->next, &QToolButton::clicked, this, [this]() {
+ const int currentPage = ui->pageNumber->text().toInt();
+ this->showPage(currentPage + 1);
+ });
+ connect(ui->pageNumber, &QLineEdit::textEdited, this, [this](const QString &text) {
+ const int page = text.toInt();
+ this->showPage(page);
+ });
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
+void MainWindow::open(const QString &path)
+{
+ if(path.isEmpty()) {
+ return;
+ }
+
+ if(!QFile::exists(path)) {
+ statusBar()->showMessage(tr("Couldn't open file because it doesn't exist"), 3000);
+ return;
+ }
+
+ qDebug("open path: %s", qUtf8Printable(path));
+ delete document;
+
+ document = Poppler::Document::load(path);
+ if(!document || document->isLocked()) {
+ statusBar()->showMessage(tr("Couldn't parse file"), 3000);
+ return;
+ }
+
+ info->setMetadata(InfoForm::Title, document->title());
+ info->setMetadata(InfoForm::Subject, document->subject());
+ info->setMetadata(InfoForm::Author, document->author());
+ info->setMetadata(InfoForm::Creator, document->creator());
+ info->setMetadata(InfoForm::CreationDate, document->creationDate().toString(Qt::RFC2822Date));
+ info->setMetadata(InfoForm::ModificationDate, document->modificationDate().toString(Qt::RFC2822Date));
+ info->setMetadata(InfoForm::Producer, document->producer());
+
+ contents->setContents(document->toc());
+
+ showPage(0);
+
+ statusBar()->showMessage(tr("Opened file %1").arg(path), 3000);
+}
+
+void MainWindow::showPage(int pageNumber)
+{
+ auto *page = document->page(pageNumber);
+ if(page == nullptr)
+ return;
+
+ ui->pageNumber->setText(QString::number(pageNumber));
+
+ const auto image = page->renderToImage();
+ auto *label = new QLabel(this);
+ label->setPixmap(QPixmap::fromImage(image));
+
+ ui->scrollArea->setWidget(label);
+
+ delete page;
+}
diff --git a/src/mainwindow/mainwindow.h b/src/mainwindow/mainwindow.h
new file mode 100644
index 0000000..14ceb49
--- /dev/null
+++ b/src/mainwindow/mainwindow.h
@@ -0,0 +1,35 @@
+#ifndef CPDF_MAINWINDOW_H
+#define CPDF_MAINWINDOW_H
+
+#include <QMainWindow>
+
+namespace Poppler {
+ class Document;
+}
+
+namespace Ui {
+ class MainWindow;
+}
+
+class InfoForm;
+class ContentsForm;
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags());
+ ~MainWindow();
+
+public slots:
+ void open(const QString &path);
+ void showPage(int pageNumber);
+
+private:
+ Ui::MainWindow *ui = nullptr;
+ InfoForm *info = nullptr;
+ ContentsForm *contents = nullptr;
+ Poppler::Document *document = nullptr;
+};
+
+#endif // CPDF_MAINWINDOW_H
diff --git a/src/mainwindow/mainwindow.ui b/src/mainwindow/mainwindow.ui
new file mode 100644
index 0000000..6c37ddf
--- /dev/null
+++ b/src/mainwindow/mainwindow.ui
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>640</width>
+ <height>480</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>cpdf</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QToolButton" name="previous">
+ <property name="text">
+ <string>Previous</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Left</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QToolButton" name="next">
+ <property name="text">
+ <string>Next</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Right</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="pageNumber"/>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="widgetResizable">
+ <bool>true</bool>
+ </property>
+ <widget class="QWidget" name="scrollAreaWidgetContents">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>620</width>
+ <height>380</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QMenuBar" name="menubar">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>640</width>
+ <height>23</height>
+ </rect>
+ </property>
+ <widget class="QMenu" name="menuFile">
+ <property name="title">
+ <string>File</string>
+ </property>
+ <addaction name="actionOpen"/>
+ <addaction name="actionQuit"/>
+ </widget>
+ <addaction name="menuFile"/>
+ </widget>
+ <widget class="QStatusBar" name="statusbar"/>
+ <action name="actionOpen">
+ <property name="text">
+ <string>Open</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+O</string>
+ </property>
+ </action>
+ <action name="actionQuit">
+ <property name="text">
+ <string>Quit</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Q</string>
+ </property>
+ </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>