diff --git a/generators/dvi/dviRenderer.cpp b/generators/dvi/dviRenderer.cpp index 452a5d8ed..bf4b5acef 100644 --- a/generators/dvi/dviRenderer.cpp +++ b/generators/dvi/dviRenderer.cpp @@ -1,770 +1,770 @@ // -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; c-brace-offset: 0; -*- // // Class: dviRenderer // // Class for rendering TeX DVI files. // Part of KDVI- A previewer for TeX DVI files. // // (C) 2001-2005 Stefan Kebekus // Distributed under the GPL // #include #include "debug_dvi.h" #include "dviRenderer.h" #include "dviFile.h" #include "dvisourcesplitter.h" #include "hyperlink.h" #include "debug_dvi.h" #include "prebookmark.h" #include "psgs.h" //#include "renderedDviPagePixmap.h" #include "dviPageInfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //------ now comes the dviRenderer class implementation ---------- dviRenderer::dviRenderer(bool useFontHinting) : dviFile(nullptr), font_pool(useFontHinting), resolutionInDPI(0), embedPS_progress(nullptr), embedPS_numOfProgressedFiles(0), shrinkfactor(3), source_href(nullptr), HTML_href(nullptr), editorCommand(QLatin1String("")), PostScriptOutPutString(nullptr), PS_interface(new ghostscript_interface), _postscript(true), line_boundary_encountered(false), word_boundary_encountered(false), current_page(0), penWidth_in_mInch(0), number_of_elements_in_path(0), currentlyDrawnPage(nullptr), m_eventLoop(nullptr), foreGroundPainter(nullptr), fontpoolLocateFontsDone(false) { #ifdef DEBUG_DVIRENDERER //qCDebug(OkularDviDebug) << "dviRenderer( parent=" << par << " )"; #endif connect(&font_pool, &fontPool::error, this, &dviRenderer::error); connect(&font_pool, &fontPool::warning, this, &dviRenderer::warning); connect(PS_interface, &ghostscript_interface::error, this, &dviRenderer::error); } dviRenderer::~dviRenderer() { #ifdef DEBUG_DVIRENDERER qCDebug(OkularDviDebug) << "~dviRenderer"; #endif QMutexLocker locker(&mutex); delete PS_interface; delete dviFile; } #if 0 void dviRenderer::setPrefs(bool flag_showPS, const QString &str_editorCommand, bool useFontHints ) { //QMutexLocker locker(&mutex); _postscript = flag_showPS; editorCommand = str_editorCommand; font_pool.setParameters( useFontHints ); } #endif //------ this function calls the dvi interpreter ---------- void dviRenderer::drawPage(RenderedDocumentPagePixmap* page) { #ifdef DEBUG_DVIRENDERER //qCDebug(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called, page number " << page->pageNumber; #endif // Paranoid safety checks if (page == nullptr) { qCCritical(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called with argument == 0" << endl; return; } // Paranoid safety checks if (page->pageNumber == 0) { qCCritical(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called for a documentPage with page number 0" << endl; return; } QMutexLocker locker(&mutex); if ( dviFile == nullptr ) { qCCritical(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called, but no dviFile class allocated." << endl; page->clear(); return; } if (page->pageNumber > dviFile->total_pages) { qCCritical(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called for a documentPage with page number " << page->pageNumber << " but the current dviFile has only " << dviFile->total_pages << " pages." << endl; return; } if ( dviFile->dvi_Data() == nullptr ) { qCCritical(OkularDviDebug) << "dviRenderer::drawPage(documentPage *) called, but no dviFile is loaded yet." << endl; page->clear(); return; } /* locateFonts() is here just once (if it has not been executed not been executed yet), so that it is possible to easily intercept the cancel signal (because for example the user tries to open another document); it would not have been possible (or more complicated) in case it was still in the document loading section. */ if ( !fontpoolLocateFontsDone ) { font_pool.locateFonts(); fontpoolLocateFontsDone = true; } double resolution = page->resolution; if (resolution != resolutionInDPI) setResolution(resolution); currentlyDrawnPage = page; shrinkfactor = 1200/resolutionInDPI; current_page = page->pageNumber-1; // Reset colors colorStack.clear(); globalColor = Qt::black; int pageWidth = page->width; int pageHeight = page->height; QImage img(pageWidth, pageHeight, QImage::Format_RGB32); foreGroundPainter = new QPainter(&img); if (foreGroundPainter != nullptr) { errorMsg.clear(); draw_page(); delete foreGroundPainter; foreGroundPainter = nullptr; } else { qCDebug(OkularDviDebug) << "painter creation failed."; } page->img = img; //page->setImage(img); // Postprocess hyperlinks // Without that, based on the way TeX draws certain characters like german "Umlaute", // some hyperlinks would be broken into two overlapping parts, in the middle of a word. QVector::iterator i = page->hyperLinkList.begin(); QVector::iterator j; while (i != page->hyperLinkList.end()) { // Iterator j always points to the element after i. j = i; j++; if (j == page->hyperLinkList.end()) break; Hyperlink& hi = *i; Hyperlink& hj = *j; bool merged = false; // Merge all hyperlinks that point to the same target, and have the same baseline. while (hi.linkText == hj.linkText && hi.baseline == hj.baseline) { merged = true; hi.box = hi.box.united(hj.box); j++; if (j == page->hyperLinkList.end()) break; hj = *j; } if (merged) { i = page->hyperLinkList.erase(++i, j); } else { i++; } } if (errorMsg.isEmpty() != true) { emit error(i18n("File corruption. %1", errorMsg), -1); errorMsg.clear(); currentlyDrawnPage = nullptr; return; } #if 0 // Tell the user (once) if the DVI file contains source specials // ... we don't want our great feature to go unnoticed. RenderedDviPagePixmap* currentDVIPage = dynamic_cast(currentlyDrawnPage); if (currentDVIPage) { if ((dviFile->sourceSpecialMarker == true) && (currentDVIPage->sourceHyperLinkList.size() > 0)) { dviFile->sourceSpecialMarker = false; // Show the dialog as soon as event processing is finished, and // the program is idle //FIXME //QTimer::singleShot( 0, this, SLOT(showThatSourceInformationIsPresent()) ); } } #endif currentlyDrawnPage = nullptr; } void dviRenderer::getText(RenderedDocumentPagePixmap* page) { bool postscriptBackup = _postscript; // Disable postscript-specials temporarily to speed up text extraction. _postscript = false; drawPage(page); _postscript = postscriptBackup; } /* void dviRenderer::showThatSourceInformationIsPresent() { // In principle, we should use a KMessagebox here, but we want to // add a button "Explain in more detail..." which opens the // Helpcenter. Thus, we practically re-implement the KMessagebox // here. Most of the code is stolen from there. // Check if the 'Don't show again' feature was used KConfig *config = KSharedConfig::openConfig(); KConfigGroup saver(config, "Notification Messages"); bool showMsg = config->readEntry( "KDVI-info_on_source_specials", true); if (showMsg) { KDialogBase dialog(i18n("KDVI: Information"), KDialogBase::Yes, KDialogBase::Yes, KDialogBase::Yes, parentWidget, "information", true, true, KStandardGuiItem::ok()); QWidget *topcontents = new QWidget (&dialog); QVBoxLayout *topcontentsVBoxLayout = new QVBoxLayout(topcontents); - topcontentsVBoxLayout->setMargin(0); + topcontentsVBoxLayout->setContentsMargins(0, 0, 0, 0); topcontentsVBoxLayout->setSpacing(KDialog::spacingHint()*2); topcontentsVBoxLayout->setMargin(KDialog::marginHint()*2); QWidget *contents = new QWidget(topcontents); topcontentsVBoxLayout->addWidget(contents); QHBoxLayout * lay = new QHBoxLayout(contents); lay->setSpacing(KDialog::spacingHint()*2); lay->addStretch(1); QLabel *label1 = new QLabel( contents); label1->setPixmap(QMessageBox::standardIcon(QMessageBox::Information)); lay->addWidget(label1); QLabel *label2 = new QLabel( i18n("This DVI file contains source file information. You may click into the text with the " "middle mouse button, and an editor will open the TeX-source file immediately."), contents); label2->setMinimumSize(label2->sizeHint()); lay->addWidget(label2); lay->addStretch(1); QSize extraSize = QSize(50,30); QCheckBox *checkbox = new QCheckBox(i18n("Do not show this message again"), topcontents); topcontentsVBoxLayout->addWidget(checkbox); extraSize = QSize(50,0); dialog.setHelpLinkText(i18n("Explain in more detail...")); dialog.setHelp("inverse-search", "kdvi"); dialog.enableLinkedHelp(true); dialog.setMainWidget(topcontents); dialog.enableButtonSeparator(false); dialog.incInitialSize( extraSize ); dialog.exec(); showMsg = !checkbox->isChecked(); if (!showMsg) { KConfigGroup saver(config, "Notification Messages"); config->writeEntry( "KDVI-info_on_source_specials", showMsg); } config->sync(); } } */ void dviRenderer::embedPostScript() { #ifdef DEBUG_DVIRENDERER qCDebug(OkularDviDebug) << "dviRenderer::embedPostScript()"; #endif if (!dviFile) return; /* embedPS_progress = new QProgressDialog(parentWidget); embedPS_progress->setWindowTitle(i18n("Embedding PostScript Files")); embedPS_progress->setLabelText(QString()); */ if (!embedPS_progress) return; embedPS_progress->setCancelButton(nullptr); embedPS_progress->setCancelButton(nullptr); embedPS_progress->setMinimumDuration(400); embedPS_progress->setMaximum(dviFile->numberOfExternalPSFiles); embedPS_progress->setValue(0); embedPS_numOfProgressedFiles = 0; quint16 currPageSav = current_page; errorMsg.clear(); for(current_page=0; current_page < dviFile->total_pages; current_page++) { if (current_page < dviFile->total_pages) { command_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page)]; end_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page+1)]; } else command_pointer = end_pointer = nullptr; memset((char *) &currinf.data, 0, sizeof(currinf.data)); currinf.fonttable = &(dviFile->tn_table); currinf._virtual = nullptr; prescan(&dviRenderer::prescan_embedPS); } delete embedPS_progress; embedPS_progress = nullptr; if (!errorMsg.isEmpty()) { emit warning(i18n("Not all PostScript files could be embedded into your document. %1", errorMsg), -1); errorMsg.clear(); } else { emit notice(i18n("All external PostScript files were embedded into your document."), -1); } // Prescan phase starts here #ifdef PERFORMANCE_MEASUREMENT //qCDebug(OkularDviDebug) << "Time elapsed till prescan phase starts " << performanceTimer.elapsed() << "ms"; //QTime preScanTimer; //preScanTimer.start(); #endif dviFile->numberOfExternalPSFiles = 0; prebookmarks.clear(); for(current_page=0; current_page < dviFile->total_pages; current_page++) { PostScriptOutPutString = new QString(); if (current_page < dviFile->total_pages) { command_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page)]; end_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page+1)]; } else command_pointer = end_pointer = nullptr; memset((char *) &currinf.data, 0, sizeof(currinf.data)); currinf.fonttable = &(dviFile->tn_table); currinf._virtual = nullptr; prescan(&dviRenderer::prescan_parseSpecials); if (!PostScriptOutPutString->isEmpty()) PS_interface->setPostScript(current_page, *PostScriptOutPutString); delete PostScriptOutPutString; } PostScriptOutPutString = nullptr; #ifdef PERFORMANCE_MEASUREMENT //qCDebug(OkularDviDebug) << "Time required for prescan phase: " << preScanTimer.restart() << "ms"; #endif current_page = currPageSav; _isModified = true; } bool dviRenderer::isValidFile(const QString& filename) const { QFile f(filename); if (!f.open(QIODevice::ReadOnly)) return false; unsigned char test[4]; if ( f.read( (char *)test,2)<2 || test[0] != 247 || test[1] != 2 ) return false; int n = f.size(); if ( n < 134 ) // Too short for a dvi file return false; f.seek( n-4 ); unsigned char trailer[4] = { 0xdf,0xdf,0xdf,0xdf }; if ( f.read( (char *)test, 4 )<4 || strncmp( (char *)test, (char *) trailer, 4 ) ) return false; // We suppose now that the dvi file is complete and OK return true; } bool dviRenderer::setFile(const QString &fname, const QUrl &base) { #ifdef DEBUG_DVIRENDERER qCDebug(OkularDviDebug) << "dviRenderer::setFile( fname='" << fname << "' )"; //, ref='" << ref << "', sourceMarker=" << sourceMarker << " )"; #endif //QMutexLocker lock(&mutex); QFileInfo fi(fname); QString filename = fi.absoluteFilePath(); // If fname is the empty string, then this means: "close". Delete // the dvifile and the pixmap. if (fname.isEmpty()) { // Delete DVI file delete dviFile; dviFile = nullptr; return true; } // Make sure the file actually exists. if (!fi.exists() || fi.isDir()) { emit error(i18n("The specified file '%1' does not exist.", filename), -1); return false; } QApplication::setOverrideCursor( Qt::WaitCursor ); dvifile *dviFile_new = new dvifile(filename, &font_pool); if ((dviFile == nullptr) || (dviFile->filename != filename)) dviFile_new->sourceSpecialMarker = true; else dviFile_new->sourceSpecialMarker = false; if ((dviFile_new->dvi_Data() == nullptr)||(dviFile_new->errorMsg.isEmpty() != true)) { QApplication::restoreOverrideCursor(); if (dviFile_new->errorMsg.isEmpty() != true) emit error(i18n("File corruption. %1", dviFile_new->errorMsg), -1); delete dviFile_new; return false; } delete dviFile; dviFile = dviFile_new; numPages = dviFile->total_pages; _isModified = false; baseURL = base; font_pool.setExtraSearchPath( fi.absolutePath() ); font_pool.setCMperDVIunit( dviFile->getCmPerDVIunit() ); // Extract PostScript from the DVI file, and store the PostScript // specials in PostScriptDirectory, and the headers in the // PostScriptHeaderString. PS_interface->clear(); // If the DVI file comes from a remote URL (e.g. downloaded from a // web server), we limit the PostScript files that can be accessed // by this file to the download directory, in order to limit the // possibilities of a denial of service attack. QString includePath; if (!baseURL.isLocalFile()) { includePath = filename; includePath.truncate(includePath.lastIndexOf(QLatin1Char('/'))); } PS_interface->setIncludePath(includePath); // We will also generate a list of hyperlink-anchors and source-file // anchors in the document. So declare the existing lists empty. //anchorList.clear(); sourceHyperLinkAnchors.clear(); //bookmarks.clear(); prebookmarks.clear(); if (dviFile->page_offset.isEmpty() == true) return false; // We should pre-scan the document now (to extract embedded, // PostScript, Hyperlinks, ets). // PRESCAN STARTS HERE #ifdef PERFORMANCE_MEASUREMENT //qCDebug(OkularDviDebug) << "Time elapsed till prescan phase starts " << performanceTimer.elapsed() << "ms"; //QTime preScanTimer; //preScanTimer.start(); #endif dviFile->numberOfExternalPSFiles = 0; quint16 currPageSav = current_page; prebookmarks.clear(); for(current_page=0; current_page < dviFile->total_pages; current_page++) { PostScriptOutPutString = new QString(); if (current_page < dviFile->total_pages) { command_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page)]; end_pointer = dviFile->dvi_Data() + dviFile->page_offset[int(current_page+1)]; } else command_pointer = end_pointer = nullptr; memset((char *) &currinf.data, 0, sizeof(currinf.data)); currinf.fonttable = &(dviFile->tn_table); currinf._virtual = nullptr; prescan(&dviRenderer::prescan_parseSpecials); if (!PostScriptOutPutString->isEmpty()) PS_interface->setPostScript(current_page, *PostScriptOutPutString); delete PostScriptOutPutString; } PostScriptOutPutString = nullptr; #if 0 // Generate the list of bookmarks bookmarks.clear(); Q3PtrStack stack; stack.setAutoDelete (false); QVector::iterator it; for( it = prebookmarks.begin(); it != prebookmarks.end(); ++it ) { Bookmark *bmk = new Bookmark((*it).title, findAnchor((*it).anchorName)); if (stack.isEmpty()) bookmarks.append(bmk); else { stack.top()->subordinateBookmarks.append(bmk); stack.remove(); } for(int i=0; i<(*it).noOfChildren; i++) stack.push(bmk); } prebookmarks.clear(); #endif #ifdef PERFORMANCE_MEASUREMENT //qCDebug(OkularDviDebug) << "Time required for prescan phase: " << preScanTimer.restart() << "ms"; #endif current_page = currPageSav; // PRESCAN ENDS HERE pageSizes.resize(0); if (dviFile->suggestedPageSize != nullptr) { // Fill the vector pageSizes with total_pages identical entries pageSizes.fill(*(dviFile->suggestedPageSize), dviFile->total_pages); } QApplication::restoreOverrideCursor(); return true; } Anchor dviRenderer::parseReference(const QString &reference) { QMutexLocker locker(&mutex); #ifdef DEBUG_DVIRENDERER qCCritical(OkularDviDebug) << "dviRenderer::parseReference( " << reference << " ) called" << endl; #endif if (dviFile == nullptr) return Anchor(); // case 1: The reference is a number, which we'll interpret as a // page number. bool ok; int page = reference.toInt ( &ok ); if (ok == true) { if (page < 0) page = 0; if (page > dviFile->total_pages) page = dviFile->total_pages; return Anchor(page, Length() ); } // case 2: The reference is of form "src:1111Filename", where "1111" // points to line number 1111 in the file "Filename". KDVI then // looks for source specials of the form "src:xxxxFilename", and // tries to find the special with the biggest xxxx if (reference.indexOf(QStringLiteral("src:"), 0, Qt::CaseInsensitive) == 0) { // Extract the file name and the numeral part from the reference string DVI_SourceFileSplitter splitter(reference, dviFile->filename); quint32 refLineNumber = splitter.line(); QString refFileName = splitter.filePath(); if (sourceHyperLinkAnchors.isEmpty()) { emit warning(i18n("You have asked Okular to locate the place in the DVI file which corresponds to " "line %1 in the TeX-file %2. It seems, however, that the DVI file " "does not contain the necessary source file information. ", refLineNumber, refFileName), -1); return Anchor(); } // Go through the list of source file anchors, and find the anchor // whose line number is the biggest among those that are smaller // than the refLineNumber. That way, the position in the DVI file // which is highlighted is always a little further up than the // position in the editor, e.g. if the DVI file contains // positional information at the beginning of every paragraph, // KDVI jumps to the beginning of the paragraph that the cursor is // in, and never to the next paragraph. If source file anchors for // the refFileName can be found, but none of their line numbers is // smaller than the refLineNumber, the reason is most likely, that // the cursor in the editor stands somewhere in the preamble of // the LaTeX file. In that case, we jump to the beginning of the // document. bool anchorForRefFileFound = false; // Flag that is set if source file anchors for the refFileName could be found at all QVector::iterator bestMatch = sourceHyperLinkAnchors.end(); QVector::iterator it; for( it = sourceHyperLinkAnchors.begin(); it != sourceHyperLinkAnchors.end(); ++it ) if (refFileName.trimmed() == it->fileName.trimmed() || refFileName.trimmed() == it->fileName.trimmed() + QStringLiteral(".tex") ) { anchorForRefFileFound = true; if ( (it->line <= refLineNumber) && ( (bestMatch == sourceHyperLinkAnchors.end()) || (it->line > bestMatch->line) ) ) bestMatch = it; } if (bestMatch != sourceHyperLinkAnchors.end()) return Anchor(bestMatch->page, bestMatch->distance_from_top); else if (anchorForRefFileFound == false) { emit warning(i18n("Okular was not able to locate the place in the DVI file which corresponds to " "line %1 in the TeX-file %2.", refLineNumber, refFileName), -1); } else return Anchor(); return Anchor(); } return Anchor(); } void dviRenderer::setResolution(double resolution_in_DPI) { // Ignore minute changes. The difference to the current value would // hardly be visible anyway. That saves a lot of re-painting, // e.g. when the user resizes the window, and a flickery mouse // changes the window size by 1 pixel all the time. if (fabs(resolutionInDPI-resolution_in_DPI) < 1) return; resolutionInDPI = resolution_in_DPI; // Pass the information on to the font pool. font_pool.setDisplayResolution( resolutionInDPI ); shrinkfactor = 1200/resolutionInDPI; return; } void dviRenderer::handleSRCLink(const QString &linkText, const QPoint& point, DocumentWidget *win) { Q_UNUSED( linkText ); Q_UNUSED( point ); Q_UNUSED( win ); #if 0 QExplicitlySharedDataPointer editor(new DVISourceEditor(*this, parentWidget, linkText, point, win)); if (editor->started()) editor_ = editor; #endif } QString dviRenderer::PDFencodingToQString(const QString& _pdfstring) { // This method locates special PDF characters in a string and // replaces them by UTF8. See Section 3.2.3 of the PDF reference // guide for information. QString pdfstring = _pdfstring; pdfstring = pdfstring.replace(QLatin1String("\\n"), QLatin1String("\n")); pdfstring = pdfstring.replace(QLatin1String("\\r"), QLatin1String("\n")); pdfstring = pdfstring.replace(QLatin1String("\\t"), QLatin1String("\t")); pdfstring = pdfstring.replace(QLatin1String("\\f"), QLatin1String("\f")); pdfstring = pdfstring.replace(QLatin1String("\\("), QLatin1String("(")); pdfstring = pdfstring.replace(QLatin1String("\\)"), QLatin1String(")")); pdfstring = pdfstring.replace(QLatin1String("\\\\"), QLatin1String("\\")); // Now replace octal character codes with the characters they encode int pos; QRegExp rx( QStringLiteral("(\\\\)(\\d\\d\\d)") ); // matches "\xyz" where x,y,z are numbers while((pos = rx.indexIn( pdfstring )) != -1) { pdfstring = pdfstring.replace(pos, 4, QChar(rx.cap(2).toInt(nullptr,8))); } rx.setPattern( QStringLiteral("(\\\\)(\\d\\d)") ); // matches "\xy" where x,y are numbers while((pos = rx.indexIn( pdfstring )) != -1) { pdfstring = pdfstring.replace(pos, 3, QChar(rx.cap(2).toInt(nullptr,8))); } rx.setPattern( QStringLiteral("(\\\\)(\\d)") ); // matches "\x" where x is a number while((pos = rx.indexIn( pdfstring )) != -1) { pdfstring = pdfstring.replace(pos, 4, QChar(rx.cap(2).toInt(nullptr,8))); } return pdfstring; } void dviRenderer::exportPDF() { /* QExplicitlySharedDataPointer exporter(new DVIExportToPDF(*this, parentWidget)); if (exporter->started()) all_exports_[exporter.data()] = exporter; */ } void dviRenderer::exportPS(const QString& fname, const QStringList& options, QPrinter* printer, QPrinter::Orientation orientation) { QExplicitlySharedDataPointer exporter(new DVIExportToPS(*this, fname, options, printer, font_pool.getUseFontHints(), orientation)); if (exporter->started()) all_exports_[exporter.data()] = exporter; } /* void dviRenderer::editor_finished(const DVISourceEditor*) { editor_.attach(0); } */ void dviRenderer::export_finished(const DVIExport* key) { typedef QMap > ExportMap; ExportMap::iterator it = all_exports_.find(key); if (it != all_exports_.end()) all_exports_.remove(key); } void dviRenderer::setEventLoop(QEventLoop *el) { if (el == nullptr) { delete m_eventLoop; m_eventLoop = nullptr; } else m_eventLoop = el; } diff --git a/ui/presentationwidget.cpp b/ui/presentationwidget.cpp index 0d299d165..a64309b86 100644 --- a/ui/presentationwidget.cpp +++ b/ui/presentationwidget.cpp @@ -1,2463 +1,2463 @@ /*************************************************************************** * Copyright (C) 2004 by Enrico Ros * * * * 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. * ***************************************************************************/ #include "presentationwidget.h" // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_LINUX #include #include // For ::close() for sleep inhibition #endif // system includes #include #include // local includes #include "annotationtools.h" #include "debug_ui.h" #include "drawingtoolactions.h" #include "guiutils.h" #include "pagepainter.h" #include "presentationsearchbar.h" #include "priorities.h" #include "videowidget.h" #include "core/action.h" #include "core/annotations.h" #include "core/audioplayer.h" #include "core/document.h" #include "core/generator.h" #include "core/movie.h" #include "core/page.h" #include "settings.h" #include "settings_core.h" // comment this to disable the top-right progress indicator #define ENABLE_PROGRESS_OVERLAY // a frame contains a pointer to the page object, its geometry and the // transition effect to the next frame struct PresentationFrame { PresentationFrame() = default; ~PresentationFrame() { qDeleteAll( videoWidgets ); } PresentationFrame(const PresentationFrame &) = delete; PresentationFrame &operator=(const PresentationFrame &) = delete; void recalcGeometry( int width, int height, float screenRatio ) { // calculate frame geometry keeping constant aspect ratio float pageRatio = page->ratio(); int pageWidth = width, pageHeight = height; if ( pageRatio > screenRatio ) pageWidth = (int)( (float)pageHeight / pageRatio ); else pageHeight = (int)( (float)pageWidth * pageRatio ); geometry.setRect( ( width - pageWidth ) / 2, ( height - pageHeight ) / 2, pageWidth, pageHeight ); Q_FOREACH ( VideoWidget *vw, videoWidgets ) { const Okular::NormalizedRect r = vw->normGeometry(); QRect vwgeom = r.geometry( geometry.width(), geometry.height() ); vw->resize( vwgeom.size() ); vw->move( geometry.topLeft() + vwgeom.topLeft() ); } } const Okular::Page * page; QRect geometry; QHash< Okular::Movie *, VideoWidget * > videoWidgets; QLinkedList< SmoothPath > drawings; }; // a custom QToolBar that basically does not propagate the event if the widget // background is not automatically filled class PresentationToolBar : public QToolBar { Q_OBJECT public: PresentationToolBar( QWidget * parent = Q_NULLPTR ) : QToolBar( parent ) {} protected: void mousePressEvent( QMouseEvent * e ) override { QToolBar::mousePressEvent( e ); e->accept(); } void mouseReleaseEvent( QMouseEvent * e ) override { QToolBar::mouseReleaseEvent( e ); e->accept(); } }; PresentationWidget::PresentationWidget( QWidget * parent, Okular::Document * doc, DrawingToolActions * drawingToolActions, KActionCollection * collection ) : QWidget( nullptr /* must be null, to have an independent widget */, Qt::FramelessWindowHint ), m_pressedLink( nullptr ), m_handCursor( false ), m_drawingEngine( nullptr ), m_screenInhibitCookie(0), m_sleepInhibitFd(-1), m_parentWidget( parent ), m_document( doc ), m_frameIndex( -1 ), m_topBar( nullptr ), m_pagesEdit( nullptr ), m_searchBar( nullptr ), m_ac( collection ), m_screenSelect( nullptr ), m_isSetup( false ), m_blockNotifications( false ), m_inBlackScreenMode( false ), m_showSummaryView( Okular::Settings::slidesShowSummary() ), m_advanceSlides( Okular::SettingsCore::slidesAdvance() ), m_goToNextPageOnRelease( false ) { Q_UNUSED( parent ) setAttribute( Qt::WA_DeleteOnClose ); setAttribute( Qt::WA_OpaquePaintEvent ); setObjectName( QStringLiteral( "presentationWidget" ) ); QString caption = doc->metaData( QStringLiteral("DocumentTitle") ).toString(); if ( caption.trimmed().isEmpty() ) caption = doc->currentDocument().fileName(); caption = i18nc( "[document title/filename] – Presentation", "%1 – Presentation", caption ); setWindowTitle( caption ); m_width = -1; m_screen = -2; // create top toolbar m_topBar = new PresentationToolBar( this ); m_topBar->setObjectName( QStringLiteral( "presentationBar" ) ); m_topBar->setMovable( false ); - m_topBar->layout()->setMargin(0); + m_topBar->layout()->setContentsMargins(0, 0, 0, 0); m_topBar->addAction( QIcon::fromTheme( layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-next") : QStringLiteral("go-previous") ), i18n( "Previous Page" ), this, SLOT(slotPrevPage()) ); m_pagesEdit = new KLineEdit( m_topBar ); QSizePolicy sp = m_pagesEdit->sizePolicy(); sp.setHorizontalPolicy( QSizePolicy::Minimum ); m_pagesEdit->setSizePolicy( sp ); QFontMetrics fm( m_pagesEdit->font() ); QStyleOptionFrame option; option.initFrom( m_pagesEdit ); m_pagesEdit->setMaximumWidth( fm.width( QString::number( m_document->pages() ) ) + 2 * style()->pixelMetric( QStyle::PM_DefaultFrameWidth, &option, m_pagesEdit ) + 4 ); // the 4 comes from 2*horizontalMargin, horizontalMargin being a define in qlineedit.cpp QIntValidator *validator = new QIntValidator( 1, m_document->pages(), m_pagesEdit ); m_pagesEdit->setValidator( validator ); m_topBar->addWidget( m_pagesEdit ); QLabel *pagesLabel = new QLabel( m_topBar ); pagesLabel->setText( QLatin1String( " / " ) + QString::number( m_document->pages() ) + QLatin1String( " " ) ); m_topBar->addWidget( pagesLabel ); connect(m_pagesEdit, &QLineEdit::returnPressed, this, &PresentationWidget::slotPageChanged); m_topBar->addAction( QIcon::fromTheme( layoutDirection() == Qt::RightToLeft ? QStringLiteral("go-previous") : QStringLiteral("go-next") ), i18n( "Next Page" ), this, SLOT(slotNextPage()) ); m_topBar->addSeparator(); QAction *playPauseAct = collection->action( QStringLiteral("presentation_play_pause") ); playPauseAct->setEnabled( true ); connect(playPauseAct, &QAction::triggered, this, &PresentationWidget::slotTogglePlayPause); m_topBar->addAction( playPauseAct ); setPlayPauseIcon(); addAction( playPauseAct ); m_topBar->addSeparator(); foreach(QAction *action, drawingToolActions->actions()) { action->setEnabled( true ); m_topBar->addAction( action ); addAction( action ); } connect( drawingToolActions, &DrawingToolActions::changeEngine, this, &PresentationWidget::slotChangeDrawingToolEngine ); connect( drawingToolActions, &DrawingToolActions::actionsRecreated, this, &PresentationWidget::slotAddDrawingToolActions ); QAction *eraseDrawingAct = collection->action( QStringLiteral("presentation_erase_drawings") ); eraseDrawingAct->setEnabled( true ); connect(eraseDrawingAct, &QAction::triggered, this, &PresentationWidget::clearDrawings); m_topBar->addAction( eraseDrawingAct ); addAction( eraseDrawingAct ); QDesktopWidget *desktop = QApplication::desktop(); if ( desktop->numScreens() > 1 ) { m_topBar->addSeparator(); m_screenSelect = new KSelectAction( QIcon::fromTheme( QStringLiteral("video-display") ), i18n( "Switch Screen" ), m_topBar ); m_screenSelect->setToolBarMode( KSelectAction::MenuMode ); m_screenSelect->setToolButtonPopupMode( QToolButton::InstantPopup ); m_topBar->addAction( m_screenSelect ); const int screenCount = desktop->numScreens(); for ( int i = 0; i < screenCount; ++i ) { QAction *act = m_screenSelect->addAction( i18nc( "%1 is the screen number (0, 1, ...)", "Screen %1", i ) ); act->setData( qVariantFromValue( i ) ); } } QWidget *spacer = new QWidget( m_topBar ); spacer->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::MinimumExpanding ); m_topBar->addWidget( spacer ); m_topBar->addAction( QIcon::fromTheme( QStringLiteral("application-exit") ), i18n( "Exit Presentation Mode" ), this, SLOT(close()) ); m_topBar->setAutoFillBackground( true ); showTopBar( false ); // change topbar background color QPalette p = m_topBar->palette(); p.setColor( QPalette::Active, QPalette::Button, Qt::gray ); - p.setColor( QPalette::Active, QPalette::Background, Qt::darkGray ); + p.setColor( QPalette::Active, QPalette::Window, Qt::darkGray ); m_topBar->setPalette( p ); // Grab swipe gestures to change pages grabGesture(Qt::SwipeGesture); // misc stuff setMouseTracking( true ); setContextMenuPolicy( Qt::PreventContextMenu ); m_transitionTimer = new QTimer( this ); m_transitionTimer->setSingleShot( true ); connect(m_transitionTimer, &QTimer::timeout, this, &PresentationWidget::slotTransitionStep); m_overlayHideTimer = new QTimer( this ); m_overlayHideTimer->setSingleShot( true ); connect(m_overlayHideTimer, &QTimer::timeout, this, &PresentationWidget::slotHideOverlay); m_nextPageTimer = new QTimer( this ); m_nextPageTimer->setSingleShot( true ); connect(m_nextPageTimer, &QTimer::timeout, this, &PresentationWidget::slotNextPage); connect(m_document, &Okular::Document::processMovieAction, this, &PresentationWidget::slotProcessMovieAction); connect(m_document, &Okular::Document::processRenditionAction, this, &PresentationWidget::slotProcessRenditionAction); // handle cursor appearance as specified in configuration if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay ) { KCursor::setAutoHideCursor( this, true ); KCursor::setHideCursorDelay( 3000 ); } else if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ) { setCursor( QCursor( Qt::BlankCursor ) ); } setupActions(); // inhibit power management inhibitPowerManagement(); show(); QTimer::singleShot( 0, this, &PresentationWidget::slotDelayedEvents ); // setFocus() so KCursor::setAutoHideCursor() goes into effect if it's enabled setFocus( Qt::OtherFocusReason ); } PresentationWidget::~PresentationWidget() { // allow power management saver again allowPowerManagement(); // stop the audio playbacks Okular::AudioPlayer::instance()->stopPlaybacks(); // remove our highlights if ( m_searchBar ) { m_document->resetSearch( PRESENTATION_SEARCH_ID ); } // remove this widget from document observer m_document->removeObserver( this ); foreach( QAction *action, m_topBar->actions() ) { action->setChecked( false ); action->setEnabled( false ); } delete m_drawingEngine; // delete frames QVector< PresentationFrame * >::iterator fIt = m_frames.begin(), fEnd = m_frames.end(); for ( ; fIt != fEnd; ++fIt ) delete *fIt; } void PresentationWidget::notifySetup( const QVector< Okular::Page * > & pageSet, int setupFlags ) { // same document, nothing to change - here we assume the document sets up // us with the whole document set as first notifySetup() if ( !( setupFlags & Okular::DocumentObserver::DocumentChanged ) ) return; // delete previous frames (if any (shouldn't be)) QVector< PresentationFrame * >::iterator fIt = m_frames.begin(), fEnd = m_frames.end(); for ( ; fIt != fEnd; ++fIt ) delete *fIt; if ( !m_frames.isEmpty() ) qCWarning(OkularUiDebug) << "Frames setup changed while a Presentation is in progress."; m_frames.clear(); // create the new frames QVector< Okular::Page * >::const_iterator setIt = pageSet.begin(), setEnd = pageSet.end(); float screenRatio = (float)m_height / (float)m_width; for ( ; setIt != setEnd; ++setIt ) { PresentationFrame * frame = new PresentationFrame(); frame->page = *setIt; const QLinkedList< Okular::Annotation * > annotations = (*setIt)->annotations(); QLinkedList< Okular::Annotation * >::const_iterator aIt = annotations.begin(), aEnd = annotations.end(); for ( ; aIt != aEnd; ++aIt ) { Okular::Annotation * a = *aIt; if ( a->subType() == Okular::Annotation::AMovie ) { Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a ); VideoWidget * vw = new VideoWidget( movieAnn, movieAnn->movie(), m_document, this ); frame->videoWidgets.insert( movieAnn->movie(), vw ); vw->pageInitialized(); } else if ( a->subType() == Okular::Annotation::ARichMedia ) { Okular::RichMediaAnnotation * richMediaAnn = static_cast< Okular::RichMediaAnnotation * >( a ); if ( richMediaAnn->movie() ) { VideoWidget * vw = new VideoWidget( richMediaAnn, richMediaAnn->movie(), m_document, this ); frame->videoWidgets.insert( richMediaAnn->movie(), vw ); vw->pageInitialized(); } } else if ( a->subType() == Okular::Annotation::AScreen ) { const Okular::ScreenAnnotation * screenAnn = static_cast< Okular::ScreenAnnotation * >( a ); Okular::Movie *movie = GuiUtils::renditionMovieFromScreenAnnotation( screenAnn ); if ( movie ) { VideoWidget * vw = new VideoWidget( screenAnn, movie, m_document, this ); frame->videoWidgets.insert( movie, vw ); vw->pageInitialized(); } } } frame->recalcGeometry( m_width, m_height, screenRatio ); // add the frame to the vector m_frames.push_back( frame ); } // get metadata from the document m_metaStrings.clear(); const Okular::DocumentInfo info = m_document->documentInfo( QSet() << Okular::DocumentInfo::Title << Okular::DocumentInfo::Author ); if ( !info.get( Okular::DocumentInfo::Title ).isNull() ) m_metaStrings += i18n( "Title: %1", info.get( Okular::DocumentInfo::Title ) ); if ( !info.get( Okular::DocumentInfo::Author ).isNull() ) m_metaStrings += i18n( "Author: %1", info.get( Okular::DocumentInfo::Author ) ); m_metaStrings += i18n( "Pages: %1", m_document->pages() ); m_metaStrings += i18n( "Click to begin" ); m_isSetup = true; } void PresentationWidget::notifyViewportChanged( bool /*smoothMove*/ ) { // display the current page changePage( m_document->viewport().pageNumber ); // auto advance to the next page if set startAutoChangeTimer(); } void PresentationWidget::notifyPageChanged( int pageNumber, int changedFlags ) { // if we are blocking the notifications, do nothing if ( m_blockNotifications ) return; // check if it's the last requested pixmap. if so update the widget. if ( (changedFlags & ( DocumentObserver::Pixmap | DocumentObserver::Annotations | DocumentObserver::Highlights ) ) && pageNumber == m_frameIndex ) generatePage( changedFlags & ( DocumentObserver::Annotations | DocumentObserver::Highlights ) ); } void PresentationWidget::notifyCurrentPageChanged( int previousPage, int currentPage ) { if ( previousPage != -1 ) { // stop video playback Q_FOREACH ( VideoWidget *vw, m_frames[ previousPage ]->videoWidgets ) { vw->stop(); vw->pageLeft(); } // stop audio playback, if any Okular::AudioPlayer::instance()->stopPlaybacks(); // perform the page closing action, if any if ( m_document->page( previousPage )->pageAction( Okular::Page::Closing ) ) m_document->processAction( m_document->page( previousPage )->pageAction( Okular::Page::Closing ) ); // perform the additional actions of the page's annotations, if any Q_FOREACH ( const Okular::Annotation *annotation, m_document->page( previousPage )->annotations() ) { Okular::Action *action = nullptr; if ( annotation->subType() == Okular::Annotation::AScreen ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageClosing ); else if ( annotation->subType() == Okular::Annotation::AWidget ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageClosing ); if ( action ) m_document->processAction( action ); } } if ( currentPage != -1 ) { m_frameIndex = currentPage; // check if pixmap exists or else request it PresentationFrame * frame = m_frames[ m_frameIndex ]; int pixW = frame->geometry.width(); int pixH = frame->geometry.height(); bool signalsBlocked = m_pagesEdit->signalsBlocked(); m_pagesEdit->blockSignals( true ); m_pagesEdit->setText( QString::number( m_frameIndex + 1 ) ); m_pagesEdit->blockSignals( signalsBlocked ); // if pixmap not inside the Okular::Page we request it and wait for // notifyPixmapChanged call or else we can proceed to pixmap generation if ( !frame->page->hasPixmap( this, ceil(pixW * qApp->devicePixelRatio()), ceil(pixH * qApp->devicePixelRatio()) ) ) { requestPixmaps(); } else { // make the background pixmap generatePage(); } // perform the page opening action, if any if ( m_document->page( m_frameIndex )->pageAction( Okular::Page::Opening ) ) m_document->processAction( m_document->page( m_frameIndex )->pageAction( Okular::Page::Opening ) ); // perform the additional actions of the page's annotations, if any Q_FOREACH ( const Okular::Annotation *annotation, m_document->page( m_frameIndex )->annotations() ) { Okular::Action *action = nullptr; if ( annotation->subType() == Okular::Annotation::AScreen ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageOpening ); else if ( annotation->subType() == Okular::Annotation::AWidget ) action = static_cast( annotation )->additionalAction( Okular::Annotation::PageOpening ); if ( action ) m_document->processAction( action ); } // start autoplay video playback Q_FOREACH ( VideoWidget *vw, m_frames[ m_frameIndex ]->videoWidgets ) vw->pageEntered(); } } bool PresentationWidget::canUnloadPixmap( int pageNumber ) const { if ( Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Low || Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Normal ) { // can unload all pixmaps except for the currently visible one return pageNumber != m_frameIndex; } else { // can unload all pixmaps except for the currently visible one, previous and next return qAbs(pageNumber - m_frameIndex) <= 1; } } void PresentationWidget::setupActions() { addAction( m_ac->action( QStringLiteral("first_page") ) ); addAction( m_ac->action( QStringLiteral("last_page") ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::Prior ) ) ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::Next ) ) ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::DocumentBack ) ) ) ); addAction( m_ac->action( QString::fromLocal8Bit(KStandardAction::name( KStandardAction::DocumentForward ) ) ) ); QAction *action = m_ac->action( QStringLiteral("switch_blackscreen_mode") ); connect(action, &QAction::toggled, this, &PresentationWidget::toggleBlackScreenMode); action->setEnabled( true ); addAction( action ); } void PresentationWidget::setPlayPauseIcon() { QAction *playPauseAction = m_ac->action( QStringLiteral("presentation_play_pause") ); if ( m_advanceSlides ) { playPauseAction->setIcon( QIcon::fromTheme( QStringLiteral("media-playback-pause") ) ); playPauseAction->setToolTip( i18nc( "For Presentation", "Pause" ) ); } else { playPauseAction->setIcon( QIcon::fromTheme( QStringLiteral("media-playback-start") ) ); playPauseAction->setToolTip( i18nc( "For Presentation", "Play" ) ); } } // bool PresentationWidget::event( QEvent * e ) { if ( e->type() == QEvent::Gesture ) return gestureEvent(static_cast(e)); if ( e->type() == QEvent::ToolTip ) { QHelpEvent * he = (QHelpEvent*)e; QRect r; const Okular::Action * link = getLink( he->x(), he->y(), &r ); if ( link ) { QString tip = link->actionTip(); if ( !tip.isEmpty() ) QToolTip::showText( he->globalPos(), tip, this, r ); } e->accept(); return true; } else // do not stop the event return QWidget::event( e ); } bool PresentationWidget::gestureEvent( QGestureEvent * event ) { // Swiping left or right on a touch screen will go to the previous or next slide, respectively. // The precise gesture is the standard Qt swipe: with three(!) fingers. if (QGesture *swipe = event->gesture(Qt::SwipeGesture)) { QSwipeGesture * swipeEvent = static_cast(swipe); if (swipeEvent->state() == Qt::GestureFinished) { if (swipeEvent->horizontalDirection() == QSwipeGesture::Left) { slotPrevPage(); event->accept(); return true; } if (swipeEvent->horizontalDirection() == QSwipeGesture::Right) { slotNextPage(); event->accept(); return true; } } } return false; } void PresentationWidget::keyPressEvent( QKeyEvent * e ) { if ( !m_isSetup ) return; switch ( e->key() ) { case Qt::Key_Left: case Qt::Key_Backspace: case Qt::Key_PageUp: case Qt::Key_Up: slotPrevPage(); break; case Qt::Key_Right: case Qt::Key_Space: case Qt::Key_PageDown: case Qt::Key_Down: slotNextPage(); break; case Qt::Key_Home: slotFirstPage(); break; case Qt::Key_End: slotLastPage(); break; case Qt::Key_Escape: if ( !m_topBar->isHidden() ) showTopBar( false ); else close(); break; } } void PresentationWidget::wheelEvent( QWheelEvent * e ) { if ( !m_isSetup ) return; // performance note: don't remove the clipping int div = e->delta() / 120; if ( div > 0 ) { if ( div > 3 ) div = 3; while ( div-- ) slotPrevPage(); } else if ( div < 0 ) { if ( div < -3 ) div = -3; while ( div++ ) slotNextPage(); } } void PresentationWidget::mousePressEvent( QMouseEvent * e ) { if ( !m_isSetup ) return; if ( m_drawingEngine ) { QRect r = routeMouseDrawingEvent( e ); if ( r.isValid() ) { m_drawingRect |= r.translated( m_frames[ m_frameIndex ]->geometry.topLeft() ); update( m_drawingRect ); } return; } // pressing left button if ( e->button() == Qt::LeftButton ) { // if pressing on a link, skip other checks if ( ( m_pressedLink = getLink( e->x(), e->y() ) ) ) return; const Okular::Annotation *annotation = getAnnotation( e->x(), e->y() ); if ( annotation ) { if ( annotation->subType() == Okular::Annotation::AMovie ) { const Okular::MovieAnnotation *movieAnnotation = static_cast( annotation ); VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( movieAnnotation->movie() ); vw->show(); vw->play(); return; } else if ( annotation->subType() == Okular::Annotation::ARichMedia ) { const Okular::RichMediaAnnotation *richMediaAnnotation = static_cast( annotation ); VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( richMediaAnnotation->movie() ); vw->show(); vw->play(); return; } else if ( annotation->subType() == Okular::Annotation::AScreen ) { m_document->processAction( static_cast( annotation )->action() ); return; } } // handle clicking on top-right overlay if ( !( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ) && m_overlayGeometry.contains( e->pos() ) ) { overlayClick( e->pos() ); return; } m_goToNextPageOnRelease = true; } // pressing the "move forward" mouse button: unlike the left button this // always means "show next page", so we unconditionally delegate to that // action on mouse button press else if ( e->button() == Qt::ForwardButton ) { slotNextPage(); } // pressing right or backward button else if ( e->button() == Qt::RightButton || e->button() == Qt::BackButton ) slotPrevPage(); } void PresentationWidget::mouseReleaseEvent( QMouseEvent * e ) { if ( m_drawingEngine ) { routeMouseDrawingEvent( e ); return; } // if releasing on the same link we pressed over, execute it if ( m_pressedLink && e->button() == Qt::LeftButton ) { const Okular::Action * link = getLink( e->x(), e->y() ); if ( link == m_pressedLink ) m_document->processAction( link ); m_pressedLink = nullptr; } if ( m_goToNextPageOnRelease ) { slotNextPage(); m_goToNextPageOnRelease = false; } } void PresentationWidget::mouseMoveEvent( QMouseEvent * e ) { // safety check if ( !m_isSetup ) return; // update cursor and tooltip if hovering a link if ( !m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden ) testCursorOnLink( e->x(), e->y() ); if ( !m_topBar->isHidden() ) { // hide a shown bar when exiting the area if ( e->y() > ( m_topBar->height() + 1 ) ) { showTopBar( false ); setFocus( Qt::OtherFocusReason ); } } else { if ( m_drawingEngine && e->buttons() != Qt::NoButton ) { QRect r = routeMouseDrawingEvent( e ); if ( r.isValid() ) { m_drawingRect |= r.translated( m_frames[ m_frameIndex ]->geometry.topLeft() ); update( m_drawingRect ); } } else { // show the bar if reaching top 2 pixels if ( e->y() <= 1 ) showTopBar( true ); // handle "dragging the wheel" if clicking on its geometry else if ( ( QApplication::mouseButtons() & Qt::LeftButton ) && m_overlayGeometry.contains( e->pos() ) ) overlayClick( e->pos() ); } } } void PresentationWidget::paintEvent( QPaintEvent * pe ) { qreal dpr = devicePixelRatioF(); if ( m_inBlackScreenMode ) { QPainter painter( this ); painter.fillRect( pe->rect(), Qt::black ); return; } if ( !m_isSetup ) { m_width = width(); m_height = height(); connect(m_document, &Okular::Document::linkFind, this, &PresentationWidget::slotFind); // register this observer in document. events will come immediately m_document->addObserver( this ); // show summary if requested if ( Okular::Settings::slidesShowSummary() ) generatePage(); } // check painting rect consistency QRect r = pe->rect().intersected( QRect( QPoint( 0, 0 ), geometry().size() ) ); if ( r.isNull() ) return; if ( m_lastRenderedPixmap.isNull() ) { QPainter painter( this ); painter.fillRect( pe->rect(), Okular::Settings::slidesBackgroundColor() ); return; } // blit the pixmap to the screen QVector allRects = pe->region().rects(); uint numRects = allRects.count(); QPainter painter( this ); for ( uint i = 0; i < numRects; i++ ) { const QRect & r = allRects[i]; if ( !r.isValid() ) continue; #ifdef ENABLE_PROGRESS_OVERLAY const QRect dR(QRectF(r.x() * dpr, r.y() * dpr, r.width() * dpr, r.height() * dpr).toAlignedRect()); if ( Okular::Settings::slidesShowProgress() && r.intersects( m_overlayGeometry ) ) { // backbuffer the overlay operation QPixmap backPixmap( dR.size() ); backPixmap.setDevicePixelRatio( dpr ); QPainter pixPainter( &backPixmap ); // first draw the background on the backbuffer pixPainter.drawPixmap( QPoint(0,0), m_lastRenderedPixmap, dR ); // then blend the overlay (a piece of) over the background QRect ovr = m_overlayGeometry.intersected( r ); pixPainter.drawPixmap( (ovr.left() - r.left()), (ovr.top() - r.top()), m_lastRenderedOverlay, (ovr.left() - m_overlayGeometry.left()) * dpr, (ovr.top() - m_overlayGeometry.top()) * dpr, ovr.width() * dpr, ovr.height() * dpr ); // finally blit the pixmap to the screen pixPainter.end(); const QRect backPixmapRect = backPixmap.rect(); const QRect dBackPixmapRect(QRectF(backPixmapRect.x() * dpr, backPixmapRect.y() * dpr, backPixmapRect.width() * dpr, backPixmapRect.height() * dpr).toAlignedRect()); painter.drawPixmap( r.topLeft(), backPixmap, dBackPixmapRect ); } else #endif // copy the rendered pixmap to the screen painter.drawPixmap( r.topLeft(), m_lastRenderedPixmap, dR ); } // paint drawings if ( m_frameIndex != -1 ) { painter.save(); const QRect & geom = m_frames[ m_frameIndex ]->geometry; QPixmap pm( geom.size() ); pm.fill( Qt::transparent ); QPainter pmPainter( &pm ); pmPainter.setRenderHints( QPainter::Antialiasing ); foreach ( const SmoothPath &drawing, m_frames[ m_frameIndex ]->drawings ) drawing.paint( &pmPainter, geom.width(), geom.height() ); if ( m_drawingEngine && m_drawingRect.intersects( pe->rect() ) ) m_drawingEngine->paint( &pmPainter, geom.width(), geom.height(), m_drawingRect.intersected( pe->rect() ) ); painter.setRenderHints( QPainter::Antialiasing ); painter.drawPixmap( geom.topLeft() , pm ); painter.restore(); } painter.end(); } void PresentationWidget::resizeEvent( QResizeEvent *re ) { // qCDebug(OkularUiDebug) << re->oldSize() << "=>" << re->size(); if ( re->oldSize() == QSize( -1, -1 ) ) return; m_screen = QApplication::desktop()->screenNumber( this ); applyNewScreenSize( re->oldSize() ); } void PresentationWidget::leaveEvent( QEvent * e ) { Q_UNUSED( e ) if ( !m_topBar->isHidden() ) { showTopBar( false ); } } // const void * PresentationWidget::getObjectRect( Okular::ObjectRect::ObjectType type, int x, int y, QRect * geometry ) const { // no links on invalid pages if ( geometry && !geometry->isNull() ) geometry->setRect( 0, 0, 0, 0 ); if ( m_frameIndex < 0 || m_frameIndex >= (int)m_frames.size() ) return nullptr; // get frame, page and geometry const PresentationFrame * frame = m_frames[ m_frameIndex ]; const Okular::Page * page = frame->page; const QRect & frameGeometry = frame->geometry; // compute normalized x and y double nx = (double)(x - frameGeometry.left()) / (double)frameGeometry.width(); double ny = (double)(y - frameGeometry.top()) / (double)frameGeometry.height(); // no links outside the pages if ( nx < 0 || nx > 1 || ny < 0 || ny > 1 ) return nullptr; // check if 1) there is an object and 2) it's a link const QRect d = QApplication::desktop()->screenGeometry( m_screen ); const Okular::ObjectRect * object = page->objectRect( type, nx, ny, d.width(), d.height() ); if ( !object ) return nullptr; // compute link geometry if destination rect present if ( geometry ) { *geometry = object->boundingRect( frameGeometry.width(), frameGeometry.height() ); geometry->translate( frameGeometry.left(), frameGeometry.top() ); } // return the link pointer return object->object(); } const Okular::Action * PresentationWidget::getLink( int x, int y, QRect * geometry ) const { return reinterpret_cast( getObjectRect( Okular::ObjectRect::Action, x, y, geometry ) ); } const Okular::Annotation * PresentationWidget::getAnnotation( int x, int y, QRect * geometry ) const { return reinterpret_cast( getObjectRect( Okular::ObjectRect::OAnnotation, x, y, geometry ) ); } void PresentationWidget::testCursorOnLink( int x, int y ) { const Okular::Action * link = getLink( x, y, nullptr ); const Okular::Annotation *annotation = getAnnotation( x, y, nullptr ); const bool needsHandCursor = ( ( link != nullptr ) || ( ( annotation != nullptr ) && ( annotation->subType() == Okular::Annotation::AMovie ) ) || ( ( annotation != nullptr ) && ( annotation->subType() == Okular::Annotation::ARichMedia ) ) || ( ( annotation != nullptr ) && ( annotation->subType() == Okular::Annotation::AScreen ) && ( GuiUtils::renditionMovieFromScreenAnnotation( static_cast< const Okular::ScreenAnnotation * >( annotation ) ) != nullptr ) ) ); // only react on changes (in/out from a link) if ( ( needsHandCursor && !m_handCursor ) || ( !needsHandCursor && m_handCursor ) ) { // change cursor shape m_handCursor = needsHandCursor; setCursor( QCursor( m_handCursor ? Qt::PointingHandCursor : Qt::ArrowCursor ) ); } } void PresentationWidget::overlayClick( const QPoint & position ) { // clicking the progress indicator int xPos = position.x() - m_overlayGeometry.x() - m_overlayGeometry.width() / 2, yPos = m_overlayGeometry.height() / 2 - position.y(); if ( !xPos && !yPos ) return; // compute angle relative to indicator (note coord transformation) float angle = 0.5 + 0.5 * atan2( (double)-xPos, (double)-yPos ) / M_PI; int pageIndex = (int)( angle * ( m_frames.count() - 1 ) + 0.5 ); // go to selected page changePage( pageIndex ); } void PresentationWidget::changePage( int newPage ) { if ( m_showSummaryView ) { m_showSummaryView = false; m_frameIndex = -1; return; } if ( m_frameIndex == newPage ) return; // switch to newPage m_document->setViewportPage( newPage, this ); if ( (Okular::Settings::slidesShowSummary() && !m_showSummaryView) || m_frameIndex == -1 ) notifyCurrentPageChanged( -1, newPage ); } void PresentationWidget::generatePage( bool disableTransition ) { if ( m_lastRenderedPixmap.isNull() ) { qreal dpr = qApp->devicePixelRatio(); m_lastRenderedPixmap = QPixmap( m_width * dpr, m_height * dpr ); m_lastRenderedPixmap.setDevicePixelRatio(dpr); m_previousPagePixmap = QPixmap(); } else { m_previousPagePixmap = m_lastRenderedPixmap; } // opens the painter over the pixmap QPainter pixmapPainter; pixmapPainter.begin( &m_lastRenderedPixmap ); // generate welcome page if ( m_frameIndex == -1 ) generateIntroPage( pixmapPainter ); // generate a normal pixmap with extended margin filling if ( m_frameIndex >= 0 && m_frameIndex < (int)m_document->pages() ) generateContentsPage( m_frameIndex, pixmapPainter ); pixmapPainter.end(); // generate the top-right corner overlay #ifdef ENABLE_PROGRESS_OVERLAY if ( Okular::Settings::slidesShowProgress() && m_frameIndex != -1 ) generateOverlay(); #endif // start transition on pages that have one if ( !disableTransition && Okular::Settings::slidesTransitionsEnabled() ) { const Okular::PageTransition * transition = m_frameIndex != -1 ? m_frames[ m_frameIndex ]->page->transition() : nullptr; if ( transition ) initTransition( transition ); else { Okular::PageTransition trans = defaultTransition(); initTransition( &trans ); } } else { Okular::PageTransition trans = defaultTransition( Okular::Settings::EnumSlidesTransition::Replace ); initTransition( &trans ); } // update cursor + tooltip if ( !m_drawingEngine && Okular::Settings::slidesCursor() != Okular::Settings::EnumSlidesCursor::Hidden ) { QPoint p = mapFromGlobal( QCursor::pos() ); testCursorOnLink( p.x(), p.y() ); } } void PresentationWidget::generateIntroPage( QPainter & p ) { qreal dpr = qApp->devicePixelRatio(); // use a vertical gray gradient background int blend1 = m_height / 10, blend2 = 9 * m_height / 10; int baseTint = QColor(Qt::gray).red(); for ( int i = 0; i < m_height; i++ ) { int k = baseTint; if ( i < blend1 ) k -= (int)( baseTint * (i-blend1)*(i-blend1) / (float)(blend1 * blend1) ); if ( i > blend2 ) k += (int)( (255-baseTint) * (i-blend2)*(i-blend2) / (float)(blend1 * blend1) ); p.fillRect( 0, i, m_width, 1, QColor( k, k, k ) ); } // draw okular logo in the four corners QPixmap logo = DesktopIcon( QStringLiteral("okular"), 64 * dpr ); logo.setDevicePixelRatio( dpr ); if ( !logo.isNull() ) { p.drawPixmap( 5, 5, logo ); p.drawPixmap( m_width - 5 - logo.width(), 5, logo ); p.drawPixmap( m_width - 5 - logo.width(), m_height - 5 - logo.height(), logo ); p.drawPixmap( 5, m_height - 5 - logo.height(), logo ); } // draw metadata text (the last line is 'click to begin') int strNum = m_metaStrings.count(), strHeight = m_height / ( strNum + 4 ), fontHeight = 2 * strHeight / 3; QFont font( p.font() ); font.setPixelSize( fontHeight ); QFontMetrics metrics( font ); for ( int i = 0; i < strNum; i++ ) { // set a font to fit text width float wScale = (float)metrics.boundingRect( m_metaStrings[i] ).width() / (float)m_width; QFont f( font ); if ( wScale > 1.0 ) f.setPixelSize( (int)( (float)fontHeight / (float)wScale ) ); p.setFont( f ); // text shadow p.setPen( Qt::darkGray ); p.drawText( 2, m_height / 4 + strHeight * i + 2, m_width, strHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i] ); // text body p.setPen( 128 + (127 * i) / strNum ); p.drawText( 0, m_height / 4 + strHeight * i, m_width, strHeight, Qt::AlignHCenter | Qt::AlignVCenter, m_metaStrings[i] ); } } void PresentationWidget::generateContentsPage( int pageNum, QPainter & p ) { PresentationFrame * frame = m_frames[ pageNum ]; // translate painter and contents rect QRect geom( frame->geometry ); p.translate( geom.left(), geom.top() ); geom.translate( -geom.left(), -geom.top() ); // draw the page using the shared PagePainter class int flags = PagePainter::Accessibility | PagePainter::Highlights | PagePainter::Annotations; PagePainter::paintPageOnPainter( &p, frame->page, this, flags, geom.width(), geom.height(), geom ); // restore painter p.translate( -frame->geometry.left(), -frame->geometry.top() ); // fill unpainted areas with background color QRegion unpainted( QRect( 0, 0, m_width, m_height ) ); QVector rects = unpainted.subtracted( frame->geometry ).rects(); for ( int i = 0; i < rects.count(); i++ ) { const QRect & r = rects[i]; p.fillRect( r, Okular::Settings::slidesBackgroundColor() ); } } // from Arthur - Qt4 - (is defined elsewhere as 'qt_div_255' to not break final compilation) inline int qt_div255(int x) { return (x + (x>>8) + 0x80) >> 8; } void PresentationWidget::generateOverlay() { #ifdef ENABLE_PROGRESS_OVERLAY qreal dpr = qApp->devicePixelRatio(); // calculate overlay geometry and resize pixmap if needed int side = m_width / 16; m_overlayGeometry.setRect( m_width - side - 4, 4, side, side ); // note: to get a sort of antialiasing, we render the pixmap double sized // and the resulting image is smoothly scaled down. So here we open a // painter on the double sized pixmap. side *= 2; QPixmap doublePixmap( side * dpr, side * dpr ); doublePixmap.setDevicePixelRatio( dpr ); doublePixmap.fill( Qt::black ); QPainter pixmapPainter( &doublePixmap ); pixmapPainter.setRenderHints( QPainter::Antialiasing ); // draw PIE SLICES in blue levels (the levels will then be the alpha component) int pages = m_document->pages(); if ( pages > 28 ) { // draw continuous slices int degrees = (int)( 360 * (float)(m_frameIndex + 1) / (float)pages ); pixmapPainter.setPen( 0x05 ); pixmapPainter.setBrush( QColor( 0x40 ) ); pixmapPainter.drawPie( 2, 2, side - 4, side - 4, 90*16, (360-degrees)*16 ); pixmapPainter.setPen( 0x40 ); pixmapPainter.setBrush( QColor( 0xF0 ) ); pixmapPainter.drawPie( 2, 2, side - 4, side - 4, 90*16, -degrees*16 ); } else { // draw discrete slices float oldCoord = -90; for ( int i = 0; i < pages; i++ ) { float newCoord = -90 + 360 * (float)(i + 1) / (float)pages; pixmapPainter.setPen( i <= m_frameIndex ? 0x40 : 0x05 ); pixmapPainter.setBrush( QColor( i <= m_frameIndex ? 0xF0 : 0x40 ) ); pixmapPainter.drawPie( 2, 2, side - 4, side - 4, (int)( -16*(oldCoord + 1) ), (int)( -16*(newCoord - (oldCoord + 2)) ) ); oldCoord = newCoord; } } int circleOut = side / 4; pixmapPainter.setPen( Qt::black ); pixmapPainter.setBrush( Qt::black ); pixmapPainter.drawEllipse( circleOut, circleOut, side - 2*circleOut, side - 2*circleOut ); // draw TEXT using maximum opacity QFont f( pixmapPainter.font() ); f.setPixelSize( side / 4 ); pixmapPainter.setFont( f ); pixmapPainter.setPen( 0xFF ); // use a little offset to prettify output pixmapPainter.drawText( 2, 2, side, side, Qt::AlignCenter, QString::number( m_frameIndex + 1 ) ); // end drawing pixmap and halve image pixmapPainter.end(); QImage image( doublePixmap.toImage().scaled( (side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); image.setDevicePixelRatio( dpr ); image = image.convertToFormat( QImage::Format_ARGB32 ); image.setDevicePixelRatio( dpr ); // draw circular shadow using the same technique doublePixmap.fill( Qt::black ); pixmapPainter.begin( &doublePixmap ); pixmapPainter.setPen( 0x40 ); pixmapPainter.setBrush( QColor( 0x80 ) ); pixmapPainter.drawEllipse( 0, 0, side, side ); pixmapPainter.end(); QImage shadow( doublePixmap.toImage().scaled( (side / 2) * dpr, (side / 2) * dpr, Qt::IgnoreAspectRatio, Qt::SmoothTransformation ) ); shadow.setDevicePixelRatio( dpr ); // generate a 2 colors pixmap using mixing shadow (made with highlight color) // and image (made with highlightedText color) QPalette pal = palette(); QColor color = pal.color( QPalette::Active, QPalette::HighlightedText ); int red = color.red(), green = color.green(), blue = color.blue(); color = pal.color( QPalette::Active, QPalette::Highlight ); int sRed = color.red(), sGreen = color.green(), sBlue = color.blue(); // pointers unsigned int * data = (unsigned int *)image.bits(), * shadowData = (unsigned int *)shadow.bits(), pixels = image.width() * image.height(); // cache data (reduce computation time to 26%!) int c1 = -1, c2 = -1, cR = 0, cG = 0, cB = 0, cA = 0; // foreach pixel for( unsigned int i = 0; i < pixels; ++i ) { // alpha for shadow and image int shadowAlpha = shadowData[i] & 0xFF, srcAlpha = data[i] & 0xFF; // cache values if ( srcAlpha != c1 || shadowAlpha != c2 ) { c1 = srcAlpha; c2 = shadowAlpha; // fuse color components and alpha value of image over shadow data[i] = qRgba( cR = qt_div255( srcAlpha * red + (255 - srcAlpha) * sRed ), cG = qt_div255( srcAlpha * green + (255 - srcAlpha) * sGreen ), cB = qt_div255( srcAlpha * blue + (255 - srcAlpha) * sBlue ), cA = qt_div255( srcAlpha * srcAlpha + (255 - srcAlpha) * shadowAlpha ) ); } else data[i] = qRgba( cR, cG, cB, cA ); } m_lastRenderedOverlay = QPixmap::fromImage( image ); m_lastRenderedOverlay.setDevicePixelRatio( dpr ); // start the autohide timer //repaint( m_overlayGeometry ); // toggle with next line update( m_overlayGeometry ); m_overlayHideTimer->start( 2500 ); #endif } QRect PresentationWidget::routeMouseDrawingEvent( QMouseEvent * e ) { if ( m_frameIndex == -1 ) // Can't draw on the summary page return QRect(); const QRect & geom = m_frames[ m_frameIndex ]->geometry; const Okular::Page * page = m_frames[ m_frameIndex ]->page; AnnotatorEngine::EventType eventType; AnnotatorEngine::Button button; // figure out the event type and button AnnotatorEngine::decodeEvent( e, &eventType, &button ); static bool hasclicked = false; if ( eventType == AnnotatorEngine::Press ) hasclicked = true; double nX = ( (double)e->x() - (double)geom.left() ) / (double)geom.width(); double nY = ( (double)e->y() - (double)geom.top() ) / (double)geom.height(); QRect ret; bool isInside = nX >= 0 && nX < 1 && nY >= 0 && nY < 1; if ( hasclicked && !isInside ) { // Fake a move to the last border pos nX = qBound(0., nX, 1.); nY = qBound(0., nY, 1.); m_drawingEngine->event( AnnotatorEngine::Move, button, nX, nY, geom.width(), geom.height(), page ); // Fake a release in the following lines eventType = AnnotatorEngine::Release; isInside = true; } else if ( !hasclicked && isInside ) { // we're coming from the outside, pretend we started clicking at the closest border if ( nX < ( 1 - nX ) && nX < nY && nX < ( 1 - nY ) ) nX = 0; else if ( nY < ( 1 - nY ) && nY < nX && nY < ( 1 - nX ) ) nY = 0; else if ( ( 1 - nX ) < nX && ( 1 - nX ) < nY && ( 1 - nX ) < ( 1 - nY ) ) nX = 1; else nY = 1; hasclicked = true; eventType = AnnotatorEngine::Press; } if ( hasclicked && isInside ) { ret = m_drawingEngine->event( eventType, button, nX, nY, geom.width(), geom.height(), page ); } if ( eventType == AnnotatorEngine::Release ) { hasclicked = false; } if ( m_drawingEngine->creationCompleted() ) { // add drawing to current page m_frames[ m_frameIndex ]->drawings << m_drawingEngine->endSmoothPath(); // manually disable and re-enable the pencil mode, so we can do // cleaning of the actual drawer and create a new one just after // that - that gives continuous drawing slotChangeDrawingToolEngine( QDomElement() ); slotChangeDrawingToolEngine( m_currentDrawingToolElement ); // schedule repaint update(); } return ret; } void PresentationWidget::startAutoChangeTimer() { double pageDuration = m_frameIndex >= 0 && m_frameIndex < (int)m_frames.count() ? m_frames[ m_frameIndex ]->page->duration() : -1; if ( m_advanceSlides || pageDuration >= 0.0 ) { double secs; if ( pageDuration < 0.0 ) secs = Okular::SettingsCore::slidesAdvanceTime(); else if ( m_advanceSlides ) secs = qMin( pageDuration, Okular::SettingsCore::slidesAdvanceTime() ); else secs = pageDuration; m_nextPageTimer->start( (int)( secs * 1000 ) ); } } void PresentationWidget::recalcGeometry() { QDesktopWidget *desktop = QApplication::desktop(); const int preferenceScreen = Okular::Settings::slidesScreen(); int screen = 0; if ( preferenceScreen == -2 ) { screen = desktop->screenNumber( m_parentWidget ); } else if ( preferenceScreen == -1 ) { screen = desktop->primaryScreen(); } else if ( preferenceScreen >= 0 && preferenceScreen < desktop->numScreens() ) { screen = preferenceScreen; } else { screen = desktop->screenNumber( m_parentWidget ); Okular::Settings::setSlidesScreen( -2 ); } const QRect screenGeom = desktop->screenGeometry( screen ); // qCDebug(OkularUiDebug) << screen << "=>" << screenGeom; m_screen = screen; setGeometry( screenGeom ); } void PresentationWidget::repositionContent() { const QRect ourGeom = geometry(); // tool bar height in pixels, make it large enough to hold the text fields with the page numbers const int toolBarHeight = m_pagesEdit->height() * 1.5; m_topBar->setGeometry( 0, 0, ourGeom.width(), toolBarHeight ); m_topBar->setIconSize( QSize( toolBarHeight * 0.75, toolBarHeight * 0.75 ) ); } void PresentationWidget::requestPixmaps() { PresentationFrame * frame = m_frames[ m_frameIndex ]; int pixW = frame->geometry.width(); int pixH = frame->geometry.height(); // operation will take long: set busy cursor QApplication::setOverrideCursor( QCursor( Qt::BusyCursor ) ); // request the pixmap QLinkedList< Okular::PixmapRequest * > requests; requests.push_back( new Okular::PixmapRequest( this, m_frameIndex, pixW, pixH, PRESENTATION_PRIO, Okular::PixmapRequest::NoFeature ) ); // restore cursor QApplication::restoreOverrideCursor(); // ask for next and previous page if not in low memory usage setting if ( Okular::SettingsCore::memoryLevel() != Okular::SettingsCore::EnumMemoryLevel::Low ) { int pagesToPreload = 1; // If greedy, preload everything if (Okular::SettingsCore::memoryLevel() == Okular::SettingsCore::EnumMemoryLevel::Greedy) pagesToPreload = (int)m_document->pages(); Okular::PixmapRequest::PixmapRequestFeatures requestFeatures = Okular::PixmapRequest::Preload; requestFeatures |= Okular::PixmapRequest::Asynchronous; for( int j = 1; j <= pagesToPreload; j++ ) { int tailRequest = m_frameIndex + j; if ( tailRequest < (int)m_document->pages() ) { PresentationFrame *nextFrame = m_frames[ tailRequest ]; pixW = nextFrame->geometry.width(); pixH = nextFrame->geometry.height(); if ( !nextFrame->page->hasPixmap( this, pixW, pixH ) ) requests.push_back( new Okular::PixmapRequest( this, tailRequest, pixW, pixH, PRESENTATION_PRELOAD_PRIO, requestFeatures ) ); } int headRequest = m_frameIndex - j; if ( headRequest >= 0 ) { PresentationFrame *prevFrame = m_frames[ headRequest ]; pixW = prevFrame->geometry.width(); pixH = prevFrame->geometry.height(); if ( !prevFrame->page->hasPixmap( this, pixW, pixH ) ) requests.push_back( new Okular::PixmapRequest( this, headRequest, pixW, pixH, PRESENTATION_PRELOAD_PRIO, requestFeatures ) ); } // stop if we've already reached both ends of the document if ( headRequest < 0 && tailRequest >= (int)m_document->pages() ) break; } } m_document->requestPixmaps( requests ); } void PresentationWidget::slotNextPage() { int nextIndex = m_frameIndex + 1; // loop when configured if ( nextIndex == m_frames.count() && Okular::Settings::slidesLoop() ) nextIndex = 0; if ( nextIndex < m_frames.count() ) { // go to next page changePage( nextIndex ); // auto advance to the next page if set startAutoChangeTimer(); } else { #ifdef ENABLE_PROGRESS_OVERLAY if ( Okular::Settings::slidesShowProgress() ) generateOverlay(); #endif if ( m_transitionTimer->isActive() ) { m_transitionTimer->stop(); m_lastRenderedPixmap = m_currentPagePixmap; update(); } } // we need the setFocus() call here to let KCursor::autoHide() work correctly setFocus(); } void PresentationWidget::slotPrevPage() { if ( m_frameIndex > 0 ) { // go to previous page changePage( m_frameIndex - 1 ); // auto advance to the next page if set startAutoChangeTimer(); } else { #ifdef ENABLE_PROGRESS_OVERLAY if ( Okular::Settings::slidesShowProgress() ) generateOverlay(); #endif if ( m_transitionTimer->isActive() ) { m_transitionTimer->stop(); m_lastRenderedPixmap = m_currentPagePixmap; update(); } } } void PresentationWidget::slotFirstPage() { changePage( 0 ); } void PresentationWidget::slotLastPage() { changePage( (int)m_frames.count() - 1 ); } void PresentationWidget::slotHideOverlay() { QRect geom( m_overlayGeometry ); m_overlayGeometry.setCoords( 0, 0, -1, -1 ); update( geom ); } void PresentationWidget::slotTransitionStep() { switch( m_currentTransition.type() ) { case Okular::PageTransition::Fade: { QPainter pixmapPainter; m_currentPixmapOpacity += 1.0 / m_transitionSteps; m_lastRenderedPixmap = QPixmap( m_lastRenderedPixmap.size() ); m_lastRenderedPixmap.setDevicePixelRatio( qApp->devicePixelRatio() ); m_lastRenderedPixmap.fill( Qt::transparent ); pixmapPainter.begin( &m_lastRenderedPixmap ); pixmapPainter.setCompositionMode( QPainter::CompositionMode_Source ); pixmapPainter.setOpacity( 1 - m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_previousPagePixmap ); pixmapPainter.setOpacity( m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_currentPagePixmap ); update(); if( m_currentPixmapOpacity >= 1 ) return; } break; default: { if ( m_transitionRects.empty() ) { // it's better to fix the transition to cover the whole screen than // enabling the following line that wastes cpu for nothing //update(); return; } for ( int i = 0; i < m_transitionMul && !m_transitionRects.empty(); i++ ) { update( m_transitionRects.first() ); m_transitionRects.pop_front(); } } break; } m_transitionTimer->start( m_transitionDelay ); } void PresentationWidget::slotDelayedEvents() { recalcGeometry(); repositionContent(); if ( m_screenSelect ) { m_screenSelect->setCurrentItem( m_screen ); connect( m_screenSelect->selectableActionGroup(), &QActionGroup::triggered, this, &PresentationWidget::chooseScreen ); } // show widget and take control show(); setWindowState( windowState() | Qt::WindowFullScreen ); connect( QApplication::desktop(), &QDesktopWidget::resized, this, &PresentationWidget::screenResized ); // inform user on how to exit from presentation mode KMessageBox::information( this, i18n("There are two ways of exiting presentation mode, you can press either ESC key or click with the quit button that appears when placing the mouse in the top-right corner. Of course you can cycle windows (Alt+TAB by default)"), QString(), QStringLiteral("presentationInfo") ); } void PresentationWidget::slotPageChanged() { bool ok = true; int p = m_pagesEdit->text().toInt( &ok ); if ( !ok ) return; changePage( p - 1 ); } void PresentationWidget::slotChangeDrawingToolEngine( const QDomElement &element ) { if ( element.isNull() ) { delete m_drawingEngine; m_drawingEngine = nullptr; m_drawingRect = QRect(); setCursor( Qt::ArrowCursor ); } else { m_drawingEngine = new SmoothPathEngine( element ); setCursor( QCursor( QPixmap( QStringLiteral("pencil") ), Qt::ArrowCursor ) ); m_currentDrawingToolElement = element; } } void PresentationWidget::slotAddDrawingToolActions() { DrawingToolActions *drawingToolActions = qobject_cast(sender()); foreach(QAction *action, drawingToolActions->actions()) { action->setEnabled( true ); m_topBar->addAction( action ); addAction( action ); } } void PresentationWidget::clearDrawings() { if ( m_frameIndex != -1 ) m_frames[ m_frameIndex ]->drawings.clear(); update(); } void PresentationWidget::screenResized( int screen ) { // we can ignore if a screen was resized in the case the screen is not // where we are on if ( screen != m_screen ) return; setScreen( screen ); } void PresentationWidget::chooseScreen( QAction *act ) { if ( !act || act->data().type() != QVariant::Int ) return; const int newScreen = act->data().toInt(); setScreen( newScreen ); } void PresentationWidget::toggleBlackScreenMode( bool ) { m_inBlackScreenMode = !m_inBlackScreenMode; update(); } void PresentationWidget::setScreen( int newScreen ) { const QRect screenGeom = QApplication::desktop()->screenGeometry( newScreen ); const QSize oldSize = size(); // qCDebug(OkularUiDebug) << newScreen << "=>" << screenGeom; m_screen = newScreen; setGeometry( screenGeom ); applyNewScreenSize( oldSize ); } void PresentationWidget::applyNewScreenSize( const QSize & oldSize ) { repositionContent(); // if by chance the new screen has the same resolution of the previous, // do not invalidate pixmaps and such.. if ( size() == oldSize ) return; m_width = width(); m_height = height(); // update the frames QVector< PresentationFrame * >::const_iterator fIt = m_frames.constBegin(), fEnd = m_frames.constEnd(); const float screenRatio = (float)m_height / (float)m_width; for ( ; fIt != fEnd; ++fIt ) { (*fIt)->recalcGeometry( m_width, m_height, screenRatio ); } if ( m_frameIndex != -1 ) { // ugliness alarm! const_cast< Okular::Page * >( m_frames[ m_frameIndex ]->page )->deletePixmap( this ); // force the regeneration of the pixmap m_lastRenderedPixmap = QPixmap(); m_blockNotifications = true; requestPixmaps(); m_blockNotifications = false; } if ( m_transitionTimer->isActive() ) { m_transitionTimer->stop(); } generatePage( true /* no transitions */ ); } void PresentationWidget::inhibitPowerManagement() { #ifdef Q_OS_LINUX QString reason = i18nc( "Reason for inhibiting the screensaver activation, when the presentation mode is active", "Giving a presentation" ); if (!m_screenInhibitCookie) { QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.ScreenSaver", "/ScreenSaver", "org.freedesktop.ScreenSaver", "Inhibit"); message << QCoreApplication::applicationName(); message << reason; QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(message); reply.waitForFinished(); if (reply.isValid()) { m_screenInhibitCookie = reply.value(); qCDebug(OkularUiDebug) << "Screen inhibition cookie" << m_screenInhibitCookie; } else { qCWarning(OkularUiDebug) << "Unable to inhibit screensaver" << reply.error(); } } if (m_sleepInhibitFd != -1) { QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.login1"), QStringLiteral("/org/freedesktop/login1"), QStringLiteral("org.freedesktop.login1.Manager"), QStringLiteral("Inhibit") ); message << QStringLiteral("sleep"); message << QCoreApplication::applicationName(); message << reason; message << QStringLiteral("block"); QDBusPendingReply reply = QDBusConnection::systemBus().asyncCall(message); reply.waitForFinished(); if (reply.isValid()) { m_sleepInhibitFd = dup(reply.value().fileDescriptor()); } else { qCWarning(OkularUiDebug) << "Unable to inhibit sleep" << reply.error(); } } #endif } void PresentationWidget::allowPowerManagement() { #ifdef Q_OS_LINUX if (m_sleepInhibitFd != -1) { ::close(m_sleepInhibitFd); m_sleepInhibitFd = -1; } if (m_screenInhibitCookie) { QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.ScreenSaver", "/ScreenSaver", "org.freedesktop.ScreenSaver", "UnInhibit"); message << m_screenInhibitCookie; QDBusPendingReply reply = QDBusConnection::sessionBus().asyncCall(message); reply.waitForFinished(); m_screenInhibitCookie = 0; } #endif } void PresentationWidget::showTopBar( bool show ) { if ( show ) { m_topBar->show(); // Don't autohide the mouse cursor if it's over the toolbar if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay ) { KCursor::setAutoHideCursor( this, false ); } // Always show a cursor when topBar is visible if ( !m_drawingEngine ) { setCursor( QCursor( Qt::ArrowCursor ) ); } } else { m_topBar->hide(); // Reenable autohide if need be when leaving the toolbar if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::HiddenDelay ) { KCursor::setAutoHideCursor( this, true ); } // Or hide the cursor again if hidden cursor is enabled else if ( Okular::Settings::slidesCursor() == Okular::Settings::EnumSlidesCursor::Hidden ) { // Don't hide the cursor if drawing mode is on if ( !m_drawingEngine ) { setCursor( QCursor( Qt::BlankCursor ) ); } } } // Make sure mouse tracking isn't off after the KCursor::setAutoHideCursor() calls setMouseTracking( true ); } void PresentationWidget::slotFind() { if ( !m_searchBar ) { m_searchBar = new PresentationSearchBar( m_document, this, this ); m_searchBar->forceSnap(); } m_searchBar->focusOnSearchEdit(); m_searchBar->show(); } const Okular::PageTransition PresentationWidget::defaultTransition() const { return defaultTransition( Okular::Settings::slidesTransition() ); } const Okular::PageTransition PresentationWidget::defaultTransition( int type ) const { switch ( type ) { case Okular::Settings::EnumSlidesTransition::BlindsHorizontal: { Okular::PageTransition transition( Okular::PageTransition::Blinds ); transition.setAlignment( Okular::PageTransition::Horizontal ); return transition; break; } case Okular::Settings::EnumSlidesTransition::BlindsVertical: { Okular::PageTransition transition( Okular::PageTransition::Blinds ); transition.setAlignment( Okular::PageTransition::Vertical ); return transition; break; } case Okular::Settings::EnumSlidesTransition::BoxIn: { Okular::PageTransition transition( Okular::PageTransition::Box ); transition.setDirection( Okular::PageTransition::Inward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::BoxOut: { Okular::PageTransition transition( Okular::PageTransition::Box ); transition.setDirection( Okular::PageTransition::Outward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::Dissolve: { return Okular::PageTransition( Okular::PageTransition::Dissolve ); break; } case Okular::Settings::EnumSlidesTransition::GlitterDown: { Okular::PageTransition transition( Okular::PageTransition::Glitter ); transition.setAngle( 270 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::GlitterRight: { Okular::PageTransition transition( Okular::PageTransition::Glitter ); transition.setAngle( 0 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::GlitterRightDown: { Okular::PageTransition transition( Okular::PageTransition::Glitter ); transition.setAngle( 315 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::Random: { return defaultTransition( KRandom::random() % 18 ); break; } case Okular::Settings::EnumSlidesTransition::SplitHorizontalIn: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Horizontal ); transition.setDirection( Okular::PageTransition::Inward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::SplitHorizontalOut: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Horizontal ); transition.setDirection( Okular::PageTransition::Outward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::SplitVerticalIn: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Vertical ); transition.setDirection( Okular::PageTransition::Inward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::SplitVerticalOut: { Okular::PageTransition transition( Okular::PageTransition::Split ); transition.setAlignment( Okular::PageTransition::Vertical ); transition.setDirection( Okular::PageTransition::Outward ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeDown: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 270 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeRight: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 0 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeLeft: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 180 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::WipeUp: { Okular::PageTransition transition( Okular::PageTransition::Wipe ); transition.setAngle( 90 ); return transition; break; } case Okular::Settings::EnumSlidesTransition::Fade: { return Okular::PageTransition( Okular::PageTransition::Fade ); break; } case Okular::Settings::EnumSlidesTransition::Replace: default: return Okular::PageTransition( Okular::PageTransition::Replace ); break; } // should not happen, just make gcc happy return Okular::PageTransition(); } /** ONLY the TRANSITIONS GENERATION function from here on **/ void PresentationWidget::initTransition( const Okular::PageTransition *transition ) { // if it's just a 'replace' transition, repaint the screen if ( transition->type() == Okular::PageTransition::Replace ) { update(); return; } const bool isInward = transition->direction() == Okular::PageTransition::Inward; const bool isHorizontal = transition->alignment() == Okular::PageTransition::Horizontal; const float totalTime = transition->duration(); m_transitionRects.clear(); m_currentTransition = *transition; m_currentPagePixmap = m_lastRenderedPixmap; switch( transition->type() ) { // split: horizontal / vertical and inward / outward case Okular::PageTransition::Split: { const int steps = isHorizontal ? 100 : 75; if ( isHorizontal ) { if ( isInward ) { int xPosition = 0; for ( int i = 0; i < steps; i++ ) { int xNext = ((i + 1) * m_width) / (2 * steps); m_transitionRects.push_back( QRect( xPosition, 0, xNext - xPosition, m_height ) ); m_transitionRects.push_back( QRect( m_width - xNext, 0, xNext - xPosition, m_height ) ); xPosition = xNext; } } else { int xPosition = m_width / 2; for ( int i = 0; i < steps; i++ ) { int xNext = ((steps - (i + 1)) * m_width) / (2 * steps); m_transitionRects.push_back( QRect( xNext, 0, xPosition - xNext, m_height ) ); m_transitionRects.push_back( QRect( m_width - xPosition, 0, xPosition - xNext, m_height ) ); xPosition = xNext; } } } else { if ( isInward ) { int yPosition = 0; for ( int i = 0; i < steps; i++ ) { int yNext = ((i + 1) * m_height) / (2 * steps); m_transitionRects.push_back( QRect( 0, yPosition, m_width, yNext - yPosition ) ); m_transitionRects.push_back( QRect( 0, m_height - yNext, m_width, yNext - yPosition ) ); yPosition = yNext; } } else { int yPosition = m_height / 2; for ( int i = 0; i < steps; i++ ) { int yNext = ((steps - (i + 1)) * m_height) / (2 * steps); m_transitionRects.push_back( QRect( 0, yNext, m_width, yPosition - yNext ) ); m_transitionRects.push_back( QRect( 0, m_height - yPosition, m_width, yPosition - yNext ) ); yPosition = yNext; } } } m_transitionMul = 2; m_transitionDelay = (int)( (totalTime * 1000) / steps ); } break; // blinds: horizontal(l-to-r) / vertical(t-to-b) case Okular::PageTransition::Blinds: { const int blinds = isHorizontal ? 8 : 6; const int steps = m_width / (4 * blinds); if ( isHorizontal ) { int xPosition[ 8 ]; for ( int b = 0; b < blinds; b++ ) xPosition[ b ] = (b * m_width) / blinds; for ( int i = 0; i < steps; i++ ) { int stepOffset = (int)( ((float)i * (float)m_width) / ((float)blinds * (float)steps) ); for ( int b = 0; b < blinds; b++ ) { m_transitionRects.push_back( QRect( xPosition[ b ], 0, stepOffset, m_height ) ); xPosition[ b ] = stepOffset + (b * m_width) / blinds; } } } else { int yPosition[ 6 ]; for ( int b = 0; b < blinds; b++ ) yPosition[ b ] = (b * m_height) / blinds; for ( int i = 0; i < steps; i++ ) { int stepOffset = (int)( ((float)i * (float)m_height) / ((float)blinds * (float)steps) ); for ( int b = 0; b < blinds; b++ ) { m_transitionRects.push_back( QRect( 0, yPosition[ b ], m_width, stepOffset ) ); yPosition[ b ] = stepOffset + (b * m_height) / blinds; } } } m_transitionMul = blinds; m_transitionDelay = (int)( (totalTime * 1000) / steps ); } break; // box: inward / outward case Okular::PageTransition::Box: { const int steps = m_width / 10; if ( isInward ) { int L = 0, T = 0, R = m_width, B = m_height; for ( int i = 0; i < steps; i++ ) { // compute shrunk box coords int newL = ((i + 1) * m_width) / (2 * steps); int newT = ((i + 1) * m_height) / (2 * steps); int newR = m_width - newL; int newB = m_height - newT; // add left, right, topcenter, bottomcenter rects m_transitionRects.push_back( QRect( L, T, newL - L, B - T ) ); m_transitionRects.push_back( QRect( newR, T, R - newR, B - T ) ); m_transitionRects.push_back( QRect( newL, T, newR - newL, newT - T ) ); m_transitionRects.push_back( QRect( newL, newB, newR - newL, B - newB ) ); L = newL; T = newT; R = newR, B = newB; } } else { int L = m_width / 2, T = m_height / 2, R = L, B = T; for ( int i = 0; i < steps; i++ ) { // compute shrunk box coords int newL = ((steps - (i + 1)) * m_width) / (2 * steps); int newT = ((steps - (i + 1)) * m_height) / (2 * steps); int newR = m_width - newL; int newB = m_height - newT; // add left, right, topcenter, bottomcenter rects m_transitionRects.push_back( QRect( newL, newT, L - newL, newB - newT ) ); m_transitionRects.push_back( QRect( R, newT, newR - R, newB - newT ) ); m_transitionRects.push_back( QRect( L, newT, R - L, T - newT ) ); m_transitionRects.push_back( QRect( L, B, R - L, newB - B ) ); L = newL; T = newT; R = newR, B = newB; } } m_transitionMul = 4; m_transitionDelay = (int)( (totalTime * 1000) / steps ); } break; // wipe: implemented for 4 canonical angles case Okular::PageTransition::Wipe: { const int angle = transition->angle(); const int steps = (angle == 0) || (angle == 180) ? m_width / 8 : m_height / 8; if ( angle == 0 ) { int xPosition = 0; for ( int i = 0; i < steps; i++ ) { int xNext = ((i + 1) * m_width) / steps; m_transitionRects.push_back( QRect( xPosition, 0, xNext - xPosition, m_height ) ); xPosition = xNext; } } else if ( angle == 90 ) { int yPosition = m_height; for ( int i = 0; i < steps; i++ ) { int yNext = ((steps - (i + 1)) * m_height) / steps; m_transitionRects.push_back( QRect( 0, yNext, m_width, yPosition - yNext ) ); yPosition = yNext; } } else if ( angle == 180 ) { int xPosition = m_width; for ( int i = 0; i < steps; i++ ) { int xNext = ((steps - (i + 1)) * m_width) / steps; m_transitionRects.push_back( QRect( xNext, 0, xPosition - xNext, m_height ) ); xPosition = xNext; } } else if ( angle == 270 ) { int yPosition = 0; for ( int i = 0; i < steps; i++ ) { int yNext = ((i + 1) * m_height) / steps; m_transitionRects.push_back( QRect( 0, yPosition, m_width, yNext - yPosition ) ); yPosition = yNext; } } else { update(); return; } m_transitionMul = 1; m_transitionDelay = (int)( (totalTime * 1000) / steps ); } break; // dissolve: replace 'random' rects case Okular::PageTransition::Dissolve: { const int gridXsteps = 50; const int gridYsteps = 38; const int steps = gridXsteps * gridYsteps; int oldX = 0; int oldY = 0; // create a grid of gridXstep by gridYstep QRects for ( int y = 0; y < gridYsteps; y++ ) { int newY = (int)( m_height * ((float)(y+1) / (float)gridYsteps) ); for ( int x = 0; x < gridXsteps; x++ ) { int newX = (int)( m_width * ((float)(x+1) / (float)gridXsteps) ); m_transitionRects.push_back( QRect( oldX, oldY, newX - oldX, newY - oldY ) ); oldX = newX; } oldX = 0; oldY = newY; } // randomize the grid for ( int i = 0; i < steps; i++ ) { #ifndef Q_OS_WIN int n1 = (int)(steps * drand48()); int n2 = (int)(steps * drand48()); #else int n1 = (int)(steps * (std::rand() / RAND_MAX)); int n2 = (int)(steps * (std::rand() / RAND_MAX)); #endif // swap items if index differs if ( n1 != n2 ) { QRect r = m_transitionRects[ n2 ]; m_transitionRects[ n2 ] = m_transitionRects[ n1 ]; m_transitionRects[ n1 ] = r; } } // set global transition parameters m_transitionMul = 40; m_transitionDelay = (int)( (m_transitionMul * 1000 * totalTime) / steps ); } break; // glitter: similar to dissolve but has a direction case Okular::PageTransition::Glitter: { const int gridXsteps = 50; const int gridYsteps = 38; const int steps = gridXsteps * gridYsteps; const int angle = transition->angle(); // generate boxes using a given direction if ( angle == 90 ) { int yPosition = m_height; for ( int i = 0; i < gridYsteps; i++ ) { int yNext = ((gridYsteps - (i + 1)) * m_height) / gridYsteps; int xPosition = 0; for ( int j = 0; j < gridXsteps; j++ ) { int xNext = ((j + 1) * m_width) / gridXsteps; m_transitionRects.push_back( QRect( xPosition, yNext, xNext - xPosition, yPosition - yNext ) ); xPosition = xNext; } yPosition = yNext; } } else if ( angle == 180 ) { int xPosition = m_width; for ( int i = 0; i < gridXsteps; i++ ) { int xNext = ((gridXsteps - (i + 1)) * m_width) / gridXsteps; int yPosition = 0; for ( int j = 0; j < gridYsteps; j++ ) { int yNext = ((j + 1) * m_height) / gridYsteps; m_transitionRects.push_back( QRect( xNext, yPosition, xPosition - xNext, yNext - yPosition ) ); yPosition = yNext; } xPosition = xNext; } } else if ( angle == 270 ) { int yPosition = 0; for ( int i = 0; i < gridYsteps; i++ ) { int yNext = ((i + 1) * m_height) / gridYsteps; int xPosition = 0; for ( int j = 0; j < gridXsteps; j++ ) { int xNext = ((j + 1) * m_width) / gridXsteps; m_transitionRects.push_back( QRect( xPosition, yPosition, xNext - xPosition, yNext - yPosition ) ); xPosition = xNext; } yPosition = yNext; } } else // if angle is 0 or 315 { int xPosition = 0; for ( int i = 0; i < gridXsteps; i++ ) { int xNext = ((i + 1) * m_width) / gridXsteps; int yPosition = 0; for ( int j = 0; j < gridYsteps; j++ ) { int yNext = ((j + 1) * m_height) / gridYsteps; m_transitionRects.push_back( QRect( xPosition, yPosition, xNext - xPosition, yNext - yPosition ) ); yPosition = yNext; } xPosition = xNext; } } // add a 'glitter' (1 over 10 pieces is randomized) int randomSteps = steps / 20; for ( int i = 0; i < randomSteps; i++ ) { #ifndef Q_OS_WIN int n1 = (int)(steps * drand48()); int n2 = (int)(steps * drand48()); #else int n1 = (int)(steps * (std::rand() / RAND_MAX)); int n2 = (int)(steps * (std::rand() / RAND_MAX)); #endif // swap items if index differs if ( n1 != n2 ) { QRect r = m_transitionRects[ n2 ]; m_transitionRects[ n2 ] = m_transitionRects[ n1 ]; m_transitionRects[ n1 ] = r; } } // set global transition parameters m_transitionMul = (angle == 90) || (angle == 270) ? gridYsteps : gridXsteps; m_transitionMul /= 2; m_transitionDelay = (int)( (m_transitionMul * 1000 * totalTime) / steps ); } break; case Okular::PageTransition::Fade: { enum {FADE_TRANSITION_FPS = 20}; const int steps = totalTime * FADE_TRANSITION_FPS; m_transitionSteps = steps; QPainter pixmapPainter; m_currentPixmapOpacity = (double) 1 / steps; m_transitionDelay = (int)( totalTime * 1000 ) / steps; m_lastRenderedPixmap = QPixmap( m_lastRenderedPixmap.size() ); m_lastRenderedPixmap.fill( Qt::transparent ); pixmapPainter.begin( &m_lastRenderedPixmap ); pixmapPainter.setCompositionMode( QPainter::CompositionMode_Source ); pixmapPainter.setOpacity( 1 - m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_previousPagePixmap ); pixmapPainter.setOpacity( m_currentPixmapOpacity ); pixmapPainter.drawPixmap( 0, 0, m_currentPagePixmap ); pixmapPainter.end(); update(); } break; // implement missing transitions (a binary raster engine needed here) case Okular::PageTransition::Fly: case Okular::PageTransition::Push: case Okular::PageTransition::Cover: case Okular::PageTransition::Uncover: default: update(); return; } // send the first start to the timer m_transitionTimer->start( 0 ); } void PresentationWidget::slotProcessMovieAction( const Okular::MovieAction *action ) { const Okular::MovieAnnotation *movieAnnotation = action->annotation(); if ( !movieAnnotation ) return; Okular::Movie *movie = movieAnnotation->movie(); if ( !movie ) return; VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( movieAnnotation->movie() ); if ( !vw ) return; vw->show(); switch ( action->operation() ) { case Okular::MovieAction::Play: vw->stop(); vw->play(); break; case Okular::MovieAction::Stop: vw->stop(); break; case Okular::MovieAction::Pause: vw->pause(); break; case Okular::MovieAction::Resume: vw->play(); break; }; } void PresentationWidget::slotProcessRenditionAction( const Okular::RenditionAction *action ) { Okular::Movie *movie = action->movie(); if ( !movie ) return; VideoWidget *vw = m_frames[ m_frameIndex ]->videoWidgets.value( movie ); if ( !vw ) return; if ( action->operation() == Okular::RenditionAction::None ) return; vw->show(); switch ( action->operation() ) { case Okular::RenditionAction::Play: vw->stop(); vw->play(); break; case Okular::RenditionAction::Stop: vw->stop(); break; case Okular::RenditionAction::Pause: vw->pause(); break; case Okular::RenditionAction::Resume: vw->play(); break; default: return; }; } void PresentationWidget::slotTogglePlayPause() { m_advanceSlides = !m_advanceSlides; setPlayPauseIcon(); if ( m_advanceSlides ) { startAutoChangeTimer(); } else { m_nextPageTimer->stop(); } } #include "presentationwidget.moc" diff --git a/ui/thumbnaillist.h b/ui/thumbnaillist.h index f80e88149..2d2b66335 100644 --- a/ui/thumbnaillist.h +++ b/ui/thumbnaillist.h @@ -1,103 +1,103 @@ /*************************************************************************** * Copyright (C) 2004 by Albert Astals Cid * * * * 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. * ***************************************************************************/ #ifndef _OKULAR_THUMBNAILLIST_H_ #define _OKULAR_THUMBNAILLIST_H_ #include #include #include #include "core/observer.h" class ThumbnailListPrivate; namespace Okular { class Document; } /** * @short A scrollview that displays page pixmap previews (aka thumbnails). * * ... */ class ThumbnailList : public QScrollArea, public Okular::DocumentObserver { Q_OBJECT public: ThumbnailList(QWidget *parent, Okular::Document *document); ~ThumbnailList(); // inherited: create thumbnails ( inherited as a DocumentObserver ) void notifySetup( const QVector< Okular::Page * > & pages, int setupFlags ) override; // inherited: hilihght current thumbnail ( inherited as DocumentObserver ) void notifyCurrentPageChanged( int previous, int current ) override; // inherited: redraw thumbnail ( inherited as DocumentObserver ) void notifyPageChanged( int pageNumber, int changedFlags ) override; // inherited: request all visible pixmap (due to a global change or so..) void notifyContentsCleared( int changedFlags ) override; // inherited: the visible areas of the page have changed void notifyVisibleRectsChanged() override; // inherited: tell if pixmap is hidden and can be unloaded bool canUnloadPixmap( int pageNumber ) const override; // redraw visible widgets (useful for refreshing contents...) void updateWidgets(); public Q_SLOTS: // these are connected to ThumbnailController buttons void slotFilterBookmarks( bool filterOn ); protected: // scroll up/down the view void keyPressEvent( QKeyEvent * e ) override; // catch the viewport event and filter them if necessary bool viewportEvent( QEvent * ) override; Q_SIGNALS: void rightClick( const Okular::Page *, const QPoint & ); private: friend class ThumbnailListPrivate; ThumbnailListPrivate *d; Q_PRIVATE_SLOT( d, void slotRequestVisiblePixmaps( int newContentsY = -1 ) ) Q_PRIVATE_SLOT( d, void slotDelayTimeout() ) }; /** * @short A vertical boxed container with zero size hint (for insertion on left toolbox) */ class ThumbnailsBox : public QWidget { Q_OBJECT public: - explicit ThumbnailsBox( QWidget * parent ) : QWidget( parent ) { QVBoxLayout *vbox = new QVBoxLayout(this); vbox->setMargin(0); vbox->setSpacing(0);} + explicit ThumbnailsBox( QWidget * parent ) : QWidget( parent ) { QVBoxLayout *vbox = new QVBoxLayout(this); vbox->setContentsMargins(0, 0, 0, 0); vbox->setSpacing(0);} QSize sizeHint() const override { return QSize(); } }; /** * @short A toolbar that sets ThumbnailList properties when clicking on items * * This class is the small toolbar that resides in the bottom of the * ThumbnailsBox container (below ThumbnailList and the SearchLine) and * emits signals whenever a button is pressed. A click action results * in invoking some method (or slot) in ThumbnailList. */ class ThumbnailController : public QToolBar { Q_OBJECT public: ThumbnailController( QWidget * parent, ThumbnailList * thumbnailList ); }; #endif