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