1 #include "PDFViewer.h"
2 
PDFViewer(const QString pdf_doc,QWidget * parent,Qt::WindowFlags flags)3 PDFViewer::PDFViewer(const QString pdf_doc, QWidget *parent, Qt::WindowFlags flags) :
4   QMainWindow(parent, flags)
5 {
6   QtPDF::PDFDocumentWidget *docWidget = new QtPDF::PDFDocumentWidget(this);
7   connect(this, SIGNAL(switchInterfaceLocale(QLocale)), docWidget, SLOT(switchInterfaceLocale(QLocale)));
8 
9 #ifdef USE_MUPDF
10   docWidget->setDefaultBackend(QString::fromLatin1("mupdf"));
11 #elif USE_POPPLERQT
12   docWidget->setDefaultBackend(QString::fromLatin1("poppler-qt"));
13 #else
14   #error At least one backend is required
15 #endif
16 
17   if (!pdf_doc.isEmpty() && docWidget)
18     docWidget->load(pdf_doc);
19   docWidget->goFirst();
20 
21   _counter = new PageCounter(this->statusBar());
22   _zoomWdgt = new ZoomTracker(this);
23   _search = new SearchLineEdit(this);
24   _toolBar = new QToolBar(this);
25 
26   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/document-open.png")), tr("Open..."), this, SLOT(open()));
27   _toolBar->addSeparator();
28 
29   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/zoomin.png")), tr("Zoom In"), docWidget, SLOT(zoomIn()));
30   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/zoomout.png")), tr("Zoom Out"), docWidget, SLOT(zoomOut()));
31   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/zoom-fitwidth.png")), tr("Fit to Width"), docWidget, SLOT(zoomFitWidth()));
32   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/zoom-fitwindow.png")), tr("Fit to Window"), docWidget, SLOT(zoomFitWindow()));
33 
34   _toolBar->addSeparator();
35   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/pagemode-single.png")), tr("Single Page Mode"), docWidget, SLOT(setSinglePageMode()));
36   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/pagemode-continuous.png")), tr("One Column Continuous Page Mode"), docWidget, SLOT(setOneColContPageMode()));
37   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/pagemode-twocols.png")), tr("Two Columns Continuous Page Mode"), docWidget, SLOT(setTwoColContPageMode()));
38   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/pagemode-present.png")), tr("Presentation Mode"), docWidget, SLOT(setPresentationMode()));
39   // TODO: fullscreen mode for presentations
40 
41   _toolBar->addSeparator();
42   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/zoom.png")), tr("Magnify"), docWidget, SLOT(setMouseModeMagnifyingGlass()));
43   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/hand.png")), tr("Pan"), docWidget, SLOT(setMouseModeMove()));
44   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/zoom-select.png")), tr("Marquee Zoom"), docWidget, SLOT(setMouseModeMarqueeZoom()));
45   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/measure.png")), tr("Measure"), docWidget, SLOT(setMouseModeMeasure()));
46   _toolBar->addAction(QIcon(QString::fromUtf8(":/QtPDF/icons/select-text.png")), tr("Select"), docWidget, SLOT(setMouseModeSelect()));
47 
48   _counter->setLastPage(docWidget->lastPage());
49   connect(docWidget, SIGNAL(changedPage(int)), _counter, SLOT(setCurrentPage(int)));
50   connect(docWidget, SIGNAL(changedZoom(qreal)), _zoomWdgt, SLOT(setZoom(qreal)));
51   connect(docWidget, SIGNAL(requestOpenUrl(const QUrl)), this, SLOT(openUrl(const QUrl)));
52   connect(docWidget, SIGNAL(requestOpenPdf(QString, QtPDF::PDFDestination, bool)), this, SLOT(openPdf(QString, QtPDF::PDFDestination, bool)));
53   connect(docWidget, SIGNAL(contextClick(const int, const QPointF)), this, SLOT(syncFromPdf(const int, const QPointF)));
54   connect(docWidget, SIGNAL(searchProgressChanged(int, int)), this, SLOT(searchProgressChanged(int, int)));
55   connect(docWidget, SIGNAL(changedDocument(const QWeakPointer<QtPDF::Backend::Document>)), this, SLOT(documentChanged(const QWeakPointer<QtPDF::Backend::Document>)));
56 
57   _toolBar->addSeparator();
58 #ifdef DEBUG
59   // FIXME: Remove this
60   _toolBar->addAction(QString::fromUtf8("en"), this, SLOT(setEnglishLocale()));
61   _toolBar->addAction(QString::fromUtf8("de"), this, SLOT(setGermanLocale()));
62   _toolBar->addSeparator();
63 #endif
64   _toolBar->addWidget(_search);
65   connect(_search, SIGNAL(searchRequested(QString)), docWidget, SLOT(search(QString)));
66   connect(_search, SIGNAL(gotoNextResult()), docWidget, SLOT(nextSearchResult()));
67   connect(_search, SIGNAL(gotoPreviousResult()), docWidget, SLOT(previousSearchResult()));
68   connect(_search, SIGNAL(searchCleared()), docWidget, SLOT(clearSearchResults()));
69 
70   statusBar()->addPermanentWidget(_counter);
71   statusBar()->addWidget(_zoomWdgt);
72   addToolBar(_toolBar);
73   setCentralWidget(docWidget);
74 
75   QDockWidget * toc = docWidget->dockWidget(QtPDF::PDFDocumentView::Dock_TableOfContents, this);
76   addDockWidget(Qt::LeftDockWidgetArea, toc);
77   tabifyDockWidget(toc, docWidget->dockWidget(QtPDF::PDFDocumentView::Dock_MetaData, this));
78   tabifyDockWidget(toc, docWidget->dockWidget(QtPDF::PDFDocumentView::Dock_Fonts, this));
79   tabifyDockWidget(toc, docWidget->dockWidget(QtPDF::PDFDocumentView::Dock_Permissions, this));
80   tabifyDockWidget(toc, docWidget->dockWidget(QtPDF::PDFDocumentView::Dock_Annotations, this));
81   toc->raise();
82 
83   QShortcut * goPrevViewRect = new QShortcut(QKeySequence(tr("Alt+Left")), this);
84   connect(goPrevViewRect, SIGNAL(activated()), docWidget, SLOT(goPrevViewRect()));
85 }
86 
open()87 void PDFViewer::open()
88 {
89   QString pdf_doc = QFileDialog::getOpenFileName(this, tr("Open PDF Document"), QString(), tr("PDF documents (*.pdf)"));
90   if (pdf_doc.isEmpty())
91     return;
92 
93   QtPDF::PDFDocumentWidget * docWidget = qobject_cast<QtPDF::PDFDocumentWidget*>(centralWidget());
94   Q_ASSERT(docWidget != NULL);
95   docWidget->load(pdf_doc);
96 }
97 
documentChanged(const QWeakPointer<QtPDF::Backend::Document> newDoc)98 void PDFViewer::documentChanged(const QWeakPointer<QtPDF::Backend::Document> newDoc)
99 {
100   if (_counter) {
101     QSharedPointer<QtPDF::Backend::Document> doc(newDoc.toStrongRef());
102     _counter->setLastPage(doc->numPages());
103   }
104 }
105 
searchProgressChanged(int percent,int occurrences)106 void PDFViewer::searchProgressChanged(int percent, int occurrences)
107 {
108   statusBar()->showMessage(tr("%1% of the document searched (%2 occurrences found)").arg(percent).arg(occurrences));
109 }
110 
openUrl(const QUrl url) const111 void PDFViewer::openUrl(const QUrl url) const
112 {
113   // **FIXME:** _We don't handle this yet!_
114   if( url.scheme() == QString::fromUtf8("file") )
115     return;
116 
117   // **TODO:**
118   // `openUrl` can fail and that needs to be handled._
119   QDesktopServices::openUrl(url);
120 }
121 
openPdf(QString filename,QtPDF::PDFDestination destination,bool newWindow) const122 void PDFViewer::openPdf(QString filename, QtPDF::PDFDestination destination, bool newWindow) const
123 {
124   qDebug() << "Open PDF" << filename << "on page" << (destination.page() + 1) << "/ at anchor" << destination.destinationName() << "(in new window =" << newWindow << ")";
125 }
126 
syncFromPdf(const int page,const QPointF pos)127 void PDFViewer::syncFromPdf(const int page, const QPointF pos)
128 {
129   qDebug() << "Invoke SyncTeX from page" << (page + 1) << "at" << pos;
130 }
131 
132 
PageCounter(QWidget * parent,Qt::WindowFlags f)133 PageCounter::PageCounter(QWidget *parent, Qt::WindowFlags f) : super(parent, f),
134   currentPage(1),
135   lastPage(-1)
136 {
137   refreshText();
138 }
139 
setLastPage(int page)140 void PageCounter::setLastPage(int page){
141   lastPage = page;
142   refreshText();
143 }
144 
setCurrentPage(int page)145 void PageCounter::setCurrentPage(int page){
146   // Assume the `SIGNAL` attached to this slot is emitting page numbers as
147   // indicies in a 0-based array.
148   currentPage = page + 1;
149   refreshText();
150 }
151 
refreshText()152 void PageCounter::refreshText() {
153   if (lastPage > 0 && currentPage > 0 && currentPage <= lastPage)
154     setText(tr("Page %1 of %2").arg(currentPage).arg(lastPage));
155   else
156     setText(QString::fromLatin1(""));
157   update();
158 }
159 
160 
ZoomTracker(QWidget * parent,Qt::WindowFlags f)161 ZoomTracker::ZoomTracker(QWidget *parent, Qt::WindowFlags f) : super(parent, f),
162   zoom(1.0)
163 {
164   refreshText();
165 }
166 
setZoom(qreal newZoom)167 void ZoomTracker::setZoom(qreal newZoom){
168   zoom = newZoom;
169   refreshText();
170 }
171 
refreshText()172 void ZoomTracker::refreshText() {
173   setText(tr("Zoom %1%").arg(zoom * 100));
174   update();
175 }
176 
177 // The SearchLineEdit class is adapted from code presented by Girish
178 // Ramakrishnan in a Qt Labs post:
179 //
180 //   http://labs.qt.nokia.com/2007/06/06/lineedit-with-a-clear-button
SearchLineEdit(QWidget * parent)181 SearchLineEdit::SearchLineEdit(QWidget *parent):
182   QLineEdit(parent)
183 {
184   previousResultButton = new QToolButton(this);
185   previousResultButton->setIcon(style()->standardIcon(QStyle::SP_ArrowLeft));
186   previousResultButton->setCursor(Qt::ArrowCursor);
187   previousResultButton->setStyleSheet(QString::fromUtf8("QToolButton { border: none; padding: 0px; }"));
188   connect(previousResultButton, SIGNAL(clicked()), this, SLOT(handlePreviousResult()));
189 
190   nextResultButton = new QToolButton(this);
191   nextResultButton->setIcon(style()->standardIcon(QStyle::SP_ArrowRight));
192   nextResultButton->setCursor(Qt::ArrowCursor);
193   nextResultButton->setStyleSheet(QString::fromUtf8("QToolButton { border: none; padding: 0px; }"));
194   connect(nextResultButton, SIGNAL(clicked()), this, SLOT(handleNextResult()));
195 
196   clearButton = new QToolButton(this);
197   clearButton->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton));
198   clearButton->setCursor(Qt::ArrowCursor);
199   clearButton->setStyleSheet(QString::fromUtf8("QToolButton { border: none; padding: 0px; }"));
200   connect(clearButton, SIGNAL(clicked()), this, SLOT(clearSearch()));
201 
202   int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
203   setStyleSheet(QString::fromUtf8("QLineEdit { padding-right: %1px; } ").arg(
204       nextResultButton->sizeHint().width() +
205       previousResultButton->sizeHint().width() +
206       clearButton->sizeHint().width() + frameWidth + 1));
207   QSize msz = minimumSizeHint();
208   setMinimumSize(qMax(msz.width(), nextResultButton->sizeHint().width() +
209       previousResultButton->sizeHint().width() +
210       clearButton->sizeHint().width() + frameWidth * 2 + 2),
211       qMax(msz.height(), clearButton->sizeHint().height() + frameWidth * 2 + 2));
212 
213   connect(this, SIGNAL(returnPressed()), this, SLOT(prepareSearch()));
214 
215   setPlaceholderText(QString::fromUtf8("Search"));
216 }
217 
resizeEvent(QResizeEvent *)218 void SearchLineEdit::resizeEvent(QResizeEvent *)
219 {
220   QSize sa = previousResultButton->sizeHint(),
221         sb = nextResultButton->sizeHint(),
222         sc = clearButton->sizeHint();
223 
224   int frameWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
225 
226   previousResultButton->move(rect().right() - frameWidth - sa.width() - sb.width() - sc.width(),
227                     (rect().bottom() + 1 - sa.height())/2);
228   nextResultButton->move(rect().right() - frameWidth - sb.width() - sc.width(),
229                     (rect().bottom() + 1 - sb.height())/2);
230   clearButton->move(rect().right() - frameWidth - sc.width(),
231                     (rect().bottom() + 1 - sc.height())/2);
232 }
233 
prepareSearch()234 void SearchLineEdit::prepareSearch() {
235   if( this->text().isEmpty() )
236     return;
237 
238   emit searchRequested(this->text());
239 }
240 
clearSearch()241 void SearchLineEdit::clearSearch() {
242   // Don't check for empty text as the user may have deleted the text, then hit
243   // the clear button. In this case, there are still other objects that may
244   // want to recieve the `searchCleared` signal.
245   clear();
246 
247   emit searchCleared();
248 }
249 
handleNextResult()250 void SearchLineEdit::handleNextResult() {
251   if( this->text().isEmpty() )
252     return;
253 
254   emit gotoNextResult();
255 }
256 
handlePreviousResult()257 void SearchLineEdit::handlePreviousResult() {
258   if( this->text().isEmpty() )
259     return;
260 
261   emit gotoPreviousResult();
262 }
263 
264 
265 // vim: set sw=2 ts=2 et
266