1 /*
2 This is part of TeXworks, an environment for working with TeX documents
3 Copyright (C) 2007-2010 Jonathan Kew
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 For links to further information, or to contact the author,
19 see <http://texworks.org/>.
20 */
21
22 #ifndef NO_POPPLER_PREVIEW
23
24 #include "mostQtHeaders.h"
25
26 // Based on code by Pino Toscano from Poppler / qt4 / Demos, released under GPL 2 or later
27 /*! \class PDFDock
28 * \file PDFDocks.cpp
29 * \brief docking panel for PDF viewer
30 *
31 * This class provides the base functions for dockable side-panel.
32 * It is used for additional information like table of contents, preview images, etc.
33 *
34 * \see PDFDocument
35 */
36
37 #include "PDFDocks.h"
38 #include "PDFDocument.h"
39 #include "universalinputdialog.h"
40
41 /*!
42 * \brief constructor
43 * \param doc actual pdf document which is displayed
44 */
PDFDock(PDFDocument * doc)45 PDFDock::PDFDock(PDFDocument *doc)
46 : QDockWidget("", doc), document(doc), filled(false)
47 {
48 connect(this, SIGNAL(visibilityChanged(bool)), SLOT(myVisibilityChanged(bool)));
49 //TODO: connect(TWApp::instance(), SIGNAL(updatedTranslators()), this, SLOT(changeLanguage()));
50 }
51
~PDFDock()52 PDFDock::~PDFDock()
53 {
54 }
55
documentLoaded()56 void PDFDock::documentLoaded()
57 {
58 if (!isHidden()) {
59 fillInfo();
60 filled = true;
61 }
62 }
63
documentClosed()64 void PDFDock::documentClosed()
65 {
66 filled = false;
67 }
68
pageChanged(int page)69 void PDFDock::pageChanged(int page)
70 {
71 Q_UNUSED(page)
72 }
73
addAction(const QString & caption,const char * slot)74 void PDFDock::addAction(const QString& caption, const char* slot)
75 {
76 QAction *act = new QAction(caption, this);
77 connect(act, SIGNAL(triggered()), slot);
78 addAction(act);
79 }
80
myVisibilityChanged(bool visible)81 void PDFDock::myVisibilityChanged(bool visible)
82 {
83 setWindowTitle(getTitle());
84 if (visible && document && !filled) {
85 fillInfo();
86 filled = true;
87 }
88 }
89
changeLanguage()90 void PDFDock::changeLanguage()
91 {
92 setWindowTitle(getTitle());
93 }
94
95 //////////////// OUTLINE ////////////////
96
fillToc(const QDomNode & parent,QTreeWidget * tree,QTreeWidgetItem * parentItem)97 static void fillToc(const QDomNode &parent, QTreeWidget *tree, QTreeWidgetItem *parentItem)
98 {
99 QTreeWidgetItem *newitem = nullptr;
100 for (QDomNode node = parent.firstChild(); !node.isNull(); node = node.nextSibling()) {
101 QDomElement e = node.toElement();
102
103 if (!parentItem)
104 newitem = new QTreeWidgetItem(tree, newitem);
105 else
106 newitem = new QTreeWidgetItem(parentItem, newitem);
107 newitem->setText(0, e.tagName());
108
109 bool isOpen = false;
110 if (e.hasAttribute("Open"))
111 isOpen = QVariant(e.attribute("Open")).toBool();
112 if (isOpen)
113 tree->expandItem(newitem);
114
115 if (e.hasAttribute("DestinationName"))
116 newitem->setText(1, e.attribute("DestinationName"));
117
118 if (e.hasChildNodes())
119 fillToc(node, tree, newitem);
120 }
121 }
122 #if POPPLER_VERSION_MAJOR>0 || POPPLER_VERSION_MINOR>=74
fillOutline(const QVector<Poppler::OutlineItem> toc,QTreeWidget * tree,QTreeWidgetItem * parentItem)123 static void fillOutline(const QVector<Poppler::OutlineItem>toc, QTreeWidget *tree, QTreeWidgetItem *parentItem)
124 {
125 QTreeWidgetItem *newitem = nullptr;
126 foreach(Poppler::OutlineItem e,toc) {
127 if (!parentItem)
128 newitem = new QTreeWidgetItem(tree, newitem);
129 else
130 newitem = new QTreeWidgetItem(parentItem, newitem);
131 newitem->setText(0, e.name());
132
133 bool isOpen = e.isOpen();
134
135 if (isOpen)
136 tree->expandItem(newitem);
137
138 if (e.destination()){
139 newitem->setText(1, e.destination()->toString());
140 }
141
142 if (e.hasChildren())
143 fillOutline(e.children(), tree, newitem);
144 }
145 }
146 #endif
147 /*! \class PDFOutlineDock
148 *
149 * \brief sidepanel for preview
150 *
151 * show page preview in the sidepanel
152 *
153 * the actual rendering is done in extra threads
154 */
155 /*!
156 * \brief constructor
157 * \param doc
158 */
PDFOutlineDock(PDFDocument * doc)159 PDFOutlineDock::PDFOutlineDock(PDFDocument *doc)
160 : PDFDock(doc)
161 {
162 setObjectName("outline");
163 tree = new PDFDockTreeWidget(this);
164 tree->setAlternatingRowColors(true);
165 tree->header()->hide();
166 tree->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
167 setWidget(tree);
168 setWindowTitle(getTitle());
169 }
170
~PDFOutlineDock()171 PDFOutlineDock::~PDFOutlineDock()
172 {
173 }
174
changeLanguage()175 void PDFOutlineDock::changeLanguage()
176 {
177 PDFDock::changeLanguage();
178 if (filled)
179 fillInfo();
180 }
181
fillInfo()182 void PDFOutlineDock::fillInfo()
183 {
184 tree->clear();
185 if (!document || document->popplerDoc().isNull()) return;
186
187
188
189 #if POPPLER_VERSION_MAJOR>0 || POPPLER_VERSION_MINOR>=74
190 QVector<Poppler::OutlineItem>toc=document->popplerDoc()->outline();
191 if(!toc.isEmpty()){
192 fillOutline(toc, tree, nullptr);
193 connect(tree, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(followTocSelection()));
194 #else
195 const QDomDocument *toc = document->popplerDoc()->toc();
196 if (toc) {
197 fillToc(*toc, tree, nullptr);
198 connect(tree, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(followTocSelection()));
199 delete toc;
200 #endif
201 } else {
202 QTreeWidgetItem *item = new QTreeWidgetItem();
203 item->setText(0, tr("No TOC"));
204 item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
205 tree->addTopLevelItem(item);
206 }
207 }
208
209 void PDFOutlineDock::documentClosed()
210 {
211 tree->clear();
212 PDFDock::documentClosed();
213 }
214
215 void PDFOutlineDock::followTocSelection()
216 {
217 QList<QTreeWidgetItem *> items = tree->selectedItems();
218 if (items.count() > 0) {
219 QTreeWidgetItem *item = items.first();
220 QString dest = item->text(1);
221 if (!dest.isEmpty())
222 document->goToDestination(dest);
223 }
224 }
225
226 PDFDockTreeWidget::PDFDockTreeWidget(QWidget *parent)
227 : QTreeWidget(parent)
228 {
229 }
230
231 PDFDockTreeWidget::~PDFDockTreeWidget()
232 {
233 }
234
235 QSize PDFDockTreeWidget::sizeHint() const
236 {
237 return QSize(120, 300);
238 }
239
240 //////////////// PDF INFO ////////////////
241
242 PDFInfoDock::PDFInfoDock(PDFDocument *doc)
243 : PDFDock(doc)
244 {
245 setObjectName("pdfinfo");
246 setWindowTitle(getTitle());
247 list = new PDFDockListWidget(this);
248 list->setAlternatingRowColors(true);
249 setWidget(list);
250 }
251
252 PDFInfoDock::~PDFInfoDock()
253 {
254 }
255
256 void PDFInfoDock::fillInfo()
257 {
258 list->clear();
259 if (!document) return;
260 QSharedPointer<Poppler::Document> spDoc(document->popplerDoc());
261 if (spDoc.isNull()) return;
262
263 const Poppler::Document *doc = spDoc.data();
264 QStringList keys = doc->infoKeys();
265 QStringList dateKeys;
266 dateKeys << "CreationDate";
267 dateKeys << "ModDate";
268 int i = 0;
269 foreach (const QString &date, dateKeys) {
270 const int id = keys.indexOf(date);
271 if (id != -1) {
272 list->addItem(date + ":");
273 list->addItem(doc->date(date).toLocalTime().toString());//Qt::SystemLocaleDate)); TODO
274 ++i;
275 keys.removeAt(id);
276 }
277 }
278 foreach (const QString &key, keys) {
279 list->addItem(key + ":");
280 list->addItem(doc->info(key));
281 ++i;
282 }
283 }
284
285 void PDFInfoDock::documentClosed()
286 {
287 list->clear();
288 PDFDock::documentClosed();
289 }
290
291 PDFDockListView::PDFDockListView(QWidget *parent)
292 : QListView(parent)
293 {
294 }
295
296 QSize PDFDockListView::sizeHint() const
297 {
298 return QSize(200, 300);
299 }
300
301 PDFDockListWidget::PDFDockListWidget(QWidget *parent)
302 : QListWidget(parent)
303 {
304 }
305
306
307 QSize PDFDockListWidget::sizeHint() const
308 {
309 return QSize(200, 300);
310 }
311
312 PDFOverviewModel::PDFOverviewModel(QObject *parent)
313 : QAbstractListModel(parent)
314 {
315 document = nullptr;
316 cache.clear();
317 }
318
319 int PDFOverviewModel::rowCount ( const QModelIndex &parent ) const
320 {
321 if (!document || document->popplerDoc().isNull()) return 0;
322 if (parent.isValid()) return 0;
323 if (!document->widget()) return 0;
324 return document->widget()->realNumPages();
325 }
326
327 QVariant PDFOverviewModel::data ( const QModelIndex &index, int role) const
328 {
329 if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= document->widget()->realNumPages()) return QVariant();
330 switch (role) {
331 case Qt::DisplayRole:
332 return QString::number(index.row() + 1);
333 case Qt::DecorationRole:
334 while (index.row() >= cache.size()) cache << QPixmap();
335 if (cache[index.row()].isNull()) {
336 const QObject *o = this; //TODO: get rid of const_cast
337 cache[index.row()] = document->renderManager->renderToImage(index.row(), const_cast<QObject *>(o), "updateImage", -1, -1, -1, -1, -1, -1, false).scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation);
338 }
339 return cache[index.row()];
340 case Qt::BackgroundRole:
341 return QColor(Qt::gray);
342 }
343 return QVariant();
344 }
345
346 void PDFOverviewModel::setDocument(PDFDocument *doc)
347 {
348 beginResetModel();
349 document = doc;
350 if (!doc) {
351 endResetModel();
352 return;
353 }
354 if (!doc->widget() || document->popplerDoc().isNull()) document = nullptr;
355 cache.clear();
356 endResetModel();
357 }
358
359 void PDFOverviewModel::updateImage(const QPixmap &pm, int page)
360 {
361 if (!document || page < 0 || page >= cache.size()) return;
362 cache[page] = pm.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation);
363 emit dataChanged(index(page), index(page));
364 }
365
366 //////////////// FONT LIST ////////////////
367
368 PDFFontsDock::PDFFontsDock(PDFDocument *doc)
369 : PDFDock(doc)
370 , scannedFonts(false)
371 {
372 setObjectName("fonts");
373 setWindowTitle(getTitle());
374 table = new QTableWidget(this);
375 #ifdef Q_OS_MAC /* don't do this on windows, as the font ends up too small */
376 QFont f(table->font());
377 f.setPointSize(f.pointSize() - 2);
378 table->setFont(f);
379 #endif
380 table->setColumnCount(4);
381 setHorizontalHeaderLabels();
382 table->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
383 table->setEditTriggers(QAbstractItemView::NoEditTriggers);
384 table->setAlternatingRowColors(true);
385 table->setShowGrid(false);
386 table->setSelectionBehavior(QAbstractItemView::SelectRows);
387 table->verticalHeader()->hide();
388 table->horizontalHeader()->setStretchLastSection(true);
389 table->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
390 setWidget(table);
391 }
392
393 PDFFontsDock::~PDFFontsDock()
394 {
395 }
396
397 void PDFFontsDock::changeLanguage()
398 {
399 PDFDock::changeLanguage();
400 setHorizontalHeaderLabels();
401 if (filled)
402 fillInfo();
403 }
404
405 void PDFFontsDock::setHorizontalHeaderLabels()
406 {
407 if (table)
408 table->setHorizontalHeaderLabels(QStringList() << tr("Name") << tr("Type") << tr("Subset") << tr("File"));
409 }
410
411 void PDFFontsDock::fillInfo()
412 {
413 if (!document) return;
414 QSharedPointer<Poppler::Document> spDoc(document->popplerDoc());
415 if (!scannedFonts) {
416 fonts = spDoc->fonts();
417 scannedFonts = true;
418 }
419 table->clearContents();
420 table->setRowCount(0);
421 table->setRowCount(fonts.count());
422 int i = 0;
423 foreach (const Poppler::FontInfo &font, fonts) {
424 if (font.name().isNull()) {
425 table->setItem(i, 0, new QTableWidgetItem(tr("[none]")));
426 } else {
427 table->setItem(i, 0, new QTableWidgetItem(font.name()));
428 }
429 table->setItem(i, 1, new QTableWidgetItem(font.typeName()));
430 table->setItem(i, 2, new QTableWidgetItem(font.isSubset() ? tr("yes") : tr("no")));
431 table->setItem(i, 3, new QTableWidgetItem(font.isEmbedded() ? tr("[embedded]") : font.file()));
432 ++i;
433 }
434 table->resizeColumnsToContents();
435 table->resizeRowsToContents();
436 }
437
438 void PDFFontsDock::documentLoaded()
439 {
440 scannedFonts = false;
441 fonts.clear();
442 PDFDock::documentLoaded();
443 }
444
445 void PDFFontsDock::documentClosed()
446 {
447 scannedFonts = false;
448 fonts.clear();
449 table->clear();
450 table->setRowCount(0);
451 PDFDock::documentClosed();
452 }
453
454 //////////////// SEARCH DOCK ////////////////
455
456 PDFBaseSearchDock::PDFBaseSearchDock(PDFDocument *doc): QDockWidget(doc), document(doc)
457 {
458 // do it completely programatic
459 setObjectName("search");
460 setWindowTitle(tr("Search"));
461 //this->resize(801, 31);
462 QWidget *tempWidget = new QWidget(this);
463 setWidget(tempWidget);
464 QGridLayout *gridLayout = new QGridLayout(tempWidget);
465 gridLayout->setContentsMargins(-1, 4, -1, 4);
466 QFrame *frame_2 = new QFrame(this);
467 frame_2->setObjectName(("frame_2"));
468 QSizePolicy sizePolicy1(QSizePolicy::Preferred, QSizePolicy::Preferred);
469 frame_2->setSizePolicy(sizePolicy1);
470 frame_2->setMinimumSize(QSize(0, 22));
471 frame_2->setFrameShape(QFrame::NoFrame);
472 frame_2->setLineWidth(0);
473 QHBoxLayout *hboxLayout = new QHBoxLayout(frame_2);
474 hboxLayout->setObjectName(("hboxLayout"));
475 hboxLayout->setContentsMargins(-1, 0, -1, 0);
476
477 QSize buttonSize(22, 22);
478
479 QLabel *label = new QLabel(frame_2);
480 label->setObjectName(("label"));
481 QSizePolicy sizePolicy3(QSizePolicy::Minimum, QSizePolicy::Preferred);
482 sizePolicy3.setHorizontalStretch(0);
483 sizePolicy3.setVerticalStretch(0);
484 sizePolicy3.setHeightForWidth(label->sizePolicy().hasHeightForWidth());
485 label->setSizePolicy(sizePolicy3);
486 label->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
487
488 hboxLayout->addWidget(label);
489
490
491 gridLayout->addWidget(frame_2, 0, 0, 1, 1);
492
493 leFind = new QLineEdit(this);
494 leFind->setClearButtonEnabled(true);
495 leFind->setObjectName(("leFind"));
496 QSizePolicy sizePolicy4(QSizePolicy::Preferred, QSizePolicy::Fixed);
497 sizePolicy4.setHorizontalStretch(2);
498 leFind->setSizePolicy(sizePolicy4);
499 leFind->setMinimumSize(QSize(120, 22));
500
501 gridLayout->addWidget(leFind, 0, 1, 1, 1);
502
503 bNext = new QToolButton(this);
504 bNext->setObjectName(("bNext"));
505 bNext->setMinimumSize(buttonSize);
506 bNext->setMaximumSize(buttonSize);
507 bNext->setIcon(getRealIcon("down"));
508
509 gridLayout->addWidget(bNext, 0, 3, 1, 1);
510
511 bPrevious = new QToolButton(this);
512 bPrevious->setObjectName(("bPrevious"));
513 bPrevious->setMinimumSize(buttonSize);
514 bPrevious->setMaximumSize(buttonSize);
515 bPrevious->setIcon(getRealIcon("up"));
516
517 gridLayout->addWidget(bPrevious, 0, 4, 1, 1);
518
519 QFrame *frame_6 = new QFrame(this);
520 sizePolicy1.setHeightForWidth(frame_6->sizePolicy().hasHeightForWidth());
521 frame_6->setSizePolicy(sizePolicy1);
522 frame_6->setFrameShape(QFrame::NoFrame);
523 gridLayout1 = new QGridLayout(frame_6);
524 gridLayout1->setContentsMargins(0, 0, 0, 0);
525 cbCase = new QCheckBox(frame_6);
526 cbCase->setObjectName(("cbCase"));
527 cbCase->setToolTip(tr("Enables case sensitive search."));
528 cbCase->setChecked(true);
529
530 gridLayout1->addWidget(cbCase, 0, 0, 1, 1);
531
532 gridLayout->addWidget(frame_6, 0, 6, 2, 2, Qt::AlignTop);
533
534 // connect by name
535 QMetaObject::connectSlotsByName(this);
536
537 // set texts
538 leFind->setToolTip(tr("Text or pattern to search for"));
539 bNext->setToolTip(tr("Find next occurrence"));
540 bPrevious->setToolTip(tr("Find previous occurrence"));
541
542 label->setText(tr(" Find :"));
543 label->setMinimumWidth(label->sizeHint().width());
544 cbCase->setText(tr("Case"));
545 cbCase->setMinimumWidth(cbCase->sizeHint().width());
546
547 minimum_width = frame_2->sizeHint().width() + leFind->sizeHint().width() + 2 * bNext->sizeHint().width() + 5 * hboxLayout->spacing();
548 //;
549
550 CONFIG_DECLARE_OPTION_WITH_OBJECT(ConfigManagerInterface::getInstance(), bool, caseConfig, false, "Preview/Search Case Sensitive", cbCase);
551
552 leFind->installEventFilter(this);
553
554 listOfWidget << cbCase;
555 }
556
557 QString PDFBaseSearchDock::getSearchText() const
558 {
559 return leFind->text();
560 }
561
562 void PDFBaseSearchDock::setSearchText(QString text)
563 {
564 leFind->setText(text);
565 }
566
567 bool PDFBaseSearchDock::hasFlagCaseSensitive() const
568 {
569 return cbCase->isChecked();
570 }
571
572 void PDFBaseSearchDock::setFocus()
573 {
574 leFind->setFocus();
575 leFind->selectAll();
576 }
577
578 void PDFBaseSearchDock::resizeEvent(QResizeEvent *e)
579 {
580 int w = e->size().width();
581 w = w - minimum_width; // remaining space
582 int row = 0;
583 int col = 0;
584 int remaining_space = w;
585 foreach (QWidget *wdg, listOfWidget) {
586 remaining_space = remaining_space - wdg->minimumWidth();
587 if (remaining_space > 0) {
588 gridLayout1->addWidget(wdg, row, col, 1, 1);
589 col++;
590 } else {
591 col = 0;
592 row++;
593 gridLayout1->addWidget(wdg, row, col, 1, 1);
594 col++;
595 remaining_space = w - wdg->minimumWidth();
596 }
597 }
598 QDockWidget::resizeEvent(e);
599 }
600
601 bool PDFBaseSearchDock::eventFilter(QObject *o, QEvent *e)
602 {
603 if ( o == leFind) {
604 int kc;
605 switch ( e->type() ) {
606 case QEvent::KeyPress :
607
608 kc = static_cast<QKeyEvent *>(e)->key();
609
610 if ( (kc == Qt::Key_Enter) || (kc == Qt::Key_Return) )
611 emit search(Qt::ShiftModifier & static_cast<QKeyEvent *>(e)->modifiers(), false);
612 else if ( kc == Qt::Key_Escape)
613 close();
614 break;
615
616 default:
617 break;
618 }
619 }
620
621 return QWidget::eventFilter(o, e);
622 }
623
624 void PDFBaseSearchDock::on_leFind_textEdited(const QString &)
625 {
626 emit search(false, true);
627 }
628
629 void PDFBaseSearchDock::on_bNext_clicked()
630 {
631 emit search(false, false);
632 }
633
634 void PDFBaseSearchDock::on_bPrevious_clicked()
635 {
636 emit search(true, false);
637 }
638
639
640 PDFSearchDock::PDFSearchDock(PDFDocument *doc): PDFBaseSearchDock(doc)
641 {
642 cbWords = new QCheckBox(this);
643 cbWords->setObjectName("cbWords");
644 cbWords->setText(tr("Words"));
645 cbWords->setToolTip(tr("Only searches for whole words."));
646 CONFIG_DECLARE_OPTION_WITH_OBJECT(ConfigManagerInterface::getInstance(), bool, wordConfig, false, "Preview/Whole Words", cbWords);
647
648 gridLayout1->addWidget(cbWords, 0, 2, 1, 1);
649
650 cbSync = new QCheckBox(this);
651 cbSync->setObjectName("cbSync");
652 cbSync->setText(tr("Sync"));
653 cbSync->setToolTip(tr("Synchronize editor when jumping to search results."));
654 CONFIG_DECLARE_OPTION_WITH_OBJECT(ConfigManagerInterface::getInstance(), bool, syncConfig, true, "Preview/Search Sync", cbSync);
655
656 gridLayout1->addWidget(cbSync, 0, 3, 1, 1);
657
658 listOfWidget << cbWords << cbSync;
659 }
660
661 bool PDFSearchDock::hasFlagWholeWords() const
662 {
663 return cbWords->isChecked();
664 }
665
666 bool PDFSearchDock::hasFlagSync() const
667 {
668 return cbSync->isChecked();
669 }
670
671
672 //////////////// SCROLL AREA ////////////////
673
674 PDFScrollArea::PDFScrollArea(QWidget *parent)
675 : QAbstractScrollArea(parent), continuous(true), pdf(nullptr), updateWidgetPositionStackWatch(0), onResizeStackWatch(0)
676 {
677 viewport()->setBackgroundRole(QPalette::NoRole);
678 viewport()->setAttribute(Qt::WA_AcceptTouchEvents, true);
679 verticalScrollBar()->setSingleStep(20);
680 horizontalScrollBar()->setSingleStep(20);
681 setFocusPolicy(Qt::StrongFocus);
682 }
683
684 void PDFScrollArea::setPDFWidget(PDFWidget *widget)
685 {
686 //from qt
687 if (pdf == widget) return;
688 delete pdf;
689 pdf = nullptr;
690 horizontalScrollBar()->setValue(0);
691 verticalScrollBar()->setValue(0);
692 if (widget->parentWidget() != viewport())
693 widget->setParent(viewport());
694 if (!widget->testAttribute(Qt::WA_Resized))
695 widget->resize(widget->sizeHint());
696 pdf = widget;
697 pdf->setAutoFillBackground(true);
698 pdf->installEventFilter(this);
699 updateScrollBars();
700 pdf->show();
701
702 }
703
704 void PDFScrollArea::ensureVisible(int x, int y, int xmargin, int ymargin)
705 {
706 int logicalX = QStyle::visualPos(layoutDirection(), viewport()->rect(), QPoint(x, y)).x();
707
708 if (logicalX - xmargin < horizontalScrollBar()->value()) {
709 horizontalScrollBar()->setValue(qMax(0, logicalX - xmargin));
710 } else if (logicalX > horizontalScrollBar()->value() + viewport()->width() - xmargin) {
711 horizontalScrollBar()->setValue(qMin(logicalX - viewport()->width() + xmargin, horizontalScrollBar()->maximum()));
712 }
713
714 if (continuous) y += pdf->gridRowHeight() * ((pdf->getPageIndex() + pdf->getPageOffset()) / pdf->gridCols());
715
716 if (y - ymargin < verticalScrollBar()->value()) {
717 verticalScrollBar()->setValue(qMax(0, y - ymargin));
718 } else if (y > verticalScrollBar()->value() + viewport()->height() - ymargin) {
719 verticalScrollBar()->setValue(qMin(y - viewport()->height() + ymargin, verticalScrollBar()->maximum()));
720 }
721 }
722
723 void PDFScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy)
724 {
725 if (continuous) QAbstractScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
726 else QAbstractScrollArea::setVerticalScrollBarPolicy(policy);
727 }
728
729 PDFScrollArea::~PDFScrollArea()
730 {
731 }
732
733 void PDFScrollArea::setContinuous(bool cont)
734 {
735 Q_ASSERT(pdf);
736 if (cont == continuous) return;
737 continuous = cont;
738 if (!cont) pdf->setGridSize(pdf->gridCols(), 1);
739 else {
740 int page = pdf->getPageIndex();
741 resizeEvent(nullptr);
742 goToPage(page, false);
743 }
744 }
745
746 void PDFScrollArea::goToPage(int page, bool sync)
747 {
748 if (continuous) {
749 int rowHeight = pdf->gridRowHeight();
750 verticalScrollBar()->setValue((page / pdf->gridCols()) * rowHeight);
751 } else pdf->goToPageDirect(page, sync);
752 }
753
754 void PDFScrollArea::ensureVisiblePageAbsolutePos(int page, const QPointF &pos, int xmargin, int ymargin)
755 {
756 Q_ASSERT(pdf);
757 if (!pdf || page < 0 || page >= pdf->realNumPages()) return;
758 if (pdf->pageRect(page).isNull()) goToPage(page); // pageRect is null if the page is not displayed.
759 QPoint scaled = (pdf->totalScaleFactor() * pos).toPoint() + pdf->pageRect(page).topLeft();
760 ensureVisible(scaled.x(), scaled.y(), xmargin, ymargin);
761 }
762
763 bool PDFScrollArea::event(QEvent *e)
764 {
765 if (e->type() == QEvent::StyleChange || e->type() == QEvent::LayoutRequest) {
766 updateScrollBars();
767 }
768 /* #ifdef QT_KEYPAD_NAVIGATION
769 else if (QApplication::keypadNavigationEnabled()) {
770 if (e->type() == QEvent::Show)
771 QApplication::instance()->installEventFilter(this);
772 else if (e->type() == QEvent::Hide)
773 QApplication::instance()->removeEventFilter(this);
774 }
775 #endif*/
776 return QAbstractScrollArea::event(e);
777 }
778
779 bool PDFScrollArea::eventFilter(QObject *o, QEvent *e)
780 {
781 if (onResizeStackWatch < 3 && o == pdf && e->type() == QEvent::Resize) {
782 onResizeStackWatch++;
783 if (continuous)
784 pdf->setGridSize(pdf->gridCols(), height() / pdf->gridRowHeight() + 2);
785 updateScrollBars();
786 onResizeStackWatch--;
787 }
788
789 return false;
790 }
791
792 void PDFScrollArea::wheelEvent(QWheelEvent *e)
793 {
794 if (pdf){// && !getContinuous()) {
795 pdf->wheelEvent(e);
796 return;
797 }
798 QAbstractScrollArea::wheelEvent(e);
799 }
800
801 void
802 PDFScrollArea::resizeEvent(QResizeEvent *)
803 {
804 Q_ASSERT(pdf);
805 if (continuous) {
806 pdf->setGridSize(pdf->gridCols(), height() / pdf->gridRowHeight() + 2, true);
807 pdf->reloadPage(false);
808 }
809 emit resized();
810 updateScrollBars();
811 }
812
813 void PDFScrollArea::scrollContentsBy(int, int)
814 {
815 Q_ASSERT(pdf);
816 updateWidgetPosition();
817 }
818
819 void PDFScrollArea::updateWidgetPosition()
820 {
821 Q_ASSERT(pdf);
822 if (updateWidgetPositionStackWatch >= 3) return;
823 updateWidgetPositionStackWatch++;
824 Qt::LayoutDirection dir = layoutDirection();
825 QScrollBar *hbar = horizontalScrollBar(), *vbar = verticalScrollBar();
826 if (!continuous) {
827 //from qt
828 QRect scrolled = QStyle::visualRect(dir, viewport()->rect(), QRect(QPoint(-hbar->value(), -vbar->value()), pdf->size()));
829 QRect aligned = QStyle::alignedRect(dir, Qt::AlignCenter, pdf->size(), viewport()->rect());
830 pdf->move(pdf->width() < viewport()->width() ? aligned.x() : scrolled.x(),
831 pdf->height() < viewport()->height() ? aligned.y() : scrolled.y());
832 } else {
833 int rowHeight = pdf->gridRowHeight();
834 QRect scrolled = QStyle::visualRect(dir, viewport()->rect(), QRect(QPoint(-hbar->value(), -(vbar->value() % rowHeight)), pdf->size()));
835 QRect aligned = QStyle::alignedRect(dir, Qt::AlignCenter, pdf->size(), viewport()->rect());
836 pdf->move(pdf->width() < viewport()->width() ? aligned.x() : scrolled.x(),
837 pdf->height() < viewport()->height() ? aligned.y() : scrolled.y());
838 int pos = vbar->value();
839 pdf->goToPageDirect((pos / rowHeight)*pdf->gridCols() , true);
840 }
841 updateWidgetPositionStackWatch--;
842 pdf->updateStatusBar(); //need to update page count when a new page is scrolled in visible area
843 }
844
845 void PDFScrollArea::updateScrollBars()
846 {
847 Q_ASSERT(pdf);
848 QScrollBar *hbar = horizontalScrollBar(), *vbar = verticalScrollBar();
849
850 QSize p = viewport()->size();
851 QSize m = maximumViewportSize();
852
853 if (m.expandedTo(pdf->size()) == m)
854 p = m; // no scroll bars needed
855
856 QSize v = pdf->size();
857
858 hbar->setRange(0, v.width() - p.width());
859 hbar->setPageStep(p.width());
860 if (!continuous) {
861 vbar->setRange(0, v.height() - p.height());
862 } else {
863 int totalRows = ((pdf->pseudoNumPages() + pdf->gridCols() - 1) / pdf->gridCols());
864 vbar->setRange(0, totalRows * pdf->gridRowHeight() - pdf->gridBorder() - p.height() - 1); // -1 is heuristic to prevent activation of the scrollbar in case of fit-page and one-page documents (might be this should be corrected in another place)
865 }
866
867 if (pdf->getScaleOption() == kFitWindow) {
868 vbar->setPageStep(pdf->gridRowHeight()); // use grid height instead of viewport height here to move exactly one page
869 } else {
870 vbar->setPageStep(p.height());
871 }
872 updateWidgetPosition();
873 }
874
875
876 //////////////// Overview ////////////////
877
878 PDFOverviewDock::PDFOverviewDock(PDFDocument *doc)
879 : PDFDock(doc), toGenerate(0)
880 {
881 setObjectName("overview");
882 setWindowTitle(getTitle());
883 list = new PDFDockListView(this);
884 list->setViewMode(QListView::IconMode);
885 list->setIconSize(QSize(128, 128));
886 list->setMovement(QListView::Static);
887 list->setSpacing(12);
888 list->setBackgroundRole(QPalette::Mid);
889 list->setLayoutMode(QListView::Batched);
890 list->setBatchSize(10);
891 list->setUniformItemSizes(true); //necessary to prevent it from rendering all pages
892 list->setModel(new PDFOverviewModel());
893 setWidget(list);
894 dontFollow = false;
895 }
896
897 PDFOverviewDock::~PDFOverviewDock()
898 {
899 }
900
901 void PDFOverviewDock::changeLanguage()
902 {
903 PDFDock::changeLanguage();
904 if (filled)
905 fillInfo();
906 }
907
908 void PDFOverviewDock::fillInfo()
909 {
910 qobject_cast<PDFOverviewModel *>(list->model())->setDocument(document);
911 connect(list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(followTocSelection()));
912 }
913
914 void PDFOverviewDock::documentClosed()
915 {
916 qobject_cast<PDFOverviewModel *>(list->model())->setDocument(nullptr);
917 PDFDock::documentClosed();
918 }
919
920 void PDFOverviewDock::followTocSelection()
921 {
922 if (dontFollow) return;
923
924 QModelIndex mi = list->currentIndex();
925 if (mi.isValid()) {
926 document->goToPage(mi.row());
927 }
928 }
929
930 void PDFOverviewDock::pageChanged(int page)
931 {
932 dontFollow = true;
933 list->setCurrentIndex(list->model()->index(page, 0));
934 list->scrollTo(list->currentIndex());
935 dontFollow = false;
936 }
937
938
939 PDFClockDock::PDFClockDock(PDFDocument *parent): PDFDock(parent), pageCount(0)
940 {
941 setObjectName("clock");
942 setWindowTitle(getTitle());
943 start = QDateTime::currentDateTime();
944 end = QDateTime::currentDateTime() .addSecs(60 * 60);
945 timer = new QTimer(this);
946 connect(timer, SIGNAL(timeout()), SLOT(onTimer()));
947 timer->start(2000);
948
949 setContextMenuPolicy(Qt::ActionsContextMenu);
950 addAction(tr("Set Interval..."), SLOT(setInterval()));
951 addAction(tr("Set Page Count..."), SLOT(setPageCount()));
952 addAction(tr("Restart"), SLOT(restart()));
953 }
954
955 PDFClockDock::~PDFClockDock()
956 {
957
958 }
959
960 void PDFClockDock::fillInfo()
961 {
962
963 }
964
965 QString PDFClockDock::getTitle()
966 {
967 return tr("Clock");
968 }
969
970 void PDFClockDock::onTimer()
971 {
972 if (isHidden()) return;
973 update();
974 }
975
976 void PDFClockDock::restart()
977 {
978 qint64 delta = start.secsTo(end);
979 start = QDateTime::currentDateTime();
980 end = start.addSecs(delta);
981 update();
982 }
983
984 void PDFClockDock::setInterval()
985 {
986 int i = (start.secsTo(end) + 30) / 60;
987 QString s = start.time().toString();
988 UniversalInputDialog d;
989 d.addVariable(&s, tr("Start time"));
990 QSpinBox* sb = d.addVariable(&i, tr("New clock interval (in minutes)"));
991 sb->setMinimum(1);
992 sb->setMaximum(9999);
993
994 if (!d.exec()) return;
995 start = QDateTime::currentDateTime();
996 start.setTime( QTime::fromString(s) );
997 end = start.addSecs(i * 60);
998 }
999
1000 void PDFClockDock::setInterval(int interval)
1001 {
1002 start = QDateTime::currentDateTime();
1003 end = start.addSecs(interval * 60);
1004 update();
1005 }
1006
1007 void PDFClockDock::setPageCount(){
1008 UniversalInputDialog d;
1009 QSpinBox* sb = d.addVariable(&pageCount, tr("Page count (negative subtracts)"));
1010 sb->setMinimum(-99999);
1011 sb->setMaximum(99999);
1012
1013 if (!d.exec()) return;
1014 }
1015
1016 void PDFClockDock::paintEvent(QPaintEvent *event)
1017 {
1018 if (!document || document->popplerDoc().isNull() || !document->widget()) {
1019 PDFDock::paintEvent(event);
1020 return;
1021 }
1022 QBrush backgroundBrush = palette().window(); //QColor::fromRgb(96, 96, 96));
1023 QColor textColor = palette().text().color();
1024 if (style()->property("manhattanstyle").toBool()) {
1025 backgroundBrush = QBrush(QColor::fromRgb(96, 96, 96));
1026 textColor = QColor(Qt::white);
1027 }
1028 QColor timeBarColor = QColor::fromRgb(175, 0, 175);
1029 QColor pagesBarColor = QColor::fromRgb(0, 122, 217);
1030
1031 QPainter p(this);
1032 QRect r = rect();
1033 p.fillRect(r, backgroundBrush);
1034
1035 // text
1036 qint64 pendingSeconds = start.secsTo(QDateTime::currentDateTime());
1037 qint64 remainingSeconds = QDateTime::currentDateTime().secsTo(end);
1038 QString text;
1039 if (pendingSeconds < 0)
1040 text = tr("wait");
1041 else if (remainingSeconds <= 90)
1042 text = tr("%1 sec").arg(qMax<qint64>(0, remainingSeconds));
1043 else
1044 text = tr("%1 min").arg((remainingSeconds + 30) / 60);
1045 QFont f = p.font();
1046 f.setPixelSize(r.height());
1047 p.setFont(f);
1048 p.setPen(textColor);
1049 int labelWidth = UtilsUi::getFmWidth(p.fontMetrics(), "9999 min");
1050 QRect textRect = rect();
1051 textRect.setWidth(labelWidth);
1052 p.drawText(textRect, Qt::AlignHCenter | Qt::AlignVCenter, text);
1053
1054 // progress bar
1055 r.adjust(labelWidth, 0, 0, 0);
1056 p.fillRect(r.x(), 0, qMax<int>(0, r.width() * pendingSeconds / qMax(qint64(start.secsTo(end)), qint64(1))), r.height() * 3 / 4, timeBarColor);
1057
1058 int effectivePageCount = pageCount > 0 ? pageCount : document->widget()->realNumPages() + pageCount;
1059 p.fillRect(r.x(), r.height() * 3 / 4, r.width() * document->widget()->getPageIndex() / qMax(1, effectivePageCount - 1), r.height() / 4, pagesBarColor);
1060 }
1061
1062
1063 MessageFrame::MessageFrame(QWidget *parent) : QFrame(parent), label(nullptr)
1064 {
1065 QHBoxLayout *layout = new QHBoxLayout();
1066 setLayout(layout);
1067 label = new QLabel("test");
1068 label->setWordWrap(true);
1069 layout->addWidget(label);
1070 layout->setContentsMargins(2, 2, 2, 2);
1071
1072 setStyleSheet("MessageFrame {background: #FFFBBF}\n"
1073 "QLabel {color: black}"); // only style the frame and the labels. Buttons remain unstyled.
1074 setVisible(false);
1075 }
1076
1077 /*
1078 * Displays the message frame with the given text.
1079 *
1080 * actions: For each action, a button with the text of the action is inserted into the message panel.
1081 * The action is triggered when the button is pressed.
1082 * The button takes ownership of the action.
1083 */
1084 void MessageFrame::showText(const QString &text, QList<QAction *> actions)
1085 {
1086 label->setText(text);
1087
1088 foreach (QPushButton *bt, buttons)
1089 delete bt;
1090 buttons.clear();
1091
1092 foreach (QAction *act, actions) {
1093 QPushButton *bt = new QPushButton(act->text());
1094 act->setParent(bt);
1095 bt->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
1096 buttons.append(bt);
1097 connect(bt, SIGNAL(clicked()), act, SIGNAL(triggered()));
1098 layout()->addWidget(bt);
1099 }
1100 show();
1101 }
1102
1103 #endif
1104