/* This is part of TeXworks, an environment for working with TeX documents Copyright (C) 2007-2010 Jonathan Kew This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . For links to further information, or to contact the author, see . */ #ifndef NO_POPPLER_PREVIEW #include "mostQtHeaders.h" // Based on code by Pino Toscano from Poppler / qt4 / Demos, released under GPL 2 or later /*! \class PDFDock * \file PDFDocks.cpp * \brief docking panel for PDF viewer * * This class provides the base functions for dockable side-panel. * It is used for additional information like table of contents, preview images, etc. * * \see PDFDocument */ #include "PDFDocks.h" #include "PDFDocument.h" #include "universalinputdialog.h" /*! * \brief constructor * \param doc actual pdf document which is displayed */ PDFDock::PDFDock(PDFDocument *doc) : QDockWidget("", doc), document(doc), filled(false) { connect(this, SIGNAL(visibilityChanged(bool)), SLOT(myVisibilityChanged(bool))); //TODO: connect(TWApp::instance(), SIGNAL(updatedTranslators()), this, SLOT(changeLanguage())); } PDFDock::~PDFDock() { } void PDFDock::documentLoaded() { if (!isHidden()) { fillInfo(); filled = true; } } void PDFDock::documentClosed() { filled = false; } void PDFDock::pageChanged(int page) { Q_UNUSED(page) } void PDFDock::addAction(const QString& caption, const char* slot) { QAction *act = new QAction(caption, this); connect(act, SIGNAL(triggered()), slot); addAction(act); } void PDFDock::myVisibilityChanged(bool visible) { setWindowTitle(getTitle()); if (visible && document && !filled) { fillInfo(); filled = true; } } void PDFDock::changeLanguage() { setWindowTitle(getTitle()); } //////////////// OUTLINE //////////////// static void fillToc(const QDomNode &parent, QTreeWidget *tree, QTreeWidgetItem *parentItem) { QTreeWidgetItem *newitem = nullptr; for (QDomNode node = parent.firstChild(); !node.isNull(); node = node.nextSibling()) { QDomElement e = node.toElement(); if (!parentItem) newitem = new QTreeWidgetItem(tree, newitem); else newitem = new QTreeWidgetItem(parentItem, newitem); newitem->setText(0, e.tagName()); bool isOpen = false; if (e.hasAttribute("Open")) isOpen = QVariant(e.attribute("Open")).toBool(); if (isOpen) tree->expandItem(newitem); if (e.hasAttribute("DestinationName")) newitem->setText(1, e.attribute("DestinationName")); if (e.hasChildNodes()) fillToc(node, tree, newitem); } } #if POPPLER_VERSION_MAJOR>0 || POPPLER_VERSION_MINOR>=74 static void fillOutline(const QVectortoc, QTreeWidget *tree, QTreeWidgetItem *parentItem) { QTreeWidgetItem *newitem = nullptr; foreach(Poppler::OutlineItem e,toc) { if (!parentItem) newitem = new QTreeWidgetItem(tree, newitem); else newitem = new QTreeWidgetItem(parentItem, newitem); newitem->setText(0, e.name()); bool isOpen = e.isOpen(); if (isOpen) tree->expandItem(newitem); if (e.destination()){ newitem->setText(1, e.destination()->toString()); } if (e.hasChildren()) fillOutline(e.children(), tree, newitem); } } #endif /*! \class PDFOutlineDock * * \brief sidepanel for preview * * show page preview in the sidepanel * * the actual rendering is done in extra threads */ /*! * \brief constructor * \param doc */ PDFOutlineDock::PDFOutlineDock(PDFDocument *doc) : PDFDock(doc) { setObjectName("outline"); tree = new PDFDockTreeWidget(this); tree->setAlternatingRowColors(true); tree->header()->hide(); tree->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); setWidget(tree); setWindowTitle(getTitle()); } PDFOutlineDock::~PDFOutlineDock() { } void PDFOutlineDock::changeLanguage() { PDFDock::changeLanguage(); if (filled) fillInfo(); } void PDFOutlineDock::fillInfo() { tree->clear(); if (!document || document->popplerDoc().isNull()) return; #if POPPLER_VERSION_MAJOR>0 || POPPLER_VERSION_MINOR>=74 QVectortoc=document->popplerDoc()->outline(); if(!toc.isEmpty()){ fillOutline(toc, tree, nullptr); connect(tree, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(followTocSelection())); #else const QDomDocument *toc = document->popplerDoc()->toc(); if (toc) { fillToc(*toc, tree, nullptr); connect(tree, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this, SLOT(followTocSelection())); delete toc; #endif } else { QTreeWidgetItem *item = new QTreeWidgetItem(); item->setText(0, tr("No TOC")); item->setFlags(item->flags() & ~Qt::ItemIsEnabled); tree->addTopLevelItem(item); } } void PDFOutlineDock::documentClosed() { tree->clear(); PDFDock::documentClosed(); } void PDFOutlineDock::followTocSelection() { QList items = tree->selectedItems(); if (items.count() > 0) { QTreeWidgetItem *item = items.first(); QString dest = item->text(1); if (!dest.isEmpty()) document->goToDestination(dest); } } PDFDockTreeWidget::PDFDockTreeWidget(QWidget *parent) : QTreeWidget(parent) { } PDFDockTreeWidget::~PDFDockTreeWidget() { } QSize PDFDockTreeWidget::sizeHint() const { return QSize(120, 300); } //////////////// PDF INFO //////////////// PDFInfoDock::PDFInfoDock(PDFDocument *doc) : PDFDock(doc) { setObjectName("pdfinfo"); setWindowTitle(getTitle()); list = new PDFDockListWidget(this); list->setAlternatingRowColors(true); setWidget(list); } PDFInfoDock::~PDFInfoDock() { } void PDFInfoDock::fillInfo() { list->clear(); if (!document) return; QSharedPointer spDoc(document->popplerDoc()); if (spDoc.isNull()) return; const Poppler::Document *doc = spDoc.data(); QStringList keys = doc->infoKeys(); QStringList dateKeys; dateKeys << "CreationDate"; dateKeys << "ModDate"; int i = 0; foreach (const QString &date, dateKeys) { const int id = keys.indexOf(date); if (id != -1) { list->addItem(date + ":"); list->addItem(doc->date(date).toLocalTime().toString());//Qt::SystemLocaleDate)); TODO ++i; keys.removeAt(id); } } foreach (const QString &key, keys) { list->addItem(key + ":"); list->addItem(doc->info(key)); ++i; } } void PDFInfoDock::documentClosed() { list->clear(); PDFDock::documentClosed(); } PDFDockListView::PDFDockListView(QWidget *parent) : QListView(parent) { } QSize PDFDockListView::sizeHint() const { return QSize(200, 300); } PDFDockListWidget::PDFDockListWidget(QWidget *parent) : QListWidget(parent) { } QSize PDFDockListWidget::sizeHint() const { return QSize(200, 300); } PDFOverviewModel::PDFOverviewModel(QObject *parent) : QAbstractListModel(parent) { document = nullptr; cache.clear(); } int PDFOverviewModel::rowCount ( const QModelIndex &parent ) const { if (!document || document->popplerDoc().isNull()) return 0; if (parent.isValid()) return 0; if (!document->widget()) return 0; return document->widget()->realNumPages(); } QVariant PDFOverviewModel::data ( const QModelIndex &index, int role) const { if (!index.isValid() || index.column() != 0 || index.row() < 0 || index.row() >= document->widget()->realNumPages()) return QVariant(); switch (role) { case Qt::DisplayRole: return QString::number(index.row() + 1); case Qt::DecorationRole: while (index.row() >= cache.size()) cache << QPixmap(); if (cache[index.row()].isNull()) { const QObject *o = this; //TODO: get rid of const_cast cache[index.row()] = document->renderManager->renderToImage(index.row(), const_cast(o), "updateImage", -1, -1, -1, -1, -1, -1, false).scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation); } return cache[index.row()]; case Qt::BackgroundRole: return QColor(Qt::gray); } return QVariant(); } void PDFOverviewModel::setDocument(PDFDocument *doc) { beginResetModel(); document = doc; if (!doc) { endResetModel(); return; } if (!doc->widget() || document->popplerDoc().isNull()) document = nullptr; cache.clear(); endResetModel(); } void PDFOverviewModel::updateImage(const QPixmap &pm, int page) { if (!document || page < 0 || page >= cache.size()) return; cache[page] = pm.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation); emit dataChanged(index(page), index(page)); } //////////////// FONT LIST //////////////// PDFFontsDock::PDFFontsDock(PDFDocument *doc) : PDFDock(doc) , scannedFonts(false) { setObjectName("fonts"); setWindowTitle(getTitle()); table = new QTableWidget(this); #ifdef Q_OS_MAC /* don't do this on windows, as the font ends up too small */ QFont f(table->font()); f.setPointSize(f.pointSize() - 2); table->setFont(f); #endif table->setColumnCount(4); setHorizontalHeaderLabels(); table->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setAlternatingRowColors(true); table->setShowGrid(false); table->setSelectionBehavior(QAbstractItemView::SelectRows); table->verticalHeader()->hide(); table->horizontalHeader()->setStretchLastSection(true); table->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); setWidget(table); } PDFFontsDock::~PDFFontsDock() { } void PDFFontsDock::changeLanguage() { PDFDock::changeLanguage(); setHorizontalHeaderLabels(); if (filled) fillInfo(); } void PDFFontsDock::setHorizontalHeaderLabels() { if (table) table->setHorizontalHeaderLabels(QStringList() << tr("Name") << tr("Type") << tr("Subset") << tr("File")); } void PDFFontsDock::fillInfo() { if (!document) return; QSharedPointer spDoc(document->popplerDoc()); if (!scannedFonts) { fonts = spDoc->fonts(); scannedFonts = true; } table->clearContents(); table->setRowCount(0); table->setRowCount(fonts.count()); int i = 0; foreach (const Poppler::FontInfo &font, fonts) { if (font.name().isNull()) { table->setItem(i, 0, new QTableWidgetItem(tr("[none]"))); } else { table->setItem(i, 0, new QTableWidgetItem(font.name())); } table->setItem(i, 1, new QTableWidgetItem(font.typeName())); table->setItem(i, 2, new QTableWidgetItem(font.isSubset() ? tr("yes") : tr("no"))); table->setItem(i, 3, new QTableWidgetItem(font.isEmbedded() ? tr("[embedded]") : font.file())); ++i; } table->resizeColumnsToContents(); table->resizeRowsToContents(); } void PDFFontsDock::documentLoaded() { scannedFonts = false; fonts.clear(); PDFDock::documentLoaded(); } void PDFFontsDock::documentClosed() { scannedFonts = false; fonts.clear(); table->clear(); table->setRowCount(0); PDFDock::documentClosed(); } //////////////// SEARCH DOCK //////////////// PDFBaseSearchDock::PDFBaseSearchDock(PDFDocument *doc): QDockWidget(doc), document(doc) { // do it completely programatic setObjectName("search"); setWindowTitle(tr("Search")); //this->resize(801, 31); QWidget *tempWidget = new QWidget(this); setWidget(tempWidget); QGridLayout *gridLayout = new QGridLayout(tempWidget); gridLayout->setContentsMargins(-1, 4, -1, 4); QFrame *frame_2 = new QFrame(this); frame_2->setObjectName(("frame_2")); QSizePolicy sizePolicy1(QSizePolicy::Preferred, QSizePolicy::Preferred); frame_2->setSizePolicy(sizePolicy1); frame_2->setMinimumSize(QSize(0, 22)); frame_2->setFrameShape(QFrame::NoFrame); frame_2->setLineWidth(0); QHBoxLayout *hboxLayout = new QHBoxLayout(frame_2); hboxLayout->setObjectName(("hboxLayout")); hboxLayout->setContentsMargins(-1, 0, -1, 0); QSize buttonSize(22, 22); QLabel *label = new QLabel(frame_2); label->setObjectName(("label")); QSizePolicy sizePolicy3(QSizePolicy::Minimum, QSizePolicy::Preferred); sizePolicy3.setHorizontalStretch(0); sizePolicy3.setVerticalStretch(0); sizePolicy3.setHeightForWidth(label->sizePolicy().hasHeightForWidth()); label->setSizePolicy(sizePolicy3); label->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter); hboxLayout->addWidget(label); gridLayout->addWidget(frame_2, 0, 0, 1, 1); leFind = new QLineEdit(this); leFind->setClearButtonEnabled(true); leFind->setObjectName(("leFind")); QSizePolicy sizePolicy4(QSizePolicy::Preferred, QSizePolicy::Fixed); sizePolicy4.setHorizontalStretch(2); leFind->setSizePolicy(sizePolicy4); leFind->setMinimumSize(QSize(120, 22)); gridLayout->addWidget(leFind, 0, 1, 1, 1); bNext = new QToolButton(this); bNext->setObjectName(("bNext")); bNext->setMinimumSize(buttonSize); bNext->setMaximumSize(buttonSize); bNext->setIcon(getRealIcon("down")); gridLayout->addWidget(bNext, 0, 3, 1, 1); bPrevious = new QToolButton(this); bPrevious->setObjectName(("bPrevious")); bPrevious->setMinimumSize(buttonSize); bPrevious->setMaximumSize(buttonSize); bPrevious->setIcon(getRealIcon("up")); gridLayout->addWidget(bPrevious, 0, 4, 1, 1); QFrame *frame_6 = new QFrame(this); sizePolicy1.setHeightForWidth(frame_6->sizePolicy().hasHeightForWidth()); frame_6->setSizePolicy(sizePolicy1); frame_6->setFrameShape(QFrame::NoFrame); gridLayout1 = new QGridLayout(frame_6); gridLayout1->setContentsMargins(0, 0, 0, 0); cbCase = new QCheckBox(frame_6); cbCase->setObjectName(("cbCase")); cbCase->setToolTip(tr("Enables case sensitive search.")); cbCase->setChecked(true); gridLayout1->addWidget(cbCase, 0, 0, 1, 1); gridLayout->addWidget(frame_6, 0, 6, 2, 2, Qt::AlignTop); // connect by name QMetaObject::connectSlotsByName(this); // set texts leFind->setToolTip(tr("Text or pattern to search for")); bNext->setToolTip(tr("Find next occurrence")); bPrevious->setToolTip(tr("Find previous occurrence")); label->setText(tr(" Find :")); label->setMinimumWidth(label->sizeHint().width()); cbCase->setText(tr("Case")); cbCase->setMinimumWidth(cbCase->sizeHint().width()); minimum_width = frame_2->sizeHint().width() + leFind->sizeHint().width() + 2 * bNext->sizeHint().width() + 5 * hboxLayout->spacing(); //; CONFIG_DECLARE_OPTION_WITH_OBJECT(ConfigManagerInterface::getInstance(), bool, caseConfig, false, "Preview/Search Case Sensitive", cbCase); leFind->installEventFilter(this); listOfWidget << cbCase; } QString PDFBaseSearchDock::getSearchText() const { return leFind->text(); } void PDFBaseSearchDock::setSearchText(QString text) { leFind->setText(text); } bool PDFBaseSearchDock::hasFlagCaseSensitive() const { return cbCase->isChecked(); } void PDFBaseSearchDock::setFocus() { leFind->setFocus(); leFind->selectAll(); } void PDFBaseSearchDock::resizeEvent(QResizeEvent *e) { int w = e->size().width(); w = w - minimum_width; // remaining space int row = 0; int col = 0; int remaining_space = w; foreach (QWidget *wdg, listOfWidget) { remaining_space = remaining_space - wdg->minimumWidth(); if (remaining_space > 0) { gridLayout1->addWidget(wdg, row, col, 1, 1); col++; } else { col = 0; row++; gridLayout1->addWidget(wdg, row, col, 1, 1); col++; remaining_space = w - wdg->minimumWidth(); } } QDockWidget::resizeEvent(e); } bool PDFBaseSearchDock::eventFilter(QObject *o, QEvent *e) { if ( o == leFind) { int kc; switch ( e->type() ) { case QEvent::KeyPress : kc = static_cast(e)->key(); if ( (kc == Qt::Key_Enter) || (kc == Qt::Key_Return) ) emit search(Qt::ShiftModifier & static_cast(e)->modifiers(), false); else if ( kc == Qt::Key_Escape) close(); break; default: break; } } return QWidget::eventFilter(o, e); } void PDFBaseSearchDock::on_leFind_textEdited(const QString &) { emit search(false, true); } void PDFBaseSearchDock::on_bNext_clicked() { emit search(false, false); } void PDFBaseSearchDock::on_bPrevious_clicked() { emit search(true, false); } PDFSearchDock::PDFSearchDock(PDFDocument *doc): PDFBaseSearchDock(doc) { cbWords = new QCheckBox(this); cbWords->setObjectName("cbWords"); cbWords->setText(tr("Words")); cbWords->setToolTip(tr("Only searches for whole words.")); CONFIG_DECLARE_OPTION_WITH_OBJECT(ConfigManagerInterface::getInstance(), bool, wordConfig, false, "Preview/Whole Words", cbWords); gridLayout1->addWidget(cbWords, 0, 2, 1, 1); cbSync = new QCheckBox(this); cbSync->setObjectName("cbSync"); cbSync->setText(tr("Sync")); cbSync->setToolTip(tr("Synchronize editor when jumping to search results.")); CONFIG_DECLARE_OPTION_WITH_OBJECT(ConfigManagerInterface::getInstance(), bool, syncConfig, true, "Preview/Search Sync", cbSync); gridLayout1->addWidget(cbSync, 0, 3, 1, 1); listOfWidget << cbWords << cbSync; } bool PDFSearchDock::hasFlagWholeWords() const { return cbWords->isChecked(); } bool PDFSearchDock::hasFlagSync() const { return cbSync->isChecked(); } //////////////// SCROLL AREA //////////////// PDFScrollArea::PDFScrollArea(QWidget *parent) : QAbstractScrollArea(parent), continuous(true), pdf(nullptr), updateWidgetPositionStackWatch(0), onResizeStackWatch(0) { viewport()->setBackgroundRole(QPalette::NoRole); viewport()->setAttribute(Qt::WA_AcceptTouchEvents, true); verticalScrollBar()->setSingleStep(20); horizontalScrollBar()->setSingleStep(20); setFocusPolicy(Qt::StrongFocus); } void PDFScrollArea::setPDFWidget(PDFWidget *widget) { //from qt if (pdf == widget) return; delete pdf; pdf = nullptr; horizontalScrollBar()->setValue(0); verticalScrollBar()->setValue(0); if (widget->parentWidget() != viewport()) widget->setParent(viewport()); if (!widget->testAttribute(Qt::WA_Resized)) widget->resize(widget->sizeHint()); pdf = widget; pdf->setAutoFillBackground(true); pdf->installEventFilter(this); updateScrollBars(); pdf->show(); } void PDFScrollArea::ensureVisible(int x, int y, int xmargin, int ymargin) { int logicalX = QStyle::visualPos(layoutDirection(), viewport()->rect(), QPoint(x, y)).x(); if (logicalX - xmargin < horizontalScrollBar()->value()) { horizontalScrollBar()->setValue(qMax(0, logicalX - xmargin)); } else if (logicalX > horizontalScrollBar()->value() + viewport()->width() - xmargin) { horizontalScrollBar()->setValue(qMin(logicalX - viewport()->width() + xmargin, horizontalScrollBar()->maximum())); } if (continuous) y += pdf->gridRowHeight() * ((pdf->getPageIndex() + pdf->getPageOffset()) / pdf->gridCols()); if (y - ymargin < verticalScrollBar()->value()) { verticalScrollBar()->setValue(qMax(0, y - ymargin)); } else if (y > verticalScrollBar()->value() + viewport()->height() - ymargin) { verticalScrollBar()->setValue(qMin(y - viewport()->height() + ymargin, verticalScrollBar()->maximum())); } } void PDFScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy) { if (continuous) QAbstractScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); else QAbstractScrollArea::setVerticalScrollBarPolicy(policy); } PDFScrollArea::~PDFScrollArea() { } void PDFScrollArea::setContinuous(bool cont) { Q_ASSERT(pdf); if (cont == continuous) return; continuous = cont; if (!cont) pdf->setGridSize(pdf->gridCols(), 1); else { int page = pdf->getPageIndex(); resizeEvent(nullptr); goToPage(page, false); } } void PDFScrollArea::goToPage(int page, bool sync) { if (continuous) { int rowHeight = pdf->gridRowHeight(); verticalScrollBar()->setValue((page / pdf->gridCols()) * rowHeight); } else pdf->goToPageDirect(page, sync); } void PDFScrollArea::ensureVisiblePageAbsolutePos(int page, const QPointF &pos, int xmargin, int ymargin) { Q_ASSERT(pdf); if (!pdf || page < 0 || page >= pdf->realNumPages()) return; if (pdf->pageRect(page).isNull()) goToPage(page); // pageRect is null if the page is not displayed. QPoint scaled = (pdf->totalScaleFactor() * pos).toPoint() + pdf->pageRect(page).topLeft(); ensureVisible(scaled.x(), scaled.y(), xmargin, ymargin); } bool PDFScrollArea::event(QEvent *e) { if (e->type() == QEvent::StyleChange || e->type() == QEvent::LayoutRequest) { updateScrollBars(); } /* #ifdef QT_KEYPAD_NAVIGATION else if (QApplication::keypadNavigationEnabled()) { if (e->type() == QEvent::Show) QApplication::instance()->installEventFilter(this); else if (e->type() == QEvent::Hide) QApplication::instance()->removeEventFilter(this); } #endif*/ return QAbstractScrollArea::event(e); } bool PDFScrollArea::eventFilter(QObject *o, QEvent *e) { if (onResizeStackWatch < 3 && o == pdf && e->type() == QEvent::Resize) { onResizeStackWatch++; if (continuous) pdf->setGridSize(pdf->gridCols(), height() / pdf->gridRowHeight() + 2); updateScrollBars(); onResizeStackWatch--; } return false; } void PDFScrollArea::wheelEvent(QWheelEvent *e) { if (pdf){// && !getContinuous()) { pdf->wheelEvent(e); return; } QAbstractScrollArea::wheelEvent(e); } void PDFScrollArea::resizeEvent(QResizeEvent *) { Q_ASSERT(pdf); if (continuous) { pdf->setGridSize(pdf->gridCols(), height() / pdf->gridRowHeight() + 2, true); pdf->reloadPage(false); } emit resized(); updateScrollBars(); } void PDFScrollArea::scrollContentsBy(int, int) { Q_ASSERT(pdf); updateWidgetPosition(); } void PDFScrollArea::updateWidgetPosition() { Q_ASSERT(pdf); if (updateWidgetPositionStackWatch >= 3) return; updateWidgetPositionStackWatch++; Qt::LayoutDirection dir = layoutDirection(); QScrollBar *hbar = horizontalScrollBar(), *vbar = verticalScrollBar(); if (!continuous) { //from qt QRect scrolled = QStyle::visualRect(dir, viewport()->rect(), QRect(QPoint(-hbar->value(), -vbar->value()), pdf->size())); QRect aligned = QStyle::alignedRect(dir, Qt::AlignCenter, pdf->size(), viewport()->rect()); pdf->move(pdf->width() < viewport()->width() ? aligned.x() : scrolled.x(), pdf->height() < viewport()->height() ? aligned.y() : scrolled.y()); } else { int rowHeight = pdf->gridRowHeight(); QRect scrolled = QStyle::visualRect(dir, viewport()->rect(), QRect(QPoint(-hbar->value(), -(vbar->value() % rowHeight)), pdf->size())); QRect aligned = QStyle::alignedRect(dir, Qt::AlignCenter, pdf->size(), viewport()->rect()); pdf->move(pdf->width() < viewport()->width() ? aligned.x() : scrolled.x(), pdf->height() < viewport()->height() ? aligned.y() : scrolled.y()); int pos = vbar->value(); pdf->goToPageDirect((pos / rowHeight)*pdf->gridCols() , true); } updateWidgetPositionStackWatch--; pdf->updateStatusBar(); //need to update page count when a new page is scrolled in visible area } void PDFScrollArea::updateScrollBars() { Q_ASSERT(pdf); QScrollBar *hbar = horizontalScrollBar(), *vbar = verticalScrollBar(); QSize p = viewport()->size(); QSize m = maximumViewportSize(); if (m.expandedTo(pdf->size()) == m) p = m; // no scroll bars needed QSize v = pdf->size(); hbar->setRange(0, v.width() - p.width()); hbar->setPageStep(p.width()); if (!continuous) { vbar->setRange(0, v.height() - p.height()); } else { int totalRows = ((pdf->pseudoNumPages() + pdf->gridCols() - 1) / pdf->gridCols()); 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) } if (pdf->getScaleOption() == kFitWindow) { vbar->setPageStep(pdf->gridRowHeight()); // use grid height instead of viewport height here to move exactly one page } else { vbar->setPageStep(p.height()); } updateWidgetPosition(); } //////////////// Overview //////////////// PDFOverviewDock::PDFOverviewDock(PDFDocument *doc) : PDFDock(doc), toGenerate(0) { setObjectName("overview"); setWindowTitle(getTitle()); list = new PDFDockListView(this); list->setViewMode(QListView::IconMode); list->setIconSize(QSize(128, 128)); list->setMovement(QListView::Static); list->setSpacing(12); list->setBackgroundRole(QPalette::Mid); list->setLayoutMode(QListView::Batched); list->setBatchSize(10); list->setUniformItemSizes(true); //necessary to prevent it from rendering all pages list->setModel(new PDFOverviewModel()); setWidget(list); dontFollow = false; } PDFOverviewDock::~PDFOverviewDock() { } void PDFOverviewDock::changeLanguage() { PDFDock::changeLanguage(); if (filled) fillInfo(); } void PDFOverviewDock::fillInfo() { qobject_cast(list->model())->setDocument(document); connect(list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(followTocSelection())); } void PDFOverviewDock::documentClosed() { qobject_cast(list->model())->setDocument(nullptr); PDFDock::documentClosed(); } void PDFOverviewDock::followTocSelection() { if (dontFollow) return; QModelIndex mi = list->currentIndex(); if (mi.isValid()) { document->goToPage(mi.row()); } } void PDFOverviewDock::pageChanged(int page) { dontFollow = true; list->setCurrentIndex(list->model()->index(page, 0)); list->scrollTo(list->currentIndex()); dontFollow = false; } PDFClockDock::PDFClockDock(PDFDocument *parent): PDFDock(parent), pageCount(0) { setObjectName("clock"); setWindowTitle(getTitle()); start = QDateTime::currentDateTime(); end = QDateTime::currentDateTime() .addSecs(60 * 60); timer = new QTimer(this); connect(timer, SIGNAL(timeout()), SLOT(onTimer())); timer->start(2000); setContextMenuPolicy(Qt::ActionsContextMenu); addAction(tr("Set Interval..."), SLOT(setInterval())); addAction(tr("Set Page Count..."), SLOT(setPageCount())); addAction(tr("Restart"), SLOT(restart())); } PDFClockDock::~PDFClockDock() { } void PDFClockDock::fillInfo() { } QString PDFClockDock::getTitle() { return tr("Clock"); } void PDFClockDock::onTimer() { if (isHidden()) return; update(); } void PDFClockDock::restart() { qint64 delta = start.secsTo(end); start = QDateTime::currentDateTime(); end = start.addSecs(delta); update(); } void PDFClockDock::setInterval() { int i = (start.secsTo(end) + 30) / 60; QString s = start.time().toString(); UniversalInputDialog d; d.addVariable(&s, tr("Start time")); QSpinBox* sb = d.addVariable(&i, tr("New clock interval (in minutes)")); sb->setMinimum(1); sb->setMaximum(9999); if (!d.exec()) return; start = QDateTime::currentDateTime(); start.setTime( QTime::fromString(s) ); end = start.addSecs(i * 60); } void PDFClockDock::setInterval(int interval) { start = QDateTime::currentDateTime(); end = start.addSecs(interval * 60); update(); } void PDFClockDock::setPageCount(){ UniversalInputDialog d; QSpinBox* sb = d.addVariable(&pageCount, tr("Page count (negative subtracts)")); sb->setMinimum(-99999); sb->setMaximum(99999); if (!d.exec()) return; } void PDFClockDock::paintEvent(QPaintEvent *event) { if (!document || document->popplerDoc().isNull() || !document->widget()) { PDFDock::paintEvent(event); return; } QBrush backgroundBrush = palette().window(); //QColor::fromRgb(96, 96, 96)); QColor textColor = palette().text().color(); if (style()->property("manhattanstyle").toBool()) { backgroundBrush = QBrush(QColor::fromRgb(96, 96, 96)); textColor = QColor(Qt::white); } QColor timeBarColor = QColor::fromRgb(175, 0, 175); QColor pagesBarColor = QColor::fromRgb(0, 122, 217); QPainter p(this); QRect r = rect(); p.fillRect(r, backgroundBrush); // text qint64 pendingSeconds = start.secsTo(QDateTime::currentDateTime()); qint64 remainingSeconds = QDateTime::currentDateTime().secsTo(end); QString text; if (pendingSeconds < 0) text = tr("wait"); else if (remainingSeconds <= 90) text = tr("%1 sec").arg(qMax(0, remainingSeconds)); else text = tr("%1 min").arg((remainingSeconds + 30) / 60); QFont f = p.font(); f.setPixelSize(r.height()); p.setFont(f); p.setPen(textColor); int labelWidth = UtilsUi::getFmWidth(p.fontMetrics(), "9999 min"); QRect textRect = rect(); textRect.setWidth(labelWidth); p.drawText(textRect, Qt::AlignHCenter | Qt::AlignVCenter, text); // progress bar r.adjust(labelWidth, 0, 0, 0); p.fillRect(r.x(), 0, qMax(0, r.width() * pendingSeconds / qMax(qint64(start.secsTo(end)), qint64(1))), r.height() * 3 / 4, timeBarColor); int effectivePageCount = pageCount > 0 ? pageCount : document->widget()->realNumPages() + pageCount; p.fillRect(r.x(), r.height() * 3 / 4, r.width() * document->widget()->getPageIndex() / qMax(1, effectivePageCount - 1), r.height() / 4, pagesBarColor); } MessageFrame::MessageFrame(QWidget *parent) : QFrame(parent), label(nullptr) { QHBoxLayout *layout = new QHBoxLayout(); setLayout(layout); label = new QLabel("test"); label->setWordWrap(true); layout->addWidget(label); layout->setContentsMargins(2, 2, 2, 2); setStyleSheet("MessageFrame {background: #FFFBBF}\n" "QLabel {color: black}"); // only style the frame and the labels. Buttons remain unstyled. setVisible(false); } /* * Displays the message frame with the given text. * * actions: For each action, a button with the text of the action is inserted into the message panel. * The action is triggered when the button is pressed. * The button takes ownership of the action. */ void MessageFrame::showText(const QString &text, QList actions) { label->setText(text); foreach (QPushButton *bt, buttons) delete bt; buttons.clear(); foreach (QAction *act, actions) { QPushButton *bt = new QPushButton(act->text()); act->setParent(bt); bt->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); buttons.append(bt); connect(bt, SIGNAL(clicked()), act, SIGNAL(triggered())); layout()->addWidget(bt); } show(); } #endif